通过erlang.mk项目,掌握基本的makefile语法,可以自己定制makefile。

1. makefile 基本规则:

1. 所有的源文件没有被编译过,则对各个源文件进行编译并进行链接,生成最后的可执行程序;
2. 每一个在上次执行make之后修改过的源代码文件在本次执行make时将会 被重新编译;
3. 头文件在上一次执行make之后被修改。则所有包含此头文件的源文件在本次执行 make 时将会被重新编译。

2. 基本格式:

TARGET... : PREREQUISITES...
  COMMAND
  ...

2.1 target(目标)通常是最后需要生成的文件名或者为了实现这个目的而必需的中间过程文件名。

也可以是一个make执行的动作的名称,如目标“clean”:伪目标(phony target)。

2.2 PREREQUISITES(依赖条件) 生成规则目标所需要的文件名列表。通常一个目标依赖于一个或者多个文件。

2.3 COMMAND(命令行) 任意的shell命令或者是可在shell下执行的程序。

  它限定了make执行这条规则时所需要的动作。可以有多个命令行,每一条命令占一行。

注意:每一个命令行必须以[Tab]字符开始,[Tab]字符告诉 make 此行是一个命令行,make程序本身并不关心命令是如何工作的,对目标文件的更新需要你在规则描述中提供正确的命令。

“make”程序所做的 就是当目标程序需要更新时执行规则所定义的命令。

建议:单目标,多依赖。就是说尽量要做到一个规则中只存在一 个目标文件,可有多个依赖文件。尽量避免使用多目标,单依赖的方式 。

最神奇的就是这三个元素都可以使用变量,变量又可以使用通配符展开。

3. 实战(build erlang.mk项目)

下面通过erlang.mk这个项目自己的build过程来了解一下最基本makefile的工作步骤:

##makefile
BUILD_CONFIG_FILE ?= $(CURDIR)/build.config
BUILD_CONFIG = $(shell sed "s/\#.*//" $(BUILD_CONFIG_FILE)) ERLANG_MK = erlang.mk
ERLANG_MK_VERSION = $(shell git describe --tags --dirty) .PHONY: all check all:
awk 'FNR==1 && NR!=1{print ""}1' $(patsubst %,%.mk,$(BUILD_CONFIG)) \
| sed 's/^ERLANG_MK_VERSION = .*/ERLANG_MK_VERSION = $(ERLANG_MK_VERSION)/' > $(ERLANG_MK) ifeq ($(p),)
check:
$(MAKE) -C test
else
check:
$(MAKE) -C test pkg-$(p)
endif

以上是根据build.config生成给我们项目自己使用的erlang.mk文件 (注意这个并不在build自定义项目,而只是生成erlang.mk,然后用它来build我们自定义的项目),它包括了变量赋值.引用,特殊变量 伪目标,终极目标, 条件语句:

3.1 变量

. Makefile 中变量和函数的展开(除规则命令行中的变量和函数以外),是在 make 读取 makefile 文件时进行的,这里的变量包括了使用“=”定义和使用指示符 “define”定义的。
. 变量可以用来代表一个文件名列表、编译选项列表、程序运行的选项参数列表、 搜索源文件的目录列表、编译输出的目录列表和所有我们能够想到的事物。
. 变量名是不包括“:”、“#”、“=”、前置空白和尾空白的任何字符串。
. 变量名是大小写敏感的。
. 另外有一些变量名只包含了一个或者很少的几个特殊的字符(符号)。称它们为 自动化变量。像“$<”、“$@”、“$?”、“$*” . 变量的赋值

3.1.1 递归展开式变量

语法:VAR  = Value

BUILD_CONFIG = $(shell sed "s/\#.*//" $(BUILD_CONFIG_FILE))

使用此风格的变量定义,可能会由于出现变量的递归定义而导致make陷入到无限的变量展开过程中,最终使 make 执行失败。如下:

CFLAGS = $(CFLAGS) –O

这种风格的变量定义中如果使用了函数,那么包含在变量值中的 函数总会在变量被引用的地方执行(变量被展开时)。

3.1.2 直接展开式变量

语法  VAR := Value
变量值中对其他量或者函数的引用在定义变量时被展开(对变量进行替换)

x := foo
y := $(x) bar
x := later
# 就等价于:
y := foo bar
x := later

3.1.3 ?= 操作符

 BUILD_CONFIG_FILE ?= $(CURDIR)/build.config 

只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。

正因为变量只是简单的宏展开,且所有的makefile都是先从头到尾先计算好更新规则后,一次性从终极目标(第一个目标)开始执行的。所以在erlang.mk中才必须core的部分写在编译前面(因为core中定义了大量与编译erl相关的参数)。

3.2 变量的引用

$(BUILD_CONFIG)
#或
${BUILD_CONFIG}

3.3 特殊的变量
$(CURDIR) 此变量代表 make 的工 作目录。当使用“-C”选项进入一个子目录后,此变量将被重新赋值。总之,如果在 Makefile 中没有对此变量进行显式的赋值操作,那么它代表 make 的工作目录。
$(MAKE) 用于递归展开make时MAKE可以带不一样的参数,比如MAKE := /bin/make 在另一个目录就是MAKE := /bin/make -t
3.4 伪目标
语法 :target不是一个真正的文件名
使用.phony:all check 的原因:避免 目录中有一个文件叫all或check,强制说明这个是伪目标。
3.5 命令行
在 Makefile 中书写在同一行中的多个命令属于一个完整的 shell 命令行,书写在独立行的一条命令是一个独立的 shell 命令行。
如果想在同一个shell中运行多条命令,应该用 \ 连接各个命令。让它成为一行命令。
3.6. 条件语句:

ifeq($(p),)
check:
$(MAKE) -C test
else
check:
$(MAKE) -C test pkg-$(p)
endif

3.7 shell 命令:

#把BUILD_CONFIG_FILE里面的所有注释去掉。
sed 's/pattern/replace_string' FileName
sed "s/\#.*//" $(BUILD_CONFIG_FILE)
#表示在除了第一个文件croe/core.mk外,其它的文件第一行都要先加一个空行。1表示返回值为1
awk 'BEGIN{print "start"} pattern{ commands} END{print "end"} file
awk 'FNR==1 && NR!=1{print ""}1' $(patsubst %,%.mk,$(BUILD_CONFIG))
#展开后:
awk 'FNR==1 && NR!=1{print ""}1' core/core.mk index/*.mk core/index.mk core/deps.mk plugins/protobuffs.mk core/erlc.mk core/docs.mk core/test.mk plugins/asciidoc.mk plugins/bootstrap.mk plugins/c_src.mk plugins/ci.mk plugins/ct.mk plugins/dialyzer.mk plugins/edoc.mk plugins/elvis.mk plugins/erlydtl.mk plugins/escript.mk plugins/eunit.mk plugins/relx.mk plugins/shell.mk plugins/triq.mk plugins/xref.mk plugins/cover.mk

awk特殊变量:
 NR:表示记录数量,在执行过程中对应于当前行号
 FNR:表示单个文件的当前行号,在多个文件中不会累加,NR会累加。
3.8 make 内置函数

patsubst %, %.mk,$(BUILD_CONFIG))

把BUILD_CONFIG里面的第行都加上.mk结尾。patsubst是makefile的一个内部函数。

3.9 终极目标

默认的情况下,make执行的是Makefile中的第一个规则,此规则的第一个目标称 之为“最终目的”或者“终极目标”(就是一个Makefile最终需要更新或者创建的目标)。

4. erlang.mk build 自定义项目

4.1 core.mk负责做什么?

  

core.mk的源代码(点这里),一起来过一遍里面定义各种变量时使用到的makefile知识 :P

$(MAKEFILE_LIST)
make 程序在读取多个 makefile 文件时,包括由环境变量“MAKEFILES”指定、 命令行指令、当前工作下的默认的以及使用指示符“include”指定包含的,在对这些文件进行解析执行之前make读取的文件名将会被自动依次追加到变量 “MAKEFILE_LIST”的定义域中
ERLANG_MK_FILENAME := $(realpath 
    $(lastword $(MAKEFILE_LIST)))
$(lastword $(MAKEFILE_LIST))
取最后一个word :内置函数  
$(realpath $(lastword $(MAKEFILE_LIST)))
表示把路径转为绝对路径(. .. ~转化为具体的路径)  
@
当前目标名字(The file name of the target.)
verbose_0 = @ 
$(verbose_$(V))

变量名也可以使用拼接的方式,

谨记:变量只是简单的文本展开

V?=0
verbose = $(verbose_$(V))

ifneq 

  

条件判断语句
ifeq ($(PLATFORM),)
UNAME_S := $(shell uname -s)
export   
把一个变量export,Tell make whether or not to export a particular variable to child processes.
export PLATFORM
::

双冒号规则中,当依赖文件比目标更新时。规则将会被执行。对于一个没有依 赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执 行。而普通规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目 标文件永远是最新的)。

当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理, 而不是像普通规则那样合并所有的依赖到一个目标文件

all:: deps
$(verbose) $(MAKE) --no-print-directory app
$(verbose) $(MAKE) --no-print-directory rel #这个也是终极目标!出现在mk中的第一个目标!

define name
multi command
endef

在使用指示符“define”定义一个多行的变量或者命令包时,其定义体 (“define”和“endef”之间的内容)会被完整的展开到 Makefile 中引用此变量的地方 (包含定义体中的注释行);make 在引用此变量的地方对所有的定义体进行处理,决 定是注释还是有效内容。

define core_http_get
wget --no-check-certificate -O $(1) $(2)|| rm $(1)
endef
#调用
$(call core_http_get,$(ELVIS),$(ELVIS_URL))

内置函数可以参考这里

在core.mk里面有一个有意思的功能:它使用 make erlang.mk 会自动更新到最新版本的erlang.mk。

erlang-mk:
git clone https://github.com/ninenines/erlang.mk $(ERLANG_MK_BUILD_DIR)
if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR); fi
cd $(ERLANG_MK_BUILD_DIR) && $(MAKE)
cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
rm -rf $(ERLANG_MK_BUILD_DIR)

基本上*.mk都是这个套路,接下来进入正题:

4.2 erlang.mk到底怎么build自定义项目的?

上一节已讲到makefile的入口:终极目标是all, 一切都是从这里开始的。

#core.mk
all:: deps
$(verbose) $(MAKE) --no-print-directory app
$(verbose) $(MAKE) --no-print-directory rel
#deps.mk
ifneq ($(SKIP_DEPS),)
deps::
else
deps:: $(ALL_DEPS_DIRS)
ifneq ($(IS_DEP),)
$(verbose) rm -f $(ERLANG_MK_TMP)/deps.log
endif
$(verbose) mkdir -p $(ERLANG_MK_TMP)
$(verbose) for dep in $(ALL_DEPS_DIRS) ; do \
if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \
echo -n; \
else \
echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \
if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \
$(MAKE) -C $$dep IS_DEP= || exit $$?; \
else \
echo "ERROR: No Makefile to build dependency $$dep."; \
exit ; \
fi \
fi \
done
endif #erlc.mk
ifeq ($(wildcard ebin/test),)
app:: app-build
else
app:: clean app-build
endif

你只要认真看看这三个mk就可以理清他们是怎么工作的,完全不需要再新了解规则!

感觉这里还是很有意思的,准备下次再画个图理理!这周就先到这里吧。

周未愉快 ~

5. 参考文档

5.1 erlang.mk github地址

5.2 makefile中文手册

5.3 makefile quick reference

6. Todo:

0.erlang.mk如何组织依赖关系?

1.如何并行编译(如何控制并行的先后顺序)?

2.如何调试makefile?

3.makefile的命令行小技巧?

4.如何给erlang.mk加自定义的功能?

--------------------------------------------------------------------------------------------------------------------------------------------------

一次编译成功~~

 

[Erlang34]erlang.mk的源码阅读1-入门makefile的更多相关文章

  1. [Erlang 0119] Erlang OTP 源码阅读指引

      上周Erlang讨论群里面提到lists的++实现,争论大多基于猜测,其实打开代码看一下就都明了.贴出代码截图后有同学问这代码是哪里找的?   "代码去哪里找?",关于Erla ...

  2. Sping学习笔记(一)----Spring源码阅读环境的搭建

    idea搭建spring源码阅读环境 安装gradle Github下载Spring源码 新建学习spring源码的项目 idea搭建spring源码阅读环境 安装gradle 在官网中下载gradl ...

  3. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  4. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  5. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  6. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  7. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  8. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  9. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

随机推荐

  1. 32位ubuntu14.04手动编译hadoop2.6.0

    下载官方编译好的包安装老是报错:Unable to load native-hadoop library for your platform 用file命令查看发现官方包里的libhadoop.so. ...

  2. SCN与数据恢复的关系

    Oracle内部主要存在以下四种SCN 1.系统检查点(system checkpoint)SCN 每当一个检查点完成时,Oracle就把该检查点对应的SCN记录到控制文件中,可以用以下语句查看当前数 ...

  3. ExtJS动态创建组件

    J是代码动态创建dom: 或者 eval有后台组织代码,前台执 ======================= ExtJS组件的动态的创建: 程序中大多数时候需要在后台根据业务逻辑创建符合要求的组件, ...

  4. Pycharm上python和unittest两种姿势傻傻分不清楚

    前言 经常有人在群里反馈,明明代码一样的啊,为什么别人的能出报告,我的出不了报告:为什么别人运行结果跟我的不一样啊... 这种问题先检查代码,确定是一样的,那就是运行姿势不对了,一旦导入unittes ...

  5. joda-time的使用

    值得一提的是该功能被整合到Java 8 中 ,被称为java8 新特新之一  .这意味着不用到包 <!-- 时间操作组件 -->          <dependency>   ...

  6. Nginx 文件下载资源配置

    下面配置是针对所有.apk文件下载 本人 需要.apk文件 放在Linux里面作为下载, 放在/root目录下面出现403 Forbinden, (暂时不清楚), 放在其他目录正常 然后新建目录/re ...

  7. javascript常用验证大全

    1. 长度限制 <script> function test() { if(document.a.b.value.length>50) { alert("不能超过50个字符 ...

  8. UGUI 锚点设置为四方扩充模式然后设置局部坐标为0将出现什么问题

    UGUI 锚点设置为四方扩充模式然后设置局部坐标为0将出现什么问题? 情形:按钮A挂在主画布上.四方扩充模式.A的中心和画面中心不重合. 这时候用代码设置A.localPosition = new V ...

  9. Python操作符重载总结&列表模型

    操作符重载 二元运算符 特殊方法 + __add__,__radd__ - __sub__,__rsub__ * __mul__,__rmul__ / __div__,__rdiv__,__trued ...

  10. PHP GD库

    <?php $file = '12.jpg'; //打开图片 $im = imagecreatefromjpeg($file); //设置水印字体颜色 $color = imagecoloral ...