linux Makefile

第一部分:

Makefile

一个工程中的源文件不计其数,其按类型,功能,模块分别放在若干目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至进行更复杂的功能操作。

Make与Makefile的关系

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

Makefile命令规则

Makefile或makefile, 一般使用Makefile。

重点

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

第二部分:

从一个简单的示例开始

Makefile基本语法

目标:依赖

Tab 命令

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

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

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

例1:a表示目标,其下是要执行的具体命令,b也表示目标,make默认执行第一个目标

a:
	@echo "Hello World"

b:
	@echo "Hello Make"

执行结果:

[blctrl@main-machine Firtst]$ make
Hello World
[blctrl@main-machine Firtst]$ make b
Hello Make

例2:a b c都是目标,a依赖b和c

a:b c
	@echo "Hello World"

b:
	@echo "Hello Make"

c:
	@echo "Hello C"

执行结果:

[blctrl@main-machine make-exer]$ make
Hello Make
Hello C
Hello World

例3:a是目标,执行的语句先显示当前目录下内容,然后编译main.cpp源文件为main,编译语句用gcc -lstdc++ main.cpp -o main替代也可以。

a:
	@ls ./
	g++ main.cpp -o main

执行结果:显示了当前目录下内容,然后进行编译生成可执行文件main,然后在当前命令行执行这个程序。 

[blctrl@main-machine Firtst]$ make
cmake-build-debug  CMakeLists.txt  main.cpp  make-exer  Makefile
g++ main.cpp -o main
[blctrl@main-machine Firtst]$ ./main
Hello, World!

例4:

a:
	@ls ./
	gcc -lstdc++ main.cpp -o main

clean:
	@rm -rf main
	@echo "make clean successfully!"

执行make后,在当前路径中产生一个main的可执行文件,执行make clean后,从当前路径中删除名为main的文件。

[blctrl@main-machine Firtst]$ make
cmake-build-debug  CMakeLists.txt  main.cpp  make-exer  Makefile
gcc -lstdc++ main.cpp -o main
[blctrl@main-machine Firtst]$ ls
cmake-build-debug  CMakeLists.txt  main  main.cpp  make-exer  Makefile
[blctrl@main-machine Firtst]$ make clean
make clean successfully!
[blctrl@main-machine Firtst]$ ls
cmake-build-debug  CMakeLists.txt  main.cpp  make-exer  Makefile

make常用选项

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

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

  • -f:可以指定除了上述文件名之外的文件作为输入文件。
  • -v:显示版本号
  • -n:只执行命令,单步显示具体命令,此处在命令中用@符号抑制命令输出。
  • -w:显示执行前执行后的路径
  • -C dir:指定makefile文件所在的目录

没有指定目录时,默认使用第一个目标,如果指定,则执行对应的命令。

第三部分

gcc编译流程详解

gcc -lstdc++ main.cpp 直接从源文件到目标可执行文件

把过程分拆:

  1. 预处理:gcc -lstdc++ main.cpp > main.ii
  2. 编译:gcc -S main.ii                   得到main.s的汇编文件
  3. 汇编:gcc -c main.s                   得到名为main.o的二进制文件
  4. 链接:gcc -lstdc++ main.o          得到名为a.out的可执行文件

例1:如下一个源文件:

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

对以上源文件进行逐步编译: 

[blctrl@main-machine Firtst]$ gcc -E main.cpp > main.ii
[blctrl@main-machine Firtst]$ gcc -S main.ii 
[blctrl@main-machine Firtst]$ gcc -c main.s 
[blctrl@main-machine Firtst]$ gcc -lstdc++ main.o
[blctrl@main-machine Firtst]$ ./a.out 
Hello, World!

例2:在一个指定目录中提供以下头文件和源文件:

//add.h头文件
#ifndef FIRTST_ADD_H
#define FIRTST_ADD_H

int  add(int, int);

#endif //FIRTST_ADD_H

//add.cpp源文件
#include "add.h"

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

//sub.h头文件
#ifndef FIRTST_SUB_H
#define FIRTST_SUB_H

int sub(int , int );

#endif //FIRTST_SUB_H

//sub.cpp源文件
#include "sub.h"
int sub(int a, int b)
{
    return a - b;
}

//mul.h头文件
#ifndef FIRTST_MUL_H
#define FIRTST_MUL_H

int mul(int , int );

#endif //FIRTST_MUL_H

//mul.cpp源文件
#include "mul.h"

int mul(int a, int b)
{
    return a * b;
}

//calc.cpp源文件
nclude <stdio.h>
#include "add.h"
#include "sub.h"
#include "mul.h"

int main(int argc, char  * argv[])
{
    int  a = 3, b = 2;

    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", sub(a, b));
    printf("a * b = %d\n", mul(a, b));
    return 0;
}

在相同目录下,新建一个名为Makefile的文件,内容如下:

calc:add.o sub.o mul.o calc.o
	gcc add.o sub.o mul.o calc.cpp -o calc

add.o:add.cpp
	gcc -c add.cpp -o add.o

mul.o:mul.cpp
	gcc -c mul.cpp -o mul.o

sub.o:sub.cpp
	gcc -c sub.cpp -o sub.o

clean:
	rm -rf *.o calc

在以上路径中,执行make命令,在执行后生成了源文件的对象文件,并且产生了一个可执行文件calc,执行这个calc程序,查看结果:

[blctrl@main-machine Second]$ ls
add.cpp  add.h  calc.cpp  Makefile  mul.cpp  mul.h  sub.cpp  sub.h
[blctrl@main-machine Second]$ make
gcc -c add.cpp -o add.o
gcc -c sub.cpp -o sub.o
gcc -c mul.cpp -o mul.o
g++    -c -o calc.o calc.cpp
gcc add.o sub.o mul.o calc.cpp -o calc
[blctrl@main-machine Second]$ ls
add.cpp  add.h  add.o  calc  calc.cpp  calc.o  Makefile  mul.cpp  mul.h  mul.o  sub.cpp  sub.h  sub.o
[blctrl@main-machine Second]$ ./calc
a + b = 5
a - b = 1
a * b = 6

执行make clean可以删除以上的.o文件以及calc可执行文件:

[blctrl@main-machine Second]$ make clean
rm -rf *.o calc
[blctrl@main-machine Second]$ ls
add.cpp  add.h  calc.cpp  Makefile  mul.cpp  mul.h  sub.cpp  sub.h

第四部分

Makefile中的变量

1) 系统变量

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

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

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

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

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

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

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

2)系统常量

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

CC      C编译器名称,默认cc

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

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

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

3) 自定义变量

定义:变量名=变量值

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

用Makefile中的变量,来改写以上Makefile文件,内容如下:

OBJS=add.o sub.o mul.o calc.o
TARGET=calc

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

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

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

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

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

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

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

执行make show能够查看以上指定的系统常量的值:

[blctrl@main-machine Second]$ make show
echo cc
cc
echo as
as
echo cc -E
cc -E
echo g++
g++
echo rm -f
rm -f

第五部分

Makefile中伪目标和模式匹配

伪目标 .PHONY:clean

声明伪目标后,makefile将不会判断目标是否存在或者该目标是否需要更新。

1) %.o:%.cpp                  .o依赖对应的.cpp文件

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

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

用伪目标和模式匹配来重写上一部分中的Makefile文件:

注意:

1)目标和依赖的相同部分用%来匹配

2)OBJS是来自当前目录中所有.cpp文件的文件名的后缀被替换成了.o

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

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

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

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

show:
	echo $(CC)
	echo $(AS)
	echo $(CPP)
	echo $(CXX)
	echo $(RM)
	echo $(wildcard ./*.cpp)
	echo $(patsubst %.cpp, %.o, ./*.cpp)

.PHONY:clean show

第六部分

Makefile中编译动态链接库

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

  • -fPIC:产生位置无关的代码。
  • -shared:共享
  • -l(小写L):指定动态库
  • -I(大写i):指定头文件目录,默认当前目录
  • -L:手动指定文件搜索目录,默认只链接共享目录

动态链接库

动态:运行时才去加载,动态加载

链接:指库文件和二进制程序分离,用某种特殊手段维护两者之间的关系。

库:库文件.dll(windows) .so(linux)

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

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

1) 动态库编译完成之后要发布(即把产生的.so文件复制到以下两个目录之一中),否则程序运行找不到。

/usr/lib
/usr/local/lib

2) 指定一个环境变量LD_LIBRARY_PATH=<库文件所在的路径>

LD_LIBRARY_PATH=<库文件所在的路径>
export LD_LIBRARY_PATH
或者
export LD_LIBRARY_PATH=<库文件所在的路径>

例1:在一个路径下建立以下两个目录,其目录结构如下:

[blctrl@main-machine Firtst]$ ls dynamic/
SoTest.cpp  SoTest.h  test.cpp
[blctrl@main-machine Firtst]$ ls Dynamic-Test/
main.cpp  SoTest.h

dynamic路径下有一个SoTest类,其头文件和源文件内容如下:

/* SoTest.h */
#ifndef FIRTST_SOTEST_H
#define FIRTST_SOTEST_H


class SoTest {
public:
    void function1();
    virtual void function2() ;
    virtual void function3()=0;
};

/* SoTest.cpp */
#include <stdio.h>
#include "SoTest.h"

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

void SoTest::function2() {
    printf("SoTest::function2\n");

在dynamic目录下,将上面的源文件编译为一个动态库文件:

[blctrl@main-machine dynamic]$ ls
SoTest.cpp  SoTest.h
[blctrl@main-machine dynamic]$ g++ -shared -fPIC SoTest.cpp -o libSoTest.so
[blctrl@main-machine dynamic]$ ls
libSoTest.so  SoTest.cpp  SoTest.h

切换到Dynamic-Test目录中,复制dynamic目录中的SoTest.h头文件,并且新建一个main.cpp的源文件,内容如下:

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

class Test: public SoTest{
public:
    virtual void function2(void);
    virtual void function3(void);
};

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

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

int main(int argc, char * argv[])
{
    Test test;

    test.function1();
    test.function2();
    test.function3();

    return 0;
}

对以上源文件和动态链接库文件进行编译,生成一个可执行文件main,执行这个可执行文件进行测试:

}[blctrl@main-machine Dynamic-Test]$ g++ -lSoTest -L ../dynamic main.cpp -o main
[blctrl@main-machine Dynamic-Test]$ ./main
./main: error while loading shared libraries: libSoTest.so: cannot open shared object file: No such file or directory
[blctrl@main-machine Dynamic-Test]$ export LD_LIBRARY_PATH=../dynamic
[blctrl@main-machine Dynamic-Test]$ ./main
SoTest::function1
Test::function2
Test::function3

例2:建立一个以下目录结构

[blctrl@bjAli dynamic]$ ls
main.cpp  Makefile  SoTest.cpp  SoTest.h

以上源文件和头文件与例1中相同,新建一个Makefile文件,内容如下:

TARGET=main

main:libSoTest.so
        $(CXX) -lSoTest -L ./lib main.cpp -o $@

libSoTest.so:SoTest.cpp
        $(CXX) -shared -fPIC $^ -o $@
        @mkdir lib
        @mv  $@ lib

clean:
        $(RM) lib/*.so $(TARGET)
        $(RM) -rf lib

在以上路径中,执行make进行编译,并且查看新产生的目录结构:

[blctrl@bjAli dynamic]$ make
g++ -shared -fPIC SoTest.cpp -o libSoTest.so
g++ -lSoTest -L ./lib main.cpp -o main
[blctrl@bjAli dynamic]$ ls -R
.:
lib  main  main.cpp  Makefile  SoTest.cpp  SoTest.h

./lib:
libSoTest.so

第七部分:Makefile中编译静态链接库 .a

静态链接库,会把库中的代码编译到二进制文件中,当程序编译完成后,该库文件可以被删除,而动态链接库不行,动态链接库必须与程序同时部署,还要保证程序能加载得到库文件。与动态库相同,静态库可以不用部署(已经被加载到程序里面了),而且运行时速度更快。但会导致程序提及更大,并且库中的内容如果有更新,则需要重新编译生成程序。

例:生成一个以下的目录结构

[blctrl@main-machine static-lib]$ ls
aTest.cpp  aTest.h  main.cpp

以上三个文件的内容如下:

aTest.h:

#ifndef FIRTST_ATEST_H
#define FIRTST_ATEST_H


class aTest {
public:
    virtual void function1();
    virtual void function2();
    virtual void function3();
};


#endif //FIRTST_ATEST_H

aTest.cpp:

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

void aTest::function1() {
    printf("aTest::function1\n");
}

void aTest::function2() {
    printf("aTest::function2\n");
}

void aTest::function3() {
    printf("aTest::function3\n");
}

main.cpp:

#include <stdio.h>
#include "aTest.h"
class Test: public  aTest{
public :
    virtual void function2();
    virtual void function3();
};

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

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

int main(int argc, char * argv[])
{
    Test test;

    test.function1();
    test.function2();
    test.function3();

    return 0;
}

以下步骤演示手动进行编译静态库文件,并且用用静态库文件编译生成可执行文件的过程:

# 编译源文件,生成二进制对象文件
[blctrl@main-machine static-lib]$ g++ -c aTest.cpp -o aTest.o
# 将二进制对象文件生成静态库文件
[blctrl@main-machine static-lib]$ ar crv libaTest.a aTest.o
a - aTest.o
# 查看静态库文件的内容
[blctrl@main-machine static-lib]$ ar t libaTest.a
aTest.o
# 编译并且链接静态库文件生成可执行文件
[blctrl@main-machine static-lib]$ g++ main.cpp -L. -laTest -o main
# 执行可执行文件,进行测试
[blctrl@main-machine static-lib]$ ./main
aTest::function1
Test::function2
Test::function3

把以上手动编译的过程用一个Makefile文件替代:

TARGET=main
LDFLAGS=-L.
LIBS=-laTest

$(TARGET):main.o libaTest.a
        $(CXX) $< $(LDFLAGS) $(LIBS)  -o $(TARGET)

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

libTest.a:aTest.o
        @ar crv $^ $@

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

.PHONY:clean

执行make clean和make分别进行测试:

[blctrl@main-machine static-lib]$ make clean
rm -f *.o main
[blctrl@main-machine static-lib]$ ls
aTest.cpp  aTest.h  libaTest.a  main.cpp  Makefile
[blctrl@main-machine static-lib]$ make
g++    -c -o main.o main.cpp
g++ main.o -L. -laTest  -o main
[blctrl@main-machine static-lib]$ ls
aTest.cpp  aTest.h  libaTest.a  main  main.cpp  main.o  Makefile
[blctrl@main-machine static-lib]$ ./main
aTest::function1
Test::function2
Test::function3
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值