RFA-032 · Case file with fixtures · Case 4 of 694 · Cargo package-boundary evidence
A Cargo Workspace Builds Locally but the Packaged Crate Misses Files
The repository is not the crate. Diagnose publish-only Rust failures by inspecting cargo package --list, the normalized manifest, and a build from the extracted archive.
- Reviewed
- Rust
- stable Rust, Cargo stable
- Targets
- host packaging target
- Profiles
- package verification
Direct answer
What this Rust failure means
- Why it happens
- The local build consumes repository and path inputs which packaging excludes, rewrites, or resolves differently for a registry consumer.
- First discriminating check
- Compare `cargo package --list` and the normalized packaged manifest with every input used by source and build scripts.
A workspace can pass cargo build, cargo test, and CI, then fail during cargo package or after somebody installs the published crate. This feels like Cargo changed the build. Usually the input changed first.
The repository and the distributable crate are not the same object. Packaging selects files, rewrites parts of the manifest, creates a .crate archive, extracts it, and builds that clean copy for verification. A local build can silently depend on files which never enter the archive.
Start with the package file list
My first command is not another workspace build:
cargo package --list
It prints the files Cargo intends to include. I search this list for every file named by:
include_str!andinclude_bytes!;- a build script;
- examples and tests;
- license and readme fields;
- code generation;
- relative paths in configuration.
If a required file is absent, I already have a smaller problem than “publishing is broken.”
Cargo uses manifest include and exclude rules, with version-control ignore rules involved when an explicit include list is not present. An ignored fixture may exist on every developer machine and still be absent from the crate.
A minimal failure
Suppose the library embeds a template:
pub const DEFAULT_TEMPLATE: &str = include_str!("../assets/default.txt");
The repository contains assets/default.txt, but .gitignore excludes assets/ because other generated files live there. A normal local build reads the existing file. The packaged source does not contain it, so Cargo's verification build fails.
The direct check is:
cargo package --list | rg 'assets/default.txt'
No output confirms the package boundary, not Rust name resolution, is the first place to repair.
The paired Atlas fixture makes that boundary observable. The failing package compiles from its checkout because the template exists there, but its explicit include list omits the asset and the extracted package cannot compile. The repaired package includes the exact input. The verifier requires the first local check to pass before it accepts the package failure, so a generally broken crate cannot impersonate this case.
Inspect the artifact Cargo will distribute
cargo package writes the archive under target/package. I inspect the archive and its generated manifest instead of assuming the source checkout is representative.
Cargo includes both the original manifest and a normalized Cargo.toml. The normalized form is what consumers effectively build. Workspace sections are removed, and path dependencies cannot be used as unpublished shortcuts. A dependency can specify a path and version locally, but the packaged dependency resolves by version from the registry.
This exposes another common case:
[dependencies]
shared-types = { path = "../shared-types", version = "0.4" }
The workspace build reads the sibling directory. The packaged crate expects version 0.4 to exist in the selected registry and to contain the compatible API. If it is missing or different, local success proved only the path graph.
Use package verification as the clean-room test
By default, cargo package extracts the archive and builds it from scratch. I keep this verification enabled. --no-verify can create an artifact, but it removes the strongest built-in check and should not become the normal escape hatch.
For a workspace, I select the exact package:
cargo package -p my-library
Then I check four views:
- repository files;
cargo package --listoutput;- the extracted packaged source and normalized manifest;
- the dependency graph available to an external consumer.
The difference between two adjacent views normally locates the failure.
Repairs with different meanings
If a source asset is required at compile time, I add a narrow include policy or stop ignoring that source asset. A small explicit include list can make review easier, but it must include every source, manifest, build input, license, and documentation file the crate needs.
If a file is generated, I decide when generation belongs:
- Check in a deterministic generated artifact when consumers should compile without the generator.
- Generate inside
build.rsfrom inputs included in the crate and write only toOUT_DIR. - Remove compile-time file embedding when the data should be supplied at runtime.
If the failure is a workspace path dependency, I publish dependencies in order with compatible version declarations, or keep the package private. Copying a sibling directory into the archive is not a dependency strategy.
False fixes I avoid
--allow-dirty permits packaging an uncommitted checkout. It does not mean ignored or excluded files will enter the crate.
--no-verify skips the extracted build. It can postpone the failure until a user downloads the crate.
Running cargo clean may remove a stale local artifact, but it does not prove the package contains its inputs.
These commands are useful for their stated purposes, not as repairs for a package-content mismatch.
The regression gate
For a publishable crate, I make cargo package -p package-name part of release CI. I also keep targeted assertions on cargo package --list for unusual inputs such as templates, schemas, native sources, or generated files.
The most useful review sentence is: “show me the files and manifest that the registry user receives.” Once I treat the .crate archive as the product, local-only assumptions become much easier to see.