Ubuntu 22.04 GNU Make 安装与命令参数详解

在 Ubuntu 22.04 上以在线(apt 源)与离线(deb 本地包)两种方式安装 GNU Make 4.3,并对版本、文件、目录、执行控制(干跑/并行/出错继续/静默/强制重建/touch/question)、调试输出及变量覆盖等命令参数逐一实测详解。

Linux开发与编译工具链make2026/10/06

Ubuntu 22.04 GNU Make 安装与命令参数详解

目标:在 Ubuntu 22.04 服务器上完成 GNU Make 的在线(apt 软件源)与离线(deb 本地包)两种安装,并针对 make 的版本信息、帮助、目标/目录指定、增量构建、执行控制(干跑/并行/出错继续/静默/强制重建/touch/question)、调试输出、变量覆盖(环境/命令行/include)等命令参数,逐项给出真实输入与真实回显。已在 Ubuntu 22.04 LTS(x86_64)环境实测通过。

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS(jammy,内核 5.15 系列)
架构x86_64
包管理器apt(dpkg 1.21.1)
软件源archive.ubuntu.com(jammy + jammy-updates + jammy-backports)+ security.ubuntu.com(jammy-security)
软件包make_4.3-4.1build1_amd64.deb(来自 jammy 主仓库)
GNU Make 版本4.3(Built for x86_64-pc-linux-gnu)
示例工程目录/opt/devchain-demo/make-ubuntu-test

环境实测回显:

grep -E "PRETTY_NAME|VERSION_ID" /etc/os-release
uname -m
which make
PRETTY_NAME="Ubuntu 22.04.5 LTS"
VERSION_ID="22.04"
x86_64
/usr/bin/make

💡 说明:Ubuntu 22.04 官方仓库自带 GNU Make 4.3,安装后可执行文件位于 /usr/bin/make,同时提供 /usr/bin/gmake 兼容别名。

二、工具概述

GNU Make(软件包名 make)是一个控制程序构建过程的自动化工具:它按照 Makefile 中定义的“目标(target)— 依赖(prerequisite)— 配方(recipe)”规则,依据文件时间戳判断哪些目标已过期,只重新执行需要的编译/链接命令,从而避免全量重复构建。

核心能力:

  • 增量构建:只有“源文件比目标文件新”或“目标文件不存在”时才执行对应配方;
  • 依赖追踪:头文件、库文件等任意依赖发生变化都会触发下游目标重建;
  • 自动化变量:$@(目标)、$<(第一个依赖)、$^(全部依赖)等简化配方书写;
  • 并行构建:-j 同时运行多个互不依赖的配方,充分利用 CPU 多核;
  • 内置规则与变量:内置 %.o: %.c 等隐式规则与 CC/CFLAGS/RM/AR 等变量,可免写常见编译规则;
  • 与 gcc/g++ 协同:gcc/g++ 负责单文件编译与链接,make 负责整棵构建图的组织与调度,二者配合构成经典的 C/C++ 构建体系。

典型使用场景:C/C++ 工程编译、软件源码安装(configure + make + make install)、CI/CD 流水线构建、内核/驱动模块构建等。

三、在线安装方案(A:apt 软件源)

3.1 基础环境与软件源

Ubuntu 22.04 的 apt 软件源在系统安装时已默认配置好,指向 archive.ubuntu.com(jammy 主仓库、jammy-updates、jammy-backports)与 security.ubuntu.com(jammy-security)。执行以下命令确认当前用户具备 root 权限(非 root 用户请使用 sudo 前缀):

sudo whoami

首次安装软件前建议更新一次软件源索引:

sudo apt update

实测输出:

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Hit:2 http://archive.ubuntu.com/ubuntu jammy-updates InRelease
Hit:3 http://archive.ubuntu.com/ubuntu jammy-backports InRelease
Hit:4 http://security.ubuntu.com/ubuntu jammy-security InRelease
Reading package lists...
Building dependency tree...
Reading state information...
8 packages can be upgraded. Run 'apt list --upgradable' to see them.

💡 说明:输出中的 Hit: 表示对应软件源索引已是最新,无需重新下载;WARNING: apt does not have a stable CLI interface. 是 apt 对脚本化调用方式的常规提示,不影响使用。

3.2 在线安装 make

sudo apt install -y make

实测输出(本次测试环境中 make 已随系统工具链安装过,apt 提示已是最新版本):

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

Reading package lists...
Building dependency tree...
Reading state information...
make is already the newest version (4.3-4.1build1).
make set to manually installed.
The following packages were automatically installed and are no longer required:
  dh-elpa-helper emacsen-common libarchive13 libcurl4 libjsoncpp25 librhash0
Use 'apt autoremove' to remove them.
0 upgraded, 0 newly installed, 0 to remove and 8 not upgraded.

