From 8771230eb89f1ec6a8ed38a8296997db99613b26 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Thu, 13 Jun 2024 15:10:50 -0500 Subject: [PATCH 1/3] Factor out a function for building a NewCrate. Co-Authored-By: Tor Hovland <55164+torhovland@users.noreply.github.com> --- src/cargo/ops/registry/publish.rs | 75 +++++++++++++++++-------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs index 4ce5e36a50a..ff7653acff3 100644 --- a/src/cargo/ops/registry/publish.rs +++ b/src/cargo/ops/registry/publish.rs @@ -321,14 +321,11 @@ fn verify_dependencies( Ok(()) } -fn transmit( +pub(crate) fn prepare_transmit( gctx: &GlobalContext, pkg: &Package, - tarball: &File, - registry: &mut Registry, registry_id: SourceId, - dry_run: bool, -) -> CargoResult<()> { +) -> CargoResult { let deps = pkg .dependencies() .iter() @@ -410,12 +407,6 @@ fn transmit( } } - // Do not upload if performing a dry run - if dry_run { - gctx.shell().warn("aborting upload due to dry run")?; - return Ok(()); - } - let deps_set = deps .iter() .map(|dep| dep.name.clone()) @@ -447,30 +438,46 @@ fn transmit( None => BTreeMap::new(), }; + Ok(NewCrate { + name: pkg.name().to_string(), + vers: pkg.version().to_string(), + deps, + features: string_features, + authors: authors.clone(), + description: description.clone(), + homepage: homepage.clone(), + documentation: documentation.clone(), + keywords: keywords.clone(), + categories: categories.clone(), + readme: readme_content, + readme_file: readme.clone(), + repository: repository.clone(), + license: license.clone(), + license_file: license_file.clone(), + badges: badges.clone(), + links: links.clone(), + rust_version, + }) +} + +fn transmit( + gctx: &GlobalContext, + pkg: &Package, + tarball: &File, + registry: &mut Registry, + registry_id: SourceId, + dry_run: bool, +) -> CargoResult<()> { + let new_crate = prepare_transmit(gctx, pkg, registry_id)?; + + // Do not upload if performing a dry run + if dry_run { + gctx.shell().warn("aborting upload due to dry run")?; + return Ok(()); + } + let warnings = registry - .publish( - &NewCrate { - name: pkg.name().to_string(), - vers: pkg.version().to_string(), - deps, - features: string_features, - authors: authors.clone(), - description: description.clone(), - homepage: homepage.clone(), - documentation: documentation.clone(), - keywords: keywords.clone(), - categories: categories.clone(), - readme: readme_content, - readme_file: readme.clone(), - repository: repository.clone(), - license: license.clone(), - license_file: license_file.clone(), - badges: badges.clone(), - links: links.clone(), - rust_version, - }, - tarball, - ) + .publish(&new_crate, tarball) .with_context(|| format!("failed to publish to registry at {}", registry.host()))?; if !warnings.invalid_categories.is_empty() { From cbc836d4b0f6a4fa3b0165970acb5d88a67108aa Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Thu, 13 Jun 2024 14:30:54 -0500 Subject: [PATCH 2/3] Add tests showing incorrect workspace packaging. Co-Authored-By: Tor Hovland <55164+torhovland@users.noreply.github.com> --- tests/testsuite/package.rs | 632 +++++++++++++++++++++++++++++++++++++ 1 file changed, 632 insertions(+) diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 3ed3c0e2ed9..33731f91599 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -5076,3 +5076,635 @@ path = "examples/z.rs" )], ); } + +// A workspace with three projects that depend on one another (level1 -> level2 -> level3). +// level1 is a binary package, to test lockfile generation. +#[cargo_test] +fn workspace_with_local_deps() { + let crates_io = registry::init(); + let p = + project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["level1", "level2", "level3"] + + [workspace.dependencies] + level2 = { path = "level2", version = "0.0.1" } + "# + ) + .file( + "level1/Cargo.toml", + r#" + [package] + name = "level1" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level1" + repository = "bar" + + [dependencies] + # Let one dependency also specify features, for the added test coverage when generating package files. + level2 = { workspace = true, features = ["foo"] } + "#, + ) + .file("level1/src/main.rs", "fn main() {}") + .file( + "level2/Cargo.toml", + r#" + [package] + name = "level2" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level2" + repository = "bar" + + [features] + foo = [] + + [dependencies] + level3 = { path = "../level3", version = "0.0.1" } + "# + ) + .file("level2/src/lib.rs", "") + .file( + "level3/Cargo.toml", + r#" + [package] + name = "level3" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level3" + repository = "bar" + "#, + ) + .file("level3/src/lib.rs", "") + .build(); + + p.cargo("package") + .replace_crates_io(crates_io.index_url()) + .with_status(101) + .with_stdout_data("") + .with_stderr_data(str![[r#" +[PACKAGING] level1 v0.0.1 ([ROOT]/foo/level1) +[UPDATING] crates.io index +[ERROR] failed to prepare local package for uploading + +Caused by: + no matching package named `level2` found + location searched: registry `crates-io` + required by package `level1 v0.0.1 ([ROOT]/foo/level1)` + +"#]]) + .run(); +} + +#[cargo_test] +fn workspace_with_local_deps_packaging_one_fails() { + let crates_io = registry::init(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["level1", "level2"] + "#, + ) + .file( + "level1/Cargo.toml", + r#" + [package] + name = "level1" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level1" + repository = "bar" + + [dependencies] + level2 = { path = "../level2", version = "0.0.1" } + "#, + ) + .file("level1/src/lib.rs", "") + .file( + "level2/Cargo.toml", + r#" + [package] + name = "level2" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level2" + repository = "bar" + "#, + ) + .file("level2/src/lib.rs", "") + .build(); + + // We can't package just level1, because there's a dependency on level2. + p.cargo("package -p level1") + .replace_crates_io(crates_io.index_url()) + .with_status(101) + .with_stdout_data("") + .with_stderr_data(str![[r#" +[PACKAGING] level1 v0.0.1 ([ROOT]/foo/level1) +[PACKAGED] 3 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[VERIFYING] level1 v0.0.1 ([ROOT]/foo/level1) +[UPDATING] crates.io index +[ERROR] failed to verify package tarball + +Caused by: + no matching package named `level2` found + location searched: registry `crates-io` + required by package `level1 v0.0.1 ([ROOT]/foo/target/package/level1-0.0.1)` + +"#]]) + .run(); +} + +// Same as workspace_with_local_deps_packaging_one_fails except that we're +// packaging a bin. This fails during lock-file generation instead of during verification. +#[cargo_test] +fn workspace_with_local_deps_packaging_one_bin_fails() { + let crates_io = registry::init(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["level1", "level2"] + "#, + ) + .file( + "level1/Cargo.toml", + r#" + [package] + name = "level1" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level1" + repository = "bar" + + [dependencies] + level2 = { path = "../level2", version = "0.0.1" } + "#, + ) + .file("level1/src/main.rs", "fn main() {}") + .file( + "level2/Cargo.toml", + r#" + [package] + name = "level2" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level2" + repository = "bar" + "#, + ) + .file("level2/src/lib.rs", "") + .build(); + + // We can't package just level1, because there's a dependency on level2. + p.cargo("package -p level1") + .replace_crates_io(crates_io.index_url()) + .with_status(101) + .with_stdout_data("") + .with_stderr_data(str![[r#" +[PACKAGING] level1 v0.0.1 ([ROOT]/foo/level1) +[UPDATING] crates.io index +[ERROR] failed to prepare local package for uploading + +Caused by: + no matching package named `level2` found + location searched: registry `crates-io` + required by package `level1 v0.0.1 ([ROOT]/foo/level1)` + +"#]]) + .run(); +} + +// Here we don't package the whole workspace, but it succeeds because we package a +// dependency-closed subset. +#[cargo_test] +fn workspace_with_local_deps_packaging_one_with_needed_deps() { + let crates_io = registry::init(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["level1", "level2", "level3"] + "#, + ) + .file( + "level1/Cargo.toml", + r#" + [package] + name = "level1" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level1" + repository = "bar" + + [dependencies] + level2 = { path = "../level2", version = "0.0.1" } + "#, + ) + .file("level1/src/main.rs", "fn main() {}") + .file( + "level2/Cargo.toml", + r#" + [package] + name = "level2" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level2" + repository = "bar" + + [dependencies] + level3 = { path = "../level3", version = "0.0.1" } + "#, + ) + .file("level2/src/lib.rs", "") + .file( + "level3/Cargo.toml", + r#" + [package] + name = "level3" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level3" + repository = "bar" + "#, + ) + .file("level3/src/lib.rs", "") + .build(); + + p.cargo("package -p level2 -p level3") + .replace_crates_io(crates_io.index_url()) + .with_status(101) + .with_stdout_data("") + .with_stderr_data(str![[r#" +[PACKAGING] level2 v0.0.1 ([ROOT]/foo/level2) +[PACKAGED] 3 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[VERIFYING] level2 v0.0.1 ([ROOT]/foo/level2) +[UPDATING] crates.io index +[ERROR] failed to verify package tarball + +Caused by: + no matching package named `level3` found + location searched: registry `crates-io` + required by package `level2 v0.0.1 ([ROOT]/foo/target/package/level2-0.0.1)` + +"#]]) + .run(); +} + +// package --list in a workspace lists all the files in all the packages. +// The output is not very good, though. See https://github.com/rust-lang/cargo/issues/13953 +#[cargo_test] +fn workspace_with_local_deps_list() { + let crates_io = registry::init(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["level1", "level2"] + "#, + ) + .file( + "level1/Cargo.toml", + r#" + [package] + name = "level1" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level1" + repository = "bar" + + [dependencies] + level2 = { path = "../level2", version = "0.0.1" } + "#, + ) + .file("level1/src/main.rs", "fn main() {}") + .file( + "level2/Cargo.toml", + r#" + [package] + name = "level2" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level2" + repository = "bar" + "#, + ) + .file("level2/src/lib.rs", "") + .build(); + + p.cargo("package --list") + .replace_crates_io(crates_io.index_url()) + .with_stdout_data(str![[r#" +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +Cargo.toml +Cargo.toml.orig +src/lib.rs + +"#]]) + .with_stderr_data("") + .run(); +} + +#[cargo_test] +fn workspace_with_local_deps_index_mismatch() { + let alt_reg = registry::RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + // We're publishing to an alternate index, but the manifests don't specify it. + // The intra-workspace deps won't be found. + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["level1", "level2"] + "#, + ) + .file( + "level1/Cargo.toml", + r#" + [package] + name = "level1" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level1" + repository = "bar" + + [dependencies] + level2 = { path = "../level2", version = "0.0.1" } + "#, + ) + .file("level1/src/main.rs", "fn main() {}") + .file( + "level2/Cargo.toml", + r#" + [package] + name = "level2" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level2" + repository = "bar" + "#, + ) + .file("level2/src/lib.rs", "") + .build(); + p.cargo(&format!("package --index {}", alt_reg.index_url())) + .with_status(1) + .with_stdout_data("") + .with_stderr_data(str![[r#" +[ERROR] unexpected argument '--index' found + +Usage: cargo package [OPTIONS] + +For more information, try '--help'. + +"#]]) + .run(); +} + +#[cargo_test] +fn workspace_with_local_deps_alternative_index() { + let alt_reg = registry::RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["level1", "level2"] + "#, + ) + .file( + "level1/Cargo.toml", + r#" + [package] + name = "level1" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level1" + repository = "bar" + + [dependencies] + level2 = { path = "../level2", version = "0.0.1", registry = "alternative" } + "#, + ) + .file("level1/src/main.rs", "fn main() {}") + .file( + "level2/Cargo.toml", + r#" + [package] + name = "level2" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "level2" + repository = "bar" + "#, + ) + .file("level2/src/lib.rs", "") + .build(); + + p.cargo(&format!("package --index {}", alt_reg.index_url())) + .with_status(1) + .with_stdout_data("") + .with_stderr_data(str![[r#" +[ERROR] unexpected argument '--index' found + +Usage: cargo package [OPTIONS] + +For more information, try '--help'. + +"#]]) + .run(); +} + +#[cargo_test] +fn workspace_with_local_dep_already_published() { + let reg = registry::init(); + + Package::new("dep", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["dep", "main"] + "#, + ) + .file( + "main/Cargo.toml", + r#" + [package] + name = "main" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "main" + repository = "bar" + + [dependencies] + dep = { path = "../dep", version = "0.1.0" } + "#, + ) + .file("main/src/main.rs", "fn main() {}") + .file( + "dep/Cargo.toml", + r#" + [package] + name = "dep" + version = "0.1.0" + edition = "2015" + authors = [] + license = "MIT" + description = "dep" + repository = "bar" + "#, + ) + .file("dep/src/lib.rs", "") + .build(); + + p.cargo("package") + .replace_crates_io(reg.index_url()) + .with_stderr_data( + str![[r#" +[PACKAGING] dep v0.1.0 ([ROOT]/foo/dep) +[PACKAGED] 3 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[PACKAGING] main v0.0.1 ([ROOT]/foo/main) +[UPDATING] crates.io index +[VERIFYING] dep v0.1.0 ([ROOT]/foo/dep) +[COMPILING] dep v0.1.0 ([ROOT]/foo/target/package/dep-0.1.0) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s +[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[VERIFYING] main v0.0.1 ([ROOT]/foo/main) +[DOWNLOADING] crates ... +[DOWNLOADED] dep v0.1.0 +[COMPILING] dep v0.1.0 +[COMPILING] main v0.0.1 ([ROOT]/foo/target/package/main-0.0.1) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]] + .unordered(), + ) + .run(); +} + +#[cargo_test] +fn workspace_with_local_and_remote_deps() { + let reg = registry::init(); + + Package::new("dep", "0.0.1").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["dep", "main"] + "#, + ) + .file( + "main/Cargo.toml", + r#" + [package] + name = "main" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "main" + repository = "bar" + + [dependencies] + dep = { path = "../dep", version = "0.1.0" } + old_dep = { package = "dep", version = "0.0.1" } + "#, + ) + .file("main/src/main.rs", "fn main() {}") + .file( + "dep/Cargo.toml", + r#" + [package] + name = "dep" + version = "0.1.0" + edition = "2015" + authors = [] + license = "MIT" + description = "dep" + repository = "bar" + "#, + ) + .file("dep/src/lib.rs", "") + .build(); + + p.cargo("package") + .replace_crates_io(reg.index_url()) + .with_status(101) + .with_stderr_data(str![[r#" +[PACKAGING] dep v0.1.0 ([ROOT]/foo/dep) +[PACKAGED] 3 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[VERIFYING] dep v0.1.0 ([ROOT]/foo/dep) +[COMPILING] dep v0.1.0 ([ROOT]/foo/target/package/dep-0.1.0) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s +[PACKAGING] main v0.0.1 ([ROOT]/foo/main) +[UPDATING] crates.io index +[ERROR] failed to prepare local package for uploading + +Caused by: + failed to select a version for the requirement `dep = "^0.1.0"` + candidate versions found which didn't match: 0.0.1 + location searched: crates.io index + required by package `main v0.0.1 ([ROOT]/foo/main)` + +"#]]) + .run(); +} From a2f0a9e7d62da5c3eb11d4e7a085d70c53f21272 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Sun, 23 Jun 2024 18:11:58 -0500 Subject: [PATCH 3/3] Adds support for packaging workspaces. Takes local dependencies into account when packaging a workspace. Builds a temporary package registry to provide local dependencies, and overlays it on the upstream registry. This adds `--registry` and `--index` flags to `cargo package`. They act much like the same arguments to `cargo publish`, except that of course we are not actually publishing to the specified registry. Instead, these arguments affect lock-file generation for intra-workspace dependencies: when simultaneously packaging a crate and one of its dependencies, the lock-file will be generated under the assumption that the dependency will be published to the specified registry. Co-Authored-By: Tor Hovland <55164+torhovland@users.noreply.github.com> --- src/bin/cargo/commands/package.rs | 20 + src/cargo/core/features.rs | 2 + src/cargo/ops/cargo_package.rs | 333 ++++++++++++++-- src/cargo/ops/registry/mod.rs | 8 +- src/cargo/ops/registry/publish.rs | 1 + src/cargo/sources/registry/index/mod.rs | 54 +-- src/cargo/sources/registry/mod.rs | 2 +- src/doc/man/cargo-package.md | 15 +- src/doc/man/generated_txt/cargo-package.txt | 10 + src/doc/src/commands/cargo-package.md | 17 +- src/etc/_cargo | 3 +- src/etc/cargo.bashcomp.sh | 2 +- src/etc/man/cargo-package.1 | 19 +- tests/testsuite/cargo/z_help/stdout.term.svg | 34 +- .../cargo_package/help/stdout.term.svg | 74 ++-- tests/testsuite/package.rs | 361 +++++++++++++++--- tests/testsuite/publish_lockfile.rs | 8 +- 17 files changed, 775 insertions(+), 188 deletions(-) diff --git a/src/bin/cargo/commands/package.rs b/src/bin/cargo/commands/package.rs index 27b48097c6a..42b3ac3d03d 100644 --- a/src/bin/cargo/commands/package.rs +++ b/src/bin/cargo/commands/package.rs @@ -5,6 +5,8 @@ use cargo::ops::{self, PackageOpts}; pub fn cli() -> Command { subcommand("package") .about("Assemble the local package into a distributable tarball") + .arg_index("Registry index URL to prepare the package for (unstable)") + .arg_registry("Registry to prepare the package for (unstable)") .arg( flag( "list", @@ -41,6 +43,23 @@ pub fn cli() -> Command { } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { + if args._value_of("registry").is_some() { + gctx.cli_unstable().fail_if_stable_opt_custom_z( + "--registry", + 13947, + "package-workspace", + gctx.cli_unstable().package_workspace, + )?; + } + if args._value_of("index").is_some() { + gctx.cli_unstable().fail_if_stable_opt_custom_z( + "--index", + 13947, + "package-workspace", + gctx.cli_unstable().package_workspace, + )?; + } + let reg_or_index = args.registry_or_index(gctx)?; let ws = args.workspace(gctx)?; if ws.root_maybe().is_embedded() { return Err(anyhow::format_err!( @@ -64,6 +83,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { jobs: args.jobs()?, keep_going: args.keep_going(), cli_features: args.cli_features()?, + reg_or_index, }, )?; diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index f613bdf9094..c44b8d51eb4 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -774,6 +774,7 @@ unstable_cli_options!( mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"), next_lockfile_bump: bool, no_index_update: bool = ("Do not update the registry index even if the cache is outdated"), + package_workspace: bool = ("Handle intra-workspace dependencies when packaging"), panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"), profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"), public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"), @@ -1276,6 +1277,7 @@ impl CliUnstable { // can also be set in .cargo/config or with and ENV "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?, "no-index-update" => self.no_index_update = parse_empty(k, v)?, + "package-workspace" => self.package_workspace= parse_empty(k, v)?, "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "public-dependency" => self.public_dependency = parse_empty(k, v)?, "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?, diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index b32f2ed20d0..a83df435b2d 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::fs::{self, File}; use std::io::prelude::*; use std::io::SeekFrom; @@ -7,19 +7,23 @@ use std::sync::Arc; use std::task::Poll; use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor}; +use crate::core::dependency::DepKind; use crate::core::manifest::Target; use crate::core::resolver::CliFeatures; use crate::core::resolver::HasDevUnits; use crate::core::{Feature, PackageIdSpecQuery, Shell, Verbosity, Workspace}; use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId}; -use crate::sources::PathSource; +use crate::sources::registry::index::{IndexPackage, RegistryDependency}; +use crate::sources::{PathSource, SourceConfigMap, CRATES_IO_REGISTRY}; use crate::util::cache_lock::CacheLockMode; use crate::util::context::JobsConfig; use crate::util::errors::CargoResult; use crate::util::toml::prepare_for_publish; -use crate::util::{self, human_readable_bytes, restricted_names, FileLock, GlobalContext}; +use crate::util::{ + self, human_readable_bytes, restricted_names, FileLock, Filesystem, GlobalContext, Graph, +}; use crate::{drop_println, ops}; -use anyhow::Context as _; +use anyhow::{bail, Context as _}; use cargo_util::paths; use flate2::read::GzDecoder; use flate2::{Compression, GzBuilder}; @@ -28,6 +32,8 @@ use tar::{Archive, Builder, EntryType, Header, HeaderMode}; use tracing::debug; use unicase::Ascii as UncasedAscii; +use super::RegistryOrIndex; + #[derive(Clone)] pub struct PackageOpts<'gctx> { pub gctx: &'gctx GlobalContext, @@ -40,6 +46,7 @@ pub struct PackageOpts<'gctx> { pub to_package: ops::Packages, pub targets: Vec, pub cli_features: CliFeatures, + pub reg_or_index: Option, } const ORIGINAL_MANIFEST_FILE: &str = "Cargo.toml.orig"; @@ -101,10 +108,10 @@ pub fn package_one( assert!(!opts.list); let ar_files = prepare_archive(ws, pkg, opts)?; - let tarball = create_package(ws, pkg, ar_files)?; + let tarball = create_package(ws, pkg, ar_files, None)?; if opts.verify { - run_verify(ws, pkg, &tarball, opts)?; + run_verify(ws, pkg, &tarball, None, opts)?; } Ok(tarball) @@ -115,6 +122,7 @@ fn create_package( ws: &Workspace<'_>, pkg: &Package, ar_files: Vec, + local_reg: Option<&TmpRegistry<'_>>, ) -> CargoResult { let gctx = ws.gctx(); let filecount = ar_files.len(); @@ -138,7 +146,7 @@ fn create_package( gctx.shell() .status("Packaging", pkg.package_id().to_string())?; dst.file().set_len(0)?; - let uncompressed_size = tar(ws, pkg, ar_files, dst.file(), &filename) + let uncompressed_size = tar(ws, pkg, local_reg, ar_files, dst.file(), &filename) .with_context(|| "failed to prepare local package for uploading")?; dst.seek(SeekFrom::Start(0))?; @@ -166,7 +174,70 @@ fn create_package( return Ok(dst); } -pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult>> { +/// Determine which registry the packages are for. +/// +/// The registry only affects the built packages if there are dependencies within the +/// packages that we're packaging: if we're packaging foo-bin and foo-lib, and foo-bin +/// depends on foo-lib, then the foo-lib entry in foo-bin's lockfile will depend on the +/// registry that we're building packages for. +fn infer_registry( + gctx: &GlobalContext, + pkgs: &[&Package], + reg_or_index: Option, +) -> CargoResult { + let publish_registry = if let Some(RegistryOrIndex::Registry(registry)) = reg_or_index.as_ref() + { + Some(registry.clone()) + } else if let Some([first_pkg_reg]) = pkgs[0].publish().as_deref() { + // If no registry is specified in the command, but all of the packages + // to publish have the same, unique allowed registry, push to that one. + if pkgs[1..].iter().all(|p| p.publish() == pkgs[0].publish()) { + Some(first_pkg_reg.clone()) + } else { + None + } + } else { + None + }; + + // Validate the registry against the packages' allow-lists. For backwards compatibility, we + // skip this if only a single package is being published (because in that case the registry + // doesn't affect the packaging step). + if pkgs.len() > 1 { + let reg_name = publish_registry.as_deref().unwrap_or(CRATES_IO_REGISTRY); + for pkg in pkgs { + if let Some(allowed) = pkg.publish().as_ref() { + if !allowed.iter().any(|a| a == reg_name) { + bail!( + "`{}` cannot be packaged.\n\ + The registry `{}` is not listed in the `package.publish` value in Cargo.toml.", + pkg.name(), + reg_name + ); + } + } + } + } + + let sid = match reg_or_index { + None => SourceId::crates_io(gctx)?, + Some(RegistryOrIndex::Registry(r)) => SourceId::alt_registry(gctx, &r)?, + Some(RegistryOrIndex::Index(url)) => SourceId::for_registry(&url)?, + }; + + // Load source replacements that are built-in to Cargo. + let sid = SourceConfigMap::empty(gctx)? + .load(sid, &HashSet::new())? + .replaced_source_id(); + + Ok(sid) +} + +/// Packages an entire workspace. +/// +/// Returns the generated package files. If `opts.list` is true, skips +/// generating package files and returns an empty list. +pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult> { let specs = &opts.to_package.to_package_id_specs(ws)?; // If -p is used, we should check spec is matched with the members (See #13719) if let ops::Packages::Packages(_) = opts.to_package { @@ -176,7 +247,6 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult