Skip to content

Tracking: modernize and harden Crossterm CI #1075

Description

@joshka

Summary

This issue is the plan of record for bringing Crossterm’s CI up to a level that can safely support required checks and, later, GitHub-based releases. It is related to the release discussion in #1023, but publishing and trusted publishing remain separate follow-up work.

The work will land as small PRs. Each PR should link this issue, and this checklist will be updated as work lands.

Implementation plan

1. Harden the existing workflow

  • Rename the existing workflow to ci.yml while preserving approximately its current coverage.
  • Pin actions/checkout to 3d3c42e5aac5ba805825da76410c181273ba90b1 (v7.0.1) with persist-credentials: false.
  • Replace hecrj/setup-rust-action@master with dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c, a durable commit from the action’s master history.
  • Set explicit contents: read permissions and grant no write permissions or secrets.
  • Add concurrency cancellation, job timeouts, fail-fast: false, deliberate runner versions, and workflow_dispatch.
  • Add grouped monthly Dependabot updates for pinned GitHub Actions.
  • Validate the workflow with Actionlint and Zizmor.

Completed in #1076.

The initial Rust installer pin used a generated toolchain-branch commit. #1083 corrects it to master history because the workflows supply the toolchain input explicitly and unreachable generated commits may eventually be garbage-collected.

This PR changed workflow execution and security, not Rust policy or test coverage.

2. Establish the lockfile baseline

  • Begin tracking the repository-root Cargo.lock generated from the existing manifests.
  • Establish a resolution baseline that does not contain the identified vulnerable or yanked bytes 1.6.0 and futures-util 0.3.30 releases.
  • Confirm that obsolete transitive dependencies observed during the audit, such as adler, are absent from the baseline.
  • Keep manifest requirements unchanged unless a newer minimum is genuinely required.
  • Include a dependency-tree summary and run the full existing test suite.

Completed in #1078.

The repository did not previously track its root lockfile, so this was not an update against a committed dependency graph. It recorded the versions Cargo resolved at that point from the existing semver requirements. Future refreshes can be reviewed as normal lockfile diffs against this baseline.

Any unmaintained development-only dependency without a safe replacement must receive a documented temporary advisory exception and a linked follow-up issue.

The temporary async-std exception was resolved in #1079.

3. Set and enforce Rust 1.85 as the MSRV

  • Narrow #1059 to the MSRV policy change, lockfile adjustment if required, and changelog entry.
  • Keep source rewrites limited to the mechanical changes required when MSRV-aware Clippy sees Rust 1.85.
  • Set rust-version = "1.85.0".
  • Define MSRV as covering the library with no default features and with every public feature enabled; development tools and examples do not independently define the crate’s MSRV.
  • Add required Rust 1.85 checks for both configurations.

Completed in #1059.

The current Rust 1.63 declaration is not retained: the all-public-features graph currently resolves a dependency requiring Rust 1.85. Preserving 1.63 would require selecting and maintaining older transitive releases.

4. Migrate to the Rust 2024 edition

  • Run rustup run 1.85.0 cargo fix --edition --allow-dirty --locked --all-targets --all-features while the manifest still declares Rust 2021, then review every generated change.
  • Preserve the existing matching behavior of exported macros unless a broader input grammar is chosen deliberately; Cargo’s migration uses expr_2021 fragments to avoid silently changing public macro behavior.
  • Replace the generated unsafe environment-variable mutations in test_no_color with the existing temp-env test helper rather than retaining generated FIXME blocks.
  • Set edition = "2024" only after applying and reviewing the migration fixups.
  • Run Rustfmt after changing the manifest and review its style-edition churn separately from the semantic migration fixups.
  • Confirm that resolver version 3, implied by the 2024 edition, does not unexpectedly change the tracked lockfile or dependency graph.
  • Verify the no-default-features and all-public-features library configurations with Rust 1.85.0, then run formatting, Clippy, docs, doctests, all-target/all-feature tests, explicit feature checks, and package verification.
  • Add a changelog entry explaining the edition change and any edition-driven semantic changes retained after review.

Rust 1.85 is the first release supporting Rust 2024, so the edition change fits the newly declared MSRV. Follow Cargo’s documented two-step migration: run cargo fix --edition first, then update the manifest. The 2024 edition also implies resolver version 3, which is Rust-version aware.

