关于 makefile 的一些学习笔记(一)
关于 makefile 的一些学习笔记(一)
0 前言
最近梳理代码时接触到许多makefile相关的一些语法,故进行一些学习和记录。
1 概述
makefile是编译c/c++时常用的文件,主要用于描述编译行为。实际上,makefile是一种特殊的脚本文件,通过make(based on linux GNU or sth else IDE)工具执行。
基本上GNU的make工作步骤如下
- 1 读入所有的Makefile
- 2 读入被include的其它Makefile
- 3 初始化文件中的变量
- 4 推导隐式规则,并分析所有规则
- 5 为所有的目标文件创建依赖关系链
- 6 根据依赖关系,决定哪些目标要重新生成
- 7 执行生成命令
根据我的理解,总的来讲,makefile的语法主要分为rules,commands,variables,conditions,functions。其中rules,commands是必要的,variables和functions很常用。
1.1 rules
makefile 的规则(rules)基本上可以分为三个部分,即 target,prerequisites 和 recipe 。 从结构上来讲,他们在makefile中的组织方式通常为
target: prerequisites
recipe
以上就组成了makefile中一组规则描述。
例如
main.o : main.c main.h
gcc -c main.c
这个例子,描述了使用 gcc -c main.c 命令(对应recipe),通过依赖 main.c main.h 文件(对应prerequisites),生成 main.o 文件(对应target)。编写时需注意格式缩进和空格,这一点和python很像。
1.2 commands
commands就是指使用make时的命令行指令的行为定义,一般来讲,基本的命令有以下的形式:
all:
@echo "#"
@echo "# Making $@ ..."
$(MAKE) PROFILE=debug app_host
$(MAKE) PROFILE=release app_host
help:
@echo "make # build executables"
@echo "make clean # clean everything"
install:
@echo "#"
@echo "# Making $@ ..."
@mkdir $(EXEC_DIR)/debug
$(CP) bin/debug/app_host $(EXEC_DIR)/debug
@mkdir $(EXEC_DIR)/release
$(CP) bin/release/app_host $(EXEC_DIR)/release
clean::
rm -rf bin
其中 all help install clean 称为伪目标,即当使用make all/help/install/clean 时, 不是make对应的文件,而是执行makefile里相应的指令。默认情况下直接使用make会执行makefile中定义的第一组指令,这里使用make和make all是等价的。
其中指令需要使用tab键开头,@表示不显示命令字,即 echo xxx 在控制台只显示xxx 。
1.3 variables
variables主要定义某个字符表示一些字符串,便于在其他部分引用。例如:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit: $(objects)
gcc -o edit $(objects)
上述定义了objects变量表示8个.o文件,然后在规则中进行了引用。通常使用$(xxx)引用变量。
1.4 conditions
conditions 主要是使用ifeq or ifneq来判断执行不同的指令,例如:
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
1.5 functions
$(patsubst <pattern>,<replacement>,<text>)
# 查找 <text> 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 <pattern> ,如果匹配的话,则以 <replacement> 替换。
$(addprefix <prefix>,<names...>)
# 把前缀 <prefix> 加到 <names> 中的每个单词前面。
$(notdir <names...>)
# 从文件名序列 <names> 中取出非目录部分。非目录部分是指最後一个反斜杠( / )之后的部分。
2 编写makefile时的一些技巧
2.1 利用make自动推导
make执行时会自动寻找文件依赖,因此一些情况下只需要在依赖部分写头文件,省略源文件。例如
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
gcc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
2.2 使用其他makefile
使用inlcude或-include命令
include products.mak
-include products.mak
products.mak 也是一种 makefile 文件,其中include前面加一个“-”表示如果找不到相关文件,不必理会,继续进行其他命令。
2.3 文件搜寻(很有用)
使用VPATH关键字定义目标依赖所在的文件路径
VPATH = host/srcs:dsp/srcs
这里定义了两个搜索路径 host/srcs 和 dsp/srcs ,中间使用 “:”隔开。
或者使用vpath关键字
vpath %.c host/srcs
vpath %.c dsp/srcs
2.4 去除源文件母路径(很有用)
dsrcs = $(wildcard ./*.c ./thread/*.c)
srcs = $(notdir $(dsrcs))
这里dsrcs定义为当前目录下的所有源文件以及 包含 thread 目录名的thread目录下所有的源文件,有时候需要把路径名称去除,就需要使用 notdir 函数,最终可获得不含路径信息的源文件名称。
2.5 自动化变量
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
foo.o: foo.c
bar.o: bar.c
其中$<和$@都是自动化变量,分别表示第一个依赖文件和目标集,通常使用在有多个目标的规则下。
这里$<表示目标 foo.o 的第一个依赖文件(即 foo.c)以及目标 bar.o 的第一个依赖文件(即 bar.o),说是第一个,实际上一个目标文件对应的源文件依赖一般也只有一个;
这里$@表示$(objects) 即 目标 foo.o 以及 目标 bar.o;
如果不使用自动化变量,则表示为如下两条规则:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
2.6 利用编译器参数寻找目标依赖
gcc -MD
生成目标文件所有依赖,输出到.d文件,必再手动书写若干文件的依赖关系。
3 一些注意事项
3.1 变量定义
objects = *.o
这表示把变量objects等于字符串*.o ,而不是所有后缀为.o的文件,如果想要定义为所有后缀为.o的文件名,应该是:
objects = $(wildcard *.o)
4 makefile例子
#
# ======== products.mak ========
#
DEPOT = /home/tl/ti
BIOS_INSTALL_DIR = $(DEPOT)/bios_6_35_04_50
CGT_ARM_PREFIX = /home/tl/arm-2009q1/bin/arm-none-linux-gnueabi-
IPC_INSTALL_DIR = $(DEPOT)/ipc_1_25_03_15
SYSLINK_INSTALL_DIR = $(DEPOT)/syslink_2_21_01_05
CGT_C674_ELF_INSTALL_DIR= $(DEPOT)/ti-cgt-c6000_8.3.5
XDC_INSTALL_DIR = $(DEPOT)/xdctools_3_25_03_72
# Use this goal to print your product variables.
.show:
@echo "BIOS_INSTALL_DIR = $(BIOS_INSTALL_DIR)"
@echo "CGT_ARM_PREFIX = $(CGT_ARM_PREFIX)"
@echo "IPC_INSTALL_DIR = $(IPC_INSTALL_DIR)"
@echo "SYSLINK_INSTALL_DIR = $(SYSLINK_INSTALL_DIR)"
@echo "CGT_C674_ELF_INSTALL_DIR = $(CGT_C674_ELF_INSTALL_DIR)"
@echo "XDC_INSTALL_DIR = $(XDC_INSTALL_DIR)"
# look for other products.mak file to override local settings
ifneq (,$(wildcard $(EXBASE)/../products.mak))
include $(EXBASE)/../products.mak
else
ifneq (,$(wildcard $(EXBASE)/../../products.mak))
include $(EXBASE)/../../products.mak/
# fix SYSLINK_INSTALL_DIR because product uses $(CURDIR)
SYSLINK_INSTALL_DIR = $(word 1,$(subst /examples, examples,$(CURDIR)))
endif
endif
#
# ======== Makefile ========
#
dsrcs = $(wildcard ./*.c ./thread/*.c)
VPATH = thread
srcs = $(notdir $(dsrcs))
EXBASE = ..
include $(EXBASE)/products.mak
-include $(addprefix bin/$(PROFILE)/obj/,$(patsubst %.c,%.ov5T.dep,$(srcs)))
objs = $(addprefix bin/$(PROFILE)/obj/,$(patsubst %.c,%.ov5T,$(srcs)))
libs = $(SYSLINK_INSTALL_DIR)/packages/ti/syslink/lib/syslink.a_$(PROFILE)
PKGPATH := $(SYSLINK_INSTALL_DIR)/packages
PKGPATH := $(PKGPATH)+$(BIOS_INSTALL_DIR)/packages
PKGPATH := $(PKGPATH)+$(IPC_INSTALL_DIR)/packages
all:
@$(ECHO) "#"
@$(ECHO) "# Making $@ ..."
$(MAKE) PROFILE=debug app_host
$(MAKE) PROFILE=release app_host
help:
@$(ECHO) "make # build executables"
@$(ECHO) "make clean # clean everything"
install:
@$(ECHO) "#"
@$(ECHO) "# Making $@ ..."
@$(MKDIR) $(EXEC_DIR)/debug
$(CP) bin/debug/app_host $(EXEC_DIR)/debug
@$(MKDIR) $(EXEC_DIR)/release
$(CP) bin/release/app_host $(EXEC_DIR)/release
clean::
$(RMDIR) bin
#
# ======== rules ========
#
app_host: bin/$(PROFILE)/app_host
bin/$(PROFILE)/app_host: $(objs) $(libs)
@$(ECHO) "#"
@$(ECHO) "# Making $@ ..."
$(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS)
bin/$(PROFILE)/obj/%.ov5T: %.c
@$(ECHO) "#"
@$(ECHO) "# Making $@ ..."
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $<
# ======== install validation ========
ifeq (install,$(MAKECMDGOALS))
ifeq (,$(EXEC_DIR))
$(error must specify EXEC_DIR)
endif
endif
# ======== toolchain macros ========
CC = $(CGT_ARM_PREFIX)gcc -c -MD -MF $@.dep -march=armv5t
AR = $(CGT_ARM_PREFIX)ar cr
LD = $(CGT_ARM_PREFIX)gcc
CPPFLAGS = -D_REENTRANT -Dxdc_target_name__=GCArmv5T \
-Dxdc_target_types__=gnu/targets/arm/std.h
CFLAGS = -Wall -ffloat-store -fPIC -Wunused -Dfar= $(CCPROFILE_$(PROFILE)) \
-I. $(addprefix -I,$(subst +, ,$(PKGPATH)))
LDFLAGS = $(LDPROFILE_$(PROFILE)) -Wall -Wl,-Map=$@.map
LDLIBS = -lpthread -lc -lrt
CCPROFILE_debug = -ggdb -D DEBUG
CCPROFILE_release = -O3 -D NDEBUG
LDPROFILE_debug = -ggdb
LDPROFILE_release = -O3
# ======== standard macros ========
ifneq (,$(wildcard $(XDC_INSTALL_DIR)/bin/echo.exe))
# use these on Windows
CP = $(XDC_INSTALL_DIR)/bin/cp
ECHO = $(XDC_INSTALL_DIR)/bin/echo
MKDIR = $(XDC_INSTALL_DIR)/bin/mkdir -p
RM = $(XDC_INSTALL_DIR)/bin/rm -f
RMDIR = $(XDC_INSTALL_DIR)/bin/rm -rf
else
# use these on Linux
CP = cp
ECHO = echo
MKDIR = mkdir -p
RM = rm -f
RMDIR = rm -rf
endif
# ======== create output directories ========
ifneq (clean,$(MAKECMDGOALS))
ifneq (,$(PROFILE))
ifeq (,$(wildcard bin/$(PROFILE)/obj))
$(shell $(MKDIR) -p bin/$(PROFILE)/obj)
endif
endif
endif
以上是omapl138 使用syslink库进行开发时使用的一个makefile示例,主要使用arm-none-linux-gnueabi-gcc作为编译器,使用syslink库作为依赖,实现多个源文件的编译,其中使用了armv5t实现了ov5T目标文件生成,最终链接获得可执行文件app_host 。
尚有未尽述之事,且待下回分解吧。

浙公网安备 33010602011771号