makefile 记录

开始先来一个Makefie例子:

CROSS = riscv64-unknown-elf-                                                                                                                                                                                       
cc = $(CROSS)gcc
Current_DIR=$(shell pwd)


INCLUDE_DIR+= -I $(Current_DIR)/../Include

C_FLAGS=$(Current_DIR)/Driver_USART.c

all:
    @cc $(INCLUDE_DIR) $(C_FLAGS) 
    

.PHONY: target 
target:
    @echo "$(Current_DIR)"
    @echo "$(INCLUDE_DIR)"
    @echo "$(C_FLAGS)"
~                      

一、Makefile 结构说明

src:源码
incl:头文件
bin:执行码
lib:静态/动态库

Makefile里主要包含了五个东西:变量定义、显式规则、隐晦规则、文件指示和注释。
1、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
2、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
3、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样。
5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符。

我们在写代码的时候不一定都是有自己来完成,一个工程中会大量使用一些比较优秀的动态库、静态库等,我们在使用这些库完成所有的代码后,需要在编译的时候将这些库使用的头文件添加到我们的工程上,将他的库文件也添加到我们的工程中,接下来我们就来看一下怎么来添加。我们在项目中很少将第三方库与我们自己的代码放到同一个目录中,而是有一些约定俗成的存放方法:将第三方的静态库放到lib文件夹,将动态库放到bin文件夹,

二、代码与函数分析

Makefile常用函数

  1. wildcard 自动搜索当前目录下的所有源文件~·
