一个单目录通用的Makefile的实现

时间:2021-11-20 12:45:32

       Makefile是Linux下程序开发的自动化编译工具,一个好的Makefile应该准确的识别编译目标与源文件的依赖关系,并且有着高效的编译效率,也就是说我们每次重新make时只需要处理那些修改过的文件即可。Makefile拥有很多功能,而很多初学者因为不会写一个Makefile文件光在运行的时候敲命令就浪费了很多时间,为了简化问题的复杂性我们针对单目录下C/C++项目开发的时候,怎么写一个单目录通用版的Makefile。

 

  单目录下的Makefile文件书写,我们假设一个项目目录code/,里面有main.c  fun1.c  fun2.c  head.h,其中main.c  fun1.c  fun2.c分别包含头文件head.h,假如我们没有Makefile,上述项目用户每次都要输入:gcc main.c fun1.c fun2.c -o main -I. 如果说我们项目有多个.c文件的时候光是输入命令行代码都相当浪费时间,如果写好Makefile之后我们就可以让Makefile帮我们做这件事情,就可以节省很多时间。

  Makefile怎么书写呢?Makefile文件的核心是:“依赖”和“目标”。依赖的意思呢一般是指我们的.c文件,所以无依赖的Makefile就是让他执行命令,意思就是没有.c文件跟函数单纯让Makefile执行一些系统命令(比如说创建目录)。一般编译程序的Makefile都是有依赖的。目标指的是你最终想得到的文件,一般指的是可执行程序。上述项目code中依赖就是main.c fun1.c fun2.c,目标就是main。书写Makefile是我们先确定好依赖跟目标的名字,按照以下格式:

目标:依赖(如果多个依赖用空格分开)

<Tab键>编译规则

(注意<Tab键>不等于四个空格,编译规则被识别出来就是因为这个Tab键)

编译规则中:

1.$^    代表所有的依赖文件(等价于main.c fun1.c fun2.c)

2.$@  代表目标文件(等价于main)

例如上述的例子我们单纯实现gcc main.c fun1.c fun2.c -o main -I.这句命令的话Makefile可以这样写:

main:main.c fun1.c fun2.c
  gcc $^ -o $@

(如果不想在终端看到这条命令打印出来我们可以在前面加个@也就是@gcc $^ -o $@)

这时我们在终端输入make其实就是执行gcc main.c fun1.c fun2.c -o main -I.在当前目录下生成一个main的可执行文件

那对于多个目标的情况怎么办呢,比如我有main.c还有main1.c我们分开一次执行一个的时候我们还要每次都输入一次rm main删除掉上一次的执行文件太麻烦了,我们可以把rm main也写成一个规则:

main:main.c fun1.c fun2.c
  @gcc $^ -o $@

clean:
  @rm main

把规则写好之后我们可以通过在终端输入make clean执行rm main命令

makefile的变量种类。
1. 自定义变量 -> makefile不需要声明类型,只需要定义名字就可以。变量默认是字符串类型的。
规则:
1)变量名与C语言规则一致。
2)引用makefile中变量时,需要在变量前面添加$。 $A $(A)
3)因为变量都是默认是字符串类型,所以""可以省略。

  我们将所有依赖都放到一个变量中C_SOURCE=main.c fun1.c fun2.c

2. 系统预设定变量
有些变量是系统中已经写好的,并且已经赋了初值的,这些变量的值就可以直接使用。
CC: -> 编译器名字,默认系统赋值是cc CC=cc cc等价于gcc
RM: -> 删除命令,默认系统赋值是rm -f RM=rm -f

  把系统预设定变量加到Makefile中CC=arm-linux-gcc(不需要运行到开发板的可以CC=cc就行这里我是需要在开发板运行所以要交叉编译工具)

3. 自动化变量 -> 变量的值不是固定的,而是会变化的。
$^ -> 代表当前的所有依赖
$@ -> 代表目标

  按照规则进行整合之后初步代码如下:

CC=arm-linux-gcc
C_SOURCE=main.c fun1.c fun2.c
TARGET=main

$(TARGET):$(C_SOURCE)
  $(CC) $^ -o $@

clean:
  rm $(TARGET)

这个时候的Makefile已经可以通用所有单目录下多个.c文件的项目,但是呢不够自动化,就是修改变量还要自己打开Makefile里面进行修改,不够爽,而且这个初步代码有许多没有考虑的问题,存在很大缺陷,所以呢我们可以改善一下代码

首先第一点假如我们目录下有个文件就叫clean那我们make clean的时候就会出错,怎么办呢?我们可以在Makefile里面加一条伪指令(就是告诉系统这个目标不是生成文件)

伪指令写法:.PHONY:clean

还有就是我们希望Makefile自动化更改变量,什么意思呢?我们每次写一个.C文件都要在Makefile里面添加依赖文件太麻烦了,Makefile创建的目的就是想简单化操作,所以如果把添加依赖文件的工作也交给Makefile那简直是再好不过了,那么应该怎么实现呢?我们可以使用函数 wildcard ,wildcard函数作用: 在指定的路径下找到相匹配的文件。例如我们假设一个变量A,A = $(wildcard *.c)  -> 在当前目录下寻找所有的.c结尾的文件,并把结果保存在变量A,每个结果之间使用空格分开。

那么我们最终的单目录通用Makefile为:

CC=arm-linux-gcc
C_SOURCE=$(wildcard *.c)
TARGET=main
INCLUDE_PATH=-I .

$(TARGET):$(C_SOURCE)
  $(CC) $^ -o $@ $(INCLUDE_PATH)

.PHONY:clean

clean:
  $(RM) $(TARGET)

这就是单个目录中比较通用的Makefile文件,对于一个目录下多个.c的编译来说这套规则已经足够,但是如果你想高档点的比如把函数封装到库里的话也可以在相应的规则上进行添加或者修改就可以了。毕竟没有绝对无脑的通用,这套代码的通用性和实用性也已经不差,试想一下在目录下拷贝一份Makefile小改一下就可以用已经很不错了。