Skip to content

Commit fa499b9

Browse files
committed
feat(bench): the cmake and bazel arms build xlings for real
两条外部臂此前都不产出二进制,而 README 把这写成「已知缺口」。**它不是缺口,是 没做完的工作**,那句注释让它躺了很久。 **cmake 臂**(两棵树都实测:configure → build → `xlings --version` 输出对应版本) * `add_subdirectory` 这条路走不通,不是难走:mbedtls 3.6.1 对缺失的 `framework/` 子模块是**无条件 FATAL_ERROR**,与 `ENABLE_TESTING` 无关。改为按各包自己的 `.xpkg.lua` —— 也就是 mcpp 对「编哪些文件」的定义 —— 生成 STATIC target。 * **glob 是错的,不只是脆**:libarchive 目录里 132 个 `.c` 而清单是 127,lua 是 34 对 32,多出来的正是 `lua.c` / `luac.c`,各自带 `main()`。新的 `xpkg_source_library.cmake` 在 configure 期打印逐包计数(127/32/108/73/…共 467 个 TU),某个模式匹配到零个文件是 FATAL_ERROR 而不是静默变小。 * **libarchive 的五个压缩后端整个缺失**:它生成的 config 头把 `HAVE_LIBZ` 等置 1,于是留下 72 个 `deflate`/`BZ2_`/`LZ4`/`lzma_`/`ZSTD_` 未定义符号。 * **C 语言从来没启用**:`project(xlings CXX)` 对 `.c` 源码没有规则。而且因为 harness 只传 `-DCMAKE_CXX_COMPILER`,这里在 `project()` 之前从 C++ 驱动推出 同族 C 驱动 —— 否则 CMake 去 PATH 找宿主 `cc`,而 payload 又按 C++ 编译器给它 套 registry sysroot,正是那个文件注释里写的两套 libc 故障。 * 共享的 `bench_add_source_dep` **漏掉模块的实现单元**(只 glob `src/*.cppm`), 于是 capi.lua 那个 428 行的 `src/capi/lua.cpp` 从未被编译。 **bazel 臂**(同样两棵树实测通过) * 「工作区边界」是**跨过去**的,不是绕过去:`@xlings_tree` 用 repository rule 解析 `BENCH_PROJECT_ROOT`(和另外两份描述读同一个变量),`@mcpp_deps` 同样 从 `.xpkg.lua` 派生 13 个包。 * **`import std;` 不是墙**:`@mcpp_deps//:std` 直接编 libc++ 自己的 `std.cppm`, BMI 沿 `cc_library` 依赖传播。 * **`--compilation_mode=opt` 原本根本编不过**,而 harness 的 release 一律传 opt: bazel 附加 `-D_FORTIFY_SOURCE=1`,glibc 的 `__fortify_function` 是内部链接, libc++ 的 std 模块无法再导出。只测 debug 永远看不到这条。 * clang 的 BMI 会**重新打开自己的源文件**,而那不是声明过的输入。 `--spawn_strategy=local` 能编过 —— 这正是陷阱:沙箱外看着好了,依赖其实一直 没声明。用 `additional_compiler_inputs` 修。 **xmake 工具链缺了 `as`**:`-B<binutils>/bin` 只让 gcc 驱动找得到汇编器,而 xrepo 建包时是 xmake 自己解析程序,于是整个 configure 停在 `cannot get program for as` —— 既不提工具链也不提当时在建哪个包。 **e2e 233 的守卫判据写错了**:它找 `cc_binary|cc_library`,而能用的 bazel 描述 只声明 `alias` —— 那是 `bazel query kind(rule, //...)` 会返回的真实规则。要抓的 幽灵是**零规则**(`Found 0 targets`),不是「没有 cc_*」。改为匹配 `name =`, 并两个方向都重验过。
1 parent 92670d0 commit fa499b9

13 files changed

Lines changed: 1420 additions & 261 deletions

File tree

bench/projects/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ compile_commands.json
2929
# bazel
3030
bazel-*/
3131
MODULE.bazel.lock
32+
33+
# bazel convenience symlinks — created in the package dir on every build,
34+
# and pointing into ~/.cache/bazel, so they are never worth tracking.
35+
bazel-*

bench/projects/common/cmake/hermetic_payload.cmake

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,20 @@ function(bench_add_source_dep target name version)
203203
string(REGEX REPLACE "[^A-Za-z0-9_]" "_" fsname "fs_${name}")
204204
target_sources(${target} PRIVATE
205205
FILE_SET "${fsname}" TYPE CXX_MODULES BASE_DIRS "${base}" FILES ${srcs})
206+
207+
# ⚠️ .cpp UNDER src/ TOO, as ORDINARY sources. A package's `src/` may hold
208+
# MODULE IMPLEMENTATION UNITS — `module mcpplibs.capi.lua;` with no `export`
209+
# — and those are not part of a CXX_MODULES file set (CMake rejects a
210+
# non-interface unit there); they are plain sources that the scanner picks
211+
# the module edge out of. mcpplibs.capi.lua 0.0.3 keeps all 428 lines of its
212+
# Lua-C wrapper in one, and globbing only `*.cppm` dropped it: the arm then
213+
# failed at the link on every `mcpplibs::capi::lua::*` symbol, naming the
214+
# consumer rather than the file that was never compiled.
215+
#
216+
# `*/src/` and not the whole package: examples/ and tests/ carry their own
217+
# main() and are not what mcpp compiles for a dependency.
218+
file(GLOB_RECURSE impls CONFIGURE_DEPENDS "${dir}/*/src/*.cpp")
219+
if(impls)
220+
target_sources(${target} PRIVATE ${impls})
221+
endif()
206222
endfunction()

