Makefile 快速入门编译项目

笔记对应的视频主要是BV1Xt4y1h7rH,在b站输入然后搜索即可。

一、相关概念

Makefile 是啥?

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
【百度百科 https://baike.baidu.com/item/Makefile/4619787 】

Make 与 Makefile 的关系

make 是一个命令工具,它解释 Makefile 中的指令。在 Makefile 文件中描述了整个工程所有文件的编译顺序、编译规则。

Makefile 命名规则

Makefile 或 makefile,一般使用 Makefile。

Cmake 又是啥?

CMake 是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的 makefile 或者 project 文件,能测试编译器所支持的 C++特性类似 UNIX 下的 automake。只是 CMake 的组态档取名为 CMakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。
【百度百科 https://baike.baidu.com/item/cmake 】

Cmake 与 CMakeLists 的关系

cmake 是一个命令工具,可用来生成 makefile。

但也要根据 CMakeLists.txt 中的内容来生成,CMakeLists.txt 就是写给 cmake 的规则。

重点

make 是一个命令工具,Makefile 是一个文件,make 执行的时候,去读取 Makefile 文件中的规则,重点是 makefile 得自己写。

cmake 是一个命令工具,CMakeLists.txt 是一个文件,cmake 执行的时候,去读取 CMakeLists.txt 文件中的规则,

重点是 CMakeLists.txt 得自己写。

二、从 hello world 开始

Makefile 基本语法

目标:依赖
    tab 命令

目标:一般是指要编译的目标,也可以是一个动作。

依赖:指执行当前目标所要依赖的先项,包括其它目标,某个具体文件或库等。一个目标可以有多个依赖。

命令:该目标下要执行的具体命令,可以没有,也可以有多条。多条时,每条命令一行。

先在 CLion 下创建一个 C++ 项目。再创建 Makefile 文件。

1
Makefile 文件中的内容:

a:
	@echo "hello world a"
b:
	@echo "hello world b"

命令行执行 make 命令,可以看到上述 Makefile 文件中有两个目标,不指定目标默认执行第一个。

2

如果报以下错误,首先需要找到安装 MinGW 的目录,进入到 bin 目录。将 mingw32-make.exe 文件改为 make.exe。

并配置环境变量 Path:E:\soft1\MinGW\bin

make : 无法将“make”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

命令行执行 make b。

3

修改 Makefile 文件中的内容为:

a:b
	@echo "hello world a"
b:
	@echo "hello world b"

再次执行 make 命令,先执行 b 目标后再执行 a。

4

修改 Makefile 文件中的内容为:

a:
	@echo "hello world a"
	@g++ main.cpp
clean:
	@del a.exe
	@echo "clean success"

先执行 make 命令,g++ 编译 main.cpp,生成可执行程序 a.exe。然后执行 a.exe。最后 make clean 删除 a.exe。

5

make 常用选项

make [-f file][options][target]

Make 默认在当前目录中寻找 GNUmakefile、makefile,Makefile 的文件作为 make 的输入文件。

-f 可以指定除上述文件名之外的文件作为输入文件。

-v 显示版本号。

make -v

GNU Make 4.2.1
Built for x86_64-w64-mingw32
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

-n 只输出命令,但并不执行,一般用来测试。

make -n

echo "hello world a"
g++ main.cpp

-s 只执行命令,但不显示具体命令,此处可在命令中用@符抑制命令输出。

-w 显示执行前执行后的路径。

make -w

make: Entering directory 'D:/CODE/C++/FirstMakeTest'
"hello world a"
make: Leaving directory 'D:/CODE/C++/FirstMakeTest'

-C dir 指定 makefile 所在的目录。

make --help

三、gcc/g++ 编译流程详解

新建一个目录。编写简易的计算器代码。

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

int main()
{
    std::cout << "add: " << add(1, 2) << std::endl;
    std::cout << "sub: " << sub(1, 2) << std::endl;
    std::cout << "multi: " << multi(1, 2) << std::endl;

    return 0;
}

6

可以看到编译和运行并没有问题。进而,编写 Makefile。在目录中新建文件 Makefile。

calc:
	g++ *.cpp

但这样编写并不好,修改一下。

