美文网首页Shell和命令
Makefile生成目录+多文件批量编译

Makefile生成目录+多文件批量编译

作者: 哈莉_奎茵 | 来源:发表于2018-03-08 17:48 被阅读21次

    示例项目结构

    ~/project$ tree
    .
    ├── include
    │   └── foo.h
    ├── lib
    │   └── foo.c
    ├── Makefile
    └── src
        ├── Makefile
        ├── test1.cc
        ├── test2.cc
        └── test3.cc
    
    3 directories, 7 files
    

    lib/foo.c是C编写的库文件,为foo()函数的具体实现,include/foo.h是对应的头文件,包含foo()函数的声明,src目录下的3个文件则是main()函数中调用foo()函数的源文件。

    需求及相应gcc编译命令

    现在需求是,把foo.c生成的.o文件放到目录obj下(若不存在则新建),然后分别对src目录下的文件进行编译生成各自的可执行文件,把生成的可执行文件放到目录bin下(若不存在则新建)。

    ~/project$ mkdir -p build
    ~/project$ gcc -c lib/foo.c -o ./build/foo.o
    ~/project$ mkdir -p bin
    ~/project$ g++ -Wall -O2 -I./include src/test1.cc obj/foo.o -o ./bin/test1
    ~/project$ g++ -Wall -O2 -I./include src/test2.cc obj/foo.o -o ./bin/test2
    ~/project$ g++ -Wall -O2 -I./include src/test3.cc obj/foo.o -o ./bin/test3
    ~/project$ tree
    .
    ├── bin
    │   ├── test1
    │   ├── test2
    │   └── test3
    ├── include
    │   └── foo.h
    ├── lib
    │   └── foo.c
    ├── Makefile
    ├── obj
    │   └── foo.o
    └── src
        ├── Makefile
        ├── test1.cc
        ├── test2.cc
        └── test3.cc
    
    6 directories, 12 files
    

    mkdir命令的-p选项则是在目录已经不存在时不报错,把该目录当成已经创建的目录。
    可以发现,编译testx.cc(x=1,2,3)的命令基本一致,都要和foo.o一起链接,都要把include目录作为包含目录。因此这些命令重复度较大,可以利用Makefile的进行简化。

    Makefile的编写

    特殊变量

    • $@ 目标文件
    • $^ 所有的依赖文件
    • $< 第一个依赖文件

    也就是说对于下面的Makefile命令

    foo.o: foo.c foo.h
        gcc -c foo.c -o foo.o
    

    可以简化为

    foo.o: foo.c foo.h
        gcc -c $< -o $@
    

    这几个特殊变量即Makefile批量编译的基础支持,可以把这样的语句理解成类似C函数的概念,那么第二行就是函数体内容,$<代指第1个输入参数,$@代指输出参数。现在的问题是如何取得需要调用的列表。

    函数

    具体用法可以参考陈皓先生的跟我一起写Makefile
    利用函数可以取得源文件列表和目标文件列表

    # ~project/Makefile
    SOURCE = $(wildcard src/*.cc)
    PROGS = $(patsubst %.cc, bin/%, $(notdir $(SOURCE)))
    
    1. wildcard函数表示可以解释输入参数src/*.cc的通配符,就像ls命令一样。因此在上面的Makefile中,SOURCEsrc目录下的.cc文件列表src/test1.cc src/test2.cc src/test3.cc
    2. notdir函数表示找出输入参数中的非目录部分,比如对src/test1.cc会返回test1.cc,因此$(notdir $(SOURCE))test1.cc test2.cc test3.cc
    3. patsubst即字符串处理,参数3是批量处理的列表,参数1是模式,参数2是替换字符串。如果列表中的元素满足模式,则用参数2来替换。这里的%类似通配符,比如test1.cc满足模式%.cc,那么替换字符串的%则表示test1

    也就是说,上述2句Makefile代码在我这个项目结构中等价于

    SOURCE = src/test1.cc src/test2.cc src/test3.cc
    PROGS = bin/test1 bin/test2 bin/test3
    

    由于是动态生成的,假如src目录下多了文件test4.cc,上述变量会分别增加src/test4.ccsrc/test4

    生成目录

    参考GNU make下创建目录的问题
    之前作者的做法类似于跟我一起学Makefile时的做法(.PHONY文件),把目录作为依赖项,然后目录下新建一个隐藏文件来作为目录的依赖项。因为在目录下创建新文件会导致目录的时间属性被更新,所以要用目录下的隐藏文件的时间属性来代替目录的时间属性。
    在Make 3.80后的版本中,可以用order-only的依赖来解决。比如
    ../obj/foo.o: foo.c | ../obj
    注意依赖参数../obj前面加了个|来表示该目录是order-only类型,因此在该目录下新建文件不会导致foo.o被重新生成。

    最终Makefile文件

    # src/Makefile 
    CC = g++
    FLAGS = -Wall
    BIN = ../bin
    LIB = ../lib
    SRC = ../src
    INC = ../include
    OBJ = ../obj
    
    FOO_LIB = $(OBJ)/foo.o
    
    SOURCE = $(wildcard $(SRC)/*.cc)
    PROGS = $(patsubst %.cc, $(BIN)/%, $(notdir $(SOURCE)))
    
    all: $(PROGS) $(FOO_LIB)
    
    $(BIN)/%: $(SRC)/%.cc $(FOO_LIB) $(INC)/foo.h | $(BIN)
        $(CC) $(FLAGS) $< $(FOO_LIB) -o $@ -I$(INC)
    
    $(BIN):
        mkdir -p $@
    
    $(FOO_LIB): $(LIB)/foo.c | $(OBJ)
        gcc -c $< -o $@
    
    $(OBJ):
        mkdir -p $(OBJ)
    
    clean:
        rm -f $(FOO_LIB)
        rm -f $(PROGS)
    
    # Makefile 
    DIRS = src
    MAKE = make
    
    all:
        for i in $(DIRS); do \
            (cd $$i && echo "making $$i" && $(MAKE)) || exit 1; \
        done
    
    clean:
        for i in $(DIRS); do \
            (cd $$i && echo "cleaning $$i" && $(MAKE) clean) || exit 1; \
        done
    

    分别是子目录和父目录的Makefile,这种做法是学习apue.3e的做法,即支持在子目录下单独make,也支持在根目录下批量调用子目录下的make。需要注意的一点是使用shell语法的for循环时,Makefile中要用$$i而非$i

    相关文章

      网友评论

        本文标题:Makefile生成目录+多文件批量编译

        本文链接:https://www.haomeiwen.com/subject/ilpyfftx.html