mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-25 11:14:46 +00:00
Override Cargo.lock
checksums when doing a dry-run publish
(#15711)
Fixes #15647. When dry-run publishing workspace without bumping versions first, the package-verification step would fail because it would see checksum mismatches between the old lock file (that saw index deps) and the new lock file where those index deps got replaced by local packages with the same version. In this PR, the packaging step modifies the old lock file's checksums before re-resolving, but only in dry-run mode.
This commit is contained in:
commit
f013ef54bb
@ -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,
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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";
|
||||
@ -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<'_>>,
|
||||
@ -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))?;
|
||||
@ -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)?;
|
||||
@ -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)?;
|
||||
|
||||
@ -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()?;
|
||||
|
||||
@ -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>,
|
||||
@ -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());
|
||||
@ -1062,6 +1090,7 @@ struct TmpRegistry<'a> {
|
||||
gctx: &'a GlobalContext,
|
||||
upstream: SourceId,
|
||||
root: Filesystem,
|
||||
checksums: HashMap<PackageId, String>,
|
||||
_lock: FileLock,
|
||||
}
|
||||
|
||||
@ -1073,6 +1102,7 @@ impl<'a> TmpRegistry<'a> {
|
||||
gctx,
|
||||
root,
|
||||
upstream,
|
||||
checksums: HashMap::new(),
|
||||
_lock,
|
||||
};
|
||||
// If there's an old temporary registry, delete it.
|
||||
@ -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()
|
||||
@ -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()))
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)?;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 dep@1.0.0 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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user