calc: add.o sub.o multi.o
	g++ add.o sub.o multi.o calc.cpp -o calc
add.o : add.cpp
	g++ -c add.cpp -o add.o
sub.o : sub.cpp
	g++ -c sub.cpp -o sub.o
multi.o : multi.cpp
	g++ -c multi.cpp -o multi.o

clean:
	@del *.o calc

执行 make && .\calc.exe 命令。

PS D:\CODE\C++\FirstMakeTest\001> make
g++ -c add.cpp -o add.o
g++ -c sub.cpp -o sub.o
g++ -c multi.cpp -o multi.o
g++ add.o sub.o multi.o calc.cpp -o calc
PS D:\CODE\C++\FirstMakeTest\001> .\calc.exe
add: 3
sub: -1
multi: 2

这样在比如 add.cpp 修改后,不会执行其他两个。

PS D:\CODE\C++\FirstMakeTest\001> make
g++ -c add.cpp -o add.o
g++ add.o sub.o multi.o calc.cpp -o calc

g++ main.cpp 直接从源代码到目标可执行文件

过程拆分

预处理 g++ -E main.cpp > main.ii

编译 g++ -S main.ii 得到名为 main.s 的汇编文件

汇编 g++ -c main.s 得到名为 main.o(.obj)的二进制文件

链接 g++ main.o 得到名为 a.out 的可执行文件

四、Makefile 中的变量

为了方便,后面将在 Ubuntu 22.04.5 LTS 上测试。

系统变量

$* 不包括扩展名的目标文件名称。

$+ 所有的依赖文件,以空格分隔。

$< 表示规则中的第一个条件。

$? 所有时间戳比目标文件晚的依赖文件,以空格分隔。

$@ 目标文件的完整名称。

$^ 所有不重复的依赖文件,以空格分隔。

$% 如果目标是归档成员,则该变量表示目标的归档成员名称。

系统常量

(可用 make -p 查看)

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

CC C 编译器名称 默认 cc

CPP C 预编译器名称 默认 cc -E

CXX C++ 编译器名称 默认 g++

RM 文件删除程序别名 默认 rm -f

自定义变量

定义:变量名=变量值

使用:$(变量名) / ${变量名}

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

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

add.o : add.cpp
        $(CXX) -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 $(CXX)
        echo $(CPP)
        echo $(CC)
        echo $(RM)

执行 make -s show && make && make clean:

7

五、Makefile 中的伪目标和模式匹配

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

当创建一个 clean 文件后,继续 make clean 将不会进行 clean。

8

Makefile 修改为:

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

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

add.o : add.cpp
        $(CXX) -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)
.PHONY: clean show
show:
        echo $(AS)
        echo $(CXX)
        echo $(CPP)
        echo $(CC)
        echo $(RM)

模式匹配:%.o:%.cpp .o 依赖于对应的 .cpp

