您现在的位置是:首页 >技术交流 >makefile的基本使用网站首页技术交流

makefile的基本使用

Giant NG 2024-08-08 00:01:02
简介makefile的基本使用

代码示例

有 main.cpp 如下:

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

int main(int argc, char *argv[])
{
    int i = 10, j = 20;

    std::cout << "i + j = " << add(i, j) << std::endl;
    std::cout << "i - j = " << sub(i, j) << std::endl;

    return 0;
}

sub.h 如下:

#ifndef SUB_H
#define SUB_H

int sub(int i1, int i2);

#endif

sub.cpp 如下:

#include "sub.h"

int sub(int i1, int i2)
{
    return i1 - i2;
}

add.h 如下:

#ifndef ADD_H
#define ADD_H

int add(int i1, int i2);

#endif

add.cpp 如下:

#include "add.h"

int add(int i1, int i2)
{
    return i1 + i2;
}

编译以上文件并运行时,打印如下:

feng@ubuntu:~/PersonalTest/makefileTest$ g++ main.cpp add.cpp sub.cpp -o main
feng@ubuntu:~/PersonalTest/makefileTest$ ./main 
i + j = 30
i - j = -10

makefile 的工作原理

任何版本的 shell 都包含 make 命令,当我们执行 make 命令时,make 就会执行 makefile 定义的操作。

makefile 的编写

基础规则

target : dependence
    command

注意

  1. command 行要使用 tab 开头
  2. 如果发现 dependence 不存在时,指令无法执行,会向下查找指令生成 dependence。

示例:

target1:
	echo "target1"

target2:
	echo "target2"

运行后打印如下:

feng@ubuntu:~/PersonalTest/makefileTest$ make
echo "target1"
target1
feng@ubuntu:~/PersonalTest/makefileTest$ make target2
echo "target2"
target2

为开篇的代码示例编写一个 makefile :

main:./main.cpp ./add.cpp ./sub.cpp
	g++ ./main.cpp ./add.cpp ./sub.cpp -o main

clean:
	rm main ./main.o ./add.o ./sub.o

注意:加上 clean 是为了通过 makefile 实现清除目的。

运行后打印如下:

feng@ubuntu:~/PersonalTest/makefileTest$ make 
g++ ./main.cpp ./add.cpp ./sub.cpp -o main
feng@ubuntu:~/PersonalTest/makefileTest$ ./main 
i + j = 30
i - j = -10
feng@ubuntu:~/PersonalTest/makefileTest$ make clean 
rm main

但是这样的 makefile 也存在一个问题,如果当前目录下存在一个名为 clean 的文件,此时执行 make clean 将会出现:

feng@ubuntu:~/PersonalTest/makefileTest$ make clean 
make: 'clean' is up to date.

makefile 认为 clean 已存在,所以无需再做更新。
因此,需要在 makefile 中利用 .PHONY 将 clean 声明为目标,而不是文件。如下:

main:./main.cpp ./add.cpp ./sub.cpp
	g++ ./main.cpp ./add.cpp ./sub.cpp -o main

.PHONY:clean
clean:
	rm main ./main.o ./add.o ./sub.o

当 dependence 不存在时

将 makefile 修改为如下:

main:./main.o ./add.o ./sub.o
	g++ ./main.o ./add.o ./sub.o -o main

.PHONY:clean
clean:
	rm main

执行后打印如下:

feng@ubuntu:~/PersonalTest/makefileTest$ make 
g++    -c -o main.o main.cpp
g++    -c -o add.o add.cpp
g++    -c -o sub.o sub.cpp
g++ ./main.o ./add.o ./sub.o -o main
feng@ubuntu:~/PersonalTest/makefileTest$ ./main 
i + j = 30
i - j = -10

可以发现,makefile 中的 .0 文件并不存在,因此它会往下寻找,而下面也没有 .o 文件,因此会自动进行编译将 .cpp 编译为 .o 文件。(这里只是针对 .cpp --> .o,如果是其他依赖库,下文中又没有指出依赖库的编译方式等,势必会报错)

在 makefile 中使用变量

使用变量可以将上述 makefile 简化为如下:

target=main
obj=./main.o ./add.o ./sub.o
cc=g++

$(target):$(obj)
	$(cc) $(obj) -o $(target)

.PHONY:clean
clean:
	rm $(target) $(obj)

模式匹配

  1. %代表每一个,*代表所有。
  2. @ 表示目标, @ 表示目标, @表示目标,<表示第一个依赖,$^表示全部的依赖

makefile 简化如下:

target=main
obj=./main.o ./add.o ./sub.o
cc=g++

$(target):$(obj)
	$(cc) $^ -o $@

.PHONY:clean
clean:
	rm $(target) $(obj)

函数使用

  1. wildcard函数:$(wildcard ./*.cpp), 获取当前目录下所有的cpp文件
  2. patsubst函数:$(patsubst %.cpp,%.o,./*.cpp),将对应的cpp文件名全部替换为.o文件名(注意逗号格式)

makefile 简化如下:

target=main
src=$(wildcard ./*.cpp)
obj=$(patsubst %.cpp,%.o,$(src))
cc=g++

$(target):$(obj)
	$(cc) $^ -o $@

.PHONY:clean
clean:
	rm $(target) $(obj)

在一个 makefile 中引入其他 makefile

在当前 main.cpp 目录下,创建一个 libSrc、lib 目录,其中 libSrc 下包含 multi.h 和 multi.cpp 文件。
multi.h 内容如下:

#ifndef MULTI_H
#define MULTI_H

int multi(int i1, int i2);

#endif

multi.cpp 内容如下:

#include "multi.h"

int multi(int i1, int i2)
{
    return i1 * i2;
}

编写一个 makefile,将 multi 编译为动态库,并移动到 lib 目录下,makefile 内容如下:

target=libmulti.so
src=$(wildcard *.cpp)
obj=$(patsubst %.cpp,%.o,$(src))
cc=g++

lib_path=../lib

$(target):$(obj)
	$(cc) -shared $^ -o $@
	mv $(target) $(lib_path)

%.o:$(src)
	$(cc) -fPIC -c $^

.PHONY:clean
clean:
	rm $(obj) $(lib_path)/$(target)

注意:

  1. .o -> 目标文件,由于这里要编译为动态库,因此加上了 -shared
  2. .cpp -> .o,需要加上 -fPIC -c 的指令,使其可以编译为动态库;

main.cpp 内容修改为如下:

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

int main(int argc, char *argv[])
{
    int i = 10, j = 20;

    std::cout << "i + j = " << add(i, j) << std::endl;
    std::cout << "i - j = " << sub(i, j) << std::endl;
    std::cout << "i * j = " << multi(i, j) << std::endl;

    return 0;
}

此时,为了链接 multi.so 进行编译,main.cpp 目录下的 makefile 也应该修改为如下:

target=libmulti.so
src=$(wildcard *.cpp)
obj=$(patsubst %.cpp,%.o,$(src))
cc=g++

lib_path=../lib

$(target):$(obj)
	$(cc) -shared $^ -o $@
	mv $(target) $(lib_path)

%.o:$(src)
	$(cc) -fPIC -c $^

.PHONY:clean
clean:
	rm $(obj) $(lib_path)/$(target)

编译运行后打印如下:

feng@ubuntu:~/PersonalTest/makefileTest$ make 
cd ./libSrc && make && cd ../
make[1]: Entering directory '/home/feng/PersonalTest/makefileTest/libSrc'
g++ -fPIC -c multi.cpp
g++ -shared multi.o -o libmulti.so
mv libmulti.so ../lib
make[1]: Leaving directory '/home/feng/PersonalTest/makefileTest/libSrc'
g++ sub.o main.o add.o -L./lib -lmulti -o main
feng@ubuntu:~/PersonalTest/makefileTest$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib
feng@ubuntu:~/PersonalTest/makefileTest$ ./main 
i + j = 30
i - j = -10
i * j = 200
feng@ubuntu:~/PersonalTest/makefileTest$ make clean
cd ./libSrc && make clean && cd ../
make[1]: Entering directory '/home/feng/PersonalTest/makefileTest/libSrc'
rm multi.o ../lib/libmulti.so
make[1]: Leaving directory '/home/feng/PersonalTest/makefileTest/libSrc'
rm main ./sub.o ./main.o ./add.o

注意export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib 这是为了临时添加动态库链接路径,使 main 可以运行起来。

如果在一个目录中,有一个同名的静态库和动态库。优先调用动态库。所以注意,不要让静态库和动态库同名。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。