makefile 不会写,cmake 虽然常用,但文档太烂了,真不好用。前一段有同事说 xmake 不错,试试


安装

官方网站有脚本可以直接下载安装,这里是 github 下载的安装包

./xmake-v2.8.1.gz.run

测试

不会写makefile?没关系,直接在源码目录运行以下命令即可直接编译:

xmake
xmake -P .

xmake f -m release
xmake f -p cross --cross=arm-linux-gnueabihf- -m release

xmake build -j4
xmake install -o install/armhf
xmake uninstall
xmake clean

xmake会自动扫描在当前目录下的源码结构,生成一个xmake.lua工程描述文件,然后尝试直接编译。

想要直接运行编译后的可执行程序,简单,直接敲:

xmake run

创建项目

xmake create -l c++ -t qt.quickapp test
xmake create -l c -t test
xmake project -k vs2017 -m "debug,release"
xmake project -k makefile

静态库

xmake create -l c -t static test

动态库

xmake create -l c -t shared test

手工填写 xmake.lua

target("test")
    set_kind("binary") 
    add_files("src/*.c")
    
target("test") 
    set_kind("static") 
    add_files("src/*.c")
    add_files("lib/libxxx.a", "obj/bbb.o")
    
target("test")
    set_kind("binary") 
    add_files("src/*.c") 
    if is_mode("debug") then 
        add_cxflags("-DDEBUG") 
    end
    
target("test") 
    set_kind("binary") 
    add_files("src/*.c") 
    after_build(function (target) 
        os.exec("file %s", target:targetfile()) 
    end)

添加依赖

target("test") 
    set_kind("static") 
    add_files("src/test/*.c") 
target("hello") 
    add_deps("test") --添加依赖 
    set_kind("binary") 
    add_files("src/hello/*.c")

直接运行

xmake run

如果有多个target目标,你可以指定需要运行的target名,例如:

xmake run test

想要快速调试程序?加上-d参数即可

xmake run -d test

界面

xmake f --menu

可以显示指定 target 配置信息,可以看到各种配置来源于哪个配置文件和具体的行数。

xmake show -t <target>

检查工程配置和代码。

xmake check

xrepo项目

xmake create -l c++ test

xmake.lua 文件

add_rules("mode.debug", "mode.release")
add_requires("cereal")
target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("cereal")

github 无法下载,手工下载

mkdir lib && cd lib
wget -O cereal-1.3.2.tar.gz https://ghproxy.com/https://github.com/USCiLab/cereal/archive/refs/tags/v1.3.2.tar.gz

设置搜索库绝对路径

xmake --pkg_searchdirs="/mnt/d/Download/test/lib/"

编译

xmake

看起来可以,不过本地没有头文件,看起来也不太舒服

语义版本设置

Xmake 的依赖包管理是完全支持语义版本选择的,例如:”~1.6.1”,对于语义版本的具体描述见:https://semver.org/

比如下面一些语义版本写法:

add_requires("tbox 1.6.*", "pcre 1.3.x", "libpng ^1.18") 
add_requires("libpng ~1.16", "zlib 1.1.2 || >=1.2.11 <1.3.0")

当然,如果我们对当前的依赖包的版本没有特殊要求,那么可以直接这么写:

add_requires("tbox", "libpng", "zlib")

这会使用已知的最新版本包,或者是master分支的源码编译的包,如果当前包有git repo地址,我们也能指定特定分支版本:

add_requires("tbox master") 
add_requires("tbox dev")

Xmake 的语义版本支持,在几年前就已经很好的支持,而 vcpkg 也仅仅在最近一年才通过清单模式勉强支持它。

即使现在,vcpkg 对版本语义的支持也很受限,只能支持 >=1.0, 1.0 等几种版本模式,想要选择任意版本的包,比如 >=1.0 <1.5 等复杂版本条件的包,vcpkg 还是无法支持。

可选包设置

如果指定的依赖包当前平台不支持,或者编译安装失败了,那么 Xmake 会编译报错,这对于有些必须要依赖某些包才能工作的项目,这是合理的。 但是如果有些包是可选的依赖,即使没有也可以正常编译使用的话,可以设置为可选包:

add_requires("tbox", {optional = true})
使用系统库

