Makefile语法

  1. Makefile语法
    1. 程序编译过程
    2. 常用函数
    3. 特殊符号
    4. 示例
      1. 编译带头文件的项目
      2. 编译链接静态库
      3. 编译链接动态库

Makefile语法

程序编译过程

编译是将源代码转换为计算机可执行的指令集的过程。这个过程可以分为多个阶段,通常包括以下步骤:

  1. 预处理(Preprocessing) -> .i文件 :
    在这一阶段,预处理器会处理源代码,执行诸如宏替换、头文件包含等操作。预处理器会根据预处理指令(以#开头)来修改源代码。例如,#include指令用于包含头文件,#define用于定义宏等。

  2. 编译(Compilation) -> .s文件 :
    编译器将预处理后的源代码转换为汇编代码。编译器会将高级语言(如C、C++、Java等)转换成汇编语言或者直接转换成机器语言。

  3. 汇编(Assembly) -> .o文件 :
    汇编器将汇编代码翻译成机器语言指令,生成目标文件。目标文件通常是二进制文件,包含了可执行代码、数据、符号表等信息。

  4. 链接(Linking) -> 可执行文件 :
    链接器将目标文件与库文件进行链接,生成最终的可执行文件。链接器会解析目标文件中使用的符号(如函数、变量),并将其与库文件中的定义进行匹配。如果符号没有找到定义,链接过程会失败。

常用函数

  1. 替换字符串:

    # substitute 替换
    # $(subst from,to,text)
    NEW_TEXT := $(subst old,new,old_text_is_here)
    
  2. 模式替换:

    # $(patsubst pattern,replacement,text)
    NEW_TEXT := $(patsubst %.c,%.o,source1.c source2.c)
    
  3. 通配符展开:

    # $(wildcard pattern)
    ALL_SOURCE_FILES := $(wildcard *.c)
    
  4. 执行 shell 命令:

    # $(shell command)
    DATE := $(shell date)
    
  5. 循环迭代:

    # $(foreach var,list,text)
    OBJ_FILES := $(foreach src,$(SRC_FILES),$(src:.c=.o))
    
  6. 条件判断:

    # $(if condition,true-part,false-part)
    TARGET := $(if $(DEBUG),debug_release,release)
    
  7. 自定义函数:

    # $(call variable,param,param,...)
    define my_function
        echo "Hello, $(1)"
    endef
    
  8. 去除字符串两端的空格:

    # $(strip string)
    STR := $(strip   hello   )
    
  9. 对列表进行排序:

    # $(sort list)
    SORTED_LIST := $(sort $(UNSORTED_LIST))
    
  10. 过滤列表中符合指定模式的元素:

    # $(filter pattern,list)
    OBJ_FILES := $(filter %.o,$(ALL_FILES))
    
  11. 给列表中的每个元素添加前缀:

    # $(addprefix prefix,names)
    FULL_PATHS := $(addprefix $(SRC_DIR)/,$(SRC_FILES))
    
  12. 给列表中的每个元素添加后缀:

    # $(addsuffix suffix,names)
    ALL_LIBS := $(addsuffix .a,$(LIB_NAMES))
    
  13. 使用 dir 函数提取目录部分:

    # $(dir filepath)
    DIR := $(dir $(FILE_PATH))
    
  14. 使用 notdir 函数剔除目录部分:

    # $(notdir filepath)
    FILENAME := $(notdir $(FILE_PATH))
    
  15. 使用 basename 提取文件名部分,不包含扩展名:

    # $(basename filepath)
    BASENAME := $(basename $(FILE_PATH))
    

特殊符号

在 Makefile 中,有一些特殊的符号和变量,它们具有特殊的含义或用途。以下是一些常见的特殊符号和变量:

  1. $@:
    表示规则的目标文件名。

  2. $<:
    表示规则中的第一个依赖文件名。

  3. $^:
    表示规则中的所有依赖文件名,以空格分隔。

  4. $?:
    表示规则中所有比目标文件新的依赖文件名,以空格分隔。

  5. $*:
    表示规则中目标文件的主文件名(不带后缀)。

  6. $(@D):
    表示规则的目标文件所在的目录路径。

  7. $(@F):
    表示规则的目标文件的文件名(不包含路径)。

示例

编译带头文件的项目

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

编译链接静态库

“ar”命令是一种用于创建、更新和提取静态库(.a文件)的工具。它通常与”makefile”配合使用,在编译过程中自动创建或更新静态库。

常用的”ar”命令包括:

  • “ar rcs”:创建一个静态库并将文件添加到其中。
  • “ar d”:从静态库中删除文件。
  • “ar t”:列出静态库中的文件。
  • “ar x”:从静态库中提取文件。
cpp_srcs := $(filter-out src/main.cpp, $(shell find src -name *.cpp))
cpp_objs := $(patsubst src/%.cpp, objs/%.o, $(cpp_srcs))

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

compile_flags := -g -O3 -std=c++11 $(I_flag)

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

# ===============编译静态库===============
lib/libxxx.a : $(cpp_objs)
    @mkdir -p $(dir $@)
    @ar rcs $@ $^

static_lib : lib/libxxx.a

# ===============链接静态库===============
lib_path := ./lib \
            ../lib2
linking_libs := xxx
# 简化版遍历%=
L_opt := $(lib_path:%=-L%)
l_opt := $(linking_libs:%=-l%)

linking_flags := $(L_opt) $(l_opt)

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

编译链接动态库

  • g++ -share 生成动态库(链接过程)

  • 编译(生成.o)选项:**-fPIC**

    -fPIC选项用于告诉编译器生成位置无关的代码(Position Independent Code,PIC)。位置无关的代码是指在内存中加载时,它可以放置在任意位置而不受影响的代码。这对于动态链接库(共享库)特别重要,因为共享库可能会被加载到进程的内存空间的任意位置。

    生成位置无关的代码的主要目的是使得动态链接库更容易地与进程地址空间中的其他代码和数据一起工作。如果没有使用 -fPIC 选项,那么生成的代码将是位置相关的,它假定代码将被加载到固定的内存地址上,这会导致动态链接库在被加载到内存时需要进行重定位。

    使用 -fPIC 选项会导致编译器生成额外的代码,以确保代码中的所有引用都是相对地址而不是绝对地址。这些额外的代码包括加载时的全局偏移表(GOT,Global Offset Table)和过程链接表(PLT,Procedure Linkage Table),它们用于实现动态链接和重定位。

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

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

compile_flags := -g -O3 -w -fPIC $(I_flag)

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

# ===============编译动态库===============
lib/libxxx.so : $(cpp_objs)
    @mkdir -p $(dir $@)
    @g++ -share $^ -o $@

dynamic_lib : lib/libxxx.so

# ===============链接动态库===============
lib_path := ./lib \
            ../lib2
linking_libs := xxx
# 简化版遍历%=
L_opt := $(lib_path:%=-L%)
l_opt := $(linking_libs:%=-l%)

linking_flags := $(L_opt) $(l_opt)

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

扩展:

-Wl是 GCC 编译器中的一个选项,它允许将选项传递给链接器。通过 -Wl 选项,你可以将链接器选项传递给底层的链接器,以控制链接过程中的行为。

-rpath是用于指定运行时动态链接器在查找共享库时应该搜索的路径的选项。当你在链接时使用 -Wl,-rpath 选项时,实际上是将 -rpath 选项传递给底层的链接器。

通常,您不需要-rpath,事实上,最好不要在可执行文件中对库搜索路径进行编码(使用-rpath选项将路径编码为二进制,可以是DT_rpath或DR_RUNPATH)

注:我自己的一般方法是在可执行文件位于构建树中,并依赖于构建树中的其他库时,将其与–rpath选项链接,以便于调试,但在安装(make install,building packages)时,需要在不使用–rpath选项的情况下重新链接,并将查找共享库的任务留给目标平台的适当动态链接器配置,例如ld.so.conf。

我的问题
在GStreamer插件注册时候发现依赖的libgstaudiosink.so找不到,而实际上系统上是有的,检查环境变量后发现和rpath配置有关。

(gst-plugin-scanner:20804): GStreamer-WARNING **: 14:35:57.201: Failed to load plugin '/usr/lib/gstreamer-1.0/libgstwestonsink.so': libgstaudiosink.so: cannot open shared object file: No such file or directory

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1430797759@qq.com

文章标题:Makefile语法

字数:1.8k

本文作者:花落阁

发布时间:2024-05-11, 10:51:48

最后更新:2024-05-11, 15:27:41

原始链接:https://hualog.dns.navy/2024/05/11/Makefile%E8%AF%AD%E6%B3%95/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。