wildcard:$(wildcard ./*.cpp) 获取当前目录下所有的 .cpp 文件

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

修改 Makefile 为:

OBJ=$(patsubst %.cpp,%.o, $(wildcard ./*.cpp))
TARGET=calc

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

# 模式匹配 目标和依赖相同部分,可用%来通配
%.o : %.cpp
        $(CXX) -c $^ -o $@

clean:
        $(RM) *.o $(TARGET)
.PHONY: clean show
show:
        echo $(AS)
        echo $(CXX)
        echo $(CPP)
        echo $(CC)
        echo $(RM)
        echo $(wildcard ./*.cpp)
        echo $(patsubst %.cpp,%.o, $(wildcard ./*.cpp))
        echo ${OBJ}

执行 make show && make && make clean:

9

六、Makefile 运行流程

保证目标是用最新的依赖生成的。

第一次全完编译,后面只编译最新的代码(部分编译)。

10

七、Makefile 中编译动态链接库

动态链接库:不会把代码编译到二进制文件中,而是在运行时才去加载,所以只需要维护一个地址。

​ -fPIC:产生位置无关的代码。

​ -shared:共享。

​ -l(小 L):指定动态库。

​ -I(大 I):指定头文件目录,默认当前目录。

​ -L:手动指定库文件搜索目录,默认只链接共享目录。

动态链接库  程序可以和库文件分离,分别发布,库文件可以多处共享
动态  运行时才去加载  动态加载
链接  指库文件和二进制程序分离,用某种特殊手段维护两者之间的关系
库    库文件 .dll  .so

首先创建文件夹7_dynamic_lib,然后创建文件 SoTest.h SoTest.cpp。

-----SoTest.h-----
class SoTest {
public:
    void func1();
    virtual void func2();
    virtual void func3()=0;
};

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

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

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

执行命令:

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

再创建 test.cpp

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

class Test : public SoTest {
public:
    void func2() {
    	printf("Test::func2\n");
    }

    void func3() {
    	printf("Test::func3\n");
    }
};

int main() {
    Test test;
    test.func1();
    test.func2();
    test.func3();
    return 0;
}

执行命令:

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

/*
g++ test.cpp -L./ -lSoTest -o test 是正确的顺序,因为链接器会先处理源文件,再加载库来解决符号引用。
g++ -lSoTest -L./ test.cpp -o test 是错误的顺序,因为链接器会先尝试加载库,但此时还不知道需要哪些符号,导致无法正确解决引用。 ---------来自kimi
*/

root@lemon-virtual-machine:~/make/src/7_dynamic_lib# ./test
SoTest::func1
Test::func2
Test::func3

然后工作目录切换到当前父目录,创建文件 main.cpp,并将 SoTest.h 拷贝过来一份。执行命令:

g++ main.cpp -lSoTest -L./7_dynamic_lib -o main
./main
报错
./main: error while loading shared libraries: libSoTest.so: cannot open shared object file: No such file or directory

可以看到 libSoTest.so => not found
root@lemon-virtual-machine:~/make/src# ldd main
        linux-vdso.so.1 (0x00007ffdf9bfb000)
        libSoTest.so => not found
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x0000749567a00000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000749567600000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x0000749567d29000)
        /lib64/ld-linux-x86-64.so.2 (0x0000749567e25000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x0000749567d09000)

1 动态库编译完成之后要发布,否则程序运行时找不到

Linux 默认动态库路径配置文件