A local trial on the merged Rust 1.85 baseline completed successfully. Cargo produced fixups in five source files; changing the manifest and running Rustfmt expanded the mechanical diff to 29 files. Rust 1.85 checks for the library with no default features and all features passed, as did the full all-target/all-feature test suite on macOS.

Completed in #1081.

5. Establish the required CI gate

Completed in #1083.

  • Add focused format, clippy, docs, test, doctest, msrv, and package jobs.
  • Run stable tests on Linux, macOS, and Windows with all targets and features, keeping tests single-threaded initially.
  • Run tests for the deliberate Unix compatibility points: defaults, serde, event-stream, no defaults, events, and events,event-stream,use-dev-tty,bracketed-paste.
  • Run tests for windows and windows,events; document that bare no-defaults is intentionally rejected on Windows.
  • Verify cargo package --locked on Linux and Windows.
  • Add a stable ci-success aggregation job intended to be the only required status check.
  • Move beta and nightly compatibility to a weekly and manually dispatched workflow.
  • Document the required local commands in the contribution guide.
  • Add an advisory beta-Clippy check that warns about likely next-stable lint failures without blocking pull requests.

Do not add CI caching until job timings demonstrate a need.

The feature matrix records deliberate compatibility points rather than an exhaustive powerset. Consider cargo-hack later if broader coverage justifies the additional runtime, policy, and pinned-tool maintenance. Stable Clippy remains required; beta Clippy is advisory because it is useful early warning but changes independently of the repository.

Completed in #1085: a justfile makes the growing set of local formatting, lint, documentation, MSRV, feature, test, and package checks easier to run correctly. The workflow commands remain the source of truth, with just recipes providing memorable entry points rather than creating a second CI policy.

6. Add dependency, API, and workflow integrity checks

  • Add deny.toml and require cargo-deny 0.20.2.
  • Allow the reviewed SPDX licenses used by the dependency graph: MIT, Apache-2.0, Apache-2.0 WITH LLVM-exception, and Unicode-3.0.
  • Fail on vulnerabilities, yanked releases, unknown registries, unreviewed Git sources, and unapproved licenses.
  • Add cargo-semver-checks 0.49.0 against the latest crates.io release.
  • Keep semver checking informational on ordinary PRs; make it blocking later in the release-preparation workflow.
  • Run Actionlint 1.7.12 and Zizmor 1.28.0 for workflow changes.
  • Use pinned, maintained first-party actions for cargo-deny and Zizmor and Actionlint's official container image.
  • Add grouped monthly Cargo dependency updates with increase-if-necessary, leaving manifest requirements unchanged when they already permit the update.
  • When repository administration is available, enable Zizmor SARIF through its official action and decide whether Code Scanning findings should block merges.

The dependency and workflow-policy subset was completed in #1084. The license list was refined against the resolved all-features graph, including development dependencies: Unicode-3.0 is required, while the initially proposed Unicode-DFS-2016, 0BSD, Zlib, and Unlicense allowances are not needed. API compatibility reporting remains a later review unit because it has different policy and failure semantics. Zizmor SARIF is deliberately deferred until repository Code Scanning settings can be configured; the required read-only audit still runs for every pull request.

Repository settings

After ci-success passes on master and representative fork PRs:

  • Create a master ruleset requiring pull requests and ci-success.
  • Prevent force pushes and branch deletion.
  • Require review conversations to be resolved.
  • Do not initially require every matrix job separately or require stale branches to be continuously rebased.
  • Re-evaluate the Actions approval setting so safe, read-only CI can run automatically for external contributors.
  • Do not use pull_request_target.

Acceptance criteria

  • Fork PRs run with read-only permissions, no secrets, pinned actions, and no persisted credentials.
  • Linux, macOS, Windows, public feature combinations, Rust 1.85, Rustdoc, doctests, and package verification are covered.
  • The publishable package is verified on both Unix and Windows.
  • Fixable advisories and yanked dependencies fail CI.
  • Public API compatibility is reported without automatically choosing the release version.
  • Workflow changes validate themselves.
  • master cannot merge while ci-success is failing.
  • No CI job has crates.io, tag, release, or other publishing authority.

Assumptions

  • This issue covers CI and repository protections only; the release workflow remains a follow-up linked to crates.io out of date #1023.
  • Release-plz is not introduced in this work.
  • crossterm-winapi provenance and release coordination are handled in the later release-process plan.
  • No public Rust API changes are intended; the Rust 2024 migration follows the MSRV change and remains compatible with Rust 1.85.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions