编写Makefile

目录

GCC是啥?

安装gcc

gcc工作流程

预处理

编译 

汇编 

链接 

gcc常用参数

Makefile

makefile是啥?

make又是啥?

Makefile命名规则

makefile框架

规则的执行

文件的时间戳

make常用选项

变量 

模式匹配和伪目标 

函数

Makefile中通用部分做公共头文件

Makefile中使用shell命令

Makefile中条件判断 


GCC是啥?

要想学会makefile,我觉得首先得了解GCC吧,要不然一团雾水,gcc是linux下的编译工具集,是GNU Compiler Collection的缩写,包含gcc、g++等编译器。

安装gcc

有些linux默认没有gcc,需要自己安装

$ sudo apt install gcc g++

可查看自己安装gcc的版本

$ gcc -V

gcc工作流程

  1. 预处理:主要做3件事:展开头文件、宏替换、去掉行注释。需要gcc调用预处理器来完成,最终得到的还是源文件
  2. 编译:需要gcc调用编译器对文件进行编译,最终得到一个汇编文件
  3. 汇编:需要gcc调用汇编器对文件进行汇编,最终得到一个二进制文件
  4. 连接:需要gcc调用链接器对程序需要调用的库进行链接,最终得到一个可执行的二进制文件

编写Makefile_第1张图片

(参数 -o 指定生成的文件名)

test.c源文件

#include 

#define NUM 5

int main() {

    // 打印5个hello world
    for(int i = 0; i < NUM; i++) {
        printf("hello world!\n");
    }
    return 0;
}
预处理
$ gcc -E test.c -o test.i

会得到一个test.i的文件

编写Makefile_第2张图片

编写Makefile_第3张图片

编译 
$ gcc -S test.i -o test.s

会得到一个test.s的汇编文件

编写Makefile_第4张图片

汇编 
$ gcc -c test.s -o test.o

会得到一个test.o的目标文件 (看不懂)

编写Makefile_第5张图片 

链接 
$ gcc test.o -o test

得到一个可执行文件test

编写Makefile_第6张图片编写Makefile_第7张图片 

在使用gcc编译时可以通过参数控制内部自动执行几个步骤

参数-c进行文件的汇编,汇编之前的两步会自动执行

该命令直接一步到位。直接链接生成可执行文件,连接之前的三步会自动执行

 (不加-o的话,gcc会自动生成文件名为a.out的可执行文件 )

gcc常用参数

-E : 预处理指定的源文件

-S : 编译指定的源文件

-c : 编译、汇编指定的源文件

-o [file2] [file1] / [file1] -o [file2] : 将file1编译成file2

-I : 指定include包含文件的搜索目录

-g : 在编译时,生成调试信息,该程序可以被调试器调试

-D : 在程序编译时指定一个宏

-w : 不生成任何警告信息

-Wall : 生成所有警告信息

-l : 在程序编译时指定使用的库

-L : 指定编译时搜索库的路径

-fPIC/fpic : 生成与位置无关的代码

-shared : 生成共享目标文件

-std : 指定C方言

Makefile

先准备几个.c文件用来测试 :

编写Makefile_第8张图片

calc.h

编写Makefile_第9张图片 

目录结构为:

编写Makefile_第10张图片 

makefile是啥?

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。(百度百科) 

make又是啥?

make是一个命令工具,是一个解释makefile中指令的命令工具,make工具在构造项目时需要加载一个叫makefile的文件,makefile关系到了整个工程的编译顺序、编译规则。

Makefile命名规则

makefile或者Makefile

makefile框架

Makefile框架是由规则构成的,make命令执行时先在Makefile文件中查找各种规则,对各种规则进行解析后运行规则,规则的基本格式:

target1,target2,...: depend1, depend2,... 

[Tab]command

        command

        ...... 

每条规则由3部分组成:目标(target)、依赖(depend)、命令(command)

  • 目标:一般指要编译的目标,也可以是一个动作
  • 依赖:执行当前目标所要依赖的条件,某个具体文件或库等一个目标可以有多个依赖
  • 命令:该目标下要执行的具体命令,可以没有,也可以有多条,多条时,每条命令占一行,每个命令前必须有一个Tab键 

