嵌入式系统——Makefile基础知识

一、什么是Makefile

  Makefile是项目工程中的编译规则文件,定义了哪些文件先行编译,哪些文件需要后编译,以及文件的链接顺序等编译规则。make是UNIX和Linux系统下执行Makefile文件的命令,源文件到可执行文件的过程称为编译,编译顺序的安排称为构建。
make与Makefile的关系
  make是一个命令工具,用来解释Makefile中的指令。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。
cmake是什么
  你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MSnmake,BSD Make(pmake),Makepp等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
  CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等。

  1. 在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下: 编写 CMake 配置文件CMakeLists.txt ;
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile。其中, PATH是 CMakeLists.txt 所在的目录。(ccmake 和 cmake 的区别在于前者提供了一个交互式的界面) ;
  3. 使用 make命令进行编译

二、Makefile基本语法

Makefile基本语法:
目标:依赖
Tab 命令  #注意:一定是要“Tab键”,空格不行
  • 目标:一般指的是待编译的目标,也可以是一个动作
  • 依赖:执行当前目标所要依赖的前提条件,包括其他目标、某个具体文件或库(一个目标可以有多个依赖)
  • 命令:达到该目标需要执行的具体命令,可以为空;也可有多条,多条时,每条命令单独一行

make常用选项

  首先,了解一下make命令的基本用法

make [-f file] [options] [target]
执行make命令,默认在当前目录中寻找GUNmakefile、makefile文件,作为make命令的输入文件
 - -f 可指定上述文件名之外的文件作为输入文件
 - -v 显示版本号
 - -n 只输出命令,但并不执行,一般用于调试或测试
 - -s 只执行命令,但不显示具体命令,此处可在命令中用@符号抑制命令输出
 - -w 显示执行前、后的路径
 - -C [dir] 指定makefile的目录
 
 没有指定目标时,默认使用第一目标
 如果指定,则执行对应的命令
常见的make命令
make 按默认配置編译
make -j8           8个进程同时编译,缩短总的时间
make world         编译所有能够编译的(postgresql 经常同时編译文档及附加模块(contrib)
make check         回归测试,用于安装完成后测试功能完整性,不能以 root 身份运行

make install       安装程序
make install-docs  安装文档(info、man手册)
make install-world 安装所有可安装的

make uninstall     删除安装的文件(生成的目录无法删除)

make clean         清除 make 生成的文件,但保留 configure 生成的文件
make distclean     将源码恢复为原始状态,即同时删除 make 与 configure 阶段生成的文件

make -C 指定源文件目录,仅安装程序的特定部分;
        如只安装客户端应用和接口:
		make -C src/bin install; 
		make -C src/include install; 
		make -C src/interfaces install; 
		make -C doc install

三、Makefile变量的引用与赋值

3.1 自定义变量

  Makefile文件中的几种赋值与引用方式:=、:= 、?=、+=,赋值操作一般是针对变量的,Makefile中的变量可以任意命名,原则上尽量见名知意。
(1)= 递归式变量赋值,左侧为变量名,右侧为变量的值,优点是变量可以重复多次赋值

xxx@XXX:~/test$ cat Makefile
var1=1
var2=$(var1)
var2=2
print:
	echo $(var2)

xxx@XXX:~$ make
echo 2
2

(2):= 展开式变量赋值,定义变量时,变量右侧的值立即替换到变量值中,前面的变量不能使用后面的变量,即变量一旦赋值,即使后面再次赋值,也不会覆盖前面定义的变量值

xxx@XXX:~/test$ cat Makefile
var1=012
var2:=$(var1)
var1=345
print:
	echo $(var2)

xxx@XXX:~/test$ make
echo 012
012

(3)?= 条件赋值,用于判断左边的变量是否被赋值过,如果是,不会执行此次赋值操作;如果不是,则给该变量赋值:

xxx@XXX:~/test$ cat Makefile 
var1=012
var2=$(var1)
var2 ?=345
print:
	echo $(var2)
	
xxx@XXX:~/test$ make
echo 012
012

(4)+= 一般用于新增目标文件编译操作,例如:

#新增目标前
objs=main.o add.o
main=$(objs)
#此时需要新增一个sub.o文件
objs=main.o add.o 
objs+=sub.o 
main=$(objs)

3.2 系统变量(自动变量)

  常见的自动变量类型

自动变量名 解释说明
$@ 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也称为静态的库文件),那么它代表这个文档的文件名;在多目标模式规则中,它代表的是触发规则被执行的文件名
$% 当目标文件是一个静态库文件时,代表静态库的一个成员名;如果目标文件不是静态库文件,其值为空
$< 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。
$? 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。
$^ 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。一个文件可重复的出现在目标的依赖中变量“ ” 只 记 录 它 的 第 一 次 引 用 的 情 况 。 就 是 说 变 量 “ ^”只记录它的第一次引用的情况。就是说变量“ ^”会去掉重复的依赖文件。
$+ 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。
$* 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录部分)。

3.3 系统常量(内置变量)

CURDIR := /home/zht # 记录当前路径
SHELL = /bin/sh
MAKEFILE_LIST :=  Makefile
.DEFAULT_GOAL := all
MAKEFLAGS = p
HOSTARCH := x86_64
CC = cc 		# C语言编译器的名称
CPP = $(CC) -E  # C语言预处理器的名称 $(CC) -E
CXX = g++       # C++语言的编译器名称
RM = rm -f		# 删除文件程序的名称
CFLAGS			# C语言编译器的编译选项,无默认值
CPPFLAGS  		# C语言预处理器的编译选项,无默认值
CXXFLAGS		# C++语言编译器的编译选项,无默认值
......

四、Makefile模式规则

模式规则

  模式规则类似于普通规则,只是在模式规则中,目标名中需要包含有模式字符“%”(一个),包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。规则的依赖文件中同样可以使用“%”,依赖文件中模式字符“%”的取值情况由目标中的“%”来决定。
  例如:对于模式规则“%.o : %.c”,它表示的含义是:所有的.o文件依赖于对应的.c文件。我们可以使用模式规则来定义隐含规则。要注意的是:模式字符“%”的匹配和替换发生在规则中所有变量和函数引用展开之后,变量和函数的展开一般发生在make读取Makefile时,而模式规则中的“%”的匹配和替换则发生在make执行时。

Makefile伪目标

  Makefile中存在一种伪目标,我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标) ,调用相应的规则,来清除许多编译的文件(如:*.o文件)因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
  当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:
.PHONY: clean
clean:
rm *.o temp
  伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。示例,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

  all : prog1 prog2 prog3
    .PHONY : all

    prog1 : prog1.o utils.o
            cc -o prog1 prog1.o utils.o

    prog2 : prog2.o
            cc -o prog2 prog2.o

    prog3 : prog3.o sort.o utils.o
            cc -o prog3 prog3.o sort.o utils.o 

五、编译动态链接库

动态链接库:在编译链接时,没有把库文件的代码加入到可执行文件中,在程序执行时由链接文件加载库文件;
Windows静态库:xxx.lib Windows动态库:xxx.dll
linux静态库:libxxx.a linux动态库:libxxx.so
参数:
-fPIC 产生位置无关的代码
-shared 共享
-l(小写L) 指定动态库
-I(大写i) 指定头文件目录,默认当前目录
-L 手动指定库文件搜索目录,默认只链接共享目录

代码准备

├── dynamic_lib.c
└── dynamic_lib.h
dynamic_lib.c文件:

#include "dynamic_lib.h"
int add(int a, int b)
{
    
    
	int c;
	c = a + b;
	return c ;
}

dynamic_lib.h文件:

#pragma onece
int add(int a, int b);
编译

生成动态库文件:

gcc -fPIC -shared dynamic_lib.c -o libdynamic_lib.so
调用动态库文件

编写一个新代码调用动态库:

#include "dynamic_lib.h"
int main()
{
    
    
    int a = 0, b = 2, c;
    c = add(a, b);
    return 0;
}

编译调用:gcc lib_test.c -o lib_test -L ../dynamic_lib/ -ldynamic_lib -I ../dynamic_lib/
注意:指定动态库时要去掉头尾

六、编译静态链接库

代码准备

├── static_lib.c
└── static_lib.h
static_lib.c代码:

#include "static_lib.h"
int add(int a, int b)
{
    
    
	int c;
	c = a + b;
	return c ;
}

static_lib.h代码:

#pragma onece
int add(int a, int b);
编译

编译过程分两步,先生成.o文件,然后根据.o文件归档为.a静态库文件
gcc -c static_lib.c
然后归档为静态库文件,这样就生成了静态库libstatic_lib.a
ar crv libstatic_lib.a static_lib.o
ar命令的用法参考:跳转地址

调用静态链接库

测试代码lib_test.c:

#include "static_lib.h"
int main()
{
    
    
    int a = 0, b = 2, c;
    c = add(a, b);
    return 0;
}

编译:gcc lib_test.c -o lib_test -L ../static_lib/ -lstatic_lib -I ../static_lib/

猜你喜欢

转载自blog.csdn.net/weixin_43229184/article/details/127536496