开源学村

Makefile 简介

前言

这篇文章旨在介绍 Makefile 这一强力的工具,对 Makefile 的规则和变量等内容没有做出完整的描述,只是简单进行了一点介绍,希望更多的人能了解并使用 Makefile

Makefile 是什么

一个正式的软件工程中,源文件按类型、功能、模块等分别放在不同的目录下,如果每次都在命令行这样: gcc a.c b.c c.c -o test ,显然是非常影响效率的,那么这时候就需要 Makefile 来进行管理,在 Makefile 中指定哪些文件先编译,那些文件后编译,在什么情况下编译哪些文件等操作。

Makefile 就是一个用来帮助我们编译的工具,和 Windows 下的 IDE 类似,只不过 Makefile 需要我们自己动手编写,一个好的 Makefile 可以极大的提升工作的效率。

Makefile 规则

target ... : prerequisites ...
command
...

target 就是我们编译文件要生成的目标prerequisites 就是我们编译文件需要的依赖command 就是用依赖生成目标所需要执行的命令

比如我们平时使用的 gcc a.c b.c -o test
这里的 test 就是我们要生成的目标, a.c 就是我们生成目标需要的依赖,而 gcc a.c -o test 则是命令。将这行命令用 Makefile 的方式来写就是:

test:a.c b.c
    gcc a.c b.c -o test

Makefile 中的命令必须用 tab 开始,不能是空格。

Makefile 可以自动推导文件以及文件依赖关系后面的命令,在后面的示例中我们可以看到目标的依赖基本都是 .o 文件而不是 .c 文件,原因正是 Makefile 强大的自动推导功能。
通常 Makefile 中还会有一个名为 clean 的目标,用来清除编译后产生的各种文件。一般情况下 Makefile 会根据依赖和目标的新旧来决定是否编译,但是如果不小心修改了目标而造成目标比依赖新的情况的话,Makefile 会因为目标比依赖新而忽略这个目标下的命令,这个时候显然会造成问题,一个解决的办法就是使用 clean 这样的目标来清除编译后的文件,然后 make 重新编译。
clean 这个目标有点特殊,他是不需要依赖的,因此也叫伪目标。一般使用方式如下:

clean:
    rm *.o test -f

Makefile 使用

make 命令执行时,需要一个 Makefile 文件(文件名为 Makefile 、 makefile 、 *.mk ),以告诉 make 命令需要怎么样的去编译和链接程序。执行时只用在命令行输入 make , Makefile 就会自动执行第一个目标下的命令。而是否执行命令则取决于依赖,如果没有目标文件或是目标后的依赖文件比目标文件新,Makefile 就会执行其下面的命令。

Makefile 中使用 # 注释,只注释 # 后的一行。
Makefile 中引用其他 Makefile,用 include 指令来引用。引用的效果就是原地展开。
Makefile 命令前面加 @ 来静默执行,即执行命令时不打印命令本身。

Makefile 变量

Makefile 中的变量和 shell 脚本中非常相似,都是直接定义,不需要类型,引用时用 $(var) 。

伪目标( .PHONY ):伪目标形式上是一个目标,但是不需要依赖,伪目标一般只是为了执行目标下面的命令(比如 clean 就是伪目标)。

Makefile 中的几种变量赋值运算符

  • = 赋值,可以被赋值为变量的值,解析时取这个变量最后的值。
  • := 也是赋值,被赋值为变量时解析为变量在这行语句时的值,即变量如果后面改变这里的值也不改变。
  • ?= 如果变量前面并没有赋值过则执行这条赋值,如果前面已经赋值过了则本行被忽略。
  • += 用来给一个已经赋值的变量接续赋值,意思就是把这次的值加到原来的值的后面。

关于 = 和 := ,比如 B=$(A)bcd ,那么 B 的值取决于变量 A 最后一次被赋值的值,即使 A 在 B 之后再次被赋值,变量 B 仍然会随着 A 的改变而改变。而 := 则只看之前 A 最后被赋值的值。

Makefile 的环境变量

  • Makefile 中用 export 导出的就是环境变量。一般情况下要求环境变量名用大写,普通变量名用小写。
  • 环境变量和普通变量不同,可以这样理解:环境变量类似于整个工程中所有 Makefile 之间可以共享的全局变量,而普通变量只是当前本 Makefile 中使用的局部变量。所以要注意:定义了一个环境变量会影响到工程中别的 Makefile 文件,因此要小心。

Makefile 中的自动变量

自动变量是 Makefile 中提前预定义的特殊意义的符号,类似 C 语言中的宏 __LINE__ 等,提前被定义并被赋予了特殊含义。

  • $@ 目标文件名,比如上文的 test 。
  • $< 第一个依赖文件名,如果依赖目标是以模式(即“ % “)定义的,那么” $< “将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $^ 依赖的文件集合,比如上文的 a.c b.c 。

