Makefile编程基础

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,也可以执行操作系统的命令。

1.示例代码

add.c

1 #include "add.h"
2 
3 int add(int a, int b)
4 {
5     return a + b;
6 }

add.h

1 #ifndef __ADD_H__
2 #define __ADD_H__
3 
4 int add(int a, int b);
5 
6 #endif /* __ADD_H__ */

sub.c

1 #include "sub.h"
2 
3 int sub(int a, int b)
4 {
5     return a - b;
6 }

sub.h

1 #ifndef __SUB_H__
2 #define __SUB_H__
3 
4 int sub(int a, int b);
5 
6 #endif /* __SUB_H__ */

main.c

 1 #include <stdio.h>
 2 #include "add.h"
 3 #include "sub.h"
 4 
 5 int main()
 6 {
 7     int a = 1, b = 2;
 8     
 9     printf("a + b = %d.\n", add(a, b));
10     printf("a - b = %d.\n", sub(a, b));
11     
12     return 0;
13 }

编译如下:

gcc add.c sub.c main.c -o main

运行如下:

$ ./main
a + b = 3.
a - b = -1.

2.Makefile基础

简单实例:

1 all:
2     @echo "hello world."
3     echo "nihao world."

加@符和不加@符输出不同,加@只显示结果,不加@会把命令和结果都显示。

输出结果:

hello world.
echo "nihao world."
nihao world.

目标:依赖

Tab 命令

目标:一般是指要编译的目标,也可以是一个动作;
依赖:指执行当前目标所要依赖的先项,包括其它目标,某个具体文件或库等,一个目标可以有多个依赖;
命令:该目标下要执行的具体命令,可以没有,也可以有多条,多条时,每条命令一行;

make常用选项

make [-f file] [options] [target]

    # make默认在当前目录中寻找GNU Makefile,makefile的文件作为make的输入文件。
    -f 可以指定除上述文件名之外的文件作为输入文件;
    -v 显示版本号;
    -d Debug;
    -n 只输出命令,但并不执行,一般用来测试;
    -s 只执行命令,但不显示具体命令,此处可在命令中用@符抑制命令输出;
    -w 显示执行前、执行后的路径;
    -C dir 指定makefile所在的目录;

没有指定目标时,默认使用第一个目标;如果指定,则执行对应的命令。

1 all:test1
2     echo "hello world."
3 
4 test1:
5     echo "nihao world."

all依赖于test1,所以test1先执行,all后执行。执行结果:

nihao world.
hello world.

根据本节知识点,第1节的编译可改为Makefile如下:

1 all:
2     gcc add.c sub.c main.c -o main
3 
4 clean:
5     rm -rf main

3.GCC编译流程

直接从源码到编译出目标可执行文件

gcc main.c -o main

上述过程可细分为如下4个步骤:

1).预处理,得到main.ii

gcc -E main.c > main.ii

2).编译,得到main.s

gcc -S main.ii

3).汇编,得到main.o

gcc -c main.s

4).链接,得到可执行文件main

gcc main.o -c main

根据本节知识点,第2节的Makefile,优化如下:

 1 main:add.o sub.o main.o
 2     gcc add.o sub.o main.o -o main
 3 
 4 add.o:add.c
 5     gcc -c add.c -o add.o
 6 
 7 sub.o:sub.c
 8     gcc -c sub.c -o sub.o
 9 
10 main.o:main.c
11     gcc -c main.c -o main.o
12 
13 clean:
14     rm -rf *.o main

4.变量

系统变量

$* 不包括扩展名的目标文件名称
$+ 所有的依赖文件,以空格分隔
$< 表示规则中的第一个条件
$? 所有时间戳比目标文件晚的依赖文件,以空格分隔
$@ 目标文件的完整名称
$^ 所有不重复的依赖文件,以空格分隔
$% 如果目标是归档成员,则该变量表示目标的归档成员名称

系统常量(make -p可查看常量)

AS 汇编程序的名称,默认为as
CC C编译器名称,默认为cc
CPP C预处理器名称,默认为cc -E
CXX C++编译器名称,默认为g++
RM 文件删除程序别名,默认rm -f
...

自定义变量

定义:变量名=变量值
使用:$(变量名)/${变量名}

根据本节知识点,第3节的Makefile,优化如下:

 1 CC=gcc
 2 OBJ=add.o sub.o main.o
 3 TARGET=main
 4 
 5 $(TARGET):$(OBJ)
 6     $(CC) $^ -o $@
 7 
 8 add.o:add.c
 9     $(CC) -c $^ -o $@
10 
11 sub.o:sub.c
12     $(CC) -c $^ -o $@
13 
14 main.o:main.c
15     $(CC) -c $^ -o $@
16 
17 clean:
18     $(RM) *.o $(TARGET)

5.伪目标和模式匹配

1).伪目标

.PHONY:clean
声明目标为伪目标之后,makefile将不会判断目标是否存在或该目录是否需要更新。

2).模式匹配

// %通配符
%.o:%.cpp ---- .o依赖于对应的.cpp

// 获取当前目录下所有的.cpp文件
wildcard ---- $(wildcard ./*.cpp)

// 将对应的cpp文件名替换成.o文件名
patsubst ---- $(patsubst %.cpp, %.o, ./*.cpp)

根据本节知识点,第4节的Makefile,优化如下:

 1 .PHONY:clean
 2 
 3 CC=gcc
 4 OBJ=$(patsubst %.c, %.o, $(wildcard ./*.c))
 5 TARGET=main
 6 
 7 $(TARGET):$(OBJ)
 8     $(CC) $^ -o $@
 9 
10 %.o:%.c
11     $(CC) -c $^ -o $@
12 
13 clean:
14     $(RM) *.o $(TARGET)

通过$(wildcard ./*.c)获取当前目录下的.c文件名,通过patsubst把.c文件名转换为.o文件名,%.o:%.c进行文件"目标:依赖"的通配。

6.运行流程

7.编译动态链接库

动态链接库:不会把代码编译到二进制文件中,而是在运行时才加载,所以只需要维护一个地址。
-fPIC ---- 产生位置无关的代码
-shared ---- 共享
-l(小写L) ---- 指定动态库
-I(大写i) ---- 指定头文件目录,默认当前目录
-L ---- 手动指定库文件搜索目录,默认只链接共享目录

1).编译动态库

gcc -shared -fPIC DynamicTest.c -o libDynamicTest.so

2).链接动态库

gcc -lDynamicTest -L./ main.c -o main

3).发布动态库

复制动态库到/usr/lib或者/usr/local/lib

4).手动指定动态库位置(Linux)

除了发布动态库之外,我们还可以手动指定动态库的位置,而不需要发布。

LD_LIBRARY_PATH=./
export LD_LIBRARY_PATH

5).转换成Makefile

 1 CC=gcc
 2 TARGET=main
 3 
 4 $(TARGET):libDynamicTest.so
 5     $(CC) -lDynamicTest -L./ main.c -o $(TARGET)
 6 
 7 libDynamicTest.so:
 8     $(CC) -shared -fPIC DynamicTest.c -o libDynamicTest.so
 9 
10 clean:
11     $(RM) *.so $(TARGET)

8.编译静态链接库

1).生成静态库

gcc -c StaticTest.cpp -o StaticTest.o
ar -r libStaticTest.a StaticTest.o

2).使用静态库

gcc -lStaticTest -L./ main.cpp -o main

3).转换成Makefile

 1 CC=gcc
 2 TARGET=main
 3 
 4 $(TARGET):libStaticTest
 5 
 6 libStaticTest:
 7     $(CC) -c StaticTest.c -o StaticTest.o
 8     $(AR) -r libStaticTest.a StaticTest.o
 9 
10 clean:
11     $(RM) *.o *.a $(TARGET)

9.公共头文件

Makefile中,都是先展开所有的变量,再调用指令
"=" ---- 赋值,但是用终值,就是不管变量调用写在赋值前还是赋值后,调用时都是取终值;
":=" ---- 也是赋值,但只受当前行及之前的代码影响,而不受后面的赋值影响.

文件目录结构

.
├── 001
│   ├── Makefile
│   ├── a.c
│   ├── b.c
│   └── c.c
├── 002
│   ├── Makefile
│   ├── x.c
│   ├── y.c
│   └── z.c
└── Makefile

主./Makefile

1 .PHONY:clean
2 
3 $(TARGET):$(OBJ)
4     $(CC) $^ -o $@
5 
6 clean:
7     $(RM) $(TARGET) $(OBJ)

001/Makefile

1 TARGET=c
2 OBJ=a.o b.o c.o
3 
4 include ../Makefile

002/Makefile

1 TARGET=z
2 OBJ=x.o y.o z.o
3 
4 include ../Makefile

执行001/Makefile,会调用主Makefile,并生成"c"可执行文件;执行002/Makefile,会调用主Makefile,并生成"z"可执行文件。

10.调用Shell命令

 1 FILE=abc
 2 
 3 A:=$(shell ls ../)
 4 B:=$(shell pwd)
 5 C:=$(shell if [ ! -f $(FILE) ]; then touch abc; fi)
 6 
 7 all:
 8     echo $(A)
 9     echo $(B)
10 
11 clean:
12     $(RM) $(FILE)

11.嵌套调用

文件目录结构

 1 .
 2 ├── 001
 3 │   ├── Makefile
 4 │   ├── a.c
 5 │   ├── b.c
 6 │   └── c.c
 7 ├── 002
 8 │   ├── Makefile
 9 │   ├── x.c
10 │   ├── y.c
11 │   └── z.c
12 └── Makefile

主./Makefile

 1 .PHONY:001 002 clean
 2 
 3 DIR=001 002
 4 
 5 all:$(DIR)
 6     echo "hello world."
 7 
 8 $(DIR):
 9     make -C $@
10 
11 clean:
12     echo $(shell for dir in $(DIR); do make -C $$dir clean; done)

001/Makefile

 1 .PHONY:clean
 2 
 3 TARGET=c
 4 OBJ=a.o b.o c.o
 5 
 6 $(TARGET):$(OBJ)
 7     $(CC) $^ -o $@
 8 
 9 clean:
10     $(RM) $(TARGET) $(OBJ)

002/Makefile

 1 .PHONY:clean
 2 
 3 TARGET=z
 4 OBJ=x.o y.o z.o
 5 
 6 $(TARGET):$(OBJ)
 7     $(CC) $^ -o $@
 8 
 9 clean:
10     $(RM) $(TARGET) $(OBJ)

运行主Makefile,则会自动执行001/Makefile和002/Makefile。

12.条件判断

ifeq ---- 判断是否相等,相等返回true,不相等返回false;
ifneq ---- 判断是否相等,不相等返回true,相等返回false;
ifdef ---- 判断变量是否存在,存在返回true,不存在返回false;
ifndef ---- 判断变量是否存在,不存在返回true,存在返回false;

 1 A:=123
 2 
 3 RS1:=
 4 RS2:=
 5 
 6 # 判断变量A的值是否等于123
 7 ifeq ($(A), 123)
 8     RS1:=yes
 9 else
10     RS1:=no
11 endif
12 
13 # 判断变量A是否存在
14 ifdef A
15     RS2:=yes
16 else
17     RS2:=no
18 endif
19 
20 all:
21     echo $(RS1)
22     echo $(RS2)

13.循环

1 TARGET=a b c d
2 
3 all:
4     # $(shell for v in $(TARGET); do touch $$v.txt; done)
5     touch $(foreach v, $(TARGET), $v.txt)
6 
7 clean:
8     echo $(shell for v in $(TARGET); do $(RM) $$v.txt; done)

14.自定义函数

1).自定义函数,不是真正的函数,本质上是多行命令;
2).自定义函数没有返回值。

1 define FUNC1
2     echo "hello world."
3     echo $(1) $(2)
4 endef
5 
6 all:
7     $(call FUNC1, abc, def)

15.make install的实现

 1 TARGET=main
 2 OBJ=main.c
 3 
 4 $(TARGET):$(OBJ)
 5     $(CC) $^ -o $@
 6 
 7 install:
 8     cp $(TARGET) /usr/local/bin
 9 
10 clean:
11     $(RM) $(TARGET)
posted @ 2023-03-09 15:45  this毛豆  阅读(48)  评论(0编辑  收藏  举报