Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1,425 changes: 1,425 additions & 0 deletions .agents/docs/2026-08-12-mcpp-adaptation-design.md

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions .github/workflows/mcpp-linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: mcpp (linux)

# The mcpp build, on Linux with GCC 16. Runs alongside the existing
# build_and_dispatch.yml (Windows/MSVC via xmake) -- the two build systems are
# independent by design, see .agents/docs/2026-08-12-mcpp-adaptation-design.md
#
# WHAT IS AND IS NOT REQUIRED HERE
#
# `packages` is a required job: mo_yanxi.utility and mo_yanxi.vulkan_wrapper
# build clean and must stay that way.
#
# `root` is continue-on-error, and that is a statement about ONE known
# blocker rather than a way to ignore failures: mo_yanxi.react_flow hits a GCC
# 16 modules bug (`recursive lazy load / failed to load pendings for
# 'std::vector'`) that seven different workarounds do not move, and xrgui
# imports react_flow from its scene and overlay layers. Everything else in the
# root package -- 1332 objects, 626 BMIs -- already compiles. The job still
# runs and still prints what failed, so a NEW breakage is visible in the log;
# it just cannot gate the branch on a compiler bug. Flip it to required the
# day react_flow builds.

on:
push:
branches: [master, main, "feat/**"]
paths-ignore: ["**/*.md", "properties/showcase/**"]
pull_request:
paths-ignore: ["**/*.md", "properties/showcase/**"]
workflow_dispatch:

env:
# Pinned rather than "latest": this project leans on specific scanner and
# module-graph behaviour, so a silent mcpp bump should be a visible diff.
MCPP_VERSION: "2026.8.11.3"

concurrency:
group: mcpp-linux-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 90

steps:
- name: Checkout (with submodules)
uses: actions/checkout@v4
with:
submodules: recursive

- name: Install host libraries
# fontconfig is the Linux system font backend used by
# src/platform/font.ixx. Everything else xrgui needs comes from the
# mcpp index (glfw pulls the X11 stack, vulkan pulls the loader).
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends libfontconfig1-dev

- name: Install mcpp ${{ env.MCPP_VERSION }}
run: |
curl -L -fsS -o mcpp.tar.gz \
"https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz"
tar -xzf mcpp.tar.gz
root="$PWD/mcpp-${MCPP_VERSION}-linux-x86_64"
mkdir -p ~/.mcpp/registry
cp -a "$root/registry/." ~/.mcpp/registry/
echo "$root/bin" >> "$GITHUB_PATH"
echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV"

- name: Report versions
run: mcpp self version

- name: Apply submodule patches
# The GCC/mcpp source-conformance fixes for utility, vulkan_wrapper and
# allocator2d live in other repositories, so they ship as patches here
# until they are upstreamed. apply.sh is idempotent.
run: ./mcpp/patches/apply.sh

- name: Cache mcpp toolchain and package downloads
uses: actions/cache@v4
with:
# The GCC 16 toolchain plus the compat.* source tarballs are ~500 MB
# and none of it changes between runs unless a pin moves.
path: |
~/.mcpp/registry/data
~/.mcpp/build-cache
key: mcpp-${{ env.MCPP_VERSION }}-${{ hashFiles('mcpp.toml', 'mcpp/pkgs/*/mcpp.toml') }}
restore-keys: mcpp-${{ env.MCPP_VERSION }}-

# ── required ────────────────────────────────────────────────────
- name: Build mo_yanxi.utility
working-directory: mcpp/pkgs/moyanxi-utility
run: mcpp build

- name: Build mo_yanxi.vulkan_wrapper
working-directory: mcpp/pkgs/moyanxi-vulkan-wrapper
run: mcpp build

# ── known-blocked, informational ────────────────────────────────
- name: Build mo_yanxi.react_flow (known GCC 16 modules bug)
id: react_flow
continue-on-error: true
working-directory: mcpp/pkgs/moyanxi-react-flow
run: mcpp build

- name: Build xrgui (blocked only by react_flow)
id: root
continue-on-error: true
run: mcpp build

- name: Summarize
if: always()
run: |
{
echo "## mcpp (linux)"
echo
echo "| step | result |"
echo "|---|---|"
echo "| mo_yanxi.utility | ok (required) |"
echo "| mo_yanxi.vulkan_wrapper | ok (required) |"
echo "| mo_yanxi.react_flow | ${{ steps.react_flow.outcome }} — blocked on a GCC 16 modules bug |"
echo "| xrgui root | ${{ steps.root.outcome }} — blocked only by react_flow |"
echo
echo "react_flow's \`:manager\` partition declares \`std::vector\` members in an"
echo "exported class; any partition importing it and then lazily needing"
echo "\`std::vector\` recurses. See .agents/docs/2026-08-12-mcpp-adaptation-design.md §14.3."
} >> "$GITHUB_STEP_SUMMARY"
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@
/properties/assets_raw/.svg_normalize_cache/
/properties/assets_raw/gen/
/properties/assets_raw/shader/spv/
/compile_commands.json
compile_commands.json
target/
.mcpp/
mcpp/shims/
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
[submodule "external/mo_yanxi_vulkan_wrapper"]
path = external/mo_yanxi_vulkan_wrapper
url = https://github.com/Yuria-Shikibe/mo_yanxi_vulkan_wrapper.git
[submodule "external/plf_hive"]
path = external/plf_hive
url = https://github.com/mattreecebentley/plf_hive.git
[submodule "external/small_vector"]
path = external/small_vector
url = https://github.com/Yuria-Shikibe/small_vector.git
Expand Down
152 changes: 152 additions & 0 deletions build.mcpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Build-time asset codegen — the mcpp counterpart of xmake.lua's
// `media.svg_to_bin` rule plus its `before_build` hook.
//
// Two outputs, both under MCPP_OUT_DIR/.assets/includes:
//
// <rel>.svg.h one bin2c byte list per normalized SVG, meant to be
// #include'd INSIDE a `char[] = { … }` initializer
// assets_summary.h the aggregate gui.assets.cpp includes, declaring one
// `constexpr inline char[]` per SVG, namespaced by the
// SVG's directory
//
// bin2c is a dozen lines here rather than an external tool, which is simpler
// than xmake's `utils.binary.bin2c` dependency and removes one more reason to
// need a host toolchain just to build.
//
// The generated tree is written to the OUTPUT directory, never into
// properties/ -- a build must not dirty the source tree.
//
// Note the summary is emitted even when there are no SVGs at all. That is the
// normal state of a fresh checkout: properties/assets_raw/gen/ is produced by
// `xmake xrgui.gen_icon` and is not committed. gui.assets.cpp only needs the
// header to EXIST; an empty one compiles, which is what lets the tree build
// without Python/Node/slangc. See .agents/docs/2026-08-12-mcpp-adaptation-design.md §8.
import mcpp;
import std;

