Makefile构建工程设计——工程构建与编写规则

来源:互联网 发布:数据挖掘工 编辑:程序博客网 时间:2024/05/17 04:15

  • 文件版本说明
  • 工程构建
    • Makefile实现目标
  • 最简单的Makefile工程文件
    • Makefile标签
    • Makefile执行语句
    • 注释隐藏显示与输出文字描述
    • 多个文件编译成一个目标文件
  • Makefile标签
    • 标签的依赖
    • 特殊标签
  • Makefile变量
    • 变量的基本书写
    • 自动化变量
      • 目标文件名集合
      • 依赖文件名集合
      • 第一个依赖的文件名
    • 头文件搜寻
    • 源码搜寻
  • Makefile嵌套
  • Makefile传参
    • 传递make中的参数
    • 传递Makefile定义的变量
      • 传递变量给自身Makefile用
      • 传递变量给嵌套Makefile用
      • Makefile嵌套注意事项
  • Makefile编译库文件
    • Makefile编译静态库文件
    • Makefile编译动态库文件
  • Makefile引用库文件

文件版本说明

版本 颁布日期 修订章节 作者 0.1 2017.02.26 撰写草稿 钟鑫 0.2 2017.02.27 添加Makefile简单工程 钟鑫 0.3 2017.03.02 添加Makefile标签描述 钟鑫 0.4 2017.03.05 添加Makefile头文件搜寻 钟鑫 0.5 2017.03.09 添加Makefile源码搜寻 钟鑫 0.6 2017.03.12 添加Makefile函数调用 钟鑫 0.7 2017.03.20 添加Makefile字符串处理函数 钟鑫 0.8 2017.04.05 添加Makefile嵌套编译 钟鑫 0.9 2017.04.10 添加Makefile编译库文件 钟鑫 1.0 2017.04.12 整理文档结构 钟鑫 1.1 2017.04.16 编译静态库文件 钟鑫 1.2 2016.04.19 编译动态库文件 钟鑫 1.3 2016.04.23 整理库文件的引用 钟鑫 1.3 2016.04.29 添加Makefile传参 钟鑫 1.4 2016.05.03 整理Makefile自动化变量 钟鑫 1.5 2016.05.07 添加Makefile传参 钟鑫 1.6 2016.05.09 添加Makefile嵌套功能 钟鑫 1.7 2016.05.10 整理Makefile标准格式 钟鑫 1.8 2016.05.14 整理文档 钟鑫

工程构建

在软件开发中,按系统不同的功能模块来分,源码成百上千,文件之间的管理和编译需要由特定的规则来创建。假如其中一个文件改变,从而需要编译整个源码,浪费了时间和精力。虽然通过软件IDE如Keil能够仅仅编译改动的文件,但在软件工程构建中,没有看得到软件的整个系统架构是如何关联的。在Linux中,通过Makefile自己定义的规则管理软件工程,从而掌握整个软件架构的设计。
工程构建的目标就是将源码编译能够运行,有嵌入式的Linux,编译后烧录到单板上运行,也有桌面级的软件,通过编译安装后即可在x86上的Linux系统运行,在一些发型版本的Linux中,通过系统自带的命令可以一键安装软件,也可以通过下载源码,通过make install命令编译安装软件。

Makefile实现目标

假如在一个文件夹中包含了整个工程,里面有一个Makefile文件。只要输入一个make命令即可完成

1、  如果这个工程没有编译过,make之后系统会自动编译整个工程;2、  如果对工程的某几个文件进行修改,make之后系统会只编译改动的文件以及关联的文件,其他文件不会编译;3、  如果对工程的某几个头文件进行修改,make之后系统会自动编译这几个头文件关联的.c文件;4、  如果工程添加了几个模块,在简单修改Makefile文件后,make之后系统会自动将新加的模块进行编译;5、  清除系统编译出来的文件,make clean之后系统会自动清理编译出来的目标文件,仅仅保留源码。

Makefile文件里面总揽了整个工程的编译规则。
make命令下支持Makefile与makefile两个文件名,但推荐使用Makefile来命令,大写显示与其他文件区别。

最简单的Makefile工程文件

1、在文件夹test_Makfile中有一个源文件:main.c

#include <stdio.h>int main(int argc,char *argv[]){    printf("test Makefile\n");}

2、建立一个Makefile文件

命令:vim Makefile

输入以下内容:

all:    gcc main.c -o main.oclean:    rm *.o

文件结构目录如下:

ghost@ghost-machine:~/workspace/testMakefile$ lsMakefile  main.c

3、运行Makefile文件编译源码并运行目标文件

命令:make以及./ main.o
ghost@ghost-machine:~/workspace/testMakefile$ makegcc main.c -o main.oghost@ghost-machine:~/workspace/testMakefile$ lsMakefile  main.c  main.oghost@ghost-machine:~/workspace/testMakefile$ ./main.o test Makefile

4、清理编译出来的目标文件,保留源码

ghost@ghost-machine:~/workspace/testMakefile$ make cleanrm *.oghost@ghost-machine:~/workspace/testMakefile$ lsMakefile  main.c

以上的步骤即包含了Makefile的主要功能:编译源码;以及最常用到的Makefile命令:make、make clean

Makefile标签

all:clean:

以上两个为Makefile的标签,在Makefile文件中会标注出特殊的颜色,Makefile关键字一定要顶格写!而且关键字要独占一行,结尾以英文的“:”结束。下一行紧跟着的为Makefile的命令。
字面解释:

all     :下方的命令为make所执行的主体,为编译代码的功能;clean   :下方命令为清除目标文件的主体,功能是清除编译过程          中产生的目标文件以及机器二进制麻或者是镜像文件。

以上描述称之为为功能命令,如果将下方执行的命令调换过来,如下所示命令仍能执行,说明功能是认为定的编码规范,因此关键字只是个标签,具体功能由用户去定义。

修改功能的Makefile文件

all:rm *.oclean:    gcc main.c -o main.o

执行

ghost@ghost-machine:~/workspace/testMakefile$ make cleangcc main.c -o main.oghost@ghost-machine:~/workspace/testMakefile$ ./test.o test Makefileghost@ghost-machine:~/workspace/testMakefile$ makerm *.o

Makefile执行语句

在Makefile中执行的语句,必须要以[Tab]键开始。

    gcc main.c -o main.o    rm *.o

以上即为Makefile所执行的命令,由用户设定,简单的Makefile命令与shell脚本的命令是一样的。

注释、隐藏显示与输出文字描述

Makefile的注释一般前面带“#”来注释,隐藏显示则在命令前面带“@”

将make clean执行的信息隐藏,在执行make clean的时候这条被隐藏的命令就不会显示出来。

all:    gcc main.c -o main.oclean:    @rm *.o

结果

ghost@ghost-machine:~/workspace/testMakefile$ makegcc main.c -o main.oghost@ghost-machine:~/workspace/testMakefile$ make cleanghost@ghost-machine:~/workspace/testMakefile$

多个文件编译成一个目标文件

通常的代码为多个文件(若干个功能模块函数和一个main),需要将这些源码编译成一个目标文件,可以采用以下方法

all:    gcc main.c module_func.c -o main.oclean:    rm *.o

Makefile标签

在执行make时,执行make 和 make clean会分别执行不同的命令,那将这两个用其他命令替代效果如下。

project1:    gcc main.c module_func.c -o main.oproject2:    rm *.o

执行结果如下

ghost@ghost-machine:~/workspace/testMakefile$ makegcc main.c module_func.c -o main.oghost@ghost-machine:~/workspace/testMakefile$ ./main.o test Makefilefile=module_func.c,func=test_func1 is called.ghost@ghost-machine:~/workspace/testMakefile$ make project2rm *.oghost@ghost-machine:~/workspace/testMakefile$ make project1gcc main.c module_func.c -o main.oghost@ghost-machine:~/workspace/testMakefile$

说明all和clean为人们约定俗成的标签,和本身功能无关,这样可以利用标签进行条件编译,通过执行make命令时传入不同的参数,来决定执行什么编译动作,编译那些文件。

标签的依赖

标签可以依赖标签,像条件编译一样,在目标标签中嵌入其他标签,达到条件联合编译的目的
书写格式,在标签“:”后面加上依赖标签,多标签以空格隔开
Makefile文件

all:project1 project2    @echo "this is all project"project1:    gcc main.c module_func.c -o main.oproject2:project3    gcc test.c -o test.oproject3:    @echo "make project3"clean:    rm *.o

执行结果

host@ghost-machine:~/workspace/testMakefile$ makegcc main.c module_func.c -o main.omake project3gcc test.c -o test.othis is all projectghost@ghost-machine:~/workspace/testMakefile$

Makefile的编译顺序:先执行标签所依赖的标签文件,再执行该标签自身的命令。

Makefile的标签也可以称做Makefile中的伪目标,和目标变量用法一样,那这样就好引起名称冲突的问题,如果源码需要生成一个clean的目标文件,但这个又和标签冲突,因为这两个的写法是一样的。为了区分make标签中的clean功能和伪目标变量的clean文件,Makefile用前置.PHONY来限定clean的功能,这样目标文件就不会与Makefile标签冲突。

all:project1 project2    @echo "this is all project"project1:    gcc main.c module_func.c -o main.oproject2:    gcc test.c -o test.o.PHONY:cheanclean:    rm *.o

特殊标签

1、.c.o:2、.cpp.o:3、%o:%c4、%o:%cpp

该类特殊标签就是将该Makefile文件下的所有源码文件(.c、.cpp)编译成目标文件(.o),使用该特殊标签时需要上一层的依赖,特殊标签在自动化变量$<有详细说明。

Makefile变量

变量的基本书写

基本的变量可以自定义命名,然后通过符号$(varname)来使用这个变量

例如:

source = main.c module_func.call:    gcc $(source) -o main.oclean:    rm *.o

其中$(source)代表了test.c这个变量

变量里面的内容除了可以直接加在变量的后面外,还可以通过符号“+=”来添加新的内容

source = main.csource += module_func.call:    gcc $(source) -o main.oclean:    rm *.o

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ makegcc main.c module_func.c -o main.oghost@ghost-machine:~/workspace/testMakefile$ ./main.o test Makefilefile=module_func.c,func=test_func1 is called.ghost@ghost-machine:~/workspace/testMakefile$ make cleanrm *.oghost@ghost-machine:~/workspace/testMakefile$

另外一种比较少用,因为符号“+=”更加简洁,执行的结果是一样的

source = main.csource := $(source) module_func.call:    gcc $(source) -o main.oclean:    rm *.o

自动化变量

自动化变量尽量少用,因为有可能不兼容其他版本的MakEFile。

目标文件名集合:$@

目标文件名,将编译出来的目标文件(.o文件)按照目标的名称命名,如果是多个目标,那么该符号“$@”则为多个目标中的集合,以下的目标文件名为project1

source = main.csource += module_func.call:project1    @echo "this is all project"project1:    gcc $(source) -o $@clean:    rm *.o

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ makegcc main.c module_func.c -o project1this is all projectghost@ghost-machine:~/workspace/testMakefile$ ./project1 test Makefilefile=module_func.c,func=test_func1 is called.ghost@ghost-machine:~/workspace/testMakefile$

此时,原先main.o的目标变成了project1的名称,$@的结果即将目标替换成了目标标签project1的名称。

按照功能书写的规则,project1已经不是目标了,而是编译代码的一个功能,所以相应的书写格式也应该改变

source = main.csource += module_func.call:main.o    @echo "this is all project"main.o:    gcc $(source) -o $@clean:    rm *.o

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ makegcc main.c module_func.c -o main.othis is all projectghost@ghost-machine:~/workspace/testMakefile$ ./main.o test Makefilefile=module_func.c,func=test_func1 is called.ghost@ghost-machine:~/workspace/testMakefile$

依赖文件名集合:$^

集合中的文件已空格间隔,如果有重复的依赖文件,该变量会去除从复的部分,仅仅保留一份
Makefile

$(DIR_TAR):$(DIR_SRC)    @echo "$^"    $(CC) $(CFLAGS) -c $^

执行结果

ghost@ghost-machine:~/workspace/testMakefile/src/module$ makemodule_func.c module_system.cgcc -g -Wall -I../../inc -I../../inc/module_system_inc -c module_func.c module_system.c

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

$<取得的结果就是依赖文件名集合中的第一个依赖文件名,
文件结构

ghost@ghost-machine:~/workspace/testMakefile/src/module$ tree.├── Makefile├── module_func.c└── module_system.c

Makefile

DIR_INC = -I../../inc\          -I../../inc/module_system_incDIR_SRC = $(wildcard ./*.c)DIR_TAR = $(patsubst %c,%o,$(DIR_SRC))CFLAGS = -g -Wall $(DIR_INC)CC = gccall:$(DIR_TAR)$(DIR_TAR):$(DIR_SRC)    $(CC) $(CFLAGS) -c $<

执行结果

ghost@ghost-machine:~/workspace/testMakefile/src/module$ makegcc -g -Wall -I../../inc -I../../inc/module_system_inc -c module_func.cgcc -g -Wall -I../../inc -I../../inc/module_system_inc -c module_func.c

依赖文件集合有两个文件module_func.c、module_system.c,所以执行语句执行了两次,每次取出集合中的第一个文件去编译,所以每次都编译到同一个文件,后面的没编译到。

要将所有文件都编译到,需要修改标签文件的格式
(DIRTAR):(DIR_SRC)修改为
1、.c.o:

all:$(DIR_TAR).c.o:    $(CC) $(CFLAGS) -c $<

执行结果

ghost@ghost-machine:~/workspace/testMakefile/src/module$ makegcc -g -Wall -I../../inc -I../../inc/module_system_inc -c module_func.cgcc -g -Wall -I../../inc -I../../inc/module_system_inc -c module_system.c

2、%c:%o

all:$(DIR_TAR)%c:%o    $(CC) $(CFLAGS) -c $<

执行结果

ghost@ghost-machine:~/workspace/testMakefile/src/module$ makegcc -g -Wall -I../../inc -I../../inc/module_system_inc   -c -o module_func.o module_func.cgcc -g -Wall -I../../inc -I../../inc/module_system_inc   -c -o module_system.o module_system.c

这个与执行以下语句是同样的效果,但以下的语句可以不依赖上一层(all)来指定目标依赖,但.c.o:和%c:%o则需要上一层的依赖才可以使用。

$(DIR_TAR):$(DIR_SRC)    $(CC) $(CFLAGS) -c $^

头文件搜寻

Makefile在编译源码时,会直接在Makefile文件层面搜索头文件,如果没有就会报错,一个工程不可能将所有头文件都放在工程表面,而是将头文件放在inc文件夹中,如果没有指定头文件路径,Makefile编译就会找不到头文件而报错,因此Makefile要指定头文件才能正常编译。

假设某些头文件放在文件夹inc下,某些头文件放在文件夹inc/module_system_inc下,那么Makefile的文件如下

DIR_INC = -I./inc \          -I./inc/module_system_incsource = main.csource += module_func.csource += module_system.cBIN_TARGET = main.oCC = gccCFLAGS = -g -Wall $(DIR_INC)all:$(BIN_TARGET)    @echo "this is all project"$(BIN_TARGET):    $(CC) $(CFLAGS) $(source) -o $@clean:    rm *.o

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ tree.├── inc│   ├── module_func.h│   └── module_system_inc│       └── module_system.h├── main.c├── Makefile├── module_func.c└── module_system.c2 directories, 6 filesghost@ghost-machine:~/workspace/testMakefile$ makegcc -g -Wall -I./inc -I./inc/module_system_inc main.c module_func.c module_system.c -o main.othis is all projectghost@ghost-machine:~/workspace/testMakefile$ ./main.o test Makefilefile=module_func.c,func=test_func1 is called.file=module_system.c,func=system_func is called.ghost@ghost-machine:~/workspace/testMakefile$ghost@ghost-machine:~/workspace/testMakefile$ make cleanrm *.oghost@ghost-machine:~/workspace/testMakefile$

源码搜寻

同样的道理,一个工程的源码也不可能放在Makefile路径下,而是应该放在src中并且按模块化分放,在Makefile中就要配置源码搜寻。
在工程中源码与头文件的树形图如下所示

ghost@ghost-machine:~/workspace/testMakefile$ tree.├── inc│   ├── module_func.h│   └── module_system_inc│       └── module_system.h├── Makefile└── src    ├── main.c    ├── module_func.c    └── module_system.c3 directories, 6 filesghost@ghost-machine:~/workspace/testMakefile$

Makefile需要加载在src文件夹下的所有.c源文件并加以编译,既然是自动搜寻就不能像上面的工程那样手动添加文件进去了,所以Makefile的书写格式如下所示

DIR_INC = -I./inc \          -I./inc/module_system_incDIR_SRC = ./srcBIN_TARGET = project.oCC = gccSRC = $(wildcard $(DIR_SRC)/*.c)CFLAGS = -g -Wall $(DIR_INC)all:$(BIN_TARGET)    @echo "this is all project"$(BIN_TARGET):    $(CC) $(CFLAGS) $(SRC) -o $@clean:    rm -r -f *.o
DIR_SRC = ./src

这个即为源码存放的路径

SRC = $(wildcard $(DIR_SRC)/*.c)

使用关键字wildcard,使得在DIR_SRC/目录下所有的.c结尾的源文件集合都放在变量SRC里面,关键字以空格间隔,可以放几个地方的目录。这个语句的功能与SRC = main.c module_func.c module_system.c
效果是一样的。

执行结果:

ghost@ghost-machine:~/workspace/testMakefile$ makegcc -g -Wall -I./inc -I./inc/module_system_inc ./src/module_func.c ./src/module_system.c ./src/main.c -o project.othis is all projectghost@ghost-machine:~/workspace/testMakefile$ ./project.o test Makefilefile=./src/module_func.c,func=test_func1 is called.file=./src/module_system.c,func=system_func is called.ghost@ghost-machine:~/workspace/testMakefile$ make cleanrm -r -f *.o

Makefile嵌套

Makefile嵌套主要是顶层一个Makefile编译后可以关联底层的Makefile进行编译。在一个工程中不止只有一个Makefile,工程应该是具有多个Makefile关联,按模块化区分。

嵌套编译代码

DIR_SRC = ./src$(MAKE) -C $(DIR_SRC)cd $(DIR_SRC) && $(MAKE)

变量MAKE在Makefile语法中定义,不需要定义。这两句都是表达一个意思,即进入目录src后运行里面的Makefile。
目录DIR_SRC = ./src只能有一个,不能包含多个。

目录结构

ghost@ghost-machine:~/workspace/testMakefile$ tree.├────inc│   ├───    module_func.h│   └───    module_system_inc│       └───    module_system.h1───   Makefile├────open_source│   ├───    main.c│   └2──    Makefile└────src    ├───    main.c3──    Makefile    └───    module        ├4──    Makefile        ├───    module_func.c        └───    module_system.c

4个Makefile的结构依次为:

顶层Makefile

DIR_SRC = ./srcOPEN_SRC = ./open_sourceCC = gccall:    @echo "this is all project"    $(MAKE) -C $(DIR_SRC)    $(MAKE) -C $(OPEN_SRC).PHONY:cheanclean:    rm -f *.o    rm -f src/*.o    rm -f src/module/*.o    rm -f open_source/*.o

open_source文件Makefile

DIR_INC = -I../../inc\          -I../../inc/module_system_incDIR_SRC = $(wildcard ./*.c)DIR_TAR = $(patsubst %c,%o,$(DIR_SRC))CFLAGS = -g -Wall $(DIR_INC)CC = gcc$(DIR_TAR):$(DIR_SRC)    $(CC) $(CFLAGS) -c $(DIR_SRC)

src文件Makefile

DIR_INC = -I../inc \          -I../inc/module_system_incSRC = main.cDIR_MOD = moduleDIR_BIN = project.oTAGMA = main.oTAG = $(wildcard ./module/*.o)CFLAGS = -g -Wall $(DIR_INC)CC = gcc$(DIR_BIN):$(TAGMA)    $(CC) $(CFLAGS) -o ../$@ $(TAGMA) $(TAG)$(TAGMA):$(SRC)    $(MAKE) -C $(DIR_MOD)    $(CC) $(CFLAGS) -c $(SRC)clean:    rm -f *.o    rm -f $(DIR_MOD)/*.o

module文件Makefile

DIR_INC = -I../inc \          -I../inc/module_system_incSRC = main.cDIR_MOD = moduleDIR_BIN = project.oTARMA = main.oTAR = $(wildcard ./module/*.o)CFLAGS = -g -Wall $(DIR_INC)CC = gcc$(DIR_BIN):$(TARMA)    $(CC) $(CFLAGS) -o ../$@ $(TARMA) $(TAR)$(TARMA):$(SRC)    $(MAKE) -C $(DIR_MOD)    $(CC) $(CFLAGS) -c $(SRC)clean:    rm -f *.o    rm -f $(DIR_MOD)/*.o

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ makethis is all projectmake -C ./srcmake[1]: Entering directory '/home/ghost/workspace/testMakefile/src'make -C modulemake[2]: Entering directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../../inc -I../../inc/module_system_inc -c ./module_func.c ./module_system.cmake[2]: Leaving directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../inc -I../inc/module_system_inc -c main.cgcc -g -Wall -I../inc -I../inc/module_system_inc -o ../project.o main.o ./module/module_func.o ./module/module_system.omake[1]: Leaving directory '/home/ghost/workspace/testMakefile/src'make -C ./open_sourcemake[1]: Entering directory '/home/ghost/workspace/testMakefile/open_source'gcc -c ./main.cgcc -o open_target.o ./main.omake[1]: Leaving directory '/home/ghost/workspace/testMakefile/open_source'ghost@ghost-machine:~/workspace/testMakefile$ lsinc  Makefile  open_source  project.o  src

Makefile传参

Makefile可以传递make中的参数,比如clean,也可以传递Makefile定义的参数

传递make中的参数

顶层Makefile

OPEN_SRC = ./open_sourceclean:     $(MAKE) -C $(OPEN_SRC) clean

嵌套层Makefile

STATICLIB = open_lib.aDYNAMICLIB = open_lib.soclean:    rm -f *.o    rm -f ./lib/$(STATICLIB)    rm -f ./lib/$(DYNAMICLIB)

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ make clean make -C ./open_source cleanmake[1]: Entering directory '/home/ghost/workspace/testMakefile/open_source'rm -f *.orm -f ./lib/open_lib.arm -f ./lib/open_lib.somake[1]: Leaving directory '/home/ghost/workspace/testMakefile/open_source'

传递Makefile定义的变量

传递的变量如果是自身Makefile使用,直接将参数传递下去,如果要传递的变量给嵌套的Makefile用,需要该变量需要设定为export才可以改变值

传递变量给自身Makefile用

比如CC变量

CC = gccall:    @echo "this is all project CC=$(CC)"

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ make this is all project CC=gccghost@ghost-machine:~/workspace/testMakefile$ make CC=g++this is all project CC=g++

传递变量给嵌套Makefile用

顶层Makefile

export LIB = STATICall:    @echo "this is all project."    @echo "CC=$(CC)"    @echo "LIB=$(LIB)"    $(MAKE) -C $(OPEN_SRC) LIB=$(MLIB)

嵌套层Makefile

all:$(OBJ)ifeq ($(LIB),STATIC)    $(AR_CONFIG) ./lib/$(STATICLIB) $^    $(RANLIB) ./lib/$(STATICLIB)else    $(CC) $(FLAGE) $(SHARE) ./lib/$(DYNAMICLIB) $^endif$(OBJ):$(SRC)    $(CC) $(FLAGE) -c $^

执行结果

ghost@ghost-machine:~/workspace/testMakefile$ makethis is all project.CC=gccLIB=STATICmake -C ./open_source LIB=STATICmake[1]: Entering directory '/home/ghost/workspace/testMakefile/open_source'gcc -g -Wall -I ./inc -fPIC -c open_module1.c open_module2.car crv ./lib/open_lib.a open_module1.o open_module2.oa - open_module1.oa - open_module2.oranlib ./lib/open_lib.amake[1]: Leaving directory '/home/ghost/workspace/testMakefile/open_source'ghost@ghost-machine:~/workspace/testMakefile$ make LIB=DTNAMIC CC=g++this is all project.CC=g++LIB=DTNAMICmake -C ./open_source LIB=DTNAMICmake[1]: Entering directory '/home/ghost/workspace/testMakefile/open_source'g++ -g -Wall -I ./inc -fPIC -shared -o ./lib/open_lib.so open_module1.o open_module2.omake[1]: Leaving directory '/home/ghost/workspace/testMakefile/open_source'

Makefile嵌套注意事项

这种方法可以传参,但有一个致命的缺点,对于顶层Makefile来说是对各个目标文件(.o)整合的过程,但对于底层的源码(.c)的改动不会关联。
想要的结果:
修改某个底层的.c文件,在顶层make一下,就会编译该文件,并更新目标文件。
现实的结果:
修改某个底层的.c文件,在顶层make一下,只看到目标文件(.o)整合的过程,没看到编译器(gcc)对源码文件的重新编译

现象
顶层Makefile

all:    @$(MAKE) -C $(DIR_SRC) LIB=$(LIB) CC=$(CC)

中间层Makefile

all:$(DIR_BIN)$(DIR_BIN):$(OBJ)    $(CC) $(CFLAGS) -o ../prj/$@ $^ $(MOD_OBJ) $(STATICLIB)$(OBJ):$(SRC)    @$(MAKE) -C module    $(CC) $(CFLAGS) -c $^

模块层Makefile

all:$(DIR_TAR).c.o:    $(CC) $(CFLAGS) -c $<

首次编译,与再次编译结果

ghost@ghost-machine:~/workspace/testMakefile$ makemake[1]: Entering directory '/home/ghost/workspace/testMakefile/src'make[2]: Entering directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../../inc -I../../inc/module_system_inc -c module_func.cgcc -g -Wall -I../../inc -I../../inc/module_system_inc -c module_system.cmake[2]: Leaving directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -c main.cgcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -o ../prj/project.o main.o ./module/module_func.o ./module/module_system.o /home/ghost/workspace/testMakefile/src/../open_source/open_lib.amake[1]: Leaving directory '/home/ghost/workspace/testMakefile/src'ghost@ghost-machine:~/workspace/testMakefile$ makemake[1]: Entering directory '/home/ghost/workspace/testMakefile/src'gcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -o ../prj/project.o main.o ./module/module_func.o ./module/module_system.o /home/ghost/workspace/testMakefile/src/../open_source/open_lib.amake[1]: Leaving directory '/home/ghost/workspace/testMakefile/src'

修改模块层的源码(.c)文件后,顶层再次运行make,但结果gcc没有编译更新后的源码

ghost@ghost-machine:~/workspace/testMakefile/src/module$ git diff .diff --git a/src/module/module_func.c b/src/module/module_func.cindex f56ffe0..198a599 100644--- a/src/module/module_func.c+++ b/src/module/module_func.c@@ -3,5 +3,5 @@ void test_func1(void) {-    printf("file=%s,func=%s is called.\n",__FILE__,__func__);+    printf("test file=%s,func=%s is called.\n",__FILE__,__func__); }ghost@ghost-machine:~/workspace/testMakefile/src/module$ cd ../../ghost@ghost-machine:~/workspace/testMakefile$ makemake[1]: Entering directory '/home/ghost/workspace/testMakefile/src'gcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -o ../prj/project.o main.o ./module/module_func.o ./module/module_system.o /home/ghost/workspace/testMakefile/src/../open_source/open_lib.amake[1]: Leaving directory '/home/ghost/workspace/testMakefile/src'

运行目标文件也没有体现最新代码的情况

ghost@ghost-machine:~/workspace/testMakefile$ ./prj/project.o test Makefilefile=module_func.c,func=test_func1 is called.file=module_system.c,func=system_func is called.open_module1_func is calledghost@ghost-machine:~/workspace/testMakefile$

根本原因是整个工程没有完整串联起来,导致在代码整合部分脱节,代码在执行模块层命令之前,就通过Makefile的特性回避了,那就是在之前就判断目标文件为最新的而不需要往下编译。这个问题在“工程组织Makefile的标准格式”中处理。

Makefile编译库文件

Makefile可以将一些公共的代码编译成库文件来给整个工程来使用。该代码独立于开发的代码之中,在修改过程中如果库文件的源码无需改动,就不需要编译该部分的代码。关于动态库文件,编译代码时引用的路径应该是绝对路径,相对路径好移植,但需要保持编译后的目标文件位置不变,才能正确引用,本次使用的是相对路径。

库文件状态

ghost@ghost-machine:~/workspace/testMakefile/open_source$├── inc│   ├── open_module1.h│   └── open_module2.h├── lib├── Makefile├── open_module1.c└── open_module2.c

Makefile编译静态库文件

将库文件编译成后缀为.a文件的形式,静态库文件就是在整个工程编译时,将库编译到目标文件中,这样会时目标文件的文件大小比较大,但好处就是库文件与目标文件捆绑在一起不用放置在特定的位置就可以引用。

编译静态库文件的方法

STATICLIB = open_lib.a$(STATICLIB):$(OBJ)    ar crv ./lib/$@ $^    ranlib ./lib/$@$(OBJ):$(SRC)    $(CC) $(FLAGE) -c $^

ar为Linux命令,将多个文件编译集合成一个文件,这里的文件指定为将依赖文件@
ar后面跟着的为ar的命令,crv为创建一个库(c)并插入模块(r),并显示打印信息(v)。

ranlib为更新静态库,更新库的有效符号表,如果后续有多个库源码加入静态库的话,在编译后需要更新一下静态库文件。

Makefile文件

STATICLIB = open_lib.aINC = -I ./incSRC = $(wildcard *.c )OBJ = $(patsubst %c,%o,$(SRC))CC = gccFLAGE = -g -Wall $(INC)AR_CONFIG = ar crvRANLIB = ranliball:$(STATICLIB)$(STATICLIB):$(OBJ)    $(AR_CONFIG) ./lib/$@ $^    $(RANLIB) ./lib/$@$(OBJ):$(SRC)    $(CC) $(FLAGE) -c $^clean:    rm -f ./*.o    rm -f ./lib/$(STATICLIB)

执行结果

ghost@ghost-machine:~/workspace/testMakefile/open_source$ makegcc -g -Wall -I ./inc -c open_module1.c open_module2.car crv ./lib/open_lib.a open_module1.o open_module2.oa - open_module1.oa - open_module2.oranlib ./lib/open_lib.a

在ar crv中如果没有加打印信息的话,会出现以下结果,链接的信息被隐藏。

ghost@ghost-machine:~/workspace/testMakefile/open_source$ makegcc -g -Wall -I ./inc -c open_module1.c open_module2.car cr ./lib/open_lib.a open_module1.o open_module2.oranlib ./lib/open_lib.a

Makefile编译动态库文件

将库文件编译成后缀为.so文件的形式,动态库在整工程编译时不会讲库文件编译到目标文件中,而是在Makefile做动态库文件的关联,在目标文件执行时根据引用的动态库文件路径去引用动态库,因此编译好后,如果是引用的动态库为相对路径的话,目标文件移动后就会找不到动态库文件。

编译动态库文件的方法

INC = -I ./inc -fPICall:$(DYNAMICLIB)$(DYNAMICLIB):$(OBJ)    $(CC) $(FLAGE) -shared -o ./lib/$@ $^$(OBJ):$(SRC)    $(CC) $(FLAGE) -c $^

-shared -o:为动态库链接文件,这里需要用gcc将目标文件集合编译生成动态库文件。

如果系统为64位的话,头文件的引用必须要加上 -fPIC,不然编译会报错,报错提示

ghost@ghost-machine:~/workspace/testMakefile/open_source$ make gcc -g -Wall -I ./inc -c open_module1.c open_module2.cgcc -g -Wall -I ./inc -shared -o ./lib/open_lib.so open_module1.o open_module2.o/usr/bin/ld: open_module1.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPICopen_module1.o: 无法添加符号: 错误的值collect2: error: ld returned 1 exit statusMakefile:14: recipe for target 'open_lib.so' failedmake: *** [open_lib.so] Error 1

Makefile文件

DYNAMICLIB = open_lib.soINC = -I ./inc -fPICSRC = $(wildcard *.c )OBJ = $(patsubst %c,%o,$(SRC))CC = gccFLAGE = -g -Wall $(INC)SHARE = -shared -oall:$(DYNAMICLIB)$(DYNAMICLIB):$(OBJ)    $(CC) $(FLAGE) $(SHARE) ./lib/$@ $^$(OBJ):$(SRC)    $(CC) $(FLAGE) -c $^clean:    rm -f *.o    rm -f ./lib/$(DYNAMICLIB)

执行结果

ghost@ghost-machine:~/workspace/testMakefile/open_source$ makegcc -g -Wall -I ./inc -fPIC -c open_module1.c open_module2.cgcc -g -Wall -I ./inc -fPIC -shared -o ./lib/open_lib.so open_module1.o open_module2.o

Makefile引用库文件

使用编译编译好的库文件或者外部使用的库文件加到Makefile里面,直接将该库文件的地址加到需要编译的目标文件中。
引用的静态库文件和动态库文件的运行方法不一样,引用静态库是将库编译进代码里面,所以目标文件移动不影响运行,但动态库不一样,如果在编译中选定了动态库的头文件,那么就不可以移动目标文件,如果移动路径的话,就会找不到动态库文件

DIR = $(shell pwd)
使用该命令可以引入编译的绝对路径,确保动态库文件找得到。

引用库文件

DIR_INC = -I../inc \          -I../inc/module_system_inc \          -I../open_source/incSRC = main.cDIR = $(shell pwd)DIR_MOD = moduleDIR_BIN = project.oTAGMA = main.oTAG = $(wildcard ./module/*.o)LIB = $(wildcard $(DIR)/../open_source/lib/*.so)#LIB = $(wildcard $(DIR)../open_source/lib/*.a)CFLAGS = -g -Wall $(DIR_INC)CC = gcc$(DIR_BIN):$(TAGMA)    $(CC) $(CFLAGS) -o $@ $(TAGMA) $(TAG) $(LIB)$(TAGMA):$(SRC)    $(MAKE) -C $(DIR_MOD)    $(CC) $(CFLAGS) -c $(SRC)

编译动态库执行结果

ghost@ghost-machine:~/workspace/testMakefile/src$ makemake -C modulemake[1]: Entering directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../../inc -I../../inc/module_system_inc -c ./module_func.c ./module_system.cmake[1]: Leaving directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -c main.cgcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -o project.o main.o ./module/module_func.o ./module/module_system.o ../open_source/lib/open_lib.so

编译静态库执行结果

ghost@ghost-machine:~/workspace/testMakefile/src$ makemake -C modulemake[1]: Entering directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../../inc -I../../inc/module_system_inc -c ./module_func.c ./module_system.cmake[1]: Leaving directory '/home/ghost/workspace/testMakefile/src/module'gcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -c main.cgcc -g -Wall -I../inc -I../inc/module_system_inc -I../open_source/inc -o project.o main.o ./module/module_func.o ./module/module_system.o ../open_source/lib/open_lib.a

执行目标文件

ghost@ghost-machine:~/workspace/testMakefile/src$ ./project.o test Makefilefile=./module_func.c,func=test_func1 is called.file=./module_system.c,func=system_func is called.open_module1_func is called

如果是编译的动态库文件,移动目标文件后会找不到路径,在Makefile中可以使用绝对路径就没有这个问题。

动态库编译移动目标文件后没有找到库文件。

ghost@ghost-machine:~/workspace/testMakefile$ ./project.o ./project.o: error while loading shared libraries: ../open_source/lib/open_lib.so: cannot open shared object file: No such file or directory