规则的执行

在调用make命令编译程序时,make会先找到Makefile文件中的第1个格则,分析并执行相关动作,但是,在很多时候要执行的动作中使用的依赖是不存在的,如果需要使用的依赖不存在,这个动作也就不会执行,所以我们需要先将我们需要的依赖生成出来,在Makefile中添加新的规则,将不存在的依赖作为新规则中的目标,当这条新的规则对应的命令执行完毕,对应目标就被生成了,同时另一条规则中需要的依赖也就存在了。

编写Makefile_第11张图片

如果只执行make命令,只会执行Makefile里的第一条规则,如果要执行不是第一条规则的命令,需要将要执行规则的目标写到make后面,即:make target。 

如果要执行上述代码中clean规则,则:

yan@ubuntu:~/a/test$ make clean
rm *.o calc

文件的时间戳

make命令执行时会根据文件的时间戳来判定是否执行Makefile文件中相关规则中的命令

目标时间戳  > 所有依赖的时间戳      ---------> 不执行 

目标时间戳  < 某些依赖的时间戳      ---------> 执行 

编写Makefile_第12张图片

假如我们修改一下add.c

编写Makefile_第13张图片 此时,目标add.o的时间戳<他的依赖add.c的时间戳,再执行make命令时就可以执行

 

从上面也可以看到,当我们只改变add.c一个文件时,再执行make只会编译有改动的代码,所以,有人一开始学就会想,明明

calc:main.c add.c sub.c mul.c div.c
    gcc main.c add.c sub.c mul.c div.c -I ./include -o calc

这样两行代码就可以解决的事,偏要把它拆分成那么多行来实现,但是,如果有很多.c文件呢,只要改动一个.c文件的内容,再执行make命令,他就会全部再编译一遍,执行效率是很低的。

make常用选项

make 默认在当前目录中寻找 GUNmakefile,makefile,Makefile 的文件作为 make 的输入文件

-f : 可以指定除上述文件名之外的文件作为输入文件

-v : 显示版本号

-n : 只输出命令但不执行

-s : 只执行命令,但不显示具体命令,此处也可在命令中用@符抑制命令输出

-w : 显示执行前执行后的路径

-C dir :  指定Makefile所在目录

变量 

  1. 自定义变量    变量名=变量值
  2. 预定义变量(系统常量):已经定义的变量,用户可直接使用,可用make -p 查看
  3. 自动变量:用来代表规则中目标文件和依赖文件,并且只能在规则的命令中使用

将变量名的值取出的方法:$(变量名)

一开始的Makefile文件(最普通的写法):


calc:main.o add.o sub.o mul.o div.o
    gcc main.o add.o sub.o mul.o div.o -o calc


main.o:main.c
    gcc main.c -c -I ./include

add.o:add.c
    gcc add.c -c -I ./include

sub.o:sub.c
    gcc sub.c -c -I ./include

mul.o:mul.c
    gcc mul.c -c -I ./include

div.o:div.c
    gcc div.c -c -I ./include

clean:
    rm *.o calc

自定义变量:(将上述2、3行改成)

编写Makefile_第14张图片

常见预定义变量:

AR  生成静态库库文件的程序名称, 默认值为ar

AS  汇编编译器的名称, 默认值为as

CC  C语言编译器的名称, 默认值为cc

CPP  C语言与编译器的名称,  默认值为$(CC) -E

CXX  C++语言编译器的名称,  默认值为g++

RM  删除文件程序的名称, 默认值为rm -f

ARFLAGS 生成静态库库文件程序选项     无默认值

ASFLAGS  汇编语言编译器的编译选项    无默认值

CFLAGS  C 语言编译器的编译选项          无默认值

CPPFLAGS  C 语言预编译的编译选项     无默认值

CXXFLAGS  C++语言编译器的编译选项  无默认值

现在又可将一开始的Makefile文件改成:

  1 TARGETS=calc
  2 OBJS=main.o add.o sub.o mul.o div.o
  3 CFLAGS+=-c
  4 include=./include
  5 $(TARGETS):$(OBJS)
  6     $(CC) $(OBJS) -o $(TARGETS)
  7
  8
  9 main.o:main.c
 10     $(CC) main.c $(CFLAGS) -I include
 11
 12 add.o:add.c
 13     $(CC) add.c $(CFLAGS) -I include
 14
 15 sub.o:sub.c
 16     $(CC) sub.c $(CFLAGS) -I include
 17
 18 mul.o:mul.c
 19     $(CC) mul.c $(CFLAGS) -I include
 20
 21 div.o:div.c
 22     $(CC) div.c $(CFLAGS) -I include
 23
 24 clean:
 25     $(RM) $(OBJS) $(TARGETS)

常见的自动变量

$*:表示目标文件的名称,不包含目标文件的扩展名

$+:表示所有的依赖文件,这些依赖文件之间以空格分开,按照出现的先后为顺序,其中可能 包含重复的依赖文件

$<:表示依赖项中第一个依赖文件的名称

$?:依赖项中,所有比目标文件时间戳晚的依赖文件,依赖文件之间以空格分开

$@:表示目标文件的名称,包含文件扩展名

$^:依赖项中,所有不重复的依赖文件,这些文件之间以空格分开

$%:如果目标是归档成员,则该变量表示目标的归档成员名称

一开始的代码可改成: 

  1 TARGETS=calc
  2 OBJS=main.o add.o sub.o mul.o div.o
  3 CFLAGS+=-c
  4 include=./include
  5 $(TARGETS):$(OBJS)
  6     $(CC) $^ -o $@
  7
  8
  9 main.o:main.c
 10     $(CC) $< $(CFLAGS) -I include
 11
 12 add.o:add.c
 13     $(CC) $< $(CFLAGS) -I include
 14
 15 sub.o:sub.c
 16     $(CC) $< $(CFLAGS) -I include
 17
 18 mul.o:mul.c
 19     $(CC) $< $(CFLAGS) -I include
 20
 21 div.o:div.c
 22     $(CC) $< $(CFLAGS) -I include
 23
 24 clean:
 25     $(RM) $(OBJS) $(TARGETS)

模式匹配和伪目标 

终于到这了,上面改的我手都麻了

有没有发现有几条很相似的语句:

编写Makefile_第15张图片 

那我们可不可以把它写成一个模板呢?那肯定可以的,这就用到了模式匹配了。

模式匹配:通过一个公式,代表若干满足条件的规则

%.o:%.c
    gcc $< -c

%.o:%.c -----> .o依赖于对应的.c

%是一个通配符,匹配的是文件名

所以一开始的代码又可以改成:

  1 TARGETS=calc
  2 OBJS=main.o add.o sub.o mul.o div.o
  3 CFLAGS+=-c
  4 include=./include
  5 $(TARGETS):$(OBJS)
  6     $(CC) $^ -o $@
  7
  8
  9 %.o:%.c
 10     $(CC) $< $(CFLAGS) -I include
 11
 12 clean:
 13     $(RM) $(OBJS) $(TARGETS)

如果我们目录下有一个名为clean的文件或目录,那执行make clean就执行不了了

那如何解决呢,这个时候就需要用到伪目标了

.PHONY:target

声明伪目标后,Makefile将不会判断目标是否存在或该目标是否需要更新

 12 .PHONY:clean
 13 clean:
 14     $(RM) $(OBJS) $(TARGETS)

函数

makefile中的函数都是有返回值的,所以要用$取出返回值

$(函数名 参数1,参数2, ....)   参数之间逗号隔开

两个常用函数:

$(wildcard PATTERN ...)

功能:获取指定目录下指定类型的文件名

参数:指定某个目录,搜索该目录下指定类型的文件

返回值:以空格分割的指定目录下的所有符合条件的文件列表

$(patsubst ,,)

功能:按照指定的模式替换指定的文件名的后缀

参数:

pattern:模式字符串,指出要被替换的文件名中的后缀

replacement:要替换成什么后缀

text:存储要被替换的原始数据

返回值:被替换过后的字符串

了解这两个函数之后,原始代码又可以修改了。。。

  1 TARGETS=calc
  2 SRCS=$(wildcard ./*.c)
  3 OBJS=$(patsubst %.c, %.o, $(SRCS))
  4 CFLAGS+=-c
  5 include=./include
  6 $(TARGETS):$(OBJS)
  7     $(CC) $^ -o $@
  8
  9
 10 %.o:%.c
 11     $(CC) $< $(CFLAGS) -I include
 12
 13 .PHONY:clean
 14 clean:
 15     $(RM) $(OBJS) $(TARGETS)

Makefile中通用部分做公共头文件

先创建两个文件夹

在里面分别写两份测试代码:

001cpp中有3个.cpp文件,如下:

编写Makefile_第16张图片

002c中有3个.c文件,如下:

编写Makefile_第17张图片

那么,通过上面的介绍,应该都会写这两个文件夹对应的makefile文件了吧,这里就直接放代码了哦

编写Makefile_第18张图片

有没有发现,他俩的代码基本上是一样的,所以我们就可以重新在两个文件夹的外面重新写一个makefile,然后里面的这两个Makefile直接包含外面的那个,这样不就少写了好多代码嘛,把公共部分给写到另一个makefile里面去。

代码如下:

  1
  2 src=$(wildcard ./*.cpp ./*.c)
  3 obj=$(patsubst %.c, %.o, $(src))
  4
  5 obj:=$(patsubst %.cpp, %.o, $(obj))
  6
  7 .PHONY:clean
  8
  9 $(target):$(obj)
 10     $(CXX) $^ -o $@
 11
 12 %.o:%.cpp
 13     $(CXX) $< -c
 14
 15 clean:
 16     $(RM) $(obj) $(target)
 17

那001cpp和002c文件夹里的Makefile就可以写成这样了。

编写Makefile_第19张图片

至于不知道 :=代表啥,可以去看看这篇博客,或者自己去百度一下。补充一点:不管是gcc还是g++都可以编译C程序,编译程序的规则和参数都相同;g++可以直接编译C++c程序,gcc编译C++程序需要添加额外参数-lstdc++。

该测试的目录结构为:

编写Makefile_第20张图片

Makefile中使用shell命令

语法:$(shell shell命令) 

  1
  2 A:=$(shell ls)
  3 B:=$(shell pwd)
  4
  5
  6
  7 test:
  8     echo $(A)
  9     echo $(B)

在makefile里面直接使用shell时需注意:

1.shell命令必须在规则里面:

编写Makefile_第21张图片

像这样子的话就会报错 

应该写成:

A:=123

test:

    if [ "$(A)" = "123" ]; then  echo "yes"; else echo "no"; fi
    echo "hello"

2. shell命令在makefile调用的时候每行shell都是一个单独的进程,上一行定义的变量在下一行是无效的

这种写法是输出不了B的值的,要写成: 

test:
    B=hello;echo $$B

或者:

test:
    B=hello;\
    echo $$B

3.在Makefile中要想引用shell的变量,应该以$$开头,shell变量不需要括号

上面那个例子中输出B的值,如果改成echo $B,则输出不了B的值。 

Makefile中条件判断 

条件判断
ifeq 判断是否相等
ifneq 判断是否不相等
ifdef 判断变量是否存在
ifndef 判断变量是否不存在

A=123

RS1:=
RS2:=
RS3:=
RS4:=

ifeq ($(A),123)
    RS1:=yes
else
    RS1:=no
endif

ifdef RS2
    RS3:=YES
else
    RS3:=NO
endif


test:
    echo $(RS1)
    echo $(RS3)

执行结果:

参考资料:Makefile | 爱编程的大丙 001-makefile相关概念介绍_哔哩哔哩_bilibili

你可能感兴趣的:(linux,运维,Makefile)