Loading

关于 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 。

尚有未尽述之事,且待下回分解吧。

参考资料

跟我一起写Makefile

posted @ 2025-06-15 23:32  Baiyug  阅读(26)  评论(0)    收藏  举报