Show HN:一个静态类型、跨平台、易于自举的构建系统
摘要
BUSY 是一个精简、静态类型、跨平台的构建系统,支持 GCC、Clang 和 MSVC 工具链,旨在易于自举和最小依赖。
查看缓存全文
缓存时间: 2026/07/04 06:37
rochus-keller/BUSY
来源:https://github.com/rochus-keller/BUSY
图标
欢迎使用 BUSY 构建系统
BUSY(全称 BUild SYstem)是一款轻量级、跨平台的构建系统,支持 GCC、Clang 和 MSVC 工具链,系统需求极低且易于自举。
与 CMake、QMake、Meson 或 GN 等其他构建系统相比,BUSY 的特点在于其静态类型的构建规范语言,以及无需对宿主系统提出额外要求即可直接从零构建项目的能力;BUSY 足够精简,甚至适合直接集成到项目的源码树中。
以下是一个使用 BUSY 的示例项目:https://github.com/rochus-keller/nappgui/。NAppGUI 是由 Francisco García 开发的一个广泛的跨平台 GUI 库,使用 C89 编写(部分使用 C++98 和 Objective-C)。请参阅 README 了解如何构建项目,并查看根目录和 src 目录(以及子目录)中的 BUSY 文件。关于规范语言的更多信息,请参阅下文和 syntax 目录。熟悉 GN 的人会在 BUSY 中识别出多种概念。
以下是几个摘录,方便参考:
``
来自顶层 BUSY 文件
submod src
let shared_lib* = src.shared_lib
let static_lib* = src.static_lib
let all! : Group {
.deps = [ shared_lib static_lib ]
# 由于使用了 ‘let’,‘deps’ 字段只能通过 ‘.’ 前缀在此处设置
# 如果使用 ‘var’,则可以在其他地方通过 ‘all.deps += xyz’ 进行修改
}
来自 src BUSY 文件
let main_config - : Config { .include_dirs += [ ./geom2d ./osbs ./sewer /* 仅展示几个 */ ] .defines += [ “NAPPGUI_LIBRARY” “NAPPGUI_BUILD_DIR="” + tostring(root_build_dir) + “"” “NAPPGUI_BUILD="” + readstring(‘../prj/build.txt’) + “"” ] }
submod core submod draw2d # 还有更多
let all_lib_sources : Group { # 仅在此 BUSY 文件中可见 .deps = [ core.sources draw2d.sources # 还有更多 ] }
let static_lib* : Library {
.name = “NAppGUI” # 否则二进制文件会命名为 “static_lib”
.lib_type = `static
.deps = [ all_lib_sources ]
}
来自 draw2d BUSY 文件
submod gtk3
let sources * : SourceSet {
.sources = [
./draw2d.cpp
./drawg.cpp
./btext.c
# 还有更多
]
.configs += ^main_config # 引用 src BUSY 文件中的 Config
if target_os == linux { .deps += gtk3.sources }else if target_os == win32 {
# 以此类推
}else {
error(“target os not supported”)
}
}
以下语法版本同样有效(适用于偏爱 Pascal 风格的人):
let sources * : SourceSet
begin
.sources := [
./draw2d.cpp
./drawg.cpp
./btext.c
]
if target_os == linux then .deps += gtk3.sources elsif target_os == win32 then
# 以此类推
else
error(“target os not supported”)
end
end
``
另一个更复杂的 BUSY 使用示例是 Oberon+ 编译器和 IDE(https://github.com/rochus-keller/Oberon);关于如何运行构建,请参阅 https://github.com/rochus-keller/LeanQt/blob/main/Readme.md。该示例还展示了 BUSY 对 Qt moc 和 rcc 工具的特殊支持。
BUSY 基于并与 Lua 虚拟机集成(但使用 C89 编写,而非 Lua)。Lua 是目前在所有平台上最易于构建的代码库之一;唯一要求是 C89 编译器;BUSY 沿袭了这一传统,并受益于 Lua 作者的杰出工作。
如果你正在寻找一款与 BUSY 完全集成并支持多核构建的强大 C/C++ IDE,请查看 LeanCreator(https://github.com/rochus-keller/LeanCreator/)。
为什么还需要另一个构建系统?
我多年来一直在使用和研究构建系统。QMake 是过去二十年中使用最多的构建系统;但它需要 Qt,并且对于大型项目而言不如 GN 等灵活。我还在一些项目中使用 CMake,自从二十年前首次接触 Visualization Toolkit 以来,我一直关注它的发展。我也在跟踪 Meson 或 GN 等较新的项目;在探索 Dart VM 源码树时,我偶然发现了 GN,甚至为其开发了一个分析工具(见 https://github.com/rochus-keller/GnTools)。这些发现足以写成几篇文章;这里仅作简要概述。
CMake 是一种功能完备但有些过时的脚本语言;“现代 CMake”现在也专注于目标和属性(类似于 GN),而不是命令式地详细说明构建步骤应如何执行;这是正确的方向,但仍然是另一层抽象,探索了这种字符串类型的动态脚本语言的极限,而其他层仍然清晰可见;CMake 本身已经变得庞大而复杂,单个开发者很难完全理解;它比大多数我想用它构建的项目还要庞大复杂。
Meson 引入了有趣的方法,并明确避免使用图灵完备的编程语言,在我看来这是正确的方式;我也喜欢通过引用抽象对象来访问结果,从而抽象掉文件系统路径的想法;然而,该语言有些奇特,显然受到 Python 的启发,且仍然是动态类型的(即使有不止字符串类型)。
构建系统似乎一直是软件开发人员希望尽可能少花时间的东西;它们被视为副产品,而非软件工程实际艺术的核心;只有这样才能解释为什么过去五十年的软件工程成果(如模块化、结构化编程、面向对象编程和静态类型检查)对其影响如此之小。如今,软件系统越来越大,跨平台成为必需,代价也随之显现。Chromium 是一个令人印象深刻的例子,多年来已发展成一个极其庞大的系统,代码行数接近 4000 万;开发者引入并废弃了多个构建系统;数年前开始使用 GN,它甚至是为 Chromium 的需求专门开发的;GN 功能强大,但也相当复杂;即使理解 Dart VM(比 Chromium 小得多)的构建过程也绝非易事;尽管我的工具(https://github.com/rochus-keller/GnTools)在这方面有所帮助,但它本质上受限于动态类型语言在静态分析方面的能力;因此,为大型软件系统开发、分析和维护构建系统,与动态语言已知的缺点基本相同,最终导致了 TypeScript 或 Dart 等的发展;在我看来,构建系统早就需要静态类型和有效的模块化手段了。
但还有其他方面,在我看来,以往的构建系统考虑不够充分。首先必须提出一个问题:构建系统本身如何构建,以及这会带来多大的复杂性和代价;构建系统对现有系统和预安装组件的要求应尽可能低;GN、Meson 或 QMake 在这方面表现较弱,因为它们需要编译器以及较新版本的 C++ 和/或 Python 的庞大库;我曾创建过一个独立的 QMake 版本,附带最小化、精简的 Qt 类集;但仍有近 200 个源文件,尝试用 g++ *.cpp 编译会耗尽所有可用内存并迅速崩溃;GN 和 Meson 都需要 Ninja,而 Ninja 是另一个需要构建的 C++ 代码库。
那么,像 Lua 这样只需要 C89 兼容编译器,并生成长度仅几百 KB 的单一二进制文件呢?gcc *.c 对 Lua 来说完美工作。用 C89 编写静态类型语言的解析器也是可行的。
这促使我亲自尝试,并参与竞争。
运行 BUSY
BUSY 通常使用 build.lua 脚本启动,后跟附加参数;build.lua 只是 C 实现 Lua 函数的一个门面,允许在必要时使用 Lua 脚本扩展或更改构建系统。
BUSY 默认使用构建 BUSY 可执行文件时所用的工具链。
默认命令行版本 lua build.lua 执行以下操作:源码树根目录假定为 ..,构建目录树根目录假定为 ./output。因此,运行 BUSY 最简单的方法是在源码树顶层创建一个 build 子目录,将 BUSY 源码放入并编译;但这不是必须的(请参阅下面的其他选项)。默认情况下,根 BUSY 文件中所有标记为默认(通过在名称后加 !)的产品都会被构建(即直接运行构建)。
使用 -S 选项可以显式设置源码目录树的根路径;作为快捷方式,你也可以直接输入路径而不带 -S 前缀。
使用 -B 选项可以显式设置构建目录树的根路径。
使用 -T 选项可以显式选择要构建的产品;例如 -T my_lib 会在根 BUSY 文件中查找名为 “my_lib” 的 Product 子类型的公共变量声明;也可以选择多个产品,或选择位于源码树更深层 BUSY 文件中的产品;后者必须从根部可见(即指定变量和子模块声明必须公开)。
使用 -P 选项可以设置参数值;语法为 -P x.y=value,其中 value 是有效的 BUSY 基本类型字面量语法;字符串和符号的语法通常需要使用命令行转义,例如 -P "string_param=\"this is a string\"",或 -P symbol_param=\`abc。同样,可以设置位于源码树更深层 BUSY 文件的参数,但前提是指定中的子模块声明是公开的。
使用 -M 选项可以设置构建模式之一:optimized、nonoptimized 或 debug;例如 -M debug;默认构建模式是 optimized。BUSY 也支持缩写选项 -opt(对应 optimized)、-nopt(对应 nonoptimized)或 -dgb(对应 debug)。
使用 -c 选项仅运行解析器/分析器以检查 BUSY 文件。不会运行构建,也不会生成任何文件或目录。
使用 -G 选项可以告诉 BUSY 为另一个构建系统生成代码。目前支持 -G qmake 选项,用于生成使用 QtCreator 构建项目所需的项目文件。在 BUSY 的未来版本中,将支持其他后端,如 -G ninja。如果未提供 -G 选项,BUSY 将自行运行构建。
指定构建
构建使用 BUSY 文件指定,这些文件包含用 BUSY 规范语言编写的代码; 关于如何使用 BUSY,请参阅 The BUSY Build System - User’s Guide, 关于规范语言的详细信息,请参阅 The BUSY Build System - Language and Built-ins Specification。
BUSY 文件是文件名为 “BUSY” 的文件,或者也可以是 “BUSY.busy”;如果目录中同时存在这两个文件,则优先使用名为 “BUSY” 的文件。
在源码树的根目录以及任何包含与构建相关的文件或子目录的子目录下,都有一个 BUSY 文件。
BUSY 文件是规范语言的“模块”。声明仅在模块内可见,除非声明为公共(*,对外层和嵌套模块可见)或受保护(-,仅对嵌套模块可见)。子模块必须使用 submod 关键字显式声明并与相应目录关联。
规范使用预声明的类型、过程和变量。预声明的类型包括基本类型 bool、int、real、string、path 和 symbol,枚举类型如 type LibraryType* = (static, shared, framework)`,以及类类型如
type Config* = class { cflags : string[] defines: string[] include_dirs: path[] /* 以此类推 */ configs: Config[] }
还有一个类层次结构;例如,Executable、Library 和 SourceSet 类都是 Product 的子类,而 Product 有一个字段 deps: Product[],用于表示产品之间的构建时依赖关系(即目标)。请注意,这些都是常规语法,可以在任何 BUSY 文件中使用;没有像 Meson 中那样在幕后用另一种语言定义的魔法类。
预声明的全局变量,如 let root_build_dir: path、let host_os: OsType、let host_toolchain: CompilerType 或 let host_toolchain_ver: int,由 BUSY 设置,可用于根据不同的操作系统或工具链调整构建。预声明的“过程”(它们实际上不是真正的过程,只是对编译器的提示)如 tostring(),可用于进行类型转换,因为例如直接将数字赋值给字符串变量会导致编译器错误。
BUSY 概念上运行三个阶段(类似于 BAZEL):加载阶段(解析所有 BUSY 文件)、分析阶段(运行语句并创建工作树)、执行阶段(深度优先运行选定的工作树)。需要注意的是,即使考虑了所有声明并创建了不同的工作树,只有被标记为默认(标识符后带 !)或由 -T 选项选中的工作树才会实际执行。
已规划或正在开发中的特性
当前 BUSY 版本对于 LeanQt(https://github.com/rochus-keller/leanqt)、Oberon+ OBXMC 工具(https://github.com/rochus-keller/Oberon/)和 NAppGUI 框架(https://github.com/rochus-keller/nappgui)的使用已具备完整功能,并在 Linux x86、x86_64 和 ARMv7(GCC)、Windows 10 x86 和 AMD64(MSVC)、Windows 7 x86(MSVC)以及 macOS 10.11 x86_64 和 12.2 M1(CLANG)上成功测试。
- 静态类型构建规范语言,“尽可能简单”
- 支持字符串、符号、路径、注释和标识符中的 Unicode(UTF-8)
- 支持 C 风格和 Pascal 风格的语法版本
- 能够直接运行构建(即独立于 Make、NMake 或 Ninja)
- 语言规范
- 使用 BUSY 制作精简的 Qt 源码树版本(见 LeanQt (https://github.com/rochus-keller/LeanQt))
- QMake 后端(已在 Linux、Windows 和 Mac 上使用 QtCreator 3 和 4 配合 LeanQt 进行测试)
- 教程
- 实现 Ninja 后端
- 实现 CMake 后端
注意:
使用以下命令成功实现了从 Linux x86 到 ARM Cortex-A7(Allwinner H3)的交叉编译:
lua build.lua ../LeanQt -P target_toolchain_path=//home/me/toolchain/bin -P HAVE_OBJECT -P target_toolchain_prefix=\"arm-linux-gnueabihf-\"
非目标
- BUSY 不是也不打算成为完整的编程语言
- BUSY 不是包生成器或管理器
- BUSY 不是 Git 客户端
- BUSY 不搜索库或工具链,也不下载任何内容;它只使用你提供的内容
- BUSY 不是 C 预处理器,也不检查 #include 指令
- BUSY 不是测试框架、生成器或管理器;但它可以运行外部脚本
- BUSY 不能替代 Ninja;它可以直接运行构建,以便更轻松地部署可构建的代码库;对于快速编辑-编译-运行循环,可以生成 Ninja 文件
构建步骤
构建 BUSY 非常简单:
- 打开终端,将当前目录设置为 BUSY 源码目录
- 运行
cc *.c -O2 -lm -o lua或cl /O2 /MD /Fe:lua.exe *.c,具体取决于你使用的是 Unix 还是 Windows 机器 - 等待几秒钟;结果是一个包含 BUSY 集成的 Lua 可执行文件
相似文章
Show HN: Nub – 类似 Bun 的用于 Node.js 的一体化工具包
Nub 是一个快速的一体化工具包,用于 Node.js,提供类似 Bun 的开发者体验,包括运行 TypeScript 文件、管理依赖项和 Node 版本,所有这些都集中在一个用 Rust 编写的 CLI 工具中。
Show HN: Nibble
Nibble 是一种类 C 的系统编程语言,用 3000 行 C 代码实现,无需外部依赖或堆分配即可生成 LLVM IR。它支持 defer、递归、多种类型、结构体、指针,并包含图形演示。
Show HN: Clx – 通过C++20将Lua编译为本地可执行文件
Clx是一个跨平台的提前编译Lua编译器,通过C++20工具链生成独立的本地可执行文件,提供有竞争力的性能、小巧的二进制文件,并支持Lua 5.5。
Show HN: 用汇编语言构建 Web 服务器,为我的生命赋予(些许缺乏的)意义
ymawky 是一个专为 macOS 编写的 ARM64 汇编 Web 服务器,其特点是不依赖 libc 仅使用系统调用,并具备基本的 HTTP 功能。
Show HN: 我花了一个周末用Rust重写了IDE中我唯一使用的部分
Kyde 是一个快速的原生Git客户端和代码编辑器,使用Rust基于Zed的gpui框架构建,具有GPU渲染、并排差异对比、tree-sitter语法高亮以及一个精心调校的深色主题。