Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/provider-contract-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ A `before generate`, `after generate` or `before build` hook receives `env_file`

**Freshness.** The environment is built for each build from the inherited environment plus the contributions of the hooks that ran in that build; a watched rebuild starts over. A hook a replan removed leaves nothing behind. A watched rebuild that fails, at any stage, keeps the environment of the last successful build (the one being served, and the one the session's `after run` hooks get); only a rebuild that succeeds replaces it.

**Scope.** The merged environment reaches every later zig invocation and hook process of the same command: the generation-time fingerprint pass (`zig build --list-steps`, which configures the generated build and so is the first toolchain consumer: a `before generate` contribution reaches it, an `after generate` one does not), the core `zig build` compile, and every later hook and replacement (`after generate`, `before`/`replace`/`after build`, `bundle` and `run` hooks). It never reaches the build of a provider's own tool (`zig build <build_step>` in the provider's package), which runs on the plain inherited environment plus the CLI's cache variables, so a provider cannot change how other providers' tools are built. The game the core `labelle run` launches keeps its own environment (the inherited one plus the run options); a provider that must reach the game uses a `run` replacement. The runtime `SDL2.dll` the core stages beside a desktop exe is looked up through `LABELLE_SDL2_LIB` as the compile saw it, so a contributed value is honoured.
**Scope.** The merged environment reaches every later zig invocation and hook process of the same command: the generation-time fingerprint pass (`zig build --list-steps`, which configures the generated build and so is the first toolchain consumer: a `before generate` contribution reaches it, an `after generate` one does not), the core `zig build` compile, and every later hook and replacement (`after generate`, `before`/`replace`/`after build`, `bundle` and `run` hooks). It never reaches the build of a provider's own tool (`zig build <build_step>` in the provider's package), which runs on the plain inherited environment plus the CLI's cache variables, so a provider cannot change how other providers' tools are built. The game the core `labelle run` launches keeps its own environment (the inherited one plus the run options); a provider that must reach the game uses a `run` replacement. The core stages no runtime library beside a built exe: it only merges the contribution into the environment above. Staging one (a DLL next to a desktop exe, say) is a provider's job, in an `after build` hook, which sees the merged environment as the compile did; the `sdl2` provider's `stage` hook copies `SDL2.dll` this way (cli#471 S4).

**No silent bypass.** A build path that cannot carry a provider's contribution or its optimize default refuses before any hook, generation or build, naming the hook or package. `--docker` builds in a container the contributions never reach, so a `build`, `run` or `bundle` (any command that reaches the container build) whose plan has a hook that may contribute (one in a contributing slot whose provider negotiates `1.3.0` or newer) is refused; `labelle generate --docker` is not, since it stops after generation and its host-side fingerprint pass gets the contributions, and neither is a build the target owner replaces, since its `replace build` hook gets them like every hook: `--docker doesn't carry provider environment contributions; build without --docker`. (`--docker` does pass the effective optimize mode as `-Doptimize`.) The legacy `labelle ios` subcommand runs its own `zig build` after generation, with neither, so it is refused when the plan has such a hook or the target's owner declares an optimize default; `labelle build --platform=ios` carries both.

Expand Down
9 changes: 5 additions & 4 deletions src/agnostic_guard_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ const allowed_words = [_][]const u8{ "macos", "windows", "linux", "darwin", "win
/// the target dir, and `upgrade all` delegates the backend pin to
/// `labelle-assembler upgrade backend`), so `cli/upgrade.zig` came clean,
/// and `cli/pipeline/generate.zig` once its comments stopped citing
/// backend and platform names: 14 entries.
/// backend and platform names: 14 entries. RFC cli#471 S4 moved SDL2
/// provisioning, runtime DLL staging and the doctor rows to the opt-in
/// `sdl2` provider (labelle-sdl), deleting `cli/sdl_provision.zig`:
/// 13 entries.
/// Shrink only: an entry whose file is clean fails the test until it is
/// removed. Note the path scan: an entry
/// under `cli/android/` or named `cli/ios.zig` stays dirty until the file is
Expand All @@ -134,7 +137,6 @@ const allowed_files = [_][]const u8{
"cli/pipeline/run.zig",
"cli/project_config.zig",
"cli/provider_dispatch.zig",
"cli/sdl_provision.zig",
};

const finding_note = "(platform/store/package names belong in providers; see docs/rfc-package-commands.md#enforcement)";
Expand Down Expand Up @@ -600,13 +602,12 @@ test "a platform in the path is a finding, and keeps an allowlist entry dirty" {
// An allowlisted file whose path names a platform stays dirty with
// clean contents: the entry is only stale once the file is moved or
// renamed.
try scan.file("cli/sdl_provision.zig", "const x = 1;\n");
try scan.file("cli/ios.zig", "const x = 1;\n");
try scan.file("cli\\ios.zig", "");
try std.testing.expectEqual(@as(usize, 7), scan.offenders.items.len);
var stale: std.ArrayList([]const u8) = .empty;
defer stale.deinit(gpa);
try scan.stale(&stale);
try std.testing.expect(!containsString(stale.items, "cli/sdl_provision.zig"));
try std.testing.expect(!containsString(stale.items, "cli/ios.zig"));
try std.testing.expect(containsString(stale.items, "cli/project_config.zig"));
}
Expand Down
1 change: 0 additions & 1 deletion src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ const provider_contract = @import("cli/provider_contract.zig");
const provider_github = @import("cli/provider_github.zig");
const provider_targets = @import("cli/provider_targets.zig");
const doctor = @import("cli/doctor.zig");
const sdl_provision = @import("cli/sdl_provision.zig");

// Argument parsing lives in cli/args.zig (extracted so neither file
// exceeds ~1000 lines). Alias the decls main/dispatch reference so their
Expand Down
14 changes: 5 additions & 9 deletions src/cli/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,6 @@ pub const reserved_env = [_][]const u8{
/// with the reason it is not CLI-owned. Kept beside `reserved_env` so the
/// guard can tell a deliberate decision from a forgotten name.
pub const unreserved_labelle_env = [_][]const u8{
// The SDL2 library directory. The CLI reads it only to find a user's
// install; a package that provisions SDL2 is meant to set it for the
// build (RFC cli#466 §9).
"LABELLE_SDL2_LIB",
// Written into a bundle's launcher script for the game; never read by
// the CLI.
"LABELLE_DATA_DIR",
Expand All @@ -297,17 +293,17 @@ test "provider env reserved names: a fixed CLI-owned table, case-folded under Wi
try std.testing.expect(reservedEnvName(name, false));
try std.testing.expect(reservedEnvName(name, true));
}
// Not a prefix ban: the SDL2 library variable and any other name stay
// settable, and an env_file setting it parses.
for ([_][]const u8{ "LABELLE_SDL2_LIB", "LABELLE_ANYTHING", "TOOLCHAIN_ROOT" }) |name| {
// Not a prefix ban: a provider's own `LABELLE_*` variable and any other
// name stay settable, and an env_file setting one parses.
for ([_][]const u8{ "LABELLE_ANYTHING", "TOOLCHAIN_ROOT" }) |name| {
try std.testing.expect(!reservedEnvName(name, false));
try std.testing.expect(!reservedEnvName(name, true));
}
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var diag: @import("provider_env.zig").Diagnostic = .{};
const file = try @import("provider_env.zig").parseFile(arena.allocator(), "{\"set\":[{\"name\":\"LABELLE_SDL2_LIB\",\"value\":\"/sdl/lib\"}]}", false, &diag);
try std.testing.expectEqualStrings("LABELLE_SDL2_LIB", file.set[0].name);
const file = try @import("provider_env.zig").parseFile(arena.allocator(), "{\"set\":[{\"name\":\"LABELLE_ANYTHING\",\"value\":\"/opt/lib\"}]}", false, &diag);
try std.testing.expectEqualStrings("LABELLE_ANYTHING", file.set[0].name);
// `Path` is PATH under Windows rules only.
try std.testing.expect(reservedEnvName("Path", true));
try std.testing.expect(!reservedEnvName("Path", false));
Expand Down
Loading
Loading