欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

程序员文章站 2024-02-18 16:11:16
...

本文转自https://blog.csdn.net/qq_37375427/article/details/79692811#0-qzone-1-9799-d020d2d2a4e8d1a374a433f596ad1440


1、环境变量(全局变量)

-makefile中能够直接使用环境变量的值

  • 如果makefile中定义了同名变量,则环境变量将被覆盖
  • 运行make时,指定“-e”选项,优先使用环境变量

-为什么要在makefile中使用环境变量

优势: 
环境变量可以在所有的makefile中使用

劣势: 
过多的依赖于环境变量,会导致移植性降低

-变量在不同makefile之间的传递方式

  • 直接在外部定义环境变量进行传递
  • 使用export定义变量进行传递(定义临时环境变量)
  • 定义make命令行变量进行传递(推荐)

下面看两个例子,来深刻理解上面的理论:

下面是makefile 的文件:

JAVA_HOME := java home
export var := D.T.Software
new := TDelphi

test :
    @echo "JAVA_HOME => $(JAVA_HOME)"
    @echo "make another file ..."
    @$(MAKE) -f makefile.2
    @$(MAKE) -f makefile.2 new:=$(new)

下面是makefile.2的文件:

test:
    @echo "JAVA_HOME => $(JAVA_HOME)"
    @echo "var => $(var)"
    @echo "new => $(new)"
  • 1

在命令行输入make 显示: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

输入make -e new=lyy 显示: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

2、目标变量(局部变量)

-作用域只在指定的目标以及连带的规则中 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

3、模式变量

-模式变量是目标变量的扩展

-作用域只在符合模式的目标及连带规则中

【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

下面看一个例子:


var := D.T.Software
new := TDelphi

test : var := test-var
%e : override new := test-new
%a : override new := test-new2

test : another
    @echo "test :"
    @echo "var => $(var)"
    @echo "new => $(new)"

another :
    @echo "another :"
    @echo "var => $(var)"
    @echo "new => $(new)"

rule :
    @echo "rule :"
    @echo "var => $(var)"
    @echo "new => $(new)"

输入:make 显示: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

可以看见变量var最终是取的:test : var := test-var 这个目标变量,而不是文件变量:var := D.T.Software

输入 make rule 显示: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

可以看出,new这个变量,取的是:%e : override new := test-new 模式匹配的变量,而不是文件变量new := TDelphi

总结:

  1. 全局变量:makefile外部定义的环境变量
  2. 文件变量:makefile中定义的变量
  3. 局部变量:指定目标的变量

Makefile也算是一门语言,它有自己的语法,那么它应该也会像C/C++语言一样,支持条件判断语句!

Makefile中的条件判断语句:

  • 可以根据条件的值来决定make的执行
  • 可以比较两个不同的变量或者变量和常量的值
ifxxx (arg1,arg2)
#for true
else
#for false
endif

注意事项: 
条件判断语句只能用于控制make实际执行的语句,但是不能控制规则中命令的执行过程!!!

条件判断语句的语法说明:

  • 常用形式
ifxxx (arg1,arg2)
  • 其他合法形式
ifxxx "arg1","arg2"
ifxxx 'arg1','arg2'
ifxxx "arg1",'arg2'
ifxxx 'arg1',"arg2"

注意下图的问题: 
判断语句前面可以是空格,但不能是tab键 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

条件判断关键字: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

下面看一个例子,来加深对上述理论的理解:

.PHONY : test

var1 := A
var2 := $(var1)
var3 := 

test:
    ifeq ($(var1),$(var2))
        @echo "var1 == var2"
    else
        @echo "var1 != var2"
    endif

    ifneq ($(var2),)
        @echo "var2 is NOT empty"
    else
        @echo "var2 is empty"
    endif

    ifdef var2
        @echo "var2 is NOT empty"
    else
        @echo "var2 is empty"
    endif

    ifndef var3
        @echo "var3 is empty"
    else
        @echo "var3 is NOT empty"
    endif     

一开始make后有这个错误: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

这个错误实际上是,判断语句前面应该是空格,而我的判断语句前面都是tab字符,我将判断语句前面的tab键全部删除换成空格后,就可以了! 
修改后,运行make显示: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

下面总结的是Makefile中判断语句的一些工程上的经验:

  1. 条件判断语句之前可以有空格,但是不能有tab键(’\t’)
  2. 在条件判断语句中,不要使用自动变量aaa@qq.com $^ $<
  3. 一个完整的条件判断语句,必须位于同一个makefile中
  4. 条件判断语句类似于C语言中的宏,预处理阶段有效,执行阶段无效
  5. make在加载makefile时

    *首先计算表达式的值(赋值方式不同,计算方式不同) 
    根据判断语句的表达式*,决定执行的内容

分析一下,下图中的两个代码,输出是相同的么: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

看看下面的代码:

.PHONY : test

var1 :=
var2 := $(var1)

var3 =
var4 = $(var3)

#var3 = 3 

test:
    ifdef var1 
        @echo "var1 is defined"
    else
        @echo "var1 is NOT defined"
    endif

    ifdef var2
        @echo "var2 is defined"    
    else
        @echo "var2 is NOT defined"    
    endif

    ifdef var3 
        @echo "var3 is defined"
    else
        @echo "var3 is NOT defined"
    endif

    ifdef var4
        @echo "var4 is defined"    
    else
        @echo "var4 is NOT defined"    
    endif

运行make后显示: 
【Makefile由浅入深完全学习记录7】Makefile中变量的高级主题下

总结:

  1. 条件判断语句根据条件的值来决定make的执行
  2. 条件判断可以比较两个不停变量或者变量和常量
  3. 条件判断在预处理阶段有效,在执行阶段无效
  4. 条件判断不能控制规则中命令的执行过程


makefile中的自动化变量

模式规则中,规则的目标和依赖文件名代表了一类文件名;规则的命令是对所有这一类文件重建过程的描述,显然,在命令中不能出现具体的文件名,否则模式规则失去意义。那么在模式规则的命令行中该如何表示文件,将是本小节的讨论的重点。

假如你需要书写一个将.c 文件编译到.o 文件的模式规则,那么你该如何为gcc 书写正确的源文件名?当然了,不能使用任何具体的文件名,因为在每一次执行模式规则时源文件名都是不一样的。为了解决这个问题,就需要使用“自动环变量”,自动化变量的取值是根据具体所执行的规则来决定的,取决于所执行规则的目标和依赖文件名。

下面对所有的自动化变量进行说明:
aaa@qq.com
表示规则的目标文件名。如果目标是一个文档文件(Linux中,一般称.a 文件为文档文件,也称为静态库文件),那么它代表这个文档的文件名。在多目标模式规则中,它代表的是哪个触发规则被执行的目标文件名。

$%
当规则的目标文件是一个静态库文件时,代表静态库的一个成员名。例如,规则的目标是“foo.a(bar.o)”,那么,“ $$%”的值就为“bar.o”,“ aaa@qq.com ”的值为“foo.a”。如果目标不是静态库文件,其值为空。

$<
规则的第一个依赖文件名。如果是一个目标文件使用隐含规则来重建,则它代表由隐含规则加入的第一个依赖文件。

$?
所有比目标文件更新的依赖文件列表,空格分割。如果目标是静态库文件名,代表的是库成员(.o 文件)。

$^
规则的所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有库成员(.o 文件)名。一个文件可重复的出现在目标的依赖中,变量“*”只记录它的一次引用情况。就是说变量“^*”会去掉重复的依赖文件。

$+
类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。

$*

在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“% ”所代表的部分(当文件名中存在目录时,“茎”也包含目录(斜杠之前)部分,可参考 10.5.4 模式的匹配 一小节)。例如:文件“dir/a.foo.b”,当目标的模式为“a.%.b ”时,“$* ”的值为“dir/a.foo ”。“茎”对于构造相关文件名非常有用。


AR 归档维护程序的名称,默认值为 ar。

ARFLAGS 归档维护程序的选项。

AS 汇编程序的名称,默认值为 as。

ASFLAGS 汇编程序的选项。

CC C 编译器的名称,默认值为 cc。

CCFLAGS C 编译器的选项。

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

CPPFLAGS C 预编译的选项。

CXX C++ 编译器的名称,默认值为 g++。

CXXFLAGS C++ 编译器的选项。

FC FORTRAN 编译器的名称,默认值为 f77。

FFLAGS FORTRAN 编译器的选项。


典型例子

CC := g++  
CFLAGS := -g  
TARGET := test  
SRCS := $(wildcard *.cpp)  
OBJS := $(patsubst %cpp,%o,$(SRCS))  

all:$(TARGET)  
%.o:%.cpp  
    $(CC) $(CFLAGS) -c $< -o aaa@qq.com 
$(TARGET):$(OBJS)  
    $(CC) $(CFLAGS) -o aaa@qq.com  
clean:  
    rm -rf $(TARGET) *.o 

 常见赋值操作的含义

= 是最基本的赋值

:= 是覆盖之前的值

?= 是如果没有被赋值过就赋予等号后面的值

+= 是添加等号后面的值

:= 和 = 的区别

“=”

make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:

x = foo
y = $(x) bar
x = xyz

在上例中,y的值将会是 xyz bar ,而不是 foo bar 。

“:=”

“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。

x := foo
y := $(x) bar
x := xyz

在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。


3 多个文件时,如何编写Makefile

例子1:将 bin src obj include 单独存放时,Makefile文件的通用编写方法

文件存放说明:

  • bin: 存放编译生成的二进制文件
  • src: 存放源文件 (add.c multis.c sub.c main.c)
  • obj: 存放编译生成的目标文件
  • include: 存放头文件 (add.h multis.h sub.h)
  • Makefile 文件和 bin、src、include处于同一级目录。

Makefile文件的写法:

INC_DIR=./include
BIN_DIR=./bin
SRC_DIR=./src
OBJ_DIR=./obj

