zoukankan      html  css  js  c++  java
  • GNU_MAKE--工程管理

    GNU MAKE--工程管理

    makefile是为工程组织编译,为“自动化编译”,一旦写成,只需要一个make命令,整个工程完全自动编译,极大提高了软件开发效率。make是一个命令工具,是一个解释makefile中指令的命令工具。一般来说,大多数IDE都有这个命令,如Delphi的make,Visual C++的nmake,linux下GNU的make。可见,Makefile都成为一种在工程方面的编译方法。
    一个Makefile告诉make命令如何编译和链接文件,规则是:
    1) 如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
    2) 如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
    3) 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的 C 文件,并链接目标程序。
    只要我们的 Makefile 写得够好,所有的这一切,我们只用一个 make 命令就可以完成,make 命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

    关于程序的编译和链接  

    一般来说,无论是C还是C++,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile),一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。
      编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。
      链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来 链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给 中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

    一. 规则

    target …: prerequisites…
      command
    make的书写规则包含两部分,一个是依赖关系,另一个是生成目标的方法。
    prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则,也就是Makefile中最核心的内容。
    makefile文件中只应该有一个最终目标,其他的目标都是被这个目标所连带出来的。一般来说,定义在makefile文件中的目标会很多,但第一条规则中的目标将被确定为最终目标。
    每个Makefile都应该写一个清空目标文件的规则(.o和执行文件),这不仅便于重编译,也有利于保持文件的清洁。这是一个“修养”。
    .PHONY: clean
    clean:
    -rm edit $(objects)

    隐含规则1:倚赖关系中有FORCE

    download: .config FORCE

    FORCE:
    .PHONY: FORCE

    FORCE总会被认为是最新的,这样当FORCE作为其他规则的倚赖时,倚赖总被认为更新过,规则中定义的命令总会被执行。

    二. 执行步骤

    1) 读入所有makefile文件;
    2) 读入被include包括的其他makefile文件;
    3) 初始化文件中变量;
    4) 推导隐式规则,并分析所有规则;
    5) 为所有目标文件创建依赖关系链;
    6) 根据依赖关系,决定哪些目标要重新生成;
    7) 执行生成命令;
    其中,1)-5)为第一阶段,6)-7)为第二阶段。

    注:倚赖关系必须先存在,然后才能执行命令,即倚赖关系先执行,然后生产目标的命令才能执行。

    三. 使用命令

    规则中的命令同OS中Shell命令行是一致的。每条命令必须以Tab键开头,除非命令紧跟在依赖关系分号后。命令行之间的空格或空行会被忽略,但空格或空行以Tab键开头的,make会认为是一个空命令,除非指定一个其他的shell。
    1. 显示命令
    命令行前加“@”字符时,这个命令不被make显示出来。
    1) 如果执行make时,带入make参数-n或--just-print,其只显示命令,而不执行命令。
    2) make参数-s或--slient全面禁止命令显示。

    2. 执行命令
    如果要让一条命令的结果应用在下一条命令上,应使用分号分隔两条命令(两命令同行)。

    3. 命令出错
    忽略错误执行:
    1) 命令行前加“-”;
    2) make加参数-i或--ignore-errors;
    3) make加参数-k或--keep-going(终止此规则执行,继续执行其他规则)。

    4. 嵌套执行make

    不同模块在不同目录中,每个目录都书写一个该目录的makefile文件。
    例:有一个子目录subdir,这个目录下有个makefile文件来指明这个目录下文件编译规则。总控makefile可以这样写:
    subsystem:
    cd subdir && $(MAKE)
    <==>
    subsystem:
    $(MAKE) –C subdir
    定义($(MAKE))宏变量是因为,也许make需要一些参数,所以定义成一个变量比较利于维护。

    dirs :=multfile/src multfile/sub 
        top/src
    
    all:
        $(foreach N, $(dirs),make -C $(N);)
    
    clean:
        $(foreach N, $(dirs),make clean -C $(N);)

    如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:
    export <variable ...>
    如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:
    unexport <variable ...>
    果你要传递所有的变量,那么只要一个export就行了。后面什么也不用跟,表示传递所有的变量。
    要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量, 那么MAKEFILES变量将会是这些参数, 并会传递到下层Makefile中, 这是一个系统级的环境变量。
    但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h” “-o”和“-W”,如果你不想往下层传递参数,那么,你可以这样来:
    subsystem:
      cd subdir && $(MAKE) MAKEFLAGS=
    如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。
    5. 定义命令包
    如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束。
    define run-yacc
    yacc $(firstword $^)
    mv y.tab.c $@
    endef
    这里,“ run-yacc”是这个命令包的名字,其不要和 Makefile 中的变量重名。在“define”和“endef”中的两行就是命令序列。

    四. 通配符

    make支持3种通配符:“*”,“?”和“[…]”。 “*”用转义字符“”来显示“*”。
    * 表示任意一个或多个字符
    ? 表示任意一个字符
    […] [abcd]表示a,b,c,d中任意一个字符,[^abcd]表示除a,b,c,d以外的字符,[0-9]表示0~9中任意一个数字
    ~ 表示用户的home目录
    在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况如果需要通配符有效,就需要使用函数wildcard。

    五. 变量

    变量声明时赋初值,使用时,需要在变量名前加上“$”符号,但最好用“()”或“{}”括起来。如使用真实的$要用“$$”来表示。
    定义变量值时,可以用其他变量来构造变量的值,有两种方式:
    1)= 左变量右侧值
    右侧值可定义在赋值前也可后。
    2):= 赋值时右侧变量必须先定义
    要定义一变量,其值是一个空格,可以
    nullstring :=
    space := $(nullstring) #end
    而dir: = /foo/bar #end代表四个空格
    3)?=
    如:FOO ?= bar
    若FOO没有定义过,变量FOO值就是bar;
    若FOO先前被定义,则这条语句什么也不做。
    4)+= 追加变量值
    如果变量之前没有定义过,“+=”会自动变成“=”,若定义过,“+=”会继承前一次操作的赋值。

    5)变量的高级用法
    这里介绍两种变量的高级使用方法,第一种是变量值的替换。我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。另外一种变量替换的技术是以“静态模式”定义的,如:
    foo := a.o b.o c.o
    bar := $(foo:%.o=%.c)
    这依赖于被替换字串中的有相同的模式,模式中必须包含一个“%”字符,这个例子同样让$(bar)变量的值为“a.c b.c c.c”。
    第二种高级用法是——“把变量的值再当成变量”。
    x = y
    y = z
    a := $($(x))
    在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)
    6) override
    如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
    override <variable> = <value>
    override <variable> := <value>
    当然,你还可以追加:
    override <variable> += <more text>
    对于多行的变量定义, 我们用define指示符, 在define指示符前, 也同样可以使用ovveride 指示符,如:
    override define foo
    bar
    endef
    7) 目标变量
    可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。
    语法是:
    <target ...> : <variable-assignment>
    <target ...> : overide <variable-assignment>
    <variable-assignment>可以是前面讲过的各种赋值表达式,如“=”、“:=”、“+=” 或是“?=”。第二个语法是针对于make命令行带入的变量,或是系统环境变量。这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:
    prog : CFLAGS = -g
    prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o
    8)模式变量
    模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
    make的“模式”一般是至少含有一个“%”的,所以,可以以如下方式给所有以[.o]结尾的目标定义目标变量:
    %.o : CFLAGS = -O
    同样,模式变量的语法和“目标变量”一样:
    <pattern ...> : <variable-assignment>
    <pattern ...> : override <variable-assignment>
    override同样是针对于系统环境传入的变量,或是make命令行指定的变量。

    六. make变量

    环境变量、自动变量和预定义变量。
    1) 环境变量
    make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果 Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)
    因此,如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的 Makefile中使用这个变量了。 这对于我们使用统一的编译参数有比较大的好处。 如果Makefile 中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。
    当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,默认情况下,只有通过命令行设置的变量会被传递,而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。
    2) 自动变量
    make管理项目所使用的自动变量全部以美元符号$开头。
    $@ Makefile文件中一条规则的目标文件名。
    $< 依赖文件中的第一个
    $^ Makefile文件中规则的目标所对应的所有依赖文件的列表,以空格分隔(不重复)
    $? 依赖文件中新于目标的文件列表,以空格分隔
    $(@D) 目标文件的目录部分,如果目标在子目录中。
    $(@F) 目标文件的文件名部分,如果目标在子目录中。
    $* 不包含扩展名的目标文件名称
    $+ 所有依赖文件,空格隔开,先后为序,可重复
    $% 如果目标是归档成员(函数库),则该变量表示目标的归档成员名称。
    3)预定义变量
    make管理项目支持的预定义变量主要用于定义程序名,以及传给这些程序的参数及标志位。
    AR 归档维护程序,缺省值为ar。
    AS 汇编程序,缺省值为as。
    CC C语言编译程序,缺省值为CC。
    CPP C语言预处理程序,缺省值为CPP。
    RM 文件删除程序,缺省值为rm –f。
    ARFLAGS 传给归档维护程序的标志,缺省值为rv。
    ASFLAGS 传给汇编程序的标志,无缺省值。
    CFLAGS 传给C语言编译的标志,无缺省值。
    CPPFLAGS 传给预处理的标志,无缺省值。
    LDFLAGS 传给链接程序的标志,无缺省值
    CXX C++编译器名称,默认值为g++。
    CXXFLAGS C++编译器选项。

    七. 使用条件判断

    使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
    语法:
    <conditional-directive>
    <text-if-true>
    endif
    或者
    <conditional-directive>
    <text-if-true>
    else
    <text-if-false>
    endif
    conditional-directive表示条件关键字,共4个:ifeq,ifneq,ifdef,ifndef。
    ifeq (<arg1>,<arg2>)
    ifneq (<arg1>,<arg2>)
    ifdef <variable-name>
    ifndef <variable-name>
    特别注意的是, make是在读取Makefile时就计算条件表达式的值, 并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。 而且,为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。

    八. make命令

    建立makefile文件后,就可以使用make命令生成和维护目标文件了。
    make [options] [macrodef] [target]
    options指定make的工作行为,macrodef指定执行makefile时的宏值,目标(target)是要更新的文件列表。
    options:
    -C dir make开始运行之后的工作目录为指定目录。如果有多个-C,后面的dir指定的目录是相对于前一个目录。
    -d 打印除一般处理信息之外的调试信息。
    -e 不允许在makefile中对环境的宏赋新值。
    -f 使用指定的文件为makefile
    -i 忽略运行makefile时命令行产生的错误,不退出make。
    -I dir 指定搜索被include的makefile的目录。
    如果命令行中有多个-I选项,按出现顺序依次搜索。与其他选项不同,允许dir紧跟在-I之后(不加空格),这是为了与C预处理器兼容。
    -k 执行命令出错时,放弃当前的目标文件,尽可能地维护其他目标。
    -n 按实际运行时执行顺序显示命令,包括@开头的命令,但不真正执行。
    -o file 不维护指定文件
    -p 显示makefile中所有宏定义和描述内部规则的规则(隐含规则),然后按一般情况执行。
    如只想打印信息而不真正维护,可以使用make –p –f /dev/null
    -q “问题模式”。如果指令的目标目前没有过期,就返回0,否则返回一个非零值。
    不运行任何命令或打印任何信息。
    -r 忽略内部规则,同时清除缺省的后缀规则。
    -s 执行但不显示执行的命令。
    -S 执行makefile命令菜单时出错即退出make。这是make的默认工作方式,一般不指定。
    -t 修改每个目标文件的创建日期,不真正重新创建文件。
    -v 打印make版本号,然后正常执行。如只想打印而不维护,使用
    make –v –f /dev/null

    九. make执行结果

    make命令执行后有三个退出码:
    0--表示成功执行
    1--如果make运行时出现错误,返回1
    2--如果使用make的“-q”选项,并且make使得一些目标不需要更新,返回2。

    十. 软件包安装

    配置、编译、安装源码包软件
    #./configure
    #make
    #make install
    ./configure比较重要的参数为--prefix,指定软件的安装目录。

    十一. 引用makefile

    在Makefile中引用其他Makefile文件的方法是,使用include filename.mk

    参考:

    1. 《跟我一起写Makefile》

    2. Makefile中的wildcard用法

    3. (GNU)MAKE----工程管理

    4. Makefile 使用总结

    5. gnu make

  • 相关阅读:
    基于TensorRT的BERT实时自然语言理解(下)
    基于TensorRT的BERT实时自然语言理解(上)
    lsof
    kata-runtime spec
    kata 虚拟机
    json + jq命令
    kata-runtime run mycontainer
    kata-runtime来运行容器
    docker + docker-runc
    kata container在aarch64上成功运行
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/5556194.html
Copyright © 2011-2022 走看看