Makefile

 meke命令一般用于编译程序,而make命令都依赖于 Makefile 文件。

 最简单的Makefile如下:

hello:  hello.c
gcc -o hello hello.c
clean:
rm -f hello

注:缩进使用 tab 键 , 因为Makefile 会为每个以 tab 键开头的命令创建一个 shell 去执行。

 具体规则参考 《GUN Make 使用手册》

以下为粗略笔记

Makefile规则与示例

为什么需要Makefile

 提高编译效率。

Makefile样式

 一个简单的Makefile文件包含一系列"规则":

目标...:依赖...
<tab>命令
  • 命令被执行的两个简单条件:

    • 目标文件还没有生成
    • 依赖文件比目标文件新

先介绍Makefile的两个函数

  1. $(foreach var,list,text)

     for each var in list,change it to text。

     对list中的每一个元素,取出赋值给var,然后把var改为text所描述的形式。

    例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d
  1. $(wildcard pattern)

      把存在的 pattern 文件列出来

    例子:
src_files := $(wildcard *.c)

完善Makefile

  1. 先来一个简单粗暴、效率低的:
test: main.c hello.c hello.h
gcc -o test main.c hello.c
  1. 再来一个Makefile,效率高、精炼,支持自动检测头文件
objs := main.o hello.o

test : $(objs)
gcc -o test $^ # 需要判断是否存在依赖文件
# .main.o.d .hello.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files)) # 把依赖文件包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif %.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $< clean:
rm *.o test -f distclean:
rm $(dep_files) *.o test -f

了解自动化变量。

通用Makefile的使用

参考linux内核的Makefile来编写一个通用的Makefile,特点:

  1. 支持多个目录、多层目录、多个文件;
  2. 支持给所有文件设置编译选项;
  3. 支持给目录设置编译选项;
  4. 支持给某个文件单独设置编译选项;
  5. 简单、好用。

通用的Makefile解释

零星知识点
make命令的使用

 执行make命令时,它会去当前目录下查找名为Makefile的文件,并根据它的只是去执行操作,生成第一个目标。

 也可以用 -f 选项指定文件,如:

make -f Makefile.build

指定文件的名字随意定义


 可以使用 -C 指定目录,切换到其他目录去,如:

make   -C   a/   abc.def

 可以指定目标,不再默认生成第一个目录,如:

make   -C   a/   abc.def   other_target
即时变量与临时变量

 变量定义语法:

形式 说明
A = xxx 延时变量
B ?= xxx 延时变量,只有第一次定义时赋值才成功,若曾被定义过,则此赋值无效。
C := xxx 立即变量
D += yyy 如果D在前面是延时变量,那么现在它还是延时变量
如果D在前面是立即变量,那么它现在还是立即变量

延时变量

 使用时才确定该值。

如:

A = $@
tets:
@echo $A

输出 A 的值为 test

即时变量

 定义时立即确定该值。

如:

A := $@
tets:
@echo $A

输出 A 的值为

变量的导出(export)

export 是供给子目录的 Makefile 使用的(即 sub-make),同一级的makefile是访问不到的。

 可以通过makefile中的内置变量MAKELEVEL可以查看当前的makefile的level。

Makefile中的shell

 Makefile中可以使用shell命令,如:

TOPDIR := $(shell_pwd)
Makefile中放置第一个命令

 执行目标时,如果不指定目标,则默认执行第一个目标。

所以,第一个目标的位置很重要。有时候不太方便把第一个目标完整地放在文件的前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖和命令。比如:

First_target:      // 这句话放在前面
........ // 其他代码,比如include其它文件得到后的 xxx 变量
First_target : $(xxx) $(xxx)
command
假想目标

Makefile中可能会有这样的目标:

clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)

:如果当前目录下更好有个名为“clean”的文件,那么执行“make clean”时就不会执行makefile中目标clean的内容,所以要把clean设置为假想目标即可:

.PHONY    :   clean
小知识

PHONY 是 phoney,即伪造的意思。PHONY后面的target是伪造的target,不是真实存在的文件。同时,注意make命令中后面的target默认是文件。

