学习Makefile从头开始学之超详细知识点+例子及细节总结+附万能makefile

目录

1.makefile 之语法规则

2.makefile 之变量

3.makefile 之变量赋值

1、"="是最普通的等号,在Makefile中容易搞错赋值等号,使用 “=”进行赋值,变量的值是整个Makefile中最后被指定的值。

2、“:=” 表示直接赋值,赋予当前位置的值。

3、“?=” 表示如果该变量没有被赋值,赋值予等号后面的值。

4、"+="和平时写代码的理解是一样的,表示将符号后面的值添加到前面的变量上

4.makefile 之函数

5.makefile 之伪目标.PHONY

6.makefile 之常用命令

1.@echo:输出指定的文本到终端

2.@mkdir:创建目录

3.@rm:删除文件或目录

4.@cp:复制文件

5.@mv:移动文件

6.@cd:改变当前工作目录

7.@$(shell command):执行shell命令

8.@$(foreach var, list, text):遍历列表,并将每个元素替换为指定的文本

9.@$(if condition, text-if-true, text-if-false):根据条件选择输出指定的文本

10.@$(wildcard pattern):匹配指定模式的文件列表

补充说明:

7.Makefile 之嵌套执行

8.Makefile 之指定路径

1.指定头文件路径:一般都是通过"-I"(大写i)来指定

2.指定库文件路径:上面指定头文件类似只不过使用的是"-L"来指定

9.Makefile 之简单示例

10.细节巩固

1.【字符串处理函数】

2.【规则中的”%”】

3.【自动化变量】

4.【”@” 和 “-” 】

5.【调试】

6.【"-c"和"-o"】

7.【清空目标文件的规则】

11.附录:gcc 命令的常用选项及万能makefile


1.makefile 之语法规则

目标...:依赖...
TAB + 命令1
命令2
命令3
...

2.makefile 之变量

$^ 表示所有的依赖文件
$@ 表示生成的目标文件
$< 代表第一个依赖文件
//例1
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
 
ALL: hello.out
 
hello.out: $(OBJ)
        gcc $< -o $@
 
$(OBJ): $(SRC)
        gcc -c $< -o $@
//其中$@表示的是生成对应的目标文件
                                             

//例2
objs = ${main.o fun0.o fun1.o fun2.o fun3.o}
main:${objs} 
    gcc -o main ${objs} 
%.o:%.c
    gcc -c $<
clear:
    rm *.o
    
//注意:            
//gcc -c $< -o $@ 和 gcc -c $< 是编译源文件为目标文件的两种常用方式,它们之间有以下区别:
//gcc -c $< -o $@
//这个命令将会编译指定的源文件($<)并生成对应的目标文件($@)。
//使用 -o 选项可以指定输出的目标文件名。例如,如果源文件为 example.c,则生成的目标文件为 example.o。
//gcc -c $<
//这个命令将会编译指定的源文件,并使用默认的命名规则生成目标文件。默认情况下,GCC 会根据源文件的名称生成目标文件,将源文件的扩展名 .c 替换为 .o。
//例如,如果源文件为 example.c,则生成的目标文件为 example.o。
//总的来说,前者允许你显式地指定输出的目标文件名,而后者则使用默认的命名规则生成目标文件。
//选择哪种方式取决于你的需求和习惯。如果你想要手动指定目标文件名,可以使用 gcc -c $< -o $@,如果对生成的目标文件名没有特殊要求,可以简化为 gcc -c $<。

3.makefile 之变量赋值

1、"="是最普通的等号,在Makefile中容易搞错赋值等号,使用 “=”进行赋值,变量的值是整个Makefile中最后被指定的值。

VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA
 //经过上面的赋值后,最后VIR_B的值是AA B,而不是A B,在make时,会把整个Makefile展开,来决定变量的值

2、“:=” 表示直接赋值,赋予当前位置的值。

VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA
//最后BIR_B的值是A B,即根据当前位置进行赋值。因此相当于“=”,“:=”才是真正意义上的直接赋值

3、“?=” 表示如果该变量没有被赋值,赋值予等号后面的值。

VIR ?= new_value
//如果VIR在之前没有被赋值,那么VIR的值就为new_value。
VIR := old_value
VIR ?= new_value
//这种情况下,VIR的值就是old_value

4、"+="和平时写代码的理解是一样的,表示将符号后面的值添加到前面的变量上

4.makefile 之函数

下面是常用的GNU Make函数的格式、功能和例子:

下面是常用的GNU Make函数的格式、功能和例子:

wildcard函数:
格式:$(wildcard pattern)
功能:匹配文件名模式,返回匹配的文件列表。
例子:$(wildcard *.c)会返回当前目录下所有以.c为后缀的文件列表。

patsubst函数:
格式:$(patsubst pattern,replacement,text)
功能:模式替换函数,根据模式匹配替换文本中的字符串。
例子:$(patsubst %.c,%.o,$(SRC))将变量$(SRC)中的每个以.c为后缀的文件名替换为以.o为后缀的文件名。

subst函数:
格式:$(subst from,to,text)
功能:字符串替换函数,将文本中的字符串进行替换。
例子:$(subst old,new,$(VAR))将变量$(VAR)中的old替换为new。

strip函数:
格式:$(strip string)
功能:去除字符串两端的空格。
例子:$(strip $(VAR))会去除变量$(VAR)两端的空格。

shell函数:
格式:$(shell command)
功能:执行Shell命令,并返回其输出结果。
例子:$(shell echo "Hello, world!")会执行echo "Hello, world!"命令并返回结果。

foreach函数:
格式:$(foreach var,list,text)
功能:遍历列表,对每个元素执行一系列操作。
例子:$(foreach file,$(FILES),$(shell chmod +x $(file)))会将变量$(FILES)中的每个文件赋值给变量file,并将其设置为可执行文件。

if函数:
格式:$(if condition,true-part,false-part)
功能:条件判断函数,根据条件的真假返回不同的值。
例子:$(if $(strip $(VAR)),yes,no)根据变量$(VAR)是否为空格返回yes或no。

notdir函数:
格式:$(notdir names...)
功能:获取路径中的文件名部分。
例子:$(notdir $(SRC))会返回变量$(SRC)中所有文件名的基本部分。

dir函数:
格式:$(dir names...)
功能:获取路径中的目录部分。
例子:$(dir $(SRC))会返回变量$(SRC)中所有文件名所在的目录。

basename函数:
格式:$(basename names...)
功能:获取文件名中的基本部分(除去后缀)。
例子:$(basename $(notdir $(SRC)))会返回变量$(SRC)中所有文件名的基本部分(除去后缀)。

5.makefile 之伪目标.PHONY

使用示例:
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
 
ALL: hello.out
 
hello.out: $(OBJ)
        gcc $< -o $@
 
$(OBJ): $(SRC)
        gcc -c $< -o $@
 
clean:
        rm -rf $(OBJ) hello.out
 
.PHONY: clean ALL
作用:声明clean和ALL为伪目标,确保它们不与同名文件冲突

6.makefile 之常用命令

1.@echo:输出指定的文本到终端

@echo "Hello, World!"

2.@mkdir:创建目录

@mkdir -p build

3.@rm:删除文件或目录

@rm -f file.txt
@rm -rf build/

4.@cp:复制文件

@cp source.txt destination.txt

5.@mv:移动文件

@mv old.txt new.txt

6.@cd:改变当前工作目录

@cd path/to/directory && make

7.@$(shell command):执行shell命令

DATE := $(shell date +%Y-%m-%d)
@echo "Today's date is $(DATE)"

8.@$(foreach var, list, text):遍历列表,并将每个元素替换为指定的文本

FILES := file1.txt file2.txt file3.txt
TARGETS := $(foreach file, $(FILES), build/$(file))

$(TARGETS): build/%.txt: %.txt
    @cp $< $@

9.@$(if condition, text-if-true, text-if-false):根据条件选择输出指定的文本

DEBUG := true

ifeq ($(DEBUG), true)
    MESSAGE := "Debug mode"
else
    MESSAGE := "Release mode"
endif

@echo $(MESSAGE)

10.@$(wildcard pattern):匹配指定模式的文件列表

SOURCES := $(wildcard src/*.c)
OBJS := $(patsubst src/%.c, build/%.o, $(SOURCES))

$(OBJS): build/%.o: src/%.c
    @$(CC) $(CFLAGS) -c $< -o $@

补充说明:

用于编译、链接和执行程序,以及清理生成的目标文件常见的Makefile命令。

1.all:默认目标,编译并生成可执行文件。
2.clean:清理目标文件和可执行文件。
3.$(CC):编译器命令,默认为gcc。
4.$(CFLAGS):编译选项。
5.$(LDFLAGS):链接选项。
6.$(TARGET):生成的可执行文件名称。
7.$(OBJS):目标文件列表。
8.$(SRCS):源文件列表。

部分简单示例:

# 编译器设置
CC = gcc
CFLAGS = -Wall -g

# 目标文件和源文件列表
OBJS = main.o foo.o bar.o
SRCS = main.c foo.c bar.c

# 默认目标
all: $(OBJS)
    $(CC) $(LDFLAGS) -o $(TARGET) $(OBJS)

# 清理目标文件和可执行文件
clean:
    rm -f $(OBJS) $(TARGET)

# 生成目标文件
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

7.Makefile 之嵌套执行

  在一些大工程中,会把不同模块或不同功能的源文件放在不同的目录中,我们可以在每个目录中都写一个该目录的Makefile这有利于让我们的Makefile变的更加简洁,不至于把所有东西全部写在一个Makefile中。列如在子目录subdir目录下有个Makefile文件,来指明这个目录下文件的编译规则。外部总Makefile可以这样写:

subsystem:
            cd subdir && $(MAKE)
其等价于:
subsystem:
            $(MAKE) -C subdir

  定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较有利于维护。两个例子意思都是先进入"subdir"目录,然后执行make命令。

我们把这个Makefile叫做总控Makefile,总控Makefile的变量可以传递到下级的Makefile中,但是不会覆盖下层Makefile中所定义的变量,除非指定了 "-e"参数。

如果传递变量到下级Makefile中,那么可以使用这样的声明:export

export variable = value
等价于
variable = value
export variable
等价于
export variable := value
等价于
variable := value
export variable
//如果需要传递所有变量,那么只要一个export就行了。后面什么也不用跟,表示传递所有变量
//如果不想让某些变量传递到下级Makefile,可以使用:unexport

8.Makefile 之指定路径

1.指定头文件路径:一般都是通过"-I"(大写i)来指定

//假设头文件在:
/home/develop/include
//则可以通过-I指定:
-I/home/develop/include
//将该目录添加到头文件搜索路径中在Makefile中则可以这样写:
CFLAGS=-I/home/develop/include
//在编译的时候,引用CFLAGS即可:
yourapp:*.c
    gcc $(CFLAGS) -o yourapp

2.指定库文件路径:上面指定头文件类似只不过使用的是"-L"来指定

LDFLAGS=-L/usr/lib -L/path/to/your/lib
//告诉链接器要链接哪些库文件,使用"-l"(小写L)如下:
LIBS = -lpthread -liconv

9.Makefile 之简单示例

目录结构

include

myinclude.h

#include <stdio.h>
void print1() ;
void print2() ;

f1

f1.c

#include "../include/myinclude.h"                                                                              

void print1()  
{  
    printf("Message f1.c\n");  
    return;  
} 

Makefile

目标前面的路径,意思是将目标生成到指定的目录下

../$(OBJS_DIR)/f1.o:f1.c                                                                                       
    @$(CC) -c $^ -o $@  

f2

f2.c

#include "../include/myinclude.h"                                                                              

void print2()  
{  
    printf("Message f2.c\n");  
    return;  
} 

Makefile

../$(OBJS_DIR)/f2.o:f2.c                                                                                       
    @$(CC) -c $^ -o $@ 

main

main.c

#include "../include/myinclude.h"                                                                                            
 
int main(int argc, char const *argv[])
{
    print1();  
    print2();  
    return 0;
}

Makefile

../$(OBJS_DIR)/main.o:main.c                                                                                   
    @$(CC) -c $^ -o $@  

obj

此目录用来存放相关生成的目标文件

Makefile

../$(BIN_DIR)/$(BIN) : $(OBJS)
    @$(CC) $^ -o $@ 

主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)

bin

此文件用来存放生成的二进制文件

10.细节巩固

1.【字符串处理函数】

如 notdir, subst, strip, wildcard(准确地说这个不属于次列)等,
需要注意的一点是:makefile中字符串的表示和Shell有很大不同。
比如  am := xy abc 相当于shell中的 am="xy abc"。
变量的表示也不一样,在Makfile中 "$(am)" 和
shell中 "$am"或者"${am}"是一个意思!!不要忽略这个。

2.【规则中的”%”】

比如 "%.o:%.c",意思是说,xxx.o 需要xxx.c,
注意,如果他们不在一个路径下,记得改为"%.o:$(path)%.c",
这样才能找到.c代码。
换句话说,"%"匹配的是这个文件的名字,并不包括路径!

3.【自动化变量】

  $@ : 规则中的目标文件
  $<  : 规则中的第一个依赖文件名
  $^  :  规则中的所有依赖文件列表,以空格分隔

  注意:这些自动化变量是和上下文环境相关联的。

4.【”@” 和 “-” 】

在依赖对应的命令中,可以用"@"来表示该条命令本身不输出,仅输出结果;
用"-"来表示该命令执行如果不成功也继续执行!
需要注意的一点是:它们都只能用在一个命令的开头。
例如:-rm rf *.o 

5.【调试】

可以在make时加上参数:如 make -n –just-print等。
或者在makefile文件中加入 $(warning xxxx) ,
这个语句可以加到变量前,make过程中就会输出!

6.【"-c"和"-o"】

cc -c main.c 和 cc -o main.c 是两个不同的编译命令,具有不同的作用和效果。

cc -c main.c:这个命令表示将 C 语言源代码文件 main.c 编译为目标文件(Object File),
而不进行链接操作。
目标文件是二进制形式的机器码,包含了编译后的代码和数据,但没有进行最终的链接操作。
目标文件通常用于后续的链接步骤,将多个目标文件合并成一个可执行程序。

cc -o main.c:这个命令表示将 C 语言源代码文件 main.c 编译为可执行程序,并将其命名为 main.c。 
-o 是 cc 命令的一个选项,用于指定输出文件的名称。
在这种情况下,main.c 文件不仅进行了编译,还进行了链接操作,
最终生成一个名为 main.c 的可执行程序文件。

因此,两者的区别在于 -c 选项只进行编译操作,生成目标文件;
而 -o 选项除了编译操作外,还进行链接操作,生成最终的可执行程序文件。

7.【清空目标文件的规则】

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,
这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”。

  一般的风格都是:
   clean:

       rm edit $(objects)
       
  更为稳健的做法是:
   .PHONY : clean

   clean :

           -rm edit $(objects)
           
前面说过,.PHONY意思表示clean是一个“伪目标”。
而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。
当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。
不成文的规矩是——“clean从来都是放在文件的最后”。                     

11.附录:gcc 命令的常用选项及万能makefile

万能Makefile

####################################################
# Generic makefile - 万能Makefile
# for compiling and linking C++ projects on Linux 
# Author: George Foot  Modified:Jackie Lee
####################################################
### Customising
#
# Adjust the following if necessary; EXECUTABLE is the target
# executable's filename, and LIBS is a list of libraries to link in
# (e.g. alleg, stdcx, iostr, etc). You can override these on make's
# command line of course, if you prefer to do it that way.
#
#
EXECUTABLE := main    # 可执行文件名
LIBDIR:=              # 静态库目录
LIBS :=               # 静 态   库 文 件 名
INCLUDES:=.           # 头文件目录
SRCDIR:=              # 除了当前目录外,其他的源代码文件目录
#
# # Now alter any implicit rules' variables if you like, e.g.:
 
CC:=g++
CFLAGS := -g -Wall -O3
CPPFLAGS := $(CFLAGS)
CPPFLAGS += $(addprefix -I,$(INCLUDES))
CPPFLAGS += -MMD
#
# # The next bit checks to see whether rm is in your djgpp bin
# # directory; if not it uses del instead, but this can cause (harmless)
# # `File not found' error messages. If you are not using DOS at all,
# # set the variable to something which will unquestioningly remove
# # files.
#

RM-F := rm -f 
 
# # You shouldn't need to change anything below this point.
#
SRCS := $(wildcard *.cpp) $(wildcard $(addsuffix /*.cpp, $(SRCDIR)))
OBJS := $(patsubst %.cpp,%.o,$(SRCS))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.cpp,$(MISSING_DEPS)))

.PHONY : all deps objs clean veryclean rebuild info

all: $(EXECUTABLE)

deps : $(DEPS)

objs : $(OBJS)

clean :
        @$(RM-F) *.o
        @$(RM-F) *.d
veryclean: clean
        @$(RM-F) $(EXECUTABLE)

rebuild: veryclean all
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
        @$(RM-F) $(patsubst %.d,%.o,$@)
endif
-include $(DEPS)
$(EXECUTABLE) : $(OBJS)
        $(CC) -o $(EXECUTABLE) $(OBJS) $(addprefix -L,$(LIBDIR)) $(addprefix -l,$(LIBS))

info:
        @echo $(SRCS)
        @echo $(OBJS)
        @echo $(DEPS)
        @echo $(MISSING_DEPS)
        @echo $(MISSING_DEPS_SOURCES)

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
CMake是一个跨平台的构建工具,用于构建C ++代码的工具,同时支持多个平台和编译器。相比于传统的Makefile,CMake提供了更加简洁、易于维护、易于理解的构建方式,而且相对于Makefile的生成方式,Cmake的生成方式更加容易自动化,很适合在大型项目中使用。对于想要深入学习CMake的用户,可以参考一些著名的CMake书籍,如《CMake Cookbook》,该书针对不同的用例提供了详细的使用指南;《Mastering CMake》讨论了如何使用CMake来构建C ++和C软件;《Professional Cmake: A Practical Guide》则强调了CMake的最佳实践和如何解决常见问题。 而Makefile则是UNIX及其衍生系统常见的程序构建工具,主要用于通过执行一个包含编译规则的脚本自动化构建过程。如果想要深入了解Makefile的使用,可以先了解一下GNU make的语法规则,很多的Makefile编写风格比较接近于GNU make的语法。相关的书籍也很丰富,如《Managing Projects with GNU Make, Third Edition》,该书对GNU make的使用做了全面的介绍,从基础概念到高级特性等方面都有所讲解;《跟我一起写Makefile》则是一本为Makefile初学者准备的教程,通过实例讲解了什么是Makefile以及如何使用Makefile来构建项目。 总之,CMake和Makefile都是重要的构建工具,在程序员的工作中扮演着不可替代的角色。随着工具的不断发展,它们的使用也越来越普及,相应地,相关的书籍越来越多,选择一本适合自己的书籍,深入学习这两种工具,相信可以让自己的构建工作更加高效、便捷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值