SRC=${wildcard ${SRC_DIR}/*.c}
OBJ=${patsubst %.c, $(OBJ_DIR)/%.o, ${notdir ${SRC}}}

#用于查看变量的值
#test:
#   echo $(SRC)
#   echo $(OBJ)

TARGET=main
BIN_TARGET=${BIN_DIR}/${TARGET}

CC=gcc
CFLAGS= -g -Wall -I${INC_DIR}

${BIN_TARGET}:${OBJ}
    ${CC} ${OBJ} -o aaa@qq.com

${OBJ_DIR}/%.o:${SRC_DIR}/%.c
    ${CC} ${CFLAGS} -c $< -o aaa@qq.com

clean:
    find ${OBJ_DIR} -name *.o -exec rm -rf {} \;

说明:

  • notdir ${SRC}: 去除.c 文件中的目录,如:./src/mytest.c, 通过notdir之后得到 mytest.c。
  • $(wildcard .cpp /xxx/xxx/.cpp) 为获取当前目录下和/xxx/xxx/目录下所有.cpp文件名
  • (patsubst(patsubst(SRC))为替换所有的.cpp为.o
  • find ${OBJ_DIR} -name *.o -exec rm -rf {} \;输入man find,查看find命令中 -exec的具体用法。

Execute command; true if 0 status is returned. All following arguments to find are taken to be argu‐ 
ments to the command until an argument consisting of ;' is encountered. The string{}’ is replaced 
by the current file name being processed everywhere it occurs in the arguments to the command, not just 
in arguments where it is alone, as in some versions of find. Both of these constructions might need to 
be escaped (with a `\’) or quoted to protect them from expansion by the shell. See the EXAMPLES sec‐ 
tion for examples of the use of the -exec option. The specified command is run once for each matched 
file. The command is executed in the starting directory. There are unavoidable security problems 
surrounding use of the -exec action; you should use the -execdir option instead.

例子2:多个文件存放在不同的目录时,Makefile的写法。

文件存放说明:

  • add 目录 (add.c add.h)
  • sub 目录 (sub.c sub.h)
  • mutis 目录 (mutis.c mutis.h)
  • main 目录(main.c Makefile)

Makefile文件的写法:

CUR_DIR=add/sub/mutis/main文件所处的目录

ADD_DIR=${CUR_DIR}/add
SUB_DIR=${CUR_DIR}/sub
MUL_DIR=${CUR_DIR}/multis
MAIN_DIR=${CUR_DIR}/main

INC_DIR= -I${ADD_DIR} \
         -I${SUB_DIR} \
         -I${MUL_DIR} \
         -I${MAIN_DIR}

SRC = ${wildcard  ${ADD_DIR}/*.c} \
      ${wildcard  ${SUB_DIR}/*.c} \
      ${wildcard  ${MUL_DIR}/*.c} \
      ${wildcard  ${MAIN_DIR}/*.c}

OBJ = ${patsubst %.c, %.o, ${SRC}}

TARGET=main
CC=gcc
CCFLAGS=-g -Wall ${INC_DIR}

${TARGET}: ${OBJ}
    ${CC} ${notdir ${OBJ} } -o aaa@qq.com
    echo "Compile done."
${OBJ}:${SRC}
    $(CC) ${CCFLAGS} -c $? 

clean:
    rm -f ${OBJ}
    rm -f *.o
    rm -f *~
    rm -f ${TARGET}
    echo "Clean done."

改进版本:

CUR_DIR=/home/xiaojie/Desktop/demo_multi_makefile/test3

ADD_DIR=${CUR_DIR}/add
SUB_DIR=${CUR_DIR}/sub
MUL_DIR=${CUR_DIR}/multis
MAIN_DIR=${CUR_DIR}/main

INC_DIR= -I${ADD_DIR} \
         -I${SUB_DIR} \
         -I${MUL_DIR} \
         -I${MAIN_DIR}

SRC = ${wildcard  ${ADD_DIR}/*.c} \
      ${wildcard  ${SUB_DIR}/*.c} \
      ${wildcard  ${MUL_DIR}/*.c} \
      ${wildcard  ${MAIN_DIR}/*.c}
OBJ = ${patsubst %.c, %.o, ${SRC}}

TARGET=main
CC=gcc
CCFLAGS=-g -Wall ${INC_DIR}


${TARGET}: ${OBJ}
    ${CC} ${OBJ} -o aaa@qq.com
    @echo "Compile done."

#${OBJ}:${SRC}
#   $(CC) ${CCFLAGS} -c $? 

$(OBJ):%.o:%.c
    @echo "Compiling $< ==> aaa@qq.com"
    ${CC} ${CCFLAGS} -c $< -o aaa@qq.com

clean:
    @rm -f ${OBJ}
    @echo "Clean object files done."

    @rm -f *~
    @echo "Clean tempreator files done."

    @rm -f ${TARGET}
    @echo "Clean target files done."

    @echo "Clean done.

说明:

1.@ echo "" 表示执行该条命令,但不输出该命令的内容。

2.改进版本中将

${OBJ}:${SRC}
    $(CC) ${CCFLAGS} -c $? 

替换成了,

$(OBJ):%.o:%.c
    @echo "Compiling $< aaa@qq.com"
    ${CC} ${CCFLAGS} -c $< -o aaa@qq.com