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.mk2# make flags3MAKEFLAGS += --warn-undefined-variables4# compile macros5CC := g++6# use bash shell7SHELL := /bin/bash8
9# compile flags10CFLAGS := -std=c++17 -O3 -Werror -Wall -Wextra11OBJCFLAGS := $(CFLAGS) -c12
13# recursive wildcard14rwildcard=$(foreach d,$(wildcard $(addsuffix, *,$(1))),$(call rwildcard,$(d)/$(2))$(filter $(subst *,%,$(2),$(d)))15
16# recursive make and clean17.PHONY: build-subdirs18build-subdirs: $(DIRS)19
20.PHONY: $(DIRS)21$(DIRS):22 make -C $@ all23 24.PHONY: clean-subdirs25clean-subdirs:26 @for d in $(DIRS); do \27 make -C $$dir clean; \28 done29 30# dependencies31$(OBJS):%.o:%.cpp32 $(CC) -o $@ $(OBJCFLAGS) $(INCLUDES) $<
subdir.mk 如下
xxxxxxxxxx61.PHONY: all2all: build-subdirs $(OBJS)3
4.PHONY: clean5clean: clean-subdirs6 rm -f $(OBJS)
Makefile 如下
xxxxxxxxxx331INCLUDES := -Iinclude2DIRS := src standalone3OBJS :=4
5PROGRMAS := main1 main26
7.PHONY: all8all: build-subdirs $(OBJS) install9
10.PHONY: install11install:12 @if [[ ! -d bin ]]; then \13 mkdir bin; \14 fi15 @for prog in $(PROGRAMS); do \16 cp standalone/$$prog bin/$$prog; \17 done18
19.PHONY: uninstall20unistall:21 rm -rf bin22
23.PHONY: clena24clean: clean-subdirs25 rm -f $(OBJS)26 make uninstall27
28.PHONY: distclean29distclean:30 cd src; make clean31 cd standalone; make distclean32 33include common.mk
standalone/Makefile 如下
xxxxxxxxxx291INCLUDES := -I../include2DIRS :=3OBJS := $(patsubst %.cpp,%.o,$(wildcard *.cpp))4
5include ../common.mk6
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 main211main1_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) $$^17endef18$(foreach prog,$(PROGRAMS,$(eval $(call PROGRAM_template,$(prog))))19
20.PHONY: all21all: build-subdirs $(PROGRAMS)22
23.PHONY: distclean24distclean:25 rm -f $(OBJS)26
27.PHONY: clean28clean:29 rm -f $(OBJS) $(PROGRAMS)
src/Makefile 如下
xxxxxxxxxx61INCLUDES := -I../include2DIRS := lib-dir1 lib-dir23OBJS :=4
5include ../subdir.mk6include ../common.mk
src/lib-dir1/Makefile 如下
xxxxxxxxxx61INCLUDES := -I../../include2DIRS := lib-subdir13OBJS := f2.o4
5include ../../subdir.mk6include ../../common.mk
src/lib-dir1/lib-subdir1/Makefile 如下
xxxxxxxxxx61INCLUDES := -I../../../include2DIRS :=3OBJS := f1.o4
5include ../../../subdir.mk6include ../../../common.mk
src/lib-dir1/lib-dir2/Makefile 如下
xxxxxxxxxx61INCLUDES := -I../../include2DIRS :=3OBJS := f3.o4
5include ../../subdir.mk6include ../../common.mk
其主要想法是,令复杂子目录下的 Makefile 保持相同的结构和形式,并尽可能地短,所以将规则都写在了 subdir.mk 和 common.mk 中,只需要 include 就好了。
##