SRCS = $(wildcard ./*.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SRCS))    通过cpp文件获得与之同名的*.o类型的文件
$(OBJS):%.o : %.cpp  
    $(CXX) $(CFLAGS) $< -o $@  
当需要指定文件目录时:指定方法 - SRCS = $(wildcard ./src/*.cpp)
将*.o文件和*.cpp文件想对应起来(这里会默认看成一个循环,即一个*.o和一个*.cpp文件均对应,直到所有的cpp文件和*.o文件都编译完成)
  1. 增加第三方库和头文件
HEADER_PATH = -I./include/  
LIB_PATH = -L./lib/  
LIBS = -ldiv    
# LIBS = libdiv.a  
$(TARGET) : $(OBJS)  
    $(CXX) $^ -o $@ $(LIB_PATH) $(LIBS)
$(OBJS):%.o : %.cpp  
    $(CXX) $(CFLAGS) $< -o $@ $(HEADER_PATH)   


PS: $(CXX) $^  -o $@ $(LIB_PATH) $(LIBS),这是在最后链接是需要告诉编译器我的库放在了那个目录,以及该库的名字

LIBS = -ldiv的方式一般用于动态库,LIBS = libdiv.a适用于静态库加载,而且只有当libdiv.a与Makefile在同一目录下才可以,一般静态库使用将目录写死的方式,但是这样的话又不方便代码的传播

  1. 遍历当前目录和其子目录
SRC_PATH = ./src
DIRS = $(shell find $(SRC_PATH) -maxdepth 3 -type d)

# 为了更大幅度的支持项目的搭建,将三种文件格式的后缀都单独便利到变量中
SRCS_CPP += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cpp))
SRCS_CC += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cc))
SRCS_C += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.c))

OBJS_CPP = $(patsubst %.cpp, %.o, $(SRCS_CPP))
OBJS_CC = $(patsubst %.cc, %.o, $(SRCS_CC))
OBJS_C = $(patsubst %.c, %.o, $(SRCS_C))
  1. 去空格函数——strip。
    功能:去掉字串中开头和结尾的空字符。
    返回:返回被去掉空格的字符串值。
    示例:
    $(strip a b c )
    把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。
  1. 字符转换 tr
    $(shell echo $(CORE) | tr a-z A-Z)
    tr: 主要用于删除文件中控制字符或进行字符转换。使用tr时要转换两个字符串:字符串1用于查询,字符串2用于处理各种转换。

  2. ifneq
    ifeq ($(变量名), 变量值 )
    比如需要判断两个变量 VALUE1 和 VALUE2 的值都存在才执行某个动作,这需要逻辑与的判断 ifneq ($(VALUE1)$(VALUE2),).果变量 VALUE1 和 VALUE2 都有具体的值,比如需要进行这样的判断: VALUE1 == V1 && VALUE2 == V2, 可以按如下的写法; ifeq ($(VALUE1)_$(VALUE2), V1_V2) ### 当然中间的下划线 "_" 可以用其他字符代替

  3. 逗号字符串和空格字符串
    空格和逗号是makefile中常见的分隔符,但是如果要进行字符串处理,空格和逗号就不能直接出现了

  4. subset 字符串替换函数
    $(subst ;,;,;)

  5. words:单词统计函数

  6. word 取单词函数
    $(word 1,hello jello yello)
    上面的语句执行后的结果为hello,意为取字符串的第一个单词

  7. 减号
    有些命令会在开头加一个'-',作用是发生错误时Makefile继续。

  8. foreach 函数
    foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的。$(foreach ,,)。这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。每一次会返回一个字符串,循环过程中所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

names := a b c d
files := $(foreach n,$(names),$(n).o)

通过foreach遍历names,每个值存到n中,并通过表达式$(n).o输出,多个值则以空格隔开
(files)的值是“a.o b.o c.o d.o”。

foreach中的参数是一个临时的局部变量,foreach函数执行完后,参数的变量将不在作用,其作用域只在foreach函数当中

对于同样功能的for而言,

all:
names = a b c d
files := $(foreach n,$(names),$(n).o)
demo:
    cd subdir && \
    for x in $(files); do \
     echo $$a; \
    done

先进入subdir目录,然后遍历files中的每个值,输出【注意】要加空格\,这样会视为一条命令

三、 问题记录

  1. recipe for target xxx”错误,不妨先看看编码是否正确
  2. -T 选项用以指定自己的链接脚本
  3. -wl: makefile中一般默认的 lib 的加载路径是/lib /usr/lib 如果想要改变程序运行时的libs的加载路径 就需要用到 -wl , rpath 参数来添加lib 加载路径。
Question: 编译程序时得到undefined reference to 'xxxx'这样的错误提示 
Answer: 那你一定是缺少某个库,用 -l参数将库加入。Linux的库命名是一致的,一般为libxxx.so,或libxxx.a,libxxx.la,那么你要链接某个库就用-lxxx,去掉头lib及"."后面的so,la,a等即可。
数学库 -lm ; posix线程 -lpthread 
lc 是link libc
lm 是link libm
lz 是link libz
  1. -I:加载头文件路径.
  2. --gc-sections: 把每个函数作为一个section,每个数据(应该是指全局变量之类的吧)也作为一个section,这样链接的时候,--gc-sections会把没用到的section丢弃掉,最终的可执行文件就只包含用到了的函数和数据。
  3. -L:指定linker的路径
  4. 当没有办法打开链接脚本时,可能时因为linker的路径不对,这个时候就要找路径是否正确,是否能在正确的路径下寻找文件。
第一种是指定连接器LD的flag
arm-linux-ld -Ttext 0x0 -o led.elf $^(这句表示使用依赖编译链接生成led.elf目标文件,
编译链接工具为arm-linux-ld),链接地址依靠链接器的flag(Ttext)来指定,为0
第二种:依靠链接脚本来指定
如:arm-linux-ld -Tlink.lds -o led.elf $^,这句功能同上,只是链接地址依赖连接脚本link.lds来指定。
在连接脚本的开头以 .=xxx的形式指定连接地址。
  1. 特殊字符
$@  表示目标文件的完整名称。
$^  表示所有的依赖文件,以空格分开,不包含重复的依赖文件。
$<  表示第一个依赖文件
$?  所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。
$%  仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是“foo.a(bar.o)”,那么,“$%”就是“bar.o”,“$@”就是“foo.a”。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空
$+  这个变量很像“$^”,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$*  不包含扩展名的目标文件名称。
如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*”的值就是“foo”.
$$: 要表示它的字面意思则需要写两个$.两个$$在shell中表示当前进程的id,一般用来临时给文件起名字。      
      $$表示$,用来shell下引用变量,而$A或者$(A)则是Makefile的变量
$(1)$(2):有点类似于执行shell脚本中的第一个参数和第二个参数...


在写Makefile时需要查看源代码,找出它们依赖于哪些头文件,这很容易出错,
一是因为有的头文件包含在另一个头文件中,在写规则时很容易遗漏,
二是如果以后修改源代码改变了依赖关系,很可能忘记修改Makefile的规则。
为了解决这个问题,可以用gcc的-M选项自动生成目标文件和源文件的依赖关系:(gcc -M main.c)
-M选项把所有包含的系统头文件也找出来了,如果我们不需要输出系统头文件的依赖关系,可以用-MM选项


举例:
all: main
main: main.o stack.o maze.o
    gcc $^ -o $@
clean:
    -rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c
include $(sources:.c=.d)
%.d: %.c
    set -e; rm -f $@; \
    $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

解析:
1、sources变量包含我们要编译的所有.c文件,$(sources:.c=.d)是一个变量替换语法,
把sources变量中每一项的.c替换成.d,所以include这一句相当于
2、set -e; rm -f maze.d; \
    cc -MM  maze.c > maze.d.$$; \
    sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
    rm -f maze.d.$$
注意,虽然在Makefile中这个命令写了四行,但其实是一条命令,make只创建一个Shell进程执行这条命令,
这条命令分为5个子命令,用;号隔开,并且为了美观,用续行符\拆成四行来写。执行步骤为:
1. set -e命令设置当前Shell进程为这样的状态:如果它执行的任何一条命令的退出状态非零则立刻终止,不再执行后续命令。
2. 把原来的maze.d删掉5
3. 重新生成maze.c的依赖关系,保存成文件maze.d。1234
%.d: %.c  其实这里主要是为每个C文件建立一个同名的后缀为.d。该文件的作用是使用gcc的-M属性来自动生成.o文件的头文件依赖关系。
4. sed的s命令是替换命令。
sed是一个非交互式的流编辑器。流编辑器是指sed每次只从文件(或输入)读入一行,然后对该行进行指定的处理,并将结果输出到屏幕,接着读入下一行。

  1. GCC GCC参数详解
1、预处理,生成 .i 的文件[预处理器cpp]
2、将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]
3、有汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]
4、连接目标代码, 生成可执行程序 [链接器ld]
-x language filename: 设定文件所使用的语言,使后缀名无效。
-x none filename: 关掉上一个选项,让gcc根据后缀名自动识别文件类型
-c: 只激活预处理编译和汇编,做成obj文件(.o 的obj文件)
-C: 在预处理是,不删除注释信息,一般和-E使用,分析程序。
-S:只激活预处理和编译,做成汇编代码。
-E:只激活预处理,不生产文件,但是需要把它重定向到一个输出文件(gcc -E hello.c > hello.txt)
-o :制定目标名称,因为默认的名字是a.out
-include file: 包含某个代码,需要另外一个文件的时候使用
-ldir:在用#include "file" 时候,gcc会在当前目录查找头文件,如果没有找到,就在-I(大写i)目录下找。
-M: 生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用 gcc -M hello.c 来测试一下
-MM: 和上面一样,但是忽略由#include 造成的依赖关系,就是不显示出来。
-MD:和-M相同,但是输出将导入到.d的文件里面   
-MMD:和 -MM 相同,但是输出将导入到 .d 的文件里面。
-Wa,option:此选项传递 option 给汇编程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会汇编程序。
-Wl.option:此选项传递 option 给连接程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会连接程序
-llibrary:指定编译的时候使用的库:如gcc -lcurses hello.c,使用ncurses库编译程序
-Ldir:指定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然编译器将只在标准库的目录找。这个dir就是目录的名称。
-O0 、-O1 、-O2 、-O3:编译器的优化选项的 4 个级别,-O0 表示没有优化, -O1 为默认值,-O3 优化级别最高
常用的指令查看:
-ansi   只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。
-c  只编译并生成目标文件。
-DMACRO 以字符串"1"定义 MACRO 宏。
-DMACRO=DEFN    以字符串"DEFN"定义 MACRO 宏。
-E  只运行 C 预编译器。
-g: 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY   连接时搜索指定的函数库LIBRARY。
-m486   针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1    优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
-shared 生成共享目标文件。通常用在建立共享库时。
-static 禁止使用共享连接。
-UMACRO 取消对 MACRO 宏的定义。
-w  不生成任何警告信息。
-Wall:生成所有警告信息。

四、cmake

cmke与make
CMake与Make最简单直接的区别

makefile 记录_第1张图片

CMake 入门实战(精)

在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:
1、编写 CMake 配置文件 CMakeLists.txt 。
2、执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile ,其中, PATH 是 CMakeLists.txt 所在的目录。 ccmake 和 cmake 的区别在于前者提供了一个交互式的界面。
3、使用 make 命令进行编译。

4.1 编写

makefile 记录_第2张图片

4.2 编译

你可能感兴趣的:(makefile 记录)