Make 在我们做linux 开发中是必不可少的一部分,它在我们编写大型项目工程文件中起到非常大的作用。

Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。Make将只编译改动的代码文件,而不用完全编译。

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

下面我们通过两个个实例来学习makefile的编写:

一、Makefile编写的基本规则

我在文件夹下有如下文件

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/example1$ ls
  2. buffer.h  defs.h  files.c  main.c  makefile  utils.c
  3. fs@ubuntu:~/qiang/makefile/example1$

这里的代码就不展示了,我说一下文件包含关系:files.c需要buffer.h及defs.h ,  main.c需要defs.h  ,utils.c需要defs.h。

我把makefile文件内容展示出来:

[cpp] view plaincopy
  1. OBJS = main.o files.o utils.o
  2. CC = gcc
  3. CFLAGS = -c
  4. Test:$(OBJS)
  5. $(CC) -o Test $(OBJS)
  6. main.o:main.c defs.h
  7. $(CC) $(CFLAGS) main.c
  8. files.o:files.c buffer.h defs.h
  9. $(CC) $(CFLAGS) files.c
  10. utils.o:utils.c  defs.h
  11. $(CC) $(CFLAGS) utils.c
  12. .PHONY:clean
  13. clean:
  14. rm -rf *.o Test
  15. @echo "Clean done!"

我们开始学习makefile编写规范:Makefile的根本任务是根据规则生成目标文件。

1、规则

一条规则包含三个:目标文件,目标文件依赖的文件,更新(或生成)目标文件的命令。

规则:

<目标文件>:<依赖文件>

<更新目标的命令>

注意:命令行前面必须是一个“TAB键”,否则会编译错误!

Example

[cpp] view plaincopy
  1. hello.o: hello.c hello.h
  2. gcc -c hello.c -o hello.o

目标hello.o 依赖于hello.c,hello.h. 生成hello.o的命令时是“gcc -c hello.c -o hello.o”

2、终极目标

makefile并不会更新所有规则中的目标,它只会更新终极目标以及终极目标依赖的目标。
默认情况下makefile的第一个目标是终极目标,而且大家约定俗成的总是将all作为第一个目标。环境变量MAKECMDGOALS记录着终极目标。

[cpp] view plaincopy
  1. Test:$(OBJS)
  2. $(CC) -o Test $(OBJS)

我们这里就是终极目标

注意:终极目标必须放在第一个,其余的可以随便放!

3、多规则目标

Makefile中,一个文件可以作为多个规则的目标,这种情形就是多规则目标。
多规则目标下,以这个文件为目标的所有规则的依赖文件将会被合并成此一个依赖文件列表,但是命令不会合并,而且实际上,这多个规则中至多只能有一个规则定义了更新命令。

all:hello.o

all:hello.h

等价于  all: hello.o hello.h

[cpp] view plaincopy
  1. main.o:main.c defs.h

我们这里就是多规则目标;

4、伪目标
一般情况下目标文件是一个具体的文件,但有时候我们只需要一个标签,如目标clean。

声明伪目标:

.PHONY:  <伪目标>

伪目标只是一个标签,这意味着伪目标的时间戳总是最新的,结果就是makefile每次都会去执行更新伪目标的命令。

[cpp] view plaincopy
  1. .PHONY:clean
  2. clean:
  3. rm -rf *.o Test
  4. @echo "Clean done!"

我们这里clean就是个伪目标,我们可以看到伪目标是没有依赖文件的,只有用make来调用时,才会执行。

5、什么时候更新目标

如果目标不存在或者依赖文件中至少有一个文件的时间戳比目标新,则执行目标更新命令。

我们这里,如果main.c 或者files被修改时,都会更新目标。

6、创建和使用变量

1)定义变量

makefile的变量定义有四种方法:

1. 立即赋值 a:=b
2. 延迟赋值 a=b
3. 条件赋值 a?=b
4. 附加赋值 a+=b

它们之间的区别是:

第一种方式,会立即计算b的值,并赋值给a;
第二种方式,相当于C++和Java的引用。如果后面b的值改变了,那么a的值也会改变;
第三种方式,如果a没有定义,则相当于a=b ,否则不执行任何操作;
第四种方式,将b的值添加到a原有的值后面,再赋值给a。

2)获取变量值

$(var) //表示取变量var的值,记得当变量名多于一个字符时,使用”()”.

[cpp] view plaincopy
  1. OBJS = main.o files.o utils.o
[cpp] view plaincopy
  1. Test:$(OBJS)
  2. $(CC) -o Test $(OBJS)

这里我们可以看到变量的创建与使用。

3)预定义变量

CC  :C编译器的名称,默认值为cc 。CPP  C预编译器的名称,默认值为$(CC) -E。

[cpp] view plaincopy
  1. CC = gcc
[cpp] view plaincopy
  1. Test:$(OBJS)
  2. $(CC) -o Test $(OBJS)

这里我们可以看到预定义变量CC的使用。

其他的预定义变量:

ARFLAGS  库文件维护程序的选项,无默认值。

ASFALGS  汇编程序的选项,无默认值。

CFALGS    C编译器的选项,无默认值。
....

[cpp] view plaincopy
  1. CFLAGS = -c
[cpp] view plaincopy
  1. main.o:main.c defs.h
  2. $(CC) $(CFLAGS) main.c

这里我们可以看到CFLAGS的使用。

4)自动变量

$* 不包含扩展名的目标文件名称

$< 第一个依赖文件的名称

$@ 目标文件的完整名称

$^ 所有不重复的目标依赖文件,以空格分开

...

变量的使用有助于我们修改makefile,大大加快了我们的开发效率。

7、回显问题

我们知道,makefile中的命令行都会被打印出来的,如果遇到我们不想打印的命令怎么办?

[cpp] view plaincopy
  1. @echo "Clean done!"

在命令行前面加 @ ,就是不回显的意思,这样"echo "Clean done!" "就不会打印。

我们来看看编译效果:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/example1$ ls
  2. buffer.h  defs.h  files.c  main.c  makefile  utils.c
  3. fs@ubuntu:~/qiang/makefile/example1$ make
  4. gcc -c main.c
  5. gcc -c files.c
  6. gcc -c utils.c
  7. gcc -o Test main.o files.o utils.o
  8. fs@ubuntu:~/qiang/makefile/example1$ ls
  9. buffer.h  files.c  main.c  makefile  utils.c
  10. defs.h    files.o  main.o  Test      utils.o
  11. fs@ubuntu:~/qiang/makefile/example1$ make clean
  12. rm -rf *.o Test
  13. Clean done!
  14. fs@ubuntu:~/qiang/makefile/example1$ ls
  15. buffer.h  defs.h  files.c  main.c  makefile  utils.c
  16. fs@ubuntu:~/qiang/makefile/example1$

二、嵌套执行Makefile

在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。

例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:

subsystem: 
            cd subdir && $(MAKE)

其等价于:

subsystem: 
            $(MAKE) -C subdir

定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行make命令。

我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。

如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:

export ;

如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:

unexport ;

如: 
     
    示例一:

export variable = value

其等价于:

variable = value 
        export variable

其等价于:

export variable := value

其等价于:

variable := value 
        export variable

示例二:

export variable += value

其等价于:

variable += value 
        export variable

如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。

需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。

但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:

subsystem: 
            cd subdir && $(MAKE) MAKEFLAGS=

如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。

还有一个在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,我们会看到:

make: Entering directory `/home/hchen/gnu/make'.

而在完成下层make后离开目录时,我们会看到:

make: Leaving directory `/home/hchen/gnu/make'

当你使用“-C”参数来指定make下层Makefile时,“-w”会被自动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总是失效的。

头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。

下面我们做一个实验,学习嵌套执行Makefile编写的过程:

1、创建顶层目录

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile$ mkdir makefileTest
  2. fs@ubuntu:~/qiang/makefile$ cd makefileTest/
  3. fs@ubuntu:~/qiang/makefile/makefileTest$ mkdir f1 f2 main obj include
  4. fs@ubuntu:~/qiang/makefile/makefileTest$ ls
  5. f1  f2  include  main  obj
  6. fs@ubuntu:~/qiang/makefile/makefileTest$

在include文件夹中创建一个共用头文件,在其中输入#include <stdio.h>

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd include/
  2. fs@ubuntu:~/qiang/makefile/makefileTest/include$ vi myinclude.h
  3. fs@ubuntu:~/qiang/makefile/makefileTest/include$ cat myinclude.h
  4. #include <stdio.h>
  5. fs@ubuntu:~/qiang/makefile/makefileTest/include$

2、创建顶层Makefile文件

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ vi Makefile

内容如下:

[cpp] view plaincopy
  1. CC = gcc
  2. SUBDIRS = f1 \
  3. f2 \
  4. main \
  5. obj  //上面三个'\'后面不能有空格
  6. OBJS = f1.o f2.o main.o
  7. BIN = myapp
  8. OBJS_DIR = obj
  9. BIN_DIR = bin
  10. export CC OBJS BIN OBJS_DIR BIN_DIR    //导出环境变量,传递到下级目录
  11. all:CHECK_DIR $(SUBDIRS)
  12. CHECK_DIR:
  13. mkdir -p $(BIN_DIR)
  14. $(SUBDIRS):ECHO
  15. make -C $@ //先进入到SUBDIRS下的目录,再执行make
  16. ECHO:
  17. @echo $(SUBDIRS)
  18. @echo begin compile
  19. CLEAN:
  20. @$(RM) $(OBJS_DIR)/*.o
  21. @rm -rf $(BIN_DIR)

 3、进入在f1目录下创建makefile

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd f1
  2. fs@ubuntu:~/qiang/makefile/makefileTest/f1$ vi f1.c

内容如下:

[cpp] view plaincopy
  1. #include "../include/myinclude.h"
  2. void print1()
  3. {
  4. printf("Message f1.c\n");
  5. return;
  6. }
[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/f1$ vi Makefile

内容如下:

[cpp] view plaincopy
  1. ../$(OBJS_DIR)/f1.o:f1.c
  2. $(CC) -c $^ -o $@

4、进入f2目录

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/f1$ cd ../f2
  2. fs@ubuntu:~/qiang/makefile/makefileTest/f2$ vi f2.c

内容如下:

[cpp] view plaincopy
  1. #include "../include/myinclude.h"
  2. void print2()
  3. {
  4. printf("Message f2.c\n");
  5. return;
  6. }
[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/f2$ vi makefile

内容如下:

[cpp] view plaincopy
  1. ../$(OBJS_DIR)/f2.o:f2.c
  2. $(CC) -c $^ -o $@

5、进入main目录

[cpp] view plaincopy
  1. <a target=_blank href="mailto:fs@ubuntu:~/qiang/makefile/makefileTest/f2$"><span style="color:#000000;">fs@ubuntu:~/qiang/makefile/makefileTest/f2$</span></a> cd ../main
  2. fs@ubuntu:~/qiang/makefile/makefileTest/main$ vi main.c

内容如下:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. int main()
  3. {
  4. print1();
  5. print2();
  6. return 0;
  7. }
[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/main$ vi Makefile

内容如下:

[cpp] view plaincopy
  1. ../$(OBJS_DIR)/main.o:main.c
  2. $(CC) -c $^ -o $@

6、进入obj目录

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd obj
  2. fs@ubuntu:~/qiang/makefile/makefileTest/obj$ ls
  3. fs@ubuntu:~/qiang/makefile/makefileTest/obj$ vi Makefile

内容如下:

[cpp] view plaincopy
  1. ../$(BIN_DIR)/$(BIN) : $(OBJS)
  2. $(CC) -o $@ $^

这样我们总体架构就完成了,我们看一下树状图:

我们执行一下:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ make
  2. Makefile:3: *** commands commence before first target.  Stop.

makefile时常遇到这样的问题,汇总网上的原因如下:
1. 上一行换行符号 \ 后面有空格
2. 本行前面的空白有非法字符
1)Makefile可能是以命令行开始:以[Tab]字符开始,但不是一个合法的命令行(例如,一个变量的赋值)。命令行必须和规则一一对应。
2)第二种原因可能是一行的第一个非空字符为分号,make会认为此处遗漏了规则的“target: prerequisite”部分。

这儿我们的原因是第一个

改正后编译:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ make
  2. mkdir -p bin
  3. f1 f2 main obj
  4. begin compile
  5. make -C f1
  6. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/f1'
  7. gcc -c f1.c -o ../obj/f1.o
  8. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/f1'
  9. make -C f2
  10. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/f2'
  11. gcc -c f2.c -o ../obj/f2.o
  12. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/f2'
  13. make -C main
  14. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/main'
  15. gcc -c main.c -o ../obj/main.o
  16. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/main'
  17. make -C obj
  18. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/obj'
  19. gcc -o ../bin/myapp f1.o f2.o main.o
  20. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/obj'

执行一下:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd bin/
  2. fs@ubuntu:~/qiang/makefile/makefileTest/bin$ ls
  3. myapp
  4. fs@ubuntu:~/qiang/makefile/makefileTest/bin$ ./myapp
  5. Message f1.c
  6. Message f2.c
  7. fs@ubuntu:~/qiang/makefile/makefileTest/bin$

Linux 应用---make及makefile的编写相关推荐

  1. 【Linux】对于make/Makefile的编写

    本文目录  背景简介 细说关于make命令和makefile文件: 使用方法 为什么执行的指令是make和make clean呢? gcc如何判断文件是否需要重新执行? 背景简介 大家第一次写 hel ...

  2. Linux C语言C++ makefile文件编写

    ps: 这里 不是很明白?尤其是 后面 三个变量,什么区别? $@ 代表目标 $^ 代表全部依赖 $< 第一个依赖 $? 第一个变化的依赖 makefile makefile 命名规则makef ...

  3. Linux C : Makefile 的编写和示例

    make工具是Unix/Linux 的一个编译工具,它按照顺序读取 Makefile  或 makefile ,进行自动地有选择地执行编译链接,只对影响到的修改的文件进行重新编译,不需要对整个工程进行 ...

  4. linux中命令对c文件进行编译,Linux下C语言编译基础及makefile的编写

    这篇文章介绍在LINUX下进行C语言编程所需要的基础知识.在这篇文章当中,我们将会学到以下内容: 源程序编译 Makefile的编写 程序库的链接 程序的调试 头文件和系统求助 1.源程序的编译 在L ...

  5. Linux下shell脚本/Makefile编写

    Linux下shell脚本/Makefile编写 一.基本概念 代码变成可执行文件,叫做编译(compile):先编译这个,还是先编译那个(即编译的安排),叫做构建(build). make只是一个指 ...

  6. Linux下的gdb调试makefile的编写

    1.gdb调试 gdb就是用于调试可执行文件,要想让程序在调试的时候有提示信息在生成文件时加上-g (1)启动gdb:gdb +包含调试信息的应用程序 (2)l(只能查询包含在可执行程序里边的.c文件 ...

  7. makefile文件编写教程

    技术交流QQ群:1027579432,欢迎你的加入! 1.make介绍 gcc:编译器(gcc根据菜谱进行编译) make: linux自带的构建器(相当于一个菜谱) 构建的规则(菜谱)在makefi ...

  8. linux C编程之makefile

    linux C编程之makefile 目的:       基本掌握了 make 的用法,能在Linux系统上编程. 环境:       Linux系统,或者有一台Linux服务器,通过终端连接.一句话 ...

  9. makefile文件编写_九图记住Makefile

    本文首次编辑时间2020.07.20 最后编辑时间2020.08.03 欢迎转载,转载请附上原文链接 参考: GNU make说明文档:https://www.gnu.org/software/mak ...

最新文章

  1. 哈工大推智能荐股,能让你稳赚不赔吗?
  2. 基于VMM的Rootkit检测技术及模型分析
  3. 网页编程从入门到精通 杨凡_学习计划丨西门子S7200编程从入门到精通
  4. xml-rpc 以及 xml-rpc 在asp.net中的实现
  5. python 定义函数为什么有个长线_关于格式化:如何在Python中打破这条长线?
  6. python 获取 字典中的指定键_python中字典方法的详细教程
  7. 周三晚八点直播丨如何通过APEX 实现自动化运维
  8. elasticsearch java api查询
  9. 为什么你的发行版仍然在使用“过时的”Linux 内核? | Linux 中国
  10. thinkphp框架下的xml交互
  11. java爬虫抓取起点小说,手把手带你爬虫 | 爬取起点小说网
  12. LeetCode每日一题——592. 分数加减运算
  13. 帝国cms模板html文件夹,帝国cms的模板保存在哪里
  14. 电脑卸载了bandzip,但是在文件的打开方式里面还是有bandzip,注册表里又搜不到相关文件,如何解决?
  15. 555定时器与频率测量
  16. markdown特殊用法(三) 脚注的输入
  17. Brain Predicted Age (二)
  18. 谷歌文件系统GFS理解
  19. java任意音频格式转换MP3格式
  20. APISpace 的 星座配对API

热门文章

  1. Smart Pointer
  2. 咨询的真相8:咨询业的“前世今生”
  3. 詹森不等式_注意詹森差距
  4. 869. 重新排序得到 2 的幂
  5. 深入理解InnoDB(7)—系统表空间
  6. leetcode 643. 子数组最大平均数 I(滑动窗口)
  7. 交互式图表_如何构建罗马数字转换器和交互式罗马数字图表
  8. 【0718作业】收集和整理面向对象的六大设计原则
  9. NeuCF源码中用到的模块(函数)
  10. mysql函数之SUBSTRING_INDEX(str,/,-1)