一口Linux 发表于 2021-10-18 19:52:22

利用Makefile给多文件、多目录C源码创建工程

0. 媒介

粉丝留言,想知道如何使用Makefile给多个文件和多级目录建立一个工程,必须安排!
关于Makefile的入门参考文章,可以先看这篇文章:
《Makefile入门教程》
为了让大家有个更加直观的感受,一口君将之前写的一个小项目,本篇在该项目底子上进行修改。
该项目详细计划和代码,见下文:
《从0写一个《电话号码管理体系》的C入门项目【适合初学者】》
一、文件

好了,开始吧!
我们将该项目标所有功能函数放到以该函数名命名的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.c11 directories, 22 files直接看下编译结果吧:
peng@ubuntu:/mnt/hgfs/code/phone$ makemake: Entering directory '/mnt/hgfs/code/phone/allfree'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/allfree'make: Entering directory '/mnt/hgfs/code/phone/create'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/create'make: Entering directory '/mnt/hgfs/code/phone/delete'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/delete'make: Entering directory '/mnt/hgfs/code/phone/display'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/display'make: Entering directory '/mnt/hgfs/code/phone/init'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/init'make: Entering directory '/mnt/hgfs/code/phone/login'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/login'make: Entering directory '/mnt/hgfs/code/phone/menu'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/menu'make: Entering directory '/mnt/hgfs/code/phone/search'make: Nothing to be done for 'all'.make: Leaving directory '/mnt/hgfs/code/phone/search'make: Entering directory '/mnt/hgfs/code/phone/main'make: Nothing to be done for 'all'.make: 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 -lpthreadphone make done! 运行结果如下:
https://p9.toutiaoimg.com/large/pgc-image/7e37137c322049eeb7efbfc6e430768c
二、Makefile常用底子知识点
符号'@' '' '$' '-' '-n '的说明




[*]'@'
通常makefile会将其执行的命令行在执行前输出到屏幕上。
如果将‘@’添加到命令行前,这个命令将不被make回显出来。
比方:
@echo--compiling module----;// 屏幕输出--compiling module----echo--compiling module----;// 没有@ 屏幕输出echo--compiling module----   
[*]' - '
通常删除,创建文件如果碰到文件不存在或者已经创建,那么盼望忽略掉这个错误,继续执行,就可以在命令前面添加 -,
-rm dir;-mkdir aaadir;
[*]' $ '
美元符号$,主要扩展打开makefile中定义的变量
[*]' $$ '
$$ 符号主要扩展打开makefile中定义的shell变量


wildcard

说明: 列出当前目录下所有符合模式“ PATTERN”格式的文件名,并且以空格分开。“ PATTERN”使用shell可识别的通配符,包括“ ?”(单字符)、“ *”(多字符)等。 示例:
$(wildcard *.c) 返回值为当前目录下所有.c 源文件列表。
patsubst

说明:把字串“ x.c.c bar.c”中以.c 结尾的单词替换成以.o 结尾的字符。 示例:
$(patsubst %.c,%.o,x.c.c bar.c)函数的返回结果 是
x.c.o bar.o notdir

说明:去除文件名中的路径信息 示例:
SRC = ( notdir ./src/a.c ) 去除文件a . c 的路径信息 , 使用 (notdir ./src/a.c) 去除文件a.c的路径信息,使用 (notdir./src/a.c)去除文件a.c的路径信息,使用(SRC)得到的是不带路径的文件名称,即a.c。
包含头文件路径

使用-I+头文件路径的方式可以指定编译器的头文件的路径 示例:
INCLUDES = -I./inc$(CC) -c $(INCLUDES) $(SRC) addsuffix

函数名称:加后缀函数—addsuffix。 语法:
$(addsuffix SUFFIX,NAMES…) 函数功能:为“NAMES…”中的每一个文件名添加后缀“SUFFIX”。参数“NAMES…” 为空格分割的文件名序列,将“SUFFIX”追加到此序列的每一个文件名 的末尾。 返回值:以单空格分割的添加了后缀“SUFFIX”的文件名序列。 函数说明: 示例:
$(addsuffix .c,foo bar) 返回值为
foo.c bar.c 包含另外一个文件:include

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。 比如命令
include file.dep即把file.dep文件在当前Makefile文件中展开,亦即把file.dep文件的内容包含进当前Makefile文件
在 include前面可以有一些空字符,但是绝不能是键开始。
foreach

foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的 语法是:
$(foreach ,, )这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。
每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,最后当整个循环竣事时,所返回的每个字符串所构成的整个字符串(以空格分隔)将会是foreach函数的返回值。
所以,最好是一个变量名,可以是一个表达式,而中一般会使用这个参数来依次枚举中的单词。
举例:
names := a b c dfiles := $(foreach n,$(names),$(n).o)上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。
留意,foreach中的参数是一个临时的局部变量,foreach函数执行完后,参数的变量将不在作用,其作用域只在foreach函数当中。
call

“ call”函数是唯一一个可以创建定制化参数函数的引用函数。 使用这个函数可以实现对用户自己定义函数引用。 我们可以将一个变量定义为一个复杂的表达式,用“ call”函数根据不同的参数对它进行展开来得到不同的结果。
函数语法:
$(call variable,param1,param2,...)函数功能: 在执行时,将它的参数“ param”依次赋值给临时变量“ $(1)”、“ $(2)” call 函数对参数的数目没有限制,也可以没有参数值,没有参数值的“ call”没有任何实际存在的意义。 执行时变量“ variable”被展开为在函数上下文有用的临时变量,变量定义中的“ $(1)”作为第一个参数,并将函数参数值中的第一个参数赋值给它; 变量中的“ $(2)”一样被赋值为函数的第二个参数值; 依此类推(变量**$(0)**代表变量“ variable”本身)。 之后对变量“ variable” 表达式的计算值。
返回值: 参数值“ param”依次替换“ $(1)”、“ $(2)”…… 之后变量“ variable”定义的表达式的计算值。
函数说明:

[*]函数中“ variable”是一个变量名,而不是变量引用。因此,通常“ call”函数中的“ variable”中不包含“ $”(当然,除非此变量名是一个计算的变量名)。
[*]当变量“ variable”是一个 make 内嵌的函数名时(如“ if”、“ foreach”、“ strip”等),对“ param”参数的使用需要留意,因为不合适或者不正确的参数将会导致函数的返回值难以预料。
[*]函数中多个“ param”之间使用逗号分割。
[*]变量“ variable”在定义时不能定义为直接展开式!只能定义为递归展开式。
函数示例:
reverse = $(2)$(1)foo = $(call reverse,a,b)all: @echo "foo=$(foo)"执行结果:
foo=ba即a替换了替换了(2)
三、编译详细说明

https://p5.toutiaoimg.com/large/pgc-image/b6125b12e29d4d6daee2513d6bbcdaa7
我们在根目录下执行make命令后,详细步调如下:

[*]include scripts/Makefile :将文件替换到当前位置,
[*]使用默认的目标all,该目标依靠于$(Target) $(Target) 在scripts/Makefile中定义了,即phone
[*]而$(Target)依靠于mm
[*]mm这个目标会执行
@ $(foreach n,$(Modules),$(call modules_make,$(n)))Modules是所有的目录名字集合, foreach 会遍历字符串$(Modules)中每个词语, 每个词语会赋值给n, 同时执行语句:
call modules_make,$(n)
[*]modules_make 被$(MAKE) -C $(1)所替换,
$(MAKE) 有默认的名字make -C:进入子目录执行make $(1) :是步调4中$(n),即每一个目录名字
终极步调4的语句就是进入到每一个目录下,执行每一个目录下的Makefile

[*]进入某一个子目录下,执行Makefile 默认目标是all,依靠Objs
Objs := $(patsubst %.c,%.o,$(Source))patsubst 把字串$ource中以.c 结尾的单词替换成以.o 结尾的字符 而
Source := $(wildcard ./*.c)wildcard 会枚举出当前目录下所有的.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 会将 /*.o追加到$(Modules)中所有的词语后面,也就是我们之前在子目录下编译生成的所有的.o文件 $(Libs) :替换为-lpthread,即所需要的动态库
大家可以根据这个步调,来分析一下执行make clean时,执行步调
完整的实例步伐请 转发,后台回复:电话号码管理
《电话号码管理-makefile版.rar》

風輕雲淡-鐡馬錔冰河 发表于 2021-10-18 23:58:50

希望能做一期把功能编译成动态库(或静态库)后,主程序再调用先编译好的动态库的makefile教程。

chris2020 发表于 2021-10-19 02:47:12

转发了

满忆小屋 发表于 2021-10-19 01:53:21

转发了

正义1987 发表于 2021-10-18 22:59:16

转发了

迷途的陀螺 发表于 2021-10-18 22:26:49

转发了

cat142691751 发表于 2021-10-18 22:47:36

转发了

平常心143188250 发表于 2021-10-18 20:44:47

转发了

Rim努力 发表于 2021-10-18 21:50:47

转发了
页: [1]
查看完整版本: 利用Makefile给多文件、多目录C源码创建工程