Makefile学习笔记

96
crazybaoli
2018.01.25 10:34 字数 4301

Makefile学习笔记

学习Makefile的资料

  1. 《跟我一起写makefile》
  2. 《GUN make manual》
  3. 《GNU Make项目管理(第三版)》

说明:
《跟我一起写makefile》可以被认为是《GUN make manual》的简化版或者说是学习笔记,适合入门学习。
《GUN make manual》可以当做参考手册,平时查阅。
《GNU Make项目管理(第三版)》用于进阶提升。

1. gcc编译

Makefile本身并不能编译代码,它需要结合gcc 命令才能实现编译的功能。

1.1 编译和链接

编译

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常需要告诉编译器头文件的所在位置(头文件中应该只是声明,定义应该放在 C/C++ 文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(.o 文件或 .obj 文件)。

链接

链接时,主要是链接函数和全局变量。所以,我们可以使用这些中间目标文件(.o 文件或 .obj 文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在 Windows 下这种包叫“库文件”(Library File),也就是 .lib文件,在 UNIX 下,是 Archive File,也就是 .a 文件。

总结

编译时,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成 Object File。而在链接程序时,链接器会在所有的 Object File 中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error)。

1.2 gcc编译选项

对于编译源码,Makefile中会用到大量的gcc选项,所以熟悉gcc编译选项对于学习Makefile至关重要。

  1. -c::只激活预处理,编译,和汇编,也就生成obj文件
  2. -S:只激活预处理和编译,就是指把文档编译成为汇编代码。
  3. -E:只激活预处理,不生成文档,需要把他重定向到一个输出文档里。
  4. -o:定制目标名称,缺省的时候gcc 编译出来的文档是a.out
  5. -ansi:关闭gnu c中和ansi c不兼容的特性,激活ansi c的专有特性。
  6. -Dmacro:相当于C语言中的#define macro
  7. -Dmacro=defn:相当于C语言中的#define macro=defn
  8. -Umacro :相当于C语言中的#undef macro
  9. -Idir:指定头文件路径。
  10. -llibrary:指定库
  11. -Ldir:定制编译的时候,搜索库的路径。
  12. -g:指示编译器,在编译的时候,产生调试信息。
  13. -static:此选项将禁止使用动态库,所以,编译出来的东西,一般都很大。
  14. -share:此选项将尽量使用动态库,所以生成文档比较小,但是需要系统由动态库。
  15. -O0 -O1 -O2 -O3:编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
  16. -Wall:会打开一些很有用的警告选项,建议编译时加此选项。
  17. -std:指定C标准,如-std=c99使用c99标准,-std=gnu99,使用C99 再加上 GNU 的一些扩展。

2. Makefile规则

2.1 书写规则

Makefile一般规则

target ... : prerequisites ...
    command
  1. make 会比较 targets文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比 targets 文件的日期要新,或者 target 不存在的话,那么 make 就会执行后续定义的command命令。
  2. makefile中,目标的执行语句(command)只能使用tab起始,不能以四个空格代替。如all、clean的下一行。
  3. 伪目标:当一个伪目标有依赖目标时,不但会执行规则(命令),依赖目标也总是会被决议(执行)。

静态模式规则

静态模式规则是这样一个规则:规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。静态模式规则比多目标规则更通用,它不需要多个目标具有相同的依赖。但是静态模式规则中的依赖文件必须是相类似的而不是完全相同的。
规则:

<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>

例子:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@

“目标模式”或是“依赖模式”中都应该有 % 。

模式规则(又称隐含模式规则)

你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有 % 字符。 % 的意思是表示一个或多个任意字符。在依赖目标中同样可以使用 % ,只是依赖目标中的 % 的取值,取决于其目标。
模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。

%.o : %.c
    <command ......>

将所有的c文件编译成目标文件。

%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

静态模式规则和隐含模式规则

Makefile 中,静态模式规则和被定义为隐含规则的模式规则都是我们经常使用的两种方式。两者相同的地方都是用目标模式和依赖模式来构建规则中的文件依赖关系,两者不同的地方是 make 在执行时使用它们的时机。
隐含规则可被用在任何和它相匹配的目标上,相反的,静态模式规则只能用在规则中明确指出的那些文件的重建过程中,不能用在除此之外的任何文件的重建过程中。

2.2 命令

@的作用

通常, make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们在命令行前用 @ 字符,那么,这个命令将不被 make 显示出来。

递归执行make

进入指定子目录,并执行子目录的Makefile文件。

SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
    $(MAKE) -C $@ all
for dir in $(SUBDIRS); 
do $(MAKE) -C $$dir all ;
done

推荐使用第一种,伪目标方式。
递归make clean:

@for dir in ${SUBDIRS}; do \
    ${MAKE} -C $$dir clean; \
done

2.3 变量

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。如果你要使用真实的$ 字符,那么你需要用 $$ 来表示。

赋值

= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值

=和:=的区别

1)=

make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:

x = foo
y = $(x) bar
x = xyz

在上例中,y的值将会是 xyz bar ,而不是 foo bar 。

2):=

“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。

x := foo
y := $(x) bar
x := xyz

在上例中,y的值将会是 foo bar ,而不是 xyz bar 。

override 指示符

如果通过make 的命令行参数对某个变量进行了设置,那么 Makefile 中对这个变量的赋值会被忽略。如果你想在 Makefile 中设置这类参数的值,那么,你可以使用“override”指示符。

override <variable>; = <value>;
override <variable>; := <value>;
override <variable>; += <more text>;

环境变量

make 运行时的系统环境变量可以在 make 开始运行时被载入到 Makefile 文件中,但是如果Makefile 中已定义了这个变量,或是这个变量由 make 命令行带入,那么系统的环境变量的值将被覆盖。(如果 make 指定了“-e”参数,那么,系统环境变量将覆盖 Makefile 中定义的变量)。
因此,如果我们在环境变量中设置了 CFLAGS 环境变量,那么我们就可以在所有的 Makefile 中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果 Makefile 中定义了 CFLAGS,那么则会使用 Makefile 中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。
当 make 嵌套调用时(参见前面的“嵌套调用”章节),上层 Makefile 中定义的变量会以系统环境变量的方式传递到下层的 Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层 Makefile 传递,则需要使用 exprot 关键字来声明。

内置变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的 makefile 中改变这些变量的值,或是在 make 的命令行中传入这些值,或是在你的环境变量中设置这些值。

关于命令的变量

• AR : 函数库打包程序。默认命令是 ar
• AS : 汇编语言编译程序。默认命令是 as
• CC : C 语言编译程序。默认命令是 cc
• CXX : C++ 语言编译程序。默认命令是 g++

• CPP : C 程序的预处理器(输出是标准输出设备)。默认命令是 $(CC) –E
• RM : 删除文件命令。默认命令是 rm –f

关于命令参数的变量

• ARFLAGS : 函数库打包程序 AR 命令的参数,默认值是 rv。
• ASFLAGS : 汇编语言编译器参数。(当明显地调用 .s 或 .S 文件时)
• CFLAGS : C 语言编译器参数。
• CXXFLAGS : C++ 语言编译器参数。
• CPPFLAGS : C 预处理器参数。(C 和 Fortran 编译器也会用到)
• LDFLAGS : 链接器参数。(如: ld )

自动化变量

所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

  • $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。
  • $% : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o),那么, $% 就是 bar.o , $@ 就是 foo.a 。如果目标不是函数库文件,那么其值为空。
  • $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $? : 所有比目标新的依赖目标的集合。以空格分隔。
  • $^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
  • $+ : 这个变量很像\ $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $* : 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么, $* 的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是 make 所识别的,那么 $* 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是 make 所能识别的后缀名,所以, $* 的值就是 foo 。这个特性是 GNU make 的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make 所不能识别的,那么 $* 就是空值。

2.4 Make的运行

常用目标

• all: 作为 Makefile 的顶层目标,一般此目标作为默认的终极目标。
• clean: 这个伪目标功能是删除所有被 make 创建的文件。
• distclean: 同样类似于伪目标“ clean”,但它们所定义的删除命令所删除的文件更多,可以包含非 make 创建的文件。例如:编译之前系统的配置文件、链接文件等。
• install: 这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
• print: 这个伪目标的功能是例出改变过的源文件。
• tar: 源程序打包备份,也就是一个 tar 文件。
• dist: 为源文件创建发布的压缩包,可以使各种压缩方式的发布包。
• TAGS: 创建当前目录下所有源文件的符号信息(“ tags”)文件,这个文件可被 vim 使用。
• check 和 test: 这两个伪目标一般用来测试 makefile 的流程。

make命令行选项

-f=file, --file=file, --makefile=file 指定需要执行的 makefile

替换变量定义

执行make时,一个含有“ =”的命令行参数“ V=X”的含义是定义变量“ V”的值为“ X”,并将这个变量作为make的参数。这种方式定义的变量会替代Makefile中的同名变量定义(如果存在,并且在Makefile中没有使用指示符“ override” 对这个变量进行说明),这个过程被称之命令行参数定义覆盖普通变量定义。

make CFLAGS=-g
make CFLAGS='-g –O2'

注:在参数中如果包含空格或者shell的特殊字符,则需要将参数放在引号中

3. so库的链接

3.1 运行时动态库的链接

在执行可执行文件时,提示:

error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

解决:有三种方法

1)将 libprint.so放入系统库或用户库目录下:

sudo cp  libprint.so /usr/local/lib
sudo ldconfig

2)在makefile中添加: -Wl,-rpath

LDFLAGS = -lm -lprint -L$(TOP_PATH)/lib -Wl,-rpath=$(TOP_PATH)/lib

说明:
gcc编译链接动态库时,很有可能编译通过但是执行时,找不到动态链接库,那是因为-L选项指定的路径只在编译时有效,编译出来的可执行文件不知道-L选项后面的值,当然找不到。
解决方法是通过-Wl,rpath=<your_lib_dir>,使得execute记住链接库的位置

3)使用LD_LIBRARY_PATH

推荐使用方法一。

3.2 编译时链接

LDFLAGS

链接器参数,如指定库位置:

LDFLAGS=-L/usr/lib -L/path/to/your/lib

LIBS

告诉链接器要链接哪些库文件,如:

LIBS = -lpthread -liconv  

最正规的做法:

LDFLAGS = -L/var/xxx/lib -L/opt/mysql/lib -lmysqlclient -liconv

因为LIBS不是makefile的自带变量,属于用户自定义变量。

4. 其它

在makefile最好使用绝对路径,而非相对路径,如:./ 等。因为在某些情况下容易出错。
例如:-Wl,-rpath=./lib选项
虽然编译出来的可执行文件test可以运行,但如果将test 拷贝到其他目录,运行时便会提示:

error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

所以正确的做法是使用:-Wl,-rpath=$(TOP_PATH)/lib

Linux
Web note ad 1