文章目录
- 0. 环境准备
- 1. 子命令
- create - 快速创建项目
- build - 构建程序
- config - 配置编译需要的参数
- show - 查看当前工程基本信息
- update - 程序自更新
- 2. C/C++ 项目常用配置
- 2.1 项目目标类型
- 2.2 添加宏定义
- 2.3 头文件路径和链接库配置
- 2.4 设置语言标准
- 2.5 设置编译优化
- 2.6 添加源文件
- 3. C/C++ 依赖库
- 3.1 直接使用系统库
- 3.2 自动查找系统库
对于需要详细解释的请参考官网,下面红框位置可全局搜索关键字。
对于本系列,建议搭配官网食用。
👉🔗xmake 使用文档官方网址
0. 环境准备
- 系统环境:centos7
- xmake安装:
- 升级当前的 git
sudo add-apt-repository ppa:git-core/ppa sudo apt update sudo apt install -y git
- xmake 安装
bash <(curl -kfsSL https://labfile.oss.aliyuncs.com/courses/2764/get.sh) v2.3.7 source ~/.xmake/profile #如果是在当前终端下继续操作使用 xmake,需要关联 xmake 环境,否则会提示 xmake 找不到,打开新的终端不需要这步。
- 安装验证
xmake --version
- 升级当前的 git
1. 子命令
xmake语法格式:
xmake [task] [options] [target]
- task:子命令任务名,xmake 提供了很多的内置子命令以及插件任务子命令,
部分子命令有简写,在帮助菜单中查看,本文中不再对子命令的简写形式重复解释。xmake --help
可以在主菜单里面查看具体有哪些子命令,
xmake 子命令 --help
就是子命令的帮助菜单。
create - 快速创建项目
快速创建项目,生成可运行最小项目。
-l
:参数指定创建工程的语言
build - 构建程序
默认命令,与仅仅执行 xmake 等价。
-v/--verbose
:查看详细完整的编译命令。-r/--rebuild
:强制重新编译所有代码。-j/--jobs
:指定多任务编译的并行任务数。-w/--warning
:编译过程中显示编译警告信息。
通过添加 -v 参数,在编译过程中,查看完整的编译选项,这是非常有用的,可以排查和确认设置的编译选项是否生效,和 -r 一起使用可以写做 -rv
。
config - 配置编译需要的参数
配置编译需要的参数,比如平台、架构等。
-m
:切换编译模式,如 debug、release(默认)…xmake f -m debug
-o
:切换编译输出目录。
默认编译 xmake 会在当前项目根目录下生成 build 子目录作为编译输出目录,如果不想生成到当前目录下,我们可以通过下面的配置命令切换到其它输出目录下。xmake f -o /tmp/build
-v
:查看详细配置信息。--cflags
:仅仅添加 C 编译选项。
--cxxflags
:仅仅添加 C++ 编译选项。
--cxflags
:同时添加 C/C++ 编译选项。
一般如果我们设置编译flags传给gcc/clang,都是要配置:-DXXX
,也可以浏览后文使用代码的方式配置(add_defines()
)xmake f --cxflags="-DXXX" #添加C/C++编译选项,为其添加宏定义"XXX" xmake -rv #输出详细编译命令,显示"-DXXX" 说明我们添加的 XXX 宏定义确实传入了编译器
-ldflags
:添加链接库和搜索路径。
另外,我们也可以通过xmake f --ldflags="-L/tmp -lpthread" #添加额外的 pthread 链接库,同时新增了 /tmp 的库搜索目录 xmake -rv #输出详细编译命令,显示新增库和新增搜索目录
--links
和--linkdirs
达到同样的效果。xmake f --links="pthread" --linkdirs="/tmp"
-c
:清空编译配置,回到初始默认状态。
show - 查看当前工程基本信息
-
查看当前工程的基本信息,以及 xmake 自身的一些基本信息
[admin@hehe hello]$ xmake show The information of xmake:version: 2.9.5+20241008 #xmake版本号host: linux/x86_64 #当前主机平台架构programdir: /home/admin/.local/share/xmake #xmake的lua脚本路径programfile: /home/admin/.local/bin/xmake #xmake的主程序路径globaldir: /home/admin/.xmake # 全局配置存储目录tmpdir: /tmp/.xmake1001/241008 # 临时目录workingdir: /home/admin/Documents/Code/hello # 当前工作目录packagedir: /home/admin/.xmake/packages #C/C++依赖包的安装和packagedir(cache): /home/admin/.xmake/cache/packages/2410The information of project: plat: linux #目标编译平台arch: x86_64 #目标编译架构mode: release #目标编译模式buildir: build #编译输出目录configdir: /home/admin/Documents/Code/hello/.xmake/linux/x86_64 # 配置缓存目录projectdir: /home/admin/Documents/Code/hello #当前工程根目录projectfile: /home/admin/Documents/Code/hello/xmake.lua #当前工程xmake.lua路径
-
-t
:显示指定 target 目标的基本信息,查看 xmake.lua 文件中定义的 target(“hello”) 目标程序的基本信息。[admin@hehe hello]$ tree . ├── build │ └── linux │ └── x86_64 │ ├── debug │ │ └── hello │ └── release │ └── hello ├── src │ └── main.cpp └── xmake.lua6 directories, 4 files [admin@hehe hello]$ xmake show -t hello The information of target(hello):at: /home/admin/Documents/Code/hello/xmake.luakind: binarytargetfile: build/linux/x86_64/debug/hello rules:-> mode.debug -> ./xmake.lua:1-> mode.release -> ./xmake.lua:1files:-> src/*.cpp -> ./xmake.lua:5compiler (cxx): /bin/gcc-> -m64linker (ld): /bin/g++-> -m64compflags (cxx):-> -m64linkflags (ld):-> -m64
update - 程序自更新
快速更新版本至最新。
-f/--force
:强制更新
2. C/C++ 项目常用配置
2.1 项目目标类型
类型关键字 | 类型 |
---|---|
binary | 二进制程序 |
static | 静态库程序 |
shared | 动态库程序 |
phony | 空目标程序 |
可执行程序
set_kind()
,就可以设置目标编译类型。一个最简单的工程描述如下。
target("hello") #目标项目set_kind("binary") #目标类型add_files("src/*.c") #添加源文件
静态库程序
想要编译生成一个带有静态库的目标程序,可以这样建工程。
xmake create -t static hello_static
# -t static 参数用于指定当前工程类型为静态库
执行入后,xmake 会生成如下文件,其中 interface.cpp 用作静态库编译,main.cpp 生成可执行程序。
查看xmake.lua的代码内容如下。
里面两个编译目标通过 target()
定义维护,一个是 static 静态库类型,一个是 binary 可执行程序类型,它们之间通过 add_deps("hello_static")
进行依赖关联,这样 xmake 在编译的时候,会优先编译依赖的静态库,并且把静态库自动集成到对应的可执行程序上去。
输入xmake进行编译,详情如下。
动态库程序
与静态库类似,把类型名改成 shared 即可。
xmake create -t shared hello_shared
看看他的 xmake.lua,也是同样使用 set_kind() 设置类型,add_deps() 添加依赖。
输入xmake进行编译,详情如下。
Phony 目标类型
phony 是一个特殊的目标程序类型,使用它定义的 target 目标不实际生成任何文件,只是执行指定的操作。
通常用于组合其它目标程序的依赖关系,实现关联编译。
🌰这里举例:如下是我的项目结构
root/
├── build/
│ └── 中间文件...
├── include/
│ └── 头文件...
├── src/
│ └── 源文件...
├── project1/
│ ├── build/
│ │ ├── bin/ # project1 的最终可执行文件
│ │ └── obj/ # project1 的中间文件
│ └── xmake.lua # project1 的 xmake 配置
├── project2/
│ ├── build/
│ │ ├── bin/ # project2 的最终可执行文件
│ │ └── obj/ # project2 的中间文件
│ └── xmake.lua # project2 的 xmake 配置
└── xmake.lua # 根目录的 xmake 配置
如果是跟我的文章学习的同学,读到这里会有点超纲。也不要紧,笔者在此不会涉及过度的内容,只针对 phony 进行解释。
先提出一个小知识点,xmake.lua 在构建时会默认生成 build 文件夹,存放生成的 目标文件 和 过程文件,这个存放数据的文件夹当然是可以指定的。
例如示例中,子项目就指定了输出目录 root/project1/build/bin
和中间目录 root/project1/build/obj
。否则当在根目录下执行 xmake 时,build 文件夹会默认生成在根目录下。
然而当如上项目嵌套时,固然我们对子项目的输出路径做了设置,但根目录下仍然会生成 build 目录,存放所有子项目的中间文件。
对付这个多余的文件夹,就可以空虚构建一个 Target,执行我们想要的命令:保证在 proj1 和 proj2 完成构建后!删除根目录下的 build 目录。
root/xmake.lua
我们可以这样写:
-- 定义根项目
set_project("MyProjects")-- 包含子项目的构建文件
includes("project1/xmake.lua")
includes("project2/xmake.lua")-- 定义一个伪目标,确保在所有子项目完成后才执行清理任务
target("clean_build")set_kind("phony") -- 定义一个虚目标,不实际编译任何东西-- 将它依赖于所有子项目,以确保它最后执行after_build(function()if os.isdir("build") thenos.rm("build")print("Root build directory deleted.")endend)-- 设置此虚目标依赖于所有其他真实目标add_deps("proj1", "proj2")
其中 add_deps()
用来设置依赖关系的接口,after_build()
用于在构建后执行其中的脚本命令。
2.2 添加宏定义
回顾子命令 config,可以让我们在命令行中快速添加 C/C++ 的编译选项,但这样的方式无法永久保存,仅仅用于本地编译。如果我们想将自己的项目发布后提供给其他用户编译,那么就需要在 xmake.lua 中去设置这些编译选项。
比如我们之前举例用命令行给编译器提供一个宏定义,在xmake.lua中就要这样写了:
add_rules("mode.debug", "mode.release")target("hello")set_kind("binary")add_files("src/*.c")add_cxflags("-DTEST1")
除了可以通过 add_cxflags()
方式添加宏定义,其实 xmake 还提供了内置的配置接口 add_defines()
去更方便设置它。
这个配置接口相比 add_cxflags 使用原始编译选项的方式更加的通用,跨编译器,也是官方更加推荐的配置方式。这个接口会自动处理各种编译的支持方式,所以不会触发 flags 的自动检测机制,更加的快速可靠。
使用 add_defines() 添加新的宏定义开关 TEST2:
add_rules("mode.debug", "mode.release")target("hello")set_kind("binary")add_files("src/*.cpp")add_cxflags("-DTEST1")add_defines("TEST2")
可以通过执行 xmake -rv 去编译查看新增的宏定义是否生效。
2.3 头文件路径和链接库配置
-
add_includedirs()
:添加头文件搜索目录。 -
add_linkdirs()
:添加库搜索目录。 -
add_links()
:添加链接库。 -
add_syslinks()
:添加系统链接库。
上述几个接口都是跨编译器配置,相比直接设置到 ldflags 更加的通用。
-
add_syslinks
通常用于添加一些系统依赖库,比如 pthread,这样 xmake 会把这些系统库链接放置的更靠后些。 -
要知道链接器在处理库链接时候,是会依赖顺序的,放置在最左边的链接会优先处理,例如 libfoo 库依赖 libpthread 库中的符号,那么我们必须严格按照这个顺序添加链接库依赖 -lfoo -lpthread,否则链接器就会报找不到 pthread 库符号的错误。
add_syslinks
和 add_links
的区别: 更新 xmake.lua 的代码内容:
add_rules("mode.debug", "mode.release")
target("hello")set_kind("binary")add_files("src/*.cpp")add_cxflags("-DTEST1")add_defines("TEST2")add_links("z")add_syslinks("pthread")add_linkdirs("/tmp")add_includedirs("/tmp")
然后执行 xmake -rv 查看编译输出,里面的红框部分就是新设置的编译选项:
-I/tmp
:添加头文件搜索路径。-L/tmp
:添加链接库搜索路径。-lz -lpthread
:添加的链接库,由于我们是通过 add_links 添加的 zlib 库,而 pthread 库是作为系统库添加的,所以被放置在 pthread 的左边优先链接。
2.4 设置语言标准
set_languages()
:接口设置目标代码编译时候的语言标准,比如是基于 c99 标准,还是 c++11、c++14 标准等。
具体支持详情👉🔗官方:设置代码语言标准
C标准和 C++ 标准可同时进行设置,例如:
# 设置 c 代码标准:c99, c++ 代码标准:c++11
set_languages("c99", "c++11")
同样执行 xmake -v 编译输出结果能看到选项 -std=c++11 生效(当前案例没有C代码,所以只有C++11生效)。
2.5 设置编译优化
xmake 默认创建的工程 xmake.lua 文件中,已经设置了 add_rules("mode.debug", "mode.release")
这两个编译规则,而默认 xmake 编译就是 release 模式编译,它会开启所有内置的编译优化选项,并不需要用户设置什么。
上图红框的部分都是编译优化相关的一些选项,比如最直接的 -O3
优化,还有 -fvisibility=hidden
-fvisibility-inlines-hidden
用于去重一些符号字符串数据,使得编译后的程序更小。
当然,我们也可以不使用 xmake 提供的内置编译规则,自己控制应该如何优化编译:
...if is_mode("release") thenset_optimize("fastest")set_strip("all")set_symbols("hidden")end
通过 is_mode("release")
自己判断和控制优化编译,可以达到跟之前一样的优化效果。
set_optimize("fastest")
对应 -O3
的编译优化开关,
set_strip("all")
和 set_symbols("hidden")
用于去掉调试符号数据,使得程序更小。
配置完成以后再执行 xmake -v,最后的编译输出里面这些编译优化选项还是同样存在的。
2.6 添加源文件
add_files()
接口用于添加源文件。
该接口不仅仅支持单个源文件的添加,还可以支持通过模式匹配的方式批量添加源文件,并且支持同时添加不同语言的源文件,这里主要讲解对 C/C++ 源文件的添加。
举个例子:
add_files("src/test_*.c", "src/**.cpp")
其中通配符 *
表示匹配当前目录下源文件,而 **
则递归匹配多级目录下的源文件。
示例目录结构创建如下。
在结构复杂的源码目录层级中,难免需要排除一些不需要的文件,xmake 提供了在模式匹配过程中手动排除一批文件的方式,同样还是这个配置接口,我们只需要通过 |
符号指定后面的排除匹配模式即可,例如。
add_files("src/**.cpp|test/*.cpp|test1/*.cpp")
# 在 src/**.cpp 基础上,忽略掉其中 test 和 test1 子目录下所有的 C++ 源文件
# test 和 test1 可以作 test*,这里为了方便展示多个过滤模式
其中分隔符 |
之后的都是需要排除的文件,这些文件也同样支持匹配模式,并且可以同时添加多个过滤模式,只要中间用 |
分割就行了。
注意:为了使得描述上更加的精简,|
之后的过滤描述都是基于前一个模式:src/**.cpp
中 * 所在的目录作为根目录,也就是 src
目录的基础上来过滤匹配的,所以后面的过滤子模式只需要设置 test/*.cpp
而不是 。src/test/*.cpp
对 add_files 更细粒度的编译选项控制:
add_files("test/*.c", "test2/test2.c", {defines = "TEST2", languages = "c99", includedirs = ".", cflags = "-O0"})
可以在add_files的最后一个参数,传入一个配置table,去控制指定files的编译选项,里面的配置参数跟target的一致,并且这些文件还会继承target的通用配置。
支持添加未知的代码文件:
通过设置 rule 自定义规则,实现这些文件的自定义构建,例如:
add_files("src/test/*.md", {rule = "markdown"})
并且可以通过 force 参数来强制禁用 cxflags, cflags 等编译选项的自动检测,直接传入编译器,哪怕编译器有可能不支持,也会设置:
add_files("src/*.c", {force = {cxflags = "-DTEST", mflags = "-framework xxx"}})
3. C/C++ 依赖库
xmake 内置了对 C/C++ 的第三方包管理仓库的集成,另外 xmake 还有自己的包依赖仓库
来更好的解决依赖包集成问题。
3.1 直接使用系统库
这里用 zlib 库举例,首先创建一个快速工程。
xmake create -l c crc32
如果我们要在代码中使用 crc32()
接口,就要包含头文件 #include <zlib.h>
。
要想使用该接口,就要在 xmake.lua 中添加以下配置以找到这个系统库。
add_links("z")
add_linkdirs("/usr/lib/x86_64-linux-gnu")
引入 zlib 库,需要添加链接库、添加链接库搜索路径、添加头文件搜索路径。这里没有额外添加头文件搜索路径,是因为 zlib.h 是在 /usr/include 目录下,这个目录 xmake 会内置自动添加对应的 -I 选项到 gcc。
3.2 自动查找系统库
上小节通过 add_linkdirs()
方式来集成系统依赖库,这个方式弊端很大,因为带有 xmake.lua 的 C/C++ 项目有可能会在其它 Linux 环境甚至在其它系统上编译,zlib 库并不一定安装在这个目录下。为了更加通用化的适配系统库路径,推荐使用可以动态查找的接口:
2.6.x 以前的版本的做法
find_packages()
:自动查找系统库所在的位置。
find_packages 来自动查找 zlib 库所在的位置,通过这种方式集成之前,我们可以先尝试直接执行下面的命令,来探测 zlib 库的位置信息,对这个接口返回的信息有个大概的了解。
xmake l find_packages zlib
结构能得到链接库名、链接库的搜索路径,有可能还会有头文件的搜索路径等信息,因此我们只需要把这些信息自动探测到后设置到对应的目标程序中,就可以实现自动集成安装在系统环境的依赖库了。
在 xmake.lua 下添加代码,意为新增一个 on_load()
脚本块,在里面调用 find_packages()
,将探测到的 zlib 库信息直接动态设置到 target 里。
on_load(function (target)target:add(find_packages("zlib"))end)
修改完成后执行 xmake -rv,其编译输出应该跟之前的输出完全一致才对。
不过需要注意的一点是:xmake 默认会缓存依赖包的检测结果,并不是每次编译都会重新检测,如果之前检测失败,那么结果也会缓存,这个时候我们可以执行 xmake f -c
,在配置时候,忽略之前的缓存内容,就会自动重新触发各种检测。
现在的版本有更便捷的做法:
如果需要的库在当前环境中还没有安装,xmake 提供了依赖包的自动远程下载以及安装集成功能!!!
add_requires()
:接口指定当前项目需要哪些包,配置这个编译时候会触发一次依赖包的安装。add_packages()
:用来配置对特定 target 目标集成指定的依赖包,这两者需要配合使用,缺一不可。
add_requires("zlib", {system = false}) # 当前的实验环境中已经存在了 zlib 库,所以 add_requires("zlib") 默认会优先自动检测系统环境中的 zlib 库,如果存在就直接使用,也就是内置了 find_packages("zlib") 的逻辑。# 而这里出于演示远程下载的逻辑,配置 {system = false} 强制触发远程下载,人为忽略了系统库的探测逻辑,在实际项目中,可根据自己的需求来决定是否配置这个选项。
target("crc32")set_kind("binary")add_files("src/*.c")add_packages("zlib")
执行 xmake 重新来编译项目,如果安装和编译成功会有信息提示,说明我们配置的 zlib 包有在官方仓库中被收录,并且当前平台支持这个依赖包的集成。
另外,我们的包检测结果都是有本地缓存的,第二次编译并不会再触发依赖包的下载安装,会直接参与集成编译。如果再次执行 xmake -rv 重新编译,就不会再去安装 zlib 库了,不过同样可以正常集成 zlib 来重新编译项目。
对于下载的依赖包可以执行卸载命令进行卸载。
xmake require --uninstall zlib
同样,虽然卸载了包,但是当前项目的配置缓存还在,如果继续执行 xmake 命令,还是会使用缓存的 zlib 包信息。
因此可以执行下 xmake f -c 命令,强制忽略缓存配置,触发依赖包的重新检测逻辑。
集成第三方库中的包:
(TODO)