bench/projects/common/xmake/payload.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ function bench_define_toolchains(manifest)
112112
set_toolset("sh", path.join(gcc_dir, "bin", "g++"))
113113
set_toolset("ar", path.join(binutils, "bin", "ar"))
114114
set_toolset("strip", path.join(binutils, "bin", "strip"))
115+
-- `as` and `ranlib` are NOT optional once xrepo builds packages with
116+
-- this toolchain. `-B<binutils>/bin` only lets the DRIVER find them;
117+
-- xmake resolves the assembler itself and stops the whole configure
118+
-- with `cannot get program for as` — a message that names neither
119+
-- the toolchain nor the package it was building (ftxui, here).
120+
set_toolset("as", path.join(gcc_dir, "bin", "gcc"))
121+
set_toolset("ranlib", path.join(binutils, "bin", "ranlib"))
122+
set_toolset("nm", path.join(binutils, "bin", "nm"))
123+
set_toolset("objcopy", path.join(binutils, "bin", "objcopy"))
115124
on_load(function (toolchain)
116125
local read_pin = function (m)
117126
if not m or not os.isfile(m) then return nil end
@@ -164,6 +173,12 @@ function bench_define_toolchains(manifest)
164173
set_toolset("sh", path.join(llvm_dir, "bin", "clang++"))
165174
set_toolset("ar", path.join(llvm_dir, "bin", "llvm-ar"))
166175
set_toolset("strip", path.join(llvm_dir, "bin", "llvm-strip"))
176+
-- Same reason as the gcc arm: xrepo package builds resolve these
177+
-- programs through the toolchain, not through the driver.
178+
set_toolset("as", path.join(llvm_dir, "bin", "clang"))
179+
set_toolset("ranlib", path.join(llvm_dir, "bin", "llvm-ranlib"))
180+
set_toolset("nm", path.join(llvm_dir, "bin", "llvm-nm"))
181+
set_toolset("objcopy", path.join(llvm_dir, "bin", "llvm-objcopy"))
167182
-- xmake finds libc++'s `std.cppm` through the SDK dir, and it reads
168183
-- that at DESCRIPTION scope — setting it inside on_load is too late
169184
-- and leaves `std and std.compat modules not found!`, after which

bench/projects/xlings/.bazelignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# The pinned trees are consumed through @xlings_tree, which symlinks the ONE
2+
# named by BENCH_PROJECT_ROOT. Left visible here they would also be part of this
3+
# package, so `bazel build //...` would glob both of them into every target it
4+
# found — and a cell that measures two builds and reports one is worse than no
5+
# cell. Ignoring them is what makes "//... builds the tree under measurement"
6+
# true rather than approximately true.
7+
xlings-2026.8.11.2
8+
xlings-2026.8.13.1
9+
10+
# Output directories of the other arms. cmake's build/ and mcpp's target/ hold
11+
# tens of thousands of files with no BUILD file among them; scanning them costs
12+
# real time on every `bazel build //...`.
13+
build
14+
target
15+
.xmake

bench/projects/xlings/.bazelrc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Flags this arm cannot build without. bench/src/engines/bazel.cppm passes the
2+
# first three itself; they are repeated here so that the command lines in
3+
# MODULE.bazel and README.md work as written, and so a hand-run build measures
4+
# the same thing the harness does.
5+
6+
# Each one's absence is a different error, and none of them says "add this flag":
7+
# without --experimental_cpp_modules: `attribute module_interfaces: requires
8+
# --experimental_cpp_modules`
9+
# without --features=cpp_modules: `the feature cpp_modules must be enabled`
10+
build --experimental_cpp_modules
11+
build --features=cpp_modules
12+
13+
# cc_binary registers the ddi aggregation action for BOTH the PIC and non-PIC
14+
# object sets but names its output <target>.CXXModules.json without a pic
15+
# suffix, so analysis dies before a single file compiles:
16+
# Attempted action contains artifacts not in previous action:
17+
# _objs/xlings/main.pic.ddi ... Outputs: are equal
18+
# Forcing one object flavour leaves one action. PIC rather than
19+
# --features=-supports_pic because it yields a PIE executable, which is what
20+
# gcc and clang produce by default for every other engine in the table.
21+
build --force_pic
22+
23+
# ⚠️ --compilation_mode=opt DOES NOT BUILD WITHOUT THIS, and the harness always
24+
# passes opt for the release profile. bazel's opt mode appends
25+
# `-D_FORTIFY_SOURCE=1` after its own `-U_FORTIFY_SOURCE`, glibc then replaces
26+
# the string/stdio/wchar functions with `__fortify_function` wrappers — which
27+
# have INTERNAL LINKAGE — and libc++'s std module cannot re-export them:
28+
#
29+
# libcxx_module/std/cwchar.inc:30:14: error: using declaration referring to
30+
# 'swprintf' with internal linkage cannot be exported
31+
# note: target of using declaration
32+
# .../xim-x-glibc/2.44/include/bits/wchar2.h:181:8
33+
#
34+
# It reads as a libc++/glibc incompatibility and is a build flag. --copt lands
35+
# after the compilation-mode flags, so the -U wins; no other engine in this
36+
# table defines _FORTIFY_SOURCE either, so removing it is also what keeps the
37+
# arms comparable rather than a local workaround.
38+
build --copt=-U_FORTIFY_SOURCE
39+
40+
# The autoconfigured toolchain appends `-lstdc++ -lm` to every link. This arm is
41+
# libc++ (the payload clang's config file sets -stdlib=libc++), and linking the
42+
# two standard libraries into one binary is a coin flip decided by link order:
43+
# --as-needed happens to drop libstdc++ here, and "happens to" is not a
44+
# guarantee anyone should rely on for a 46k-line binary.
45+
build --repo_env=BAZEL_LINKLIBS=-lm

bench/projects/xlings/BUILD.bazel

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,45 @@
1-
# See MODULE.bazel: this cannot build xlings today, and the blocker is the
2-
# workspace boundary rather than anything about modules.
1+
# The bazel arm of the xlings control target. See MODULE.bazel for why the two
2+
# interesting things — the tree and its dependencies — are external repositories
3+
# rather than globs, and mcpp_registry.bzl for how they are built.
34
#
4-
# The shape a working version would take is kept here so the gap is legible:
5+
# THIS PACKAGE DECLARES ONE RULE ON PURPOSE, and that one rule is the point of
6+
# the file. It used to declare none: a comment-only BUILD.bazel makes
7+
# `bazel build //...` exit 0 in 0.14s with `Found 0 targets`, and the harness
8+
# published that as a 0.43s cold build in a column next to cmake's 94s.
9+
# bench/src/engines/bazel.cppm now asks `bazel query kind(rule, //...)` before it
10+
# will measure anything here, so an empty description reports a reason instead of
11+
# a number — but the fix for "no rules" is a rule.
512
#
6-
# cc_binary(
7-
# name = "xlings",
8-
# srcs = ["src/main.cpp"],
9-
# module_interfaces = glob(["src/**/*.cppm"]) + [
10-
# "std.cppm", # copied from libc++, listed FIRST
11-
# ],
12-
# includes = ["src/libs/json"],
13-
# defines = ["LIBARCHIVE_STATIC", "UNICODE", "_UNICODE"],
14-
# copts = ["-std=c++23", "-Wno-reserved-module-identifier"],
15-
# linkopts = ["-static-libstdc++"],
16-
# )
13+
# The alias, rather than the cc_binary itself, because `module_interfaces` takes
14+
# labels bazel can glob and the sources live in a submodule this package must not
15+
# write into. @xlings_tree carries the generated cc_binary; see _XLINGS_BUILD in
16+
# mcpp_registry.bzl for the source set and why it is not `src/main.cpp` alone.
1717
#
18-
# built with:
19-
# bazel build //:xlings --experimental_cpp_modules --features=cpp_modules --force_pic
18+
# MEASURED, on bazel 9.2.0 + rules_cc 0.2.22 with the payload clang 22.1.8,
19+
# `--compilation_mode=opt`, cold after `bazel clean`:
2020
#
21-
# ...from inside the xlings checkout, which is the part that does not work: this
22-
# description would have to be written INTO the tree being measured.
21+
# tree TUs compiled cold `--version`
22+
# xlings-2026.8.11.2 110 .cppm + 2 .cpp 46.4s xlings 2026.8.11.2
23+
# xlings-2026.8.13.1 110 .cppm + 92 .cpp 18.0s xlings 2026.8.13.1
24+
#
25+
# plus the dependency set, whose counts are the check that the .xpkg.lua
26+
# manifests are being read rather than the trees globbed: libarchive 127 (a glob
27+
# is 132), lua 32 (a glob is 34, and the two extra each define `main`), mbedtls
28+
# 108, xz 74, ftxui 73, zstd 26, zlib 15, bzip2 7, lz4 5, and 19 translation
29+
# units across the four mcpplibs packages — 18 of them module interfaces, one of
30+
# which is the generated mcpplibs.xpkg.lua_stdlib.
31+
#
32+
# bazel build //:xlings # the default pin
33+
# BENCH_PROJECT_ROOT=$PWD/xlings-2026.8.11.2 bazel build //:xlings
34+
# bazel run //:xlings -- --version
35+
#
36+
# The binary itself lands at
37+
# bazel-bin/external/+xlings_tree+xlings_tree/xlings
38+
# because that is the repository that owns the rule; `bazel run` above is the
39+
# path-independent way to reach it.
40+
41+
alias(
42+
name = "xlings",
43+
actual = "@xlings_tree//:xlings",
44+
visibility = ["//visibility:public"],
45+
)

0 commit comments

Comments
 (0)