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
20 changes: 11 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ PUBLISH_PACKAGES ?=
UV ?= uv
WORKFLOW_TEST_VENV ?= .venv
LINT_CRATES ?= bumpy_road_function conditional_max_n_branches function_attrs_follow_docs module_max_lines module_must_have_inner_docs no_expect_outside_tests test_must_not_have_example no_std_fs_operations no_unwrap_or_else_panic whitaker_suite
CARGO_DYLINT_VERSION ?= 5.0.0
DYLINT_LINK_VERSION ?= 5.0.0
CARGO_DYLINT_VERSION ?= 6.0.1
DYLINT_LINK_VERSION ?= 6.0.1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# Host-tool installs run under this toolchain: the dylint 6.0.1 lockfile
# needs a newer rustc than the repository's pinned nightly provides.
DYLINT_TOOLS_TOOLCHAIN ?= stable
WHITAKER_SCRIPT ?= $(HOME)/.local/bin/whitaker

build: target/debug/$(APP) ## Build debug binary
Expand Down Expand Up @@ -238,6 +241,7 @@ release-installer-dry-run: ## Build and package the host-platform installer arch

publish-check: ## Build, test, and validate packages before publishing
@export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; command -v cargo-nextest >/dev/null || { echo "Install cargo-nextest (cargo install cargo-nextest)"; exit 1; }
set -eu; \
export PATH="$$PATH:$(TOOL_PATH_SUFFIX)"; \
PINNED_TOOLCHAIN=$$(awk -F '\"' '/^channel/ {print $$2}' rust-toolchain.toml); \
TOOLCHAIN="$$PINNED_TOOLCHAIN"; \
Expand All @@ -247,15 +251,13 @@ publish-check: ## Build, test, and validate packages before publishing
RUSTFLAGS="-Z force-unstable-if-unmarked $(RUST_FLAGS)" $(CARGO) +$$TOOLCHAIN nextest run --profile ci $(TEST_CARGO_FLAGS) $(BUILD_JOBS); \
TMP_DIR=$$(mktemp -d); \
trap 'rm -rf "$$TMP_DIR"' 0 INT TERM HUP; \
if ! command -v cargo-dylint >/dev/null 2>&1; then \
$(CARGO) install --locked --version $(CARGO_DYLINT_VERSION) cargo-dylint; \
fi; \
if ! command -v dylint-link >/dev/null 2>&1; then \
$(CARGO) install --locked --version $(DYLINT_LINK_VERSION) dylint-link; \
fi; \
DYLINT_TOOLS_DIR="$$TMP_DIR/dylint-tools"; \
scripts/install-dylint-tools.sh "$$DYLINT_TOOLS_DIR" "$(CARGO_DYLINT_VERSION)" "$(DYLINT_LINK_VERSION)" "$(CARGO)" "$(DYLINT_TOOLS_TOOLCHAIN)"; \
if [ -d "$$DYLINT_TOOLS_DIR/bin" ]; then export PATH="$$DYLINT_TOOLS_DIR/bin:$$PATH"; fi; \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
TARGET_DIR="$$TMP_DIR/target"; \
git clone "$(WHITAKER_REPO)" "$$TMP_DIR/whitaker-src"; \
cd "$$TMP_DIR/whitaker-src" && { \
cd "$$TMP_DIR/whitaker-src" || exit 1; \
{ \
CLONE_HEAD=$$(git rev-parse HEAD); \
TARGET_REV=$${GIT_TAG:-$${WHITAKER_REV:-$$CLONE_HEAD}}; \
git checkout "$$TARGET_REV"; \
Expand Down
5 changes: 4 additions & 1 deletion docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Whitaker itself. For using Whitaker lints in a project, see the
cargo install cargo-dylint dylint-link
```

(`make publish-check` provisions these automatically at the pinned
versions; see [Pre-publish validation](publishing.md#pre-publish-validation).)

CI also installs or provides job-specific tools such as `cargo-nextest`, `bun`,
`uv`, Mermaid CLI, and Nixie before running the targets that need them. Local
runs of those targets require the same tools on `PATH`.
Expand Down Expand Up @@ -757,7 +760,7 @@ Example entry:
[[dependency_binaries]]
package = "cargo-dylint"
binary = "cargo-dylint"
version = "4.1.0"
version = "6.0.1"
license = "MIT OR Apache-2.0"
repository = "https://github.com/trailofbits/dylint"
```
Expand Down
20 changes: 19 additions & 1 deletion docs/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,25 @@ make publish-check PUBLISH_PACKAGES="whitaker-common whitaker-installer"

This target builds the workspace, runs tests with the pinned toolchain, and
packages the crates named in `PUBLISH_PACKAGES` for inspection, which here
means both `whitaker-common` and `whitaker-installer`.
means both `whitaker-common` and `whitaker-installer`. The target runs under
`set -eu`, so any failed step aborts the gate immediately rather than
continuing with a partially built or stale toolchain.

Before building the lint libraries, `publish-check` provisions the pinned
Dylint tools by delegating to `scripts/install-dylint-tools.sh`. Host-tool
installs run under the toolchain named by `DYLINT_TOOLS_TOOLCHAIN` (default
`stable`), because the dylint 6.0.1 lockfile requires a newer rustc than the
repository's pinned nightly provides. The script
compares any installed `cargo-dylint` against `CARGO_DYLINT_VERSION`, and
checks `dylint-link` via `cargo install --list` (`dylint-link` is a linker
shim whose `--version` is forwarded to `cc`, so it cannot be probed directly).
Tools that are missing or mismatched are installed into an isolated, per-run
temporary root; the Makefile prepends that root's `bin/` directory to `PATH`
only when it exists, so the pinned versions take precedence without touching
any system-wide install. If either install fails, the script exits non-zero
and the gate fails fast rather than proceeding with stale or absent tools.
This behaviour is covered by
`tests/workflows/test_install_dylint_tools.py`.

To validate the installer archive path used by the release workflow on the
current host platform, run:
Expand Down
4 changes: 2 additions & 2 deletions installer/dependency-binaries.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[[dependency_binaries]]
package = "cargo-dylint"
binary = "cargo-dylint"
version = "4.1.0"
version = "6.0.1"
license = "MIT OR Apache-2.0"
repository = "https://github.com/trailofbits/dylint"

[[dependency_binaries]]
package = "dylint-link"
binary = "dylint-link"
version = "4.1.0"
version = "6.0.1"
license = "MIT OR Apache-2.0"
repository = "https://github.com/trailofbits/dylint"
2 changes: 1 addition & 1 deletion installer/src/dependency_binaries/install/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ fn archive_filename_uses_dependency_version() {
.expect("dependency should exist");
assert_eq!(
archive_filename(dependency, &target),
"cargo-dylint-x86_64-unknown-linux-gnu-v4.1.0.tgz"
"cargo-dylint-x86_64-unknown-linux-gnu-v6.0.1.tgz"
);
}

Expand Down
24 changes: 15 additions & 9 deletions installer/src/dependency_binaries/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ pub fn manifest_contents() -> &'static str {
/// [[dependency_binaries]]
/// package = "cargo-dylint"
/// binary = "cargo-dylint"
/// version = "4.1.0"
/// version = "6.0.1"
/// license = "MIT OR Apache-2.0"
/// repository = "https://github.com/trailofbits/dylint"
/// "#;
Expand All @@ -178,7 +178,7 @@ pub fn manifest_contents() -> &'static str {
/// [[dependency_binaries]]
/// package = "cargo-dylint"
/// binary = "cargo-dylint"
/// version = "4.1.0"
/// version = "6.0.1"
/// license = "MIT OR Apache-2.0"
/// repository = "https://github.com/trailofbits/dylint"
///
Expand Down Expand Up @@ -302,7 +302,7 @@ mod tests {
[[dependency_binaries]]
package = "cargo-dylint"
binary = "cargo-dylint"
version = "4.1.0"
version = "6.0.1"
license = "MIT OR Apache-2.0"
"#
}
Expand All @@ -313,7 +313,7 @@ mod tests {
[[dependency_binaries]]
package = "cargo-dylint"
binary = "cargo-dylint"
version = "4.1.0"
version = "6.0.1"
license = "MIT OR Apache-2.0"
repository = "https://github.com/trailofbits/dylint"

Expand Down Expand Up @@ -360,12 +360,18 @@ mod tests {
}
}

#[test]
fn find_dependency_binary_returns_matching_package() {
let tool = find_dependency_binary("cargo-dylint")
#[rstest]
#[case::cargo_dylint("cargo-dylint", "cargo-dylint", "6.0.1")]
#[case::dylint_link("dylint-link", "dylint-link", "6.0.1")]
fn find_dependency_binary_returns_matching_package(
#[case] package: &str,
#[case] binary: &str,
#[case] version: &str,
) {
let tool = find_dependency_binary(package)
.expect("embedded manifest should stay parseable")
.expect("tool should exist");
assert_eq!(tool.binary(), "cargo-dylint");
assert_eq!(tool.version(), "4.1.0");
assert_eq!(tool.binary(), binary);
assert_eq!(tool.version(), version);
}
}
12 changes: 6 additions & 6 deletions installer/src/deps/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fn install_dylint_tools_falls_back_to_cargo_install_when_binstall_missing() {
binstall_version_check_with_result(Ok(failure_output("missing binstall"))),
ExpectedCall {
cmd: "cargo",
args: vec!["install", "--locked", "--version", "4.1.0", "cargo-dylint"],
args: vec!["install", "--locked", "--version", "6.0.1", "cargo-dylint"],
result: Ok(success_output()),
},
cargo_dylint_check_with_result(Ok(success_output())),
Expand Down Expand Up @@ -173,7 +173,7 @@ fn install_dylint_tools_reports_total_failure_after_all_fallbacks() {
binstall_install("cargo-dylint", Ok(failure_output("binstall failed"))),
ExpectedCall {
cmd: "cargo",
args: vec!["install", "--locked", "--version", "4.1.0", "cargo-dylint"],
args: vec!["install", "--locked", "--version", "6.0.1", "cargo-dylint"],
result: Ok(failure_output("cargo install failed")),
},
]);
Expand Down Expand Up @@ -205,14 +205,14 @@ fn install_dylint_tools_builds_from_source_when_repository_asset_is_missing() {
let mut repository_installer = MockDependencyBinaryInstaller::new();
repository_installer.expect_install().returning(|_, _, _| {
Err(DependencyBinaryInstallError::NotFound {
url: "https://example.test/cargo-dylint-x86_64-unknown-linux-gnu-v4.1.0.tgz".to_owned(),
url: "https://example.test/cargo-dylint-x86_64-unknown-linux-gnu-v6.0.1.tgz".to_owned(),
})
});
let executor = StubExecutor::new(vec![
binstall_version_check_with_result(Ok(success_output())),
ExpectedCall {
cmd: "cargo",
args: vec!["install", "--locked", "--version", "4.1.0", "cargo-dylint"],
args: vec!["install", "--locked", "--version", "6.0.1", "cargo-dylint"],
result: Ok(success_output()),
},
cargo_dylint_check_with_result(Ok(success_output())),
Expand Down Expand Up @@ -244,15 +244,15 @@ fn install_dylint_tools_skips_dylint_link_when_cargo_dylint_source_build_install
.once()
.returning(|_, _, _| {
Err(DependencyBinaryInstallError::NotFound {
url: "https://example.test/cargo-dylint-x86_64-unknown-linux-gnu-v4.1.0.tgz"
url: "https://example.test/cargo-dylint-x86_64-unknown-linux-gnu-v6.0.1.tgz"
.to_owned(),
})
});
let executor = StubExecutor::new(vec![
binstall_version_check_with_result(Ok(success_output())),
ExpectedCall {
cmd: "cargo",
args: vec!["install", "--locked", "--version", "4.1.0", "cargo-dylint"],
args: vec!["install", "--locked", "--version", "6.0.1", "cargo-dylint"],
result: Ok(success_output()),
},
cargo_dylint_check_with_result(Ok(success_output())),
Expand Down
4 changes: 2 additions & 2 deletions installer/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ fn ensure_dylint_tools_installs_missing_tools(
},
ExpectedCall {
cmd: "cargo",
args: vec!["install", "--locked", "--version", "4.1.0", "cargo-dylint"],
args: vec!["install", "--locked", "--version", "6.0.1", "cargo-dylint"],
result: Ok(success_output()),
},
ExpectedCall {
Expand Down Expand Up @@ -207,7 +207,7 @@ fn ensure_dylint_tools_propagates_install_failures(test_base_dirs: TestBaseDirs)
},
ExpectedCall {
cmd: "cargo",
args: vec!["install", "--locked", "--version", "4.1.0", "cargo-dylint"],
args: vec!["install", "--locked", "--version", "6.0.1", "cargo-dylint"],
result: Ok(failure_output("cargo install failed")),
},
]);
Expand Down
59 changes: 59 additions & 0 deletions scripts/install-dylint-tools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env sh
# install-dylint-tools.sh — Ensure the pinned cargo-dylint and dylint-link
# versions are available, installing into an isolated root when the
# system-wide binaries are missing or the wrong version.
#
# Usage:
# scripts/install-dylint-tools.sh TOOLS_ROOT CARGO_DYLINT_VERSION DYLINT_LINK_VERSION [CARGO] [TOOLCHAIN]
#
# When TOOLCHAIN is given, installs run under `cargo +TOOLCHAIN`. The
# host tools are toolchain-independent, but their locked dependencies
# can require a newer rustc than a repository's pinned nightly (e.g.
# cargo-dylint 6.0.1 locks cargo-util 0.2.28, which needs rustc 1.93),
# so callers pass a modern toolchain such as `stable`.
#
# TOOLS_ROOT is used as the cargo install --root; binaries land in
# TOOLS_ROOT/bin, which the caller should prepend to PATH when it exists.
# The root is only created when an install is needed, so callers can use
# its absence to mean "the system tools already match".
#
# cargo-dylint is probed via `cargo-dylint dylint --version` (the
# subcommand form: since 6.x the binary rejects a bare --version).
# dylint-link cannot be probed either way: it is a linker shim whose
# --version is forwarded to cc, so the installed version is read from
# `cargo install --list`.
#
# Exits non-zero if any required install fails, so callers never proceed
# with stale tools.
set -eu

if [ "$#" -lt 3 ] || [ "$#" -gt 5 ]; then
echo "usage: $0 TOOLS_ROOT CARGO_DYLINT_VERSION DYLINT_LINK_VERSION [CARGO] [TOOLCHAIN]" >&2
exit 2
fi

tools_root=$1
cargo_dylint_version=$2
dylint_link_version=$3
cargo=${4:-cargo}
toolchain=${5:-}

run_cargo() {
if [ -n "$toolchain" ]; then
"$cargo" "+$toolchain" "$@"
else
"$cargo" "$@"
fi
}

installed_cargo_dylint=$(cargo-dylint dylint --version 2>/dev/null | awk '{print $2}' || true)
if [ "$installed_cargo_dylint" != "$cargo_dylint_version" ]; then
run_cargo install --locked --version "$cargo_dylint_version" \
--root "$tools_root" cargo-dylint
fi

if ! "$cargo" install --list 2>/dev/null |
grep -q "^dylint-link v$dylint_link_version:"; then
run_cargo install --locked --version "$dylint_link_version" \
--root "$tools_root" dylint-link
fi
Loading
Loading