目录
更详细的Makefile学习可以参照《跟我一起学Makefile》文档。
含有交叉编译、pthread_create()处理
摘自:使用makefile编译含有pthread_create()函数时报错:对‘pthread_create’未定义的引用
交叉编译的话直接将Makefile里面的gcc替换成arm-linux-gnueabihf-gcc即可。
在linux应用程序中使用了多线程编程,但是makefile编译却报如下错误:
/tmp/cc5i6uH7.o:在函数‘main’中:
tcpSever.c:(.text+0x62):对‘modRegInit’未定义的引用
tcpSever.c:(.text+0x76):对‘ryS’未定义的引用
tcpSever.c:(.text+0xc0):对‘ryS’未定义的引用
tcpSever.c:(.text+0x10a):对‘ryS’未定义的引用
tcpSever.c:(.text+0x14e):对‘ryS’未定义的引用
collect2: error: ld returned 1 exit status
网上查找解决方案,发现修改makefile即可。如下
test: rySys.o tcpSever.o
gcc -o test rySys.o tcpSever.o -lpthread
rySys.o: rySys.c rySys.h
gcc -c rySys.c -lpthread
tcpSever.o: tcpSever.c rySys.c rySys.h
gcc -c tcpSever.c -lpthread
clean:
rm *.o
rm test
初次使用
当文件有几十、上百甚至上万个的时候用终端输入 gcc 命令的方法显然是不现实的,为此提出了一个解决大工程编译的工具:make,描述哪些文件需要编译、哪些需要 重新编译 的文件就叫做 Makefile。使用的时候只需要一个 make 命令即可完成整个工程的自动编译,极大的提高了软件开发的效率。
使用make工具可以自动完成编译工作,这些工作包括:
- 如果修改了某几个源文件,则只重新编译这几个源文件(原理是make会检查文件的修改日期);
- 如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。
1、编写Makefile文件
在工程目录下创建Makefile文件:
Makefile 里面是由一系列的规则组成的,这些规则格式如下:
目标…... : 依赖文件集合……
命令1
命令2
……
Makefile 和 .c 文件是处于同一个目录的,在Makefile文件中输入如下代码:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
main.o: main.c
gcc -c main.c
fun1.o: fun1.c
gcc -c fun1.c
fun2.o: fun2.c
gcc -c fun2.c
clean:
rm *.o
rm main
上述代码中所有行首需要空出来的地方一定要使用 “TAB” 键!不要使用空格键!这是 Makefile 的语法要求
2、make
Makefile 编写好以后我们就可以使用make命令来编译我们的工程了,直接在命令行中输入make即可,make 命令会在当前目录下查找是否存在 “Makefile” 这个文件,如果存在的话就会按照 Makefile 里面定义的编译方式进行编译:
make
此时我们修改一下 fun1.c 的文件源码,再make:
可以看出因为我们修改了fun1.c这个文件,所以fun1.c和最后的可执行文件main重新编译了,其它没有修改过的文件就没有编译,直接链接.o文件即可,而且我们只需要输入make命令即可,非常方便。
3、make clean
make clean
Makefile基本语法
1、Makefile规则格式
目标...: 依赖文件集合...
命令1
命令2
......
!!命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
- 比如:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
这条规则的目标是main,main.o、fun1.o、fun2.o是生成main的依赖文件,如果要更新目标main,就必须先更新它的所有依赖文件,如果依赖文件中的任何一个有更新(更改时间),那么目标也必须更新,“更新”就是执行一遍规则中的命令列表。make命令会为Makefile中的每个以 TAB 开始的命令创建一个 Shell 进程去执行。
- 示例:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
main.o: main.c
gcc -c main.c
fun1.o: fun1.c
gcc -c fun1.c
fun2.o: fun2.c
gcc -c fun2.c
clean:
rm *.o
rm main
make命令在执行这个Makefile的时候其执行步骤如下:
首先更新第一条规则中的main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为Makefile的工作完成。在第一次编译的时候由于main还不存在,因此第一条规则会执行,第一条规则依赖于文件main.o、fun1.o和fun2.o这个三个.o文件,这三个.o文件目前还都没有,因此必须先更新这三个文件。make 会查找以这三个.o文件为目标的规则并执行。
以main.o为例,发现更新main.o的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc –c main.c”,这行命令就是不链接编译main.c,生成main.o。
最后一个规则目标是clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行clean的话可以直接使用命令make clean,执行以后就会删除当前目录下所有的.o文件以及main,因此clean的功能就是完成工程的清理工作。
- 总结make执行过程:
① make命令会在当前目录下查找以 Makefile(或makefile) 命名的文件。
② 当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
③ 当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标。
!!注意: 除了 Makefile 的“终极目标”所在的规则以外,其它规则的顺序在 Makefile中是没有意义的,“终极目标”就是指在使用 make 命令的时候没有指定具体的目标时,make 默认的那个目标,它是 Makefile 文件中第一条规则的目标,如果 Makefile 中的第一个规则有多个目标,那么这些目标中的第一个目标就是 make 的“终极目标”。
2、变量
跟 C 语言一样 Makefile 也是支持变量的,如前面的例子:
main: main.o fun1.o fun2.o
gcc -o main main.o fun1.o fun2.o
上述 Makefile 语句中,main.o、fun1.o、fun2.o 这三个依赖文件,我们输入了两遍,我们这个 Makefile 比较小,如果 Makefile 复杂的时候这种重复输入的工作就会非常费时间,Makefile 加入了变量支持,类似 C 语言中的宏。使用变量将上面的代码修改:
# Makefile 变量的使用
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
Makefile 的注释用#。
我们定义了一个变量objects,并且给这个变量进行了赋值,其值为字符串main.o fun1.o fun2.o,Makefile 中变量的引用方法是 $(变量名),如本例中: $(objects)。
我们在定义变量objects 的时候使用“=”对其进行了赋值,Makefile
变量的赋值符还有其它两个“:=”和“?=”,我们来看一下这三种赋值符的区别:
3、变量赋值符(=、:=、?=、+=)
使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,比如如下代码:
① 赋值符=:
name = lcx
curname = $(name)
name = licx
print:
@echo curname: $(curname)
第一行定义了一个变量name,值为lcx,第二行定义了变量curname,值也为lcx,第三行变量name的值改为licx,第五、六行是输出变量curname的值。在 Makefile 要输出一串字符的话使用echo。(第六行中的echo前面加了个 @符号,因为 Make 在执行的过程中会自动输出命令执行过程,在命令前加上@就不会输出命令执行过程)执行结果:
可以看出,curname的值不是lcx,而是变量name最后一次赋值的结果。
② 赋值符:=:
name = lcx
curname := $(name)
name = licx
print:
@echo curname: $(curname)
执行结果:
显然,此时curname值为lcx,因为赋值符:=只能使用前面定义的值。
③ 赋值符?=:
curname ?= licx
上述代码的意思是,如果变量curname前面没有被赋值,那么此变量就是licx,如果前面已经赋过值了,那么就使用前面赋的值。
④ 变量追加符+=:
Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串,此时就要使用变量追加符号+=,如:
objects = main.o fun1.o
objects += fun2.o
print:
@echo objects: $(objects)
可以看出,objects最后的值为main.o fun1.o fun2.o。
4、模式规则(通配符)
如下示例代码:
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
main.o: main.c
gcc -c main.c
fun1.o: fun1.c
gcc -c fun1.c
fun2.o: fun2.c
gcc -c fun2.c
clean:
rm *.o
rm main
显然,如果工程中.c文件很多,这样做就很不方便,我们可以使用 Makefile 中的模式规则,使用一条规则来将所有的.c文件编译为对应的.o文件。
模式规则中,至少在规则的目标定义中要包含%,否则就是一般规则,目标中的%表示对文件名的匹配,%表示长度任意的非空字符串,比如%.c就是所有的以.c结尾的文件,类似于通配符。
示例可以改成如下形式:
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
%.o: %.c
#命令
clean:
rm *.o
rm main
第六行的命令我们需要借助下面的自动化变量 ↓↓↓
5、自动化变量
上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,那么如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是完成这个功能的!
自动化变量就是这种变量会把模中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中,常用的自动化变量如下:
常用的三种: $@、 $< 和 $^
那么上述代码我们可以完善:
objects = main.o fun1.o fun2.o
main: $(objects)
gcc -o main $(objects)
%.o: %.c
gcc -c $<
clean:
rm *.o
rm main
6、伪目标
伪目标的主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如文章示例有如下代码用来完成清理工程的功能:
clean:
rm *.o
rm main
上述规则中并没有创建clean文件的命令,因此工作目录下永远都不会存在文件clean,当我们输入make clean以后,后面的rm *.o和rm main总是会执行。如果我们在工作目录下创建一个名为clean(重名)的文件,当执行make clean的时候,规则因为没有依赖文件,所以目标被认为是最新的 (刚创建了一个clean文件,是最新的),因此后面的rm命令也就不会执行,如图所示:
为了避免这个问题,我们可以将clean声明为伪目标,声明方式如下:
.PHONY:clean
那么我们可以将示例代码修改为:
.PHONY : clean
clean:
rm *.o
rm main
7、条件判断
在 C 语言中我们通过条件判断语句来根据不同的情况来执行不同的分支,Makefile 也支持条件判断,语法有两种如下:
<条件关键字>
<条件为真时执行的语句>
endif
以及:
<条件关键字>
<条件为真时执行的语句>
else
<条件为假时执行的语句>
endif
其中条件关键字有 4 个:ifeq、ifneq、ifdef 和 ifndef,这四个关键字其实分为两对、ifeq 与ifneq、ifdef 与 ifndef,先来看一下 ifeq 和 ifneq,ifeq 用来判断是否相等,ifneq 就是判断是否不相等,ifeq 用法如下:
ifeq (<参数 1>, <参数 2>)
ifeq ‘<参数 1 >’,‘ <参数 2>’
ifeq “<参数 1>”, “<参数 2>”
ifeq “<参数 1>”, ‘<参数 2>’
ifeq ‘<参数 1>’, “<参数 2>”
上述用法中都是用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和“参数 2”可以为函数返回值。ifneq 的用法类似,只不过 ifneq 是用来了比较“参数 1”和“参数 2”是否不相等,如果不相等的话就为真。
ifdef 和 ifndef 的用法如下:
ifdef <变量名>
如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是一个函数的返回值。ifndef 用法类似,但是含义用户 ifdef 相反。
8、函数使用
Makefile 支持函数,类似 C 语言一样,Makefile 中的函数是已经定义好的,我们直接使用,不支持我们自定义函数。make 所支持的函数不多,函数的用法如下:
$(函数名 参数集合)
或者:
${函数名 参数集合}
可以看出,调用函数和调用普通变量一样,使用符号 “$ ” 来标识。参数集合是函数的多个参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以“$”开头。
接下来我们介绍几个常用的函数,来保证后面的学习。其它的函数大家可以参考《跟我一起写 Makefile》这份文档。
1、函数 subst
函数 subst 用来完成字符串替换,调用形式如下:
$(subst <from>,<to>,<text>)
此函数的功能是将字符串< text>中的内容替换为< to>,函数返回被替换以后的字符串,比如如下示例:
$(subst zzk,ZZK,my name is zzk
把字符串“my name is zzk”中的“zzk”替换为“ZZK”,替换完成以后的字符串为“my name is ZZK”。
2、函数 patsubst
函数 patsubst 用来完成模式字符串替换,使用方法如下:
$(patsubst <pattern>,<replacement>,<text>)
此函数查找字符串< text>中的单词是否符合模式< pattern>,如果匹配就用< replacement>来替换掉,< pattern>可以使用通配符“%”,表示任意长度的字符串,函数返回值就是替换后的字符串。如果< replacement>中也包涵“%”,那么< replacement>中的“%”将是< pattern>中的那个“%”所代表的字符串,比如:
$(patsubst %.c,%.o,a.c b.c c.c)
将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字符串为“a.o b.o c.o”。
3、函数 dir
函数 dir 用来获取目录,使用方法如下:
$(dir <names…>)
此函数用来从文件名序列< names>中提取出目录部分,返回值是文件名序列< names>的目录部分,比如:
$(dir </src/a.c>)
提取文件“/src/a.c”的目录部分,也就是“/src”。
4、函数 notdir
函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名,用法如下:
$(notdir <names…>)
此函数用与从文件名序列< names>中提取出文件名非目录部分,比如:
$(notdir </src/a.c>)
提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。
5、函数 foreach
foreach 函数用来完成循环,用法如下:
$(foreach <var>, <list>,<text>)
此函数的意思就是把参数中的单词逐一取出来放到参数 < var> 中, 然后再执行 < text > 所包含的表达式。每次 < text > 都会返回一个字符串,循环的过程中,< text > 中所包含的每个字符串会以空格隔开,最后当整个循环结束时,< text > 所返回的每个字符串所组成的整个字符串将会是函数 foreach 函数的返回值。
6、函数 wildcard
通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,通配符不会自动展开,这个时候就要用到函数 wildcard,使用方法如下:
$(wildcard PATTERN…)
比如:
$(wildcard *.c)
上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。