Makefile学习记录

Makefile基础语法

目标:依赖
Tab 命令
目标:一般指要编译的目标,也可以是一个动作
依赖:指执行当前目标索要依赖的选项,包括其它目标,某个具体文件或库等, 一个目标可以有多个依赖
命令: 该目标下要执行的具体命令,可以没有,也可以有多条,多条时,每条命令一行

Makefile案例

新建Makefile

a:
    @echo hello world

终端下切换到当前Makefile所在目录,执行make,有如下输出

image.png

make 默认从上往下编译第一个目标

a:
    @echo hello world

b:
    @echo hello b

c:
    @echo hello c
image.png

如果想要编译指定的目标使用make 目标名称, 如下图:


image.png

目标依赖:目标a依赖目标b 和c

a:b c
    @echo hello world

b:
    @echo hello b

c:
    @echo hello c

终端执行make


image.png

可以看到上述先编译b目标,然后编译c目标,最后才编译a目标

多条命令

a:
    @echo hello world
        @ls -al
    @g++ .\main.cpp

make clean

a:
    @echo hello world
    @ls -al
    @g++ .\main.cpp

clean:
    rm -rf a.exe
    @echo make clean success

先执行make后,会生成a.exe, 使用make clean清除中间目录

make常用选项

make默认在当前目录中寻找GUNmakefile,makefile,Makefile的文件作为make的输入文件,-f可以指定除上述文件名之外的文件作为输入文件

make [-f file][options][target]
-v 显示版本号
-n 只输出命令,但是不执行,一般用来测试
-s 只执行命令,但不现实具体命令,此处可在命令中用@抑制符命令输出
-w 显示执行前执行后的路径
-C dir 指定makefile所在的目录
没指定目标时,默认使用第一个目标,如果指定, 则执行对应的目标命令

编译流程

我们先分别新建add,sub,muli三个功能类,以及用来计算的calc.cpp


image.png

add.h

#ifndef ADD_H
#define ADD_H

int add(int a, int b);
#endif

add.cpp

#include "add.h"

int add(int a, int b) {
    return a +b + 100;
}

calc.cpp

#include<iostream>
#include "add.h"
#include "sub.h"
#include "multi.h"

int main() {
    int a = 4;
    int b = 2;
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", sub(a, b));
    printf("a * b = %d\n", multi(a, b));
    return 0;
}
gcc add.cpp -c -o add.o  

