0. 前言
粉丝留言,想知道如何使用Makefile构建多文件、多级目录的项目,必须要整理一下!
关于Makefile的介绍参考文章,可以先阅读这篇文章:
””
为了让大家有更直观的体验,一口君会做一个之前写的小项目,本文会在这个项目的基础上进行修改。
本项目的详细设计及代码如下所示:
””
1. 文件
好的,让我们开始吧!
我们把项目的所有功能函数放在以函数命名的c文件中,并放在对应名称的子目录中。
例如,函数 allfree() 存储在 allfree/allfree.c 中
最终的目录结构如下图所示:
peng@ubuntu:/mnt/hgfs/code/phone$ tree .
.
├── allfree
│ ├── allfree.c
│ └── Makefile
├── create
│ ├── create.c
│ └── Makefile
├── delete
│ ├── delete.c
│ └── Makefile
├── display
│ ├── display.c
│ └── Makefile
├── include
│ ├── Makefile
│ └── phone.h
├── init
│ ├── init.c
│ └── Makefile
├── login
│ ├── login.c
│ └── Makefile
├── main
│ ├── main.c
│ └── Makefile
├── Makefile
├── menu
│ ├── Makefile
│ └── menu.c
├── scripts
│ └── Makefile
└── search
├── Makefile
└── search.c
11 directories, 22 files
我们直接看一下编译结果:
peng@ubuntu:/mnt/hgfs/code/phone$ make
make[1]: Entering directory '/mnt/hgfs/code/phone/allfree'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/allfree'
make[1]: Entering directory '/mnt/hgfs/code/phone/create'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/create'
make[1]: Entering directory '/mnt/hgfs/code/phone/delete'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/delete'
make[1]: Entering directory '/mnt/hgfs/code/phone/display'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/display'
make[1]: Entering directory '/mnt/hgfs/code/phone/init'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/init'
make[1]: Entering directory '/mnt/hgfs/code/phone/login'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/login'
make[1]: Entering directory '/mnt/hgfs/code/phone/menu'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/menu'
make[1]: Entering directory '/mnt/hgfs/code/phone/search'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/search'
make[1]: Entering directory '/mnt/hgfs/code/phone/main'
make[1]: Nothing to be done for 'all'.
make[1]: Leaving directory '/mnt/hgfs/code/phone/main'
gcc -Wall -O3 -o phone allfree/*.o create/*.o delete/*.o display/*.o init/*.o login/*.o menu/*.o search/*.o main/*.o -lpthread
phone make done!
运行结果如下:
2、Makefile常用的基础知识点 [0] 符号说明 '@' '$' '$$' '-' '-n ''@'
通常makefile在执行前会将其执行的命令行输出到屏幕上。
如果在命令行前添加“@”,则 make 不会回显该命令。
例如:
@echo --compiling module----; // 屏幕输出 --compiling module----
echo --compiling module----; // 没有@ 屏幕输出echo --compiling module----
'-'
通常删除和创建文件。 如果遇到不存在或者已经创建的文件,如果想忽略这个错误继续执行,可以在命令后面加上-。
-rm dir;
-mkdir aaadir;
'$'
美元符号$,主要是展开和打开makefile中定义的变量。
'$$'
$$符号主要扩展并打开makefile中定义的shell变量。
[1] 通配符
阐明:
列出当前目录中与模式“PATTERN”匹配并以空格分隔的所有文件名。 “PATTERN”使用shell可以识别的键值,包括“?” (单个字符)、“*”(多个字符)等
例子:
$(wildcard *.c)
返回值是当前目录中所有.c源文件的列表。
[2] 帕特萨斯特
描述:将字符串“xcc bar.c”中以.c结尾的短语替换为以.o结尾的字符。
例子:
$(patsubst %.c,%.o,x.c.c bar.c)
函数返回结果
是的
x.c.o bar.o
[3] 不目录
说明:从文件名中删除路径信息
例子:
SRC = ( notdir ./src/a.c )
要删除文件ac的路径信息,请使用(notdir./src/ac)删除文件ac的路径信息。 使用(notdir./src/ac)删除文件ac的路径信息。 使用(SRC)得到的结果不带路径的文件名编译指定源码目录,即ac。
[4]包含头文件路径
使用-I+头文件路径指定编译器头文件的路径。
例子:
INCLUDES = -I./inc
$(CC) -c $(INCLUDES) $(SRC)
[5] 添加后缀
函数名称:添加后缀函数—addsuffix。
语法:
$(addsuffix SUFFIX,NAMES…)
功能:为“NAMES...”中的每个文件名添加后缀“SUFFIX”。 参数“名称...”
对于以空格分隔的文件名序列,请将“SUFFIX”附加到该序列中的每个文件名。
结束 。
返回值: 由单个空格分隔并添加后缀“SUFFIX”的文件名序列。
功能说明:
例子:
$(addsuffix .c,foo bar)
返回值为
foo.c bar.c
[6]包含另一个文件:include
在 Makefile 中使用 include 关键字来包含其他 Makefile。 这与C语言中的#include非常相似。 包含的文件将原样放置在当前文件的包含位置。
比如命令
include file.dep
即展开当前Makefile中的file.dep文件,即将file.dep文件的内容包含到当前Makefile中。
include后可以有一些空字符,但不能以[Tab]键开头。
[7] foreach
foreach函数与其他函数特别不同。因为该函数用于循环
语法是:
$(foreach <var>,<list>,<text> )
该函数的意义就是将参数中的短语一一取出放入参数指定的变量中,然后执行包含的表达式。
每次都会返回一个字符串。 在循环过程中,返回的每个字符串都会以空格分隔。 最后,当整个循环结束时,返回的每个字符串(用空格分隔)组成的整个字符串将是foreach函数的返回值。
因此,最好使用变量名,变量名可以是表达式,该参数通常用于按顺序枚举短语。
例子:
names := a b c d
files := $(foreach n,$(names),$(n).o)
在上面的例子中,$(name)中的短语将被一一取出并存储在变量“n”中。 “$(n).o”每次根据“$(n)”计算一个值。 这些值是用空格分隔的,最终是作为foreach函数返回的,所以$(files)的值为“ao bo co do”。
请注意,foreach 中的参数是临时局部变量。 foreach函数执行后,参数的变量将不再起作用,其作用域仅在foreach函数内。
[8] 呼叫
“call”函数是唯一可以创建具有不同参数的函数的参考函数。
使用该函数可以实现对用户定义函数的引用。
我们可以将变量定义为复杂的表达式,并使用“call”函数根据不同的参数对其进行扩展,以获得不同的结果。
功能句型:
$(call variable,param1,param2,...)
功能功能:
在执行过程中,其参数“param”按顺序正式参数化为临时变量“$(1)”和“$(2)”。 呼叫”没有实际意义。
变量“variable”在执行过程中被扩展为函数上下文中有效的临时变量,变量定义中的“$(1)”作为第一个参数,将函数参数值中的第一个参数赋给它;
变量中的“$(2)”也用作函数的第二个参数值;
依此类推(变量**$(0)**代表变量“变量”本身)。
随后对变量“variable”表达式求值。
返回值:
参数值“param”替换“$(1)”、“$(2)”...,然后替换变量“variable”定义的表达式的估计值。
功能说明:
函数中的“变量”是变量名,而不是变量引用。 因此,通常“调用”函数中的“变量”中不包含“$”(当然编译指定源码目录,除非该变量名称是估计的变量名称)。 当变量“variable”是make中嵌入的函数名(如“if”、“foreach”、“strip”等)时,“param”参数的使用需要小心,因为参数不合适或不正确这将导致函数的返回值不可预测。 使用冒号分隔函数中的多个“参数”。 变量‘variable’定义时不能直接扩展! 只能定义为递归展开。
功能示例:
reverse = $(2)$(1)
foo = $(call reverse,a,b)
all:
@echo "foo=$(foo)"
结果:
foo=ba
即a代替(1),b代替(1),b代替(1),b代替(2)
3.编译详细说明
我们在根目录下执行make命令后,详细步骤如下:
include script/Makefile :替换当前位置的文件,使用默认目标 all,这取决于 $(Target)
$(Target)在scripts/Makefile中定义,它是phone,$(Target)取决于mmmm。 该目标将被执行。
@ $(foreach n,$(Modules),$(call modules_make,$(n)))
Modules是所有目录名称的集合,
foreach 将遍历字符串 $(Modules) 中的每个单词和句子,
每个习语都会给n一个形式参数,
同时执行语句:
call modules_make,$(n)
module_make 替换为 $(MAKE) -C $(1),
$(MAKE) 的默认名称为 make
-C:进入子目录并执行make
$(1):就是步骤4中的$(n),即各个目录名
步骤4最后一句是单步进入各个目录,执行各个目录下的Makefile。
进入某个子目录并执行Makefile
默认target是all,依赖于Objs
Objs := $(patsubst %.c,%.o,$(Source))
patsubst 将字符串 $source 中以 .c 结尾的短语替换为以 .o 结尾的字符
和
Source := $(wildcard ./*.c)
通配符将列出当前目录中的所有 .c 文件
所以最后第6步就是编译子目录下的所有.c文件,生成文件名对应的.o文件。
$(CC) $(CFLAGS) -o $(Target) $(AllObjs) $(Libs)
这些变量在文件scripts/Makefile中定义
$(CC) :替换为gcc,制作编译器
$(CFLAGS):替换为-Wall -O3,即编译时的优化级别
-o $(Target):生成可执行程序phone
$(AllObjs) :
AllObjs := $(addsuffix /*.o,$(Modules))
addsuffix 会在 $(Modules) 中的所有单词和句子旁边追加 /*.o,即我们之前在子目录中编译生成的所有 .o 文件
$(Libs):替换为-lpthread,所需的动态库
可以按照这一步来分析执行make clean时的执行步骤。
完整的示例程序:
链接: 提取码:57n6
《电话号码管理-makefile版本.rar》