文章目录
1.一些概念
构建
- 将源代码文件和资源文件转换为应用程序的过程。包含预处理,编译,汇编,连接,打包部署过程。
为什么需要构建工具
- 项目小则直接使用编译命令进行手动编译来完成构建。而大型项目包含大量的源码文件,头文件,库文件(供链接的目标模块),且依赖关系复杂,构建是一个复杂过程,因而需要使用自动化编译工具来完成构建。常用的构建工具:apach ant,maven,bazel,make,cmake,gradle,webpack,rollup,esbuild等。
手动执行编译命令完成构建工作
# C/C++
gcc source.c -o executable
# Java
javac source.java
make
- make最初用于C/C++编译构建,但也是一个通用构建工具。make根据makefile文件来执行构建操作。
makefile
- makefile由规则构成,每条规则由target,prerequisite,action构成。target是要生成的文件,prerequisite是生成target所依赖的文件,action指示如何使用prerequisite生成target。
- make通过target和prerequisite的修改时间戳决定是否执行操作(target修改时间戳 < prerequisite修改时间戳:执行操作;否则跳过)。
- target没有prerequisite文件,则make查看target文件是否存在,存在则认为target是最新的,跳过执行;target不存在则执行操作。
- 注意,action行以一个tab开头,不能是空格,否则格式错误。
- 实际项目中,更为规范的做法是将编译和链接分成开来写。
2. 例子
2.1 makefile基本写法
# directory tree
make-learning
├── greeting.c
├── greeting.h
├── main.c
└── makefile
# makefile
hello: main.c greeting.c
gcc main.c greeting.c -o hello
# 执行构建
cd make-learning
make # 将在当前目录下找makefile执行
2.2 将编译和链接分开
# makefile
hello: main.o greeting.o
gcc main.o greeting.o -o hello
hello.o: main.c
gcc -c main.c -o main.o
greeting.o: greeting.c
gcc -c greeting.c -o greeting.o
# 执行构建
cd make-learning
make # 找到当前目录构建所有target
3. 伪目标
为什么需要伪目标
- 在构建中需要执行一些不生成文件的操作(如删除临时文件,产生文档,打包等)。
3.1 例子
3.1.1 伪目标:clean
没有将clean声明为伪target的情形
# makefile
hello: main.o greeting.o
gcc main.o greeting.o -o hello
hello.o: main.c
gcc -c main.c -o main.o
greeting.o: greeting.c
gcc -c greeting.c -o greeting.o
clean:
rm -f *.o hello
# 没有clean文件的情况
make clean
# out: rm -f *.o hello
# 有clean文件的情况,clean目标的执行失效
touch clean
make clean
# out: make: `clean' is up to date.
make将clean当做文件处理,如果本地有clean文件,则make认为clean文件是最新的而不予执行。可以提前声明clean为伪目标来避免执行clean target被本地的clean文件影响。
将clean声明为伪target的情形
# makefile
.PHONY: clean
hello: main.o greeting.o
gcc main.o greeting.o -o hello
hello.o: main.c
gcc -c main.c -o main.o
greeting.o: greeting.c
gcc -c greeting.c -o greeting.o
clean:
rm -f *.o hello
# 没有clean文件的情况
make clean
# out: rm -f *.o hello
# 有clean文件的情况,clean目标的执行失效
touch clean
make clean
# out: rm -f *.o hello
3.1.2 伪目标: all
可以通过命令行参数指定要make的target,也可以通过all这个伪target来make多个target。
# makefile
.PHONY: clean all
all: hello world
echo 'all done!!'
hello: main.o greeting.o
gcc main.o greeting.o -o hello
world: main.o greeting.o
gcc main.o greeting.o -o hello
hello.o: main.c
gcc -c main.c -o main.o
greeting.o: greeting.c
gcc -c greeting.c -o greeting.o
clean:
rm -f *.o hello
# 执行伪target all
make all
<< EOF out:
cc -I/opt/homebrew/opt/llvm/include -c -o main.o main.c
gcc -c greeting.c -o greeting.o
gcc main.o greeting.o -o hello
gcc main.o greeting.o -o world
echo 'all done!!'
all done!!
EOF
hello和world两个target规则一样,可以放到一个规则中写,只需用空格隔开两个target名即可。其中使用自动变量$@来表示两个target中的每一个。
hello world: main.o greeting.o
gcc main.o greeting.o -o $@
不打印伪目标 all 的action:在action前加@
.PHONY: clean all
all: hello world
@echo 'all done!!'
hello.o和greeting.o规则一样,可以放在一个规则中写,需要使用通配符号和自动变量。
自动变量:$@ 表示目标文件, $< 表示第一个依赖文件, $^表示所有依赖文件
# before
hello.o: main.c
gcc $(CFLAGS) -c $< -o $@
greeting.o: greeting.c
gcc $(CFLAGS) -c $< -o $@
# after
%.o: %.c
gcc $(CFLAGS) -c $< -o $@
3.2 完整的例子
# makefile
.PHONY: clean all
CFLAGS = -Wall -g -O2
targets = hello world
sources = hello.o: main.c
objects = main.o greeting.o
all: $(targets)
@echo 'all done!!'
$(targets): $(objects)
gcc $(CFLAGS) $(objects) -o $@
%.o: %.c
gcc $(CFLAGS) -c $< -o $@
clean:
rm -f *.o hello world
# 执行
make all
<< EOF
gcc -Wall -g -O2 -c main.c -o main.o
gcc -Wall -g -O2 -c greeting.c -o greeting.o
gcc -Wall -g -O2 main.o greeting.o -o hello
gcc -Wall -g -O2 main.o greeting.o -o world
all done!!
EOF
ps:
- touch一个已经存在的文件,将更新该文件的时间戳 。
- -f参数指定make的规则文件。make能自动识别规则文件makefile,如果规则文件makefile改成其他名字,则需要通过-f参数指定规则文件。
- -C参数指定make执行的目录。项目有多个子模块,每个子模块下有自己的makefile,在项目根目录的makefile中调用子模块的makefile。