常用函数
  • $(foreach var, list, text)

    for each var in list,change it to text.

    直接上例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // dep_files 的最终结果为“**.a.o.d .b.o.d**”

  • $(wildcard pattern)

    列出 pattern 存在的文件。

    例子:
src_files = $(wildcard *.c) // src_files的值为当前目录下所有 .c 文件。

  • $(filter pattern..., text)

    text中符合pattern格式的内容留下来
obj-y := a.o b.o c/ d/
DIR := $(filter %/, $(obj-y)) //结果为:c/ d/

  • $(filter-out pattern..., text)

    text中符合pattern格式的内容删除掉
obj-y := a.o b.o c/ d/
DIR := $(filter-out %/, $(obj-y)) // 结果为:a.o b.o

  • $(patsubst pattern, replacement, text)

    寻找text中符合pattern格式的内容,用replacement代替他们。
subdir-y := a.o b.o c/ d/
subdir := $(patsubst %/, %, $(obj-y)) // 结果为:c d

*通用Makefile设计思想
  1. 在Makefile文件中确定要编译的文件、目录,比如:
obj-y += main.o
obj-y += a/

Makefile 文件总是被 Makefile.build 包含的。

  1. 在 Makefile.build 中设置编译规则,有 3 条编译规则

    1. 怎么编译子目录?

      1. 进入子目录编译即可:
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
2. 如何编译当前目录中的目标文件?
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
3. 当前目录下的 **.o** 和子目录下的 **built-in.o** 要打包起来:
$(TARGET) : built-in.o
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o

思想总结

  1. Makefile文件确定要编译的文件和目录
  2. Makefile.build文件包含规则
  3. 每个需要编译的子目录都放个Makefile文件,把需要编译的文件编译成built-in.o
  4. 再把所有子目录的built-in.o链接到顶层的built-in.o
  5. 最后把顶层built-in.o链接到APP

最好重新分析一下通用Makefile

一个通用Makefile

本程序的Makefile分为3类:

  1. 顶层目录的Makefile
  2. 顶层目录的Makefile.build
  3. 各级子目录的Makefile

源码(注释版)

Makefile

CROSS_COMPILE = # 交叉编译工具头,如:arm-linux-gnueabihf-
AS = $(CROSS_COMPILE)as # 把汇编文件生成目标文件
LD = $(CROSS_COMPILE)ld # 链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者一个可执行文件
CC = $(CROSS_COMPILE)gcc # 编译器,对 C 源文件进行编译处理,生成汇编文件
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar # 打包器,用于库操作,可以通过该工具从一个库中删除或则增加目标代码模块
NM = $(CROSS_COMPILE)nm # 查看静态库文件中的符号表 STRIP = $(CROSS_COMPILE)strip # 以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码
OBJCOPY = $(CROSS_COMPILE)objcopy # 复制一个目标文件的内容到另一个文件中,可用于不同源文件之间的格式转换
OBJDUMP = $(CROSS_COMPILE)objdump # 查看静态库或则动态库的签名方法 # 共享到sub-Makefile
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP # -Wall : 允许发出 GCC 提供的所有有用的报警信息
# -O2 : “-On”优化等级
# -g : 在可执行程序中包含标准调试信息
# -I : 指定头文件路径(可多个)
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include # LDFLAGS是告诉链接器从哪里寻找库文件,这在本Makefile是链接最后应用程序时的链接选项。
LDFLAGS := # 共享到sub-Makefile
export CFLAGS LDFLAGS # 顶层路径
TOPDIR := $(shell pwd)
export TOPDIR # 最终目标
TARGET := test # 本次整个编译需要源 文件 和 目录
# 这里的“obj-y”是自己定义的一个格式,和“STRIP”这些一样,*但是 一般内核会搜集 ”obj-”的变量*
obj-y += main.o # 需要把当前目录下的 main.c 编进程序里
obj-y += sub.o # 需要把当前目录下的 sub.c 编进程序里
obj-y += subdir/ # 需要进入 subdir 这个子目录去寻找文件来编进程序里,具体是哪些文件,由 subdir 目录下的 Makefile 决定。
#obj-y += $(patsubst %.c,%.o,$(shell ls *.c)) # 第一个目标
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built ! # 处理第一个依赖,**转到 Makefile.build 执行**
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build # 处理最终目标,把前期处理得出的 built-in.o 用上
$(TARGET) : built-in.o
$(CC) -o $(TARGET) built-in.o $(LDFLAGS) # 清理
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET) # 彻底清理
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)

Makefile.build

注意,include 命令,相对路径为 执行本 Makefile.build 的路径

# 伪目标
PHONY := __build
__build: # 清空需要的变量
obj-y :=
subdir-y :=
EXTRA_CFLAGS := # 包含同级目录Makefile
# 这里要注意,相对路径为 执行本 Makefile.build 的路径
include Makefile # 获取当前 Makefile 需要编译的子目录的目录名
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y) # 把子目录的目标定为以下注释
# built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) # 获取当前目录需要编进程序的文件名作为,并写为目标
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
# 使修改头文件 .h 后,重新make后可以重新编译(重要)
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# 列出存在的文件
dep_files := $(wildcard $(dep_files)) ifneq ($(dep_files),)
include $(dep_files)
endif PHONY += $(subdir-y) # 第一个目标
__build : $(subdir-y) built-in.o # 优先编译 子目录的内容
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build # 链接成 目标
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^ dep_file = .$@.d # 生成 cur_objs 目标
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $< .PHONY : $(PHONY)

使用说明

  • 本程序的Makefile分为3类:

    1. 顶层目录的 Makefile
    2. 顶层目录的 Makefile.build
    3. 各级子目录的 Makefile

1. 各级子目录的Makefile

  • 参考:
# 可以不添加下面两个变量
EXTRA_CFLAGS :=
CFLAGS_test.o := obj-y += test.o
obj-y += subdir/
  • obj-y += file.o 表示把当前目录下的file.c编进程序里,
  • obj-y += subdir/ 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
  • EXTRA_CFLAGS, 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
  • CFLAGS_xxx.o, 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置
注意
  1. "subdir/"中的斜杠"/"不可省略
  2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
  3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项

2. 顶层目录的Makefile

主要作用:

  • 整个工程的参数初期定义

    • 架构
    • 工具链
    • 编译工具
    • 编译参数
    • 需要导出的变量
    • 等等
  • 定义最终目标
  • 跳转到 Makefile.build

3. 顶层目录的Makefile.build **

注意:该文件虽然放在顶层,但是也是提供给各级子目录使用的。

主要功能:

  • 把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,把各个subdir/built-in.o和当前目录的目标 .o 合并打包为当前目录的built-in.o

使用提示

  • 执行"make"来编译,执行 make clean 来清除,执行 make distclean 来彻底清除。
Makefile附件
一些符号
符号 说明
$@ 表示规则中的目标文件集
$% 当目标为函数库的时候,则表示规则中的目标成员名。反之为空。如一个目标为"foo.a(bar.o)",那么,"$%"就是"bar.o",以空格分隔开。
$< 依赖文件集合中的第一个文件,如果依赖文件以"%"形式出现,则表示符合模式的一系列文件集合
$? 所有比目标新的依赖集合,以空格分隔开。
$^ 所有依赖文件集合,以空格分隔开。如果依赖有相同,则取其一。
$+ 和 "$^"类同,但是不会把相同的删除掉。
$* 这个变量表示目标模式中 "%"及其之前的部分,如果目标是 test/a.test.c,目标模式为 a.%.c, 那么 "$* " 就是 test/a.test。

参考

  • 以上笔记为整理韦东山老师笔记而分享出来的

【linux】-Makefile简要知识+一个通用Makefile的更多相关文章

  1. 一个通用Makefile的编写

    作者:杨老师,华清远见嵌入式学院讲师. 我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文件.如果我们用gcc去一个一个编译每一个源文件的 ...

  2. 一个通用Makefile详解

    我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文 件. 如果我们用gcc去一个一个编译每一个源文件的话,效率会低很多,但是如果我们可以写 ...

  3. 一步一步写一个简单通用的makefile(四)--写一个通用的makefile编译android可执行文件

    通常要把我们自己的的代码编译成在android里面编译的可执行文件,我们通常是建一个文件夹 . ├── Android.mk ├── Application.mk ├── convolve.cl ├─ ...

  4. [Makefile]多文件的通用Makefile

    下面是一个糅合多线程和多文件的示例 emc-test.c #include <stdio.h> #include <pthread.h> #include "char ...

  5. Makefile学习与进阶之Makefile.am和$$(M)的意思

    (1)makefile 中,出现$$(M) 是什么意思,发现还是看实际的Makefile长知识啊 在makefile中,会经常使用shell命令,也经常见到$var 和 $$var的情况,有什么区别呢 ...

  6. Linux C编程学习之开发工具3---多文件项目管理、Makefile、一个通用的Makefile

    GNU Make简介 大型项目的开发过程中,往往会划分出若干个功能模块,这样可以保证软件的易维护性. 作为项目的组成部分,各个模块不可避免的存在各种联系,如果其中某个模块发生改动,那么其他的模块需要相 ...

  7. 编写一个通用的Makefile文件

    1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...

  8. 一个简单的通用Makefile实现

    一个简单的通用Makefile实现   Makefile是Linux下程序开发的自动化编译工具,一个好的Makefile应该准确的识别编译目标与源文件的依赖关系,并且有着高效的编译效率,即每次重新ma ...

  9. 一个通用的Makefile (转)

    据http://bbs.chinaunix.net/thread-2300778-1-1.html的讨论,发现还是有很多人在问通用Makefile的问题,这里做一个总结.也作为以后的参考.       ...

随机推荐

  1. Oracle缓存表与Oracle缓存的区别

    一.Oracle缓存表 与 Oracle缓存 的概念 Oracle 缓存:是把Oracle近期查询的语句放置在Oracle设定的缓存当中. Oracle 缓存表:是把某个表放置在缓存当中,缓存是Ora ...

  2. 一文读懂Redis常见对象类型的底层数据结构

    Redis是一个基于内存中的数据结构存储系统,可以用作数据库.缓存和消息中间件.Redis支持五种常见对象类型:字符串(String).哈希(Hash).列表(List).集合(Set)以及有序集合( ...

  3. MS SQL SERVER执行大脚本文件时,提示“内存不足”的解决办法

    问题描述: 当客户服务器不允许直接备份时,往往通过导出数据库脚本的方式来部署-还原数据库, 但是当数据库导出脚本很大,用Microsoft SQL Server Management Studio执行 ...

  4. 《3D打印与工业制造》个人总结 —— 周吉瑞

    <3D打印与工业制造>个人总结 ---- 周吉瑞 JERRY_Z. ~ 2020 / 10 / 24 转载请注明出处!️ 目录 <3D打印与工业制造>个人总结 ---- 周吉瑞 ...

  5. CentOS7 安装telnet-0.17-64.el7.x86_64

    1.安装客服端,服务端,xinetd yum -y install telnet telnet-server xinetd 以上要想完成telnet安装,telnet服务端和xinetd必须安装,至于 ...

  6. B. GameGame 解析(思維、博弈)

    Codeforce 1383 B. GameGame 解析(思維.博弈) 今天我們來看看CF1383B 題目連結 題目 兩個人在玩遊戲,有一個長度為\(n\)的數列\(a\),每次每個人選一個數字和目 ...

  7. F. Make It Connected 解析(思維、MST)

    Codeforce 1095 F. Make It Connected 解析(思維.MST) 今天我們來看看CF1095F 題目連結 題目 給你\(n\)個點,每個點\(u\)還有一個值\(a[u]\ ...

  8. KVM虚拟化管理平台WebVirtMgr部署及使用

    KVM虚拟化管理平台WebVirtMgr部署及使用   需求: 公司机房有一台2U的服务器(64G内存,32核),由于近期新增业务比较多,测试机也要新增,服务器资源十分有限.所以打算在这台2U服务器上 ...

  9. jdk1.8特性2

    public class User { private Long id; private String userName; private String roleName; private Strin ...

  10. java 文件和byte 互转

    /** * 获得指定文件的byte数组 */ private byte[] getBytes(String filePath){ byte[] buffer = null; try { File fi ...