chore: stop sharing a cache with the binary the audit job runs

This commit is contained in:
2026-09-09 12:05:11 +00:00
parent b6a6750499
commit 462d9cbec2
5 changed files with 93 additions and 11 deletions

32
.gitignore vendored
View File

@@ -8,3 +8,35 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information # MSVC Windows builds of rustc generate these, which store debugging information
*.pdb *.pdb
# the cargo home CI keeps inside the project so it lands in the cache
.cargo/
# where the completions build writes when it is not given a directory
completions/
# os
.DS_Store
# editors
.vscode
.idea
.ignore
# ai
CLAUDE.local.md
.claude
# languages
.python-version
# nix
.nix-venv
.envrc
.direnv/
# project-local
TODO.md
# database dumps, which this tool exists to keep out of a repository
dumps
*.partial

View File

@@ -5,30 +5,38 @@ default:
files: files:
- Cargo.lock - Cargo.lock
paths: paths:
# no .cargo/bin here: the audit job runs what is in it, and this cache is
# shared by every branch and merge request, so a pipeline could otherwise
# leave behind the binary a later scheduled audit executes
- .cargo/registry - .cargo/registry
# so the audit job does not rebuild cargo-audit on every schedule
- .cargo/bin
- target - target
variables: variables:
# keep the registry inside the project so it lands in the cache # keep the registry inside the project so it lands in the cache
CARGO_HOME: $CI_PROJECT_DIR/.cargo CARGO_HOME: $CI_PROJECT_DIR/.cargo
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
# pinned, rather than whatever version exists on the night this runs
CARGO_AUDIT_VERSION: "0.21.2"
# without this a push to a branch with an open merge request runs twice # without this a push to a branch with an open merge request runs twice
workflow: workflow:
rules: rules:
# first, because a scheduled pipeline sets CI_COMMIT_BRANCH as well and so
# would be matched by the branch rule below, or vetoed by the one above it
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never when: never
- if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_BRANCH
- if: $CI_PIPELINE_SOURCE == "schedule"
check: check:
script: script:
- rustup component add rustfmt clippy - rustup component add rustfmt clippy
- cargo fmt --check - cargo fmt --check
- cargo clippy --all-targets -- -D warnings # --locked on the first cargo invocation too: without it clippy resolves the
# dependencies and rewrites a Cargo.lock that has drifted from Cargo.toml,
# and the --locked commands after it then pass against what it just wrote
- cargo clippy --locked --all-targets -- -D warnings
- cargo test --locked - cargo test --locked
- cargo build --release --locked - cargo build --release --locked
rules: rules:
@@ -39,19 +47,33 @@ check:
# runs only from a pipeline schedule, so set one up in the project settings # runs only from a pipeline schedule, so set one up in the project settings
audit: audit:
script: script:
- cargo install cargo-audit --locked - cargo install cargo-audit --version $CARGO_AUDIT_VERSION --locked
- cargo audit - cargo audit
# reports dependencies that have drifted behind, without changing the lockfile # reports dependencies that have drifted behind, without changing the lockfile
- cargo update --dry-run - cargo update --dry-run
# its own cache, so the audit tool is not rebuilt on every schedule while
# still being written only by this schedule-only job. the pinned version is
# part of the key, so a bump fetches rather than reusing the old binary
cache:
key: audit-tools-$CARGO_AUDIT_VERSION
paths:
- .cargo/bin
- .cargo/registry
rules: rules:
- if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "schedule"
allow_failure: true
# the msrv declared in Cargo.toml, so it fails when something needs a newer rustc # the msrv declared in Cargo.toml, so it fails when something needs a newer rustc
msrv: msrv:
image: rust:1.85 image: rust:1.85
script: script:
- cargo build --locked - cargo build --locked
# its own key: artifacts built by another rustc are of no use to this job, and
# sharing one only has the two toolchains taking turns overwriting it
cache:
key: msrv-$CI_COMMIT_REF_SLUG
paths:
- .cargo/registry
- target
rules: rules:
- if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "schedule"
when: never when: never

View File

@@ -25,6 +25,14 @@ fn install_dir(shell: Shell) -> Option<(PathBuf, bool)> {
} }
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
// naming any rerun-if condition replaces cargo's default of re-running the
// script whenever anything in the package changed, so everything that
// shapes a completion script has to be named here. without them an
// installed script goes stale against the binary it completes
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-changed=src/cli");
println!("cargo::rerun-if-changed=Cargo.toml");
println!("cargo::rerun-if-env-changed=SHELL_COMPLETIONS_DIR"); println!("cargo::rerun-if-env-changed=SHELL_COMPLETIONS_DIR");
for shell in Shell::value_variants() { for shell in Shell::value_variants() {
println!( println!(
@@ -40,7 +48,18 @@ fn main() -> Result<(), Error> {
continue; continue;
}; };
fs::create_dir_all(&dir)?; // a build script's working directory is the package root, so a relative
// dir would quietly install into the source tree and one holding `..`
// somewhere else again. only a path that says where it means is taken
if requested && !dir.is_absolute() {
return Err(Error::other(format!(
"the completions directory must be an absolute path, not {}",
dir.display()
)));
}
fs::create_dir_all(&dir)
.map_err(|e| Error::other(format!("creating {}: {e}", dir.display())))?;
let path = generate_to(*shell, &mut cmd, env!("CARGO_PKG_NAME"), &dir)?; let path = generate_to(*shell, &mut cmd, env!("CARGO_PKG_NAME"), &dir)?;
if requested { if requested {

View File

@@ -9,7 +9,7 @@ pub enum Django {
/// Start a bash session in a fresh django container /// Start a bash session in a fresh django container
Bash, Bash,
/// Prepare empty management command 'command' in app 'app' /// Prepare an empty management command NAME in the app at APP
MakeCommand { app: PathBuf, name: String }, MakeCommand { app: PathBuf, name: String },
/// Run Django's manage.py makemigrations /// Run Django's manage.py makemigrations

View File

@@ -27,15 +27,24 @@ fn script(shell: Shell) -> Vec<u8> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Shell, script}; use super::{Ahab, Shell, script};
use clap::ValueEnum; use clap::{CommandFactory, ValueEnum};
#[test] #[test]
fn every_shell_gets_a_script_covering_the_subcommands() { fn every_shell_gets_a_script_covering_the_subcommands() {
// asked of the parser rather than listed here, which is a list that
// silently stops covering the newest command
let subcommands: Vec<String> = Ahab::command()
.get_subcommands()
.map(|sub| sub.get_name().to_string())
.collect();
assert!(subcommands.len() > 1, "{subcommands:?}");
for shell in Shell::value_variants() { for shell in Shell::value_variants() {
let out = String::from_utf8(script(*shell)).expect("script is utf8"); let out = String::from_utf8(script(*shell)).expect("script is utf8");
for subcommand in ["django", "postgres", "link", "completions"] { for subcommand in &subcommands {
assert!( assert!(
out.contains(subcommand), out.contains(subcommand),
"{shell:?} script never mentions {subcommand}" "{shell:?} script never mentions {subcommand}"