Linux学习记录:Makefile
Posted 河边小咸鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux学习记录:Makefile相关的知识,希望对你有一定的参考价值。
Makefile
- 这是本人在学习makefile时的记录,方便日后查询。
- 所有我遇到的makefile相关的内容都会被记录在这篇笔记中,所以在之后接触到makefile相关的新内容后,会对这篇笔记的内容进行更新。
零、Makefile简介
-
描述了整个工程的编译、链接规则
· 工程中的哪些源文件需要编译以及如何编译
· 需要创建哪些库文件以及如何创建这些库文件
· 如何产生期望得到的最终可执行文件
· 可以快速的构建和管理工程 -
make的执行机制
· makefile文件的命名:Makefile
或makefile
· 在make执行时,会依次寻找GNUmakefile、Makefile、makefile,如未找到则报错,找到则执行此makefile文件。
· 在执行makefile文件时,make会检测每一条命令的返回值,如果失败的话会报错并终止make,否则会继续进行。
· 可以使用make -f
来指定make命令读取的脚本名 -
makefile的执行流程
· 从makefile的第一个目标开始执行(从上往下数第一个)
· 首先看该目标的依赖项,看依赖项是否存在
· 如果不存在依赖项,则执行命令后结束。
· 如果存在就先执行依赖项相关目标(然后看依赖项目标是否有依赖项…以此不断寻找最内层,有点dfs的感觉)
PS: 这里简单提一嘴编译和链接的过程…
步骤号 | 执行前 | 要干嘛 | 过程 | 执行后 |
---|---|---|---|---|
1 | 源文件(.c, .cpp, .h) | 预处理器(Preprocessor)进行预处理 | 引入头文件、进行宏替换等 | 预编译文件(.i, .ii) |
2 | 预编译文件(.i, .ii) | 编译器(Compiler)进行编译处理 | 比如使用gcc或者g++进行编译 | 汇编码(.s) |
3 | 汇编码(.s) | 汇编程序(Assembler)进行汇编 | 把汇编码转为机器码 | 机器码(.o, .obj) |
4 | 机器码(.o, .obj) | 链接器(Linker)进行链接 | 对静态库(.lib, .a)进行连接 | 得到可执行文件 |
一、基础语法
基础语法如下:
target...: prerequisttes...
command...
...
其中:
1.target为目标文件,可以是obj文件也可以是可执行文件
2.prerequisttes为依赖性,即生成目标文件所关联到的文件
3.command为指令,即make所需要执行的指令
1. 单源文件例子
下面是一个简单的单源文件makefile例子:
如上图所示,在当前文件夹内有两个文件 main.c 和 makefile,而文件中的内容分别如下:
/*
* main.c
* 为简单的打印HelloWorld
*/
#include<stdio.h>
int main()
{
printf("HelloWorld\\n");
return 0;
}
/*
* makefile
* 设定目标文件为main,依赖项为main.c,命令语句为gcc main.c -o main
*/
main: main.c
gcc main.c -o main
如下图,执行make命令后,可以看到它执行了预设的gcc命令,随后正常生成了main可执行文件,并可以正常执行。由此,一个最简单的基础makefile示例就完成了。
2. 多源文件例子
下面是一个简单的多源文件makefile例子:
如下,是一个文件夹里的四个文件:func.h
、func.c
、main.c
、makefile
,具体内容都列在下方。其中func为一个简单的加法函数,而makefile中涉及了变量和伪目标的概念,具体内容在下面都会记录。
/*func.h*/
#ifndef _FUNC_H_
#define _FUNC_H_
int func(int a, int b);
#endif
/*func.c*/
#include"func.h"
int func(int a, int b)
{
return a + b;
}
/*main.c*/
#include<stdio.h>
#include"func.h"
int main()
{
printf("HelloWorld\\n1 + 2 = %d\\n", func(1, 2));
return 0;
}
/*makefile 涉及到变量和伪目标,具体概念都在下文*/
TARGET = main
cc = gcc
FILE = main.c func.o
TARGET: $(FILE)
$(CC) $(FILE) -w -o $(TARGET)
func.o: func.c
$(CC) -c func.c
.PHONY: clean
clean:
rm $(TARGET) *.o
如下图,执行make
命令,可以看到gcc命令由下向上执行(因为从最内层依赖项开始生成),生成了最终目标文件main
,执行main发现func函数正常被调用,说明make成功。
如下图,执行make clean
命令,可以看到rm
命令被正常执行,删除了main文件和所有.o后缀的文件。说明伪目标clean建立成功。自此该多源文件例子已经完成。
二、变量相关
变量简介:
makefile脚本中可以引入变量来使得编写更加简便以及清晰。变量的声明非常简单,格式为 变量名 = 值
;而调用变量的格式则为 $(变量名)
, 由此即可使用变量。
一个简单的例子:
对上文单文件例子中的makefile进行简单修改,引入一些变量。修改如下:
/*修改前*/
main: main.c
gcc main.c -o main
/*修改后*/
TARGET = main
cc = gcc
FILE = main.c
$(TARGET): $(FILE)
$(CC) $(FILE) -o $(TARGET)
如下图,在修改完makefile后,make仍可正常进行。通过变量,可以方便日后的修改,比如说想把gcc换成g++,就可以把编译器设为变量,想更换编译器时直接修改变量值即可,可以大大减少修改量。
三、伪目标
通常makefile中第一个目标为最终目标,后续目标和最终目标有依赖关系。但是有时候想要执行清空生成的文件等一些单独执行的命令时,很明显这些命令并不会生成目标文件,由此和生成最终目标也没有必要关系,需要与普通的command进行区分,这时就出现了伪目标的概念。
伪目标不是一个输出文件,而是一个标签。在执行make指令时,并不会主动执行伪目标的命令(因为伪目标没有依赖项),想要执行伪目标就必须使用命令 make 伪目标名
或者把伪目标放到makefile最上面。而显式声明伪目标的语法为 .PHONY: 伪目标名
,随后设定调用此伪目标时执行的命令即可。
一个简单的例子:
对上文单文件例子中的makefile进行简单修改(多文件例子中已经存在clean),在末尾引入伪目标,使其可以删除掉生成的main可执行文件。修改如下:
/*修改后*/
TARGET = main
cc = gcc
FILE = main.c
$(TARGET): $(FILE)
$(CC) $(FILE) -o $(TARGET)
.PHONY: clean
clean:
rm $(TARGET)
如下图,在执行伪目标clean后,删除掉了设定好的最终目标main。在make后,再次生成最终目标main文件。
四、make嵌套执行
在大的工程会把源文件分为很多个目录,为了逻辑上的简单,会为每个子目录写一个makefile文件,而最上层的makefile文件被称为总控makefile。通过执行总控makefile,即可自动执行下层的makefile文件,从而使得项目总体进行make操作。
下面是一个例子:
我在当前文件夹内创建了三个新文件夹:main
存放main.c源码、func
存放func.h/c源码、build
中存放obj文件和最终可执行文件。并且三个文件夹内都有自己的makefile文件,而当前文件夹下有总控makefile文件。源码如下:
- ./func:
/* func.h */
#ifndef _FUNC_H_
#define _FUNC_H_
int func(int a, int b);
#endif
/* func.c */
#include"func.h"
int func(int a, int b)
{
return a + b;
}
/* makefile */
CC = gcc
func.o: func.c
$(CC) -c func.c -o func.o
cp func.o ../build/func.o
.PHONY: clean
clean:
rm *.o
- ./main:
/* main.c */
#include<stdio.h>
#include"../func/func.h"
int main()
{
printf("HelloWorld\\n1 + 2 = %d\\n", func(1, 2));
return 0;
}
/* makefile */
CC = gcc
main.o: main.c
$(CC) -c main.c -o main.o
cp main.o ../build/main.o
.PHONY: clean
clean:
rm *.o
- ./build:
CC = gcc
test: func.o main.o
$(CC) func.o main.o -I ../func -o test
.PHONY: clean
clean:
rm test *.o
- ./:
.PHONY: all
all:
cd func;make
cd main;make
cd build;make
.PHONY: clean
clean:
cd build;make clean
cd main;make clean
cd func;make clean
可以看到上面四部分文件存在一个总控makefile和三个子makefile,通过make最终在build文件夹内生成可执行文件test,而test输出HelloWorld已经一个简单的加法式子。
由上图可以看到,在执行make
命令后,其按顺序依次进入各个文件夹并执行make
命令(蓝框内),在make完成后,挨个查看各个文件夹,发现文件均正常生成。最后执行可执行文件test,发现正常执行,说明本次make成功。
由上图可以看到,在执行make clean
命令后,其按顺序进入各个文件夹并执行make clean
命令,随后相关文件均被清除。自此该例子完成,其已经实现了make嵌套的功能。
以上是关于Linux学习记录:Makefile的主要内容,如果未能解决你的问题,请参考以下文章