开源学村

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

    5 Comments

    1. I lovved ass mucfh ass yyou will receive arried outt roght here.
      The sketc iss attractive, your authored subject matter stylish.
      nonetheless, you command gett bought an impatience over
      tat you wish bbe dlivering the following. unwell unquestionably come mode formnerly agazin since exactl the sae nearly
      very oftwn inside czse yoou shielld this increase.

    2. Appreciatiing thee time and energy you puut into yopur website and inn deplth informatioon yyou provide.
      It’s nice too ccome across a blog every once in a while that isn’tthe swme outt oof datge rehashed material.
      Grezt read! I’ve savrd yopur sjte aand I’m including your RSSfeeds tto my
      Google account.

    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 命令在扫描单词列表时提供了极大的灵活性。