Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,65 @@ jobs:
"$1" "$2" "$3" "$4" "$5" "$6" "$i" "$7"
done
}
if [ "$full" = 1 ]; then ln=3; mn=1; wn=2; else ln=1; mn=1; wn=1; fi
# Shard count follows the WORK, per platform, measured.
#
# It used to be binary — full run: linux 3 / macos 1 / windows 2,
# anything else: 1 each — which asserts two things that are not
# true. "A partial run is small": touching a widely consumed
# descriptor selects every member that consumes it, and this PR's
# `pkgs/c/compat.openssl.lua` selected four; one linux shard took
# grpc-codegen (3563s) and grpc-module (1701s) back to back and was
# cancelled at exactly 1h30m, the job cap. "A full macOS run fits in
# one shard": at 6922s measured it does not — it fit only while the
# total sat just under the cap, and the run that added grpc-codegen
# (1715s on macOS) pushed it to 1h30m20s.
#
# Sharding is not only a wall-clock lever. Each shard is its own job
# with its own `timeout-minutes`, so it is also how the work is made
# to FIT. That is the half the "macOS concurrency is 1, so extra
# shards run back to back" note above left out: back to back is fine
# when the alternative is not finishing.
#
# tests/member-timings.tsv already holds every member's measured
# cost and plan_shards.lua already packs by it (LPT), so the fan-out
# comes from the same table: sum this run's members for that
# platform and take one shard per ~70 minutes, which leaves ~20
# minutes of headroom under the 90-minute cap for a cold cache.
#
# The caps are where runner concurrency comes back in: linux 3 (the
# measured concurrency — a 4th shard would queue), macOS 2, windows
# 2. Past those, more shards buy fit that is already there and pay
# another checkout + mcpp download + cache restore.
#
# Full run, from the table: linux 13570s over 3 -> ~75min/shard
# (observed slowest 77), macOS 6922s over 2 -> ~57, windows 8043s
# over 2 -> ~67. linux is the tight one — its cap binds before the
# ~70-minute target does, so it is the first place to look if a
# cold full run starts brushing 90 again. The levers, in order:
# raise the linux cap to 4 (costs a queued runner), then the job
# timeout.
shards_for() { # platform cap -> shard count
local secs
secs=$(XPLAT="$1" lua5.4 -e '
local plat = os.getenv("XPLAT")
local sel, all = {}, (os.getenv("MEMBERS") == "__ALL__")
if not all then
for m in (os.getenv("MEMBERS") or ""):gmatch("%S+") do sel[m] = true end
end
local total = 0
for line in io.lines("tests/member-timings.tsv") do
local p, m, s = line:match("^(%S+)\t(%S+)\t(%d+)$")
if p == plat and m and (all or sel[m]) then total = total + tonumber(s) end
end
print(total)')
local n=$(( secs / 4200 + 1 ))
[ "$n" -gt "$2" ] && n="$2"
echo "$1 work: ${secs}s (measured) -> $n shard(s)" >&2
echo "$n"
}
ln=$(shards_for linux 3)
mn=$(shards_for macos 2)
wn=$(shards_for windows 2)
{
printf '{"include":['
emit linux ubuntu-latest linux-x86_64 tar.gz bin/mcpp registry/bin/xlings "$ln"
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ members = [
"tests/examples/godot-cpp-module",
"tests/examples/godot-cpp-module-v10",
"tests/examples/godot-cpp-v10",
"tests/examples/grpc-codegen",
"tests/examples/grpc-module",
"tests/examples/gui-stack",
"tests/examples/imgui",
Expand Down
91 changes: 87 additions & 4 deletions pkgs/c/compat.openssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,23 @@ package = {

xpm = {
linux = {
deps = { "xim:make@latest", "xim:perl@latest" },
-- glibc + linux-headers are here for the same reason make and perl
-- are: this package builds through its own Makefile with a bare
-- `cc`, so every tool it uses has to be something this descriptor
-- resolved, not something it hopes to find. See cc_override().
--
-- The specs are xim:gcc's own, character for character, and NOT
-- `@latest`. openssl's objects are linked into projects built by
-- that gcc, so the C library it compiles against has to be the one
-- the toolchain uses; `@latest` would be free to resolve a NEWER
-- glibc than the toolchain's and introduce symbols the final link
-- cannot satisfy. Matching the ranges means both resolve the same
-- node -- already on disk wherever gcc is, so this costs a
-- resolution rather than a download.
deps = {
"xim:make@latest", "xim:perl@latest",
"xim:glibc@>=2.39", "xim:linux-headers@5.11.1",
},
["3.5.1"] = {
url = {
GLOBAL = "https://github.com/openssl/openssl/releases/download/openssl-3.5.1/openssl-3.5.1.tar.gz",
Expand Down Expand Up @@ -161,13 +177,80 @@ end
-- inherit the resolved toolchain's sysroot flags — it just runs `cc`. On macOS
-- the toolchain in PATH is xim's llvm, which has no macOS SDK wired up, so
-- every compile would fail on <stdio.h>. Pin Apple's own driver, which finds
-- the SDK by itself. Left alone elsewhere: on linux the xim gcc carries its
-- own payload and is the right compiler to use.
-- the SDK by itself.
--
-- On linux the compiler used to be left to PATH, on the strength of "the xim
-- gcc carries its own payload and is the right compiler to use". The binary is
-- right; the assumption that it is SELF-SUFFICIENT is not. PATH reaches it
-- through its xvm shim, and the shim injects `--sysroot=<subos>` resolved
-- against **the subos the calling process resolved to** (xim-pkgindex
-- pkgs/g/gcc.lua) -- whose own comment states the other half of the contract:
--
-- Consumers that bypass the shim supply their own header flags.
--
-- OpenSSL's Makefile is exactly such a consumer: it calls a bare `cc`. So the
-- flags are this hook's business, the same way `make` and `perl` already are.
--
-- Leaving it ambient breaks whenever the resolved subos is not the one holding
-- the toolchain. This hook runs with its cwd inside the CONSUMING PROJECT's
-- xlings home (<proj>/.mcpp/.xlings/data/runtimedir/openssl-<v>), so the subos
-- resolves to the PROJECT one, which holds only what that project installed --
-- no usr/include, usually no usr/ at all. A `--sysroot` at an empty tree does
-- not fall back to the default search, it SUPPRESSES it, and every compile
-- dies a long way from the cause:
--
-- include/internal/common.h:14:11: fatal error: stdlib.h: No such file
-- .../xim-x-gcc/16.1.0/lib/gcc/.../limits.h:210:15: fatal error: limits.h
--
-- (the second is gcc's own `#include_next`, which reads like a broken compiler
-- and is not).
--
-- So resolve the C library the way make and perl are resolved -- from declared
-- build deps -- and hand openssl the three things the payload gcc needs:
-- headers (-isystem), crt objects (-B) and -lc (-L). `--sysroot` is re-pointed
-- at the glibc payload root, which is NOT FHS-shaped and therefore contributes
-- no search path of its own: it neutralises whatever the shim injected without
-- opening a door to the host's /usr/include. A later --sysroot wins, so this
-- needs no cooperation from the shim.
--
-- This is a local restatement of what mcpp's own link model does
-- (src/build/flags.cppm, "payload-first, --sysroot fallback"). Duplicating a
-- decision is a real cost; the alternative is worse, because openssl builds
-- outside mcpp's compile rules by construction and has no other way to be told.
-- If xim ever hands install() hooks a ready CC/CFLAGS, this should become that.
local function libc_payloads()
local glibc = pkginfo.build_dep("xim:glibc") or pkginfo.build_dep("glibc")
local kern = pkginfo.build_dep("xim:linux-headers")
or pkginfo.build_dep("linux-headers")
local groot = glibc and glibc.path
local kroot = kern and kern.path
if not (groot and os.isfile(path.join(groot, "include", "stdlib.h"))) then
return nil
end
return groot, kroot
end

local function cc_override()
if os.host() == "macosx" and os.isfile("/usr/bin/cc") then
return "CC=/usr/bin/cc "
end
return ""
if os.host() ~= "linux" then return "" end

local groot, kroot = libc_payloads()
if not groot then
log.warn("openssl: no xim:glibc payload resolved; leaving CC to PATH"
.. " (fails if the active subos carries no libc headers)")
return ""
end
local libdir = os.isdir(path.join(groot, "lib64"))
and path.join(groot, "lib64") or path.join(groot, "lib")
local cc = "gcc --sysroot=" .. groot
.. " -isystem " .. path.join(groot, "include")
if kroot and os.isdir(path.join(kroot, "include")) then
cc = cc .. " -isystem " .. path.join(kroot, "include")
end
cc = cc .. " -B " .. libdir .. " -L " .. libdir
return "CC=" .. sh_quote(cc) .. " "
end

-- Does this perl actually RUN, with the core modules Configure opens with?
Expand Down
18 changes: 9 additions & 9 deletions pkgs/g/grpc-plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,28 @@ package = {
linux = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
macosx = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
windows = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
},
Expand Down
19 changes: 10 additions & 9 deletions pkgs/g/grpc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
-- 索引(#151),满打满算一天,唯一的消费者是本仓的 tests/examples/grpc-module。
-- 为一天的历史留一份永久重复条目不划算。
--
-- 归档换成 v1.83.0-2:`src/` / `include/` / `third_party/` 与 v1.83.0 逐字节相同,
-- 多出的只有 plugin/(grpc_cpp_plugin)与 rules/(grpcgen)。v1.83.0 的 sha256 已
-- 经发布过,移动 tag 会让它校验失败(mcpp#349:发布数据不得让程序失效),故另起。
-- 归档现为 v1.83.0-4。历次重打包中 `src/` / `include/` / `third_party/` 与
-- v1.83.0 逐字节相同,变的只有 plugin/(grpc_cpp_plugin)、rules/(grpcgen)
-- 与本包 manifest 的 codegen feature。已发布过的 sha256 不能靠移动 tag 复用
-- (mcpp#349:发布数据不得让程序失效),所以每次重打包都另起一个 tag。
-- Form A descriptor: the public gRPC package ships its own mcpp.toml, so this
-- file carries metadata and a download address and nothing else. mcpp's default
-- lookup finds <verdir>/*/mcpp.toml inside the GitHub source tarball wrap.
Expand Down Expand Up @@ -58,19 +59,19 @@ package = {
linux = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
macosx = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
-- No windows block, and the reason is a DEPENDENCY rather than gRPC:
Expand Down
18 changes: 9 additions & 9 deletions pkgs/g/grpcgen.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,28 @@ package = {
linux = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
macosx = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
windows = {
["1.83.0"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-3.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-3.tar.gz",
GLOBAL = "https://github.com/mcpplibs/grpc-m/archive/refs/tags/v1.83.0-4.tar.gz",
CN = "https://gitcode.com/mcpp-res/grpc/releases/download/1.83.0/grpc-m-1.83.0-4.tar.gz",
},
sha256 = "9a0514a325e348fb013a89bb7b1acb9b41f42d5e9f3c92cee8be788e1e48d74f",
sha256 = "991e9928e0fde0bd9a9fdc80229d976438f03d953b6679a6a71edab258c54baa",
},
},
},
Expand Down
23 changes: 23 additions & 0 deletions tests/examples/grpc-codegen/build.mcpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// One line, and it finds every .proto under proto/ by itself.
//
// The rule comes from the `grpcgen` package, which this project does NOT
// declare on linux/macOS: `grpc = { features = ["codegen"] }` re-exports it,
// along with protoc and grpc_cpp_plugin. Which tools gRPC codegen needs is the
// gRPC package's knowledge.
import std;
import mcpp;
import grpcgen;

int main() {
// No gRPC on windows — compat.openssl has no windows xpm entry, so the
// grpc package is linux/macOS only and there is nothing to generate. The
// guard is on the CALL, not the import: `import grpcgen;` is a
// compile-time dependency and cannot be conditional (docs/01 §4.2 forbids
// #if-guarded imports), so the module is declared on windows too — it is a
// pure C++23 rule package with no platform-limited dependencies of its own.
//
// Same shape as tests/examples/protobuf-protoc: return 0 so the member
// still builds, and let the test compile to a visible skip.
if (std::string_view(mcpp::target_os()) == "windows") return 0;
return grpcgen::generate_all() ? 0 : 1;
}
46 changes: 46 additions & 0 deletions tests/examples/grpc-codegen/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# grpc-codegen test project: the ONE-DEPENDENCY form of gRPC codegen.
#
# Its sibling tests/examples/grpc-module covers the module surface with no
# codegen at all. This member covers what that one's header says is impossible:
#
# "NO protoc output anywhere: gRPC's codegen needs host tools mcpp cannot
# hand a consumer"
#
# That stopped being true in mcpp 2026.8.6.2. `features = ["codegen"]` makes
# the grpc package re-export protoc, grpc_cpp_plugin and the grpcgen rule to
# whoever depends on it, so this manifest names ONE package and build.mcpp is
# one line — and the .proto below is compiled from a stub that did not exist
# when the build started.
#
# What this member protects, and what no other member can:
# * `reexport = true` really reaches a consumer through the published index
# (every other test of it is a path dependency in mcpp's own e2e);
# * `rerun_if_changed_glob` really drives `generate_all()` — the file list is
# never written down anywhere;
# * the host tools are built for the BUILD machine and produce stubs that
# link against the runtime this project links, which is the property the
# whole design exists to make inexpressible-to-get-wrong.
#
# linux + macOS only, for the same reason grpc-module is: compat.openssl has no
# windows xpm entry, so on windows this member carries no dependency and the
# test compiles to a no-op main().
[package]
name = "grpc-codegen-tests"
version = "0.1.0"

# `cfg(unix)`, not one block per OS: the condition is "everywhere gRPC
# resolves", and what excludes windows is compat.openssl having no xpm entry
# there — a single fact, so a single predicate. mcpp evaluates `unix` as
# family == "unix" (src/build/prepare.cppm, alongside any()/all()/not()).
[target.'cfg(unix)'.dependencies.grpc]
grpc = { version = "1.83.0", features = ["codegen"] }

[target.'cfg(unix)'.build]
cxxflags = ["-DHAVE_GRPC=1"]

# windows carries the RULE but not gRPC itself, so build.mcpp compiles (its
# `import grpcgen;` cannot be conditional) and then returns early. grpcgen is a
# pure C++23 rule package — no openssl, no platform-limited dependency — which
# is why it is available here while grpc is not.
[target.'cfg(windows)'.dependencies.mcpplibs]
grpcgen = { version = "1.83.0", host-module = true }
Loading
Loading