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
1 change: 1 addition & 0 deletions src/bin/cargo/commands/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
keep_going: args.keep_going(),
cli_features: args.cli_features()?,
reg_or_index,
dry_run: false,
},
)?;

Expand Down
4 changes: 4 additions & 0 deletions src/cargo/core/resolver/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ unable to verify that `{0}` is the same as when the lockfile was generated
&self.checksums
}

pub fn set_checksum(&mut self, pkg_id: PackageId, checksum: String) {
self.checksums.insert(pkg_id, Some(checksum));
}

pub fn metadata(&self) -> &Metadata {
&self.metadata
}
Expand Down
44 changes: 40 additions & 4 deletions src/cargo/ops/cargo_package/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ pub struct PackageOpts<'gctx> {
pub targets: Vec<String>,
pub cli_features: CliFeatures,
pub reg_or_index: Option<ops::RegistryOrIndex>,
/// Whether this packaging job is meant for a publishing dry-run.
///
/// Packaging on its own has no side effects, so a dry-run doesn't
/// make sense from that point of view. But dry-run publishing needs
/// special packaging behavior, which this flag turns on.
///
/// Specifically, we want dry-run packaging to work even if versions
/// have not yet been bumped. But then if you dry-run packaging in
/// a workspace with some declared versions that are already published,
/// the package verification step can fail with checksum mismatches.
/// So when dry-run is true, the verification step does some extra
/// checksum fudging in the lock file.
pub dry_run: bool,
}

