超详细的Makefile语法#实例

关于Makefile脚本的编写,在Linux开发项目中使用的比较多,一个好的Makefile能让你编译源码事半功倍,高效率让整个项目编译起来。
这里主要针对平时工作中常用的语法做了下总结,在讲解Makefile语法的编写前,先啰嗦下程序编译的几个步骤,可能在window的IDE环境,可能编译的几个步骤很少注意到,但在linux系统环境下编译,你可以很明显的观察到程序编译包括了预编译,汇编,编译,链接几个步骤,我们常说的程序编译其实仅仅只是几个步骤中的一个,好,言归正传,GNU的make有许多的内容,闲言少叙,还是让我们开始走进Makefile的世界里。

目录

    • Makefile的规则
    • 在Makefile中使用变量
        • 变量声明和使用
        • 自动化变量$@ $< $<
        • 变量赋值常用符号
        • Makefile环境变量
    • 在Makefile使用函数
        • 函数使用语法
        • 字符串常用处理函数
        • 文件名常用操作函数
        • 常用遍历操作函数
        • Makefile执行shell函数
    • Makefile的伪目标
    • 头文件路径及系统逻辑目录
    • 使用动态库编译
        • 使用动态库编译程序
    • 总结

Makefile的规则

target:prerequisites
	command
  1. target:表示一个目标文件,可以是Object File,也可以是执行文件,还可以是一个标签(Label),后面对对于这种用法进行说明
  2. prerequisites:生成target目标所需要的文件或是目标或者可以理解是生成target的依赖文件
  3. command: make需要执行的命令可以是任意的Shell命令,但是需要注意command一定要以TAB键开头,否则make不认识

几乎所有的Makefile编写都少不了在这个规则下添砖加瓦,接下来就开启认识Makefile的大门

在Makefile中使用变量

变量声明和使用

Makefile在声明变量前需要给予初值,在使用变量时在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来
举个栗子:

objects = obj_a.o obj_b.o obj_c.o

program : $(objects)
	cc -o program $(objects)

自动化变量$@ $< $<

经常会使用到 $@, $<, $^三个变量,这是Makefile非常重要的三个变量
$@ 表示目标文件, $< 表示第一个依赖文件, $^表示所有的依赖文件

变量赋值常用符号

= 是最基本的赋值(与位置无关,整个makefile展开后再赋值)
:= 是覆盖之前的值(与位置有关,变量在makefile里随时赋值)
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值

?=和+=都比较容易理解,就不举栗子了,这里对=和:=举个栗子:

"="表示make会将整个makefile展开后,再决定变量的值
 x = foo
 y = $(x) bar
 x = xyz
 在上例中,y的值将会是 xyz bar,而不是 foo bar 

":="表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值
x := foo
y := $(x) bar
x := xyz
在上例中,y的值将会是 foo bar,而不是 xyz bar了

Makefile环境变量

make 运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖,如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile中使用这个变量了,上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层 Makefile传递,则需要使用exprot关键字来声明

在Makefile使用函数

函数使用语法

$(<function> <arguments> )
或者
${ }
很像变量的使用,也是用$来标识的
<function>就是函数名
<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔

字符串常用处理函数

函数 意义 返回
$(subst from, to, text ) 把字符串text中的from字符串替换成to 函数返回被替换过后的字符串

举个栗子:

$(subst .c, .o, hello.c)
把hello.c的.c替换为.o输出为hello.o
函数 意义 返回
$(patsubst pattern, replacement, text ) 查找text中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式pattern,如果匹配的话,则以replacement替换 函数返回被替换过后的字符串

举个栗子:

$(patsubst %.c, %.o, a.c.c b.c c.c)
把字串"a.c.c b.c c.c"符合模式[%.c]的单词替换成[%.o],返回结果是"a.c.o b.o c.o"
函数 意义 返回
$(strip string) 去掉string字串中开头和结尾的空字符 返回被去掉空格的字符串值

举个栗子:

$(strip "a b c ")
把字符串"a b c "去到开头和结尾的空格,结果是"a b c"

文件名常用操作函数

函数 意义 返回
$(dir ) 从文件名序列names中取出目录部分 返回文件名序列names的目录部分

举个栗子:

$(dir src/foo.c hacks)
返回值是"src/ ./"
函数 意义
$(notdir ) 从文件名序列names中取出非目录部分,非目录部分是指最后一个反斜杠("/")之后的部分 返回文件名序列names的非目录部分

举个栗子:

$(notdir src/foo.c hacks)
返回值是"foo.c hacks"

常用遍历操作函数

函数 意义 返回
$(foreach var, list, text ) 把参数list中的单词逐一取出放到参数var所指定的变量中,然后再执行text所包含的表达式 返回的每个字符串所组成的整个字符串(以空格分隔)

举个栗子:

names := a b c d
files := $(foreach n, $(names), $(n).o)
$(name)中的单词会被挨个取出,并存到变量"n"中,"$(n).o"每次根据"$(n)"计算出一个值,
这些值以空格分隔,最后作为foreach函数的返回,
所以$(files)的值是"a.o b.o c.o d.o"
函数 意义 返回
$(wildcard pattern) 展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表 返回匹配的以空格分开的文件列表

举个栗子:

foo.c bar.c foo bar 
$(wildcard ./*.c)
展开后返回的字符串为"foo.c bar.c"

这个函数常常和patsubst配合使用
$(patsubst %.c, %.o, $(wildcard *.c))
首先使用“wildcard”函数获取工作目录下的.c文件列表,
之后将列表中所有文件名的后缀.c替换为.o,
这样我们就可以得到在当前目录可生成的.o文件列表

Makefile执行shell函数

shell 函数也不像其它的函数,它的参数应该就是操作系统Shell的命令
举个栗子:

./a.c ./b.c
files := $(shell echo *.c)
这样就把当前路径的"a.c b.c"赋值给files了

Makefile的伪目标

“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。
为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”

all 这个伪目标是所有目标的目标,其功能一般是编译所有的目标
clean 这个伪目标功能是删除所有被make创建的文件
install 这个伪目标功能是安装已编译好的程序,就是把目标执行文件拷贝到指定的目标中

举个栗子:

all:prog1 prog2 prog3 prog4

clean:
	rm -f ./*.o
install:
	install -m 755 bin BIN_DIR
	 
.PHONY: all clean install

头文件路径及系统逻辑目录

-Idir_include
大写的I紧跟着头文件路径
–sysroot
如果在编译时指定了-sysroot就是为编译时指定了逻辑目录。编译过程中需要引用的库,头文件,如果要到/usr/include目录下去找的情况下,则会在前面加上逻辑目录

使用动态库编译

动态库链接原理和使用

  1. Linux是通过 /etc/ld.so.cache 文件搜寻要链接的动态库的。而 /etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的,注意 /etc/ld.so.conf 中并不必包含 /lib 和 /usr/lib,ldconfig程序会自动搜索这两个目录,所以把 libtest.so 所在的路径添加到 /etc/ld.so.conf 中,再以root权限运行 ldconfig 程序更新ld.so.cache
  2. export LD_LIBRARY_PATH=库文件patch

举个栗子:

编译出动态库

gcc test.c  -fPIC -shared -o libtest.so
-fPIC:Position Independent Code编译出位置无关代码
-shared:编译动态库选项

使用动态库编译程序

gcc -o hello hello.c -L. -ltest
-L 表示动态库所在路径注意-L和path间不能有空格
-ltest表示动态库libtest.so

总结

本篇文章到此已经告一段落了,工作中使用Makefile常见的语法就由这些最基础的构成,比较复杂的那就是根据实际项目构建通用性,扩展性比较强的Makefile脚本了,这里等后续再逐渐深入讲解,下面附上一个最近写的一个组件对应的Makefile,详细源码部分可以参考源码

BIN = app

SRC = $(wildcard ./*.c)
OBJ = $(subst .c,.o,$(SRC))

all:$(BIN)

$(BIN):$(OBJ)
	gcc -o $@ $^

$(OBJ):%.o:%.c
	gcc -c $< -o $@

clean:
	rm -rf ./*.o
	rm -rf ${BIN} ${OBJ}

.PHONY: clean all

你可能感兴趣的:(C语言)