Makefile(二)

三、make是如何工作的

在默认的方式下,也就是我们只输入make命令。那么,

    1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
    2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
    3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
    4、如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
    5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。

而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。


四、makefile中使用变量

在上面的例子中,先让我们看看edit的规则:

      edit : main.o kbd.o command.o display.o /
                  insert.o search.o files.o utils.o
            cc -o edit main.o kbd.o command.o display.o /
                       insert.o search.o files.o utils.o

我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:

     objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:

    objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)
    main.o : main.c defs.h
            cc -c main.c
    kbd.o : kbd.c defs.h command.h
            cc -c kbd.c
    command.o : command.c defs.h command.h
            cc -c command.c
    display.o : display.c defs.h buffer.h
            cc -c display.c
    insert.o : insert.c defs.h buffer.h
            cc -c insert.c
    search.o : search.c defs.h buffer.h
            cc -c search.c
    files.o : files.c defs.h buffer.h command.h
            cc -c files.c
    utils.o : utils.c defs.h
            cc -c utils.c
    clean :
            rm edit $(objects)


于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。

关于变量更多的话题,我会在后续给你一一道来。


五、让make自动推导

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。

只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。


    objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)

    main.o : defs.h
    kbd.o : defs.h command.h
    command.o : defs.h command.h
    display.o : defs.h buffer.h
    insert.o : defs.h buffer.h
    search.o : defs.h buffer.h
    files.o : defs.h buffer.h command.h
    utils.o : defs.h

    .PHONY : clean
    clean :
            rm edit $(objects)

这种方法,也就是make的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。

关于更为详细的“隐晦规则”和“伪目标文件”,我会在后续给你一一道来。


六、另类风格的makefile

即然我们的make可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的makefile吧。

    objects = main.o kbd.o command.o display.o /
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)

    $(objects) : defs.h
    kbd.o command.o files.o : command.h
    display.o insert.o search.o files.o : buffer.h

    .PHONY : clean
    clean :
            rm edit $(objects)

这种风格,让我们的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的.o文件,那就理不清楚了。


七、清空目标文件的规则

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:

        clean:
            rm edit $(objects)

更为稳健的做法是:

        .PHONY : clean
        clean :
                -rm edit $(objects)

前面说过,.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。


上面就是一个makefile的概貌,也是makefile的基础,下面还有很多makefile的相关细节,准备好了吗?准备好了就来。


转自:http://blog.youkuaiyun.com/haoel/article/details/2887

跟我一起写 Makefile 作者:陈皓 整理:祝冬华 来源网络,希望能与大家分享这份学习资料,资源分数也设置了最低值,如有侵权,请联系我删除文件。 第一部分、概述 (6)部分、关于程序的编译和链接 (6) 第三部分、Makefile 介绍 (7) 一、Makefile的规则 (7) 、一个示例 (8) 三、make是如何工作的 (9) 四、makefile中使用变量 (10) 五、让make自动推导 (11) 六、另类风格的makefile (12) 七、清空目标文件的规则 (13) 第四部分、Makefile 总述 (13) 一、Makefile里有什么? (13) 1、显式规则。 (14) 2、隐晦规则。 (14) 3、变量的定义。 (14) 4、文件指示。 (14) 5、注释。 (14) Makefile的文件名 (15) 三、引用其它的Makefile (15) 四、环境变量 MAKEFILES (16) 五、make的工作方式 (16) 第五部分、书写规则 (17) 一、规则举例 (17) 、规则的语法 (17) 三、在规则中使用通配符 (18) 四、文件搜寻 (19) 五、伪目标 (20) 六、多目标 (22) 七、静态模式 (22) 八、自动生成依赖性 (24) 第六部分书写命令 (25) 一、显示命令 (26) 、命令执行 (26) 三、命令出错 (27) 四、嵌套执行make (28) 五、定义命令包 (30) 第七部分使用变量 (30) 一、变量的基础 (31) 、变量中的变量 (32) 三、变量高级用法 (34) 四、追加变量值 (37) 五、override 指示符 (37) 六、多行变量 (38) 八、目标变量 (39) 九、模式变量 (40) 第八部分使用条件判断 (40) 一、示例 (40) 、语法 (42) 第九部分使用函数 (43) 一、函数的调用语法 (44) 、字符串处理函数 (44) 1、subst (44) 2、patsubst (45) 3、strip (45) 4、findstring (46) 5、filter (46) 6、filter-out (46) 7、sort (47) 8、word (47) 9、wordlist (47) 10、words (47) 11、firstword (48) 12、字符串函数实例 (48) 三、文件名操作函数 (48) 1、dir (48) 2、notdir (48) 3、suffix (49) 4、basename (49) 5、addsuffix (49) 6、addprefix (49) 7、join (50) 四、foreach 函数 (50) 五、if 函数 (50) 六、call函数 (51) 七、origin函数 (51) “undefined” (52) “default” (52) “file” (52) “command line” (52) “override” (52) “automatic” (52) 八、shell函数 (53) 九、控制make的函数 (53) 1、error (53) 2、warning (54) 第十部分 make 的运行 (54) 、指定Makefile (54) 三、指定目标 (55) “all” (56) “clean” (56) “install” (56) “print” (56) “tar” (56) “dist” (56) “TAGS” (56) “check”和“test” (56) 四、检查规则 (57) 五、make的参数 (57) 第十一部分隐含规则 (61) 一、使用隐含规则 (61) 、隐含规则一览 (62) 1、编译C程序的隐含规则 (63) 2、编译C++程序的隐含规则 (63) 3、编译Pascal程序的隐含规则 (63) 4、编译Fortran/Ratfor程序的隐含规则 (63) 5、预处理Fortran/Ratfor程序的隐含规则 (63) 6、编译Modula-2程序的隐含规则 (63) 7、汇编和汇编预处理的隐含规则 (64) 8、链接Object文件的隐含规则 (64) 9、Yacc C程序时的隐含规则 (64) 10、Lex C程序时的隐含规则 (64) 11、Lex Ratfor程序时的隐含规则 (65) 12、从C程序、Yacc文件或Lex文件创建Lint库的隐含规则 (65) 三、隐含规则使用的变量 (65) 1、关于命令的变量。 (65) 2、关于命令参数的变量 (66) 四、隐含规则链 (67) 五、定义模式规则 (68) 1、模式规则介绍 (68) 2、模式
<think>我们需要使用Makefile实现一个维表驱动功能。表驱动是一种编程模式,它通过查找表(lookup table)来避免复杂的条件逻辑。在Makefile中,我们可以利用变量和函数来实现类似的功能。 假设我们有一个需求:根据不同的操作系统和架构组合,执行不同的命令。例如: - 操作系统: linux, darwin (macOS), windows - 架构: amd64, arm64 我们希望构建一个维表,使得给定一个操作系统和架构,我们可以获取到对应的命令(或变量)。 在Makefile中,我们可以这样设计: 1. 定义一个维表的键(key)的生成方式:比如将操作系统和架构组合成一个字符串,例如 "linux_amd64"。 2. 定义一个变量,其名称由这个组合字符串构成,然后赋予相应的值。 然后,我们可以通过$(变量前缀_$(OS)_$(ARCH))的方式来获取对应的值。 具体步骤: 假设我们定义如下映射: linux_amd64: echo "Building for Linux x86_64" linux_arm64: echo "Building for Linux ARM64" darwin_amd64: echo "Building for macOS x86_64" darwin_arm64: echo "Building for macOS ARM64" windows_amd64: echo "Building for Windows x86_64" windows_arm64: echo "Building for Windows ARM64" 我们可以这样实现: 首先,定义操作系统和架构的变量,这些可以在调用make时传入,例如: make OS=linux ARCH=amd64 然后,定义一个维表变量,例如: TABLE_linux_amd64 = echo "Building for Linux x86_64" TABLE_linux_arm64 = echo "Building for Linux ARM64" ... 最后,通过组合变量来获取对应的命令。 我们还可以使用make的内置函数,如$(value)或$(eval)来动态获取。 但是,注意Makefile中变量名不能有空格,所以我们用下划线连接。 下面是一个示例Makefile: ```makefile # 定义表驱动的内容 TABLE_linux_amd64 := echo "Building for Linux x86_64" TABLE_linux_arm64 := echo "Building for Linux ARM64" TABLE_darwin_amd64 := echo "Building for macOS x86_64" TABLE_darwin_arm64 := echo "Building for macOS ARM64" TABLE_windows_amd64 := echo "Building for Windows x86_64" TABLE_windows_arm64 := echo "Building for Windows ARM64" # 默认的OS和ARCH,如果未在命令行指定则使用这些值 OS ?= linux ARCH ?= amd64 # 组合键 KEY := $(OS)_$(ARCH) # 从表中获取对应的命令 # 注意:这里使用间接引用,即$($(变量))的方式 COMMAND := $(TABLE_$(KEY)) # 如果找不到,则使用默认命令(可选) ifndef COMMAND COMMAND := echo "Unsupported platform: $(OS)_$(ARCH)" endif # 目标 all: @$(COMMAND) # 测试所有组合 test: @echo "Testing all combinations:" @for os in linux darwin windows; do \ for arch in amd64 arm64; do \ echo "Testing $$os $$arch:"; \ make --no-print-directory OS=$$os ARCH=$$arch; \ done; \ done ``` 解释: 1. 我们定义了一系列以“TABLE_”开头的变量,后面跟着操作系统和架构的组合。 2. 通过用户传入(或默认)的OS和ARCH,组合成KEY。 3. 然后通过变量嵌套引用($(TABLE_$(KEY)))来获取对应的命令字符串。 4. 如果找不到,则设置一个默认命令。 5. 在all目标中,执行这个命令。 6. 我们还提供了一个test目标,用于测试所有组合。 注意:在Makefile中,变量名是区分大小写的,并且我们使用`:=`进行立即赋值。 使用示例: make OS=linux ARCH=amd64 make OS=darwin ARCH=arm64 另外,test目标会遍历所有的组合并执行。 但是,上面的方法有一个缺点:如果组合很多,我们需要预先定义很多变量。另外,如果组合键中包含特殊字符(如下划线以外的字符)可能会出现问题,所以确保键的命名是有效的变量名(只包含字母、数字和下划线,且以字母开头)。 另一种方法是使用`foreach`和`if`等函数来构建一个更复杂的表,但上面的方法简单直接。 下面我们给出完整的Makefile代码:</think># 使用 Makefile 维表实现表驱动功能 在 Makefile 中实现表驱动功能是一种高效管理复杂配置的方法,特别适合处理多平台、多架构或多配置的场景。下面我将详细介绍如何实现维表驱动功能。 ## 完整解决方案 ```makefile # ==================== # 维表驱动实现 # ==================== # 定义操作系统维度 OS_LIST := linux windows darwin # 定义架构维度 ARCH_LIST := amd64 arm64 # 定义维表(操作系统 x 架构) # 格式: <os>-<arch>:<output_suffix>:<compiler_flags> define TABLE linux-amd64:.linux-x86_64:-march=x86-64 -O2 linux-arm64:.linux-arm64:-march=armv8-a -O2 windows-amd64:.win64.exe:-march=x86-64 -O2 windows-arm64:.win-arm64.exe:-march=armv8-a -static darwin-amd64:.macos-x86_64:-march=x86-64 -O2 darwin-arm64:.macos-arm64:-arch arm64 -O2 endef # 将维表转换为可解析格式 TABLE_DATA := $(subst $(newline), ,$(TABLE)) # 查找表函数 # 用法: $(call lookup,<os>,<arch>,<field_index>) lookup = $(word 3,$(filter $1-$2:%,$(subst :, ,$(TABLE_DATA)))) # 默认目标:构建所有平台版本 all: $(foreach os,$(OS_LIST), \ $(foreach arch,$(ARCH_LIST), \ bin/myapp-$(os)-$(arch)$(call lookup,$(os),$(arch),1) \ )) # 构建规则 bin/myapp-%: src/main.c @mkdir -p $(@D) @os_arch=$(subst -, ,$*); \ os=$${os_arch%% *}; \ arch=$${os_arch##* }; \ echo "Building for $$os/$$arch"; \ flags="$(call lookup,$$os,$$arch,2)"; \ gcc $$flags $< -o $@ # 清理 clean: rm -rf bin # 显示帮助信息 help: @echo "可用目标:" @echo " all - 构建所有平台版本 (默认)" @echo " clean - 清理构建文件" @echo " help - 显示此帮助信息" @echo "" @echo "支持的平台组合:" @for os in $(OS_LIST); do \ for arch in $(ARCH_LIST); do \ printf " %-10s %-7s -> %s\\n" "$$os" "$$arch" "bin/myapp-$$os-$$arch$$(call lookup,$$os,$$arch,1)"; \ done; \ done .PHONY: all clean help ``` ## 代码解释 ### 1. 维表定义 ```makefile define TABLE linux-amd64:.linux-x86_64:-march=x86-64 -O2 linux-arm64:.linux-arm64:-march=armv8-a -O2 windows-amd64:.win64.exe:-march=x86-64 -O2 windows-arm64:.win-arm64.exe:-march=armv8-a -static darwin-amd64:.macos-x86_64:-march=x86-64 -O2 darwin-arm64:.macos-arm64:-arch arm64 -O2 endef ``` - 使用 `define` 创建多行变量作为维表 - 每行格式:`<os>-<arch>:<output_suffix>:<compiler_flags>` - 包含三个字段:平台标识、输出后缀和编译标志 ### 2. 表数据处理 ```makefile TABLE_DATA := $(subst $(newline), ,$(TABLE)) ``` - 将换行符替换为空格,使表数据成为单行字符串 - 便于后续使用 Make 的文本处理函数操作 ### 3. 查找函数 ```makefile lookup = $(word 3,$(filter $1-$2:%,$(subst :, ,$(TABLE_DATA)))) ``` - 接受三个参数:OS、Arch 和要提取的字段索引 - 工作流程: 1. 将表数据中的冒号替换为空格 2. 使用 `filter` 匹配特定平台行 3. 使用 `word` 提取所需字段 ### 4. 目标生成 ```makefile all: $(foreach os,$(OS_LIST), \ $(foreach arch,$(ARCH_LIST), \ bin/myapp-$(os)-$(arch)$(call lookup,$(os),$(arch),1) \ )) ``` - 使用嵌套 `foreach` 遍历所有 OS 和 Arch 组合 - 动态生成目标路径:`bin/myapp-<os>-<arch><suffix>` - 通过查找表获取正确的文件后缀 ### 5. 模式规则 ```makefile bin/myapp-%: src/main.c @os_arch=$(subst -, ,$*); \ os=$${os_arch%% *}; \ arch=$${os_arch##* }; \ flags="$(call lookup,$$os,$$arch,2)"; \ gcc $$flags $< -o $@ ``` - 使用模式规则 `%` 匹配所有目标 - 从目标名中提取 OS 和 Arch - 调用查找函数获取编译标志 - 使用正确的标志编译程序 ## 使用示例 1. 构建所有平台版本: ```bash make ``` 2. 构建特定平台(如 Linux ARM64): ```bash make bin/myapp-linux-arm64.linux-arm64 ``` 3. 查看支持的平台: ```bash make help ``` 4. 清理构建文件: ```bash make clean ``` ## 优势与适用场景 这种表驱动方法的优势: 1. **集中管理**:所有平台配置在一个地方定义 2. **易于扩展**:添加新平台只需在表中新增一行 3. **减少重复**:避免为每个平台编写单独规则 4. **一致性**:确保所有平台使用相同构建逻辑 适用场景: - 跨平台编译(Windows/Linux/macOS) - 多架构支持(x86/ARM/RISC-V) - 多配置构建(Debug/Release/Profiling) - 多语言版本管理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值