Makefile札记

Makefile:= ?= += =的区别


Makefile中我们经常看到 = := ?= +=这几个赋值运算符,那么他们有什么区别呢?我们来做个简单的实验

新建一个Makefile,内容为:

ifdef DEFINE_VRE

    VRE = “Hello World!”

else

endif

ifeq ($(OPT),define)

    VRE ?= “Hello World! First!”

endif

ifeq ($(OPT),add)

    VRE += “Kelly!”

endif

ifeq ($(OPT),recover)

    VRE := “Hello World! Again!”

endif

all:

    @echo $(VRE)

敲入以下make命令:

make DEFINE_VRE=true OPT=define 输出:Hello World!

make DEFINE_VRE=true OPT=add 输出:Hello World! Kelly!

make DEFINE_VRE=true OPT=recover  输出:Hello World! Again!

make DEFINE_VRE= OPT=define 输出:Hello World! First!

make DEFINE_VRE= OPT=add 输出:Kelly!

make DEFINE_VRE= OPT=recover 输出:Hello World! Again!

从上面的结果中我们可以清楚的看到他们的区别了

= 是最基本的赋值

:= 是覆盖之前的值

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

+= 是添加等号后面的值

之前一直纠结makefile“=”“:=”的区别到底有什么区别,因为给变量赋值时,两个符号都在使用。网上搜了一下,有人给出了解答,但是本人愚钝,看不懂什么意思。几寻无果之下,也就放下了。今天看一篇博客,无意中发现作者对于这个问题做了很好的解答。解决问题之余不免感叹,有时候给个例子不就清楚了吗?为什么非要说得那么学术呢。^_^

      1“=”

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

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

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

      2“:=”

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

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

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



makefile宏定义:EXTRA_CFLAGS += -DCONFIG_ =y


EXTRA_CFLAGS += -D CONFIG_ =y

1.

假如定义一个宏CONFIG_DEBUG

.c里面定义为:#define CONFIG_DEBUG

makefile里定义为: CONFIG_DEBUG=y

假如说我们想在makefile里为.c文件进入一个宏定义,就用EXTRA_CFLAGS += DCONFIG_DEBUG(等价于在.c文件里定义#define CONFIG_DEBUG)

这时CONFIG_DEBUG=yEXTRA_CFLAGS += DCONFIG_DEBUG的区别应该你已经看出来的,前者是对makefile编译时用的,比如说obj-(CONFIG_DEBUG) += test.o,而后者则是对.c源文件里的用的,比如说:

#if defined(CONFIG_DEBUG) 

...

#else

...

#endif

2.


假如定义一个宏CONFIG_DEBUG = 3

.c里面定义为:#define CONFIG_DEBUG 3

假如说我们想在makefile里为.c文件进入一个宏定义,就用EXTRA_CFLAGS += -DCONFIG_DEBUG=3

此时两者的定义完全相同。



Makefile中通配符*%的区别是什么?

此两者均为通配符,但更准确的讲,%Makefile规则通配符,一般用于规则描述,如

%.o:%c

    $(CC)  $< -o $@

表示所有的目标文件及其依赖文件,或者

$(filter %.c ,SOURCES)

此处SOURCES表示包含.c .cc .cpp等多类型源文件,该过滤器函数将c文件过滤出来,而%.c即为此过滤器规则。

通配符*则不具备上述功能。尤其是在Makefile,当变量定义或者函数调用时,该通配符的展开功能就失效了,即不能正常使用了,此时需要借助wildcard函数。二者应用范围不同。



Makefile有三个非常有用的变量。分别是$@$^$<代表的意义分别是:

# 这是上面那个程序的Makefile文件

mainmain.o mytool1.o mytool2.o 


gcc -o main main.o mytool1.o mytool2.o 


main.omain.c mytool1.h mytool2.h 


gcc -c main.c 


mytool1.omytool1.c mytool1.h 


gcc -c mytool1.c 


mytool2.omytool2.c mytool2.h 


gcc -c mytool2.c

有了这个Makefile文件,不论我们什么时候修改了源程序当中的什么文件,我们只要执行make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件它连理都不想去理的。 下面我们学习Makefile是如何编写的。 Makefile中也#开始的行都是注释行.Makefile中最重要的是描述文件的依赖关系的说明。一般的格式是: 

targetcomponents 

TAB rule 

第一行表示的是依赖关系。第二行是规则。 


比如说我们上面的那个Makefile文件的第二行。 

mainmain.o mytool1.o mytool2.o 

表示我们的目标(target)main的依赖对象(components)main.o mytool1.omytool2.o当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令。

就象我们的上面那个Makefile第三行所说的一样要执行 gcc-o main main.o mytool1.o mytool2.o注意规则一行中的TAB表示那里是一个TAB Makefile有三个非常有用的变量。

分别是$@$^$<代表的意义分别是: 

$@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。 如果我们使用上面三个变量,

那么我们可以简化我们的Makefile文件为: 

# 这是简化后的Makefile 

mainmain.o mytool1.o mytool2.o 

gcc -o $@ $^ 


main.omain.c mytool1.h mytool2.h 

gcc -c $< 


mytool1.omytool1.c mytool1.h 

gcc -c $<


mytool2.omytool2.c mytool2.h 

gcc -c $< 


经过简化后,我们的Makefile是简单了一点,不过人们有时候还想简单一点。这里我们学习一个Makefile的缺省规则 .c.o gcc -c $< 这个规则表示所有的 .o文件都是依赖与相应的.c文件的。例如mytool.o依赖于mytool.c这样Makefile还可以变为: 


# 这是再一次简化后的Makefile 

mainmain.o mytool1.o mytool2.o 

gcc -o $@ $^ 

.c.o 

gcc -c $< 


好了,我们的Makefile 也差不多了,如果想知道更多的关于Makefile的规则,可以查看相应的文档。





makefilePHONY的相关介绍

PHONY 目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用PHONY目标:避免和同名文件冲突,改善性能。

如果编写一个规则,并不产生目标文件,则其命令在每次make该目标时都执行。例如:
  clean:
  rm *.o temp
因为"rm"命令并不产生"clean"文件,则每次执行"make clean"的时候,该命令都会执行。如果目录中出现了"clean"文件,则规则失效了:没有依赖文件,文件"clean"始终是最新的,命令永远不会执行;为避免这个问题,可使用".PHONY"指明该目标。如:
  .PHONY : clean
  这样执行"make clean"会无视"clean"文件存在与否。

已知phony 目标并非是由其它文件生成的实际文件,make会跳过隐含规则搜索。这就是声明phony目标会改善性能的原因,即使你并不担心实际文件存在与否。
  完整的例子如下:
  .PHONY : clean
  clean :
  rm *.o temp

phony 目标可以有依赖关系。当一个目录中有多个程序,将其放在一个makefile中会更方便。因为缺省目标是makefile中的第一个目标,通常将这个phony目标叫做"all",其依赖文件为各个程序:
  all : prog1 prog2 prog3
  .PHONY : all
  prog1 : prog1.o utils.o
           cc -o prog1 prog1.o utils.o
  prog2 : prog2.o
           cc -o prog2 prog2.o
  prog3 : prog3.o sort.o utils.o
           cc -o prog3 prog3.o sort.o utils.o

假设你的一个项目最后需要产生两个可执行文件。你的主要目标是产生两个可执行文件,但这两个文件是相互独立的——如果一个文件需要重建,并不影响另一个。你可以使用假象目的达到这种效果。一个假象目的跟一个正常的目的几乎是一样的,只是这个目的文件是不存在的。因此, make总是会假设它需要 被生成,当把它的依赖文件更新后,就会执行它的规则里的命令行。

如果在我们的 makefile 开始处输入:

all : exec1 exec2

其中 exec1 exec2是我们做为目的的两个可执行文件。 make把这个 'all' 做为它的主要目的,每次执行时都会尝试把 'all'更新。但既然这行规则里没有哪个命令来作用在一个叫 'all'实际文件(事实上 all并不会在磁碟上实际产生),所以这个规则并不真的改变 'all'的状态。可既然这个文件并不存在,所以 make会尝试更新 all 规则,因此就检查它的依靠 exec1, exec2是否需要更新,如果需要,就把它们更新,从而达到我们的目的。

假象目的也可以用来描述一组非预设的动作。例如,你想把所有由 make产生的文件删除,你可以在 makefile里设立这样一个规则:

veryclean :
rm *.o
rm myprog

前提是没有其它的规则依靠这个 'veryclean'目的,它将永远不会被执行。但是,如果你明确的使用命令 'make veryclean' make会把这个目的做为它的主要目标,执行那些 rm命令。

如果你的磁碟上存在一个叫 veryclean文件,会发生什么事?这时因为在这个规则里没有任何依靠文件,所以这个目的文件一定是最新的了(所有的依靠文件都已经是最新的了),所以既使用户明确命令 make重新产生它,也不会有任何事情发生。解决方法是标明所有的假象目的(用 .PHONY),这就告诉 make不用检查它们是否存在于磁碟上,也不用查找任何隐含规则,直接假设指定的目的需要被更新。在 makefile里加入下面这行包含上面规则的规则:

.PHONY : veryclean

就可以了。注意,这是一个特殊的 make规则,make知道 .PHONY是一个特殊目的,当然你可以在它的依靠里加入你想用的任何假象目的,而 make知道它们都是假象目的。






 Makefileshell脚本区别


Makefile可以调用shell脚本,但是Makefileshell脚本是不同的。本文试着归纳一下Makefileshell脚本的不同。
1 shell中所有引用以$打头的变量其后要加{},而在Makefile中的变量是以$打头的后加()。实例如下:
Makefile
PATH="/data/"
SUBPATH=$(PATH)

Shell
PATH="/data/"
SUBPATH=${PATH}

2Makefile中所有以$打头的单词都会被解释成Makefile中的变量。如果你需要调用shell中的变量(或者正则表达式中锚定句位$),都需要加两个$符号($$)。实例如下:

PATH="/data/"

all:
    echo ${PATH}
    echo $$PATH

 例子中的第一个${PATH}引用的是Makefile中的变量,而不是shell中的PATH环境变量,后者引用的事Shell中的PATH环境变量。


3
、通配符区别
shell
中通配符*表示所有的字符
Makefile
中通配符%表示所有的字符

4、在Makefile中只能在某一个target下的命令中调用Shell脚本,其他地方是不能输出的。比如如下代码就是没有任何输出:

VAR="Hello"
echo "$VAR"

all:
   .....
以上代码任何时候都不会输出,没有在任何target下得命令内,如果上述代码改为如下:

VAR="Hello"

all:
    echo "$VAR"
    .....以上代码,在make all的时候将会执行echo命令。

5、在Makefile中执行shell命令,一行创建一个进程来执行。这也是为什么很多Makefile中有很多行的末尾都是“;  \”,以此来保证代码是一行而不是多行,这样Makefile可以在一个进程中执行,例如:

SUBDIR=src example
all:
    @for subdir in $(SUBDIR); \
    do\
        echo "building "; \
    done上述可以看出for循环中每行都是以”; \”结尾的。

6、获取当前目录

PATH=`pwd` 注意是``,不是’'







 Makefile中的shell2009-12-24 09:27:05

分类:


一下摘录Makefile中调用shell的一段


install:

        -if [ ! -e xxx ]; then sudo mkdir xxx; fi


注意,将上面的if语句写到一行的话,必须在fi前面加上分号,否则会出现下面错误

unexpected end of file


下面转一个相关文章

MakefileShell的问题

 

大概只要知道Makefile的人,都知道Makefile可以调用Shell脚本。但是在实际使用时,并不那么简单,一些模棱两可的地方可能会让你抓狂。你若不信,可以先看几个例子,想象一下这些这些例子会打印什么内容,记下你想象的结果,然后在计算机上运行这些例子,对照看一下。

 

 

示例一:

if [ "$(BUILD)" = "debug" ]; then  echo "build debug"; else echo "build release"; fi

all:

    echo "done"

示例二:

all:

    @CC=arm-linux-gcc

    @echo $(CC)

示例三:

CC=arm-linux-gcc

all:

    @echo $(CC)

示例四:

SUBDIR=src example

all:

    @for subdir in $(SUBDIR); \

    do\

        echo "building " $(subdir); \

    done

 

 

说明:

1.         Shell脚本在target里才有效,其它地方都被忽略掉了。所以示例一中,”build debug”之类的字符串根本打印不出来。示例一的正确写法是:

 

示例一:

all:

    if [ "$(BUILD)" = "debug" ]; then  echo "build debug"; else echo "build release"; fi

    echo "done"

 

2.         make把每一行Shell脚本当作一个独立的单元,它们在单独的进程中运行。示例二中,两行Shell脚本在两个莫不相干的进程里运行,第一个进程把CC设置为arm-linux-gcc,第二个进程是不知道的,所以打印的结果自然不是arm-linux-gcc了。示例二的正确写法是:

 

示例二:

all:

    @CC=arm-linux-gcc; echo $(CC)

或者:

all:

@CC=arm-linux-gcc; \

echo $(CC)

 

3.         make在调用Shell之前先进行预处理,即展开所有Makefile的变量和函数。这些变量和函数都以$开头。示例三中,Shell拿的脚本实际上是echo arm-linux-gcc,所以打印结果正确。

 

4.         make预处理时,所有以$开头的,它都不会放过。要想引用Shell自己的变量,应该以$$开头。另外要注意,Shell自己的变量是不需要括号的。示例四的正确写法是:

 

示例四:

SUBDIR=src example

all:

    @for subdir in $(SUBDIR); \

    do\

        echo "building " $$subdir; \

    done

感谢,Thanks




 linux Makefile obj-m obj-y ..

分类: Linux

2013-02-20 14:01 1773人阅读 评论(0) 收藏 举报

目标定义是Kbuild Makefile的主要部分,也是核心部分。主要是定义了要编 译的文件,所有的选项,以及到哪些子目录去执行递归操作。 最简单的Kbuild makefile 只包含一行: 例子: obj-y += foo.o 该例子告诉Kbuild在这目录里,有一个名为foo.o的目标文件。foo.o将从foo.c foo.S文件编译得到。 如果foo.o要编译成一模块,那就要用obj-m了。所采用的形式如下: 例子: obj-$(CONFIG_FOO) += foo.o $(CONFIG_FOO)可以为y(编译进内核) m(编译成模块)。如果CONFIG_FOO不是y m,那么该文件就不会被编译联接了


Linux各级内核源代码的子目录下都有Makefile,大多数Makefile要嵌入主目录下的Rule.makeRule.make将识别各个Makefile中所定义的一些变量。变量obj-y表示需要编绎到内核中的目标文件名集合,定义O_TARGET表示将obj-y连接为一个O_TARGET名称的目标文件,定义L_TARGET表示将obj-y合并为一个L_TARGET名称的库文件。同样obj-m表示需要编绎成模块的目标文件名集合。如果还需进行子目录make,则需要定义subdir-ysubdir-m。在Makefile中,用"obj-$(CONFIG_BINFMT_ELF) += binfmt_elf.o"和"subdir-$(CONFIG_EXT2_FS) += ext2"这种形式自动为obj-yobj-msubdir-ysubdir-m添加文件名。有时,情况没有这么单纯,还需要使用条件语句个别对待。Makefile中还有其它一些变量,如mod-subdirs定义了subdir-m以外的所有模块子目录。 


Rules.make是如何使make进入子目录的呢? 先来看subdir-y是如何处理的,在Rules.make中,先对subdir-y中的每一个文件名加上前缀"_subdir_"再进行排序生成subdir-list集合,再以它作为目标集,对其中每一个目标产生一个子make,同时将目标名的前缀去掉得到子目录名,作为子make的起始目录参数。subdir-msubdir-y类似,但情况稍微复杂一些。由于subdir-y中可能有模块定义,因此利用mod-subdirs变量将subdir-y中模块目录提取出来,再与subdir-m合成一个大的MOD_SUB_DIRS集合。subdir-m的目标所用的前缀是"_modsubdir_"。 


一点说明,子目录中的MakefileRules.make都没有嵌入.config文件,它是通过主Makefile向下传递MAKEFILES变量完成的。MAKEFILESmake自已识别的一个变量,在执行新的Makefile之前,make会首先加载MAKEFILES所指的文件。在主Makefile中它即指向.config 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++中this指针基本概念和使用

class Person { public:int m_A;//非静态成员变量&#xff0c;属于对象上void func(/*Person * this*/){}; //非静态成员函数 不属于对象身上static int m_B;//静态成员函数&#xff0c;不属于对象上static void fun2(){};//静态成员函数 &#xff0c;不属于对象身上//double …

通用Makefile实现

Makefile是Linux下程序开发的自动化编译工具&#xff0c;一个好的Makefile应该准确的识别编译目标与源文件的依赖关系&#xff0c;并且有着高效的编译效率&#xff0c;即每次重新make时只需要处理那些修改过的文件即可。Makefile拥有很多复杂的功能&#xff0c;这里不可能也没必…

c++中空指针访问成员函数

如果成员函数没有用到this &#xff0c;那么空指针可以直接访问 如果成员函数用到this 指针&#xff0c;就要注意&#xff0c;要判断是否为空&#xff0c;防止程序崩溃 #include<iostream>using namespace std;class Person{public:void show(){//没有 用到this指针&am…

从0开始python学习-35.allure报告企业定制

目录 1. 搭建allure环境 2. 生成报告 3. logo定制 4. 企业级报告内容或层级定制 5. allure局域网查看 1. 搭建allure环境 1.1 JDK&#xff0c;使用PyCharm 找到pycharm安装目录找到java.exe记下jbr目录的完整路径&#xff0c;eg: C:\Program Files\JetBrains\PyCharm Com…

grep 常用命令

这个--include选项,可以这样使用: grep -rn --include*.c --include*.h re . 可以指定多次, 如果真是上面的这种情况, 其实可以用 grep -rn --include*.[ch] re . 但是, 如果源文件中含有C源代码,上面的方法就不凑效了, 因为[]中只能放一个字符. grep -rn --include*.{cp…

c++中友元函数详解

友元 友元分为&#xff1a;友元函数和友元类 友元提供了一种突破封装的方式&#xff0c;有时提供了便利。但是友元会增加耦合度&#xff0c;破坏了封装&#xff0c;所以友元不宜多 用。 全局函数做友元函数 全局函数写到类中做声明 并且最前面写关键字 friend 友元函数可访问…

Linux时间函数札记

关于gmtime、gmtime_r、localtime、localtime_r 测试环境&#xff1a;vmware 7 Redhat5.5&#xff0c;系统时间使用UTC&#xff0c;时区为上海。 1、函数功能介绍 使用man gmtime或man localtime都可以的得到这几个函数的介绍。原型如下&#xff1a; struct tm *gmtime(const …

c++实现顺序表的相关操作

Myarray.h文件 #pragma once#include<iostream>using namespace std;class MyArray { public:MyArray();//默认构造 默认100容量MyArray(int capacity);MyArray(const MyArray& array);~MyArray();//尾插法void Push_Back(int val);//根据索引获取值int getData(int…

系统架构札记

什么是高内聚、低耦合&#xff1f; 起因&#xff1a;模块独立性指每个模块只完成系统要求的独立子功能&#xff0c;并且与其他模块的联系最少且接口简单&#xff0c;两个定性的度量标准――耦合性和内聚性。 耦合性也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一…

c++中运算符重载(加号运算,左移运算,前置后置++运算符,赋值运算,关系运算,函数运算)

运算符重载注意 重载的运算符要易读内置的数据类型的表达式的运算符是不可以改变的不要重载&& 和 | | 运算符&#xff0c;[]和->运算符只能通过成员函数进行重载<<和>>只能通过全局函数配合友元函数进行重载 加号运算符重载 如果想让自定义数据类型 进…

linux fstab解读

fstab这个文件挺有用的。 从左到右&#xff1a; /dev/device mount-point type rules dump fsck 1. /dev/device: 不用说了吧&#xff1f;例如&#xff0c;/dev/hda1为M$-Win9x下的c:盘。 2. mount-point: 挂载点。例如&#xff0c;把/dev/hda1挂到/mnt/mywinc下。 3. type: ex…

c++实现字符串类的封装

MyString.h文件 #define _CRT_SECURE_NO_WARNINGS#pragma once#include<iostream>#include<string>using namespace std;class MyString{friend ostream & operator<<(ostream & cout, MyString & str);friend istream & operator>>(…

c++中的继承--1(引出,继承方式,继承的对象模型)

继承的引出 概念&#xff1a; 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特 性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构…

Makefile经典教程(掌握这些足够)

makefile很重要 什么是makefile&#xff1f;或许很多Winodws的程序员都不知道这个东西&#xff0c;因为那些Windows的IDE都为你做了这个工作&#xff0c;但我觉得要作一个好的和professional的程序员&#xff0c;makefile还是要懂。这就好像现在有这么多的HTML的编辑器&#xf…

c++中的继承--2(继承中的析构函数和构造函数,继承中同名成员,继承中静态成员)

继承中的构造函数和析构函数 继承中的构造和析构顺序 子类创建对象时&#xff0c;先调用父类的构造&#xff0c;然后调用自身构造析构顺序与构造顺序相反子类不会继承父类的构造函数和析构函数如果父类中没有合适默认构造&#xff0c;那么子类可以利用初始化列表的方式显示的…

Linux锁机制和线程安全

锁机制是多线程编程中最常用的同步机制&#xff0c;用来对多线程间共享的临界区进行保护。 1. 互斥锁&#xff1a;pthread_mutex&#xff0c;属于sleep-waiting类型的锁 pthread_mutex_t *mutex; int pthread_mutex_int(mutex, attr) //以动态方式创建互斥锁&#xff0c;参…

c++中的继承--3(多继承问题,菱形继承)

继承中的多继承 #include<iostream>using namespace std;class Base1 { public:Base1(){m_A 10;} public:int m_A;};class Base2 { public:Base2(){m_A 10;} public:int m_B;int m_A;};class Son :public Base1, public Base2 {public:int m_C;int m_D; };void test01…

c++中的多态---1(多态概念,静态联编和动态联编,多态原理解析,重载,重写,重定义的对比)

多态的基本概念 多态是面向对象设计语言数据抽象和继承之外的第三个基本特征多态性(polymorphism)提供接口与具体实现之间的另一层隔膜&#xff0c;从而将“what”和“how”分离开来&#xff0c;多态性改善了代码的可读和组织性&#xff0c;同时也使创建的程序具有可扩展性&am…

Ubuntu下各种服务搭建及操作技巧

Ubuntu下搭建TFTP 1、安装软件包 sudo apt-get install tftpd tftp xinetd 2、建立配置文件 在/etc/xinetd.d/下建立一个配置文件tftp sudo vi /etc/xinetd.d/tftp 内容如下 service tftp { socket_type dgram protocol udp wait yes user root …

c++多态--2(计算器,纯虚函数和抽象类)

为什么要用多态 早期方法不利于扩展开闭原则 开闭原则 对扩展开放 对修改关闭利用多态实现—利于后期扩展&#xff0c;结构性非常好&#xff0c;可读性高&#xff0c;效率稍微低&#xff0c;发生多态内部结构复杂 多态成立的条件 又继承 子类重写父类虚函数的函数&#xff1…