Linux应用开发基础知识——Makefile 的使用(二)

简介: Linux应用开发基础知识——Makefile 的使用(二)


前言:

Linux 中使用 make 命令来编译程序,特别是大程序;而 make 命令所执 行的动作依赖于 Makefile 文件。最简单的 Makefile 文件: hello: hello.c 、gcc -o hello hello.c 、clean:、 rm -f hello 将上述 4 行存为 Makefile 文件,放入 01_hello 目录下,然后直接执行 make 命令即可编译程序,执行 “make clean”即可清除编译出来的结果。 make 命令根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免编译已经编译过的、没有变化的程序,可以大大提高编译效率。目前我们只需要会使用Makefile即可,等后面学到驱动更底层的知识时候再详细认识Makefile。

一、Makefile 规则与示例

规则

建立两个文件

(1)a.c

#include <stdio.h>
 
int main()
{
        func_b();
        return 0;
}

(2)b.c

#include <stdio.h>
 
void func_b()
{
        printf("This is B\n");
}

(3)Makefile

test:a.o b.o
        gcc -o test a.o b.o
 
a.o : a.c
        gcc -c -o a.o a.c
 
b.o : b.c
        gcc -c -o b.o b.c
book@100ask:~/source/04_2018_Makefile/001_test_app$ gcc -o test a.c b.c

       这样编译会全部编译步骤进行一下,如果每次这样编译会大大降低效率,应该改哪步进行编译哪步,这样才能更好的提高编译效率,所以我们就要学习makefile的规则,一个简单的 Makefile 文件包含一系列的“规则”,其样式如下:

目标(target)… : 依赖(prerequiries)…

命令(command)

如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目 标文件”。

命令被执行的 2 个条件:依赖文件比目标文件,或是目标文件还没生成

二、Makefile的语法

1.通配符: %.o

$@ 表示目标

$<  表示第1个依赖文件

$^   表示所有依赖文件

test: a.o b.o c.o
        gcc -o test $^
 
%.o : %.c
        gcc -c -o $@ $<

2.假想目标:.PHONY

Makefile

test: a.o b.o c.o
        gcc -o test $^
 
%.o : %.c
        gcc -c -o $@ $<
 
clean:
        rm *.o test
 
.PHONY: clean
 

3.即时变量、延时变量,export

简单变量(即时放量):

A := xxx        #    A的值即刻确定,在定义时即确定

B = xxx           #    B的值使用到时才确定

:=        #即时变量

=         #延时变量

?=       #延时变量,如果是第1次定义才起效,如果在前面该变量已定义则忽略这句

+=        #附加,它是即时变量还是延时变量取决于前面的定义

Makefile  

A := $(C)
B = $(C)
C = abc
 
#D = 100ask
D ?= weidongshan
 
all:
        @echo A = $(A)
        @echo B = $(B)
        @echo D = $(D)
 
C += 123

三、Makefile 的函数

$(foreach var,list,text)

简单地说,就是 for each var in list, change it to text。

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

A = a b c
B = $(foreach f, $(A), $(f).o)
 
all:
        @echo B = $(B)

$(wildcard pattern)

pattern 所列出的文件是否存在,把存在的文件都列出来。

$(filter pattern. ..,text)                    #在text中取出符合patten格式的值

$(filter-out pattern.. . ,text)            #在text中取出不符合patten格式的值

C = a b c d/
 
D = $(filter %/, $(C))
E = $(filter-out %/, $(C))
 
all:
        @echo D = $(D)
        @echo E = $(E)

$(wildcard pattern)

pattern定义了文件名的格式,wildcard取出其中存在的文件

files = $(wildcard *.c)    #列出符合后缀是.c的文件都有哪些
 
all:
        @echo files = $(files)

files2 = a.c b.c c.c d.c e.c
files3 = $(wildcard $(files2))
 
all:
        @echo files = $(files)
        @echo files3 = $(files3)

$(patsubst pattern,replacement,$(files2))

从列表中取出每——个值,如果符合pattern,则替换为replacement

 

files2 = a.c b.c c.c d.c e.c
files3 = $(wildcard $(files2))
 
dep_files = $(patsubst %.c,%.d,$(files2))    #将files2中.c后缀文件替换为.d后缀文件
 
all:
        @echo dep_files = $(dep_files)

所有Makefile 函数代码

A = a b c
B = $(foreach f, $(A), $(f).o)
 
 
C = a b c d/
 
D = $(filter %/, $(C))
E = $(filter-out %/, $(C))
 
files = $(wildcard *.c)
 
 
files2 = a.c b.c c.c d.c e.c  abc
files3 = $(wildcard $(files2))
 
dep_files = $(patsubst %.c,%.d,$(files2))
 
all:
        @echo B = $(B)
        @echo D = $(D)
        @echo E = $(E)
        @echo files = $(files)
        @echo files3 = $(files3)
        @echo dep_files = $(dep_files)

四、Makefile实例

1.支持头文件依赖

a.c

 
#include <stdio.h>
 
void func_b();
void func_c();
 
 
int main()
{
        func_b();
        func_c();
        return 0;
}

b.c

 
#include <stdio.h>
 
void func_b()
{
        printf("This is B\n");
}

c.c

 
#include <stdio.h>
#include "c.h"
 
void func_c()
{
        printf("This is C = %d\n", C);
}
 

c.h

 
#define C 4

gcc -M c.c                                       //打印出依赖

gcc -M -MF c.d c.c                         //把依赖写入文件c.d

gcc -c -o c.o c.c -MD -MF c.d        //编译c.o,把依赖写入文件c.d

Makefile

objs = a.o b.o c.o
 
dep_files := $(patsubst %,.%.d, $(objs))
dep_files := $(wildcard $(dep_files))
 
 
test: $(objs)
  gcc -o test $^
 
ifneq ($(dep_files),)                              #将依赖文件添加进去
include $(dep_files)
endif
 
%.o : %.c
  gcc -c -o $@ $< -MD -MF .$@.d        #自动生成依赖文件
 
clean:
  rm *.o test
 
distclean:
  rm $(dep_files)
  
.PHONY: clean 

2.添加CFLAGS

c.c

 
#include <stdio.h>
#include <c.h>
 
void func_c()
{
        printf("This is C = %d\n", C);
}

CFLAGS = -Werror -I.include         #-Werror将所有的警告变为错误

                                                      #-I.include执行当前目录下的include文件

%.o : %.c

        gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d       #自动生成依赖文件

 

Makefile

objs = a.o b.o c.o
 
dep_files := $(patsubst %,.%.d, $(objs))
dep_files := $(wildcard $(dep_files))
 
CFLAGS = -Werror -I.include                           #-Werror将所有的警告变为错误
                                                      #-I.include执行当前目录下的include文件
 
test: $(objs)
        gcc -o test $^
 
ifneq ($(dep_files),)                                 #将依赖文件添加进去
include $(dep_files)
endif
 
%.o : %.c
        gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d       #自动生成依赖文件
 
clean:
        rm *.o test
 
distclean:
        rm $(dep_files)
 
.PHONY: clean

五、Makefile的使用

参考 Linux 内核的 Makefile 编写了一个通用的 Makefile,它可以用来 编译应用程序:

1.支持多个目录、多层目录、多个文件;

2.支持给所有文件设置编译选项;

3.支持给某个目录设置编译选项;

4.支持给某个文件单独设置编译选项;

5.简单、好用。

说明:

本程序的Makefile分为3类:

1. 顶层目录的Makefile

2. 顶层目录的Makefile.build

3. 各级子目录的Makefile

一、各级子目录的Makefile:

  它最简单,形式如下:

EXTRA_CFLAGS  :=

CFLAGS_file.o :=

obj-y += file.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的编译选项

二、顶层目录的Makefile:

  它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,

  主要是定义工具链前缀CROSS_COMPILE,

  定义编译参数CFLAGS,

  定义链接参数LDFLAGS,

  这些参数就是文件中用export导出的各变量。

三、顶层目录的Makefile.build:

  这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o

四、怎么使用这套Makefile:

1.把顶层Makefile, Makefile.build放入程序的顶层目录

  在各自子目录创建一个空白的Makefile

2.确定编译哪些源文件

  修改顶层目录和各自子目录Makefile的obj-y :

  obj-y += xxx.o
   obj-y += yyy/

   这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录    

3. 确定编译选项、链接选项

  修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项;

  修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项;

 

  修改各自子目录下的Makefile:

  "EXTRA_CFLAGS",    它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置

  "CFLAGS_xxx.o",    它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置

 

