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..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 @@ -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..05a0aa4 100644 --- a/deps/cmake.cppm +++ b/deps/cmake.cppm @@ -118,8 +118,62 @@ 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 (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}") + 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}) + if(_a MATCHES " ") + set(_a "\"${_a}\"") + endif() + 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 +430,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 +451,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 +473,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 +551,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 +605,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; +}