From 7e54061cbb1181dbc8dd7f0b37a1f12435a39e05 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 20 Aug 2026 04:18:26 +0200 Subject: [PATCH] chore(bazel): generated clippy config from workspace lints at release time - Added scripts/gen-clippy-bazelrc.ts: emits bazel/clippy.bazelrc from [workspace.lints] in Cargo.toml (groups by ascending priority, then per-lint overrides, alphabetical within each tier); --check mode verifies sync without writing. - release.ts regenerates it alongside the lockfiles; the release_gate CD job runs the --check so drift only blocks publishing, never ordinary CI. Added the gen:clippy package script. --- .github/workflows/ci.yml | 7 +++ bazel/clippy.bazelrc | 3 +- package.json | 1 + scripts/gen-clippy-bazelrc.ts | 86 +++++++++++++++++++++++++++++++++++ scripts/release.ts | 6 ++- 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100755 scripts/gen-clippy-bazelrc.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65b7cde66..67c66620f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -465,6 +465,13 @@ jobs: needs: [release_metadata, check, rust_validate, native_addons, test_workspace, test_coding_agent_singleton, test_ts_native, test_coding_agent_ui, test_coding_agent_runtime, test_coding_agent_native, test_smoke, install_methods] runs-on: ubuntu-22.04 steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/bun-install + # Generated-config drift gate: bazel/clippy.bazelrc is maintained by + # scripts/release.ts (like the lockfiles); a stale file only blocks + # publishing, never ordinary CI. + - name: Check generated clippy config + run: bun scripts/gen-clippy-bazelrc.ts --check - run: echo "release validation green" # Builds (does not publish) the Linux-hosted release binaries in parallel diff --git a/bazel/clippy.bazelrc b/bazel/clippy.bazelrc index 57b7b5ed7..c9750746b 100644 --- a/bazel/clippy.bazelrc +++ b/bazel/clippy.bazelrc @@ -1,4 +1,5 @@ -# Generated from [workspace.lints] in Cargo.toml (see .bazelrc clippy-strict). +# Generated by scripts/gen-clippy-bazelrc.ts from [workspace.lints] in Cargo.toml. +# Do not edit by hand; run `bun run gen:clippy` after changing the lint policy. # Applies only to crates that opt in via `[lints] workspace = true`. build:clippy-strict --@rules_rust//rust/settings:clippy_flag=-Amismatched_lifetime_syntaxes build:clippy-strict --@rules_rust//rust/settings:clippy_flag=-Wclippy::all diff --git a/package.json b/package.json index 5ed355ac4..ae8f8aa48 100644 --- a/package.json +++ b/package.json @@ -163,6 +163,7 @@ "publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run", "release": "bun scripts/release.ts", "gen:models": "bun --cwd=packages/catalog run gen:models", + "gen:clippy": "bun scripts/gen-clippy-bazelrc.ts", "gen:stats": "bun --cwd=packages/stats run gen:stats", "gen:stats:reset": "bun --cwd=packages/stats run gen:stats:reset", "gen:changelog": "bun scripts/rewrite-changelog.ts", diff --git a/scripts/gen-clippy-bazelrc.ts b/scripts/gen-clippy-bazelrc.ts new file mode 100755 index 000000000..dba7026c5 --- /dev/null +++ b/scripts/gen-clippy-bazelrc.ts @@ -0,0 +1,86 @@ +#!/usr/bin/env bun +/** + * Generates `bazel/clippy.bazelrc` from `[workspace.lints]` in `Cargo.toml` so + * the bazel `clippy-strict` config (applied to crates that opt in via + * `[lints] workspace = true`) can never drift from the cargo lint policy. + * + * Emission mirrors rustc lint-level resolution: `[workspace.lints.rust]` + * entries first (bare rustc lint flags), then clippy entries — negative + * `priority` values (the lint groups) before per-lint overrides, alphabetical + * within each tier. + * + * Usage: + * bun scripts/gen-clippy-bazelrc.ts # rewrite bazel/clippy.bazelrc + * bun scripts/gen-clippy-bazelrc.ts --check # exit 1 when the file is stale + */ +import * as path from "node:path"; + +type LintLevel = "allow" | "warn" | "deny" | "forbid"; +type LintEntry = LintLevel | { level: LintLevel; priority?: number }; +type LintTable = Record; + +const FLAG_BY_LEVEL: Record = { allow: "A", warn: "W", deny: "D", forbid: "F" }; + +const repoRoot = path.join(import.meta.dir, ".."); +const outputPath = path.join(repoRoot, "bazel", "clippy.bazelrc"); + +function normalize(entry: LintEntry): { level: LintLevel; priority: number } { + if (typeof entry === "string") return { level: entry, priority: 0 }; + return { level: entry.level, priority: entry.priority ?? 0 }; +} + +/** Lint names ordered by ascending priority tier, alphabetical within a tier. */ +function orderedNames(table: LintTable): string[] { + return Object.keys(table).sort((a, b) => { + const pa = normalize(table[a]).priority; + const pb = normalize(table[b]).priority; + if (pa !== pb) return pa - pb; + return a < b ? -1 : a > b ? 1 : 0; + }); +} + +function flagLines(table: LintTable, prefix: string): string[] { + return orderedNames(table).map(name => { + const { level } = normalize(table[name]); + return `build:clippy-strict --@rules_rust//rust/settings:clippy_flag=-${FLAG_BY_LEVEL[level]}${prefix}${name}`; + }); +} + +async function render(): Promise { + const cargo = Bun.TOML.parse(await Bun.file(path.join(repoRoot, "Cargo.toml")).text()) as { + workspace?: { lints?: { rust?: LintTable; clippy?: LintTable } }; + }; + const lints = cargo.workspace?.lints; + if (!lints?.clippy) { + throw new Error("Cargo.toml has no [workspace.lints.clippy] table"); + } + const lines = [ + "# Generated by scripts/gen-clippy-bazelrc.ts from [workspace.lints] in Cargo.toml.", + "# Do not edit by hand; run `bun run gen:clippy` after changing the lint policy.", + "# Applies only to crates that opt in via `[lints] workspace = true`.", + ...flagLines(lints.rust ?? {}, ""), + ...flagLines(lints.clippy, "clippy::"), + ]; + return `${lines.join("\n")}\n`; +} + +const expected = await render(); +const current = await Bun.file(outputPath) + .text() + .catch(() => ""); + +if (process.argv.includes("--check")) { + if (current !== expected) { + console.error( + "bazel/clippy.bazelrc is stale relative to [workspace.lints] in Cargo.toml.\n" + + "Run `bun run gen:clippy` and commit the result.", + ); + process.exit(1); + } + console.log("bazel/clippy.bazelrc is in sync with Cargo.toml."); +} else if (current === expected) { + console.log("bazel/clippy.bazelrc already up to date."); +} else { + await Bun.write(outputPath, expected); + console.log("Wrote bazel/clippy.bazelrc."); +} diff --git a/scripts/release.ts b/scripts/release.ts index 0ac70949e..88182a803 100755 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -340,12 +340,16 @@ async function cmdRelease(versionOrBump: string): Promise { } console.log(` sentinel: ${sentinelName}\n`); - // 4. Regenerate lockfiles + // 4. Regenerate lockfiles and generated configs console.log("Regenerating lockfiles..."); await $`rm -f bun.lock`; await $`bun install`; await $`cargo generate-lockfile`; await generateNixBunDeps(nixBunDepsGenerator); + // bazel/clippy.bazelrc mirrors [workspace.lints] in Cargo.toml; regenerate + // it here (like the lockfiles) so the bazel clippy policy can never drift. + // The release_gate CI job runs the matching `--check`. + await $`bun scripts/gen-clippy-bazelrc.ts`; console.log(); // 5. Update changelogs