这里由于add.cpp里没有main方法,所有编译需要加上 -c参数表示仅进行编译,不进行链接) 否则会报undefined reference to `WinMain'错误


image.png

makefile如下

# 这样不好,因为每次单模块修改,都会重新编译所有的模块
#calc:
#   g++ add.cpp sub.cpp multi.cpp calc.cpp -o calc

# 下面这样确保首次编译后,修改单模块只会编译单模块以及主目标文件
calc:add.o sub.o multi.o
    g++ add.o sub.o multi.o calc.cpp -o calc

add.o:add.cpp
    g++ add.cpp -c -o add.o

sub.o:sub.cpp
    g++ sub.cpp -c -o sub.o 

multi.o:multi.cpp
    g++ multi.cpp -c -o multi.o

clean:
    rm -rf *.o calc

首次make编译,编译流程如下:


image.png

此时我们修改add.cpp

#include "add.h"

int add(int a, int b) {
    return a +b + 300;
}

再次进行编译:


image.png

可以看到这次只编译了add.o和calc目标

编译流程步骤

预处理:g++ -E main.cpp>main.ii
编译: g++ -S main.ii 得到main.s的汇编代码
汇编: g++ -c main.s 得到main.o的二进制文件
链接: g++ main.o 得到名为a.out的可执行文件(window下为a.exe)


image.png

makefile中的变量

系统变量

* 不包括扩展名的目标文件名称+ 所有的依赖文件,以空格分隔
< 表示规则中的第一个条件? 所有时间戳比目标文件晚的依赖文件,以空格分隔
@ 目标文件的完整名称^ 所有不重复的依赖文件,以空格分隔
$% 如果目标时归档成员,则该变量表示目标的归档成员名称

系统常量(可用make -p查看)

AS 汇编程序的名称 默认为as
CC C编译器的名称 默认为cc
CPP C编译器名称 默认为cc -E
CXX C++编译器名称 默认g++
RM 文件删除程序别名 默认rm -f

自定义变量

定义: 变量名=变量值
使用: (变量名)/(变量名)

使用变量优化makefile第一版本

OBJ=add.o sub.o multi.o calc.o
TARGET=calc

$(TARGET):$(OBJ)
    g++ $(OBJ) -o $(TARGET)

add.o:add.cpp
    g++ add.cpp -c -o add.o

sub.o:sub.cpp
    g++ sub.cpp -c -o sub.o 

multi.o:multi.cpp
    g++ multi.cpp -c -o multi.o

calc.o:calc.cpp
    g++ calc.cpp -c -o calc.o

clean:
    rm -rf *.o calc
image.png

使用变量优化makefile第二版本

OBJ=add.o sub.o multi.o calc.o
TARGET=calc

$(TARGET):$(OBJ)
    g++ $(OBJ) -o $(TARGET)

add.o:add.cpp
    g++ $^ -c -o $@

sub.o:sub.cpp
    g++ $^ -c -o $@

multi.o:multi.cpp
    g++ $^ -c -o $@

calc.o:calc.cpp
    g++ $^ -c -o $@

clean:
    rm -rf *.o $(TARGET)

使用变量优化makefile第三版本

我们先看下几个系统常量的值

show:
    echo $(AS)
    echo $(CC)
    echo $(CPP)
    echo $(CXX)
    echo $(RM)
image.png
OBJ=add.o sub.o multi.o calc.o
TARGET=calc

$(TARGET):$(OBJ)
    $(CXX) $(OBJ) -o $(TARGET)

add.o:add.cpp
    g++ $^ -c -o $@

sub.o:sub.cpp
    $(CXX) $^ -c -o $@

multi.o:multi.cpp
    $(CXX) $^ -c -o $@

calc.o:calc.cpp
    $(CXX) $^ -c -o $@

clean:
    $(RM) *.o $(TARGET)

show:
    echo $(AS)
    echo $(CC)
    echo $(CPP)
    echo $(CXX)
    echo $(RM)

makefile中的伪目标和模式匹配

伪目标

伪目标:.PHONY:目标名称
声明目标为伪目标之后,makefile将不会判断目标是否存在或该目标是否需要更新

比如:我们已有的makefile中有clean 和show两个目标


image.png

如果,这里我在makefile同级目录下创建了clean 和show文件,此时在编译,就会什么都不做


image.png

解决方法
我们使用.PHONY:clean show声明clean和show为伪目标,就可以编译这两个目标了

模式匹配

%.o:%.cpp         .o依赖于对应的.cpp
wildcard           $(wildcard ./*.cpp)获取当前目录下所有的.cpp文件

patsubst   $(patsubst,  %.cpp, %.o, $(wildcard ./*.cpp))将对应的cpp文件名替换成.o文件名

使用模式匹配简化makefile

OBJ=add.o sub.o multi.o calc.o
TARGET=calc

.PHONY:clean

$(TARGET):$(OBJ)
    $(CXX) $(OBJ) -o $(TARGET)

#模式匹配  %目标:%依赖
#目标和依赖相同的部分可以用%来通配
# %.o:%.cpp 
%.o:%.cpp
    g++ $^ -c -o $@

clean:
    $(RM) *.o $(TARGET)
show:
    echo $(wildcard ./*.cpp)
    echo $(patsubst %.cpp,%.o,$(wildcard ./*.cpp))

make -s show

image.png

在做进一步优化

#将对应的cpp文件名替换成.o文件名
OBJ=$(patsubst %.cpp,%.o,$(wildcard ./*.cpp))
TARGET=calc

.PHONY:clean

$(TARGET):$(OBJ)
    $(CXX) $(OBJ) -o $(TARGET)

#模式匹配  %目标:%依赖
#目标和依赖相同的部分可以用%来通配
# %.o:%.cpp 
%.o:%.cpp
    g++ $^ -c -o $@

clean:
    $(RM) *.o $(TARGET)

show:
    echo $(wildcard ./*.cpp)
    echo $(patsubst %.cpp,%.o,$(wildcard ./*.cpp))

makefile编译动态链接库

动态链接库:win(.dll) linux(.so)
动态链接库: 不会把代码编译到二进制文件中,而是在运行时才去加载,所以只需要维护一个地址
-fPIC 产生位置无关的代码
-shared 共享
-l(小L) 指定动态库
-I(大写的i)指定头文件目录,默认当前目录
-L 手动指定库文件搜索目录,默认值链接共享目录
创建SoTest.h

#ifndef SOTEST_H
#define SOTEST_H

#pragma once

class SoTest
{
public:
    void fun1();
    virtual void fun2()=0;
};

#endif

SoTest.cpp

#include<iostream>
#include "SoTest.h"

void SoTest::fun1()
{
    printf("SoTest func1 \n");
}

void SoTest::fun2()
{
    printf("SoTest func2 \n");
}

test.cpp

#include<iostream>

#include "SoTest.h"



using namespace std;



class Test:public SoTest{

    public:

        void fun1() {

            printf("Test fun1..\n");

        }



        virtual void fun2() override{

            printf("Test fun2...\n");

        }

};



int main() {

    Test t1;

    t1.fun1();

    t1.fun2();

}

生成动态库

g++ -shared -fPIC SoTest.cpp -o libSoTest.so

使用静态库

g++ -lSoTest -L./ test.cpp -o test

makefile编译静态库 .lib .a

静态链接库:会把库中的代码编译到二进制文件中,当程序编译完成后,该库文件可以删除
而动态链接库不行,动态链接库必须与程序同时部署,还要保证程序能加载得到库文件

与动态库相比,静态库可以不用部署(已经被加载到程序里了),而且运行时速度更快,但是会导致程序体积更大,并且库中的内容如果有更新,则需要重新编译生成程序。

aTest.h

#ifndef ATEST_H
#define ATEST_H

#pragma once

class aTest
{
public:
    void fun1();
};

#endif

aTest.cpp

#include "aTest.h"
#include<iostream>

void aTest::fun1() {
    std::cout << "aTest fun1"<<std::endl;
}

生成静态库

g++ -c aTest.cpp  -o aTest.o
ar -r libaTest.a aTest.o
image.png

使用静态库

g++ -laTest -L./ main.cpp -o main

makefile语法

Makefile中,都是先展开所有变量,在调用指令
=赋值,但是用终值,就是不管变量调用写在赋值前还是赋值后,调用时都取终值

A=123
B=$(A)

a:
    echo $(A) $(B)

A=456
image.png

:=也是赋值,但是只受当前行及之前的代码影响,而不受后面的赋值影响

X=789
Y=$(X)abc
Y:=$(Y)def

b:
    echo $(X) $(Y)

X=333   
image.png

makefile中调用shell命令

FILE=abc
A:=$(shell ls)
B:=$(shell pwd)
C :=$(shell if [ ! -f $(FILE) ];then touch $(FILE);fi;)

a:
    echo $(A) 
    echo $(B)
image.png

makefile嵌套调用

# -C指定工作目录

all:
    make -C ./001
    make -C ./002

clean:
    make -C ./001 clean
    make -C ./002 clean

分别去001 和002目录下找到makefile编译

makefile中的条件判断

ifeq 判断是否相等,相等返回true,不相等返回false
ifneq 判断是否不相等,相等返回true,不相等返回false
ifdef 判断变量是否存在,存在返回true,不存在返回false
Ifndef 判断变量是否不存在,不存在返回true,存在返回false


A:=321123

RS1:=
RS2:=
RS3:=
RS4:=


ifeq ($(A),123)
    RS1:=123
else
    ifeq ($(A),321)
        RS1:=321
    else
        RS1:=no-123-321
    endif

endif

ifndef A
    RS3:=yes
else
    RS3:=no
endif

ifndef FLAG
    FLAG:=default-flag
endif


all:
    echo $(RS1)
    echo $(RS3)
    echo flag=$(FLAG)

<font color="red">ifeq,ifneq 与条件之间要有空格,不然会报错
可以只有if,没有else,
但是没有 elseif的用法,如果要实现 elseif,就要写嵌套

命令行传参 make -f Makefile FLAG=456 如果有Makefile ,则可写成 make FLAG=456</font>

makefile中的循环

#循环
#makefile 中只有一个循环 foreach,只支持 GNU Make ,其它平台的make ,可以用shell 中的循环来实现
#可以在循环中逐个的修改值



TARGET:=a b c d
FILE:=$(foreach v, $(TARGET),$v.txt)

all:
    #echo $(TARGET)
    #echo $(foreach v, $(TARGET),$v)
    #touch $(TARGET)
    #touch $(foreach v, $(TARGET),$v.txt)
    #mkdir $(foreach v, $(TARGET),$v_txt)
    #echo $(FILE)

    for v in $(TARGET);\
        do touch $$v.txt;\
    done;\

    $(shell for v in $(TARGET); do touch $$v-txt;done)


clean:
    $(RM) -rf $(TARGET) *txt

makefile自定义函数

define FUN1
    echo FUN1
endef

all:
    $(call FUN1)
``
### 自定义函数传参

define FUN1
echo (1)(2) $(3)
endef

all:
$(call FUN1, abc, def, 123)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • Makefile学习笔记 学习Makefile的资料 《跟我一起写makefile》 《GUN make manu...
    crazybaoli阅读 2,950评论 0 4
  • 一.编译&链接 1.编译: 1)每个源文件都对应于一个中间目标文件(O文件或是OBJ文件); 2)编译器只检测程序...
    Brlat阅读 628评论 0 0
  • 说明 这只是笔记。。。。。。。。 从0开始搭建测试环境 下载android ndk ,然后配置ndk环境变量,使n...
    吉凶以情迁阅读 5,095评论 0 3
  • 1. 概述 1.1 前言 之前在Linux下写C/C++都是直接输命令行,虽然有使用make的经历,但没有自己动手...
    kophy阅读 3,865评论 0 19
  • 当我们写好程序后,需要通过编译,链接后生成可执行文件,这个可执行文件也就是我们通常说的程序。那么什么是编译,什么是...
    程序大飞阅读 30,729评论 3 24