Makefile基础语法
目标:依赖
Tab 命令
目标:一般指要编译的目标,也可以是一个动作
依赖:指执行当前目标索要依赖的选项,包括其它目标,某个具体文件或库等, 一个目标可以有多个依赖
命令: 该目标下要执行的具体命令,可以没有,也可以有多条,多条时,每条命令一行
Makefile案例
新建Makefile
a:
@echo hello world
终端下切换到当前Makefile所在目录,执行make,有如下输出
make 默认从上往下编译第一个目标
a:
@echo hello world
b:
@echo hello b
c:
@echo hello c
如果想要编译指定的目标使用make 目标名称, 如下图:
目标依赖:目标a依赖目标b 和c
a:b c
@echo hello world
b:
@echo hello b
c:
@echo hello c
终端执行make
可以看到上述先编译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
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'错误
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编译,编译流程如下:
此时我们修改add.cpp
#include "add.h"
int add(int a, int b) {
return a +b + 300;
}
再次进行编译:
可以看到这次只编译了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)
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
使用变量优化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)
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两个目标
如果,这里我在makefile同级目录下创建了clean 和show文件,此时在编译,就会什么都不做
解决方法
我们使用.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
在做进一步优化
#将对应的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
使用静态库
g++ -laTest -L./ main.cpp -o main
makefile语法
Makefile中,都是先展开所有变量,在调用指令
=赋值,但是用终值,就是不管变量调用写在赋值前还是赋值后,调用时都取终值
A=123
B=$(A)
a:
echo $(A) $(B)
A=456
:=也是赋值,但是只受当前行及之前的代码影响,而不受后面的赋值影响
X=789
Y=$(X)abc
Y:=$(Y)def
b:
echo $(X) $(Y)
X=333
makefile中调用shell命令
FILE=abc
A:=$(shell ls)
B:=$(shell pwd)
C :=$(shell if [ ! -f $(FILE) ];then touch $(FILE);fi;)
a:
echo $(A)
echo $(B)
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 (2) $(3)
endef
all:
$(call FUN1, abc, def, 123)