• Linux 应用---make及makefile的编写


      Make 在我们做linux 开发中是必不可少的一部分,它在我们编写大型项目工程文件中起到非常大的作用。

         Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。Make将只编译改动的代码文件,而不用完全编译。

        而Makefile是Make读入的唯一配置文件,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

       下面我们通过两个个实例来学习makefile的编写:

    一、Makefile编写的基本规则

        我在文件夹下有如下文件

    fs@ubuntu:~/qiang/makefile/example1$ ls
    buffer.h  defs.h  files.c  main.c  makefile  utils.c
    fs@ubuntu:~/qiang/makefile/example1$ 
    

    这里的代码就不展示了,我说一下文件包含关系:files.c需要buffer.h及defs.h ,  main.c需要defs.h  ,utils.c需要defs.h。

    我把makefile文件内容展示出来:

    OBJS = main.o files.o utils.o
    CC = gcc
    CFLAGS = -c
    
    Test:$(OBJS)
    $(CC) -o Test $(OBJS)
    main.o:main.c defs.h
    $(CC) $(CFLAGS) main.c
    files.o:files.c buffer.h defs.h
    $(CC) $(CFLAGS) files.c
    utils.o:utils.c  defs.h
    $(CC) $(CFLAGS) utils.c
    
    .PHONY:clean
    clean:
    rm -rf *.o Test
    @echo "Clean done!"

    我们开始学习makefile编写规范:Makefile的根本任务是根据规则生成目标文件。

    1、规则

    一条规则包含三个:目标文件,目标文件依赖的文件,更新(或生成)目标文件的命令。

    规则:               

    <目标文件>:<依赖文件>

             <更新目标的命令>

    注意:命令行前面必须是一个“TAB键”,否则会编译错误!

    Example      

    hello.o: hello.c hello.h    
    gcc -c hello.c -o hello.o

    目标hello.o 依赖于hello.c,hello.h. 生成hello.o的命令时是“gcc -c hello.c -o hello.o”

    2、终极目标

    makefile并不会更新所有规则中的目标,它只会更新终极目标以及终极目标依赖的目标。
    默认情况下makefile的第一个目标是终极目标,而且大家约定俗成的总是将all作为第一个目标。环境变量MAKECMDGOALS记录着终极目标。

    Test:$(OBJS)
    $(CC) -o Test $(OBJS)

    我们这里就是终极目标

    注意:终极目标必须放在第一个,其余的可以随便放!

    3、多规则目标

    Makefile中,一个文件可以作为多个规则的目标,这种情形就是多规则目标。
    多规则目标下,以这个文件为目标的所有规则的依赖文件将会被合并成此一个依赖文件列表,但是命令不会合并,而且实际上,这多个规则中至多只能有一个规则定义了更新命令。

    all:hello.o            

    all:hello.h            

    等价于  all: hello.o hello.h

    main.o:main.c defs.h

    我们这里就是多规则目标;

    4、伪目标
    一般情况下目标文件是一个具体的文件,但有时候我们只需要一个标签,如目标clean。

    声明伪目标:   

    .PHONY:  <伪目标>

    伪目标只是一个标签,这意味着伪目标的时间戳总是最新的,结果就是makefile每次都会去执行更新伪目标的命令。

    .PHONY:clean
    clean:
    rm -rf *.o Test
    @echo "Clean done!"

    我们这里clean就是个伪目标,我们可以看到伪目标是没有依赖文件的,只有用make来调用时,才会执行。

    5、什么时候更新目标

    如果目标不存在或者依赖文件中至少有一个文件的时间戳比目标新,则执行目标更新命令。

    我们这里,如果main.c 或者files被修改时,都会更新目标。

    6、创建和使用变量

    1)定义变量

    makefile的变量定义有四种方法:

    1. 立即赋值 a:=b
    2. 延迟赋值 a=b
    3. 条件赋值 a?=b
    4. 附加赋值 a+=b

    它们之间的区别是:

    第一种方式,会立即计算b的值,并赋值给a;
    第二种方式,相当于C++和java的引用。如果后面b的值改变了,那么a的值也会改变;
    第三种方式,如果a没有定义,则相当于a=b ,否则不执行任何操作;
    第四种方式,将b的值添加到a原有的值后面,再赋值给a。

    2)获取变量值

    $(var) //表示取变量var的值,记得当变量名多于一个字符时,使用”()”.

    OBJS = main.o files.o utils.o
    Test:$(OBJS)$(CC) -o Test $(OBJS)

    这里我们可以看到变量的创建与使用。

    3)预定义变量

      CC  :C编译器的名称,默认值为cc 。CPP  C预编译器的名称,默认值为$(CC) -E。

    CC = gcc
    Test:$(OBJS)$(CC) -o Test $(OBJS)

    这里我们可以看到预定义变量CC的使用。

    其他的预定义变量:

    ARFLAGS  库文件维护程序的选项,无默认值。

    ASFALGS  汇编程序的选项,无默认值。

    CFALGS    C编译器的选项,无默认值。
    ....

    CFLAGS = -c
    main.o:main.c defs.h$(CC) $(CFLAGS) main.c

    这里我们可以看到CFLAGS的使用。

    4)自动变量

    $* 不包含扩展名的目标文件名称

    $< 第一个依赖文件的名称

    $@ 目标文件的完整名称

    $^ 所有不重复的目标依赖文件,以空格分开

    ...

        变量的使用有助于我们修改makefile,大大加快了我们的开发效率。

    7、回显问题

    我们知道,makefile中的命令行都会被打印出来的,如果遇到我们不想打印的命令怎么办?

    @echo "Clean done!"

    在命令行前面加 @ ,就是不回显的意思,这样"echo "Clean done!" "就不会打印。

    我们来看看编译效果:

    fs@ubuntu:~/qiang/makefile/example1$ ls
    buffer.h  defs.h  files.c  main.c  makefile  utils.c
    fs@ubuntu:~/qiang/makefile/example1$ make
    gcc -c main.c
    gcc -c files.c
    gcc -c utils.c
    gcc -o Test main.o files.o utils.o
    fs@ubuntu:~/qiang/makefile/example1$ ls
    buffer.h  files.c  main.c  makefile  utils.c
    defs.h    files.o  main.o  Test      utils.o
    fs@ubuntu:~/qiang/makefile/example1$ make clean
    rm -rf *.o Test
    Clean done!
    fs@ubuntu:~/qiang/makefile/example1$ ls
    buffer.h  defs.h  files.c  main.c  makefile  utils.c
    fs@ubuntu:~/qiang/makefile/example1$ 
    


    二、嵌套执行Makefile

         在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。

    例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:

        subsystem:
                cd subdir && $(MAKE)

    其等价于:

        subsystem:
                $(MAKE) -C subdir

    定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行make命令。

    我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。

    如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:

        export ;

    如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明: 

        unexport ;

    如:
        
        示例一:

            export variable = value

            其等价于:

            variable = value
            export variable

            其等价于:

            export variable := value

            其等价于:

            variable := value
            export variable

        示例二:

            export variable += value

            其等价于:

            variable += value
            export variable

    如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。

    需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。

    但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:

        subsystem:
                cd subdir && $(MAKE) MAKEFLAGS=

    如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。

    还有一个在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,我们会看到:

        make: Entering directory `/home/hchen/gnu/make'.

    而在完成下层make后离开目录时,我们会看到:

        make: Leaving directory `/home/hchen/gnu/make'

    当你使用“-C”参数来指定make下层Makefile时,“-w”会被自动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总是失效的。

    头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。

    下面我们做一个实验,学习嵌套执行Makefile编写的过程:

    1、创建顶层目录

    fs@ubuntu:~/qiang/makefile$ mkdir makefileTest
    fs@ubuntu:~/qiang/makefile$ cd makefileTest/
    fs@ubuntu:~/qiang/makefile/makefileTest$ mkdir f1 f2 main obj include
    fs@ubuntu:~/qiang/makefile/makefileTest$ ls
    f1  f2  include  main  obj
    fs@ubuntu:~/qiang/makefile/makefileTest$ 
    

    在include文件夹中创建一个共用头文件,在其中输入#include

    fs@ubuntu:~/qiang/makefile/makefileTest$ cd include/
    fs@ubuntu:~/qiang/makefile/makefileTest/include$ vi myinclude.h
    fs@ubuntu:~/qiang/makefile/makefileTest/include$ cat myinclude.h 
    #include 
    fs@ubuntu:~/qiang/makefile/makefileTest/include$ 
    

    2、创建顶层Makefile文件

    fs@ubuntu:~/qiang/makefile/makefileTest$ vi Makefile
    

    内容如下:

    CC = gcc                                                                                                         
    SUBDIRS = f1 \
     f2 \
     main \
     obj  //上面三个'\'后面不能有空格
    OBJS = f1.o f2.o main.o
    BIN = myapp
    OBJS_DIR = obj
    BIN_DIR = bin
    export CC OBJS BIN OBJS_DIR BIN_DIR    //导出环境变量,传递到下级目录
    
    all:CHECK_DIR $(SUBDIRS)
    CHECK_DIR:
    mkdir -p $(BIN_DIR)
    $(SUBDIRS):ECHO
    make -C $@ //先进入到SUBDIRS下的目录,再执行make
    
    ECHO:
    @echo $(SUBDIRS)
    @echo begin compile
    
    CLEAN:
    @$(RM) $(OBJS_DIR)/*.o
    @rm -rf $(BIN_DIR)

     3、进入在f1目录下创建makefile

    fs@ubuntu:~/qiang/makefile/makefileTest$ cd f1
    fs@ubuntu:~/qiang/makefile/makefileTest/f1$ vi f1.c
    

    内容如下:

    #include "../include/myinclude.h"                                                                              
    
    void print1()  
    {  
    printf("Message f1.c\n");  
    return;  
    } 
    fs@ubuntu:~/qiang/makefile/makefileTest/f1$ vi Makefile

    内容如下:

    ../$(OBJS_DIR)/f1.o:f1.c                                                                                       
    $(CC) -c $^ -o $@  

     4、进入f2目录

    fs@ubuntu:~/qiang/makefile/makefileTest/f1$ cd ../f2
    fs@ubuntu:~/qiang/makefile/makefileTest/f2$ vi f2.c
    

    内容如下:

    #include "../include/myinclude.h"                                                                              
    
    void print2()  
    {  
    printf("Message f2.c\n");  
    return;  
    } 
    fs@ubuntu:~/qiang/makefile/makefileTest/f2$ vi makefile

    内容如下:

    ../$(OBJS_DIR)/f2.o:f2.c                                                                                       
    $(CC) -c $^ -o $@ 

     5、进入main目录

    fs@ubuntu:~/qiang/makefile/makefileTest/f2$ cd ../main
    fs@ubuntu:~/qiang/makefile/makefileTest/main$ vi main.c
    

    内容如下:

    #include                                                                                              
    
    int main()  
    {  
    print1();  
    print2();  
    
    return 0; 
    }  
    
    fs@ubuntu:~/qiang/makefile/makefileTest/main$ vi Makefile

    内容如下:

    ../$(OBJS_DIR)/main.o:main.c                                                                                   
    $(CC) -c $^ -o $@  

    6、进入obj目录

    fs@ubuntu:~/qiang/makefile/makefileTest$ cd obj
    fs@ubuntu:~/qiang/makefile/makefileTest/obj$ ls
    fs@ubuntu:~/qiang/makefile/makefileTest/obj$ vi Makefile

    内容如下:

    ../$(BIN_DIR)/$(BIN) : $(OBJS)
    $(CC) -o $@ $^

    这样我们总体架构就完成了,我们看一下树状图:

    我们执行一下:

    fs@ubuntu:~/qiang/makefile/makefileTest$ make
    Makefile:3: *** commands commence before first target.  Stop.

    makefile时常遇到这样的问题,汇总网上的原因如下:
    1. 上一行换行符号 \ 后面有空格
    2. 本行前面的空白有非法字符
    1)Makefile可能是以命令行开始:以[Tab]字符开始,但不是一个合法的命令行(例如,一个变量的赋值)。命令行必须和规则一一对应。
    2)第二种原因可能是一行的第一个非空字符为分号,make会认为此处遗漏了规则的“target: prerequisite”部分。

    这儿我们的原因是第一个

    改正后编译:

    fs@ubuntu:~/qiang/makefile/makefileTest$ make
    mkdir -p bin
    f1 f2 main obj
    begin compile
    make -C f1
    make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/f1'
    gcc -c f1.c -o ../obj/f1.o
    make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/f1'
    make -C f2
    make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/f2'
    gcc -c f2.c -o ../obj/f2.o 
    make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/f2'
    make -C main
    make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/main'
    gcc -c main.c -o ../obj/main.o  
    make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/main'
    make -C obj
    make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/obj'
    gcc -o ../bin/myapp f1.o f2.o main.o
    make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/obj'
    

    执行一下:

    fs@ubuntu:~/qiang/makefile/makefileTest$ cd bin/
    fs@ubuntu:~/qiang/makefile/makefileTest/bin$ ls
    myapp
    fs@ubuntu:~/qiang/makefile/makefileTest/bin$ ./myapp
    Message f1.c
    Message f2.c
    fs@ubuntu:~/qiang/makefile/makefileTest/bin$ 
  • 相关阅读:
    LayaBox---TypeScript---类
    java排班算法-几班几倒
    线扫相机DALSA--常见问题四:修改相机参数,参数保存无效情况
    java计算机毕业设计哈尔滨旅游项目推荐平台演示录像2020MyBatis+系统+LW文档+源码+调试部署
    Lumiprobe 双功能交联剂丨Sulfo-Cyanine5 双-NHS 酯
    Django有3种视图与说明
    Spring IOC之ListableBeanFactory
    C++设计模式 - 总结
    docker新手村教程
    气液分离器的选型介绍
  • 原文地址:https://blog.csdn.net/m0_62089210/article/details/127400045