make 是功能强大的构建工具,也是 cmake 的底层构建工具,即使在今天在中小型项目中也很适合。想要更好地使用 make 的强大功能,需要参考手册,同时项目 Makefile-Templates 也很值得参考。这里并不会介绍 make 的基本使用方法,而是关注于实际构造的细节。
这里假设要构建一个有一点复杂的项目,src 目录下可能包含多个子目录,同时可执行文件的 standalone 下可能要构造多个可执行文件。假设目录结构为
include
lib-dir1
lib-subdir1
f1.h
f2.h
lib-dir2
f3.h
src
lib-dir1
lib-subdir1
f1.cpp
f2.cpp
lib-dir2
f3.cpp
standalone
main1.cpp
main2.cpp
同时 main1 只依赖 lib-dir1 下的库,而 main2 依赖该项目中所有的库。
此时,要求编写的 Makefile 尽可能通用和简单,一个很好的想法是利用 include
来复用 make 的规则。很好,我遍布下去了,快进到结果。
根目录下包含 common.mk
,subdir.mk
和 Makefile
,common.mk
如下
x1# common.mk
2# make flags
3MAKEFLAGS += --warn-undefined-variables
4# compile macros
5CC := g++
6# use bash shell
7SHELL := /bin/bash
8
9# compile flags
10CFLAGS := -std=c++17 -O3 -Werror -Wall -Wextra
11OBJCFLAGS := $(CFLAGS) -c
12
13# recursive wildcard
14rwildcard=$(foreach d,$(wildcard $(addsuffix, *,$(1))),$(call rwildcard,$(d)/$(2))$(filter $(subst *,%,$(2),$(d)))
15
16# recursive make and clean
17.PHONY: build-subdirs
18build-subdirs: $(DIRS)
19
20.PHONY: $(DIRS)
21$(DIRS):
22 make -C $@ all
23
24.PHONY: clean-subdirs
25clean-subdirs:
26 @for d in $(DIRS); do \
27 make -C $$dir clean; \
28 done
29
30# dependencies
31$(OBJS):%.o:%.cpp
32 $(CC) -o $@ $(OBJCFLAGS) $(INCLUDES) $<
subdir.mk
如下
xxxxxxxxxx
61.PHONY: all
2all: build-subdirs $(OBJS)
3
4.PHONY: clean
5clean: clean-subdirs
6 rm -f $(OBJS)
Makefile
如下
xxxxxxxxxx
331INCLUDES := -Iinclude
2DIRS := src standalone
3OBJS :=
4
5PROGRMAS := main1 main2
6
7.PHONY: all
8all: build-subdirs $(OBJS) install
9
10.PHONY: install
11install:
12 @if [[ ! -d bin ]]; then \
13 mkdir bin; \
14 fi
15 @for prog in $(PROGRAMS); do \
16 cp standalone/$$prog bin/$$prog; \
17 done
18
19.PHONY: uninstall
20unistall:
21 rm -rf bin
22
23.PHONY: clena
24clean: clean-subdirs
25 rm -f $(OBJS)
26 make uninstall
27
28.PHONY: distclean
29distclean:
30 cd src; make clean
31 cd standalone; make distclean
32
33include common.mk
standalone/Makefile
如下
xxxxxxxxxx
291INCLUDES := -I../include
2DIRS :=
3OBJS := $(patsubst %.cpp,%.o,$(wildcard *.cpp))
4
5include ../common.mk
6
7LIB1_OBJS := $(patsubst %.cpp,%.o,$(call rwildcard,../src/lib-dir1,*.cpp))
8LIB2_OBJS := $(patsubst %.cpp,%.o,$(call rwildcard,../src/lib-dir2,*.cpp))
9
10PROGRAMS := main1 main2
11main1_OBJS := main1.cpp $(LIB1_OBJS)
12main2_OBJS := main2.cpp $(LIB1_OBJS) $(LIB2_OBJS)
13
14define PROGRAM_template =
15$(1): $$($(1)_OBJS)
16 $(CC) -o $$@ $$(CFLAGS) $$^
17endef
18$(foreach prog,$(PROGRAMS,$(eval $(call PROGRAM_template,$(prog))))
19
20.PHONY: all
21all: build-subdirs $(PROGRAMS)
22
23.PHONY: distclean
24distclean:
25 rm -f $(OBJS)
26
27.PHONY: clean
28clean:
29 rm -f $(OBJS) $(PROGRAMS)
src/Makefile
如下
xxxxxxxxxx
61INCLUDES := -I../include
2DIRS := lib-dir1 lib-dir2
3OBJS :=
4
5include ../subdir.mk
6include ../common.mk
src/lib-dir1/Makefile
如下
xxxxxxxxxx
61INCLUDES := -I../../include
2DIRS := lib-subdir1
3OBJS := f2.o
4
5include ../../subdir.mk
6include ../../common.mk
src/lib-dir1/lib-subdir1/Makefile
如下
xxxxxxxxxx
61INCLUDES := -I../../../include
2DIRS :=
3OBJS := f1.o
4
5include ../../../subdir.mk
6include ../../../common.mk
src/lib-dir1/lib-dir2/Makefile
如下
xxxxxxxxxx
61INCLUDES := -I../../include
2DIRS :=
3OBJS := f3.o
4
5include ../../subdir.mk
6include ../../common.mk
其主要想法是,令复杂子目录下的 Makefile
保持相同的结构和形式,并尽可能地短,所以将规则都写在了 subdir.mk
和 common.mk
中,只需要 include 就好了。
##