test: a project and its pragma dependency load a shared sibling apart
The fixture app has `#pragma crafter dependency "../tool"` and loads ../shared for itself, which the tool loads too; the app is loaded with no args, the case where the two cache keys used to match. With CRAFTER_BUILD_MARCH=x86-64-v2, the app's copy of shared has to keep that march and no -fPIC, and the tool's copy has to be host tooling. Before the fix the app got the tool's copy back: one Configuration, with march native and -fPIC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e420c9f958
commit
95896a7ddd
7 changed files with 161 additions and 0 deletions
|
|
@ -282,6 +282,8 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
||||||
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
||||||
cfg.AddTest("ProjectArgs").Dependencies({ CrafterBuildLib.get() })
|
cfg.AddTest("ProjectArgs").Dependencies({ CrafterBuildLib.get() })
|
||||||
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
||||||
|
cfg.AddTest("HostToolingSibling").Dependencies({ CrafterBuildLib.get() })
|
||||||
|
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
||||||
cfg.AddTest("Script").Dependencies({ CrafterBuildLib.get() })
|
cfg.AddTest("Script").Dependencies({ CrafterBuildLib.get() })
|
||||||
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
|
||||||
cfg.AddTest("ConcurrentDependencyReset").Dependencies({ CrafterBuildLib.get() });
|
cfg.AddTest("ConcurrentDependencyReset").Dependencies({ CrafterBuildLib.get() });
|
||||||
|
|
|
||||||
22
tests/HostToolingSibling/fixture/app/project.cpp
Normal file
22
tests/HostToolingSibling/fixture/app/project.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import Crafter.Build;
|
||||||
|
#pragma crafter dependency "../tool"
|
||||||
|
using namespace Crafter;
|
||||||
|
|
||||||
|
// Loads for itself the sibling its tool loads, by the same path and, when
|
||||||
|
// the app gets no args, with the same args.
|
||||||
|
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
||||||
|
Configuration* shared = LocalProject({ .projectFile = "../shared/project.cpp" });
|
||||||
|
|
||||||
|
Configuration cfg;
|
||||||
|
cfg.path = "./";
|
||||||
|
cfg.name = "app";
|
||||||
|
cfg.outputName = "app";
|
||||||
|
cfg.type = ConfigurationType::Executable;
|
||||||
|
ApplyStandardArgs(cfg, args);
|
||||||
|
cfg.dependencies = { shared, ProjectDependency("tool") };
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export module Shared;
|
||||||
|
import std;
|
||||||
|
export std::int32_t SharedValue() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
23
tests/HostToolingSibling/fixture/shared/project.cpp
Normal file
23
tests/HostToolingSibling/fixture/shared/project.cpp
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import Crafter.Build;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using namespace Crafter;
|
||||||
|
|
||||||
|
// The sibling both the tool and the app load. ApplyStandardArgs on purpose:
|
||||||
|
// it reads CRAFTER_BUILD_MARCH from the environment, which is how the test
|
||||||
|
// tells the app's copy from the one built as host tooling.
|
||||||
|
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
||||||
|
Configuration cfg;
|
||||||
|
cfg.path = "./";
|
||||||
|
cfg.name = "shared";
|
||||||
|
cfg.outputName = "shared";
|
||||||
|
cfg.type = ConfigurationType::LibraryStatic;
|
||||||
|
ApplyStandardArgs(cfg, args);
|
||||||
|
std::array<fs::path, 1> ifaces = { "interfaces/Shared" };
|
||||||
|
std::array<fs::path, 0> impls = {};
|
||||||
|
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export module Tool;
|
||||||
|
import std;
|
||||||
|
import Shared;
|
||||||
|
export std::int32_t ToolValue() {
|
||||||
|
return SharedValue();
|
||||||
|
}
|
||||||
24
tests/HostToolingSibling/fixture/tool/project.cpp
Normal file
24
tests/HostToolingSibling/fixture/tool/project.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import Crafter.Build;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using namespace Crafter;
|
||||||
|
|
||||||
|
// The app's `#pragma crafter dependency`, with a sibling of its own.
|
||||||
|
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
||||||
|
Configuration* shared = LocalProject({ .projectFile = "../shared/project.cpp" });
|
||||||
|
|
||||||
|
Configuration cfg;
|
||||||
|
cfg.path = "./";
|
||||||
|
cfg.name = "tool";
|
||||||
|
cfg.outputName = "tool";
|
||||||
|
cfg.type = ConfigurationType::LibraryStatic;
|
||||||
|
ApplyStandardArgs(cfg, args);
|
||||||
|
cfg.dependencies = { shared };
|
||||||
|
std::array<fs::path, 1> ifaces = { "interfaces/Tool" };
|
||||||
|
std::array<fs::path, 0> impls = {};
|
||||||
|
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
79
tests/HostToolingSibling/main.cpp
Normal file
79
tests/HostToolingSibling/main.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
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<std::string_view, 0> 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;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue