makefile简单梳理总结总结

本文深入解析Makefile的使用场景与工作原理,介绍如何利用Makefile优化大型项目的编译流程,包括规则制定、依赖管理、目标文件更新策略,以及变量、函数、伪目标等高级特性。

 

1、使用makefile的原因是因为,当工程很大的时候,有很多文件,这些文件也有可能很分散,若直接用gcc编译则拼写gcc命令很长,很麻烦,很容易出错,所以借助makefile工具将所有编译命令写到文件中帮助gcc编译,避免出错,方便管理。

2、makefile的命名只有两种方式makefile和Makefile

3、规则中的三要素:目标,依赖,命令。

目标依赖文件

           tab键后跟命令

举例子在main函数中依次会调用到add.c min.c mul.c div.c中的函数,我们期待生成一个App,写vi makefile文件中写makefile版本1如下内容

       App:main.c add.c min.c mul.c div.c

            gcc main.c add.c min.c mul.c div.c -o App

保存makefile内容退出后,在终端输入make指令即可编译执行。

4、make指令需要预先安装,安装指令为sudo apt install make

5gcc main.c add.c min.c mul.c div.c -o App这句话的缺点是每个文件都编译了一遍,假如我们只是修改了其中某一个文件的话,其实只用编译这个修改的文件即可。在有几百个文件的系统中,每次只编译更改的文件而不是全部编译这样效率更高。因此修改makefile版本2如下所示,

App:main.o add.o min.o mul.o div.o

    gcc main.o add.o min.o mul.o div.o -o App
 

main.o:main.c

gcc -c main.c

 

add.o:add.c

    gcc -c add.c
 

min.o:min.c

    gcc -c min.c
 

mul.o:mul.c

    gcc -c mul.c
 

div.o:div.c

    gcc -c div.c

以上的makefile中,第一行是终极目标(终极目标一定要写在最前边),当生成终极目标App时候发现没有main.o 然后就会往后边找,后边找到并生成main.o后,发现还缺add.o,继续再往后找,直到终极目标完成。当我们生成App可执行文件后,若发现第二次只修改add.c一个文件时候,此时make就只编译add.c一个文件。

makefile根据终极目标从上至下递归寻找依赖,然后从下至上执行命令直到生成终极目标。

 

6makefile的工作原理:makefile检查文件是否有更新,makefile看目标文件和依赖文件的最后更新时间,若依赖文件的时间更接近现在的时间,那么就证明依赖文件最近被修改过,需要通过执行命令编译新的依赖文件来更新目标文件了。

7makefile中的文件名字出现很多次,可以使用一个变量表示它们并替换它们,方便书写。

这里我们定义变量obj obj=main.o add.o min.o mul.o div.o然后用$符取obj中的值,$符的意思就是取变量中的值。例子如下

obj=main.o add.o min.o mul.o div.o

target=App

$(target):$(obj)

gcc $(obj) -o $(target)

 

main.o:main.c

gcc -c main.c

 

add.o:add.c

    gcc -c add.c
 

min.o:min.c

    gcc -c min.c
 

mul.o:mul.c

    gcc -c mul.c
 

div.o:div.c

    gcc -c div.c

 

8、对于很多行的的操作都一样,如对main.c add.c mul.c等的操作一样,只有名称不一样,我们可以使用模式规则来简化

使用模式规则之前

    main.o:main.c

        gcc -c main.c

使用模式规则之后

    %.o:%.c

        gcc $< -o $@

在这里当生成终极目标App时候需要检查依赖,当发现没有main.o ,就会向下寻找,找到这里就会匹配%.omain.o,由于%的内容一样的所以%c就为mainc,整行就为main.o:main.c。然后再依次查找add.omin.omul.odiv.o并执行相应的匹配。

$<$@$^都是makefile中的自动变量,自动变量就是指它的值是不定的。这三个自动变量只能在规则中使用。

$<表示当前规则中的第一个依赖,这里是指main.c

$@表示当前规则中的目标, 这里是指main.o

$^表示当前规则中所有的依赖,假如是终极目标那一行的所有依赖项这里是指main.oadd.omin.omul.odiv.o

综合以上我们修改makefile版本3如下

    obj=main.o add.o min.o mul.o div.o

    target = App

    $(target):$(obj)

        gcc $(obj) -o App

 

    %.o:%.c

        gcc -c $< -o $@

 

9makefile系统中还有一些系统维护的变量,这些系统变量都是大写的。系统维护的变量有些有默认值,有些没有默认值,无论有无默认值我们都可以修改。

CC的默认值为ccCC=cc(cc功能其实gcc)我么可以改为CC=gcc  CPPFLAGS是预处理时候选项可以设置为-ICPPFLAGS=-I此外还有编译时候参数CFLAGS和链接时候参数-Wall -g -c ,LDFLAGS链接库使用的选项 -L -l

 

10、makefile中所有的函数都有返回值,不使用返回值话,没必要使用makefile中的函数。

 

11当终极目标有很多依赖.o文件的时候,把所有.o文件写出来比较低效,且易出错。我们要生成终极目标需要所有.o文件,我们生成.o文件需要所有的.c,因此我们自下往上执行先找到.c文件,然后生成对应的.o文件,最后生成终极目标。我们可以借助makefilewildcard函数获取指定目录下的.c文件

    src= $(wildcard ./*.c)

  wildcard是函数名,是找到所有的.c文件的意思 ./*.c是函数参数 $(wildcard)是取函数wildcardd的返回值, wildcard函数查找某个目录下某种类型的文件,查到后以字符串形式返回,这里返回的是main.c add.c min.c mul.c div.c

     obj= $(patsubst ./%.c, ./%.o, $(src))

patsubst是函数名, 是匹配替换的意思./%.o, ./%.c, $(src)是指将当前文件下.c文件替换为.o文件, .c文件来源为$(src),然后obj就是main.o add.o min.o mul.o div.o

综合以上我们修改makefile版本4如下

    src= $(wildcard ./*.c)

    obj= $(patsubst ./%.o, ./%.c, $(src))

    target = App

    CC=gcc

    $(target):$(obj)

        $(CC) $(obj) -o App

 

    %.o:%.c

        gcc -c $< -o $@

 

12、有些时候我们每次需要删除.o文件,我们可以将这个操作写入makefile中, 一下clean这个目标没有依赖项,直接执行rm $(obj)操作

    clean:

        rm $(obj) $(target)

makefile中,由于终极目标是App,并且终极目标与clean这个目标无关,所以在终端黑窗口敲make并不会执行clean相关操作。

要想执行clean操作,需要在终端敲make clean指令,不过make clean这个指令只会执行生成目标cleanrm操作,不会执行其他操作如生成终极目标的操作。

有些时候当clean进行rm删除操作的时候,当没有对应文件会告警,我们可以加-f参数要求强制执行,不管文件存不存在这个指令都会执行。

    clean:

        rm $(obj) $(target) -f

 

13、有些时候我们当前文件下已经有一个clean文件,如我们touch clean 后,再然后执行make clean会发现提示当前的clean是最新的,没有执行相关操作。 这是由于makefile的工作原理是当发现目标是最新的时候就不更新或执行相应操作。我们为此可以添加一个伪目标,声明clean为伪目标,就不会再做比较哪个clean更新了,不会和本地磁盘的clean比较了。

.PHONY:clean声明clean为伪目标。

综合以上我们修改makefile版本5如下

    src= $(wildcard ./*.c)

    obj= $(patsubst ./%.c, ./%.o, $(src))

    target = App

    CC=gcc

    $(target):$(obj)

        $(CC) $(obj) -o App

 

    %.o:%.c

        gcc -c $< -o $@

 

    .PHONY:clean

    clean:

        rm $(obj) $(target) -f

14、特别注意有些时候会在指令前边加一个横杠-,表示的是当前命令执行失败的话,忽略错误继续往下执行。

 clean:

        -rm $(obj) $(target) -f

综合以上我们经常写的简单易懂makefile易读版本如下

   App:main.o add.o min.o mul.o div.o

    gcc main.o add.o min.o mul.o div.o -o App

 

    main.o:main.c

        gcc -c main.c

 

    add.o:add.c

        gcc -c add.c

 

   min.o:min.c

        gcc -c min.c

 

    mul.o:mul.c

        gcc -c mul.c

 

    div.o:div.c

        gcc -c div.c

 

    .PHONY:clean

    clean:

        -rm *.o App -f

 

<think>嗯,用户想了解如何在Makefile中设置环境变量。这个问题其实涉及到Makefile中变量和环境变量的交互机制,需要仔细梳理清楚。 用户可能是Linux开发人员,正在编写Makefile时遇到环境变量相关的问题。从ta提问的简洁性来看,应该是有一定Makefile基础但遇到了具体困惑。我注意到引用材料里提到环境变量会作为宏导入Makefile这个关键点,这可能是解答的核心。 首先需要区分三种变量设置方式:直接赋值、export命令和shell环境继承。直接赋值是最简单的,比如PATH := /new/path这种格式。但要注意Makefile变量作用域的特殊性——默认只在当前Makefile有效。 export指令是关键,它能让变量向下传递到子进程。用户如果需要在recipe中使用自定义环境变量,就必须用export声明。不过引用材料[2]提到环境变量会覆盖Makefile变量,这个陷阱一定要提醒用户。 递归make调用的情况更复杂些。通过export传递变量时,子make进程会继承父进程的环境。但如果是用命令行参数传递,比如$(MAKE) VAR=value的形式,优先级会更高。这个细节虽然高级但很实用。 最后要考虑用户可能的实际场景:ta可能是想为编译设置特定路径,或者控制构建参数。应该给出一个综合示例展示PATH设置和CFLAGS传递,这样最直观。安全方面要提醒覆盖系统变量的风险,特别是PATH这种关键变量。 对了,还要补充查看实际环境的方法,比如make -p和printenv的组合使用。很多用户不知道这些调试技巧,实际排查问题时很有用。 相关问题设计要覆盖延伸需求:如何调试变量问题属于实用技巧,环境变量与命令行变量的优先级是进阶知识,而永久设置方法则是针对shell环境的补充。这些都能帮助用户构建完整知识链。</think>在 Makefile 中设置环境变量主要有以下几种方式,它们的作用范围和优先级有所不同: ### 方法 1:在 Makefile 中定义变量并导出 (最常见) * **定义变量:** 使用 `VAR_NAME = value` 或 `VAR_NAME := value` (立即展开) 语法定义变量。 * **导出为环境变量:** 使用 `export VAR_NAME` 指令将该变量导出,使其成为**子进程**(例如 shell 命令、编译器等)的环境变量。 * **示例:** ```makefile # 定义变量 MY_ENV_VAR = This is set in the Makefile ANOTHER_VAR := /custom/path # 导出变量,使其在 recipe 命令中作为环境变量可用 export MY_ENV_VAR export ANOTHER_VAR all: @echo "Inside Make recipe: MY_ENV_VAR is $$MY_ENV_VAR" # 需要 $$ 转义 $ @echo "ANOTHER_VAR points to $$ANOTHER_VAR" ``` * **运行 `make` 后,在 recipe 命令中 (`all:` 下面的命令) 就能访问环境变量 `MY_ENV_VAR` 和 `ANOTHER_VAR`。** * **注意:** 这些变量仅在 `make` 进程及其**子进程**中有效。在调用 `make` 的父 shell 环境中不会被改变。 ### 方法 2:在 Recipe 命令中直接设置 (临时) * 可以在单个 recipe 的命令行中直接设置环境变量,作用域仅限于**该条命令**的执行期间。 * **语法:** `VAR1=value1 VAR2=value2 command` * **示例:** ```makefile target: # 设置 DEBUG=1 仅对这次 gcc 调用有效 DEBUG=1 gcc -o program source.c # 设置 PATH 仅对这次 ls 调用有效 PATH="/my/special/bin:${PATH}" ls -l ``` ### 方法 3:在调用 `make` 时设置 (覆盖) * 可以在运行 `make` 命令的 shell 中直接设置环境变量。这些环境变量会被 `make` 读取,并**作为宏导入到 Makefile 中**[^2]。 * **重要:** 在 Makefile 中定义的变量(即使是 `export` 的)**不会**改变调用 `make` 的父 shell 的环境。 * **示例 (在 bash/zsh 等中):** ```bash $ export CUSTOM_FLAG="-O2" # 设置父 shell 的环境变量 $ make # 该环境变量会被 make 导入 ``` * **优先级:** 在命令行设置的环境变量 (`export VAR=value; make`) 会**覆盖** Makefile 内部对同名变量的定义[^2]。 ### 关键点总结 1. **作用域:** * Makefile 中 `export` 的变量:对 `make` 进程的**所有子进程**(recipe 命令)可见。 * Recipe 命令前设置的变量 (`VAR=val cmd`):仅对**该条命令**可见。 * 调用 `make` 时父 shell 的环境变量:会被 `make` **导入作为宏**,可以在 Makefile 和 recipe 中使用。 2. **`export` 是核心:** 要让在 Makefile 中定义的变量在 recipe 命令中作为环境变量生效,**必须使用 `export` 指令**。 3. **环境变量覆盖 Makefile 变量:** 在调用 `make` 之前设置的环境变量,如果与 Makefile 内部定义的变量同名,会**覆盖** Makefile 中的定义[^2]。 4. **查看实际环境:** * 在 recipe 中使用 `printenv` 或 `env` 命令查看实际生效的环境变量。 * 使用 `make -p` 查看 `make` 内置的默认规则、变量(包括从环境导入的)及其值[^1][^2]。 ### 示例:设置编译路径和标志 ```makefile # Makefile # 定义编译器路径和标志变量 COMPILER_PATH := /opt/my_toolchain/bin CUSTOM_CFLAGS := -Wall -O2 -I./include # 导出它们,使它们在调用 gcc 时作为环境变量可用 export PATH := $(COMPILER_PATH):$(PATH) # 将编译器路径添加到 PATH 环境变量开头 export CFLAGS := $(CUSTOM_CFLAGS) # 设置 CFLAGS 环境变量(许多编译器会读取) all: program program: source.c # 这里直接调用 gcc 就能使用 PATH 中的 /opt/my_toolchain/bin/gcc # 并且 gcc 会自动读取 CFLAGS 环境变量中的值 gcc -o program source.c clean: rm -f program ``` **运行:** ```bash $ make ``` 在这个例子中,`PATH` 和 `CFLAGS` 环境变量在编译 `source.c` 的 `gcc` 命令执行时被正确设置。 ### 注意事项 * **覆盖系统变量:** 在 Makefile 中修改 `PATH`, `LD_LIBRARY_PATH` 等关键系统环境变量时要格外小心,确保路径正确且不会破坏系统功能。 * **递归 Make:** 如果 Makefile 调用另一个 `make` (`$(MAKE)`),父 Makefile 中 `export` 的变量会作为环境变量传递给子 `make` 进程[^2]。 * **命令行参数优先:** 通过 `make VAR=value` 命令行方式设置的变量,优先级最高,会覆盖 Makefile 内部定义和从父 shell 环境导入的同名变量。 [^1]: MakeFile _10.3 Variables Used by Implicit Rules。The following tables describe of some of the more commonly-used predefined variables. This list is not exhaustive, and the default values shown here may not be what are selected by make for your environment. To see the complete list of predefined variables for your instance of GNU make you can run `make -p' in a directory with no makefiles. [^2]: Make has a simple macro definition and substitution mechanism. Macros are defined in a Makefile as = pairs. ... There are lots of default macros ... NOTE: That your environment variables are exported into the make as macros. They will override the defaults. [^3]: The idea is to automate and optimize the construction of programs/files -- ie. to leave enough foot prints so that others can follow. Makefile Naming make is going to look for a file called Makefile, if not found then a file called makefile. Use the first (so the name stands out in listings). [^4]: how to write Makefile。Note, though, that the solution above is imperfect.... if you have an indirect dependency on a header file, it still won't be updated correctly. So, you will still need to invoke "make clean" before invoking "make" if you have a header file that affects source files other than the source file with the corresponding name, which -- for me -- is about 99% of the time.
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值