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)

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Makefile学习笔记 学习Makefile的资料 《跟我一起写makefile》 《GUN make manu...
    crazybaoli阅读 3,176评论 0 4
  • 一.编译&链接 1.编译: 1)每个源文件都对应于一个中间目标文件(O文件或是OBJ文件); 2)编译器只检测程序...
    Brlat阅读 726评论 0 0
  • 说明 这只是笔记。。。。。。。。 从0开始搭建测试环境 下载android ndk ,然后配置ndk环境变量,使n...
    吉凶以情迁阅读 5,510评论 0 3
  • 当我们写好程序后,需要通过编译,链接后生成可执行文件,这个可执行文件也就是我们通常说的程序。那么什么是编译,什么是...
    程序大飞阅读 31,207评论 3 24
  • 0. 作用 Makefile文件告诉Make怎样编译和连接成一个程序。 1. Makefile基本语法与执行 示例...
    jdzhangxin阅读 4,807评论 2 6

友情链接更多精彩内容