默认的设置,Xmake 会去优先检测系统库是否存在(如果没设置版本要求),如果用户完全不想使用系统库以及第三方包管理提供的库,那么可以设置:

add_requires("tbox", {system = false})

而如果配置成:

add_requires("tbox", {system = true})

就是仅仅查找使用系统库,不会去远程下载安装它,这类似于 CMake 的 find_package,但是集成方式更加简单一致。

使用调试版本的包

如果我们想同时源码调试依赖包,那么可以设置为使用debug版本的包(当然前提是这个包支持debug编译):

add_requires("tbox", {debug = true})
启用包的可选特性

我们也可以安装带有指定特性的包,比如安装开启了 zlib 和 libx265 的 ffmpeg 包。

add_requires("ffmpeg", {configs = {zlib = true, libx265 = true}})
传递额外的编译选项

我们也可以传递额外的编译选项给包:

add_requires("spdlog", {configs = {cxflags = "-Dxxx"}})

独立的包管理命令 Xrepo

Xrepo 是一个基于 Xmake 的跨平台 C/C++ 包管理器。

它是一个独立于 Xmake 的命令程序,用于辅助用户去管理依赖包,类似 vcpkg/conan,但相比它们,有额外多了一些实用的特性,我们会简单介绍一些。

多仓库管理

除了可以直接从官方仓库:xmake-repo 检索安装包之外, 我们还可以添加任意多个自建的仓库,甚至可以完全隔离外网,仅仅在公司内部网络维护私有包的安装集成。

只需要通过下面的命令,添加上自己的仓库地址:

xrepo add-repo myrepo https://github.com/mygroup/myrepo

基本使用

xrepo install zlib tbox

安装指定版本包

完整支持 Semantic Versioning (语义版本)。

xrepo install "zlib 1.2.x" 
xrepo install "zlib >=1.2.0"

安装指定平台包

xrepo install -p iphoneos -a arm64 zlib 
xrepo install -p android [--ndk=/xxx] zlib 
xrepo install -p mingw [--mingw=/xxx] zlib 
xrepo install -p cross --sdk=/xxx/arm-linux-musleabi-cross zlib

安装调试版本包

xrepo install -m debug zlib

安装动态库版本包

xrepo install -k shared zlib

安装指定配置包

xrepo install -f "vs_runtime=MD" zlib 
xrepo install -f "regex=true,thread=true" boost

安装第三方包管理器的包

xrepo install brew::zlib 
xrepo install vcpkg::zlib 
xrepo install conan::zlib/1.2.11

查看包的库使用信息

xrepo fetch pcre2 { { linkdirs = { "/usr/local/Cellar/pcre2/10.33/lib" }, links = { "pcre2-8" }, defines = { "PCRE2_CODE_UNIT_WIDTH=8" }, includedirs = "/usr/local/Cellar/pcre2/10.33/include" } }

导入导出安装后的包

xrepo 可以快速导出已经安装后的包,包括对应的库文件,头文件等等。

xrepo export -o /tmp/output zlib

也可以在其他机器上导入之前导出的安装包,实现包的迁移。

xrepo import -i /xxx/packagedir zlib

搜索支持的包

xrepo search zlib "pcr*" 
    zlib: 
      -> zlib: A Massively Spiffy Yet Delicately Unobtrusive Compression Library (in xmake-repo) 
    pcr*:
      -> pcre2: A Perl Compatible Regular Expressions Library (in xmake-repo) 
      -> pcre: A Perl Compatible Regular Expressions Library (in xmake-repo)

另外,现在还可以从 vcpkg, conan, conda 以及 apt 等第三方包管理器中搜索它们的包,只需要加上对应的包命名空间就行,例如:

xrepo search vcpkg::pcre 
The package names: 
    vcpkg::pcre: 
      -> vcpkg::pcre-8.44
      #8: Perl Compatible Regular Expressions -> vcpkg::pcre2-10.35
      #2: PCRE2 is a re-working of the original Perl Compatible Regular Expressions library

包虚拟环境管理

我们可以通过在当前目录下,添加 xmake.lua 文件,定制化一些包配置,然后进入特定的包 shell 环境。

add_requires("zlib 1.2.11") 
add_requires("python 3.x", "luajit")

进行虚拟环境:

xrepo env shell 
python --version 
luajit --version

在 Xmake 中集成第三方构建系统

在 Xmake 中集成 Cmake 项目

Xmake 并不打算分裂 C/C++ 生态,它能很好和兼容复用现有 cmake/autoconf/meson 维护的项目,比如可以将一些其他使用 CMake 维护的代码库,直接本地集成进来,参与混合编译。

也就是说,Xmake 不会强制用户将所有的项目重新 port 到 xmake.lua,现有的 CMake 项目,一样可以快速集成到 Xmake 项目中去。

例如,我们有如下项目结构:

.
├── foo 
│ ├── CMakeLists.txt 
│ └── src 
│ ├── foo.c 
│ └── foo.h 
├── src 
│ └── main.c 
├── test.lua 
└── xmake.lua

foo 目录下是一个使用 CMake 维护的静态库,而根目录下使用了 Xmake 来维护,我们可以在 xmake.lua 中通过定义 package("foo") 包来描述如何构建 foo 代码库。

add_rules("mode.debug", "mode.release") 

package("foo") 
add_deps("cmake") 
set_sourcedir(path.join(os.scriptdir(), "foo")) 
on_install(function (package) 
local configs = {} 
table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:debug() and "Debug" or "Release")) 
table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF")) 
import("package.tools.cmake").install(package, configs) 
end) 

on_test(function (package) 
assert(package:has_cfuncs("add", {includes = "foo.h"})) 
end) 
package_end() 

add_requires("foo") 
target("demo") 
set_kind("binary") 
add_files("src/main.c") 
add_packages("foo")

其中,我们通过 set_sourcedir() 来设置 foo 包的代码目录位置,然后通过 import 导入 package.tools.cmake 辅助模块来调用 cmake 构建代码,xmake 会自动获取生成的 libfoo.a 和对应的头文件。

如果仅仅本地源码集成,我们不需要额外设置 add_urls 和 add_versions。

定义完包后,我们就可以通过 add_requires("foo") 和 add_packages("foo") 来集成使用它了,就跟集成远程包一样的使用方式。

另外,on_test 是可选的,如果想要严格检测包的编译安装是否成功,可以在里面做一些测试。

在 Xmake 中集成 Autoconf 项目

我们也可以使用 package.tools.autoconf 来本地集成带有 autoconf 维护的第三方代码库。

package("libev") 
set_sourcedir(path.join(os.scriptdir(), "3rd/libev")) 

on_install(function (package) 
import("package.tools.autoconf").install(package) 
end)

package.tools.autoconf 和 package.tools.cmake 模块都是可以支持 mingw/cross/iphoneos/android 等交叉编译平台和工具链的,xmake 会自动传递对应的工具链进去,用户不需要做任何其他事情。

在 Xmake 中集成 Gn 项目

我们也可以使用 package.tools.gn 来本地集成带有 GN 维护的第三方代码库。

package("skia") 
set_sourcedir(path.join(os.scriptdir(), "3rd/skia")) 
add_deps("gn", "ninja") 

on_install(function (package) 
import("package.tools.gn").install(package) 
end)

在 Xmake 中查找使用 CMake/C++ 包

现在 CMake 已经是事实上的标准,所以 CMake 提供的 find_package 已经可以查找大量的系统库和模块,我们也可以完全复用 CMake 的这部分生态来扩充 xmake 对包的集成。

只需要像集成 vcpkg/conan 包那样,将包命名空间改成 cmake:: 就可以了。

add_requires("cmake::ZLIB", {alias = "zlib", system = true}) 

target("test") 
set_kind("binary") 
add_files("src/*.c") 
add_packages("zlib")

我们指定 system = true 告诉 xmake 强制从系统中调用 cmake 查找包,如果找不到,不再走安装逻辑,因为 cmake 没有提供类似 vcpkg/conan 等包管理器的安装功能,只提供了包查找特性。

指定版本

add_requires("cmake::OpenCV 4.1.1", {system = true})

指定组件

add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"}})}

预设开关

add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"}, presets = {Boost_USE_STATIC_LIB = true}}})

相当于内部调用 find_package 查找包之前,在 CMakeLists.txt 中预定义一些配置,控制 find_package 的查找策略和状态。

set(Boost_USE_STATIC_LIB ON) 
-- will be used in FindBoost.cmake find_package(Boost REQUIRED COMPONENTS regex system)

设置环境变量

add_requires("cmake::OpenCV", {system = true, configs = {envs = {CMAKE_PREFIX_PATH = "xxx"}}})

指定自定义 FindFoo.cmake 模块脚本目录
mydir/cmake_modules/FindFoo.cmake

add_requires("cmake::Foo", {system = true, configs = {moduledirs = "mydir/cmake_modules"}})

在 Cmake 中集成 Xrepo 依赖包

除了可以在 Xmake 中集成 CMake 项目,我们也可以在 CMake 中直接集成 Xmake/Xrepo 提供的包,只需要使用 xrepo-cmake 提供的 CMake Wrapper。

例如:

cmake_minimum_required(VERSION 3.13.0) 
project(foo)

# Download xrepo.cmake if not exists in build directory. 
if(NOT EXISTS "${CMAKE_BINARY_DIR}/xrepo.cmake") 
    message(STATUS "Downloading xrepo.cmake from https://github.com/xmake-io/xrepo-cmake/") 

    # mirror https://cdn.jsdelivr.net/gh/xmake-io/xrepo-cmake@main/xrepo.cmake 
    file(DOWNLOAD "https://raw.githubusercontent.com/xmake-io/xrepo-cmake/main/xrepo.cmake" "${CMAKE_BINARY_DIR}/xrepo.cmake" TLS_VERIFY ON) 
endif() 

# Include xrepo.cmake so we can use xrepo_package function. 
include(${CMAKE_BINARY_DIR}/xrepo.cmake) 
xrepo_package("zlib") 
add_executable(example-bin "") 
target_sources(example-bin PRIVATE src/main.cpp ) 
xrepo_target_packages(example-bin zlib)
添加带有配置的包

我们,也可以跟在 Xmake 中一样,定制包的可选特性。

xrepo_package("gflags 2.2.2" CONFIGS "shared=true,mt=true") 
add_executable(example-bin "") 
target_sources(example-bin PRIVATE src/main.cpp ) 
xrepo_target_packages(example-bin gflags)
使用来自第三个存储库的包

除了从 Xmake 官方维护的存储库安装软件包之外,我们也可以直接在 CMake 中使用它来安装来自第三方仓库的包,只需将仓库名称添加为命名空间即可。

例如:vcpkg::zlib, conan::pcre2。

xrepo_package("conan::gflags/2.2.2") 
xrepo_package("conda::gflags 2.2.2") 
xrepo_package("vcpkg::gflags")
xrepo_package("brew::gflags")

通过这种方式,我们将在 CMake 中集成使用 vcpkg/conan 包的方式进行了统一,并且额外提供了自动包安装特性,以及对 homebrew/conda 等其他包仓库的支持。


xmake-vscode 插件

我们之前的所有实验,都是使用 xmake 的命令行程序在终端下操作完成的,这对于一些初学者来说还是有不少门槛的,并且操作起来也不能够像其它 IDE 等带有可视化界面的开发环境那样顺手,尤其是代码的编辑、编译和断点调试都需要不停的切换各种终端、编辑器环境才能完成。

为了方便我们的日常开发,xmake 官方提供了可以快速无缝集成到 Visual Studio Code 编辑器的 xmake-vscode 插件,使用这个插件我们可以通过 vscode 编辑器环境来一站式进行 C/C++ 程序开发,内置 xmake 编译、断点调试、编译错误分析定位、编译配置的快速切换等各种实用功能。

而 Visual Studio Code 编辑器是微软推出的一款轻量级跨平台的编辑器,具有非常好的跨平台性、可扩展性,我们在实验环境的桌面上,就能找到带有 Visual Studio Code 字样的图标,双击运行就可以打开它。

安装 xmake-vscode 插件

首先在环境中安装 xmake,执行如下命令:

