GNU Make 笔记

 

参考文档:

GNU Make Manual   https://www.gnu.org/software/make/manual/make.html

《跟我一起写makefile》

《GNU make》

 

GNU make 有哪些功能?

GNU make 如何使用?

包括了make的规则、变量、函数以及脚本。

 

GNU make 程序 “将源代码转换成可执行文件” 的工作进行自动化

make 的优点是得到程序中各元素之间的关系,make 根据时间戳判断应该重新进行哪些步骤,

以产生你所需要的程序。 make 定义了一种语言,描述源文件、中间文件以及可执行文件之间的关系。

 

案例1:假设有如下程序

 

使用命令行编译

gcc -o hellomake hellomake.c hellofunc.c

 

缺点

1. 如果有更多的文件,gcc命令复杂

2. 如果只改动某个文件,所有文件都需要重新编译

 

1. Makefile 版本1

hellomake: hellomake.c hellofunc.c
     gcc -o hellomake hellomake.c hellofunc.c 

 

hellomake 是工作目标

hellomake.c 与 hellofunc.c 是为了创建hellomake的必须存在的文件

gcc -o 是为了创建 hellomake 目标而执行的shell 命令

 

另一种写法

hellomake: hellomake.o hellofunc.o            # hellomake 表示最终目标
    gcc -o hellomake hellomake.o hellfunc.o

hellomake.o : hellomake.c hellomake.h         # hellomake.o 模块
    gcc -c hellomake.c

hellofunc.o: hellofunc.c hellomake.h          #hellofunc 模块
    gcc -c hellofunc.c

clean:
  rm hellomake *.o

 

make 首先看到hellomake 目标, 发现必须得有 hellomake.o 与 hellofunc.o 必要条件

依次向下查看发现有 hellomake.o 目标, 有 hellomake.c 与 hellomak.h 文件,执行命令生成  hellomake.o 目标

依次向下查看发现有 hellofunc.o 目标, 有 hellofunc.c 与 hellomake.h 文件,执行命令生成  hellofunc.o 目标

生成  hellomake 目标 

 

说明:Makefile 文件中包含一组用编译应用程序的规则,make 看到的第一项规则叫默认规则

规则由三部分组成: target(目标), prerequiste(必要条件), command(命令).

target...: prereq1 prereq2 ...
  commands
       ....

 

本例中:目标文件是 hellomake 和 hellomake.o hellofunc.o 文件, 依赖文件就是冒号后面的c文件和.h文件

工作目标 - 是一个必须建造的文件或进行的事情。

必要条件 - 是工作目标得以创建之前,必须事先存在的那些文件

执行的命令 - 当必要条件成立时,要创建目标执行的shell命令

 

Makefile 中一般有五个东西:

显示规则, 隐晦规则, 变量定义, 文件指示,注释

 

规则分为 

具体规则 - 上面的版本一

模式规则 - 使用的是通配符,不是具体的名称

隐含规则 - 可以是模式规则 也可以是 内置于make的后缀规则。

静态模式规则 - 只能应用在特定的工作目标文件中。

 

 

GNU make 执行步骤:

1. 读入所有Makefile

2. 读入被include的其他Makefile

3. 初始化文件中的变量

4. 推导隐晦规则, 分析所有规则

5. 为所有的目标文件创建依赖关系

6. 根据依赖关系,决定哪些目标文件要重新定义

7. 执行命令

 

缺点

* 所有代码文件都在同一目录中

* 如果增加一个文件,需要在target, command, clean中同时增加,增加了重复,下面通过定义变量来

  解决这个问题。

 

通配符的概念

*.* 表示所有 文件名中带. 的文件名

? 代表任何一个单一字符

[...] 代表字符集

[^...] 代表字符集补集

~ 当前用户主目录

 

例如如下 

prog: *.c              // 列出所有文件

  $(CC) -o $@ $^          

 

(1) $@ ——目标文件的名称;
(2) $^ ——所有的依赖文件,以空格分开,不包含重复的依赖文件;
(3) $< ——第一个依赖文件的名称。 

 

phony target 假想的目标的概念

任何不代表文件的工作

场景: 以工作目标充当标签来代表命令脚本, 比如 make clean , make world 等

clean:

  rm -f *.o lexer.c

make 无法区分 文件形式的工作目标 与 假想的工作目标, 如果目录中有clean文件,

则会以文件形式的工作目标。

make 为了区分真正的文件与假想工作目标, 引入了 .PHONY 告诉 make , 该工作目标不是一个真正的文件

.PHONY: clean
clean: 
  rm -f *.o lexer.c

 

 

.PHONE 与注释一起用:

hello: build_msg hello.c
    gcc hello.c -o hello

.PHONY: build_msg
build_msg:
    @printf "#\n# Building hello \n#\n"

 

输出结果

& make
#
# Building hello
#
gcc hello.c -o hello

 

常用的假想目标

all: 执行编译应用程序的所有命令

install: 从已编译的二进制文件进行应用程序的安装

clean: 将产生源代码的二进制文件删除

disclean: 删除编译过程中所产生的任何文件

TAGS: 建立可供编辑器使用的标记表

info: 从Textinfo 源代码来创建GNU info 文件

check: 执行与应用程序相关的任何测试

 

 

变量 $(variable-name)

一个例子

#常数
CC := gcc
MKDIR  := mkdir -p

# 内部变量
sources = *.c
objects  = $(subst  .c, .o, $(sources))

# 函数
maybe-make-dir = $(if $(whildcard $1),, $(MKDIR) $1)
assert-not-null = $(if $1,, $(error Illegal null value.))

 

 

make 中变量的类型

:= 用来创建简单变量

= 用来创建递归变量

?= 用来创建附带条件的变量赋值运算

+= 称为附加运算符

 

自动变量

$@  工作目标的文件名

$%  档案文件成员结构中的文件名称

$<   第一个必要条件的文件名

$?   时间戳在工作目标之后的所有必要条件,并以空格隔开这些必要条件

$^  所有必要条件的文件名,并以空格隔开

$+ 如同 $^, 但包含重复的文件名。

$*  工作目标的主文件名。

 

目录结构

VPATH = src include

vpath %.l %.c src

vpath %.h include

CPPFLAGS = -I include

VPATH 变量的内容是一份目录列表,可供make搜索其所需要的文件。

 

2. Makefile 版本2

CC=gcc
CFLAGS=-I.
OBJ=hellomake.o hellofunc.o hellomake: $(OBJ) $(CC)
-o hellomake $(OBJ) -I.

hellomake.o : hellomake.c hellomake.h gcc -c hellomake.c hellofunc.o: hellofunc.c hellomake.h gcc -c hellofunc.c

.PYTHON: clean

clean:
  rm hellomake *.o

 

说明:使用变量的方式来简化了重复定义的问题

GNU make 很强大,具有自动识别依赖关系的能力,可以不用自己定义OBJ

 

3. Makefile 版本3

CC=gcc
CFLAGS=-I.
OBJ=hellomake.o hellofunc.o hellomake: $(OBJ) $(CC) -o hellomake $(OBJ) -I.

hellomake.o : hellomake.h hellofunc.o: hellomake.h
.PYTHON: clean
clean:
  rm hellomake $(OBJ)

 

说明:.PYTHON 表示clean是一个psuodo目标文件。

 

Makefile 版本5

CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o 

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
    gcc -o $@ $^ $(CFLAGS)

 

 

Makefile 版本6

IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)

ODIR=obj
LDIR =../lib

LIBS=-lm

_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))

_OBJ = hellomake.o hellofunc.o 
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))


$(ODIR)/%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
    gcc -o $@ $^ $(CFLAGS) $(LIBS)

.PHONY: clean
clean:
    rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ 

 

 

 

在BSD与Linux平台通用的makefile

编译 make

Debug  make DEBUG=1

BSD  gmake BSD=1

# Makefile for utelnetd
# 
# Configure this with the following environment variables:
#

# where to install
INSTDIR        := /usr/local/bin/

# GNU target string 
CROSS        :=

# where to find login program
ifneq ("", "$(BSD)")
LOGIN        := /usr/bin/login
else
LOGIN        := /bin/login
endif

ifneq ("", "$(BSD)")
CORE        := utelnetd.core
else
CORE        := core
endif

# nothing to configure below this line... ---8<---8<---8<---

PROGS     = utelnetd

INSTMODE  = 0755
INSTOWNER = root
INSTGROUP = root

OBJS      = utelnetd.o

CC        = $(CROSS)gcc
INSTALL   = install

CFLAGS     += -I. -pipe -DSHELLPATH=\"$(LOGIN)\" -Wall

ifneq ("","$(DEBUG)")
CFLAGS   += -DDEBUG -g -Os
STRIP      = \#
else
CFLAGS     += -fomit-frame-pointer
STRIP      = $(CROSS)strip 
endif

ifeq ("1", "$(BSD)")
CFLAGS   += -DBSD
endif


all: $(PROGS)

$(PROGS): $(OBJS)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
    $(STRIP) --remove-section=.comment --remove-section=.note $@

.PHONY: install
install: $(PROGS)
    $(INSTALL) -d $(INSTDIR)
    $(INSTALL) -m $(INSTMODE) -o $(INSTOWNER) -g $(INSTGROUP) $(PROGS) $(INSTDIR)

.PHONY: clean
clean:
    rm -f $(PROGS) *.o $(CORE)

 

 

 

 

 

 

 

 

 

 

 

 

 

GNU make 官方文档 https://www.gnu.org/software/make/manual/make.html

 

posted @ 2017-12-20 10:08  elewei  阅读(449)  评论(0编辑  收藏  举报