From 7919bda55fd7e461e98e6c59c3d2af8264538f53 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Thu, 15 Aug 2024 15:18:13 +0700 Subject: [PATCH 1/5] Add misbehaving test for packaging unpublishable workspace --- tests/testsuite/package.rs | 131 +++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 799540623..812d8ddd3 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -6173,6 +6173,77 @@ The registry `alternative` is not listed in the `package.publish` value in Cargo .run(); } +#[cargo_test] +fn registry_inference_ignores_unpublishable() { + let _alt_reg = registry::RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + + 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" + publish = false + + [dependencies] + dep = { path = "../dep", version = "0.1.0", registry = "alternative" } + "#, + ) + .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" + publish = ["alternative"] + "#, + ) + .file("dep/src/lib.rs", "") + .build(); + + p.cargo("package -Zpackage-workspace") + .masquerade_as_nightly_cargo(&["package-workspace"]) + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] conflicts between `package.publish` fields in the selected packages + +"#]]) + .run(); + + p.cargo("package -Zpackage-workspace --registry=alternative") + .masquerade_as_nightly_cargo(&["package-workspace"]) + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] `main` cannot be packaged. +The registry `alternative` is not listed in the `package.publish` value in Cargo.toml. + +"#]]) + .run(); +} + #[cargo_test] fn registry_not_inferred_because_of_multiple_options() { let _alt_reg = registry::RegistryBuilder::new() @@ -6337,3 +6408,63 @@ fn registry_not_inferred_because_of_mismatch() { "#]]) .run(); } + +#[cargo_test] +fn unpublishable_dependency() { + let _alt_reg = registry::RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + + 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", registry = "alternative" } + "#, + ) + .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" + publish = false + "#, + ) + .file("dep/src/lib.rs", "") + .build(); + + p.cargo("package -Zpackage-workspace") + .masquerade_as_nightly_cargo(&["package-workspace"]) + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] conflicts between `package.publish` fields in the selected packages + +"#]]) + .run(); +} From 37812e38022b98fd12fa0ab0c41fdfc502a77dc1 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Mon, 19 Aug 2024 13:29:25 +0700 Subject: [PATCH 2/5] Split infer_registry into inference and validation parts. --- src/cargo/ops/cargo_package.rs | 176 +++++++++++++++++---------------- 1 file changed, 89 insertions(+), 87 deletions(-) diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 17ef0ecbb..a4ef0b17e 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -6,7 +6,6 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::task::Poll; -use super::RegistryOrIndex; use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor}; use crate::core::dependency::DepKind; use crate::core::manifest::Target; @@ -15,6 +14,7 @@ use crate::core::resolver::HasDevUnits; use crate::core::{Feature, PackageIdSpecQuery, Shell, Verbosity, Workspace}; use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId}; use crate::ops::lockfile::LOCKFILE_NAME; +use crate::ops::registry::RegistryOrIndex; use crate::sources::registry::index::{IndexPackage, RegistryDependency}; use crate::sources::{PathSource, SourceConfigMap, CRATES_IO_REGISTRY}; use crate::util::cache_lock::CacheLockMode; @@ -174,95 +174,43 @@ fn create_package( return Ok(dst); } -/// 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 reg_or_index = match reg_or_index { - Some(r) => r, - None => { - if pkgs[1..].iter().all(|p| p.publish() == pkgs[0].publish()) { - // If all packages have the same publish settings, we take that as the default. - match pkgs[0].publish().as_deref() { - Some([unique_pkg_reg]) => RegistryOrIndex::Registry(unique_pkg_reg.to_owned()), - None | Some([]) => RegistryOrIndex::Registry(CRATES_IO_REGISTRY.to_owned()), - Some([reg, ..]) if pkgs.len() == 1 => { - // For backwards compatibility, avoid erroring if there's only one package. - // The registry doesn't affect packaging in this case. - RegistryOrIndex::Registry(reg.to_owned()) - } - Some(regs) => { - let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect(); - regs.sort(); - regs.dedup(); - // unwrap: the match block ensures that there's more than one reg. - let (last_reg, regs) = regs.split_last().unwrap(); - bail!( - "--registry is required to disambiguate between {} or {} registries", - regs.join(", "), - last_reg - ) - } - } - } else { - let common_regs = pkgs - .iter() - // `None` means "all registries", so drop them instead of including them - // in the intersection. - .filter_map(|p| p.publish().as_deref()) - .map(|p| p.iter().collect::>()) - .reduce(|xs, ys| xs.intersection(&ys).cloned().collect()) - .unwrap_or_default(); - if common_regs.is_empty() { - bail!("conflicts between `package.publish` fields in the selected packages"); - } else { - bail!( - "--registry is required because not all `package.publish` settings agree", - ); - } +/// If this set of packages has an unambiguous publish registry, find it. +pub(crate) fn infer_registry(pkgs: &[&Package]) -> CargoResult> { + if pkgs[1..].iter().all(|p| p.publish() == pkgs[0].publish()) { + // If all packages have the same publish settings, we take that as the default. + match pkgs[0].publish().as_deref() { + Some([unique_pkg_reg]) => { + Ok(Some(RegistryOrIndex::Registry(unique_pkg_reg.to_owned()))) + } + None | Some([]) => Ok(None), + Some(regs) => { + let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect(); + regs.sort(); + regs.dedup(); + // unwrap: the match block ensures that there's more than one reg. + let (last_reg, regs) = regs.split_last().unwrap(); + bail!( + "--registry is required to disambiguate between {} or {} registries", + regs.join(", "), + last_reg + ) } } - }; - - // 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 { - if let RegistryOrIndex::Registry(reg_name) = ®_or_index { - 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 - ); - } - } - } + } else { + let common_regs = pkgs + .iter() + // `None` means "all registries", so drop them instead of including them + // in the intersection. + .filter_map(|p| p.publish().as_deref()) + .map(|p| p.iter().collect::>()) + .reduce(|xs, ys| xs.intersection(&ys).cloned().collect()) + .unwrap_or_default(); + if common_regs.is_empty() { + bail!("conflicts between `package.publish` fields in the selected packages"); + } else { + bail!("--registry is required because not all `package.publish` settings agree",); } } - - let sid = match reg_or_index { - RegistryOrIndex::Index(url) => SourceId::for_registry(&url)?, - RegistryOrIndex::Registry(reg) if reg == CRATES_IO_REGISTRY => SourceId::crates_io(gctx)?, - RegistryOrIndex::Registry(reg) => SourceId::alt_registry(gctx, ®)?, - }; - - // 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. @@ -294,7 +242,7 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult = pkgs.iter().map(|p| p.0).collect(); - let publish_reg = infer_registry(ws.gctx(), &just_pkgs, opts.reg_or_index.clone())?; + let publish_reg = get_registry(ws.gctx(), &just_pkgs, opts.reg_or_index.clone())?; debug!("packaging for registry {publish_reg}"); let mut local_reg = if ws.gctx().cli_unstable().package_workspace { @@ -343,6 +291,60 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult, +) -> CargoResult { + let reg_or_index = match reg_or_index { + Some(r) => Some(r), + None => infer_registry(pkgs)?, + }; + + let reg = reg_or_index + .clone() + .unwrap_or_else(|| RegistryOrIndex::Registry(CRATES_IO_REGISTRY.to_owned())); + + // 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 { + if let RegistryOrIndex::Registry(reg_name) = ® { + 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 { + RegistryOrIndex::Index(url) => SourceId::for_registry(&url)?, + RegistryOrIndex::Registry(reg) if reg == CRATES_IO_REGISTRY => SourceId::crates_io(gctx)?, + RegistryOrIndex::Registry(reg) => SourceId::alt_registry(gctx, ®)?, + }; + + // Load source replacements that are built-in to Cargo. + let sid = SourceConfigMap::empty(gctx)? + .load(sid, &HashSet::new())? + .replaced_source_id(); + + Ok(sid) +} + /// Just the part of the dependency graph that's between the packages we're packaging. /// (Is the package name a good key? Does it uniquely identify packages?) #[derive(Clone, Debug, Default)] From b3860a4a66490f6c6cf60dbd74d8fef1939df86a Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Mon, 19 Aug 2024 13:33:58 +0700 Subject: [PATCH 3/5] Move infer_registry into a more public spot --- src/cargo/ops/cargo_package.rs | 41 +--------------------------------- src/cargo/ops/registry/mod.rs | 41 +++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index a4ef0b17e..24851330e 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -14,7 +14,7 @@ use crate::core::resolver::HasDevUnits; use crate::core::{Feature, PackageIdSpecQuery, Shell, Verbosity, Workspace}; use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId}; use crate::ops::lockfile::LOCKFILE_NAME; -use crate::ops::registry::RegistryOrIndex; +use crate::ops::registry::{infer_registry, RegistryOrIndex}; use crate::sources::registry::index::{IndexPackage, RegistryDependency}; use crate::sources::{PathSource, SourceConfigMap, CRATES_IO_REGISTRY}; use crate::util::cache_lock::CacheLockMode; @@ -174,45 +174,6 @@ fn create_package( return Ok(dst); } -/// If this set of packages has an unambiguous publish registry, find it. -pub(crate) fn infer_registry(pkgs: &[&Package]) -> CargoResult> { - if pkgs[1..].iter().all(|p| p.publish() == pkgs[0].publish()) { - // If all packages have the same publish settings, we take that as the default. - match pkgs[0].publish().as_deref() { - Some([unique_pkg_reg]) => { - Ok(Some(RegistryOrIndex::Registry(unique_pkg_reg.to_owned()))) - } - None | Some([]) => Ok(None), - Some(regs) => { - let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect(); - regs.sort(); - regs.dedup(); - // unwrap: the match block ensures that there's more than one reg. - let (last_reg, regs) = regs.split_last().unwrap(); - bail!( - "--registry is required to disambiguate between {} or {} registries", - regs.join(", "), - last_reg - ) - } - } - } else { - let common_regs = pkgs - .iter() - // `None` means "all registries", so drop them instead of including them - // in the intersection. - .filter_map(|p| p.publish().as_deref()) - .map(|p| p.iter().collect::>()) - .reduce(|xs, ys| xs.intersection(&ys).cloned().collect()) - .unwrap_or_default(); - if common_regs.is_empty() { - bail!("conflicts between `package.publish` fields in the selected packages"); - } else { - bail!("--registry is required because not all `package.publish` settings agree",); - } - } -} - /// Packages an entire workspace. /// /// Returns the generated package files. If `opts.list` is true, skips diff --git a/src/cargo/ops/registry/mod.rs b/src/cargo/ops/registry/mod.rs index 016d4cebb..c6ac91a26 100644 --- a/src/cargo/ops/registry/mod.rs +++ b/src/cargo/ops/registry/mod.rs @@ -19,7 +19,7 @@ use cargo_credential::{Operation, Secret}; use crates_io::Registry; use url::Url; -use crate::core::{PackageId, SourceId}; +use crate::core::{Package, PackageId, SourceId}; use crate::sources::source::Source; use crate::sources::{RegistrySource, SourceConfigMap}; use crate::util::auth; @@ -322,3 +322,42 @@ pub(crate) struct RegistrySourceIds { /// User-defined source replacement is not applied. pub(crate) replacement: SourceId, } + +/// If this set of packages has an unambiguous publish registry, find it. +pub(crate) fn infer_registry(pkgs: &[&Package]) -> CargoResult> { + if pkgs[1..].iter().all(|p| p.publish() == pkgs[0].publish()) { + // If all packages have the same publish settings, we take that as the default. + match pkgs[0].publish().as_deref() { + Some([unique_pkg_reg]) => { + Ok(Some(RegistryOrIndex::Registry(unique_pkg_reg.to_owned()))) + } + None | Some([]) => Ok(None), + Some(regs) => { + let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect(); + regs.sort(); + regs.dedup(); + // unwrap: the match block ensures that there's more than one reg. + let (last_reg, regs) = regs.split_last().unwrap(); + bail!( + "--registry is required to disambiguate between {} or {} registries", + regs.join(", "), + last_reg + ) + } + } + } else { + let common_regs = pkgs + .iter() + // `None` means "all registries", so drop them instead of including them + // in the intersection. + .filter_map(|p| p.publish().as_deref()) + .map(|p| p.iter().collect::>()) + .reduce(|xs, ys| xs.intersection(&ys).cloned().collect()) + .unwrap_or_default(); + if common_regs.is_empty() { + bail!("conflicts between `package.publish` fields in the selected packages"); + } else { + bail!("--registry is required because not all `package.publish` settings agree",); + } + } +} From a9987f098277e63c0c543d58599cdb8e33719ba8 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Mon, 19 Aug 2024 12:06:11 +0700 Subject: [PATCH 4/5] Use the shared source building in package This changes the registry validation slightly, adding in a check forbidding implicit source replacement. This affects the tests (which configure a dummy registry for source replacement), so we also weaken the checks by only erroring for registry issues when there are actually local dependencies. --- src/cargo/ops/cargo_package.rs | 64 ++++++++++++++++------------------ src/cargo/ops/registry/mod.rs | 2 +- tests/testsuite/package.rs | 3 +- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 24851330e..de1f44f27 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap}; use std::fs::{self, File}; use std::io::prelude::*; use std::io::SeekFrom; @@ -16,7 +16,7 @@ use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId}; use crate::ops::lockfile::LOCKFILE_NAME; use crate::ops::registry::{infer_registry, RegistryOrIndex}; use crate::sources::registry::index::{IndexPackage, RegistryDependency}; -use crate::sources::{PathSource, SourceConfigMap, CRATES_IO_REGISTRY}; +use crate::sources::{PathSource, CRATES_IO_REGISTRY}; use crate::util::cache_lock::CacheLockMode; use crate::util::context::JobsConfig; use crate::util::errors::CargoResult; @@ -202,19 +202,28 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult = pkgs.iter().map(|p| p.0).collect(); - let publish_reg = get_registry(ws.gctx(), &just_pkgs, opts.reg_or_index.clone())?; - debug!("packaging for registry {publish_reg}"); + + // The publish registry doesn't matter unless there are local dependencies, + // so only try to get one if we need it. If they explicitly passed a + // registry on the CLI, we check it no matter what. + let sid = if deps.has_no_dependencies() && opts.reg_or_index.is_none() { + None + } else { + let sid = get_registry(ws.gctx(), &just_pkgs, opts.reg_or_index.clone())?; + debug!("packaging for registry {}", sid); + Some(sid) + }; let mut local_reg = if ws.gctx().cli_unstable().package_workspace { let reg_dir = ws.target_dir().join("package").join("tmp-registry"); - Some(TmpRegistry::new(ws.gctx(), reg_dir, publish_reg)?) + sid.map(|sid| TmpRegistry::new(ws.gctx(), reg_dir, sid)) + .transpose()? } else { None }; - let deps = local_deps(pkgs.iter().map(|(p, f)| ((*p).clone(), f.clone()))); - // Packages need to be created in dependency order, because dependencies must // be added to our local overlay before we can create lockfiles that depend on them. let sorted_pkgs = deps.sort(); @@ -258,52 +267,35 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult, ) -> CargoResult { - let reg_or_index = match reg_or_index { + let reg_or_index = match reg_or_index.clone() { Some(r) => Some(r), None => infer_registry(pkgs)?, }; + // Validate the registry against the packages' allow-lists. let reg = reg_or_index .clone() .unwrap_or_else(|| RegistryOrIndex::Registry(CRATES_IO_REGISTRY.to_owned())); - - // 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 { - if let RegistryOrIndex::Registry(reg_name) = ® { - for pkg in pkgs { - if let Some(allowed) = pkg.publish().as_ref() { - if !allowed.iter().any(|a| a == reg_name) { - bail!( + if let RegistryOrIndex::Registry(reg_name) = reg { + for pkg in pkgs { + if let Some(allowed) = pkg.publish().as_ref() { + if !allowed.iter().any(|a| a == ®_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 { - RegistryOrIndex::Index(url) => SourceId::for_registry(&url)?, - RegistryOrIndex::Registry(reg) if reg == CRATES_IO_REGISTRY => SourceId::crates_io(gctx)?, - RegistryOrIndex::Registry(reg) => SourceId::alt_registry(gctx, ®)?, - }; - - // Load source replacements that are built-in to Cargo. - let sid = SourceConfigMap::empty(gctx)? - .load(sid, &HashSet::new())? - .replaced_source_id(); - - Ok(sid) + Ok(ops::registry::get_source_id(gctx, reg_or_index.as_ref())?.replacement) } /// Just the part of the dependency graph that's between the packages we're packaging. @@ -322,6 +314,12 @@ impl LocalDependencies { .map(|name| self.packages[&name].clone()) .collect() } + + pub fn has_no_dependencies(&self) -> bool { + self.graph + .iter() + .all(|node| self.graph.edges(node).next().is_none()) + } } /// Build just the part of the dependency graph that's between the given packages, diff --git a/src/cargo/ops/registry/mod.rs b/src/cargo/ops/registry/mod.rs index c6ac91a26..96d80adca 100644 --- a/src/cargo/ops/registry/mod.rs +++ b/src/cargo/ops/registry/mod.rs @@ -191,7 +191,7 @@ fn registry( /// /// The return value is a pair of `SourceId`s: The first may be a built-in replacement of /// crates.io (such as index.crates.io), while the second is always the original source. -fn get_source_id( +pub(crate) fn get_source_id( gctx: &GlobalContext, reg_or_index: Option<&RegistryOrIndex>, ) -> CargoResult { diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 812d8ddd3..6e79ca250 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -5999,7 +5999,8 @@ fn registry_not_in_publish_list() { .masquerade_as_nightly_cargo(&["package-workspace"]) .with_status(101) .with_stderr_data(str![[r#" -[ERROR] registry index was not found in any configuration: `alternative` +[ERROR] `foo` cannot be packaged. +The registry `alternative` is not listed in the `package.publish` value in Cargo.toml. "#]]) .run(); From 96d4d6f6cbd3f62f2b3cef69214056cae9884846 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Mon, 19 Aug 2024 12:07:40 +0700 Subject: [PATCH 5/5] Allow unpublishable crates to be packaged --- src/cargo/ops/cargo_package.rs | 9 +++++-- src/cargo/ops/registry/mod.rs | 18 ++++++++++---- tests/testsuite/package.rs | 43 +++++++++++++++++++++++++++++----- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index de1f44f27..aa5aca3c9 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -243,7 +243,9 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult CargoResult> { - if pkgs[1..].iter().all(|p| p.publish() == pkgs[0].publish()) { - // If all packages have the same publish settings, we take that as the default. - match pkgs[0].publish().as_deref() { + // Ignore "publish = false" packages while inferring the registry. + let publishable_pkgs: Vec<_> = pkgs + .iter() + .filter(|p| p.publish() != &Some(Vec::new())) + .collect(); + + let Some((first, rest)) = publishable_pkgs.split_first() else { + return Ok(None); + }; + + // If all packages have the same publish settings, we take that as the default. + if rest.iter().all(|p| p.publish() == first.publish()) { + match publishable_pkgs[0].publish().as_deref() { Some([unique_pkg_reg]) => { Ok(Some(RegistryOrIndex::Registry(unique_pkg_reg.to_owned()))) } @@ -346,7 +356,7 @@ pub(crate) fn infer_registry(pkgs: &[&Package]) -> CargoResult