Zhonghui

每个不曾起舞的日子,都是对生命的辜负

User Tools

Site Tools


程序:makefile:makefile

Makefile

说明

  • Make仅能用于Linux
  • Make执行编译命令
  • Make依赖的文件是Makefile
  • Make直接执行Shell命令也可以

运行

在有Makefile文件的目录下直接运行即可。make -j16代表使用16个核心去编译,根据自己机器的配置选择即可。有的时候编译的时候会出现类似“文件太大”的错误,看着不像是代码的问题,其实就是编译的临时文件太大了,编译选项里面加上-Wa,-mbig-obj即可。

语法

缩进必须使用Tab,不能使用空格
命令前加@表示执行该命令时不显示
$(CC):读取变量的值
CC=gcc:设置变量的值
目标<冒号>依赖<换行TAB>命令:描述规则的基本结构
目标(target):规则的目标,可以是中间文件、标签或者可执行文件
依赖(prerequisites):要生成Targets需要的文件
命令(recipe):任意的Shell命令,用于生成目标文件
$(<function> <arguments>):函数调用格式
ifeq、ifneq、else、endif:条件判断
$@、$%、$<、$?:部分预定义的宏、自动化变量
*、?、[]、%:四种通配符

基础逻辑

makefile的基本语法是这样的:
目标: 依赖
然后make通过时间戳判断是否需要执行命令,“时间戳机制允许 make 检查目标文件是否比其依赖文件(通常是源代码或其他目标文件)更旧。这是 make 如何决定是否需要重新构建目标文件的基础逻辑”

如果没有写[依赖]的话
(待验证)这组命令就一定会执行

关于头文件

make可以自己手动指定h文件的依赖关系(每个o文件的编译依赖于那些h文件),确保h文件有修改的时候重新编译某些文件,但是这样很繁琐(include的关系可能很复杂);另一种方式是每个o文件的编译都依赖于所有h文件,这样的坏处是h文件有任何一处变动,整个项目都要重新编译;还是一种方式是使用编译器生成的依赖关系(参数是:-MMD -MP,编译器会生成d文件表示依赖关系;引入依赖关系到Makefile:参照下方代码;这里暂且记录到此,后面再细看)

DEPS = $(OBJS:.o=.d)
-include $(DEPS)

关于构建路径

make 后面不跟路径,并没有 make .. 这种写法,make命令就是读取当前目录的Makefile然后执行
cmake 后面可以跟路径,比如 cmake ..

关于伪目标

伪目标:phony targets

伪目标像这样声明:.PHONY: all clean。这样声明之后,all 和 clean 就不会被识别为路径或者文件名之类的(即使同名的文件或者路径存在),这里有点乱,待求证

伪目标对于嵌套调用还挺重要的,看下面的内容就理解了

嵌套

怎么嵌套的?
每一个子文件夹(或者部分的子文件夹)下都有一个Makefile,负责一部分的构建任务。我们从源码的根目录执行make,然后根据Makefile中描述的依赖关系,子文件夹下的make也会被自动执行

变量可以传递吗?
每个 Makefile 默认都是上下文无关的(也就是没有变量传递),不管它们的调用关系是怎样的。但是如果需要我们也可以实现变量传递,比如使用export导出环境变量,那么后面执行的make是可以读取到这个环境变量的;或者将公用的部分写入一个公用文件然后需要的时候都include进来

那么如何调用子文件夹的Make呢?
简单来说就一句命令:

$(MAKE) -C sub_dir_name

还有一个问题是什么时候执行上面这句命令呢?我个人经验认为可以分为3类,以下举例说明:
比如当前我们在/,/src下面有一堆源文件都需要编译,这些编译的工作交给/src/Makefile,我们/Makefile只负责链接,比如我们想链接得到一个xxx.lib

  • (1)(尚未验证)我们可以直接手动调用上面的命令($(MAKE) -C src),像这样写:
# 代码的意思是,我要构建xxx.lib,虽外需要src内部编译好之后,我们才能开始构建xxx.lib
# 但是我们不管这个依赖关系,我构建xxx.lib的时候,不管src内部是不是构建好了,反正我都手动执行一下src的make
xxx.lib:
    $(MAKE) -C src
    <link command>
  • (2)(容易错!)我们可以给【构建src】这个任务取个名字(比如build_src),注意这个名字不能取成【src】,为什么呢,我们想一下如果取成【src】,Make就会把【src】识别成文件夹(因为/src真的存在,make找得到),那么这个文件夹的时间戳是不变的(应该吧),就会导致make找不到事情做,无法构建,详细看代码!
# 正确的实现
# 代码的意思是:我们需要构建xxx.lib,但是构建之前需要先完成【build_src】这个任务
xxx.lib: build_src
   <link command>
 
# 代码的意思是:为了完成【build_src】这个任务,我们执行下面的命令就可以了
build_src:
    $(MAKE) -C src
 
# 错误的实现
# 代码的意思是:xxx.lib的构建依赖于【src这个文件夹】(因为src这个文件夹是存在的)
# 而src早就创建了,时间戳是很旧的,所以这条构建命令不会触发
xxx.lib: src
   <link command>
 
# 同理,这条构建命令也不会触发
src:
    $(MAKE) -C src
  • (3)(个人常用方法)这种方法和(2)挺像的,就是使用前面提到的【伪目标】,既然src是一个存在的文件夹,那么我们把它加到伪目标的声明里面(表明,「我们这里的src不是指的那个文件夹哦」),就可以正常工作了。那么总结一下,如果涉及到【构建子文件夹】,最好是把子文件夹的名字加入到伪目标的声明中(文件是不用加的,废话
.PHONY: src
 
xxx.lib: src
   <link command>
 
src:
    $(MAKE) -C src

关于(3),AI:
“伪目标(phony targets),它们不与实际的文件关联,每次运行make时总是被执行。”
“.PHONY: 声明all 和 clean为伪目标,确保即使存在同名文件夹或文件时,这些目标也会被执行。”

例子

Makefile

all: Project
 
CC = g++
 
Target = Engine
 
InclDir = -I Support/Include
 
LinkDir = Support/Lib
 
CompFlag = -Wall -O2 -c --std=c++17 $(InclDir)
LinkFlag = -l pthread
 
Objects  = Main.o
Objects += SupportA.o
Objects += SupportB.o
 
%.o: Project/%.cpp; -@ $(CC) -o $@ $< $(CompFlag)
 
Project: $(Objects); -@ $(CC) $(LinkFlag) $(Objects) -o $(Target)
 
Auto  = @set -e; rm -f $@;
Auto += $(CC) -MM $(InclDir) $< > $@.$$$$;
Auto += sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;
Auto += rm -f $@.$$$$
 
%.d: Project/%.cpp; $(Auto)
 
-include $(Objects:.o=.d)
 
clean: ; -@ rm $(Target) $(Objects) $(Objects:.o=.d)

待整理

如果你在windows上使用mingw-make编译动态库,那么生成的是a文件和dll文件,哈哈哈混搭风格 因为编译按照mingw-make来,所以需要a文件,但是运行的时候就和编译器无关了, 需要的是dll,总不能让windows找so文件吧~ linux上的动态库没有a文件吗?只有so文件? MinGW支持链接lib文件,像这样:-lOpenImageDenoise,OpenImageDenoise.lib,太强了

Odt笔记(20221007)

/var/www/DokuWikiStick/dokuwiki/data/pages/程序/makefile/makefile.txt · Last modified: 2024/12/10 17:00 by zhonghui