bash <(curl -kfsSL https://labfile.oss.aliyuncs.com/courses/2764/shget.text) v2.3.7
source ~/.xmake/profile

检查版本,验证安装成功

xmake --version

在使用 xmake-vscode 插件之前,我们需要安装它,安装过程很简单,只需要切换到 vscode 的插件扩展 tab 页,然后输入 xmake 搜索相关插件。

如果看到下图所示内容,说明我们已经成功找到了 xmake-vscode 插件,然后点击旁边的 Install 字样按钮就可以完成安装了。

创建和打开 C/C++ 工程

插件安装完成后,我们可以先来创建一个空的 C 工程,之前介绍过可以使用 xmake create 命令来创建工程。但是这里我们打算直接使用 xmake-vscode 插件提供的工程创建功能来完成 C/C++ 工程的创建。

在创建之前,需要先准备一个空目录用来存放工程文件,例如将工程放置在 ~/Code/vscode_test 目录下,如果还不存在此目录,可以先手动创建下。

然后,我们直接在 vscode 里面打开这个空目录,只需要通过点击下图红框位置的按钮。

点击后,我们进入刚刚创建的 vscode_test 目录,选中并打开它。

打开目录后,我们就可以开始创建工程文件了,继续进入菜单的 view 子菜单打开 vscode 里面的命令面板,如下图。

命令面板打开后,我们输入 xmake 字符串,就会看到一系列跟 xmake 插件相关的命令,然后从其中找到带 Create Project 字样的命令。

这个时候,由于当前还没有 xmake.lua 文件,会在底下弹出一个提示框,继续点击里面的蓝色按钮。
接着就会弹出编程语言的选择列表,这里选择第二项,也就是 C++ 语言。

选择完语言后,还会提示选择工程类型,这里选择 console 终端程序类型。

选择完成后,整个项目就创建好了,下图左边就是新创建的工程文件,根目录下有 xmake.lua,而图最底下的工具栏就是 xmake 插件的操作面板了,我们的大部分操作都可以通过这个面板快速完成,包括:编译、运行、调试以及配置切换等等。

工具栏面板介绍

创建完成工程后,我们会看到 vscode 底部工具链出现了一排跟 xmake 相关的操作面板,xmake 的大部分操作都可以通过这个面板上的工具按钮来快速完成。

另外,不仅仅是创建工程,如果安装 xmake-vscode 插件后,使用 vscode 打开一个带有 xmake.lua 文件的 C/C++ 工程根目录,那么 vscode 底部的 xmake 工具栏面板也会被自动激活。

通过上图,我们大概能知道每个按钮的具体功能,具体更进一步的使用方式,我们会接下来会挨个讲解。

编译 C/C++ 程序

刚刚我们通过 vscode 创建的一个 C++ 项目工程,其内部就是执行了 xmake 的 xmake create vscode_test 命令来完成的,整个工程文件结构如下。

.
├── src
│   └── main.cpp
└── xmake.lua

在编译之前,先调整下生成的 xmake.lua 文件,修改成如下配置。

add_rules("mode.debug", "mode.release")

target("test")
set_kind("binary")
add_files("src/*.cpp")

其实,也就是将程序目标名改成 test,然后点击 vscode 底下的 xmake 工具栏里面的 Build 按钮,来编译我们新创建的 C++ 工程。

运行程序

编译完成后就可以尝试运行程序了,还是使用底下的工具栏按钮,点击运行图标。
如果运行成功,就会看到实际的运行输出信息:Hello world!。

调试程序

接下来,我们重点讲解如何通过 vscode 配合 xmake-vscode 插件来实现断点调试 C/C++ 程序。

不过在调试前还需要做一些准备工作,由于默认 xmake 采用 release 模式编译的目标程序,是不带调试符号信息的,因此我们需要先将编译模式切换到 debug 调式编译模式去重新编译它,使其带上调试符号信息。

具体如何切换到 debug 模式,可以参考下图的操作,点击底下 release 文本所在的按钮,然后在上面列出的列表中,选择 debug 项即可。

完成切换后可以看到底下的 release 按钮已经变成了 debug 字样,然后点击 build 按钮,就可以重新编译带有调试符号的目标程序了。

调试版本程序编译完成后,还需要额外做一件事,那就是安装 vscode 的 C/C++ 插件,因为 xmake-vscode 插件的调试功能是基于这个插件的,我们仅仅只需要首次使用时安装它即可。

重新切到插件市场页面,搜索 C/C++,显示出来的第一项就是,点击 Install 安装即可。

安装好 C/C++ 插件,就可以开始调试操作了。首先点击 main.cpp 打开源文件,然后在需要下断点的代码行左侧位置点击下断点,如果出现小红点,说明已经成功下好了调试断点。

断点下好后,就可以点击底下的调试按钮,开启断点调试了,如果一切顺利,程序运行起来后就会命中刚刚设置的断点。

目标程序切换

接下来再来详细讲解编译,之前我们已经使用过 build 按钮来编译工程,但如果一个项目中存在多个目标程序又不想全部编译,我们就需要指定编译哪个目标程序。

在命令行中,我们可以通过 xmake build test 命令来显示的指定编译哪个目标,而在 vscode 中,xmake 插件也提供了很方便的目标切换操作,来快速切换编译。

首先修改 xmake.lua 文件,新增一个名为 test2 的目标程序,用于之后的目标切换测试,例如。

add_rules("mode.debug", "mode.release")

target("test")
set_kind("binary")
add_files("src/*.cpp")

target("test2")
set_kind("binary")
add_files("src/*.cpp")

然后点击底下 default 字样的按钮,之后会在顶部显示整个项目所有目标程序名的列表,我们点击其中的 test2 目标程序,就可以完成切换。

如果切换成功,底下的 default 文本就会变成 test2,由于默认编译 xmake 会自动编译所有目标程序,所以最初显示的是 default 文本。

这个时候,我们再执行编译,就能看到实际仅仅只编译了我们指定的 test2 目标程序。

编译错误信息

如果我们的工程代码没写对导致编译出错,可以直接从编译输出中看到错误信息。

而 xmake-vscode 插件还会自动解析编译错误输出信息,分类每个编译错误,并可通过双击指定错误,跳转定位到指定的错误代码位置,也就是下图所示位置。

查看编译详细信息

在命令行中,我们可以通过 xmake -v 来查看编译过程中的详细命令参数信息,而在 vscode 中同样可以通过配置开启详细输出。

首先打开菜单,点击 File -> Preferences -> Settings 子菜单。

然后在打开的 Setting 配置页面,输入 xmake 找到所有跟 xmake 插件相关的配置项,其中有一项是 BuildLevel,它就是用于设置编译过程中的输出信息级别。

默认是 warnings 级别,仅仅输出编译警告信息以及正常信息,我们把它改为 verbose 级别,就可以输出完整的编译命令行参数了。

至于 debug 级别对应的就是 xmake -vD 的诊断信息,还会进一步打印出错的栈信息。

我们这里将配置切换成 verbose 级别后,再重新构建下程序,看看实际的输出是怎样的。不过由于底部只提供了 build 按钮,没有 rebuild 按钮,为了执行重新编译,我们需要从菜单里面的命令面板中,找到 xmake 的 Rebuild 命令,点击执行才行。

从下图的红色箭头位置找到对应的命令面板。

然后再打开的命令面板中输入 xmake 过滤出所有跟 xmake 相关的命令列表,找到 Rebuild 命令后点击编译即可,如下图。

开启 verbose 级别后,我们就能看到编译输出中完整的命令参数了。

xmake.lua 编辑和自动补全

xmake-vscode 插件还内置了对 xmake.lua 文件编写时的自动提示和补全支持,我们只需要输入 add_、set_ 等字样的文本,就会自动列举出与其相关的所有 api 供我们使用,来方便快速配置 xmake.lua。

我们可以通过编辑 xmake.lua 文件对里面 test2 目标程序添加一个 add_defines("TEST2") 来体验下自动补全功能。

最终的完整配置内容如下。

add_rules("mode.debug", "mode.release")

target("test")
set_kind("binary")
add_files("src/*.cpp")

target("test2")
set_kind("binary")
add_files("src/*.cpp")
add_defines("TEST2")

而我们输入时的补全特性可以通过下面的图片体会到。

完成配置后,我们再来点击底下的 Build 按钮执行编译,看下详细命令输出,应该能够正常看到新加上的 -DTEST2 宏定义了。


简单使用还挺不错,以后尽量不用 cmake 了。
后面那些都是来自官网,我也没看完,备用吧。