From 6f4c056dc55399c89223a0aca81dcf468fc0d271 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 30 Oct 2026 01:40:27 +0900 Subject: [PATCH 1/3] 0.21.0: deps-cmake compiles a cross build's subproject from the engine's contract When the build's target is not the machine, the subproject is configured with the compilers, words and archiver of the engine's contract for the configuration (${mcpp.contract}, mcpp 2026.10.30.1+), read when the action runs, under Ninja; CMAKE_SYSTEM_NAME is the target's system and the try-compile builds a static library. The package needs mcpp 2026.10.30.1. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/scripts/check-deps-and-qt.sh | 24 ++++- .github/workflows/ci.yml | 5 + README.md | 12 ++- deps/cmake.cppm | 109 +++++++++++++++++++--- docs/deps.md | 21 +++++ docs/plugin-development.md | 2 +- mcpp.toml | 4 +- src/plugins.cppm | 2 +- tests/cmake-cross/answer/CMakeLists.txt | 9 ++ tests/cmake-cross/answer/answer.c | 14 +++ tests/cmake-cross/answer/include/answer.h | 8 ++ tests/cmake-cross/build.mcpp | 10 ++ tests/cmake-cross/mcpp.toml | 22 +++++ tests/cmake-cross/src/main.cpp | 8 ++ 14 files changed, 227 insertions(+), 23 deletions(-) create mode 100644 tests/cmake-cross/answer/CMakeLists.txt create mode 100644 tests/cmake-cross/answer/answer.c create mode 100644 tests/cmake-cross/answer/include/answer.h create mode 100644 tests/cmake-cross/build.mcpp create mode 100644 tests/cmake-cross/mcpp.toml create mode 100644 tests/cmake-cross/src/main.cpp diff --git a/.github/scripts/check-deps-and-qt.sh b/.github/scripts/check-deps-and-qt.sh index 9ab9af6..56cf920 100644 --- a/.github/scripts/check-deps-and-qt.sh +++ b/.github/scripts/check-deps-and-qt.sh @@ -210,6 +210,27 @@ cmake_consumer() { echo "ok: a file added to the subproject re-ran its installation" } +# A BUILD FOR ANOTHER TARGET (0.21.0): the subproject is compiled with the +# compilers and words of the engine's contract for the target, read when the +# action runs, and not with the machine's toolset. On a glibc machine, +# `--target x86_64-linux-musl` must give a subproject compiled against musl. +cmake_cross() { + cd "$ROOT/tests/cmake-cross" + rm -rf target + mkdir -p target/ci + "$MCPP" build --target x86_64-linux-musl > target/ci/build.log 2>&1 || { cat target/ci/build.log; fail "the cross build failed"; } + script=$(find target -path '*deps-cmake/answer/answer.cmake' | head -1) + [ -n "$script" ] || fail "no deps-cmake script was written" + grep -q "the engine's contract for x86_64-linux-musl" "$script" || { cat "$script"; fail "the script does not read the contract"; } + cache=$(find target -path '*deps-cmake/answer/build-*/CMakeCache.txt' | head -1) + grep -q '^CMAKE_SYSTEM_NAME:.*=Linux' "$cache" || { grep CMAKE_SYSTEM_NAME "$cache"; fail "CMAKE_SYSTEM_NAME is not Linux"; } + grep -q '^CMAKE_TRY_COMPILE_TARGET_TYPE:.*=STATIC_LIBRARY' "$cache" || fail "the try-compile links" + "$MCPP" run --target x86_64-linux-musl | tee target/ci/run.log + grep -q '^cmake-cross: the subproject was compiled against not glibc$' target/ci/run.log || + fail "the subproject was compiled with the machine's C library" + echo "ok: a subproject of a cross build is compiled from the engine's contract" +} + # AN INSTALLATION IS BUILT ONCE (0.18.0). deps-cmake keeps what a subproject # installed under a key that holds no path of the machine, and copies it into # the prefix of any later build with the same key. Only a toolset the engine @@ -673,6 +694,7 @@ case "${1:-}" in vcpkg-workspace) vcpkg_workspace ;; cmake-consumer) cmake_consumer ;; cmake-cache) cmake_cache ;; + cmake-cross) cmake_cross ;; qt-consumer) qt_consumer ;; qt-widgets-consumer) qt_widgets_consumer ;; qt-sdk-consumer) qt_sdk_consumer ;; @@ -682,5 +704,5 @@ case "${1:-}" in vcpkg-managed) vcpkg_managed ;; vcpkg-msbuild-refused) vcpkg_msbuild_refused ;; vcpkg-make-port) vcpkg_make_port ;; - *) echo "usage: $0 vcpkg-consumer|vcpkg-libcxx|archive-consumer|vcpkg-workspace|cmake-consumer|cmake-cache|qt-consumer|qt-widgets-consumer|qt-sdk-consumer|qt-import-only|plugin-logic|tool-sources|vcpkg-crt|vcpkg-managed|vcpkg-msbuild-refused|vcpkg-make-port"; exit 2 ;; + *) echo "usage: $0 vcpkg-consumer|vcpkg-libcxx|archive-consumer|vcpkg-workspace|cmake-consumer|cmake-cache|cmake-cross|qt-consumer|qt-widgets-consumer|qt-sdk-consumer|qt-import-only|plugin-logic|tool-sources|vcpkg-crt|vcpkg-managed|vcpkg-msbuild-refused|vcpkg-make-port"; exit 2 ;; esac diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6356622..f54455c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1756,6 +1756,11 @@ jobs: - name: deps-cmake keeps an installation and takes it for the same key run: bash .github/scripts/check-deps-and-qt.sh cmake-cache + # 0.21.0: a subproject of a build for another target is compiled from the + # engine's contract for it (mcpp 2026.10.30.1+): musl from a glibc machine. + - name: deps-cmake compiles a cross build's subproject from the engine's contract + run: bash .github/scripts/check-deps-and-qt.sh cmake-cross + - name: rules-qt runs moc, rcc, lupdate and lrelease, with the SDK the project declares run: bash .github/scripts/check-deps-and-qt.sh qt-consumer diff --git a/README.md b/README.md index cfdd8f0..cef0443 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ module name the member declares, and configures it there. ```toml [build-dependencies.mcpp] -plugins = { version = "0.20.0", features = ["rules-spirv"], host-module = true } +plugins = { version = "0.21.0", features = ["rules-spirv"], host-module = true } ``` ```cpp @@ -51,9 +51,11 @@ naming it to avoid a download, and the scenarios on both sides -- is in ## Members -From 0.20.0 the package as a whole needs mcpp 2026.10.27.1 (`[package] mcpp`), -whose protocol 17 states a platform's fields and whose `mcpp pack` writes -`mcpp-package.json` beside every staged tree (0.19.0 needed 2026.10.1.3, whose +From 0.21.0 the package as a whole needs mcpp 2026.10.30.1 (`[package] mcpp`), +which writes the configuration's contract that `deps-cmake` compiles a cross +build from (0.20.0 needed 2026.10.27.1, whose protocol 17 states a platform's +fields and whose `mcpp pack` writes `mcpp-package.json` beside every staged +tree; 0.19.0 needed 2026.10.1.3, whose protocol 15 states where each tool and payload comes from); the column states the release each member's behaviour first needed. @@ -76,7 +78,7 @@ the release each member's behaviour first needed. | `dist-web` | `mcpp.dist.web` | 2026.9.13.1 | Copies a `wasm32-emscripten` program and its files into a web directory with an `index.html`. | [dist](docs/dist.md#dist-web) | | `dist-apk` | `mcpp.dist.apk` | 2026.10.1.3 | Packs the native closure into a signed APK or App Bundle, with Java, Kotlin and Maven libraries. | [dist-apk](docs/dist-apk.md) | | `deps-vcpkg` | `mcpp.deps.vcpkg` | 2026.10.1.3 | Installs a `vcpkg.json` manifest as an action with the toolset mcpp resolved, and maps the prefix into the build. | [deps](docs/deps.md#deps-vcpkg) | -| `deps-cmake` | `mcpp.deps.cmake` | 2026.10.1.3 | Builds and installs a CMake subproject as an action with Ninja and the toolset mcpp resolved, keeps the installation for any build with the same key, and maps the prefix into the build. | [deps](docs/deps.md#deps-cmake) | +| `deps-cmake` | `mcpp.deps.cmake` | 2026.10.1.3; a cross build 2026.10.30.1 | Builds and installs a CMake subproject as an action with Ninja and the toolset mcpp resolved -- for another target, the compilers and words of the engine's contract -- keeps the installation for any build with the same key, and maps the prefix into the build. | [deps](docs/deps.md#deps-cmake) | | `deps-archive` | `mcpp.deps.archive` | 2026.10.1.3 | Extracts a zip archive the project keeps and places its tree beside the program. | [deps](docs/deps.md#deps-archive) | Some features add a sub-capability to a member: `dist-apk-kotlin` and diff --git a/deps/cmake.cppm b/deps/cmake.cppm index b96290f..f653d0d 100644 --- a/deps/cmake.cppm +++ b/deps/cmake.cppm @@ -118,8 +118,58 @@ struct options { // `~/.cache/mcpp-plugins/deps-cmake`). Read when the action runs; the place // is no part of what is built. See docs/deps.md. std::string cache; + // `CMAKE_SYSTEM_NAME` of a build for another target (0.21.0). Empty + // derives it from the target's system: Linux, Android, Darwin, iOS, + // Windows, Emscripten, Generic for bare metal, and Linux for an ok image, + // whose C library is POSIX at the source level. + std::string system_name; }; +// The `CMAKE_SYSTEM_NAME` of a target system (`mcpp::target_os()`). +inline std::string cmake_system_name(std::string_view os) { + if (os == "linux" || os == "openkal") return "Linux"; + if (os == "android") return "Android"; + if (os == "macos") return "Darwin"; + if (os == "ios") return "iOS"; + if (os == "windows") return "Windows"; + if (os == "emscripten") return "Emscripten"; + return "Generic"; +} + +// The CMake code that reads the engine's contract for this configuration +// (`${mcpp.contract}`, mcpp 2026.10.30.1+) into variables when the action +// runs: `_mcpp_` and `_mcpp_ld` the programs, `_mcpp_<...>_flags` +// their words joined, `_mcpp_ar` the archiver. Read then and not when this +// program plans: the engine writes the file after build programs have run. +inline std::string contract_reader() { + return R"(if(NOT EXISTS "${MCPP_CONTRACT}") + message(FATAL_ERROR "mcpp.deps.cmake: a build for another target reads the engine's contract, " + "and '${MCPP_CONTRACT}' does not exist; it is written by mcpp 2026.10.30.1 and later") +endif() +file(READ "${MCPP_CONTRACT}" _mcpp_contract) +function(_mcpp_words out) + string(JSON _n LENGTH "${_mcpp_contract}" ${ARGN}) + set(_w "") + if(_n GREATER 0) + math(EXPR _last "${_n} - 1") + foreach(_i RANGE ${_last}) + string(JSON _a GET "${_mcpp_contract}" ${ARGN} ${_i}) + string(APPEND _w " ${_a}") + endforeach() + endif() + string(STRIP "${_w}" _w) + set(${out} "${_w}" PARENT_SCOPE) +endfunction() +foreach(_lang c cxx asm) + string(JSON _mcpp_${_lang} GET "${_mcpp_contract}" compile ${_lang} program) + _mcpp_words(_mcpp_${_lang}_flags compile ${_lang} args) +endforeach() +string(JSON _mcpp_ld GET "${_mcpp_contract}" link program) +string(JSON _mcpp_ar GET "${_mcpp_contract}" link archiver) +_mcpp_words(_mcpp_ld_flags link args) +)"; +} + // The variable a `-D[:]=` argument defines; empty for any // other argument. inline std::string_view defined_variable(std::string_view arg) { @@ -376,11 +426,20 @@ inline prefix use(const options& opt) { const bool chosen = chooses_compiler(opt.cache_args); const bool generatorChosen = chooses_generator(opt.cache_args); const bool visualStudio = opt.generator == options::generator_kind::visual_studio; - auto tools = visualStudio ? ts::resolve(opt.toolset) : ts::resolve_named(opt.toolset); - if (!tools) { - std::cerr << std::format("{}: {}\n", who, tools.error()); + // A BUILD FOR ANOTHER TARGET IS COMPILED AS THE ENGINE COMPILES IT (0.21.0): + // the compilers, target words and archiver of the engine's contract, which + // the action reads when it runs. The toolset this program resolves is the + // machine's, and a cross build linked with it would not be the target's. + const std::string targetTriple = mcpp::target(), hostTriple = mcpp::host(); + const bool contract = !chosen && !targetTriple.empty() && !hostTriple.empty() + && targetTriple != hostTriple; + auto resolvedTools = visualStudio ? ts::resolve(opt.toolset) : ts::resolve_named(opt.toolset); + if (!resolvedTools && !contract) { + std::cerr << std::format("{}: {}\n", who, resolvedTools.error()); return {}; } + const ts::resolved_tools noTools{}; + const ts::resolved_tools* tools = resolvedTools ? &*resolvedTools : &noTools; const std::string crt = opt.crt_linkage.empty() ? tools->crt : opt.crt_linkage; if (!crt.empty() && crt != "static" && crt != "dynamic") { std::cerr << std::format("{}: options::crt_linkage is '{}'; it is \"static\" or \"dynamic\".\n", @@ -388,15 +447,16 @@ inline prefix use(const options& opt) { return {}; } const std::string ninja = mcpp::ninja_program(); - const bool named = !chosen && tools->how == ts::mechanism::chain && !tools->cxx.empty(); - const bool useNinja = named && !generatorChosen && !ninja.empty(); - const bool instance = !chosen && !generatorChosen && tools->how == ts::mechanism::instance; + const bool named = !contract && !chosen && tools->how == ts::mechanism::chain && !tools->cxx.empty(); + const bool useNinja = (named || contract) && !generatorChosen && !ninja.empty(); + const bool instance = !contract && !chosen && !generatorChosen && tools->how == ts::mechanism::instance; // One build directory per toolset statement: CMake refuses a cache made // with another generator or instance. What 0.16.0 configured stays in // `build/`. std::string key; - if (useNinja) key = "ninja;" + tools->identity + ";" + tools->cxx; + if (contract) key = "contract;" + targetTriple; + else if (useNinja) key = "ninja;" + tools->identity + ";" + tools->cxx; else if (named) key = "named;" + tools->identity + ";" + tools->cxx; else if (instance) key = "instance;" + tools->instance_dir + ";" + tools->toolset_version; const std::string name = opt.name.empty() ? source.filename().string() : opt.name; @@ -409,8 +469,9 @@ inline prefix use(const options& opt) { p.include = mcpp::deps::generic(root / opt.dirs.include); p.lib = mcpp::deps::generic(root / opt.dirs.lib); p.bin = mcpp::deps::generic(root / opt.dirs.bin); - p.mechanism = std::string(ts::name(named ? ts::mechanism::chain - : instance ? ts::mechanism::instance : ts::mechanism::detected)); + p.mechanism = contract ? std::string("contract") + : std::string(ts::name(named ? ts::mechanism::chain + : instance ? ts::mechanism::instance : ts::mechanism::detected)); const auto tool = cmake_exe(opt); const std::string cmake = tool.program; @@ -486,21 +547,41 @@ inline prefix use(const options& opt) { + (crt == "dynamic" ? "DLL" : "")); } for (auto const& x : opt.cache_args) same(x); + if (contract) { + same("-DCMAKE_SYSTEM_NAME=" + (opt.system_name.empty() ? cmake_system_name(mcpp::target_os()) + : opt.system_name)); + // A try-compile links nothing: the target's startup files and C + // library reach a program only through the engine's link. + same("-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY"); + keyed.push_back("contract " + targetTriple); + } - std::string steps = "execute_process(COMMAND ${CMAKE_COMMAND}"; + std::string steps = contract ? contract_reader() : std::string(); + steps += "execute_process(COMMAND ${CMAKE_COMMAND}"; for (auto const& x : configure) steps += "\n " + bracket(x); + if (contract) + steps += "\n \"-DCMAKE_C_COMPILER=${_mcpp_c}\" \"-DCMAKE_CXX_COMPILER=${_mcpp_cxx}\"" + "\n \"-DCMAKE_ASM_COMPILER=${_mcpp_asm}\" \"-DCMAKE_AR=${_mcpp_ar}\"" + "\n \"-DCMAKE_C_FLAGS=${_mcpp_c_flags}\" \"-DCMAKE_CXX_FLAGS=${_mcpp_cxx_flags}\"" + "\n \"-DCMAKE_ASM_FLAGS=${_mcpp_asm_flags}\"" + "\n \"-DCMAKE_EXE_LINKER_FLAGS=${_mcpp_ld_flags}\"" + "\n \"-DCMAKE_SHARED_LINKER_FLAGS=${_mcpp_ld_flags}\""; steps += "\n RESULT_VARIABLE rc)\n" "if(NOT rc EQUAL 0)\n message(FATAL_ERROR \"configure exited ${rc}\")\nendif()\n" "execute_process(COMMAND ${CMAKE_COMMAND} --build " + bracket(buildS) + " --config " + bracket(opt.config) + " --target install --parallel\n RESULT_VARIABLE rc)\n" "if(NOT rc EQUAL 0)\n message(FATAL_ERROR \"build and install exited ${rc}\")\nendif()\n"; std::string text = "# Written by mcpp.deps.cmake: configure, build and install " + name + ".\n" - "# toolset: " + ts::describe(*tools) + "\n"; + "# toolset: " + (contract ? std::string("the engine's contract for ") + targetTriple + : ts::describe(*tools)) + "\n"; const auto files = mcpp::deps::files_under(source); // A key states the compilers through the toolset's identity. On the // MSVC ABI that is the MSVC toolset, which a row compiler (clang-cl, // `compiler::row`) is not, so such an installation is not kept. - const bool identified = instance || (named && (!tools->msvc_abi || opt.toolset.cc == ts::compiler::abi_native)); + // A contract build is not kept either: its compilers are named by the + // contract when the action runs, after this key is written. + const bool identified = !contract + && (instance || (named && (!tools->msvc_abi || opt.toolset.cc == ts::compiler::abi_native))); if (!identified || opt.cache == "off") { text += steps; } else { @@ -520,7 +601,9 @@ inline prefix use(const options& opt) { a.id = id.c_str(); a.role = mcpp::roles::prepare; a.description = desc.c_str(); - a.arg(cmake.c_str()).arg("-P").arg(scriptS.c_str()); + a.arg(cmake.c_str()); + if (contract) a.arg("-DMCPP_CONTRACT=${mcpp.contract}"); + a.arg("-P").arg(scriptS.c_str()); // Named tools run with the environment the engine runs them with, and // their directories first on PATH. if (named) { diff --git a/docs/deps.md b/docs/deps.md index d9fcd74..0fc7753 100644 --- a/docs/deps.md +++ b/docs/deps.md @@ -256,6 +256,27 @@ Each toolset statement configures its own build directory, because CMake refuses a cache made with another generator or instance; the directory 0.16.0 configured stays `build/`. +**A build for another target (0.21.0, mcpp 2026.10.30.1+).** When the build's +target is not the machine (`mcpp build --target x86_64-linux-musl`, an ok image +`--target x86_64-openkal`), the toolset above is the machine's and is not used. +The subproject is compiled as the engine compiles the target: the action reads +the engine's contract for the configuration (`${mcpp.contract}`, see mcpp's +docs/30) when it runs, and configures with its compilers +(`CMAKE__COMPILER`), its words (`CMAKE__FLAGS`: target, sysroot, +target-side headers, platform and profile flags), its link words and its +archiver, under Ninja. `CMAKE_SYSTEM_NAME` is the target's system (Linux, +Android, Darwin, iOS, Windows, Emscripten, `Generic` for bare metal, and Linux +for an ok image, whose C library is POSIX at the source level), or +`options::system_name`; `CMAKE_TRY_COMPILE_TARGET_TYPE` is `STATIC_LIBRARY`, +because a program reaches the target's startup files and C library only through +the engine's link. The libraries the subproject installs are linked into the +program by that link. A tool the subproject runs at build time for the machine +(LLVM's `llvm-tblgen`) is built in the host graph, as a build dependency's tool, +and named to the subproject with a `cache_args` entry. An installation of a +cross build is not kept in the cache below: its compilers are named by the +contract when the action runs, after the key is written. Compilers or a +toolchain file in `cache_args` decide here too. + ### An installation is built once (0.18.0) The action keeps what it installed in a cache outside the package, and a later diff --git a/docs/plugin-development.md b/docs/plugin-development.md index 02a964b..970b63f 100644 --- a/docs/plugin-development.md +++ b/docs/plugin-development.md @@ -52,7 +52,7 @@ mcpp-index refuses such a package. ```toml [build-dependencies.mcpp] -plugins = { version = "0.20.0", features = ["plugins-core"], host-module = true, reexport = true } +plugins = { version = "0.21.0", features = ["plugins-core"], host-module = true, reexport = true } ``` `reexport = true` is needed when the plugin's consumers import L2 modules in diff --git a/mcpp.toml b/mcpp.toml index c2beb0f..707cbcf 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] name = "plugins" namespace = "mcpp" -version = "0.20.0" +version = "0.21.0" # The first mcpp release that speaks protocol 17 (SPEC-013, mcpp#847): the # platform's directives `mcpp.plugins.platform` states (`link_driver`, # `link_input`, `artifact_name`, `seal`, `run_host`), the `platform` phase, and @@ -9,7 +9,7 @@ version = "0.20.0" # reads. It carries protocol 15 (mcpp#755): `xpkg_source`, `xpkg_program`, # `xpkg_request`, `mcpp::decision`, `mcpp::toolchain`, `mcpp::phase`, and the # `provision = "on-request"` entries below. -mcpp = ">=2026.10.27.1" +mcpp = ">=2026.10.30.1" description = "Official mcpp build plugins: rule packages under mcpp.rules.*, build-time utilities under mcpp.tools.*, each member selected by a feature" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/src/plugins.cppm b/src/plugins.cppm index 8e30a34..ed8f95f 100644 --- a/src/plugins.cppm +++ b/src/plugins.cppm @@ -49,7 +49,7 @@ export namespace mcpp::plugins { // // One package, one version: the number lives in mcpp.toml, and the CI step // `the collection states its own version` compares the two. -inline constexpr std::string_view version = "0.20.0"; +inline constexpr std::string_view version = "0.21.0"; } // namespace mcpp::plugins diff --git a/tests/cmake-cross/answer/CMakeLists.txt b/tests/cmake-cross/answer/CMakeLists.txt new file mode 100644 index 0000000..cb133ab --- /dev/null +++ b/tests/cmake-cross/answer/CMakeLists.txt @@ -0,0 +1,9 @@ +# A static library whose one function says which C library compiled it. +cmake_minimum_required(VERSION 3.16) +project(answer LANGUAGES C) + +add_library(answer STATIC answer.c) +target_include_directories(answer PUBLIC $) + +install(TARGETS answer ARCHIVE DESTINATION lib) +install(FILES include/answer.h DESTINATION include) diff --git a/tests/cmake-cross/answer/answer.c b/tests/cmake-cross/answer/answer.c new file mode 100644 index 0000000..35ff5e3 --- /dev/null +++ b/tests/cmake-cross/answer/answer.c @@ -0,0 +1,14 @@ +#include "answer.h" +#include + +/* glibc defines __GLIBC__ in , which does not include; + does, and so does every glibc header that a musl one is not. */ +#include + +const char* answer_libc(void) { +#if defined(__GLIBC__) + return "glibc"; +#else + return "not glibc"; +#endif +} diff --git a/tests/cmake-cross/answer/include/answer.h b/tests/cmake-cross/answer/include/answer.h new file mode 100644 index 0000000..c5d972c --- /dev/null +++ b/tests/cmake-cross/answer/include/answer.h @@ -0,0 +1,8 @@ +#pragma once +#ifdef __cplusplus +extern "C" { +#endif +const char* answer_libc(void); +#ifdef __cplusplus +} +#endif diff --git a/tests/cmake-cross/build.mcpp b/tests/cmake-cross/build.mcpp new file mode 100644 index 0000000..dadfce0 --- /dev/null +++ b/tests/cmake-cross/build.mcpp @@ -0,0 +1,10 @@ +import std; +import mcpp; +import mcpp.deps.cmake; + +int main() { + mcpp::deps::cmake::options o; + o.source = "answer"; + o.libraries = { "answer" }; + return mcpp::deps::cmake::use(o) ? 0 : 1; +} diff --git a/tests/cmake-cross/mcpp.toml b/tests/cmake-cross/mcpp.toml new file mode 100644 index 0000000..6a24aab --- /dev/null +++ b/tests/cmake-cross/mcpp.toml @@ -0,0 +1,22 @@ +# Fixture: a CMake subproject built for ANOTHER TARGET (0.21.0). Built with +# `--target x86_64-linux-musl` on a glibc machine, the subproject is compiled +# with the compilers and words of the engine's contract for that target, not +# with the machine's toolset, and linked into the static program. +[package] +name = "cmake-cross" +version = "0.1.0" +description = "Fixture: a CMake subproject compiled for the build's target from the engine's contract" +license = "Apache-2.0" +authors = ["mcpp-community"] + +[language] +standard = "c++23" +modules = true +import_std = true + +[build-dependencies.mcpp] +plugins = { path = "../..", features = ["deps-cmake"], host-module = true } + +[targets.cmake-cross] +kind = "bin" +main = "src/main.cpp" diff --git a/tests/cmake-cross/src/main.cpp b/tests/cmake-cross/src/main.cpp new file mode 100644 index 0000000..863bec1 --- /dev/null +++ b/tests/cmake-cross/src/main.cpp @@ -0,0 +1,8 @@ +#include + +#include + +int main() { + std::printf("cmake-cross: the subproject was compiled against %s\n", answer_libc()); + return 0; +} From d82c89bc9365ec4d14e3c4144797facd07ecdd8d Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 30 Oct 2026 02:39:01 +0900 Subject: [PATCH 2/3] ci: the AppImage fixture asks that the refusal lists appimage, not the whole list of an older engine mcpp 2026.10.27.1 lists zip and single-file among the built-in formats. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f54455c..4a0f853 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1206,7 +1206,7 @@ jobs: set -e [ "$rc" = 2 ] || { echo "FAIL: --format bogus exited $rc, expected 2"; exit 1; } [ -z "$so" ] || { echo "FAIL: the refusal wrote to stdout"; exit 1; } - grep -q 'available in this build: tar, dir, appimage' <<<"$se" \ + grep -qE 'available in this build: .*\bappimage\b' <<<"$se" \ || { echo "FAIL: the refusal does not name appimage"; echo "$se"; exit 1; } "$MCPP" pack --format appimage | tee pack.log From 2b0d90c0a60a02c8a479e1f1938bec6379e742cb Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 30 Oct 2026 06:34:20 +0900 Subject: [PATCH 3/3] deps-cmake: a contract word with a space is quoted in the flags string, as CMake splits it Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- deps/cmake.cppm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deps/cmake.cppm b/deps/cmake.cppm index f653d0d..05a0aa4 100644 --- a/deps/cmake.cppm +++ b/deps/cmake.cppm @@ -139,7 +139,8 @@ inline std::string cmake_system_name(std::string_view os) { // The CMake code that reads the engine's contract for this configuration // (`${mcpp.contract}`, mcpp 2026.10.30.1+) into variables when the action // runs: `_mcpp_` and `_mcpp_ld` the programs, `_mcpp_<...>_flags` -// their words joined, `_mcpp_ar` the archiver. Read then and not when this +// their words joined (a word with a space quoted, as CMake splits a flags +// string), `_mcpp_ar` the archiver. Read then and not when this // program plans: the engine writes the file after build programs have run. inline std::string contract_reader() { return R"(if(NOT EXISTS "${MCPP_CONTRACT}") @@ -154,6 +155,9 @@ function(_mcpp_words out) math(EXPR _last "${_n} - 1") foreach(_i RANGE ${_last}) string(JSON _a GET "${_mcpp_contract}" ${ARGN} ${_i}) + if(_a MATCHES " ") + set(_a "\"${_a}\"") + endif() string(APPEND _w " ${_a}") endforeach() endif()