/etc/ld.so.conf /etc/ld.so.conf.d/*.conf

/usr/lib /usr/local/lib

2 运行时手动指定动态库目录

临时设置LD_LIBRARY_PATH:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径

再次执行,即可成功。

root@lemon-virtual-machine:~/make/src# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/make/src/dynamic_lib
root@lemon-virtual-machine:~/make/src# ./main
SoTest::func1
MainTest::func2
MainTest::func3

当然还有其他方法。

另外,编写 Makefile。

test: libSoTest.so
	$(CXX) -o test test.cpp -L. -lSoTest
	cp libSoTest.so /usr/local/lib

libSoTest.so: SoTest.cpp
	$(CXX) -fPIC -shared -o libSoTest.so SoTest.cpp

clean:
	$(RM) *.so test

八、Makefile 中编译静态链接库

.lib .a

静态链接库:会把库中的代码编译到二进制文件中,当程序编译完成后,该库文件可以删除。

而动态链接库不行,动态链接库必须与程序同时部署,还要保证程序能加载得到库文件

与动态库相比,静态库可以不用部署(已经被加载到程序里面了),而且运行时速度更快(因为不用去加载)。

但是会导致程序体积更大,并且库中的内容如果有更新,则需要重新编译生成程序。

首先创建文件夹8_static_lib,然后创建文件 aTest.h aTest.cpp,执行命令。

-----aTest.h-----
class aTest {
public:
    void func1();

};

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

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

root@lemon-virtual-machine:~/make/src/8_static_lib# g++ -c aTest.cpp -o aTest.o
root@lemon-virtual-machine:~/make/src/8_static_lib# ar -r libaTest.a aTest.o
ar: creating libaTest.a

至此,创建完静态库,然后修改 main.cpp。

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

class MainTest : public SoTest {
public:
    void func2() {
        std::cout << "MainTest::func2" << std::endl;
    }
    void func3() {
        std::cout << "MainTest::func3" << std::endl;
    }
};

int main() {
    MainTest test;
    test.func1();
    test.func2();
    test.func3();

    aTest test2;
    test2.func1();
    return 0;
}

重新编译运行。

root@lemon-virtual-machine:~/make/src# g++ main.cpp -lSoTest -L./7_dynamic_lib -laTest -L./8_static_lib -o main
root@lemon-virtual-machine:~/make/src# ./main
SoTest::func1
MainTest::func2
MainTest::func3
func1

编写 Makefile

libaTest:
	$(CXX) -c aTest.cpp -o aTest.o
	$(AR) -r libaTest.a aTest.o

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

给 main.cpp 也编写一个 Makefile。

TARGET:=main

CFLAGS:=-L7_dynamic_lib -L8_static_lib
LDFLAGS:=-lSoTest -laTest

$(TARGET):
	$(CXX) main.cpp $(CFLAGS) $(LDFLAGS) -o $(TARGET)

clean:
	$(RM) $(TARGET)

此时的目录结构

root@lemon-virtual-machine:~/make/src# tree
.
├── 7_dynamic_lib
│   ├── libSoTest.so
│   ├── Makefile
│   ├── SoTest.cpp
│   ├── SoTest.h
│   ├── test
│   └── test.cpp
├── 8_static_lib
│   ├── aTest.cpp
│   ├── aTest.h
│   ├── aTest.o
│   ├── libaTest.a
│   └── Makefile
├── aTest.h
├── first_make
│   ├── add.cpp
│   ├── add.h
│   ├── calc.cpp
│   ├── clean
│   ├── Makefile
│   ├── multi.cpp
│   ├── multi.h
│   ├── sub.cpp
│   └── sub.h
├── main
├── main.cpp
├── Makefile
└── SoTest.h

九、Makefile 中通用部分做公共头文件

先创建目录9_include。再创建文件夹001,002。

在001中创建文件 a.cpp、b.cpp、c.cpp 和 Makefile。

-----a.cpp-----
#include <iostream>
void func1() {
    std::cout << "func1" << std::endl;
}

-----b.cpp-----
#include <iostream>
void func2() {
    std::cout << "func2" << std::endl;
}

-----c.cpp-----
extern void func1();
extern void func2();
#include <iostream>
int main() {
    func1();
    func2();
    return 0;
}

-----Makefile-----
TARGET:= c
OBJS:= a.o b.o c.o
.PHONY: clean

$(TARGET): $(OBJS)
	$(CXX) $^ -o $@

#%.o: %.c
#	$(CXX) -c $^ -o $@

clean:
	$(RM) $(TARGET) $(OBJS)

执行 make 命令。

root@lemon-virtual-machine:~/make/src/9_include/001# make
g++    -c -o a.o a.cpp
g++    -c -o b.o b.cpp
g++    -c -o c.o c.cpp
g++ a.o b.o c.o -o c

同理,在 002 目录创建文件 x.c、y.c、z.c 和 Makefile。

-----x.c-----
#include <stdio.h>

void func1() {
    printf("func1-c\n");
}

-----y.c-----
#include <stdio.h>

void func2() {
    printf("func2-c\n");
}

-----z.c-----
extern void func1();
extern void func2();
#include <stdio.h>
int main() {
    func1();
    func2();
    return 0;
}

-----Makefile-----
TARGET:= z
OBJS:= x.o y.o z.o
.PHONY: clean

$(TARGET): $(OBJS)
	$(CXX) $^ -o $@

#%.o: %.c
#	$(CXX) -c $^ -o $@

clean:
	$(RM) $(TARGET) $(OBJS)

执行 make 命令。

root@lemon-virtual-machine:~/make/src/9_include/002# make
cc    -c -o x.o x.c
cc    -c -o y.o y.c
cc    -c -o z.o z.c
g++ x.o y.o z.o -o z

提取公共的部分到父目录的 Makefile 中。

#公共
SOURCES = $(wildcard ./*.cpp ./*.c)
OBJS = $(patsubst %.cpp,%.o,$(SOURCES))
OBJS := $(patsubst %.c,%.o,$(OBJS))

.PHONY: clean

ifndef TARGET
TARGET := test
endif

$(TARGET): $(OBJS)
	$(CXX) $^ -o $@

#%.o: %.c
#	$(CXX) -c $^ -o $@

clean:
	$(RM) $(TARGET) $(OBJS)

show:
	echo $(SOURCES)
	echo $(OBJS)

而 001、002 目录中的 Makefile。

-----001 Makefile-----
TARGET:= c

include ../Makefile

-----002 Makefile-----
TARGET:= z

include ../Makefile

在 001 目录中执行 make && make clean && make show 命令。

root@lemon-virtual-machine:~/make/src/9_include/001# make
g++    -c -o a.o a.cpp
g++    -c -o b.o b.cpp
g++    -c -o c.o c.cpp
g++ a.o b.o c.o -o c
root@lemon-virtual-machine:~/make/src/9_include/001# ./c
func1
func2
root@lemon-virtual-machine:~/make/src/9_include/001# make clean
rm -f c ./a.o ./b.o ./c.o
root@lemon-virtual-machine:~/make/src/9_include/001# make show
echo ./a.cpp ./b.cpp ./c.cpp
./a.cpp ./b.cpp ./c.cpp
echo ./a.o ./b.o ./c.o
./a.o ./b.o ./c.o

目录结构:

root@lemon-virtual-machine:~/make/src/9_include# tree
.
├── 001
│   ├── a.cpp
│   ├── b.cpp
│   ├── c.cpp
│   └── Makefile
├── 002
│   ├── Makefile
│   ├── x.c
│   ├── x.o
│   ├── y.c
│   ├── y.o
│   ├── z
│   ├── z.c
│   └── z.o
└── Makefile

=(延迟扩展赋值):
使用 = 进行赋值时,变量的值会在使用时进行扩展。这意味着在定义变量时,它不会立即计算变量值中的变量引用,而是将这些变量引用保留到实际使用变量时才进行替换。

variable = $(value)

如果 value 变量在定义 variable 时尚未定义,$(value) 将保持为一个待扩展的变量,直到它被实际使用时才会进行替换。

:=(即时扩展赋值):
使用 := 进行赋值时,变量的值会在赋值时立即进行扩展。这意味着在定义变量时,它将立即计算变量值中的变量引用,并替换为相应的值。

variable := $(value)

如果 value 变量在定义 variable 时尚未定义,$(value) 将被替换为一个空字符串,而不是保留为一个待扩展的变量。

​ ----------------------------------来自https://www.cnblogs.com/codedingzhen/p/18075102。

十、Makefile 中调用 shell 命令

创建文件夹 10_shell。编写 Makefile。

FILE = abc

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

a:
	echo $(A)
	echo $(B)
	echo $(c)

clean:
	$(RM) -rf $(FILE)

执行命令。

root@lemon-virtual-machine:~/make/src/10_shell# make
echo 10_shell 7_dynamic_lib 8_static_lib 9_include aTest.h first_make main main.cpp Makefile SoTest.h
10_shell 7_dynamic_lib 8_static_lib 9_include aTest.h first_make main main.cpp Makefile SoTest.h
echo /root/make/src/10_shell
/root/make/src/10_shell
echo

root@lemon-virtual-machine:~/make/src/10_shell# ll
total 12
drwxr-xr-x 2 root root 4096  4月  6 10:59 ./
drwxr-xr-x 7 root root 4096  4月  6 10:57 ../
-rw-r--r-- 1 root root    0  4月  6 10:59 abc
-rw-r--r-- 1 root root  184  4月  6 10:59 Makefile

十一、Makefile 中的嵌套调用

编写 Makefile。

.PHONY: 001 002
DIR = 001 002
all: $(DIR)

$(DIR):
	make -C $@
clean:
	echo $(shell for d in $(DIR); do make -C $$d clean; done)

all-v1:
	make -C ./001
	make -C ./002
clean-v1:
	make -C ./001 clean
	make -C ./002 clean
  1. .PHONY 声明
    • .PHONY: 001 002 声明 001002 为“伪目标”(phony targets)。这意味着这些目标不对应于实际的文件名,而是用来执行特定的命令序列。
  2. 变量定义
    • DIR = 001 002 定义了一个变量 DIR,包含两个子目录的名称。
  3. all 目标
    • all: $(DIR) 定义了一个 all 目标,它依赖于 DIR 变量中列出的所有子目录。这意味着执行 make all 时,会依次构建 001002
  4. 子目录构建规则
    • $(DIR):DIR 变量中的每个子目录定义了一个规则。make -C $@ 命令用于在每个子目录中执行 make,其中 -C 选项指定了要切换到的目录($@ 是自动变量,代表目标名称,即子目录名)。
  5. clean 目标
    • clean: 定义了一个 clean 目标,用于清理构建生成的文件。
    • echo $(shell for d in $(DIR); do make -C $$d clean; done) 使用 shell 函数在每个子目录中执行 make clean$$d 用于在 shell 命令中正确引用 d 变量。

$$d 是 Makefile 中的一种变量引用方式,用于在 shell 函数中正确地传递和解析变量 d 的值。它确保变量在 shell 环境中被正确处理,而不是在 Makefile 解析阶段就被展开。这种写法在编写复杂的 Makefile 时非常有用,特别是在需要与 shell 交互的场景中。

十二、Makefile 中的条件判断

ifeq 判断是否相等,相等返回 true,不相等返回 false。

ifneq 判断是否不相等,相等返回 true,不相等返回 false。

ifdef 判断变量是否存在,存在返回 true,不存在返回 false。

ifndef 判断变量是否不存在,不存在返回 true,存在返回 false。

A := 123
RS1 :=
RS2 :=
RS3 :=
RS4 :=

ifeq ($(A),321)
	RS1 := true
else
	ifeq ($(A),456)
		RS2 := true
	else
		RS3 := true
	endif
endif

ifdef $(A)
	RS4 := true
else
	RS4 := false
endif

#可以通过命令行传参 make FLAG=111
ifndef FLAG
	FLAG := default
endif

all:
	@echo $(RS1)
	@echo $(RS2)
	@echo $(RS3)
	@echo $(RS4)
	@echo $(FLAG)

十三、Makefile 中的循环

# makefile 中只有一个循环 foreach,只支持 GNU Make,
# 其他平台的 make,可以用 shell 中的循环来实现
TARGET := a b c d
all:
	for i in $(TARGET); do \
		echo $$i; \
	done
	touch $(foreach v, $(TARGET), $v.txt)
clean:
	$(RM) -f $(TARGET) *.txt
  1. Shell 循环

    for i in $(TARGET); do \
        echo $$i; \
    done
    
    • 这是一个 shell 命令中的 for 循环,用于遍历 TARGET 变量中的每个元素。
    • $$i 表示循环变量 i 的值。注意这里使用了两个美元符号 $$,因为第一个 $ 被 Makefile 解释,第二个 $ 被 shell 解释。
    • echo $$i 命令打印当前循环变量的值。
  2. Makefile foreach 函数

    touch $(foreach v, $(TARGET), $v.txt)
    
    • foreach 是 Makefile 的一个函数,用于对 TARGET 中的每个元素执行指定的操作。
    • $(foreach v, $(TARGET), $v.txt) 生成一个以 TARGET 中每个元素为名称的 .txt 文件列表。
    • touch 命令用于创建这些文件(如果它们不存在的话)。

十四、Makefile 中的自定义函数的实现和调用

define FUNC1
	echo $(0)
	echo $(1)
	echo $(2)
	echo "func1"
endef

all:
	$(call FUNC1,abc,def)
  • define 关键字用于开始一个宏的定义。
  • FUNC1 是宏的名称。
  • 宏体包含四条 echo 命令,分别打印宏调用时传递的前三个参数(如果有的话)以及字符串 "func1"
  • $(0)$(1)$(2) 是宏参数的引用,其中 $(0) 通常用于引用宏的名称,而 $(1)$(2) 等用于引用传递给宏的参数。
  • endef 关键字用于结束宏的定义。

十五、make install 的实现

make 1
make install 2 3 4
make clean 5

1 将源文件编译成二进制可执行文件(包括各种库文件)

2 创建目录,将可执行文件拷贝到指定目录(安装目录)

3 加全局可执行的路径

4 加全局的启停脚本

5 重置编辑环境,删除无关文件

编写一个计数程序。

#include<iostream>
#include<unistd.h>
using namespace std;

int main(){
    int i=0;
    while (true){
        i++;
        cout<<"009-main-running-"<<i<<endl;
        sleep(1);
    }
    return 0;
}

Makefile 内容。

TARGET := 009_main
OBJS := $(TARGET).o

PATHS := /root/tmp/009_main/
BIN := /usr/local/bin/

START_SH := $(TARGET)_start
STOP_SH := $(TARGET)_stop
LOG := $(TARGET).log

CC = g++
$(TARGET): $(OBJS)

install:
	if [ -d $(PATHS)]; \
		then echo $(PATHS) exists; \
	else \
  		mkdir $(PATHS); \
  		cp $(TARGET) $(PATHS); \
  		ln -sv $(PATHS)$(TARGET) $(BIN); \
  		touch $(PATHS)$(LOG); \
  		chmod a+rwx $(PATHS)$(LOG); \
  		echo "$(TARGET) > $(PATHS)$(LOG) & echo $(TARGET) running..." > $(PATHS)$(START_SH); \
  		echo "killall $(TARGET)" > $(PATHS)$(STOP_SH); \
  		chmod a+x $(PATHS)$(START_SH) $(PATHS)$(STOP_SH); \
  		ln -sv $(PATHS)$(START_SH) $(BIN); \
  		ln -sv $(PATHS)$(STOP_SH) $(BIN); \
	fi

clean:
	$(RM) $(TARGET) $(OBJS)
	$(RM) -r $(PATHS)
	$(RM) $(BIN)$(TARGET) $(BIN)$(START_SH) $(BIN)$(STOP_SH)

.PHONY: clean install

变量定义

TARGET := 009_main
OBJS := $(TARGET).o
PATHS := /root/tmp/009_main/
BIN := /usr/local/bin/
START_SH := $(TARGET)_start
STOP_SH := $(TARGET)_stop
LOG := $(TARGET).log
CC = g++
  • TARGET 是程序的目标名称。
  • OBJS 是目标文件的列表。
  • PATHS 是程序的安装路径。
  • BIN 是可执行文件的链接路径。
  • START_SHSTOP_SH 是启动和停止脚本的名称。
  • LOG 是日志文件的名称。
  • CC 是编译器,这里使用 g++

构建规则

$(TARGET): $(OBJS)
  • 这个规则指定了如何从目标文件 $(OBJS) 构建目标程序 $(TARGET)。具体的编译命令没有给出,通过自动推导。

安装规则

install:
	if [ -d $(PATHS)]; \
		then echo $(PATHS) exists; \
	else \
  		mkdir $(PATHS); \
  		cp $(TARGET) $(PATHS); \
  		ln -sv $(PATHS)$(TARGET) $(BIN); \
  		touch $(PATHS)$(LOG); \
  		chmod a+rwx $(PATHS)$(LOG); \
  		echo "$(TARGET) > $(PATHS)$(LOG) & echo $(TARGET) running..." > $(PATHS)$(START_SH); \
  		echo "killall $(TARGET)" > $(PATHS)$(STOP_SH); \
  		chmod a+x $(PATHS)$(START_SH) $(PATHS)$(STOP_SH); \
  		ln -sv $(PATHS)$(START_SH) $(BIN); \
  		ln -sv $(PATHS)$(STOP_SH) $(BIN); \
	fi
  • install 目标首先检查 $(PATHS) 目录是否存在,如果不存在,则创建该目录。
  • 将目标程序 $(TARGET) 复制到 $(PATHS) 目录,并在 $(BIN) 目录创建一个符号链接。
  • 创建日志文件 $(LOG),并设置其权限为可读写执行。
  • 创建启动脚本 $(START_SH) 和停止脚本 $(STOP_SH),并设置其权限为可执行。
  • $(BIN) 目录创建启动和停止脚本的符号链接。

清理规则

clean:
	$(RM) $(TARGET) $(OBJS)
	$(RM) -r $(PATHS)
	$(RM) $(BIN)$(TARGET) $(BIN)$(START_SH) $(BIN)$(STOP_SH)
  • clean 目标删除目标程序、目标文件、安装目录、符号链接等,清理构建和安装过程中生成的所有文件。

伪目标

.PHONY: clean install
  • .PHONY 声明 cleaninstall 为目标,即使存在同名文件,也不会与文件混淆。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值