namespace {

std::string sanitize(std::string_view s) {
std::string out;
out.reserve(s.size());
for (const char c : s) {
out.push_back((c == '.' || c == '-') ? '_' : c);
}
return out;
}

// Directory path -> C++ namespace name: `icons/ui` becomes `icons::ui`,
// matching xmake.lua's `dir:gsub("[\\/]", "::"):gsub("[%.%-]", "_")`.
std::string namespace_of(const std::filesystem::path& rel_dir) {
std::string out;
for (const auto& part : rel_dir) {
if (!out.empty()) out += "::";
out += sanitize(part.string());
}
return out;
}

// Write `<file>` as a comma-separated byte list. The result is #include'd
// inside a braced initializer, so it must NOT carry braces of its own.
bool write_bin2c(const std::filesystem::path& src, const std::filesystem::path& dst) {
std::ifstream in(src, std::ios::binary);
if (!in) return false;

std::filesystem::create_directories(dst.parent_path());
std::ofstream out(dst, std::ios::trunc);
if (!out) return false;

char byte{};
for (std::size_t n = 0; in.get(byte); ++n) {
if (n != 0) out << ',';
if (n % 16 == 0) out << '\n';
out << static_cast<int>(static_cast<unsigned char>(byte));
}
out << '\n';
return true;
}

// Only rewrite when the content actually differs: the summary is an input to
// every TU that includes it, so touching it on every build would defeat
// incremental compilation.
void write_if_changed(const std::filesystem::path& path, std::string_view content) {
if (std::ifstream in{path}; in) {
const std::string existing{std::istreambuf_iterator<char>{in},
std::istreambuf_iterator<char>{}};
if (existing == content) return;
}
std::filesystem::create_directories(path.parent_path());
std::ofstream{path, std::ios::trunc} << content;
}

} // namespace

int main() {
const std::filesystem::path root = mcpp::manifest_dir();
const std::filesystem::path src_base = root / "properties/assets_raw/gen";
const std::filesystem::path out_dir =
std::filesystem::path{mcpp::out_dir()} / ".assets/includes";

std::filesystem::create_directories(out_dir);

std::vector<std::filesystem::path> svgs;
if (std::filesystem::is_directory(src_base)) {
for (const auto& e : std::filesystem::recursive_directory_iterator{src_base}) {
if (e.is_regular_file() && e.path().extension() == ".svg") {
svgs.push_back(e.path());
}
}
}
// Directory iteration order is unspecified; the summary must be stable or
// its content hash flaps and every consumer rebuilds.
std::ranges::sort(svgs);

std::string summary = "#pragma once\n";

for (const auto& svg : svgs) {
const auto rel = std::filesystem::relative(svg, src_base);
const auto header_rel = std::filesystem::path{rel.string() + ".h"};
const auto header_abs = out_dir / header_rel;

std::error_code ec;
const auto src_time = std::filesystem::last_write_time(svg, ec);
const auto dst_time = std::filesystem::last_write_time(header_abs, ec);
if (ec || !std::filesystem::exists(header_abs) || dst_time < src_time) {
if (!write_bin2c(svg, header_abs)) {
std::println("build.mcpp: cannot convert {}", svg.string());
return 1;
}
}

// Always spelled with '/' -- this string lands in a #include.
std::string inc = header_rel.generic_string();
const std::string ns = namespace_of(rel.parent_path());
const std::string var = sanitize(rel.filename().string());

if (!ns.empty()) summary += std::format("namespace {} {{\n", ns);
summary += std::format(
"#if __has_include(\"{0}\")\n"
"constexpr inline char {1}[] = {{\n"
"#include \"{0}\"\n"
"}};\n"
"#else\n"
"constexpr inline char {1}[] = {{}};\n"
"#endif\n",
inc, var);
if (!ns.empty()) summary += std::format("}} // namespace {}\n", ns);
}

write_if_changed(out_dir / "assets_summary.h", summary);

// Private to this package -- consumers of xrgui have no business seeing
// the generated tree.
mcpp::include_dir(out_dir.string().c_str());

// The SET of files matters, not just their contents: adding an icon has to
// regenerate the summary, and a plain rerun-if-changed on a directory
// would not catch it.
mcpp::rerun_if_changed_glob("properties/assets_raw/gen/**/*.svg");

std::println("build.mcpp: {} svg asset(s) -> {}", svgs.size(), out_dir.string());
return 0;
}
1 change: 0 additions & 1 deletion external/plf_hive
Submodule plf_hive deleted from 6a8a04
Loading
Loading