Makefile从入门到项目编译实战(学习笔记)

1.make和makefile介绍

1. make

  make 是一个应用程序,位于 /usr/bin/make 目录下,make 有如下的功能:

  (1)解析源程序之间的依赖关系

  (2)根据依赖关系自动维护编译工作

  (3)执行宿主操作系统中的各种命令

2. makefile 

  makefile 是一个描述文件

  (1)定义一系列的规则来指定源文件编译的先后顺序。

  (2)拥有特定的语法规则,支持函数定义和函数调用。

  (3)能够直接集成操作系统中的各种命令。

3. make 和 makefile 之间的关系

  makefile 中的描述用于指导 make 程序如何完成工作。

  make 根据 makefile 中的规则执行命令,最后完成编译输出。

4.相关概念

Makefile 是啥
工个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefie 定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
【百度百科 https://baike.baidu.com/item/Makefile/4619787】


Make与Makefile 的关系
make 是一个命令工具,它解释 Makefile 中的指令。在 Makefile 文件中描述了整个工程所有文件的编译顺序、编译规则。


Makefile 命名规则
Makefile 或 makefile,一般使用 Makefile

Cmake 又是啥
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的 makefie 或者 project 文件,能测试编译器所支持的 C++特性,类似 UNIX 下的 utomake。只是 CMoke的组态档取名为 CMakelists.txt。cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的Makefile 或 Windows Visual C++的 projects/workspaces),然后再依一般的建构方式使用。百度百科 https://baike.baidu.com/item/cmake


Cmake与CMakeLists 的关系
cmake 是一个命令工具,可用来生成,makefile。但也要根据 CMakelists.txt 中的内容来生成,CMakelists.txt 就是写给cmake 的规则。
重点
make 是一个命令工具,Makefile 是一个文件,make 执行的时候,去读取Makefile 文件中的规则,重点是 makefile 得自己写。
cmake是一个命令工具,CMakelists.txt 是一个文件,cmake 执行的时候,去读取 CMakelists.txt文件中的规则,重点是(Makelists.txt 得自己写。

2.makefile的结构

1. makefile 的意义

  (1)makefile 用于定义源文件之间的依赖关系 (在阅读开源软件源码时,可通过Makefile掌握源码中各个文件之间的关系)

  (2)makefile 说明如何编译各个源文件并生成可执行程序  

2. makefile 的规则

  (1)makefile 规则的定义

      形式1:targets : prerequisites command

      形式2:targets : prerequisites 

          '\t'     command      

  (2)makefile 规则中各元素的含义

    targets 目标

      • 通常是需要生成的目标文件名  
      • make 所需执行的命令名称

    prerequisities 依赖        

      • 当前目标所依赖的其它目标或文件

     command 命令

      • 完成目标所需要执行的命令(系统自带命令 — PATH 环境变量、用户命令 — 自己指定路径)

  (3)规则中的注意事项

    targets 可以包含多个目标

      • 使用空格对多个目标进行分隔  

    prerequisities 可以包含多个依赖          

      • 使用空格对多个依赖进行分隔

     [Tab] 键: '\t'

      • 每一个命令行必须以 [Tab] 字符开始
      • [Tab] 字符告诉 make 此行是一个命令行

    续行符: \ 

      • 可以将内容分开写到下一行,提高程序的可读性

    makefile可以在命令前添加 @ 符号,使该命令静默执行

  (4)一个 makefile 的规则示例 

all : test
      echo "make all"
 
 test :
      echo "make test"

3. makefile依赖(prerequisites)的规则

  (1)当目标对应的文件不存在时,执行对应的命令

  (2)当依赖在时间上比项目更新时,执行对应命令

  (3)当依赖关系连续发生时,对比依赖链上的每一个目标

4. makefile编程实验

 //mian.c源码
 
 int main(void)
 {
     func();
     return 0;
 }
 //func.c源码
 
 #include <stdio.h>
 
 void func()
 {
     printf("hello make!\n");
 }
  #makefile源码
  
  hello.out all : main.o func.o
      gcc -o hello.out main.o func.o
  
  main.o : main.c
      gcc -o main.o -c main.c
  
  func.o : func.c
     gcc -o func.o -c func.c

  (1)工程开发中可以将 最终可执行文件名 all 同时作为makefile中第一条规则的目标。但最终可执行文件名要放在all之前,make默认使用多个目标中的首个目标。这样做的好处是当默认执行make时,make会自行判断依赖关系有没有发生更新,如果没有就不会重新编译程序。如果这里用all作为目标,由于不存在all这个文件,每次执行make时,第一条规则的命令 总会被执行,这与实际不符。如果需要强制编译该程序,执行 make all 即可。

  (2)makefile不仅可以用在C/C++中,同样可以用在其它编程语言中,如Java等。makefile只是解决问题的一种方法,但普遍应用于C/C++中。

5. hello word实战

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

没有指定目标,默认执行第一个;指定目标,指定对应命令  

 make clean删除编译的中间文件;

 

指定makefile所在目录;

3.程序编译流程详解

1.没有使用makefile的编译

2.把编译命令放置makefile

这样的makefile不好!!!

 修改上面的makefile:

修改目的

# 第一次编译两小时
# 第二次编译五分钟
# 这样分开来写,保证只编译有改动的代码


#calc:
#	gcc add.cpp sub.cpp multi.cpp calc.cpp -o calc

calc:add.o sub.o multi.o
	gcc add.o sub.o multi.o calc.cpp -o calc

add.o:add.cpp
	gcc -c add.cpp -o add.o

sub.o:sub.cpp
	gcc -c sub.cpp -o sub.o

multi.o:multi.cpp
	gcc -c multi.cpp -o multi.o

 3.编译流程详解

 main.o文件是执行不了的,还需要进行最后一步链接!即:gcc -lstdc++ main.0 得到名为 a.out 的可执行文件

 正常情况:gcc -lstdc++ main.cpp即可得到a.out,实际这条命令内部执行了4个步骤:

gcc/g++ 编译流程详解:
gcc -lstdc++ main.cpp 直接从源代码到目标可执行文件了
把过程拆分
预处理  :gcc -E main.cpp > main.ii
编译     : gcc -s main.i 得到名为 main.s 的汇编文件

汇编     :gcc-c main.s 得到名为 main.o(.obj)的二进制文件

链接     :gcc -lstdc++ main.0 得到名为 a.out 的可执行文件

4.makefile脚本运行流程介绍

保证目标是用最新的依赖生成的

第一次全完编译,后面只编译最新的代码(部份编译)

实际是进行层层递归,套娃式执行

5.makefile伪目标和模式匹配

1. makefile 中的目标究竟是什么?

  (1)默认情况下,make 认为目标对应着一个文件  ==>  目标即文件名

  (2)make 首先会检测目标对应的文件是否存在,若不存在则执行依赖和命令。若存在则会比较目标文件和依赖文件的新旧关系,决定是否执行命令。

      在 make 中,通过比较目标文件和依赖文件的时间戳,来判断两者的新旧关系。make 程序使用的时间戳的类型是 mtime(modify time),即文件发生修改的时间。

      在 linux 中,有三个时间的概念,修改时间 mtime(modify time)、访问时间 atime(access time)、状态改动时间 ctime(change time)。

  (3)make 以文件处理作为第一优先级。

2. 伪目标的引入

  下面的代码有什么意义?   

  

  执行 make clean 会将第2课中编译生成的中间 .o 文件和 hello.out 目标文件删除。但如果该目录下存在名为 clean 的文件就会导致删除命令执行失败。

  有时我们并不希望目标对应的都是文件,而只是把目标当作一个标签来使用,这就引入了makefile中的伪目标。

  (1)makefile 中的伪目标

    • 通过 .PHONY 关键字声明一个伪目标
    • 伪目标不对应任何实际的文件(目录下有同名的文件也不会影响执行
    • 不管伪目标的依赖是否更新,命令总是执行

  (2)makefile 伪目标的语法:先声明,后使用

    本质:伪目标是 make 中特殊目标 .PHONY 的依赖。

    

编程实验

 1 # makefile伪目标的引入
 2 
 3 hello.out all : func.o main.o
 4     gcc -o hello.out func.o main.o
 5     
 6 func.o : func.c
 7     gcc -o func.o -c func.c
 8     
 9 main.o : main.c
10     gcc -o main.o -c main.c
11 
12 .PHONY : clean
13 clean :
14     rm *.o hello.out

  (3)makefile 伪目标的妙用:规则调用(函数调用)

  

 原理:当一个目标的依赖包含伪目标时,伪目标所定义的命令总是会被执行。当执行 make rebuild 时首先会删除之前编译生成的垃圾文件,然后重新编译整个工程。

 1 # makefile中利用伪目标实现规则调用
 2 
 3 hello.out : func.o main.o
 4     gcc -o hello.out func.o main.o
 5     
 6 func.o : func.c
 7     gcc -o func.o -c func.c
 8     
 9 main.o : main.c
10     gcc -o main.o -c main.c
11 
12 .PHONY : rebuild clean all
13 
14 rebuild : clean all
15 
16 all : hello.out
17 
18 clean :
19     rm *.o hello.out

  (4)技巧:绕开 .PHONY 关键字定义伪目标

    .PHONY 关键字只有标准的make(GNU make)才拥有,在使用非标准的make时可以使用如下技巧定义伪目标。   

    

  原理:如果一个规则只有一个目标,并且该目标不是一个存在的文件名,则在执行此规则时,目标总会被认为是最新的。

        当执行 make clean 时,由于 FORCE 会被认为是最新的(FORCE 比 clean 要新),clean 下的命令必然被执行。

 1 #非GNU make下伪目标的实现方法
 2 
 3 hello.out : func.o main.o
 4     gcc -o hello.out func.o main.o
 5     
 6 func.o : func.c
 7     gcc -o func.o -c func.c
 8     
 9 main.o : main.c
10     gcc -o main.o -c main.c
11 
12 clean : FORCE
13     rm *.o hello.out
14 FORCE :   

3.伪目标实战

伪目标 .PHONY:clean
声明目标为伪目标之后,makefile将不会判断目标是否存在或该目标是否需要更新
%.o:%.cpp            .o依赖于对应的 .cpp
wildcard                $(wildcard ./*.cpp)获取当前目录下所有的.cpp 文件
patsubst               $(patsubst %.cpp,%o,./*.cpp)将对应的cpp 文件名替换成 .o 文件名

 通过.PHONY声明后,再执行make clean就起作用了(makefile就不会再判断目标是存在或者是否需要更新了)

模式匹配,原始makefile

进行模式匹配后:

以下两个用法:

wildcard                $(wildcard ./*.cpp)获取当前目录下所有的.cpp 文件
patsubst               $(patsubst %.cpp,%o,./*.cpp)将对应的cpp 文件名替换成 .o 文件名

6.makefile中的变量

1.变量和不同的赋值方式

1. makefile中的变量

  (1)makefile 中支持程序设计语言中变量的概念

  (2)makefile 中的变量只代表文本数据(字符串)

  (3)makefile 中的命名规则

     — 变量名可以包含字符 , 数字 , 下划线

     — 不能包含 ":" , "#" , "="  或 " "

     — 变量名大小写敏感

2. 变量的定义和使用

    

 1 #演示变量的使用
 2 
 3 CC := gcc
 4 TARGET := hello.out
 5 
 6 $(TARGET) : main.o func.o
 7     $(CC) -o $(TARGET) main.o func.o
 8 
 9 main.o : main.c
10     $(CC) -o  main.o -c main.c
11 
12 func.o : func.c
13     $(CC) -o func.o -c func.c
14     
15 .PHONY : rebuild all clean
16 
17 rebuild : clean all
18 
19 all : $(TARGET)
20 
21 clean : 
22     rm *o $(TARGET)

3. makefile中变量的赋值方式

  (1)简单赋值(:=)

    — 程序设计语言中的通用的赋值方式

    — 只针对当前语句的变量有效

  

  (2)递归赋值(=)

    — 赋值操作可能影响多个其它变量

    — 所有与目标变量相关的其它变量都将受到影响

    — 脚本语言也是顺序一句一句执行的,递归赋值会改变与目标变量相关的其它变量

    — 只会影响那些同样是递归赋值的变量

  

  (3)条件赋值(?=)

    — 如果变量未定义,使用赋值符号中的只定义变量

    — 如果变量已经定义,赋值无效

  

  (4)追加赋值(+=)

    — 原变量值之后加上一个新值

    — 原变量与新值之间由空格隔开

  

 1 #演示4种变量的定义方式
 2 
 3 # ex1
 4 # x := foo
 5 # y := $(x)b
 6 # x := new
 7 
 8 # ex2
 9 # x = foo
10 # y = $(x)b        //这里必须也是 = 不能是 := 否则不会影响
11 # x = new
12 
13 # a = $(b)
14 # b = $(c)
15 # c = hello-makefile
16 
17 # ex3
18 # x := foo
19 # y := $(x)b
20 # x ?= new
21 
22 # ex4
23 # x := foo
24 # y := $(x)b
25 # x += new
26 
27 .PHONY : test
28 
29 test :
30     @echo "x => $(x)"
31     @echo "y => $(y)"
32     @echo "a => $(a)"
33     @echo "b => $(b)"
34     @echo "c => $(c)"

2. 预定义变量的使用

1. makefile中的预定义变量

  (1)自动变量

    •  $@  @^  @<    

  (2)特殊变量

    • (MAKE),
  • (MAKECMDGOALS) , $(MAKEFILE_LIST) 
  • (MAKEVERSION),
    • (CURDIR) , $(.VARIABLES) 
    • ......

2. 自动变量的使用

  (1)自动变量的意义

      $@   当前规则中触发命令被执行的目标

      $^  当前规则中所有的依赖

      $<  当前规则中的第一个依赖

  (2)自动变量的使用示例

    

         执行 make all 后程序的输出

     $@ => all

     $^ => first second third

     $< => first

  注意:

    ⅰ. 在执行makefile脚本时,make首先会展开脚本中的变量等,相当于C中预处理过程。然后再将相应规则中的命令交给shell执行。

    ⅱ. "$" 对于makefile有特殊含义,输出时需要加上一个 "$" 进行转义。

    ⅲ. "$@" 对于Bash Shell 有特殊含义,输出时需要加上 "\" 进行转义。 $@是shell输入的参数的个数。

 1 # 演示自动变量的使用
 2 
 3 CC := gcc
 4 TARGET := hello.out
 5 
 6 $(TARGET) : main.o func.o
 7     $(CC) -o $@ $^
 8 
 9 main.o : main.c
10     $(CC) -o  $@ -c $<
11 
12 func.o : func.c
13     $(CC) -o $@ -c $<
14     
15 .PHONY : rebuild all clean
16 
17 rebuild : clean all
18 
19 all : $(TARGET)
20 
21 clean : 
22     rm *o $(TARGETa)

3. 特殊变量的使用 

  (1)$(MAKE),当前make解释器的文件名

  (2)$(MAKECMDGOALS),命令行中指定的目标名(make的命令行参数 make xx , xx 即MAKECMDGOALS)

  (3)$(MAKEFILE_LIST)

    • make所需要处理的 makefile 文件列表
    • 当前 makefile 的文件名总是位于列表的最后
    • 文件名之间以空格进行分隔
 1 # 测试以上三个特殊变量的含义
 2 
 3 .PHONY : all out first second third test
 4 
 5 all out : 
 6     @echo "$(MAKE)"
 7     @echo "$(MAKECMDGOALS)"
 8     @echo "$(MAKEFILE_LIST)"
 9     
10 first :
11     @echo "first"
12     
13 second :
14     @echo "second"
15     
16 third :
17     @echo "third"
18     
19 test :
20     @$(MAKE) first
21     @$(MAKE) second
22     @$(MAKE) third

  (4)$(MAKE_VERSION),当前make解释器的版本

  (5)$(CURDIR),当前make解释器的工作目录

  (6)$(.VARIABLES),所有已经定义的变量名列表(自定义变量预定义变量(自动变量、特殊变量))

 1 # 测试以上三个特殊变量的含义
 2 
 3 .PHONY : test1 test2
 4 
 5 TDelphi := Delphi Tang
 6 D.T.Software := D.T.
 7     
 8 test1 :
 9     @echo "$(MAKE_VERSION)"
10     @echo "$(CURDIR)"
11     @echo "$(.VARIABLES)"
12     
13 test2 :
14     @echo "$(RM)"

3 变量的高级主题

1. makefile中变量的替换

1.1 变量值的普通替换

  (1)使用指定字符(串)替换变量值中的后缀字符(串)

  (2)语法格式: $(var:a=b) 或 ${var:a=b}

      • 替换表达式中不能有任何的空格
      • make 中支持使用 ${} 对变量进行取值

 1 # 变量的普通替换(替换后缀)
 2 
 3 src := a.cc b.cc c.cc
 4 obj := ${src:cc=o}
 5 
 6 test :
 7     @echo "obj => $(obj)"
 8 
 9 # 输出结果
10 # obj => a.o b.o c.o
1.2 变量值的模式替换

  (1)使用 % 保留变量值中的指定字符,替换其它字符,%可以替换任何字符串

  (2)语法格式: $(var:a%b=x%y) 或 ${var:a%b=x%y}

      • 替换表达式中不能有任何的空格
      • make 中支持使用 ${} 对变量进行取值
      • 模式替换是整体匹配模式,如 a%b 能和 a123b 匹配,但是不能和 sa123b 匹配

 1 # 变量的模式替换
 2 
 3 src := a1b.c a2b.c a3b.c
 4 obj := ${src:a%b.c=x%y}
 5 
 6 test :
 7     @echo "obj => $(obj)"
 8 
 9 # 输出结果
10 # obj => x1y x2y x3y
1.3 规则中的模式替换

  (1)语法格式

    targets : target-pattern : prereq-pattern

      command1

      command2

      ...

  (2)语法含义:通过 target-pattern 从 targets 中匹配子目标;再通过 prereq-pattern 从子目标生成依赖;进而构成完整的规则。

      

  (3)规则中使用模式替换的意义

     ① 在大型的工程中 .o 文件的数量很多,按照一般的写法 makefile 会非常繁琐,而使用模式替换可以自动生成众多 .o 文件需要的规则。

     ② 在工程中要增添或者删减文件,只需要在 OBJS 变量中增添或删减即可,非常方便。如果采用一般的写法,要增添或删减规则,很繁琐

2 变量值的嵌套引用

  (1)一个变量名之中可以包含对其它变量的引用

  (2)嵌套引用的本质是使用一个变量表示另外一个变量

    

   注意:  x := y 这里的 y 是字符(字符串)  而 y := z 中的 y 表示的是变量,这两者是两个东东

3. 命令行变量

  (1)运行 make 时,在命令行定义变量

  (2)命令行变量默认覆盖 makefile 中定义的变量

  (3)在实际工程中,通过命令行变量临时改变变量的值,得到特殊的可执行程序(一般测试使用)

 1 # 例1:make 中的命令行变量
 2 
 3 test :
 4     echo "hm => $(line)"
 5 
 6 # 执行 make hm=swj
 7 # 输出 hm => swj
 8 
 9 # 例2:make中的命令行变量
10 
11 hm := hello makefile
12 
13 test :
14     echo "hm => $(hm)"
15 
16 # 执行 make hm=cmd
17 # 输出 hm => cmd ,覆盖了makefile中原本定义的 hm 变量

4. override 关键字

  (1)用于指示 makefile 中定义的变量不能被覆盖

  (2)变量的定义和赋值都需要使用 override 关键字

1 # 测试override关键字用法
2 
3 override var := test
4 
5 test :
6     @echo "var => $(var)"
7 
8 # 执行 make var=cmd
9 # 输出 var => test  变量没有被命令行变量所覆盖

5. define 关键字

  (1)用于在 makefile 中定义多行变量

  (2)多行变量的定义从变量名开始到 endef 结束

  (3)可使用 override 关键字防止变量被覆盖

  (4)define 定义的变量等价于使用 = 定义的变量(define 赋值的方式是递归赋值

 1 # 演示define 的功能
 2 
 3 define foo
 4 I'm fool!
 5 endef
 6 
 7 override define cmd
 8     @echo "run cmd ls ..."    # 命令前面留Tab,直观表达
 9     @ls
10 endef
11 
12 test :
13     @echo "foo => $(foo)"
14     ${cmd}    # 这里的 ${cmd} 前面必须有Tab

6. makefile中的环境变量(全局变量)

  (1)makefile中使用系统环境变量

    makefile中可以直接使用系统中的环境变量(系统环境变量的本质就是全局的键值对)

    • 如果 makefile 中定义了同名变量,那么环境变量将会被覆盖
    • 运行 make 时指定 "-e" 选项,优先使用环境变量 
 1 # 测试makefile中环境变量的使用
 2  
 3 # 例1
 4 PATH := my path
 5 
 6 test :
 7      @echo "PATH => $(PATH)"
 8  
 9  #输出结果: my path
10   
11  # 例2
12  test :
13      @echo "PATH => $(PATH)"
14  
15  # 输出结果 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
16  
17  # 例3
18  PATH := my path
19  
20  test :
21      @echo "PATH => $(PATH)"
22  
23  # 执行 make 时添加 -e 选项
24  # 输出结果:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

  (2)为什么要在makefile中使用环境变量?

    — 优势:环境变量可以在所有 makefile 中使用

    — 劣势:过多的依赖于环境变量会导致移植性降低

  (3)变量在不同makefile之间的传递方式

    — 直接使用系统中的环境变量(不推荐,太依赖于系统)

    — 使用 export 定义环境变量进行传递(定义临时的环境变量)

    — 定义 make 命令行变量进行传递(推荐)

 1 # makefile 演示变量在不同 makefile 之间的传递方式
 2 
 3 # makefile文件
 4 
 5 HOME := my home
 6 var := swj
 7 
 8 test :
 9     @echo "HOME => $(HOME)"
10     @echo "var => $(var)"
11     @echo "make another file ..."
12     @$(MAKE) -f makefile.1 
13 
14 # makefile.1文件
15 
16 test :
17     @echo "HOME => $(HOME)"
18     @echo "var => $(var)"
19 
20 # 输出结果:
21 HOME => my home
22 var => swj
23 make another file ...
24 make[1]: 正在进入目录 `/home/swj/12-plan/makefile/lesson-7'
25 HOME => my home
26 var => 
27 make[1]:正在离开目录 `/home/swj/12-plan/makefile/lesson-7'

    — 该例说明的问题:

      • makefile中定义环境变量会把系统中该环境变量的值覆盖
      • 在makefile中定义的变量只具有 "文件作用域" ,在makefile.1  中不能使用

    — 遇到的坑:由于我的ubuntu没有安装Java,没有使用JAVA_HOME这个环境变量,使用的是PATH环境变量,PATH := my path ,导致后面调用make出现错误。原因是由于make是一个进程,它会复制系统的一份环境变量来使用,复制的环境变量作用域是 "进程域", 上面 PATH := my path 就把该环境变量的值更改了,导致后面调用make时出现 "找不到命令" 的错误。环境变量之间的关系如下图所示。 

        

 1 # 演示 export 和 命令行传递变量给其它makefile
 2 
 3 # makefile 文件
 4 HOME := my home
 5 export var := swj
 6 new := cool
 7 
 8 test :
 9     @echo "HOME => $(HOME)"
10     @echo "var => $(var)"
11     @echo "make another file ..."
12     @$(MAKE) -f makefile.1 
13     @$(MAKE) -f makefile.1 new:=$(new)
14 
15 # makefile1 文件
16 test :
17     @echo "HOME => $(HOME)"
18     @echo "var => $(var)"
19     @echo "new => $(new)"
20 
21 # 输出结果:
22 HOME => my home
23 var => swj
24 make another file ...
25 make[1]: 正在进入目录 `/home/swj/12-plan/makefile/lesson-7'
26 HOME => my home
27 var => swj
28 new => 
29 make[1]:正在离开目录 `/home/swj/12-plan/makefile/lesson-7'
30 make[1]: 正在进入目录 `/home/swj/12-plan/makefile/lesson-7'
31 HOME => my home
32 var => swj
33 new => cool
34 make[1]:正在离开目录 `/home/swj/12-plan/makefile/lesson-7'

7. makefile中的局部变量

  (1)目标变量

    — 作用域只在指定目标及连带规则中

      • target : name<assignment>value
      • target : override name<assignment>value
1 # 测试目标变量的使用
2 
3 var := swj
4 test : var := test-var
5 
6 test :
7     @echo "var => $(var)"
8 
9 # 输出结果:var => test-var

  (2)模式变量

    — 模式变量是目标变量的扩展

    — 作用域只在符合模式的目标及连带规则中

      • pattern : name <assignment> value
      • pattern : override name <assignment> value 
 1 # 测试模式变量的使用
 2 
 3 var := swj
 4 %t : var := test-var
 5 
 6 test :
 7     @echo "var => $(var)"
 8     
 9 another :
10     @echo "var => $(var)"
11 
12 # 输出结果
13 var => test-var
14 var => swj 

8 makefile中的变量类型

  (1)全局变量:makefile 外部定义的环境变量。

  (2)文件变量:makefile 中定义的变量。

  (3)局部变量:指定目标的变量。

15.变量实战操作

系统变量

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

 系统常量(可用 make -p 查看):执行make -p >mp;把所有的系统常量内容输出到mp文件;不想写命令,可以使用这些系统常量替代

好处:可以跨平台
AS        汇编程序的名称, 默认为 as

CC       C编译器名称   默认 cc
CPP     C预编译器名称默认 cc -E
CXX     C++ 编译器名称默认g++

RM      文件删除程序别名 默认 rm -f 

 自定义变量

定义:变量名=变量值

使用:$(变量名)/$变量名}

变量替换 ,第一版

 变量替换,第二版:

变量替换,第三版:

7.makefile条件判断和循环语句

1. makefile中的条件判断语句

  (1)makefile 中支持条件判断语句

    — 可以根据条件的值来决定 make 的执行

    — 可以比较 两个不同变量 或者 变量和常量值 方法 

      ifxxx  (arg1,arg2)

      # for true

      else

      # for false

      endif

  (2)注意事项:条件判断语句只能用于控制 make 实际执行的语句 ; 但是不能控制规则中命令的执行过程。

2. 条件判断语句的语法

  (1)常用形式

    ifxxx   (arg1,arg2)

  (2)其它合法形式

    ifxxx   "arg1"  "arg2"

      ifxxx   'arg1'   'arg2'

    ifxxx   "arg1"   'arg2'

    ifxxx   'arg1'   "arg2" 

  (3)小贴士(注意条件判断语句的格式)

       

3. 条件判断关键字

    

1 .PHONY : test
 2 
 3 var1 := A
 4 var2 := $(var1)
 5 var3 :=
 6 
 7 test:
 8     ifeq ($(var1),$(var2)) 
 9         @echo "var1 == var2"
10     else
11         @echo "var1 != var2"
12     endif
13     
14     ifneq ($(var2),)
15         @echo "var2 is NOT empty"    
16     else
17         @echo "var2 is empty"    
18     endif
19     
20     ifdef var2
21         @echo "var2 is NOT empty"    
22     else
23         @echo "var2 is empty"    
24     endif
25     
26     ifndef var3
27         @echo "var3 is empty"    
28     else
29         @echo "var3 is NOT empty"    
30     endif

4. 一些工程经验

  (1)条件判断语句之前可以有空格,但是不能有 Tab 字符('\t')

  (2)在条件判断语句中不要使用自动变量(@,

^ , $<)

  (3)一条完整的条件语句必须位于同一个 makefile 中

  (4)条件判断类似C语言中的宏,预处理阶段有效,执行阶段无效

  (5)make 在加载 makefile 时

      • 首先计算表达式的值(赋值方式不同,计算方式不同)
      • 根据判断语句的表达式决定执行的内容    

 1 .PHONY : test
 2 # 赋值方式不同,计算方式不同
 3 var1 :=
 4 var2 := $(var1)
 5 
 6 var3 =
 7 var4 = $(var3)  # 由于是递归赋值,因此不能确定变量的值,默认为def
 8 
 9 #var3 = 3 
10 
11 test:
12     ifdef var1 
13         @echo "var1 is defined"
14     else
15         @echo "var1 is NOT defined"
16     endif
17     
18     ifdef var2
19         @echo "var2 is defined"    
20     else
21         @echo "var2 is NOT defined"    
22     endif
23     
24     ifdef var3 
25         @echo "var3 is defined"
26     else
27         @echo "var3 is NOT defined"
28     endif
29     
30     ifdef var4
31         @echo "var4 is defined"    
32     else
33         @echo "var4 is NOT defined"    
34     endif
35  
36 #输出结果
37 var1 is NOT defined
38 var2 is NOT defined
39 var3 is NOT defined
40 var4 is defined

5.实战操作

条件判断实操

ifeq     判断是否相等,相等返回 true,不相等返回 false

ifneq   判断是否不相等,相等返回 true,不相等返回 false

ifdef    判断变量是否存在,存在返回 true,不存在返回 false

lfndef  判断变量是否不存在,不存在返回 true,存在返回 false


lfeq ($(A),123)
else
endif

lfdef A
else
endif
ifeq,ifneq 与条件之间要有空格,不然会报错可以只有 if,没有 else,但是没有 elseif 的用法,如果要实现 elseif,就要写嵌套


命令行传参: make-fMakefileFLAG=456 如果有Makefile ,则可写成 make FLAG=456

 

通过make传参

 

 循环语句实操

8.makefile函数定义和调用

函数定义和调用

1. makefile中的函数

  (1)make 解释器提供了一系列的函数供 makefile 调用 (预定义函数)

  (2)在 makefile 中支持自定义函数实现,并调用执行 (自定义函数)

  (3)通过 define 关键字实现自定义函数

2. 在 makefile 中自定义函数

2.1 自定义函数的语法

  函数定义:

     其中,$(0) 代表被调用的函数名(1),

(2) , $(3)... 代表调用函数时后面的传参

     

  函数调用:

    

2.2 深入理解自定义函数

  (1)自定义函数本质是一个多行变量,无法直接调用,需要使用 call 关键字,$(call func , param1, param2, ...)  首先将参数传递到多行变量里的命令中,然后将命令原地展开

  (2)自定义函数是一种过程调用,没有任何的返回值。函数和过程是两种东西,C 语言中进行了统一,加 return 是函数,不加 return 是过程。

  (3)自定义函数用于定义命令集合,并应用于规则中

实验1:自定义函数的使用】

 1 # 演示自定义函数的使用
 2 
 3 .PHONY : test
 4 
 5 define func1
 6     @echo "My name is $(0)"
 7 endef
 8 
 9 define func2
10     @echo "my name is $(0)"
11     @echo "param 1 => $(1)"
12     @echo "param 2 => $(2)"
13 endef
14 
15 test :
16     $(call func1)
17     $(call func2, swj, cool)
18 
19 # 输出结果
20 My name is func1
21 my name is func2
22 param 1 =>  swj
23 param 2 =>  cool

实验2:深入理解自定义函数】

 1 .PHONY : test
 2 
 3 define func1
 4     @echo "My name is $(0)"
 5 endef
 6 
 7 test :
 8     $(call func1)
 9     $(func1)
10 
11 # 输出结果:注意两者的区别,call会传参后原地展开,多行变量不会传参直接原地展开
12 My name is func1
13 My name is 

3. make 解释器中的预定义函数

3.1 预定义函数的概念

  (1)make 的函数提供了处理文件名、变量和命令的函数

  (2)可以在需要的地方调用函数来处理指定的参数

  (3)函数在调用的地方被替换为处理结果

3.2 预定义函数的调用

  

3.3 自定义函数和预定义函数差别

  (1)makefile 中不支持真正意义上的自定义函数,自定义函数只是 call 函数的实参,并在 call 中执行

  (2)自定义函数的本质是多行变量

  (3)预定义的 call 函数在调用时会将参数传递给多行变量(call 的对象必须为多行变量,否则不起作用,不会传递参数)

编程实验

 1 .PHONY : test
 2 
 3 define func1
 4     @echo "My name is $(0)"
 5 endef
 6 
 7 define func2
 8     @echo "My name is $(0)"
 9 endef
10 
11 var1 := $(call func1)
12 var2 := $(call func2)
13 var3 := $(abspath ./)
14 
15 test :
16     @echo "var1 => $(var1)"
17     @echo "var2 => $(var2)"
18     @echo "var3 => $(var3)"
19 
20 # 输出结果
21 var1 =>     @echo My name is func1
22 var2 =>     @echo My name is func2
23 var3 => /home/swj/12-plan/makefile/lesson-9

 变量与函数综合实例

1. 实战需求

  (1)自动生成 target 文件夹存放可执行文件

  (2)自动生成 objs 文件夹存放编译生成的目标文件(*.o)

  (3)支持调试版本的编译选项

  (4)考虑代码的扩展性  ——> 使用变量

2. 工具原料

  — $(wildcard _pattern)

    • 获取当前工作目录中满足_pattern的文件或目录列表

  — $(addprefix _prefix, _names)

    • 给名字列表_names中的每一个名字增加前缀_prefix

3. 关键技巧

  — 自动获取当前目录下的源文件列表(函数调用)

    • SRCS := $(wildcard *.c)  

  — 根据源文件列表生成目标文件列表(变量的值替换)

    • OBJS := $(SRCS:.c=.o)  

  — 对每一个目标文件列表加上路径前缀(函数调用)

    • OBJS := $(addprefix path/, $(OBJS))

4. 规则中的模式替换(目录结构)

  — $(OBJS) : %.o : %.c 形式的模式替换对象是$(OBJS)对应的目标列表

  — %.o : %.c 形式的模式替换对象是当前工作目录

      

5. 编译规则的依赖

  首先创建 objs 和 target 文件夹分别用来存放 .o 中间文件和可执行程序,然后创建 .o 中间文件和可执行程序,需要在前面添加前缀(放入前面创建的目录中)。

  根据(DIRS)、

(OBJS)两个依赖创建文件夹和相应的 .o 文件。后面再由模式匹配生成 .o 文件。

  (自上而下的一种委派)

 1 # 考虑代码的扩展性
 2 CC := gcc
 3 MKDIR := mkdir
 4 RM := rm -rf
 5 
 6 # 存放可执行程序和中间文件的文件夹
 7 DIR_OBJS := objs
 8 DIR_TARGET := target
 9 
10 DIRS := $(DIR_OBJS) $(DIR_TARGET)
11 
12 # 可执行程序
13 TARGET := $(DIR_TARGET)/hello-makefile
14 
15 # .o中间文件
16 SRCS := $(wildcard *.c)
17 OBJS := $(SRCS:.c=.o)
18 OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
19 
20 .PHONY : rebuild clean all
21 
22 $(TARGET) : $(DIRS) $(OBJS)
23     $(CC) -o $@ $(OBJS)
24     @echo "Target File ==> $@"
25 
26 $(DIRS) :
27     $(MKDIR) $@
28     
29 # 增加debug选项-g
30 $(DIR_OBJS)/%.o : %.c
31     ifeq ($(DEBUG),true)
32         $(CC) -o $@ -c -g $^
33     else
34         $(CC) -c -o $@ $^
35     endif
36     
37 rebuild : clean all
38 
39 all : $(TARGET)
40 
41 clean : 
42     $(RM) $(DIRS)
43     

其他实操

6. 小结

  — 目录可以成为目标的依赖,在规则中创建目录

  — 预定义函数是makefile实战时不可或缺的部分

  — 规则中的模式匹配可以直接针对目录中的文件

  — 可以使用命令行变量编译特殊的目标版本

9.makefile调用shell命令


10.makefile嵌套调用

第一版:

第二版:


11.makefile路径搜索


12.makefile同样部分做公共头文件

第一版本:

工程1 的Makefile

工程2的makefile

公共的makefile

第二版本:

工程1 的Makefile

工程2的makefile

公共的makefile(修改为部分)

第三版:

相关知识讲解

Makefie 中,都是先展开所有变量,再调用指令
=      赋值,但使用终值,就是不管变量请用写在规值前还是值后,调用时都是取终值

:=     也是赋值,但是只要当前行及之前的代码影响,而不会受后面的值影响

正确的写法使用:=赋值


13.makefile编译静态链接库和动态链接库


14.make_install实现


15.makefile其它语法使用说明

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Makefile是一种用来构建和管理程序的工具,它通过一个文本文件来指导编译器如何去编译、链接程序。理解并掌握Makefile的使用可以提高程序的编译效率和可维护性。下面是关于Makefile入门到精通的解释: 入门阶段: 在初学者阶段,需要掌握Makefile的基本语法和规则。学习如何编写一个简单的Makefile,并且能够使用Makefile来管理和编译一个简单的程序。掌握Makefile的目标、依赖关系、命令和变量的使用。 进阶阶段: 在进阶阶段,需要了解更多的Makefile的高级特性和技巧。学习如何使用条件语句、循环语句和函数来编写更灵活和复杂的Makefile。了解如何使用Makefile来管理多个源文件和目录结构。 精通阶段: 在精通阶段,需要深入理解Makefile背后的原理和机制。了解Makefile的工作原理,包括依赖关系的自动推导、文件更新的判断和并行编译等。还需要掌握一些高级的技巧,例如使用Makefile来实现代码自动生成、跨平台编译项目的自动化构建等。 实践阶段: 在实践阶段,需要应用所学的知识来解决实际的编译和构建问题。学会分析和优化Makefile,以提高构建过程的效率和可维护性。实践中可能还需要了解如何与其他构建工具和版本控制系统进行集成,以及如何处理复杂的项目依赖关系。 总之,掌握Makefile需要从入门到精通经过一定的学习和实践过程。通过不断的学习和实践,逐渐提高对Makefile的理解和应用能力,才能真正驾驭Makefile并充分发挥其作用。 ### 回答2: Makefile是一种用于自动化编译和构建软件的脚本文件。它是基于依赖关系的构建工具,通过定义文件依赖关系和编译规则,可以自动检测源代码的变化并重新编译相关文件,从而提高软件开发的效率。 要学习Makefile,首先需要了解Makefile的基本语法和规则。Makefile由多个规则组成,每个规则包含目标文件、依赖文件和命令。目标文件是生成的文件,依赖文件是生成目标文件所需的源文件或其他目标文件,命令是生成目标文件的具体步骤。 在Makefile中,可以定义变量来存储常用的路径或编译选项,这样可以方便地在多个规则中复用。还可以使用条件判断、循环和函数等高级语法来实现更复杂的功能。 经常使用的命令有make、make clean和make install。make命令用于编译源代码并生成目标文件,如果源文件有更新,make会自动重新编译相关文件。make clean命令用于清理生成的目标文件和临时文件,make install命令用于安装生成的可执行文件或库文件到指定位置。 学习Makefile还需要掌握一些常用的编译规则和选项。例如,可以通过定义编译规则来指定编译器、编译选项和链接选项。可以使用特殊变量$@表示目标文件,$^表示所有的依赖文件,$<表示第一个依赖文件。还可以使用通配符和模式匹配来处理多个文件或目录。 除了基本的语法和规则,还可以学习一些高级的技巧和技术,例如使用配置文件、自动化测试、并行编译等。通过不断实践和积累经验,可以逐渐提高对Makefile的掌握程度,从入门到精通。 总结来说,要从入门到精通Makefile,需要掌握基本的语法和规则,学习常用的命令和选项,并通过实际项目的练习来加深理解和提高技能水平。 ### 回答3: makefile是一种用于自动化构建和管理项目的工具。它可以根据一组规则,自动推断出需要重新编译的文件,并且根据这些规则自动执行相应的指令,从而简化了项目的构建流程和维护工作。 首先,我们需要了解makefile的基本语法和组成部分。makefile由一系列规则组成,每个规则包含一个目标、依赖关系和执行的指令。目标是需要生成的文件,依赖关系是该文件生成所依赖的文件,指令则是实际执行的操作。 其次,学习makefile中的变量。变量可以用于存储常用的路径、命令等信息。使用变量可以简化项目配置和维护,提高可维护性。 然后,理解makefile中的模式规则。模式规则是一种通用的规则,可以根据目标和依赖的模式,推断出需要执行的指令。使用模式规则可以减少重复的规则定义,提高makefile的灵活性。 接下来,学习条件判断和循环控制。条件判断可以根据不同的情况选择执行不同的指令,循环控制可以重复执行某些指令。这些功能可以使makefile更加灵活和自动化。 最后,掌握makefile中的常用函数和命令。makefile提供了一系列内置函数和命令,用于处理文本、路径、文件等操作。熟练掌握这些函数和命令,可以提高makefile的处理能力和效率。 总结而言,要精通makefile需要掌握其基本语法、规则定义、变量使用、模式规则、条件判断、循环控制、内置函数和命令等知识。通过实践和不断学习,掌握这些内容后,就可以灵活运用makefile来构建和管理项目,提高开发效率和代码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值