简易Makefile编写笔记

简易Makefile编写笔记


g++的基本使用

   习惯IDE的一键编译运行有时候并不是一件好事,特别是对于我这种连编译、链接、构建、调试分别是什么都搞不清楚的人。查阅了一些资料后,我得知gcc/g++是一种常用的程序构建工具,它可以把源代码、头文件链接起来,构建出可执行的二进制文件。

 root
 ├── bin
 ├── include
 │   └── add.h
 └── src
     ├── add.cpp
     └── main.cpp

   问题来了,如上方所示,一个很简单的工程,根目录下的include下有一个头文件,src下有两个源文件,如果想将它们编译成可执行文件prod.out放进bin里面,就需要用-o指定编译目标,并在后面跟上目标依赖的所有源文件和头文件。

$ g++ -o bin/prod.out src/add.cpp src/main.cpp include/add.h

如果修改了某个源文件,想要重新编译,就面临着两个问题:

  1. 如果工程很大,涉及到很多源文件和头文件,这个命令会很长,敲起来很麻烦
  2. 没有改动的文件也会被重新编译,编译过程将会很耗时

Makefile和make

把上面的命令写成makefile,然后在Terminal里敲make就能实现编译,makefile里的第一行分为两部分,冒号前的是目标,冒号后的是目标的依赖,第二行则是要运行的编译命令,注意在makefile里的命令前都要加上tab缩进

bin/prod.out: src/main.cpp src/add.cpp include/add.h
	g++ -std=c++11 -o bin/prod.out src/main.cpp src/add.cpp include/add.h

上面这段代码可重用性很低,现在解决一下重用性问题,其实就是用宏替代,如此就解决了第一个问题。

CC = g++
C11 = -std=c++11
OBJ = bin/prod.out
SRC = src/main.cpp src/add.cpp
INC = include/add.h

$(OBJ) : $(SRC) $(INC)
	$(CC) $(C11) -o $(OBJ) $(SRC) $(INC)

而针对第二个问题,只要在连接成可执行文件前,生成并保留.o文件,make会根据源文件修改时间选择性重新编译,然后连接成最终的可执行文件。

CC = g++
C11 = -std=c++11
TARGET = bin/prod.out
INC = include/add.h
OBJ = obj/main.o obj/add.o

# 注意下面两行必须放在这里,放在最后的话将不会运行
$(TARGET) : $(OBJ)
	$(CC) $(C11) -o $(TARGET) $(OBJ)

obj/main.o : src/main.cpp $(INC)
	$(CC) $(C11) -c src/main.cpp -o obj/main.o

obj/add.o : src/add.cpp $(INC)
	$(CC) $(C11) -c src/add.cpp -o obj/add.o

这样写还是太麻烦了,每个源文件都要编译,都要单独写两行,所以用正则表达改写一下。
这里的%.o表示当前路径下所有的.o文件,可在前面加上路径连用,$<表示依赖项的第一项$@表示当前目标。

CC = g++
C11 = -std=c++11
TARGET = bin/prod.out
INC = include/add.h
OBJ = obj/main.o obj/add.o

$(TARGET) : $(OBJ)
	$(CC) $(C11) -o $(TARGET) $(OBJ)

# 表示将src中的cpp文件编译为同名的.o文件保存到obj中
# $< 表示依赖文件的第一项,即src/%.cpp
# $@ 表示当前目标,即obj/%.o
obj/%.o : src/%.cpp $(INC)
	$(CC) $(C11) -c $< -o $@

最终版本

最后,把路径也用宏优化一下

CC = g++
C11 = -std=c++11

# path assignment
WORK_DIR = ./
INC_PATH = $(join $(WORK_DIR), include)
SRC_PATH = $(join $(WORK_DIR), src)
OBJ_PATH = $(join $(WORK_DIR), obj)
BIN_PATH = $(join $(WORK_DIR), bin)

# src/*.cpp
SRC_FILE_WITH_DIR = $(wildcard $(join $(SRC_PATH), /*.cpp))
# *.cpp
SRC_FILE = $(notdir $(SRC_FILE_WITH_DIR))
# *.o
OBJ_FILE = $(SRC_FILE:%.cpp=%.o)
# obj/*.o
OBJ_FILE_WITH_DIR = $(patsubst %.o, $(OBJ_PATH)/%.o, $(OBJ_FILE))

TARGET = $(join $(BIN_PATH), /work)

$(OBJ_PATH)/%.o : $(SRC_PATH)/%.cpp
	$(CC) $(C11) -I$(INC_PATH) -c $< -o $@

$(TARGET) : $(OBJ_FILE_WITH_DIR)
	$(CC) $(C11) -o $@ $^

.PHONY: clean
clean:
	@rm $(OBJ_FILE_WITH_DIR) # @表示不在终端打印执行的命令
	@rm $(TARGET)

最终生成的文件目录结构如下:

 root
 ├── bin
 │   └── prod.out 可执行文件
 ├── include
 │   └── add.h
 ├── makefile
 ├── obj
 │   ├── add.o
 │   └── main.o
 └── src
     ├── add.cpp
     └── main.cpp

总结

  编译C++工程,用g++不能实现“改哪个编译哪个”的智能编译,也不能避免依赖关系多时,编译命令的冗长复杂重用性低,用makefile可以一次性满足这两个愿望。一方面,正则表达和宏使得使得代码更加灵活,makefile一旦写好,以后编译只需在终端敲make,另一方面,保留.o文件使得make能自动发现改动过的文件,选择性地重新编译,节省编译时间。

猜你喜欢

转载自blog.csdn.net/songbinxu/article/details/82751988
今日推荐