Skip to main content

Module cargo

Module cargo 

Source
Expand description

Rust ecosystem adapter: cargo-publish / cargo-publish-ci (crates.io) and cargo-dist.

cargo-publish publishes a crate to crates.io via cargo publish. cargo-dist plans and builds distributable binaries locally (dist), but its upload is the CI release workflow — so its publish body is AdapterError::Unsupported from this host rather than a fabricated receipt for a build-only command. verify (for cargo-publish) reconciles against crates.io through RegistryQuery via the adapter’s default path.

§cargo-publish-ci — the crates.io publish runs in CI, not here

cargo-publish-ci is cargo-publish’s CI-delegated identity, for a repo whose release model is “push the version tag; a tag-triggered workflow runs cargo publish with the repo’s registry secret” (glasspad’s publish-crates.yml, the common publish-from-CI-not-a-laptop pattern). Such a repo deliberately forbids the local publish: the maintainer’s ~/.cargo/credentials.toml may be stale (403) or absent, and the CI token is the source of truth — so an engine cut that ran cargo publish here would either fail or race the workflow into a double-publish.

It differs from cargo-publish in exactly one respect: the publish. Its dry_run and build run the identical local gates (cargo check, cargo package --no-verify), because those are read-only preflights whose whole value is catching an unpublishable manifest before the irreversible tag push — and a repo that publishes from CI still wants them. Its publish is AdapterError::Unsupported, and it declares is_ci_delegated, so the coordinator journals target_delegated and skips it in publish-all (never publishing, never failing). It does not own the GitHub Release (it uploads to crates.io), so a plan carrying only this delegated identity still gets an engine-created Release.

The result is a cut whose terminal actionable phase is the tag push, followed by the mandatory verify barrier — which polls the crates.io index until CI’s publish is observed (see the coordinator’s delegated-verify wait). “Delegated” never means “assumed”: an unobserved target still fails the cut.

§One plan target = one publish unit (ADR-0004)

Each plan target publishes exactly its own package — one target ⇒ one cargo publish -p <package>. The coordinator owns all cross-target ordering: it cuts same-ecosystem targets in dependency order (a dependency’s target before its dependents’), so the adapter never re-orders or re-publishes another target’s crate. This removes the earlier closure-per-target model, where two authorities (coordinator + adapter) each computed overlapping publish orderings and the crates.io publish→index lag between them could trigger a duplicate cargo publish of a shared dependency → a partial-publish trap.

A workspace whose crates depend on one another still cannot publish in one shot: crates.io rejects a crate whose sibling dependency is not yet indexed (no matching package named … found). So before publishing its own package, the adapter discovers that package’s publishable intra-workspace dependencies (read-only cargo metadata) and waits for each to be crates.io-index-visible (polling the injected RegistryQuery, bounded by a timeout) — the dependency’s own target, cut earlier, already published it, so this only closes the index-lag window. A crate with no publishable workspace dependencies publishes immediately with no wait.

§Deferred packaging for a =-pinned dependent (cargo-interleave, ADR-0002)

A dependent crate that pins its workspace dependency by exact version (dep = "=X.Y.Z", the shape /shipshape-init emits) cannot be cargo packaged before that dependency is published — not even with --no-verify. cargo package resolves the =-pinned dependency against the crates.io index while preparing the upload (a published .crate cannot reference a path dep), and that version only lands later, in publish-all. --no-verify skips the isolated verify compile, but not this index resolution. So a strict build-all that packaged every crate up front could never package such a dependent (release-cut-build-phase-dep-ordering).

The fix scopes the ADR-0002 phase barrier narrowly for cargo. dry_run / build read the workspace graph and probe the registry (see unpublished_workspace_deps), then branch on whether the target depends on a workspace crate not yet on the crates.io index:

  • No unpublished workspace dep (a leaf, or a dependent whose workspace deps are already published — a re-cut): fully packaged pre-publish — cargo check (compile safety net) then cargo package --no-verify (produces the .crate, validating the manifest). It CAN be packaged: cargo package resolves the dep against the index it is already on.
  • Depends on a not-yet-published workspace crate: its packaging is deferred to cargo publish in publish-all, which packages and publishes as one unit after the dependency is published and index-visible (build runs only the index-independent cargo check, which resolves the sibling via its path). This is the “build interleaves with publish” exception the coordinator relies on: the dependent’s package step is intrinsic to its dep-ordered publish, not a premature global-build step. The pre-publish compile safety net (cargo check over every target) still runs as a global build-all barrier, so a compile error in the default host build fails before any irreversible publish.

The registry probe is fail-closed: a dep the registry cannot confirm as published defers, so a registry outage never risks a build-all cargo package that resolves a =-pinned dep against an index it cannot reach.

The consequence is a target model where each publishable crate is its own declared target (which is what /shipshape-init emits). A multi-crate workspace that wants every crate on crates.io declares every crate as a target; a target whose package depends on a workspace crate that is not itself a declared target — and whose required version is not already on the index — times out waiting for that crate, the signal that it must be declared. (If that dependency’s version happens to already be published, the wait clears and the publish proceeds; the coverage check that would catch an under-declared plan up front is tracked separately, not owned by the adapter.)

Structs§

CargoAdapter
The rust release adapter, operating as cargo-publish, cargo-publish-ci, or cargo-dist.