Makefile

Makefile

0.基本介绍

0.0 基本格式

targets : prerequisties
[tab键]command
  • target:目标文件,可以是 OjectFile,也可以是执行文件,还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。
  • prerequisite:要生成那个 target 所需要的文件或是目标。
  • command:是 make 需要执行的命令

0.1 Makefile规则

  • make 会在当前目录下找到一个名字叫 Makefilemakefile 的文件
  • 如果找到,它会找文件中第一个目标文件(target),并把这个文件作为最终的目标文件
  • 如果 target 文件不存在,或是 target 文件依赖的 .o 文件(prerequities)的文件修改时间要比 target 这个文件新,就会执行后面所定义的命令 command 来生成 target 这个文件
  • 如果 target 依赖的 .o 文件(prerequisties)也存在,make 会在当前文件中找到 target 为 .o 文件的依赖性,如果找到,再根据那个规则生成 .o 文件

0.2 伪目标

为了避免 target 和 Makefile 同级目录下 文件/文件夹 重名的这种情况,我们可以使用一个特殊的标记 .PHONY 来显式地指明一个目标是 "伪目标",向 make 说明,不管是否有这个文件/文件夹,这个目标就是 "伪目标"

.PHONY : clean

只要有这个声明,不管是否有 "clean" 文件/文件夹,要运行 "clean" 这个目标,只有"make clean" 这个命令

伪目标:linux之Makefile 编写、规则、伪目标、变量 - sugarx - 博客园 (cnblogs.com)

1.makefile变量

1.1 变量的定义

cpp := src/main.cpp
obj := objs/main.o

1.2 变量的引用

# 可以用 `()` 或 `{}`
cpp := src/main.cpp 
obj := objs/main.o

$(obj) : ${cpp}
	@g++ -c $(cpp) -o $(obj)  # 命令前加@表示执行是不输出该条命令,正常会在命令窗中输出执行的指令

compile : $(obj)

1.3 预定义变量

  • $@: 目标(target)的完整名称
  • $<: 第一个依赖文件(prerequisties)的名称
  • $^: 所有的依赖文件(prerequisties),以空格分开,不包含重复的依赖文件
cpp := src/main.cpp
obj := objs/main.o

$(obj) : ${cpp}
	@g++ -c $< -o $@

compile : $(obj)
.PHONY : compile

2.Makefile常用符号

=

  • 简单的赋值运算符
  • 用于将右边的值分配给左边的变量
  • 如果在后面的语句中重新定义了该变量,则将使用新的值
  • 类似指针,传的是地址过去,而不是具体的那个值
HOST_ARCH   = aarch64
TARGET_ARCH = $(HOST_ARCH)

# 更改了变量 a
HOST_ARCH   = amd64

debug:
	@echo $(TARGET_ARCH)  # amd64

:=

  • 立即赋值运算符
  • 用于在定义变量时立即求值
  • 该值在定义后不再更改
  • 即使在后面的语句中重新定义了该变量
  • 类似值传递,copy了一份值过去
HOST_ARCH   := aarch64
TARGET_ARCH := $(HOST_ARCH)

# 更改了变量 a
HOST_ARCH := amd64

debug:
	@echo $(TARGET_ARCH)  # aarch64

?=

  • 默认赋值运算符
  • 如果该变量已经定义,则不进行任何操作
  • 如果该变量尚未定义,则求值并分配
HOST_ARCH  = aarch64
HOST_ARCH ?= amd64

debug:
    @echo $(HOST_ARCH)  # aarch64

+=

  • 累加
CXXFLAGS := -m64 -fPIC -g -O0 -std=c++11 -w -fopenmp

CXXFLAGS += $(include_paths)

\

  • 续行符
LDLIBS := cudart opencv_core \
          gomp nvinfer protobuf cudnn pthread \
          cublas nvcaffe_parser nvinfer_plugin 

* %

  • *: 通配符表示匹配任意字符串,可以用在目录名或文件名中
  • %: 通配符表示匹配任意字符串,并将匹配到的字符串作为变量使用

3.Makefile常用函数

函数调用,很像变量的使用,也是以 “$” 来标识的,其语法如下:

$(fn, arguments) or ${fn, arguments}
  • fn: 函数名
  • arguments: 函数参数,参数间以逗号 , 分隔,而函数名和参数之间以“空格”分隔

3.1 shell

$(shell <command> <arguments>)
  • 名称:shell 命令函数 —— shell
  • 功能:调用 shell 命令 command
  • 返回:函数返回 shell 命令 command 的执行结果
# shell 指令,src 文件夹下找到 .cpp 文件
cpp_srcs := $(shell find src -name "*.cpp") 
# shell 指令, 获取计算机架构
HOST_ARCH := $(shell uname -m)

3.2 subst

$(subst <from>,<to>,<text>)
  • 名称:字符串替换函数——subst
  • 功能:把字串 <text> 中的 <from> 字符串替换成 <to>
  • 返回:函数返回被替换过后的字符串
cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(subst src/,objs/,$(cpp_objs)) 

3.3 patsubst

$(patsubst <pattern>,<replacement>,<text>)
  • 名称:模式字符串替换函数 —— patsubst
  • 功能:通配符 %,表示任意长度的字串,从 text 中取出 patttern, 替换成 replacement
  • 返回:函数返回被替换过后的字符串
cpp_srcs := $(shell find src -name "*.cpp") #shell指令,src文件夹下找到.cpp文件
cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs)) #cpp_srcs变量下cpp文件替换成 .o文件

3.4 foreach

$(foreach <var>,<list>,<text>)
  • 名称:循环函数——foreach。
  • 功能:把字串<list>中的元素逐一取出来<var>,执行<text>包含的表达式
  • 返回:<text>所返回的每个字符串所组成的整个字符串(以空格分隔)
library_paths := /datav/shared/100_du/03.08/lean/protobuf-3.11.4/lib \
                 /usr/local/cuda-10.1/lib64

library_paths := $(foreach item,$(library_paths),-L$(item))

3.5 dir

$(dir <names...>)
  • 名称:取目录函数——dir。
  • 功能:从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
  • 返回:返回文件名序列的目录部分。
$(dir src/foo.c hacks)    # 返回值是“src/ ./”

3.6 notdir

$(notdir <names...>)
  • 函数的功能是从文件名序列 names 中取出非目录的部分。非目录的部分是最后一个反斜杠之后的部分。
libs   := $(notdir $(shell find /usr/lib -name lib*))

3.7 filter

$(filter pattern, text)
  • pattern 是用于匹配的模式,可以包含通配符%

  • text 是要进行筛选的单词列表或变量

libs    := $(notdir $(shell find /usr/lib -name lib*))
a_libs  := $(filter %.a,$(libs))
so_libs := $(filter %.so,$(libs))

3.8 basename

$(basename <names...>)
  • 从文件名序列 <names> 中取出各个文件名的前缀部分

  • 返回文件名序列 <names> 的前缀序列,如果文件没有前缀,则返回空字串。

libs    := $(notdir $(shell find /usr/lib -name lib*))
a_libs  := $(subst lib,,$(basename $(filter %.a,$(libs))))
so_libs := $(subst lib,,$(basename $(filter %.so,$(libs))))

3.9 filter-out

  • 剔除不想要的字符串
objs := objs/add.o objs/minus.o objs/main.o
cpp_objs := $(filter-out objs/main.o, $(objs))

3.10 wildcard

Makefile中wildcard的介绍 - haoxing990 - 博客园 (cnblogs.com)

4.Conditional Rules

4.1 ifeq/else/endif

# build flags
ifeq ($(TARGET_OS),darwin)
    LDFLAGS += -rpath $(CUDA_PATH)/lib
    CCFLAGS += -arch $(HOST_ARCH)
else ifeq ($(HOST_ARCH)-$(TARGET_ARCH)-$(TARGET_OS),x86_64-armv7l-linux)
    LDFLAGS += --dynamic-linker=/lib/ld-linux-armhf.so.3
    CCFLAGS += -mfloat-abi=hard
else ifeq ($(TARGET_OS),android)
    LDFLAGS += -pie
    CCFLAGS += -fpie -fpic -fexceptions
endif

4.2 ifneq/else/endif

HOST_ARCH   := $(shell uname -m)
TARGET_ARCH ?= $(HOST_ARCH)
temp := $(filter $(TARGET_ARCH),x86_64 aarch64 sbsa ppc64le armv7l)

ifneq (,$(filter $(TARGET_ARCH),x86_64 aarch64 sbsa ppc64le armv7l))
    ifneq ($(TARGET_ARCH),$(HOST_ARCH))
        ifneq (,$(filter $(TARGET_ARCH),x86_64 aarch64 sbsa ppc64le))
            TARGET_SIZE := 64
        else ifneq (,$(filter $(TARGET_ARCH),armv7l))
            TARGET_SIZE := 32
        endif
    else
        TARGET_SIZE := $(shell getconf LONG_BIT)
    endif
else
    $(error ERROR - unsupported value $(TARGET_ARCH) for TARGET_ARCH!)
endif

4.3 ifdef/else/endif

ifdef TARGET_OVERRIDE # cuda toolkit targets override
    NVCCFLAGS += -target-dir $(TARGET_OVERRIDE)
endif

5.Compile

5.1 编译过程

  • 预处理
cpp_srcs := $(shell find src -name *.cpp)
pp_files := $(patsubst src/%.cpp,src/%.i,$(cpp_srcs))

src/%.i : src/%.cpp
	@g++ -E $^ -o $@

preprocess : $(pp_files)

clean :
	@rm -f src/*.i

debug :
	@echo $(pp_files)
	
.PHONY : debug preprocess clean
  • 编译成汇编语言
cpp_srcs := $(shell find src -name *.cpp)
as_files := $(patsubst src/%.cpp,src/%.s,$(cpp_srcs))

src/%.s : src/%.cpp
	@g++ -S $^ -o $@

assemble : $(as_files)

clean :
	@rm -f src/*.s

debug :
	@echo $(as_files)

.PHONY : debug assemble clean
  • 编译成目标文件
cpp_srcs := $(shell find src -name *.cpp)
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))

objs/%.o : src/%.cpp
	@mkdir -p $(dir $@)
	@g++ -c $^ -o $@

objects : $(cpp_objs)

clean :
	@rm -rf objs src/*.o

debug :
	@echo $(cpp_objs)

.PHONY : debug objects clean
  • 链接成可执行文件
cpp_srcs := $(shell find src -name *.cpp)
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))


objs/%.o : src/%.cpp
	@mkdir -p $(dir $@)
	@g++ -c $^ -o $@

workspace/exec : $(cpp_objs)
	@mkdir workspace/exec
	@g++ $^ -o $@

run : workspace
	@./$<

clean :
	@rm -rf objs workspace/exec

debug :
	@echo $(as_files)

.PHONY : debug run clean

5.2 编译选项

编译选项

  • -m64: 指定编译为 64 位应用程序
  • -std=: 指定编译标准,例如:-std=c++11、-std=c++14
  • -g: 包含调试信息
  • -w: 不显示警告
  • -O: 优化等级,通常使用:-O3
  • -I: 加在头文件路径前
  • fPIC: (Position-Independent Code), 产生的没有绝对地址,全部使用相对地址,代码可以被加载到内存的任意位置,且可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的

链接选项

  • -l: 加在库名前面
  • -L: 加在库路径前面
  • -Wl,<选项>: 将逗号分隔的 <选项> 传递给链接器
  • -rpath=: "运行" 的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找

5.3 编译带头文件的程序

add.hpp

#ifndef ADD_HPP
#define ADD_HPP
int add(int a, int b);

#endif // ADD_HPP

add.cpp

int add(int a, int b)
{
    return a+b;
}

minus.hpp

#ifndef MINUS_HPP
#define MINUS_HPP
int minus(int a, int b);

#endif // MINUS_HPP

minus.cpp

int minus(int a, int b)
{
    return a-b;
}

main.cpp

#include <stdio.h>
#include "add.hpp"
#include "minus.hpp"

int main()
{
    int a=10; int b=5;
    int res = add(a, b);
    printf("a + b = %d\n", res);
    res = minus(a, b);
    printf("a - b = %d\n", res);

    return 0;
}

Makefile

cpp_srcs := $(shell find src -name *.cpp)
cpp_objs := $(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))

# 你的头文件所在文件夹路径(建议绝对路径)
include_paths := 
I_flag        := $(include_paths:%=-I%)


objs/%.o : src/%.cpp
	@mkdir -p $(dir $@)
	@g++ -c $^ -o $@ $(I_flag)

workspace/exec : $(cpp_objs)
	@mkdir -p $(dir $@)
	@g++ $^ -o $@ 

run : workspace/exec
	@./$<

debug :
	@echo $(I_flag)

clean :
	@rm -rf objs

.PHONY : debug run

6.Makefile静态库编译

6.1 程序

add.hpp

#ifndef ADD_HPP
#define ADD_HPP
int add(int a, int b);

#endif // ADD_HPP

add.cpp

int add(int a, int b)
{
    return a+b;
}

minus.hpp

#ifndef MINUS_HPP
#define MINUS_HPP
int minus(int a, int b);

#endif // MINUS_HPP

minus.cpp

int minus(int a, int b)
{
    return a-b;
}

main.cpp

#include <stdio.h>
#include "add.hpp"
#include "minus.hpp"

int main()
{
    int a=10; int b=5;
    int res = add(a, b);
    printf("a + b = %d\n", res);
    res = minus(a, b);
    printf("a - b = %d\n", res);

    return 0;
}

6.2 编译过程

  • 源文件[.c/cpp] -> Object文件[.o]

    g++ -c [.c/cpp][.c/cpp]... -o [.o][.o]... -I[.h/hpp] -g
    
  • Object文件[.o] -> 静态库文件[lib库名.a]

    ar -r [lib库名.a] [.o][.o]...
    
  • main 文件[.c/cpp] -> Object 文件[.o]

    g++ -c [main.c/cpp] -o [.o] -I[.h/hpp] 
    
  • 链接 main 的 Object 文件与静态库文件 [lib库名.a]

    g++ [main.o] -o [可执行文件] -l[库名] -L[库路径]
    

7.Makefile动态库(共享库)

7.1 编库

编译 .c 文件
源文件[.c/cpp] -> Object文件[.o]

g++ -c [.c/cpp][.c/cpp]... -o [.o][.o]... -I[.h/hpp] -g -fpic

Object文件[.o] -> 动态库文件[lib库名.so]

g++ -shared [.o][.o]... -o [lib库名.so] 

main文件[.c/cpp] -> Object文件[.o]

g++ -c [main.c/cpp] -o [.o] -I[.h/hpp] -g

7.2 链接

链接 main 的 Object 文件与动态库文件[lib库名.so]

g++ [main.o] -o [可执行文件] -l[库名] -L[库路径] -Wl,-rpath=[库路径]
posted @ 2024-09-14 09:43  ihuahua1415  阅读(12)  评论(0编辑  收藏  举报
*/