target:prerequisites
command
以上 是 makefile 最核心的规则,target 是目标集,prerequisites 是为了生成 target 的依赖集,command 是为了生成 target 需要执行的命令
#根据 main.o 生成 main.out
main.out : main.o
gcc main.o -o main.out
#生成 main.o
main.o: main.c
gcc -c main.c
其中 prerequisites 和 command 都可以为空, 如果需要的 prerequisites 不存在,就会按照 prerequisites 的生成规则来先生成 prerequisites
#为了生成 all, 最后会执行 script.sh 脚本,这种方式是间接执行脚本
all:
./script.sh
#为了生成 all, 需要先生成 program1 和 program2
all: program1 program2
下面以业界常用的编译规则为例
#指定 c 编译器
CC := gcc
#定义一个空的目标变量,表示最终需要生成的程序
PROGRAM =
#下面是根据最终需要生成的目标,设置不同的编译选项
#如果是生成的动态链接库,则目标文件为 PROGRAM=$(SO_TARGET)
ifdef SO_TARGET
CFLAGS += -shared -fPIC
CXXFLAGS += -shared -fPIC
PROGRAM=$(SO_TARGET)
CSRES := $(shell echo $(SO_TARGET) | awk -F. '{print $$1}')
REPORTDIR := $(shell echo $(SO_TARGET) | awk -F. '{print $$1}')
endif
#如果是生成的二进制程序,则目标文件为 PROGRAM=$(APP_TARGET)
ifdef APP_TARGET
CFLAGS += -rdynamic
CXXFLAGS += -rdynamic
PROGRAM=$(APP_TARGET)
CSRES := $(APP_TARGET)
REPORTDIR := $(APP_TARGET)
endif
#如果是生成的静态链接库,则目标文件为 PROGRAM=$(LIB_TARGET)
ifdef LIB_TARGET
PROGRAM=$(LIB_TARGET)
CSRES := $(shell echo $(LIB_TARGET) | awk -F. '{print $$1}')
REPORTDIR := $(shell echo $(LIB_TARGET) | awk -F. '{print $$1}')
endif
## Stable Section: usually no need to be changed. But you can add more.
##==========================================================================
SHELL = /bin/sh
EMPTY =
SPACE = $(EMPTY) $(EMPTY)
# 如果 PROGRAM 为空,则默认的输出结果为 a.out
ifeq ($(PROGRAM),)
CUR_PATH_NAMES = $(subst /,$(SPACE),$(subst $(SPACE),_,$(CURDIR)))
PROGRAM = $(word $(words $(CUR_PATH_NAMES)),$(CUR_PATH_NAMES))
ifeq ($(PROGRAM),)
PROGRAM = a.out
endif
endif
# 如果 SRCDIRS 变量为空,则默认当前目录为编译目录
ifeq ($(SRCDIRS),)
SRCDIRS = .
endif
# 目标目录下所有的源文件的集合
SOURCES = $(foreach d,$(SRCDIRS),$(wildcard $(addprefix $(d)/*,$(SRCEXTS))))
# 目标目录下所有的头文件的集合
HEADERS = $(foreach d,$(SRCDIRS),$(wildcard $(addprefix $(d)/*,$(HDREXTS))))
# 目标目录下所有的 .c 源文件的集合
SRC_CXX = $(filter-out %.c,$(SOURCES))
# 目标目录下所有的 .o 文件的集合 (注意== 这些只是文件名集合的变量, 实际上这些 .o 文件还没有生成)
OBJS = $(addsuffix .o, $(basename $(SOURCES)))
#定义编译的命令和参数,展开之后其实就是 gcc -g -Wall -Werror -Wshadow.... -c
COMPILE.c = $(CC) $(CFLAGS) $(INCFLAGS) -c
COMPILE.cxx = $(CXX) $(CXXFLAGS) $(INCFLAGS) -c
LINK.c = $(CC) $(CFLAGS) $(LDFLAGS)
LINK.cxx = $(CXX) $(CXXFLAGS) $(LDFLAGS)
AR = ar -cr #用于生成静态库的 ar
#伪目标,防止当前目录下有与目标名冲突的文件或文件夹,导致这些目标生成不了
.PHONY: all objs tags ctags clean distclean help show
#将默认的前缀置空
.SUFFIXES:
#生成最终目标 all
#由于 all 又依赖于 precommand/$(PROGRAM)/postcommand, 因此会依次先生成这三个目标
all: precommand $(PROGRAM) postcommand
#以下是生成 .o 文件的规则
#----------------------------------------
objs:$(OBJS)
#将所有的 .c 文件编译生成对应的 .o 文件, 这里生成之后, $(OBJS) 就可以用了, 也就是 objs 构建完成
# $< 表示所有的依赖目标集(也就是 .c 集合),$@ 表示目标集(也就是 .o 集合)
%.o:%.c
$(COMPILE.c) $< -o $@
%.o:%.C
$(COMPILE.cxx) $< -o $@
%.o:%.cc
$(COMPILE.cxx) $< -o $@
%.o:%.cpp
$(COMPILE.cxx) $< -o $@
%.o:%.CPP
$(COMPILE.cxx) $< -o $@
%.o:%.c++
$(COMPILE.cxx) $< -o $@
%.o:%.cp
$(COMPILE.cxx) $< -o $@
%.o:%.cxx
$(COMPILE.cxx) $< -o $@
# Rules for generating the tags.
#----------------------------------------
tags: $(HEADERS) $(SOURCES)
$(ETAGS) $(ETAGSFLAGS) $(HEADERS) $(SOURCES)
ctags: $(HEADERS) $(SOURCES)
$(CTAGS) $(CTAGSFLAGS) $(HEADERS) $(SOURCES)
# Rules for generating the executable.
#----------------------------------------
# 这里的 $(PRECOMMAND)/$(POSTCOMMAND)/$(CLEANCOMMAND) 可以是脚本或需要执行的命令
precommand:
$(PRECOMMAND)
postcommand:
$(POSTCOMMAND)
cleancommand:
$(CLEANCOMMAND)
#生成最终目标 $(PROGRAM),依赖目标集为 $(OBJS)
#根据最终是生成库文件还是生成二进制文件,来决定是执行链接, 还是 ar
$(PROGRAM):$(OBJS)
ifdef LIB_TARGET #如果是生成 lib 的话就是用 ar
$(AR) $@ $(OBJS)
else
ifeq ($(wildcard *.cpp *.cc),) # C program
$(LINK.c) $(OBJS) $(LIBFLAGS) -o $@
@echo Type $@ to execute the program.
else # C++ program
$(LINK.cxx) $(OBJS) $(LIBFLAGS) -o $@
@echo Type $@ to execute the program.
endif
endif
clean: cleancommand
$(RM) $(OBJS) $(PROGRAM) TAGS
# Show help.
help:
@echo 'Generic Makefile for C/C++ Programs (gcmakefile) version 0.5'
@echo 'Copyright (C) 2007, 2008 whyglinux <whyglinux@hotmail.com>'
@echo
@echo 'Usage: make [TARGET]'
@echo 'TARGETS:'
@echo ' all (=make) compile and link.'
@echo ' NODEP=yes make without generating dependencies.'
@echo ' objs compile only (no linking).'
@echo ' tags create tags for Emacs editor.'
@echo ' ctags create ctags for VI editor.'
@echo ' clean clean objects and the executable file.'
# @echo ' distclean clean objects, the executable and dependencies.'
@echo ' show show variables (for debug use only).'
@echo ' help print this message.'
@echo
@echo 'Report bugs to <whyglinux AT gmail DOT com>.'
# Show variables (for debug use only.)
show:
@echo 'PROGRAM :' $(PROGRAM)
@echo 'SRCDIRS :' $(SRCDIRS)
@echo 'HEADERS :' $(HEADERS)
@echo 'SOURCES :' $(SOURCES)
@echo 'SRC_CXX :' $(SRC_CXX)
@echo 'OBJS :' $(OBJS)
@echo 'COMPILE.c :' $(COMPILE.c)
@echo 'COMPILE.cxx :' $(COMPILE.cxx)
@echo 'link.c :' $(LINK.c)
@echo 'link.cxx :' $(LINK.cxx)
以上是针对所有项目的通用的编译规则,那么具体到整个项目,该如何构造编译树逐级编译呢?
假设有一个大型项目 project , 下面有各级子项目如下
.
└── project
├── projectA
│ └── projectA1
│ └── projectA2
│ └── projectA3
├── projectB
│ └── projectB1
│ └── projectB2
│ └── projectB3
└── rules.make
那么 rules.make 可以作为最基本的编译规则放在项目根目录下。 各级目标依次写 makefile 去向下递归编译
假设 projectA3 的目录树如下
.
└── source
├── apps
│ ├── app1
│ ├── app2
├── libs
├── makefile
那么我们如何编写 projectA 的 makefile 以及如何编译 projectA 呢
首先是 source 下的 makefile ,因为有 apps 和 libs 目录,因此要依次对 apps 和 libs 目录进行编译
all: libs apps
libs:
make -C ./libs all
apps:
make -C ./apps all
clean:
make -C ./libs clean
make -C ./apps clean
scan:
make -C ./libs scan
make -C ./apps scan
.PHONY: all libs apps scan clean
那么对于apps 下的 app1,app2,appN ….. 也是一样的递归向下编译
DIRS = app1 app2 appN...
all:
@for i in ${DIRS}; \
do \
make -C $${i}; \
if [ $$? != 0 ] ; then \
exit 1; \
fi \
done
clean:
@for i in ${DIRS}; \
do \
make -C $${i} clean -j; \
done
scan:
@for i in ${DIRS}; \
do \
make -C $${i} scan; \
done
.PHONY: all clean scan $(DIRS)
一直递归到最后一层包含源代码的目录,就可以直接 include 项目根目录下的编译规则了,
#找到根目录
ROOTDIR=$(shell while true; do if [ -f rules.make ]; then pwd;exit; else cd ..;fi;done;)
#make 之前需要执行的命令
PRECOMMAND = ./script.sh
#make 之后需要清理的命令
CLEANCOMMAND = make -C ./protodef/ clean
#可以自己加一些自定义的链接库
LDFLAGS += -lxxx
#指定最终生成的目标的地方
APP_TARGET=$(APPDIR)/appproxywrk
#以上定义的变量,在inlcude 根目录下的编译规则之后,都会传递到 rules.make 中去
include $(ROOTDIR)/rules.make
最后直接在projectA 下执行一下,就可以将projectA 下的所有项目逐级编译好,并放在指定的文件夹下
make -C source/
上述 makefile 的一些语法细节如下:
.PHONY: all objs tags ctags clean distclean help show
make 命令不指定目标的时候将会只构建第一个目标, 也就是 如果第一个目标是 all , 即使后面还有 clean 之类的,如果不显示指定, 最后只会构建 all, 当然,all 的依赖目标集也会被构建。
-C 是指定需要编译的路径
make -C source/
$<
和 $@
是自动化变量,$<
表示所有的依赖目标集,$@
表示目标集, 以下命令中,依赖目标集为所有的 . c 文件,目标集则为对应的 .o 文件%.o:%.c
$(COMPILE.c) $< -o $@
关于缩进,tab 的区分很严格
关于 foreach 语法,自行百度