feat(script): scripts in project.cpp, run with crafter-build run
A project.cpp could describe how a project is built, tested and linted, but not the chores around it: packaging, installing, publishing. Those stayed shell lines in a README or in someone's history. Configuration gains `scripts`, a plain list like `defines`. A Script is a name and a C++ callback, as a lint rule is; there is no script language and no config file. The callback gets a ScriptContext (the project, the project file's directory, the arguments) and returns the exit status. ctx.Run runs a shell command with the terminal attached and returns its status. It is only stored when project.cpp loads, which happens for every verb and for every project that depends on this one. `crafter-build run <name> [args...]` runs one, a bare `run` lists them. Only the scripts of the project crafter-build was pointed at are runnable, never a dependency's. Everything after the script's name is handed to the script untouched. The two branches that do this sit ahead of every other branch of the parse loop, --version and --help included. Below the verbs, `run package clean` would delete build/, and any other word would land in the project's args and so in the variant hash, moving the build to another directory. Options before the name keep their meaning, so project args go there. The driver is its own partition, :Script. It refuses a project with no scripts, a name that is empty or starts with '-' (it could never be typed), a name declared twice (running either would be a guess) and an unknown name, each with exit 1 and without running anything. A script that throws exits 1. A return value outside 0..255 becomes 1: an exit status is eight bits, and a raw wait status such as 256 would otherwise wrap to success. Two helpers in :Platform: - RunCommandAttached runs a command with inherited stdio. Every helper so far captured through popen, which is right for a compile step and useless for a command that runs for half an hour or asks a question. It reads the wait status through DecodeWaitStatus, which RunCommandChecked and RunCommandWithTimeout now share. - ShellQuote is exported. It existed twice, as ShellQuote in :External and ShellQuoteSh in :Test, with the same behaviour; both are gone. A script that puts a path into a command needs it too. Not included: a string shorthand for one-command scripts (a one-line lambda covers it) and a ctx.Build() helper (Build() is already exported). A new partition and new exported symbols: run ./build.sh. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
6dcdad4515
commit
561398013c
12 changed files with 313 additions and 56 deletions
|
|
@ -356,6 +356,45 @@ export namespace Crafter {
|
|||
bool needsAst = false;
|
||||
};
|
||||
|
||||
struct Configuration;
|
||||
|
||||
// What a Script's callback is handed when `crafter-build run <name>`
|
||||
// starts it.
|
||||
struct ScriptContext {
|
||||
// The Configuration project.cpp returned. Read the project through
|
||||
// this rather than capturing the local in CrafterBuildProject by
|
||||
// reference: that local is gone by the time a script runs.
|
||||
Configuration& project;
|
||||
// Absolute directory of the project file. Commands run in the
|
||||
// directory crafter-build was started from, like everything else it
|
||||
// does, so a script names its files through this.
|
||||
fs::path projectDir;
|
||||
// Everything the user typed after the script's name, untouched:
|
||||
// crafter-build interprets none of it.
|
||||
std::span<const std::string> args;
|
||||
// Run `command` through sh with the terminal attached — output
|
||||
// appears as it is produced, the command can prompt, Ctrl-C reaches
|
||||
// it — after echoing it as `$ command` (not under -q). Returns its
|
||||
// exit code, 128+signal when it was killed; return that from the
|
||||
// script to make it the exit code of crafter-build. Quote anything
|
||||
// that is not a literal with ShellQuote. Out-of-line like
|
||||
// LintContext's members: the callback runs from project.so.
|
||||
std::int32_t Run(std::string_view command) const;
|
||||
};
|
||||
|
||||
// A project script: a chore that belongs to the project but is not part
|
||||
// of building it (packaging, installing, publishing, serving), run by
|
||||
// name with `crafter-build run <name> [args...]`. `run` returns the
|
||||
// process exit code. It is only stored when project.cpp is loaded, which
|
||||
// happens for every verb and for every project that depends on this one,
|
||||
// so whatever a script needs to probe it probes when it runs. Only the
|
||||
// scripts of the project crafter-build was pointed at can be run, never
|
||||
// a dependency's.
|
||||
struct Script {
|
||||
std::string name;
|
||||
std::function<std::int32_t(ScriptContext&)> run;
|
||||
};
|
||||
|
||||
// The host target triple: what the resolved toolchain's clang++ prints
|
||||
// for -print-target-triple (see GetToolchain). Used as the default for
|
||||
// Configuration::target so projects don't have to hardcode it for the
|
||||
|
|
@ -446,6 +485,12 @@ export namespace Crafter {
|
|||
std::vector<Test> tests;
|
||||
// Lint rules for `crafter-build lint`. Populate via AddLintRule.
|
||||
std::vector<LintRule> lintRules;
|
||||
// Scripts for `crafter-build run <name>`, a plain list like defines:
|
||||
// cfg.scripts.push_back({"docs", [](ScriptContext& ctx) {
|
||||
// return ctx.Run("python3 tools/gen-docs.py");
|
||||
// }});
|
||||
// Not part of VariantId: a script changes nothing that is compiled.
|
||||
std::vector<Script> scripts;
|
||||
void GetInterfacesAndImplementations(std::span<fs::path> interfaces, std::span<fs::path> implementations);
|
||||
// Retry the `import X;` names GetInterfacesAndImplementations could not
|
||||
// place, against the dependency DAG as it stands now. Sources are
|
||||
|
|
|
|||
Loading…
Reference in a new issue