Makefile 工程管理

文章目录

本篇全面深入的讲解 make 项目管理工具,为后续阅读 U-Boot、内核等复杂源码及项目开发打下基础

Make 的原理及 Makefile 的基础知识

Make 简介

工程管理器,顾名思义,是指管理较多的文件

Make 工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入 Makefile 文件的内容来执行大量的编译工作

Make 将只编译改动的代码文件,而不用完全编译。

Makefile 基本结构

Makefile 是 Make 读入的唯一配置文件

  • 由 make 工具创建的目标体(target),通常是目标文件或可执行文件
  • 要创建的目标体所依赖的文件(dependency_file
  • 创建每个目标体时需要运行的命令(command
  • 注意:命令行前面必须是一个 TAB 键,否则编译错误为:*** missing separator. Stop.

Makefile 格式

target: dependency_files
<TAB>  command

例子

hello.o: hello.c hello.h
gcc –c hello.c –o hello.o

Makefile 变量

一个复杂一些的例子:

  • sunq: kang.o yul.o
        gcc kang.o yul.o -o sunq
    kang.o: kang.c kang.h 
        gcc –Wall –O -g –c kang.c -o kang.o
    yul.o: yul.c 
        gcc - Wall –O -g –c yul.c -o yul.o
    

    注释:

    • -Wall 允许发出 gcc 所有有用的报警信息
    • -c 只是编译不链接,生成目标文件 .o
    • -o file 表示把输出文件输出到 file

    关于更多的用 man 工具

创建和使用变量

创建变量的目的:用来代替一个文本字符串:

  • 系列文件的名字
  • 传递给编译器的参数
  • 需要运行的程序
  • 需要查找源代码的目录
  • 你需要输出信息的目录
  • 你想做的其它事情

变量定义的两种方式

  • 递归展开方式 VAR=var
  • 简单方式 VAR:=var
    • 变量使用 $(VAR)
    • $ 则用 $$ 来表示
    • 类似于编程语言中的宏

刚才的例子

OBJS = kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
	$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
	$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
	$(CC) $(CFLAGS) -c yul.c -o yul.o

递归展开方式 VAR=var;例子:

foo = $(bar) 
bar = $(ugh) 
ugh = Huh? 

$(foo) 的值为?

echo $(foo) 来进行查看

  • 优点:它可以向后引用变量
  • 缺点:不能对该变量进行任何扩展,例如:
    CFLAGS = $(CFLAGS) -O
    会造成死循环

简单方式 VAR:=var

m := mm 
x := $(m) 
y := $(x) bar 
x := later 
echo $(x) $(y) # 看看打印什么信息?
  • 用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值进行展开
  • 这种定义变量的方式更适合在大的编程项目中使用,因为它更像我们一般的编程语言

?= 定义变量

dir := /foo/bar
FOO ?= bar # FOO是?
  • 含义是,如果 FOO 没有被定义过,那么变量 FOO 的值就是 bar,如果 FOO 先前被定义过,那么这条语将什么也不做,其等价于:

    ifeq ($(origin FOO), undefined)
    FOO = bar
    endif
    

为变量添加值;你可以通过 += 为已定义的变量添加新的值

Main=hello.o hello-1.o
Main+=hello-2.o

预定义变量

  • AR 库文件维护程序的名称,默认值为 ar
  • AS 汇编程序的名称,默认值为 as
  • CC C 编译器的名称,默认值为 cc
  • CPP C 预编译器的名称,默认值为 $(CC) –E
  • CXX C++ 编译器的名称,默认值为 g++
  • FC FORTRAN 编译器的名称,默认值为 f77
  • RM 文件删除程序的名称,默认值为 rm -f

例子:

Hello: main.c main.h 
<tab> $(CC) –o hello main.c
clean:
<tab> $(RM) hello

预定义变量

  • ARFLAGS 库文件维护程序的选项,无默认值。
  • ASFLAGS 汇编程序的选项,无默认值。
  • CFLAGS C 编译器的选项,无默认值。
  • CPPFLAGS C 预编译的选项,无默认值。
  • CXXFLAGS C++ 编译器的选项,无默认值。
  • FFLAGS FORTRAN 编译器的选项,无默认值。

刚才的例子

  • OBJS = kang.o yul.o
    CC = gcc	
    CFLAGS = -Wall -O -g
    sunq : $(OBJS)
    	$(CC) $(OBJS) -o sunq
    kang.o : kang.c kang.h
    	$(CC) $(CFLAGS) -c kang.c -o kang.o
    yul.o : yul.c yul.h
    	$(CC) $(CFLAGS) -c yul.c -o yul.o
    

自动变量

  • $* 不包含扩展名的目标文件名称
  • $+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
  • $< 第一个依赖文件的名称
  • $? 所有时间戳比目标文件晚的的依赖文件,并以空格分开
  • $@ 目标文件的完整名称
  • $^ 所有不重复的目标依赖文件,以空格分开
  • $% 如果目标是归档成员,则该变量表示目标的归档成员名称

刚才的例子:

OBJS = kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
	$(CC) $^ -o $@
kang.o : kang.c kang.h 
	$(CC) $(CFLAGS) -c $< -o $@
yul.o : yul.c yul.h
	$(CC) $(CFLAGS) -c $< -o $@

环境变量

  • make 在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量
  • 如果用户在 Makefile 中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量

Make 命令的选项及 Makefile 中的隐含规则

Make 使用

直接运行 make

选项

  • -C dir 读入指定目录下的 Makefile
  • -f file 读入当前目录下的 file 文件作为 Makefile
    make -f Makefile.debug
    make -f Makefile.debug clean
  • -i 忽略所有的命令执行错误
  • -I dir 指定被包含的 Makefile 所在目录
  • -n 只打印要执行的命令,但不执行这些命令
  • -p 显示 make 变量数据库和隐含规则
  • -s 在执行命令时不显示命令
  • -w 如果 make 在执行过程中改变目录,打印当前目录名
  • -j \nproc`` 根据电脑内核数进行编译

Makefile 的隐含规则

隐含规则 1:编译 C 程序的隐含规则

  • <n>.o 的目标依赖目标会自动推导为 <n>.c 并且其生成命令是 $(CC) -c $(CPPFLAGS) $(CFLAGS)

隐含规则 2:链接 Object 文件的隐含规则

  • <n> 目标依赖于 <n>.o,通过运行 C 的编译器来运行链接程序生成(一般是 ld),其生成命令是:$(CC) (LDFLAGS) <n>.o

  • $(LOADLIBES) $(LDLIBS),这个规则对于只有一个源文件的工程有效,同时也对多个 Object 文件(由不同的源文件生成)的也有效。例如如下:

    • 规则:
      x : x.o y.o z.o

    • 并且 x.cy.cz.c 都存在时,隐含规则将执行如下命令:

      cc -c x.c -o x.o
      cc -c y.c -o y.o
      cc -c z.c -o z.o
      cc x.o y.o z.o -o x
      
    • 如果没有一个源文件(如上例中的 x.c)和你的目标名字(如上例中的 x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的

实例 1:

  • $ ls -R
    Makefile	clean		f2.c		include
    Makefile2	f1.c		head.h		main.c
    
    ./include:
    myinclude.h
    
  • CFLAGS=-c -Wall -I include
    f1:f1.o f2.o main.o	
    
    .PHONY:clean
    clean:
    	rm *.o f1
    

实例 2:

  • ls -R
    Makefile	clean		f1.c		head.h		main.c
    Makefile2	config.mk	 f2.c		include
    
    ./include:
    myinclude.h
    
    OBJS=f1.o f2.o
    OBJS+=main.o
    CFLAGS=-c -Wall -I include
    ~           
    ~           
    ~ 
    ~ 
    ~ 
    "config.mk" 3L, 55C
    
    
  • #OBJS=f1.o f2.o
    #OBJS+=main.o
    #CFLAGS=-c -Wall -I include
    include config.mk
    test:$(OBJS)
    	gcc $(OBJS) -o test
    #f1.o:f1.c
    #f2.o:f2.c
    #main.o:main.c
    .PHONY:clean
    clean:
    	rm *.o test
    
    

VPATH 及嵌套的 Makefile

VPATH 的用法

VPATH:虚路径

  • 在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当 make 需要去寻找文件的依赖关系,你可以在文件前加上路径,但最好的方法是把一个路径告诉 make,让 make 在自动去找。
  • Makefile 文件中的特殊变量 VPTH 就是文成这个功能的,如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么 make 就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了。
  • VPATH = src:../headers
  • 上面的定义指定两个目录,src../headers,make 会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当目前目录永远是最高的优先搜索的地方)

实例 3:

  • $ ls -R
    Makefile	Makefile2	main		src2
    Makefile1	include		src1
    
    ./include:
    head.h		myinclude.h
    
    ./main:
    main.c
    
    ./src1:
    f1.c
    
    ./src2:
    f2.c
    
  • FLAGS=-c -Wall -I include
    VPATH=src1 src2 main
    f1:f1.o f2.o main.o
    
    .PHONY:clean
    clean:
            find ./ -name "*.o" -exec rm {} \;;rm f1
    

嵌套的 Makefile

案例:

ls -R
Makefile	f2		main
f1		include		obj

./f1:
Makefile	f1.c

./f2:
Makefile	f2.c

./include:
myinclude.h

./main:
Makefile	main.c

./obj:
Makefile
CC=gcc
SUBDIRS=f1 \
		f2 \
		main \
		obj
OBJS=f1.o f2.o main.o
BIN=myapp
OBJS_DIR=obj
BIN_DIR=bin
export CC OBJS BIN OBJS_DIR BIN_DIR

all:CHECK_DIR $(SUBDIRS)
CHECK_DIR:
	mkdir -p $(BIN_DIR)
$(SUBDIRS):ECHO
	make -C $@
ECHO:
	@echo $(SUBDIRS)
	@echo begin compile
CLEAN:
	@$(RM) $(OBJS_DIR)/*.o
	@rm -rf $(BIN_DIR)
  • 我们注意到有一句 @echo $(SUBDIRS)
  • @(RM)并不是我们自己定义的变量,那它是从哪里来的呢?
  • make -C $@
  • export CC OBJS BIN OBJS_DIR BIN_DIR
上一篇:【福利】h3c无线资料


下一篇:linux命令