Makefile配置

126 阅读5分钟

.h和.c文件的差别(在.h声明,.c中实现)

.h:声明一个文件/declare,int add(int a, int b),编译器会根据声明来判断你用的函数对不对 .c:实现/(define | implement),int add(int a, int b) { return a + b; }

.h文件在哪里

系统目录(工具链目录),也可以自己指定目录

printf在哪里

在库里面,库在哪里 => 可以在系统目录,也可以自己指定目录 image.png

Makefile

基本规则
目标 : 依赖1 依赖2 ...
[TAB]命令
编译和链接分开执行
gcc -c -o a.o a.c
gcc -c -o b.o b.c
链接:
gcc -o test a.o b.o

比如:上面的例子,当我们修改a.c之后,a.c会重现编译然后再把它们链接在一起就可以了。b.c就不需要重新编译。
那么问题又来了,怎么知道哪些文件被更新了/被修改了?
比较a.o和a.c的时间,如果a.c的时间比a.o的时间更加新的话,就表明.ac被修改了,同理,a.c和a.o也是这样。a.o或者b.o的时间比test更加新,就表明需要重新生成test文件。

当“依赖”比“目标”新,执行它们下面的命令。我们要把上面三个命令写成makefifile规则,如下:
testa.o b.o //test是目标,它依赖于a.o b.o文件,一旦a.o或者b.o比test新的时候,
就需要执行下面的命令,重新生成test可执行程序。
gcc -o test a.o b.o
a.o : a.c //a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o
gcc -c -o a.o a.c
b.o : b.c //b.o依赖于b.c,当b.c更加新的话,执行下面的命令,来生成b.o
gcc -c -o b.o b.c

Makefile基本语法

自动变量

  • $@:目标
  • $^:所有目标依赖
  • $<:目标依赖列表中的第一个依赖
  • $?:所有目标依赖中被修改过的文件
    test: a.o b.o
        gcc -o test $^
    %.o: %.c 
        gcc -c -o $@ $<

假想目标

image.png image.png

变量(即时变量,延时变量)

即时变量:
A := xxx; // A的值即可就可以确定,定义时就已经确定
延时变量:
B = xxx; // B的值使用时才确定

image.png
E还没有赋值,所以即时变量拿到的值是空,延时变量就可以拿到abc这个值 image.png

:= 即时变量
= 延时变量
?= 延时变量, 如果是第一次定义才会有效,如果在前面该变量已定义,则忽略这句
+= 附加,及时变量还是延时变量取决于前面的定义 (C = ABC C += 123),此时就是延时变量

条件赋值?=

条件赋值是指一个变量如果没有被定义过,就直接给它赋值;如果之前被定义过,那么这条赋值语句就什么都不做。如下面的语句:

CC = gcc
CC ?= arm-linux-gnueabi-gcc
$(BIN): $(OBJS)
@echo $(CC)
$(CC) -o $(BIN) $(OBJS)

当make解析Makefile,遇到上面的条件赋值语句时,因为CC已经被定义过而且被赋值,所以这个条件语句就会什么都不做。在Makefile中使用echo (CC)打印变量CC的值,你会发现(CC)打印变量CC的值,你会发现(CC)的值是gcc

追加赋值

追加赋值是指一个变量,以前已经被赋值,现在想给它增加新的值,此时可以使用+=追加赋值。如下面的语句:

OBJS = hello.o
OBJS += module.o
就等价于:
OBJS = hello.o module.o

关闭执行的命令打印

如果你不想在make编译的时候打印正在执行的命令,可以在每条命令的前面加一个@

C := $(E)
D = $(E)
E = abc
alls
    @echo $(C)
    @echo $(D)

#环境变量 image.png

通过export传递变量

image.png

条件判断

ifeq 关键字

ifeq关键字用来判断两个参数是够相等,相等时条件成立为true,不相等为false。ifeq一般和变量结合使用:

mode = debug
hello: hello.c
ifeq ($(mode),debug)
    @echo "debug mode"
    gcc -g -o hello hello.c
else
    @echo "release mode"
    gcc -o hello hello.c
endif

image.png

ifneq 关键字

image.png

ifdef 关键字

image.png

ifndef 关键字

image.png

用户自定义函数

image.png

make提供了一系列文本处理函数:subst、patsubst、strip、findstring、filter、filer-out、sort、word、wordlist、words、fistword。

www.zhaixue.cc/makefile/ma…

函数

list = a b C 
name = $(foreach item, $(list), $(item).o)
foreach:
    @echo name = $(name)

执行:make foreach image.png

filter_list = a b c d.c
filter_value = $(filter %.c, $(filter_list))
filter_not_value = $(filter-out %.c, $(filter_list))
filter:
    @echo filter_value = $(filter_value)
    @echo filter_not_value = $(filter_not_value)

image.png

files = $(wildcard *.c)
file2 = a.c b.c c.c d.c e.c # 判断那些文件真实存在
file3 = $(wildcard $(file2))
wildcard: 
    @echo files = $(files)
    @echo file2 = $(file3)

image.png

file2 = a.c b.c c.c d.c e.c
dep_file = $(patsubst %.c, %.d, $(file2))   # 替换并且生成一个新文件
patsubst: 
    @echo dep_file = $(dep_file)

image.png

打印依赖文件
gcc -M a.c
把依赖写入文件c.d
gcc -M -MF c.d c.d
编译c.o,把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d

makefie文件可以写成这样
gcc -c -o $@ $< -MD -MF .$@.d // .c$@.d表示隐藏文件

解决包含.h的编译修改不重新编译的问题

image.png

image.png

#解决make时包含.h的编译修改不重新编译的问题
objs = a.o b.o
dep_file := $(patsubst %, .%.d, $(objs)) 
dep_file := $(wildcard $(dep_file)) 
test: $(objs) 
    gcc -o test $^
    echo $(dep_file)
    
ifneq ($(dep_file),) # dep_file != 空 
include $(dep_file) 
endif 
%.o: %.c 
    gcc -c -o $@ $< -MD -MF .$@.d 
clean_dep: 
    rm $(dep_file) $(objs) 
.PHONY: clean_dep
添加CFLASS
CFLASS = -Werror # 警告变错误
gcc $(CFLASS) -c -o $@ $< -MD -MF .$@.d
CFLASS = -Werror -Iinclude #指定include文件夹为默认的头文件文件夹
自定义的.h文件可以写成 #include
gcc $(CFLASS) -c -o $@ $< -MD -MF .$@.d

通用makefile使用

使用说明:

本程序的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"来彻底清除
   

顶层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 # 头文件:当前文件夹下面的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) : 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

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 : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

dep_file = .$@.d

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