此外还可以向 Makefile 传参, $# 存放传递参数个数, $1 存放第一个参数的字符串, $2 存放第二个参数的字符串……

其他

通配符:比如在当前文件夹下有 1.c 2.c 12.c test.c 1.h 。

  • % 若干个任意字符,和 * 很相似,但是 % 一般只用于规则描述中,又叫做规则通配符。
  • * 若干个任意字符 *.c 匹配 1.c 2.c 12.c test.c 。
  • ? 1个任意字符 ?.c 匹配 1.c 2.c 。
  • [] 将 [] 中的字符依次去和外面的结合匹配 [12].c 匹配 1.c 2.c 。

Makefile 与 shell 脚本非常相似,shell 脚本中能使用的 Makefile 也能使用,比如 awk 等工具。

Makefile 示例

这个示例是我从九鼎创展的 x210 开发板例程中找的一个 Makefile 。

#
# Makefile for module.
#

CROSS       ?= arm-none-eabi-
NAME        := template-framebuffer-tingl-cube

#
# System environment variable.
#
ifneq (,$(findstring Linux, $(shell uname -s)))
HOSTOS      := linux
endif
ifneq (,$(findstring windows, $(shell uname -s)))
HOSTOS      := windows
endif
ifeq ($(strip $(HOSTOS)),)
$(error unable to determine host operation system from uname)
endif

#
# Load variables of flag.
#
ASFLAGS     := -g -ggdb -Wall -O3
CFLAGS      := -g -ggdb -Wall -O3
CXXFLAGS    := -g -ggdb -Wall -O3
LDFLAGS     := -T link.ld -nostdlib
ARFLAGS     := -rcs
OCFLAGS     := -v -O binary
ODFLAGS     :=
MCFLAGS     := -mcpu=cortex-a8 -mtune=cortex-a8 -march=armv7-a -mfpu=neon -ftree-vectorize -ffast-math -mfloat-abi=softfp

LIBDIRS     :=
LIBS        :=
INCDIRS     :=
SRCDIRS     :=

#
# Add necessary directory for INCDIRS and SRCDIRS.
#
INCDIRS     += include \
               include/hardware \
               include/library
SRCDIRS     += source \
               source/startup \
               source/hardware \
               source/arm \
               source/library \
               source/library/ctype \
               source/library/errno \
               source/library/exit \
               source/library/malloc \
               source/library/stdlib \
               source/library/string \
               source/library/stdio \
               source/library/math \
               source/graphic \
               source/graphic/maps/software

#
# Add external library
#
INCDIRS     += source/tinygl \
               source/tinygl/include
SRCDIRS     += source/tinygl/

#
# You shouldn't need to change anything below this point.
#
AS          := $(CROSS)gcc -x assembler-with-cpp
CC          := $(CROSS)gcc
CXX         := $(CROSS)g++
LD          := $(CROSS)ld
AR          := $(CROSS)ar
OC          := $(CROSS)objcopy
OD          := $(CROSS)objdump
MKDIR       := mkdir -p
CP          := cp -af
RM          := rm -fr
CD          := cd
FIND        := find

ifeq ($(strip $(HOSTOS)), linux)
MKV210      := tools/linux/mkv210
endif
ifeq ($(strip $(HOSTOS)), windows)
MKV210      := tools/windows/mkv210
endif

#
# X variables
#
X_ASFLAGS   := $(MCFLAGS) $(ASFLAGS)
X_CFLAGS    := $(MCFLAGS) $(CFLAGS)
X_CXXFLAGS  := $(MCFLAGS) $(CXXFLAGS)
X_LDFLAGS   := $(LDFLAGS)
X_OCFLAGS   := $(OCFLAGS)
X_LIBDIRS   := $(LIBDIRS)
X_LIBS      := $(LIBS) -lgcc

X_OUT       := output
X_NAME      := $(patsubst %, $(X_OUT)/%, $(NAME))
X_INCDIRS   := $(patsubst %, -I %, $(INCDIRS))
X_SRCDIRS   := $(patsubst %, %, $(SRCDIRS))
X_OBJDIRS   := $(patsubst %, .obj/%, $(X_SRCDIRS))

X_SFILES    := $(foreach dir, $(X_SRCDIRS), $(wildcard $(dir)/*.S))
X_CFILES    := $(foreach dir, $(X_SRCDIRS), $(wildcard $(dir)/*.c))
X_CPPFILES  := $(foreach dir, $(X_SRCDIRS), $(wildcard $(dir)/*.cpp))

X_SDEPS     := $(patsubst %, .obj/%, $(X_SFILES:.S=.o.d))
X_CDEPS     := $(patsubst %, .obj/%, $(X_CFILES:.c=.o.d))
X_CPPDEPS   := $(patsubst %, .obj/%, $(X_CPPFILES:.cpp=.o.d))
X_DEPS      := $(X_SDEPS) $(X_CDEPS) $(X_CPPDEPS)

X_SOBJS     := $(patsubst %, .obj/%, $(X_SFILES:.S=.o))
X_COBJS     := $(patsubst %, .obj/%, $(X_CFILES:.c=.o))
X_CPPOBJS   := $(patsubst %, .obj/%, $(X_CPPFILES:.cpp=.o)) 
X_OBJS      := $(X_SOBJS) $(X_COBJS) $(X_CPPOBJS)

VPATH       := $(X_OBJDIRS)

.PHONY: all clean
all : $(X_NAME)

$(X_NAME) : $(X_OBJS)
    @echo [LD] Linking $@.elf
    @$(CC) $(X_LDFLAGS) $(X_LIBDIRS) -Wl,--cref,-Map=$@.map $^ -o $@.elf $(X_LIBS)
    @echo [OC] Objcopying $@.bin
    @$(OC) $(X_OCFLAGS) $@.elf $@.bin
    @echo make header information for irom booting
    @$(MKV210) $@.bin

$(X_SOBJS) : .obj/%.o : %.S
    @echo [AS] $<
    @$(AS) $(X_ASFLAGS) $(X_INCDIRS) -c $< -o $@
    @$(AS) $(X_ASFLAGS) -MD -MP -MF $@.d $(X_INCDIRS) -c $< -o $@

$(X_COBJS) : .obj/%.o : %.c
    @echo [CC] $<
    @$(CC) $(X_CFLAGS) $(X_INCDIRS) -c $< -o $@
    @$(CC) $(X_CFLAGS) -MD -MP -MF $@.d $(X_INCDIRS) -c $< -o $@

$(X_CPPOBJS) : .obj/%.o : %.cpp
    @echo [CXX] $<
    @$(CXX) $(X_CXXFLAGS) $(X_INCDIRS) -c $< -o $@   
    @$(CXX) $(X_CXXFLAGS) -MD -MP -MF $@.d $(X_INCDIRS) -c $< -o $@

clean:
    @$(RM) $(X_DEPS) $(X_OBJS) $(X_OBJDIRS) $(X_OUT)
    @echo Clean complete.

#
# Include the dependency files, should be place the last of makefile
#
sinclude $(shell $(MKDIR) $(X_OBJDIRS) $(X_OUT)) $(X_DEPS)

下面这个是 uboot1.3.4 中的 Makefile ,比较老了,与新版 uboot 有些差异:

uboot 主 Makefile 分析


本文来自 CDUT LUG 社区作者苏森的原创投稿。
转载请注明原文出处,否则必究相关责任。
原文链接:https://blog.cdutlug.org/2017/11/makefile_introduction/
本文链接:https://linuxstory.org/makefile_introduction/

对这篇文章感觉如何?

太棒了
0
不错
0
爱死了
1
不太好
0
感觉很糟
0
Linux 爱好与学习者,虽然唱歌一般但业余喜欢捣鼓点音乐相关的一些东西,惰性与勤奋并存的纠结体~

    You may also like

    Leave a reply

    您的电子邮箱地址不会被公开。 必填项已用 * 标注

    此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

    More in:开源学村

    开源学村

    SoCal Linux博览会回归20周年

    现在已经是支持和推广FOSS社区的第20个年头了,SCaLE 20x - 南加州Linux博览会将于2023年3月9日至12日在帕萨迪纳会议中心举行。 这个为期4天的年度活动汇集了充满活力的开源用户社 […]
    开源学村

    如何在Linux上杀死僵尸进程

    消灭僵尸进程! 也称为“defunct”或“dead”进程 - 简单来说,僵尸进程是指已经结束,但仍存在于系统进程表中的进程。理想情况下,一旦进程完成其工作/执行,它应该从进程表中清除。但由于某些原因 […]
    开源学村

    2022年,从学习Rust开始

    Rust作为一个新语言,已经连续五年(2016,2017,2018,2019,2020)在Stack Overflow开发者调查的“最受喜爱编程语言”。Rust是一个值得学习的编程语言,它对安全的专注,会帮助你许多。学习Rust 从这本小小的Rust Cheat Sheet出发,了解Rust语言的基本操作。
    开源学村

    使用 Linux 命令行解决Wordle 问题

    你可以使用 Linux 的 grep 命令和 fgrep 命令来解决Wordle 问题(进行筛选排除),grep 命令使用正则表达式进行搜索,fgrep 命令也像 grep 一样搜索文本,但不使用正则表达式,grep 和 fgrep 命令在扫描单词列表时提供了极大的灵活性。