// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® #include import std; import Crafter.Build; namespace fs = std::filesystem; using namespace Crafter; // A sibling that a project and its `#pragma crafter dependency` both load // is two configurations: the tool's copy is host tooling, built for the // host and position-independent, and the project's is built the way the // project builds. ForceHostTooling rewrites the tool's graph in place, and // the project cache used to hand the project that same rewritten // Configuration whenever the two loads had the same key, as they do when // the project gets no args. The project then built against a sibling // compiled with -march=native while itself building for x86-64-v2, and // clang refused the sibling's BMI. namespace { std::int32_t Failures = 0; void Check(bool cond, std::string_view msg) { if (!cond) { std::println(std::cerr, "FAIL: {}", msg); ++Failures; } } } int main() { const fs::path installedHome = GetCrafterBuildHome(); if (!fs::exists(installedHome / "Crafter.Build.cppm")) { std::println(std::cerr, "SKIP: no module sources at {}", installedHome.string()); return 77; } // Staged because LoadProject drops a build/ tree next to every project // file it compiles, and the pre-pass builds the tool and its sibling. const fs::path fixture = fs::current_path() / "tests" / "HostToolingSibling" / "fixture"; const fs::path scratch = fs::temp_directory_path() / "crafter-build-host-tooling-sibling"; std::error_code ec; fs::remove_all(scratch, ec); fs::create_directories(scratch); fs::copy(fixture, scratch, fs::copy_options::recursive); // The march the project builds for. Host tooling overrides it to // native, so a sibling that comes back with native was rewritten. setenv("CRAFTER_BUILD_MARCH", "x86-64-v2", 1); // From the app's own directory, as crafter-build runs it: the fixture's // LocalProject paths are relative to the working directory. const fs::path cwd = fs::current_path(); fs::current_path(scratch / "app"); std::array noArgs = {}; try { Configuration cfg = LoadProject("project.cpp", noArgs); Check(cfg.dependencies.size() == 2, std::format("the app declared its sibling and its tool (got {})", cfg.dependencies.size())); if (cfg.dependencies.size() == 2) { const Configuration* shared = cfg.dependencies[0]; const Configuration* tool = cfg.dependencies[1]; const Configuration* toolShared = tool->dependencies.size() == 1 ? tool->dependencies[0] : nullptr; Check(toolShared != nullptr, "the tool declared its sibling"); Check(shared != toolShared, "the app's sibling is not the tool's copy of it"); Check(shared->march == "x86-64-v2", std::format("the app's sibling keeps the app's march (got {})", shared->march)); Check(!std::ranges::contains(shared->compileFlags, "-fPIC"), "the app's sibling is not built as host tooling"); if (toolShared) { Check(toolShared->march == "native", std::format("the tool's copy is built for the host (got {})", toolShared->march)); Check(std::ranges::contains(toolShared->compileFlags, "-fPIC"), "the tool's copy is position-independent"); } } } catch (const std::exception& e) { fs::current_path(cwd); std::println(std::cerr, "FAIL: LoadProject threw: {}", e.what()); return 1; } fs::current_path(cwd); return Failures > 0 ? 1 : 0; }