Makefile.am 的例子

这篇用“能直接抄走改”的方式,整理 Automake 常见写法。
如果你已经会写 Makefile,这篇重点是:如何把规则写进 Makefile.am,再由 Autotools 生成可移植构建系统。

1. 先理解三层关系

  1. configure.ac:定义项目配置逻辑(检测依赖、生成哪些 Makefile)。
  2. Makefile.am:声明各目录编译/安装规则。
  3. configure + make:生成 Makefile.in/Makefile 后执行构建。

一句话:configure.ac 管“配置”,Makefile.am 管“目标与安装”。

2. 最小项目结构示例

1
2
3
4
5
6
7
8
myapp/
configure.ac
Makefile.am
src/
Makefile.am
main.c
include/
myapp.h

顶层 configure.ac

1
2
3
4
5
6
7
8
9
AC_INIT([myapp], [1.0.0], [you@example.com])
AM_INIT_AUTOMAKE([foreign subdir-objects])
AC_PROG_CC

AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT

顶层 Makefile.am

1
SUBDIRS = src

src/Makefile.am

1
2
3
bin_PROGRAMS = myapp
myapp_SOURCES = main.c
myapp_CPPFLAGS = -I$(top_srcdir)/include

3. 常见变量命名规则(最重要)

Automake 大量语义都在变量名里:

  1. bin_PROGRAMS:安装到 $(bindir) 的可执行文件。
  2. lib_LTLIBRARIES:安装到 $(libdir) 的 libtool 库。
  3. noinst_LIBRARIES:仅构建,不安装。
  4. *_SOURCES:目标的源码列表。
  5. *_CPPFLAGS / *_CFLAGS / *_LDFLAGS:目标级编译/链接参数。

示例:

1
2
3
4
bin_PROGRAMS = appctl
appctl_SOURCES = appctl.c cli.c
appctl_CPPFLAGS = -I$(top_srcdir)/include
appctl_LDADD = ../lib/libcore.la

4. 安装脚本、配置文件、数据文件

很多人把 mkdir/cp 写在 SPEC %install,更推荐先在 Makefile.am 描述好。

4.1. 安装脚本

1
2
3
4
scriptdir = $(libexecdir)/myapp/scripts
dist_script_SCRIPTS = \
scripts/init.sh \
scripts/healthcheck.sh

4.2. 安装配置/数据

1
2
myconfdir = $(sysconfdir)/myapp
dist_myconf_DATA = conf/myapp.conf

说明:

  1. dist_:文件已存在于源码树,且会进入 make dist 包。
  2. nodist_:文件构建时生成,不在源码树静态存在。

5. 生成文件用 nodist_(很常见)

例如模板 version.h.in 在构建时生成 version.h

1
2
3
4
5
6
7
BUILT_SOURCES = version.h
nodist_include_HEADERS = version.h

version.h: version.h.in
$(AM_V_GEN) sed 's,@VERSION@,$(PACKAGE_VERSION),g' $< > $@

CLEANFILES = version.h

6. 多目录构建与顺序

顶层用 SUBDIRS 控制构建顺序:

1
SUBDIRS = lib src tools

如果是条件构建:

1
2
3
if BUILD_TESTS
SUBDIRS += tests
endif

对应条件通常在 configure.acAM_CONDITIONAL 定义。

7. 单元测试与 make check

推荐把测试接入 Automake 标准入口:

1
2
3
4
check_PROGRAMS = test_core
test_core_SOURCES = test_core.c
test_core_LDADD = ../lib/libcore.la
TESTS = test_core

这样可以统一执行:

1
make check

8. 自定义目标与 .PHONY

Makefile.am 里也可以加自定义命令:

1
2
3
.PHONY: lint
lint:
cppcheck --enable=warning,style $(myapp_SOURCES)

.PHONY 避免和同名文件冲突。

9. 一个“中型项目”片段示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ACLOCAL_AMFLAGS = -I m4

SUBDIRS = lib src
if BUILD_TESTS
SUBDIRS += tests
endif

pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = myapp.pc

systemdunitdir = $(prefix)/lib/systemd/system
dist_systemdunit_DATA = packaging/myapp.service

scriptdir = $(libexecdir)/myapp/scripts
dist_script_SCRIPTS = scripts/precheck.sh scripts/recover.sh

EXTRA_DIST = README.md docs/architecture.md

这个片段对应你原文里的典型场景:

  1. 多目录编译。
  2. 安装 service/script/pkgconfig。
  3. 用条件开关控制 tests。

10. 与 SPEC 配合的建议

Makefile.am 写好安装规则后,SPEC %install 可简化为:

1
2
3
%install
rm -rf %{buildroot}
make install DESTDIR=%{buildroot}

SPEC %files 只需声明安装结果路径,不再手工 cp 文件,维护成本会低很多。

11. 常见坑

  1. 变量名拼错后缀(如把 _DATA 写成 _DATAS)。
  2. 忘记把生成文件放到 BUILT_SOURCES,并行编译时容易失败。
  3. SUBDIRS 顺序不对导致依赖库先后错误。
  4. 脚本放错变量(SCRIPTS vs DATA)造成权限不符合预期。

12. 我的建议

  1. 安装语义尽量前移到 Makefile.am,不要堆在 SPEC 里。
  2. 先写最小可运行目录,再增量拆分子目录和条件编译。
  3. 每次改规则都跑一遍:autoreconf -fi && ./configure && make && make install DESTDIR=/tmp/pkgroot

这样可以尽早验证“构建成功”与“安装路径正确”是不是同时成立。