Xmake 带你轻松构建 C/C++ 项目 是我们在实验楼上推出的一门 xmake 入门和进阶课程(收费),以边学边做实验的方式快速学习 xmake 的使用。
通过此处优惠码购买可享 9 折优惠:NYFbmf3X
Xmake 是一个基于 Lua 的轻量级跨平台 C/C++ 构建工具,使用 xmake.lua 维护项目构建,相比 makefile/CMakeLists.txt 而言,配置语法更加简洁直观,对新手非常友好,短时间内就能快速入门 C/C++ 项目的编译开发,提供一站式跨平台编译、运行、调试、打包、安装等操作,能够让大家把更多的精力集中在实际的项目开发上。
虽然,简单易用是 xmake 的一大特色,但 xmake 的功能也是非常强大的,既能够像 make/ninja 那样直接编译项目,也可以像 cmake/meson 那样生成工程文件,还有内置的包管理系统来帮助大家解决 C/C++ 依赖库的集成使用问题。
本课程以循序渐进的方式,带你入门和进阶 xmake,从最基础的编译配置,到复杂项目的定制化组织和维护,在课程最后几节,我们还通过实战的方式,一步步带你体验第三方 C/C++ 项目的移植编译,以及 vscode/xmake 集成环境的可视化编译操作流程。最后一节实验中,我们还会讲解如何使用 xmake 和 vscode 去编译开发基于 Qt 的应用程序。
C/C++ 程序的编译运行和调试 | xmake 基础命令使用 |
C/C++ 依赖库集成和使用 | 大型工程结构的维护 |
xmake 复杂脚本和规则的编写 | 如何实现跨平台编译 |
xmake 插件开发 | VS Code/xmake 集成环境的使用 |
xmake 的基础语法 | C/C++ 项目基础编译配置 |
多个目标程序的依赖编译 | 预编译头文件的设置 |
cmake/makefile 工程文件的生成 | xmake 脚本运行插件的使用 |
C/C++ 代码的移植编译 | Qt 项目程序的编译开发 |
链接地址:https://www.lanqiao.cn/courses/2764
我们也可以通过扫描下方二维码进入课程:
xmake 是一个基于 Lua 的轻量级跨平台构建工具,使用 xmake.lua 维护项目构建,相比 makefile/CMakeLists.txt,配置语法更加简洁直观,对新手非常友好,短时间内就能快速入门,能够让用户把更多的精力集中在实际的项目开发上。
随着 xmake 最近几年不断的迭代发展,xmake 已经在 Github 收获 2.9K star,300+ fork,30+ 贡献者,并且处理了 900+ issues,5400+ Commits,活跃用户也在不断增长。
现在,xmake v2.3.7 版本发布了,在新版本中,我们主要完善了 xmake 自身的稳定性和兼容性,通过两个月的不断迭代,修复了很多用户反馈的各种使用问题,使用体验和稳定性有了很大的提升。
另外,我们在这个版本中也新增对 TinyC 和 Emscripten (WebAssembly) 编译工具链的支持。
尤其是针对 windows 平台,我们提供了额外的 xmake-tinyc 安装包,里面内置了 tinyc 编译器,使得用户可以完全脱离臃肿的 vs 环境,一键安装,开箱即用,只需要 5M 的安装包即可开发简单的 C 程序,同时还自带了整套 winapi 头文件。
最后,我们还改进了 trybuild 模式编译,通过 xmake 可以快速编译 autotools/cmake 维护的第三方项目,并且可以快速对接 android/ios/mingw等交叉编译环境,实现快速移植编译。
新版本中,我们将 xmake 安装包提交到了 windows winget 以及 ubuntu ppa 仓库,我们可以更加方便快捷地安装 xmake。
winget install xmake
sudo add-apt-repository ppa:xmake-io/xmake
sudo apt update
sudo apt install xmake
当然,我们还支持很多其他的安装方式,对于其他平台的详细安装方式见:安装文档。
当前我们已经支持非常多的工具链环境,而在这个版本中,我们又新增了 TinyC 和 Emscripten (WebAssembly) 编译工具链的支持,我们可以通过下面的命令快速切换到对应的工具链来编译。
xmake f --toolchain=[tinyc|emscripten]
xmake
我们还在新版本中,额外提供了两个安装包,内置集成了 TinyC 编译环境,整个安装包只需要 5M,还包含了 winsdk api。
安装包可以在 xmake 的 github/releases 目录下找到。
通过这个安装包,我们编译开发 C 程序就可以完全摆脱臃肿的 vs 开发环境(好几个 G),实现一键安装,开箱即用,对于我们平常刷刷 leetcode,写点 C 测试代码还是非常有用的,没必要为此特定安装整个 vs 进来。
另外,如果我们要查看 xmake 支持的所有工具链,可以执行下面的命令,另外 xmake f -p cross --sdk=/xxx
的编译配置可以支持更多通用的交叉工具链。
$ xmake show -l toolchains
xcode Xcode IDE
vs VisualStudio IDE
yasm The Yasm Modular Assembler
clang A C language family frontend for LLVM
go Go Programming Language Compiler
dlang D Programming Language Compiler
gfortran GNU Fortran Programming Language Compiler
zig Zig Programming Language Compiler
sdcc Small Device C Compiler
cuda CUDA Toolkit
ndk Android NDK
rust Rust Programming Language Compiler
llvm A collection of modular and reusable compiler and toolchain technologies
cross Common cross compilation toolchain
nasm NASM Assembler
gcc GNU Compiler Collection
mingw Minimalist GNU for Windows
gnu-rm GNU Arm Embedded Toolchain
envs Environment variables toolchain
fasm Flat Assembler
tinyc Tiny C Compiler
emcc A toolchain for compiling to asm.js and WebAssembly
这个版本重点对其他语言的支持做了一些改进,比如新增了fortran的编译支持,zig语言的实验性支持,另外对golang/dlang增加了第三方依赖包支持以及交叉编译支持。
虽然,xmake重点关注c/c++的构建支持,但是其他语言的支持xmake也会不定期做一些改进,其主要目的并不是替代它们官方自身的构建系统,仅仅只是为了支持与c/c++的混合编译,更好的为c/c++项目服务, 毕竟有些c/c++项目中,还是会偶尔调用其他语言的代码接口,比如与cuda, dlang, objc,swift, asm等语言的混合调用,所以xmake还是会对他们做一些基础性的编译支持。
另外,关于c/c++方面,我们也对vs预览版中新的/sourceDependencies xxx.json
输出的头文件依赖格式也做了支持(这对于多语言下,头文件依赖检测会更加的可靠稳定)。
这个版本开始,我们已经完全支持使用gfortran编译器来编译fortran项目,我们可以通过下面的命令,快速创建一个基于fortran的空工程:
$ xmake create -l fortran -t console test
它的xmake.lua内容如下:
add_rules("mode.debug", "mode.release")
target("test")
set_kind("binary")
add_files("src/*.f90")
更多代码例子可以到这里查看:Fortran Examples
xmake是一个基于Lua的轻量级现代化c/c++的项目构建工具,主要特点是:语法简单易上手,提供更加可读的项目维护,实现跨平台行为一致的构建体验。
本文主要详细讲解下,如何通过添加自定义的脚本,在脚本域实现更加复杂灵活的定制。
xmake.lua采用二八原则实现了描述域、脚本域两层分离式配置。
什么是二八原则呢,简单来说,大部分项目的配置,80%的情况下,都是些基础的常规配置,比如:add_cxflags
, add_links
等,
只有剩下不到20%的地方才需要额外做些复杂来满足一些特殊的配置需求。
而这剩余的20%的配置通常比较复杂,如果直接充斥在整个xmake.lua里面,会把整个项目的配置整个很混乱,非常不可读。
因此,xmake通过描述域、脚本域两种不同的配置方式,来隔离80%的简单配置以及20%的复杂配置,使得整个xmake.lua看起来非常的清晰直观,可读性和可维护性都达到最佳。
对于刚入门的新手用户,或者仅仅是维护一些简单的小项目,通过完全在描述配置就已经完全满足需求了,那什么是描述域呢?它长这样:
target("test")
set_kind("binary")
add_files("src/*.c")
add_defines("DEBUG")
add_syslinks("pthread")
一眼望去,其实就是个 set_xxx
/add_xxx
的配置集,对于新手,完全可以不把它当做lua脚本,仅仅作为普通的,但有一些基础规则的配置文件就行了。
这是不是看着更像配置文件了?其实描述域就是配置文件,类似像json等key/values的配置而已,所以即使完全不会lua的新手,也是能很快上手的。
而且,对于通常的项目,仅通过set_xxx/add_xxx
去配置各种项目设置,已经完全满足需求了。
这也就是开头说的:80%的情况下,可以用最简单的配置规则去简化项目的配置,提高可读性和可维护性,这样对用户和开发者都会非常的友好,也更加直观。
如果我们要针对不同平台,架构做一些条件判断怎么办?没关系,描述域除了基础配置,也是支持条件判断,以及for循环的:
target("test")
set_kind("binary")
add_files("src/*.c")
add_defines("DEBUG")
if is_plat("linux", "macosx") then
add_links("pthread", "m", "dl")
end
target("test")
set_kind("binary")
add_files("src/*.c")
add_defines("DEBUG")
for _, name in ipairs({"pthread", "m", "dl"}) do
add_links(name)
end
这是不是看着有点像lua了?虽说,平常可以把它当做普通配置问题,但是xmake毕竟基于lua,所以描述域还是支持lua的基础语言特性的。
!> 不过需要注意的是,描述域虽然支持lua的脚本语法,但在描述域尽量不要写太复杂的lua脚本,比如一些耗时的函数调用和for循环
并且在描述域,主要目的是为了设置配置项,因此xmake并没有完全开放所有的模块接口,很多接口在描述域是被禁止调用的,
即使开放出来的一些可调用接口,也是完全只读的,不耗时的安全接口,比如:os.getenv()
等读取一些常规的系统信息,用于配置逻辑的控制。
!> 另外需要注意一点,xmake.lua是会被多次解析的,用于在不同阶段解析不同的配置域:比如:option()
, target()
等域。
因此,不要想着在xmake.lua的描述域,写复杂的lua脚本,也不要在描述域调用print去显示信息,因为会被执行多遍,记住:会被执行多遍!!!
限制描述域写复杂的lua,各种lua模块和接口都用不了?怎么办?这个时候就是脚本域出场的时候了。
如果用户已经完全熟悉了xmake的描述域配置,并且感觉有些满足不了项目上的一些特殊配置维护了,那么我们可以在脚本域做更加复杂的配置逻辑:
target("test")
set_kind("binary")
add_files("src/*.c")
on_load(function (target)
if is_plat("linux", "macosx") then
target:add("links", "pthread", "m", "dl")
end
end)
after_build(function (target)
import("core.project.config")
local targetfile = target:targetfile()
os.cp(targetfile, path.join(config.buildir(), path.filename(targetfile)))
print("build %s", targetfile)
end)
只要是类似:on_xxx
, after_xxx
, before_xxx
等字样的function body内部的脚本,都属于脚本域。
在脚本域中,用户可以干任何事,xmake提供了import接口可以导入xmake内置的各种lua模块,也可以导入用户提供的lua脚本。
byOpen是一个绕过移动端系统限制的dlopen库。
支持App中加载和使用Android系统库接口(即使maps中还没有被加载也支持)。
Android 7以上dlopen, System.load都是被限制调用的,虽然目前网上有Nougat_dlfunctions等库通过从maps中找so库来绕过加载限制。
不过对于app中还没被加载到maps的so库,这种方式就不行了。
而byOpen不仅支持fake dlopen方式从maps加载,还可以将还没加载到maps的so库绕过系统限制强行加载进来使用,实现更加通用化得dlopen。
注:目前的实现方式理论上还是比较通用的,至少我这Android 10上测试ok,但还没完整详细测试过,是否使用请自行评估。
具体实现原理还是比较简单的,主要还是借鉴了一种绕过Android P对非SDK接口限制的简单方法的思想和实现方式。
虽然这篇文章中主要目的是为了绕过hide api,不过它里面使用的将自己假装成系统调用的方式,一样可以用到System.loadLibrary
上去,让系统以为是系统自身在调用System.loadLibrary
从而绕过Android N的classloader-namespace限制,将系统/system/lib中任意so库加载到maps中,然后再通过fake dlopen的方式去dlsym。
关于fake dlopen的方式实现,网上已有很多实现,比如:
byOpen参考了里面的实现,重新实现了一遍,并且做了一些小改进: