Crafter.Debug/project.cpp
Jorijn van der Graaf fb2c06c32e
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
  #pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.

Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.

The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00

58 lines
2.2 KiB
C++

// 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;
namespace {
// The configurations this file makes besides the one it returns, at
// stable addresses for the pointers in `dependencies`. One set per load,
// not statics: a static would keep the first load's args for every load.
std::deque<Configuration> owned;
// This file's directory. A configuration a project file makes for itself
// keeps the path it is given, and "./" would later resolve against the
// consumer's directory instead of this one.
fs::path Root() {
return fs::path(std::source_location::current().file_name()).parent_path();
}
}
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
Configuration cfg;
cfg.path = "./";
cfg.name = "Crafter.Debug";
cfg.outputName = "Crafter.Debug";
cfg.type = ConfigurationType::LibraryStatic;
ApplyStandardArgs(cfg, args);
{
std::array<fs::path, 1> interfaces = { "interfaces/Crafter.Debug" };
std::array<fs::path, 0> implementations = {};
cfg.GetInterfacesAndImplementations(interfaces, implementations);
}
// The client, crafter-debug. One of the library's dependencies, so a
// consumer's debug build compiles it in the same pass as the app; an
// executable adds no link flags to what depends on it. Built for the same
// target as the library, and not through ApplyStandardArgs, which would
// turn it into a library when a consumer passes --lib.
Configuration& client = owned.emplace_back();
client.path = Root();
client.name = "crafter.debug-cli";
client.outputName = "crafter-debug";
client.type = ConfigurationType::Executable;
client.target = cfg.target;
client.march = cfg.march;
client.mtune = cfg.mtune;
client.sysroot = cfg.sysroot;
client.debug = cfg.debug;
{
std::array<fs::path, 0> interfaces = {};
std::array<fs::path, 1> implementations = { "implementations/main" };
client.GetInterfacesAndImplementations(interfaces, implementations);
}
cfg.dependencies.push_back(&client);
return cfg;
}