💡 说明:在尚未安装 make 的 Ubuntu 22.04 机器上执行同一命令,apt 会从 jammy 主仓库下载 make_4.3-4.1build1_amd64.deb(180 kB)并自动安装,回显将出现 Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 make amd64 4.3-4.1build1 [180 kB] 与 Fetched 180 kB in ... 等下载行。

3.3 验证安装

make --version | head -1
which make
GNU Make 4.3
/usr/bin/make

💡 说明:GNU Make 4.3 即 Ubuntu 22.04 官方仓库中的 make 版本;执行文件为 /usr/bin/make,gmake 是它的兼容别名。

四、离线安装方案(B:deb 本地包)

适用于内网隔离、无法直连外网的服务器。思路:在一台可联网的同版本 Ubuntu 22.04 机器上用 apt 把 make 的 deb 包下载到本地,再拷贝到目标服务器用 dpkg 离线安装。

4.1 在联网机器上下载 deb 包

sudo apt install --download-only --no-install-recommends -y make

--download-only 表示只下载不安装,--no-install-recommends 表示不拉取推荐的附加包。在 make 尚未安装的机器上,deb 包会被下载到 /var/cache/apt/archives/ 并提示 Download complete and in download only mode。

若机器上 make 已安装,apt 会提示已是最新版本而不触发下载;此时可附加 --reinstall 强制重新下载,以完整展示下载过程(本次实测即采用该方式采集回显):

sudo apt install --download-only --no-install-recommends -y --reinstall make

实测输出:

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

Reading package lists...
Building dependency tree...
Reading state information...
0 upgraded, 0 newly installed, 1 reinstalled, 0 to remove and 8 not upgraded.
Need to get 180 kB of archives.
After this operation, 0 B of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/main amd64 make amd64 4.3-4.1build1 [180 kB]
Fetched 180 kB in 2s (106 kB/s)
Download complete and in download only mode

4.2 确认下载结果并拷贝到离线目录

ls -lh /var/cache/apt/archives/*.deb | head

实测输出:

-rw-r--r-- 1 root root 176K Feb 16  2022 /var/cache/apt/archives/make_4.3-4.1build1_amd64.deb

创建离线目录并拷贝 deb 包:

mkdir -p /opt/devchain-demo/make-ubuntu-test/offline
cp /var/cache/apt/archives/*.deb /opt/devchain-demo/make-ubuntu-test/offline/
ls -lh /opt/devchain-demo/make-ubuntu-test/offline/

实测输出:

total 176K
-rw-r--r-- 1 root root 176K Oct  6 06:22 make_4.3-4.1build1_amd64.deb

⚠️ 注意:deb 文件名中的 4.3-4.1build1 是 make 的版本号,amd64 是架构标识;下载机与离线目标机必须同为 Ubuntu 22.04(jammy)且同为 x86_64,否则 deb 包无法直接安装。

4.3 查看 deb 包元信息

dpkg-deb -I /opt/devchain-demo/make-ubuntu-test/offline/make_4.3-4.1build1_amd64.deb

实测输出:

 new Debian package, version 2.0.
 size 179742 bytes: control archive=1222 bytes.
     995 bytes,    25 lines      control
     989 bytes,    15 lines      md5sums
 Package: make
 Source: make-dfsg
 Version: 4.3-4.1build1
 Architecture: amd64
 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
 Original-Maintainer: Manoj Srivastava <srivasta@debian.org>
 Installed-Size: 416
 Depends: libc6 (>= 2.34)
 Suggests: make-doc
 Conflicts: make-guile
 Replaces: make-guile
 Section: devel
 Priority: optional
 Multi-Arch: allowed
 Homepage: https://www.gnu.org/software/make/
 Description: utility for directing compilation
  GNU Make is a utility which controls the generation of executables
  and other target files of a program from the program's source
  files. It determines automatically which pieces of a large program
  need to be (re)created, and issues the commands to (re)create
  them. Make can be used to organize any task in which targets (files)
  are to be automatically updated based on input files whenever the
  corresponding input is newer --- it is not limited to building
  computer programs. Indeed, Make is a general purpose dependency
  solver.

💡 说明:Depends: libc6 (>= 2.34) 表明 make 只依赖 glibc(libc6),Ubuntu 22.04 默认已安装,因此拷贝这一个 deb 包即可完成离线安装。

4.4 在离线目标机上安装

将 make_4.3-4.1build1_amd64.deb 拷贝到离线目标机(例如放入 /opt/offline/)后执行:

sudo dpkg -i /opt/offline/make_4.3-4.1build1_amd64.deb

dpkg 安装成功后会输出 Unpacking make ...、Setting up make ... 等安装日志。安装完成后验证:

make --version | head -1
GNU Make 4.3

⚠️ 注意:若目标机缺少 libc6 依赖,dpkg -i 会报依赖错误,此时需一并下载 libc6 的 deb 包离线安装;也可以改用 sudo apt install -y /opt/offline/make_4.3-4.1build1_amd64.deb,让 apt 自动解析并处理本地 deb 包的依赖。

五、命令与参数详解

5.1 准备示例工程

本章所有参数演示均基于一个多文件 C 工程。先创建测试目录:

mkdir -p /opt/devchain-demo/make-ubuntu-test

创建 hello.c(主程序,引用 helper.h 与 util.h):

#include <stdio.h>
#include "helper.h"
#include "util.h"

int main(void)
{
    printf("Hello, GNU Make on Ubuntu 22.04!\n");
    printf("sum(1, 2) = %d\n", sum(1, 2));
    print_helper();
    return 0;
}

创建 helper.h:

#ifndef HELPER_H
#define HELPER_H

void print_helper(void);

#endif

创建 helper.c:

#include <stdio.h>

void print_helper(void)
{
    printf("helper.c compiled and linked\n");
}

创建 util.h:

#ifndef UTIL_H
#define UTIL_H

int sum(int a, int b);

#endif

创建 util.c:

#include "util.h"

int sum(int a, int b)
{
    return a + b;
}

创建 Makefile(本工程构建规则,也是后续所有参数演示的载体;配方行必须以 Tab 开头):

# 演示 GNU Make 4.3 的示例 Makefile
CC      = gcc
CFLAGS  = -Wall
TARGET  = hello
OBJS    = hello.o helper.o util.o

.PHONY: all clean distclean

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^
	@echo "==> 链接完成,生成可执行文件 $@"

hello.o: hello.c helper.h util.h
	$(CC) $(CFLAGS) -c -o $@ hello.c

helper.o: helper.c helper.h
	$(CC) $(CFLAGS) -c -o $@ helper.c

util.o: util.c util.h
	$(CC) $(CFLAGS) -c -o $@ util.c

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

clean:
	rm -f $(TARGET) *.o
	@echo "==> 清理完成:已删除目标文件与可执行文件"

distclean: clean
	rm -f *.out
	@echo "==> 深度清理完成"

💡 说明:该 Makefile 演示了 GNU Make 的典型写法——变量定义(CC、CFLAGS、TARGET、OBJS)、显式规则($(TARGET): $(OBJS))、头文件依赖(hello.o: hello.c helper.h util.h)、模式规则(%.o: %.c)、自动化变量($@ 目标、$< 第一个依赖、$^ 全部依赖)、@echo 抑制回显以及 .PHONY 伪目标声明(all、clean、distclean)。

5.2 查看版本与帮助:--version、--help

--version:打印版本号与版权信息。

make --version

实测输出:

GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

--help:打印全部命令行选项的简要说明。

make --help

实测输出(关键片段):

Usage: make [options] [target] ...
Options:
  -b, -m                      Ignored for compatibility.
  -B, --always-make           Unconditionally make all targets.
  -C DIRECTORY, --directory=DIRECTORY
                              Change to DIRECTORY before doing anything.
  -d                          Print lots of debugging information.
  --debug[=FLAGS]             Print various types of debugging information.
  -e, --environment-overrides
                              Environment variables override makefiles.
  -E STRING, --eval=STRING    Evaluate STRING as a makefile statement.
  -f FILE, --file=FILE, --makefile=FILE
                              Read FILE as a makefile.
  -h, --help                  Print this message and exit.
  -i, --ignore-errors         Ignore errors from recipes.
  -I DIRECTORY, --include-dir=DIRECTORY
                              Search DIRECTORY for included makefiles.
  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.
  -k, --keep-going            Keep going when some targets can't be made.
  -l [N], --load-average[=N], --max-load[=N]
                              Don't start multiple jobs unless load is below N.
  -L, --check-symlink-times   Use the latest mtime between symlinks and target.
  -n, --just-print, --dry-run, --recon
                              Don't actually run any recipe; just print them.
  -o FILE, --old-file=FILE, --assume-old=FILE
                              Consider FILE to be very old and don't remake it.
  -O[TYPE], --output-sync[=TYPE]
                              Synchronize output of parallel jobs by TYPE.
  -p, --print-data-base       Print make's internal database.
  -q, --question              Run no recipe; exit status says if up to date.
  -r, --no-builtin-rules      Disable the built-in implicit rules.
  -R, --no-builtin-variables  Disable the built-in variable settings.
  -s, --silent, --quiet       Don't echo recipes.
  --no-silent                 Echo recipes (disable --silent mode).
  -S, --no-keep-going, --stop
                              Turns off -k.
  -t, --touch                 Touch targets instead of remaking them.
  --trace                     Print tracing information.
  -v, --version               Print the version number of make and exit.
  -w, --print-directory       Print the current directory.
  --no-print-directory        Turn off -w, even if it was turned on implicitly.
  -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE
                              Consider FILE to be infinitely new.
  --warn-undefined-variables  Warn when an undefined variable is referenced.

This program built for x86_64-pc-linux-gnu
Report bugs to <bug-make@gnu.org>

5.3 指定目录与目标:-C

-C DIRECTORY:执行任何操作前先切换到 DIRECTORY 目录(等价于先 cd 再执行 make),无需在工程目录里手动切换。

指定构建目标 hello:

make -C /opt/devchain-demo/make-ubuntu-test hello

实测输出(首次构建,全部目标过期):

make: Entering directory '/opt/devchain-demo/make-ubuntu-test'
gcc -Wall -c -o hello.o hello.c
gcc -Wall -c -o helper.o helper.c
gcc -Wall -c -o util.o util.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello
make: Leaving directory '/opt/devchain-demo/make-ubuntu-test'

💡 说明:-C 模式下 make 会打印 Entering directory / Leaving directory 提示;若不想看到该提示,可加 --no-print-directory。

不带目标执行(默认目标 all,目标已最新):

make -C /opt/devchain-demo/make-ubuntu-test

实测输出:

make: Entering directory '/opt/devchain-demo/make-ubuntu-test'
make: Nothing to be done for 'all'.
make: Leaving directory '/opt/devchain-demo/make-ubuntu-test'

运行生成的可执行文件:

/opt/devchain-demo/make-ubuntu-test/hello

实测输出:

Hello, GNU Make on Ubuntu 22.04!
sum(1, 2) = 3
helper.c compiled and linked

5.4 增量构建完整演示(核心)

GNU Make 的核心价值是增量构建:只重建“源文件比目标文件新”的部分。下面完整演示一次“全量构建 → 修改源码 → 增量重编 → touch 触发”的闭环。

第一步,清理后全量构建:

cd /opt/devchain-demo/make-ubuntu-test
make clean
make

实测输出:

rm -f hello *.o
==> 清理完成:已删除目标文件与可执行文件
gcc -Wall -c -o hello.o hello.c
gcc -Wall -c -o helper.o helper.c
gcc -Wall -c -o util.o util.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

第二步,修改 hello.c 中的打印文案(模拟代码变更):

sed -i "s/Hello, GNU Make on Ubuntu 22.04!/Hello, GNU Make incremental build!/" hello.c
grep printf hello.c

实测输出:

    printf("Hello, GNU Make incremental build!\n");
    printf("sum(1, 2) = %d\n", sum(1, 2));

第三步,再次执行 make——只有 hello.o 被重编,helper.o / util.o 未动,随后仅重新链接:

make

实测输出:

gcc -Wall -c -o hello.o hello.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

运行验证修改已生效:

./hello

实测输出:

Hello, GNU Make incremental build!
sum(1, 2) = 3
helper.c compiled and linked

第四步,touch 仅更新文件时间戳(内容不变),make 依据时间戳判断 hello.c 比 hello.o 新,依然会重编:

touch hello.c
make

实测输出:

gcc -Wall -c -o hello.o hello.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

💡 说明:整个增量闭环展示了 make 的判定逻辑——“目标文件不存在”或“任一依赖比目标文件新”时执行对应配方;helper.o / util.o 因依赖未变而被跳过,节省了大量编译时间,这正是 make 被广泛用于大型工程的原因。

5.5 指定 Makefile 文件:-f

-f FILE:不读取默认的 Makefile / makefile / GNUmakefile,改读指定文件。 先用同一份源文件准备一个备用构建文件 alt.mk(仅目标名与清理规则不同):

# 备用 Makefile 文件名:make -f alt.mk 演示
CC      = gcc
CFLAGS  = -Wall
TARGET  = hello_alt

all: $(TARGET)

$(TARGET): hello.o helper.o util.o
	$(CC) $(CFLAGS) -o $@ $^

hello.o: hello.c helper.h util.h
	$(CC) $(CFLAGS) -c -o $@ hello.c

helper.o: helper.c helper.h
	$(CC) $(CFLAGS) -c -o $@ helper.c

util.o: util.c util.h
	$(CC) $(CFLAGS) -c -o $@ util.c

clean:
	rm -f $(TARGET) *.o

先清理再用 -f alt.mk 构建:

cd /opt/devchain-demo/make-ubuntu-test
make -f alt.mk clean
make -f alt.mk

实测输出:

rm -f hello_alt *.o
gcc -Wall -c -o hello.o hello.c
gcc -Wall -c -o helper.o helper.c
gcc -Wall -c -o util.o util.c
gcc -Wall -o hello_alt hello.o helper.o util.o

💡 说明:-f 常用于多套构建方案并存(如 debug.mk / release.mk)或第三方工程指定非标准文件名;--file、--makefile 是等价长选项。

5.6 干跑模式:-n / --dry-run

-n / --dry-run:只打印将要执行的命令,并不真正执行(不编译、不链接、不产生任何文件变化),常用于构建前“预演”。

cd /opt/devchain-demo/make-ubuntu-test
touch hello.c
make -n

实测输出:

gcc -Wall -c -o hello.o hello.c
gcc -Wall -o hello hello.o helper.o util.o
echo "==> 链接完成,生成可执行文件 hello"

长选项形式效果相同:

touch hello.c
make --dry-run

实测输出:

gcc -Wall -c -o hello.o hello.c
gcc -Wall -o hello hello.o helper.o util.o
echo "==> 链接完成,生成可执行文件 hello"

💡 说明:对比 5.4 节的真实构建回显可以看到,干跑模式下只“打印”命令,配方中的命令并未执行;注意 @echo 配方在干跑模式下打印的是命令本身(echo "..." 这一行),而真实执行时打印的是命令的输出文本。

5.7 并行任务数:-j

-j N:允许同时运行 N 个互不依赖的配方任务,充分使用多核 CPU 加速构建;-j 不带数字表示不限制并发数。

cd /opt/devchain-demo/make-ubuntu-test
make clean
make -j4

实测输出:

rm -f hello *.o
==> 清理完成:已删除目标文件与可执行文件
gcc -Wall -c -o hello.o hello.c
gcc -Wall -c -o helper.o helper.c
gcc -Wall -c -o util.o util.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

💡 说明:-j4 下三个 .o 编译任务在 4 个并行作业中并发执行(互无依赖),此处回显顺序看起来与串行相同,是因为命令回显按作业启动顺序输出;可以搭配 --output-sync(如 --output-sync=line)让并行输出按行归组,避免交错。

串行构建对照(同一工程一次只跑一个任务):

make clean
make -j1

实测输出:

rm -f hello *.o
==> 清理完成:已删除目标文件与可执行文件
gcc -Wall -c -o hello.o hello.c
gcc -Wall -c -o helper.o helper.c
gcc -Wall -c -o util.o util.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

⚠️ 注意:-j 并行仅适用于目标之间没有依赖关系(或依赖已由 Makefile 显式描述)的场景;若配方间存在隐式顺序(如两个目标写同一临时文件),并行可能引发竞态,需在 Makefile 中正确声明依赖。

5.8 出错继续:-k

-k / --keep-going:某个目标构建失败时,继续尝试构建其他可以构建的目标,而不是立即停止;-k 常用于一次性收集所有错误。为演示先构造两个不存在的目标 bad1.o、bad2.o(无对应源文件)。

不加 -k 时,遇到第一个错误立即终止(bad2.o 不再尝试):

cd /opt/devchain-demo/make-ubuntu-test
make bad1.o bad2.o
echo "exit=$?"

实测输出:

make: *** No rule to make target 'bad1.o'.  Stop.
exit=2

加 -k 后,两个目标都被尝试、错误被逐个列出:

make -k bad1.o bad2.o
echo "exit=$?"

实测输出:

make: *** No rule to make target 'bad1.o'.
make: *** No rule to make target 'bad2.o'.
exit=2

💡 说明:两次调用退出码均为 2(make 出错的标准退出码),区别在于 -k 下 make 遍历完了所有目标再统一报错,适合“一次构建收集全部错误”的排障场景。

5.9 静默模式:-s

-s / --silent / --quiet:不回显配方命令本身,只输出命令产生的内容(与配方行首 @ 作用相同,但作用于全局)。

cd /opt/devchain-demo/make-ubuntu-test
touch hello.c
make -s
echo "--- 静默构建完成(exit=$?)"
ls -l hello

实测输出:

==> 链接完成,生成可执行文件 hello
--- 静默构建完成(exit=0)
-rwxr-xr-x 1 root root 16136 Oct  6 06:24 hello

💡 说明:与 5.4 节真实构建回显对比可见,make -s 下 gcc ... 编译/链接命令不再回显,而 @echo 配方输出(==> 链接完成...)仍正常显示;exit=0 表明构建成功,hello 可执行文件已生成(16136 字节)。

5.10 无条件重建:-B

-B / --always-make:无视时间戳判断,无条件重建所有目标(等价于把所有源文件都当作“刚被修改过”)。

cd /opt/devchain-demo/make-ubuntu-test
make -B

实测输出(即使所有目标均为最新,仍全量重编):

gcc -Wall -c -o hello.o hello.c
gcc -Wall -c -o helper.o helper.c
gcc -Wall -c -o util.o util.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

💡 说明:-B 与 5.4 节中“Nothing to be done”的常规行为形成鲜明对比,适合怀疑时间戳异常、或需要强制全量重建的场合(如清理缓存后)。

5.11 打印内部数据库:-p

-p / --print-data-base:打印 make 的内部数据库,包括全部变量定义(内置变量 + Makefile 变量)与全部规则(内置隐式规则 + Makefile 规则),是排查“make 为什么这么干”的利器。

抓取 CC 与 CFLAGS 变量行:

cd /opt/devchain-demo/make-ubuntu-test
make -p 2>/dev/null | grep -E "^(CC|CFLAGS) ?="

实测输出:

CFLAGS = -Wall
CC = gcc

💡 说明:CC = gcc、CFLAGS = -Wall 来自本工程 Makefile 的显式赋值;若 Makefile 未定义,make -p 会打印内置默认值(如内置 CC = cc)。

抓取数据库开头片段:

make -p 2>/dev/null | head -25

实测输出:

make: Nothing to be done for 'all'.
# GNU Make 4.3
# Built for x86_64-pc-linux-gnu
# Copyright (C) 1988-2020 Free Software Foundation, Inc.
# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software: you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

# Make data base, printed on Tue Oct  6 06:24:44 2026

# Variables

# default
PREPROCESS.S = $(CC) -E $(CPPFLAGS)
# default
COMPILE.m = $(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
# default
ARFLAGS = rv
# default
AS = as
# default
AR = ar
# default
OBJC = cc
# default

💡 说明:# default 前缀的行是 make 内置变量/规则;数据库中还包含 # Makefile 前缀的行(来自当前 Makefile)以及 %.o: %.c 等隐式规则、目标依赖图等,全部打印时内容很长,实际排查常用 make -p 2>/dev/null | grep ... 定向过滤。

5.12 环境变量覆盖:-e

-e / --environment-overrides:允许环境变量覆盖 Makefile 中的赋值。默认情况下 Makefile 内的显式赋值优先级高于同名环境变量;加 -e 后环境变量反超。

先看默认行为:命令行注入环境变量 CFLAGS=-O2,但 Makefile 里 CFLAGS = -Wall 显式赋值优先,-n 干跑显示仍用 -Wall:

cd /opt/devchain-demo/make-ubuntu-test
touch hello.c
CFLAGS=-O2 make -n

实测输出:

gcc -Wall -c -o hello.o hello.c
gcc -Wall -o hello hello.o helper.o util.o
echo "==> 链接完成,生成可执行文件 hello"

再加 -e,环境变量反超 Makefile 赋值,编译选项变为 -O2:

CFLAGS=-O2 make -e -n

实测输出:

gcc -O2 -c -o hello.o hello.c
gcc -O2 -o hello hello.o helper.o util.o
echo "==> 链接完成,生成可执行文件 hello"

💡 说明:变量优先级从高到低为:命令行变量(VAR=value)> Makefile 内显式赋值 > 环境变量 > 内置默认值;-e 会把“环境变量”的优先级提升到“Makefile 内显式赋值”之上,常用于在不动 Makefile 的前提下统一注入编译参数。

5.13 追加 include 搜索目录:-I

-I DIRECTORY / --include-dir=DIRECTORY:为 Makefile 中的 include 指令追加搜索目录。先在 inc/ 子目录准备被包含的变量文件 vars.mk,再写一个使用 include 的 inc.mk:

cd /opt/devchain-demo/make-ubuntu-test
mkdir -p inc

创建 inc/vars.mk:

MSG = hello from inc dir

创建 inc.mk:

include vars.mk

show:
	@echo "MSG = $(MSG)"

不带 -I 时,make 只在当前目录找 vars.mk,找不到直接报错:

make -f inc.mk show
echo "exit=$?"

实测输出:

inc.mk:1: vars.mk: No such file or directory
make: *** No rule to make target 'vars.mk'.  Stop.
exit=2

加 -I 指定搜索目录后,include vars.mk 成功解析:

make -I /opt/devchain-demo/make-ubuntu-test/inc -f inc.mk show

实测输出:

MSG = hello from inc dir

💡 说明:-I 常用于把公共构建片段(公共变量、公共规则)集中放在共享目录,多个工程通过 include common.mk 复用;make 的 include 搜索顺序为:当前目录 → -I 指定目录 → 系统默认目录。

5.14 视为旧/新文件:-o、-W

-o FILE / --old-file:把 FILE 视为“非常旧”而跳过它的重建(相当于告诉 make 该文件无需更新)。

cd /opt/devchain-demo/make-ubuntu-test
make clean
make
touch hello.c

说明:此时 hello.c 比 hello.o 新。不加 -o 时 make -n 会预演重编 hello.o:

make -n

实测输出:

gcc -Wall -c -o hello.o hello.c
gcc -Wall -o hello hello.o helper.o util.o
echo "==> 链接完成,生成可执行文件 hello"

加 -o hello.o 后,hello.o 被视为旧文件而豁免重建,其时间戳保持不变,下游目标也随之判定为最新:

make -o hello.o

实测输出:

make: Nothing to be done for 'all'.

-W FILE / --what-if / --new-file / --assume-new:把 FILE 视为“无限新”(模拟该文件刚被修改过),从而强制重建依赖它的目标,但不实际改动文件内容与时间戳。

make -W hello.c

实测输出:

gcc -Wall -c -o hello.o hello.c
gcc -Wall -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

💡 说明:-o/-W 是成对的“假设”开关,常用于调试构建逻辑或强制/阻止某个特定文件的更新,而无需真正修改文件;实际文件系统里 hello.c 的 mtime 并未因 -W 改变。

5.15 仅更新时间戳:-t

-t / --touch:不执行配方,只把目标文件的时间戳 touch 成当前时间,使 make 认为目标“已是最新”。

cd /opt/devchain-demo/make-ubuntu-test
make -t
echo "--- make -t 后文件时间戳 ---"
ls -l --time-style=full-iso hello hello.o hello.c

实测输出:

make: Nothing to be done for 'all'.
--- make -t 后文件时间戳 ---
-rwxr-xr-x 1 root root 16136 2026-10-06 06:24:58.736258197 +0000 hello
-rw-r--r-- 1 root root   206 2026-10-06 06:24:48.334102643 +0000 hello.c
-rw-r--r-- 1 root root  1776 2026-10-06 06:24:58.711257824 +0000 hello.o

💡 说明:make -t 后 hello 与 hello.o 的 mtime 被更新到 06:24:58(当前时间),hello.c 的 mtime(06:24:48)保持不变;由于 hello.o 现在“看起来比 hello.c 新”,后续不带参数的 make 将判定无需重编。该参数常用于“假装构建已完成”的打包/发布场景,但需注意它不会真正生成有效二进制。

5.16 只查不建:-q

-q / --question:不执行任何配方,只根据目标是否最新返回退出码:0 表示全部最新,1 表示存在需要更新的目标。

目标全部最新时:

cd /opt/devchain-demo/make-ubuntu-test
make -q
echo "exit=$?"

实测输出:

exit=0

touch 使 hello.c 变新后:

touch hello.c
make -q
echo "exit=$?"

实测输出:

exit=1

💡 说明:-q 适合在脚本中判断“是否需要重新构建”(如 CI 缓存命中判断);退出码 0 与 1 分别对应“无需构建”与“需要构建”,2 表示出错。

5.17 调试输出:--debug

--debug[=FLAGS]:打印详细的构建决策过程(读取哪些 Makefile、逐一考虑哪些目标、依赖新旧比较、是否重建等),是理解 make 内部判断逻辑的利器。v 表示 verbose(最详细)。

cd /opt/devchain-demo/make-ubuntu-test
make --debug=v 2>&1 | head -32

实测输出(片段):

GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Reading makefiles...
Reading makefile 'Makefile'...
Updating makefiles....
Updating goal targets....
Considering target file 'all'.
 File 'all' does not exist.
  Considering target file 'hello'.
    Considering target file 'hello.o'.
      Considering target file 'hello.c'.
       Finished prerequisites of target file 'hello.c'.
      No need to remake target 'hello.c'.
      Considering target file 'helper.h'.
       Finished prerequisites of target file 'helper.h'.
      No need to remake target 'helper.h'.
      Considering target file 'util.h'.
       Finished prerequisites of target file 'util.h'.
      No need to remake target 'util.h'.
     Finished prerequisites of target file 'hello.o'.
     Prerequisite 'hello.c' is newer than target 'hello.o'.
     Prerequisite 'helper.h' is older than target 'hello.o'.
     Prerequisite 'util.h' is older than target 'hello.o'.
    Must remake target 'hello.o'.
gcc -Wall -c -o hello.o hello.c
    Successfully remade target file 'hello.o'.
    Considering target file 'helper.o'.
      Considering target file 'helper.c'.

💡 说明:从片段可清晰看到 make 的判定过程——Considering target file 'hello.o' → 逐个比较依赖的 mtime → Prerequisite 'hello.c' is newer than target 'hello.o' → Must remake target 'hello.o' 并执行编译。--debug=a(all)、--debug=i(implicit)、--debug=j(jobs)等可按需组合。

5.18 命令行变量赋值:VAR=value

在命令行直接以 VAR=value 形式传参,覆盖 Makefile 中的同名变量,优先级高于 Makefile 内显式赋值(也高于 -e 下的环境变量)。

cd /opt/devchain-demo/make-ubuntu-test
touch hello.c
make CFLAGS=-O2 all

实测输出(编译与链接选项均由 -Wall 变为 -O2):

gcc -O2 -c -o hello.o hello.c
gcc -O2 -o hello hello.o helper.o util.o
==> 链接完成,生成可执行文件 hello

💡 说明:命令行变量赋值是覆盖编译选项最直接的方式,常用于“不改 Makefile 的前提下临时切换优化级别/宏开关/安装路径”,如 make CFLAGS="-O2 -g" PREFIX=/usr/local all。

5.19 伪目标:clean / distclean

本工程 Makefile 通过 .PHONY 声明了 all、clean、distclean 三个伪目标(不生成同名文件,总是执行配方)。

cd /opt/devchain-demo/make-ubuntu-test
make clean

实测输出:

rm -f hello *.o
==> 清理完成:已删除目标文件与可执行文件
make distclean

实测输出(distclean 依赖 clean,先清理目标文件再清理附加产物):

rm -f hello *.o
==> 清理完成:已删除目标文件与可执行文件
rm -f *.out
==> 深度清理完成

结合 -C 在任意目录下清理指定工程:

make -C /opt/devchain-demo/make-ubuntu-test clean

实测输出:

make: Entering directory '/opt/devchain-demo/make-ubuntu-test'
rm -f hello *.o
==> 清理完成:已删除目标文件与可执行文件
make: Leaving directory '/opt/devchain-demo/make-ubuntu-test'

⚠️ 注意:若 Makefile 未把 clean 声明为 .PHONY,而目录中恰好存在名为 clean 的文件,make 会认为目标已最新而跳过清理——这是常见的“clean 不生效”陷阱。

六、配置说明

6.1 变量定义

CC = gcc、CFLAGS = -Wall 属于“递归展开变量”(=);也可用 := 定义“立即展开变量”、用 ?= 仅在未定义时赋值、用 += 追加。命令行 VAR=value 可临时覆盖。

6.2 自动化变量

变量含义
$@当前目标的文件名
$<第一个依赖的文件名
$^全部依赖的文件名(去重,空格分隔)
$?比目标新的所有依赖
$*模式规则中 % 匹配的部分

6.3 模式规则

%.o: %.c 表示“任意 .o 目标都可由同名 .c 源文件生成”,配合内置的 COMPILE.c 等规则可大幅简化 Makefile;本工程虽显式写了三个 .o 规则,%.o: %.c 仍作为兜底规则存在。

6.4 伪目标声明

.PHONY: all clean distclean 告诉 make 这些目标不对应真实文件,总是执行配方;常见伪目标还包括 install、test、check。

6.5 配方回显控制

  • 行首 @:该行命令本身不回显(输出仍显示);
  • make -s / --silent:全局不回显配方命令;
  • make -n:只打印命令不执行。

6.6 include 包含机制

include vars.mk 可将公共变量/规则片段引入当前 Makefile;搜索顺序为当前目录 → -I 指定目录 → 系统目录。用 -include(前缀减号)可忽略被包含文件缺失的错误。

6.7 变量优先级总结

命令行 VAR=value > Makefile 显式赋值 > 环境变量(加 -e 后提升为最高之一)> 内置默认值。

七、常见问题(FAQ)

Q1:报错 make: *** No rule to make target 'xxx.o'. Stop. 目标没有对应的源文件或构建规则。检查是否拼错文件名、源文件是否缺失,或是否缺少匹配的(显式/隐式/模式)规则。

Q2:报错 Makefile:2: *** missing separator. Stop. 配方行没有以 Tab 开头(用了空格)。GNU Make 规定配方行必须用 Tab 缩进,将空格改为 Tab 即可。

Q3:提示 make: Nothing to be done for 'all'. 但我刚改了源码? 目标文件时间戳比源文件新。可能原因:系统时间被回拨、文件被 touch 过、或依赖列表遗漏了该头文件(应把头文件写入对应目标的依赖,如 hello.o: hello.c helper.h util.h)。可用 make -B 强制全量重建验证。

Q4:为什么头文件改了 make 却不重编? Makefile 中没有声明源文件与头文件的依赖关系,make 只认显式/隐式声明的依赖。补上头文件依赖,或使用 gcc -MM 自动生成依赖(配合 -include *.d)。

Q5:make clean 没有反应? clean 未声明为 .PHONY 且目录中存在名为 clean 的文件。将 clean 加入 .PHONY,或先删除该文件。

Q6:并行构建(-j)时输出交错混乱? 多个任务的输出混在一起。使用 --output-sync=line(按行)或 --output-sync=target(按目标)整理输出;也可用 make -j4 2>&1 | tee build.log 归档日志。

Q7:如何在不动 Makefile 的情况下改编译选项? 命令行传变量:make CFLAGS=-O2 all;或注入环境变量并加 -e:CFLAGS=-O2 make -e all。

Q8:make 与 gmake 的区别? Ubuntu 上 /usr/bin/gmake 是 /usr/bin/make 的兼容别名(GNU Make);部分工程脚本习惯调用 gmake。

八、卸载与清理

8.1 查看 make 包安装状态

dpkg -l make

实测输出:

Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version       Architecture Description
+++-==============-=============-============-=================================
ii  make           4.3-4.1build1 amd64        utility for directing compilation

💡 说明:状态列 ii 表示“已安装(install/install)”且配置完好,Version 为 4.3-4.1build1,Architecture 为 amd64。

8.2 卸载 make

若确需卸载(如改用 build-essential 或其他构建工具时需评估依赖),执行:

sudo apt remove -y make

需要连同配置文件一并清除时:

sudo apt purge -y make

⚠️ 注意:make 是 build-essential 等元包的依赖项,卸载前建议先执行 apt-cache rdepends make 查看反向依赖,避免破坏其他工具链。

8.3 清理示例工程构建产物

示例工程的中间产物与可执行文件可用工程自带伪目标清理:

cd /opt/devchain-demo/make-ubuntu-test
make clean

九、参考链接