const ORIGINAL_MANIFEST_FILE: &str = "Cargo.toml.orig";
Expand Down Expand Up @@ -125,6 +138,7 @@ enum GeneratedFile {
#[tracing::instrument(skip_all)]
fn create_package(
ws: &Workspace<'_>,
opts: &PackageOpts<'_>,
pkg: &Package,
ar_files: Vec<ArchiveFile>,
local_reg: Option<&TmpRegistry<'_>>,
Expand Down Expand Up @@ -159,7 +173,7 @@ fn create_package(
gctx.shell()
.status("Packaging", pkg.package_id().to_string())?;
dst.file().set_len(0)?;
let uncompressed_size = tar(ws, pkg, local_reg, ar_files, dst.file(), &filename)
let uncompressed_size = tar(ws, opts, pkg, local_reg, ar_files, dst.file(), &filename)
.context("failed to prepare local package for uploading")?;

dst.seek(SeekFrom::Start(0))?;
Expand Down Expand Up @@ -311,7 +325,7 @@ fn do_package<'a>(
}
}
} else {
let tarball = create_package(ws, &pkg, ar_files, local_reg.as_ref())?;
let tarball = create_package(ws, &opts, &pkg, ar_files, local_reg.as_ref())?;
if let Some(local_reg) = local_reg.as_mut() {
if pkg.publish() != &Some(Vec::new()) {
local_reg.add_package(ws, &pkg, &tarball)?;
Expand Down Expand Up @@ -720,11 +734,12 @@ fn error_custom_build_file_not_in_package(
/// Construct `Cargo.lock` for the package to be published.
fn build_lock(
ws: &Workspace<'_>,
opts: &PackageOpts<'_>,
publish_pkg: &Package,
local_reg: Option<&TmpRegistry<'_>>,
) -> CargoResult<String> {
let gctx = ws.gctx();
let orig_resolve = ops::load_pkg_lockfile(ws)?;
let mut orig_resolve = ops::load_pkg_lockfile(ws)?;

let mut tmp_ws = Workspace::ephemeral(publish_pkg.clone(), ws.gctx(), None, true)?;

Expand All @@ -736,6 +751,18 @@ fn build_lock(
local_reg.upstream,
local_reg.root.as_path_unlocked().to_owned(),
);
if opts.dry_run {
if let Some(orig_resolve) = orig_resolve.as_mut() {
let upstream_in_lock = if local_reg.upstream.is_crates_io() {
SourceId::crates_io(gctx)?
} else {
local_reg.upstream
};
for (p, s) in local_reg.checksums() {
orig_resolve.set_checksum(p.with_source_id(upstream_in_lock), s.to_owned());
}
}
}
}
let mut tmp_reg = tmp_ws.package_registry()?;

Expand Down Expand Up @@ -811,6 +838,7 @@ fn check_metadata(pkg: &Package, gctx: &GlobalContext) -> CargoResult<()> {
/// Returns the uncompressed size of the contents of the new archive file.
fn tar(
ws: &Workspace<'_>,
opts: &PackageOpts<'_>,
pkg: &Package,
local_reg: Option<&TmpRegistry<'_>>,
ar_files: Vec<ArchiveFile>,
Expand Down Expand Up @@ -868,7 +896,7 @@ fn tar(
GeneratedFile::Manifest(_) => {
publish_pkg.manifest().to_normalized_contents()?
}
GeneratedFile::Lockfile(_) => build_lock(ws, &publish_pkg, local_reg)?,
GeneratedFile::Lockfile(_) => build_lock(ws, opts, &publish_pkg, local_reg)?,
GeneratedFile::VcsInfo(ref s) => serde_json::to_string_pretty(s)?,
};
header.set_entry_type(EntryType::file());
Expand Down Expand Up @@ -1062,6 +1090,7 @@ struct TmpRegistry<'a> {
gctx: &'a GlobalContext,
upstream: SourceId,
root: Filesystem,
checksums: HashMap<PackageId, String>,
_lock: FileLock,
}

Expand All @@ -1073,6 +1102,7 @@ impl<'a> TmpRegistry<'a> {
gctx,
root,
upstream,
checksums: HashMap::new(),
_lock,
};
// If there's an old temporary registry, delete it.
Expand Down Expand Up @@ -1118,6 +1148,8 @@ impl<'a> TmpRegistry<'a> {
.update_file(tar.file())?
.finish_hex();

self.checksums.insert(package.package_id(), cksum.clone());

let deps: Vec<_> = new_crate
.deps
.into_iter()
Expand Down Expand Up @@ -1178,4 +1210,8 @@ impl<'a> TmpRegistry<'a> {
dst.write_all(index_line.as_bytes())?;
Ok(())
}

fn checksums(&self) -> impl Iterator<Item = (PackageId, &str)> {
self.checksums.iter().map(|(p, s)| (*p, s.as_str()))
}
}
1 change: 1 addition & 0 deletions src/cargo/ops/registry/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
keep_going: opts.keep_going,
cli_features: opts.cli_features.clone(),
reg_or_index: reg_or_index.clone(),
dry_run: opts.dry_run,
},
pkgs,
)?;
Expand Down
77 changes: 77 additions & 0 deletions tests/testsuite/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8082,3 +8082,80 @@ fn unpublished_dependency() {
(),
);
}

// This is a companion to `publish::checksum_changed`, but because this one
// is packaging without dry-run, it should fail.
#[cargo_test]
fn checksum_changed() {
let registry = registry::RegistryBuilder::new()
.http_api()
.http_index()
.build();

Package::new("dep", "1.0.0").publish();
Package::new("transitive", "1.0.0")
.dep("dep", "1.0.0")
.publish();

let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["dep"]

[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
license = "MIT"
description = "foo"
documentation = "foo"

[dependencies]
dep = { path = "./dep", version = "1.0.0" }
transitive = "1.0.0"
"#,
)
.file("src/lib.rs", "")
.file(
"dep/Cargo.toml",
r#"
[package]
name = "dep"
version = "1.0.0"
edition = "2015"
"#,
)
.file("dep/src/lib.rs", "")
.build();

p.cargo("check").run();

p.cargo("package --workspace -Zpackage-workspace")
.masquerade_as_nightly_cargo(&["package-workspace"])
.replace_crates_io(registry.index_url())
.with_status(101)
.with_stderr_data(str![[r#"
[WARNING] manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
[PACKAGING] dep v1.0.0 ([ROOT]/foo/dep)
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
[ERROR] failed to prepare local package for uploading

Caused by:
checksum for `dep v1.0.0` changed between lock files

this could be indicative of a few possible errors:

* the lock file is corrupt
* a replacement source in use (e.g., a mirror) returned a different checksum
* the source itself may be corrupt in one way or another

unable to verify that `dep v1.0.0` is the same as when the lockfile was generated

"#]])
.run();
}
76 changes: 76 additions & 0 deletions tests/testsuite/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4378,3 +4378,79 @@ fn all_unpublishable_packages() {
"#]])
.run();
}

#[cargo_test]
fn checksum_changed() {
let registry = RegistryBuilder::new().http_api().http_index().build();

Package::new("dep", "1.0.0").publish();
Package::new("transitive", "1.0.0")
.dep("dep", "1.0.0")
.publish();

let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["dep"]

[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
license = "MIT"
description = "foo"
documentation = "foo"

[dependencies]
dep = { path = "./dep", version = "1.0.0" }
transitive = "1.0.0"
"#,
)
.file("src/lib.rs", "")
.file(
"dep/Cargo.toml",
r#"
[package]
name = "dep"
version = "1.0.0"
edition = "2015"
"#,
)
.file("dep/src/lib.rs", "")
.build();

p.cargo("check").run();

p.cargo("publish --dry-run --workspace -Zpackage-workspace")
.masquerade_as_nightly_cargo(&["package-workspace"])
.replace_crates_io(registry.index_url())
.with_stderr_data(str![[r#"
[UPDATING] crates.io index
[WARNING] crate [email protected] already exists on crates.io index
[WARNING] manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
[PACKAGING] dep v1.0.0 ([ROOT]/foo/dep)
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
[UPDATING] crates.io index
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[VERIFYING] dep v1.0.0 ([ROOT]/foo/dep)
[COMPILING] dep v1.0.0 ([ROOT]/foo/target/package/dep-1.0.0)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[VERIFYING] foo v0.0.1 ([ROOT]/foo)
[UNPACKING] dep v1.0.0 (registry `[ROOT]/foo/target/package/tmp-registry`)
[COMPILING] dep v1.0.0
[COMPILING] transitive v1.0.0
[COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[UPLOADING] dep v1.0.0 ([ROOT]/foo/dep)
[WARNING] aborting upload due to dry run
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
[WARNING] aborting upload due to dry run

"#]])
.run();
}