4. 使用哪个编译器?

  修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-)

 

5. 确定应用程序的名字:

  修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字

6. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除

1.顶层目录

Makefile

 
CROSS_COMPILE = 
AS    = $(CROSS_COMPILE)as
LD    = $(CROSS_COMPILE)ld
CC    = $(CROSS_COMPILE)gcc
CPP   = $(CC) -E
AR    = $(CROSS_COMPILE)ar
NM    = $(CROSS_COMPILE)nm
 
STRIP   = $(CROSS_COMPILE)strip
OBJCOPY   = $(CROSS_COMPILE)objcopy
OBJDUMP   = $(CROSS_COMPILE)objdump
 
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
 
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
 
LDFLAGS := 
 
export CFLAGS LDFLAGS
 
TOPDIR := $(shell pwd)
export TOPDIR
 
TARGET := test
 
 
obj-y += main.o
obj-y += sub.o
obj-y += a/
 
 
all : start_recursive_build $(TARGET)
  @echo $(TARGET) has been built!
 
start_recursive_build:
  make -C ./ -f $(TOPDIR)/Makefile.build
 
$(TARGET) : start_recursive_build
  $(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

PHONY := __build
__build:
 
 
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
 
include 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)
 
# c/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))
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 : $(subdir-y) $(cur_objs)
  $(LD) -r -o $@ $(cur_objs) $(subdir_objs)
 
dep_file = .$@.d
 
%.o : %.c
  $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
  
.PHONY : $(PHONY)

2.子目录

 
EXTRA_CFLAGS := -D DEBUG
CFLAGS_sub3.o := -D DEBUG_SUB3
 
obj-y += sub2.o 
obj-y += sub3.o 

六、通用 Makefile 的解析

通用 Makefile 的设计思想

在 Makefile 文件中确定要编译的文件、目录,比如:

obj-y += main.o
obj-y += a/

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

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

怎么编译子目录? 进入子目录编译:

$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build

怎么编译当前目录中的文件?

%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

当前目录下的.o 和子目录下的 built-in.o 要打包起来:

built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^

顶层 Makefile 中把顶层目录的 built-in.o 链接成 APP:

$(TARGET) : built-in.o
    $(CC) $(LDFLAGS) -o $(TARGET) built-in.o

情景演绎


目录
相关文章
|
2月前
|
Linux C++
Linux c/c++之makefile的基础使用
Linux下C/C++项目中makefile的基本使用,包括基础、进阶和高级用法,以及如何创建和使用makefile来自动化编译过程。
18 0
Linux c/c++之makefile的基础使用
|
7月前
|
小程序 Linux 开发工具
【Linux】Linux 开发工具(vim、gcc/g++、make/Makefile)+【小程序:进度条】-- 详解
【Linux】Linux 开发工具(vim、gcc/g++、make/Makefile)+【小程序:进度条】-- 详解
|
6月前
|
Linux 开发工具 数据安全/隐私保护
Linux基础——Linux开发工具(下)_make/makefile
Linux基础——Linux开发工具(下)_make/makefile
48 1
|
5月前
|
Java Linux C++
【Linux】Make和Makefile快速入门
【Linux】Make和Makefile快速入门
32 0
|
6月前
|
Linux 芯片
Linux 驱动开发基础知识——查询方式的按键驱动程序_编写框架(十三)
Linux 驱动开发基础知识——查询方式的按键驱动程序_编写框架(十三)
70 2
|
5月前
|
Java Linux 编译器
编写和使用Linux Makefile
编写和使用Linux Makefile
|
6月前
|
Linux
【make/Makefile】Linux下进度条的设计与实现
【make/Makefile】Linux下进度条的设计与实现
|
6月前
|
NoSQL 编译器 Linux
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
91 0
|
7月前
|
Shell Linux 编译器
C语言,Linux,静态库编写方法,makefile与shell脚本的关系。
总结:C语言在Linux上编写静态库时,通常会使用Makefile来管理编译和链接过程,以及Shell脚本来自动化构建任务。Makefile包含了编译规则和链接信息,而Shell脚本可以调用Makefile以及其他构建工具来构建项目。这种组合可以大大简化编译和构建过程,使代码更易于维护和分发。
69 5
|
7月前
|
Linux Windows 编译器