Skip to content

Commit b2430df

Browse files
committed
Auto merge of #14408 - tweag:infer-registry-ignore-unpublishable, r=epage
Be more permissive while packaging unpublishable crates. This PR allows for packaging workspaces that include `publish = false` crates, in some circumstances: - unpublishable crates are ignored when inferring the publish registry - when checking whether the publish registry is valid, we ignore unpublishable crates - we don't put unpublishable crates in the local registry overlay, so if any workspace crates depend on an unpublishable crate then they will fail to verify. This PR also contains a refactor, moving the registry inference logic to `registry/mod.rs`, where it will be reused by the upcoming publish-workspace feature. I put the refactor and the logic changes in different commits. Fixes #14356
2 parents 1f4ef10 + 96d4d6f commit b2430df

File tree

3 files changed

+281
-103
lines changed

3 files changed

+281
-103
lines changed

src/cargo/ops/cargo_package.rs

Lines changed: 66 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use std::collections::{BTreeSet, HashMap, HashSet};
1+
use std::collections::{BTreeSet, HashMap};
22
use std::fs::{self, File};
33
use std::io::prelude::*;
44
use std::io::SeekFrom;
55
use std::path::{Path, PathBuf};
66
use std::sync::Arc;
77
use std::task::Poll;
88

9-
use super::RegistryOrIndex;
109
use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor};
1110
use crate::core::dependency::DepKind;
1211
use crate::core::manifest::Target;
@@ -15,8 +14,9 @@ use crate::core::resolver::HasDevUnits;
1514
use crate::core::{Feature, PackageIdSpecQuery, Shell, Verbosity, Workspace};
1615
use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId};
1716
use crate::ops::lockfile::LOCKFILE_NAME;
17+
use crate::ops::registry::{infer_registry, RegistryOrIndex};
1818
use crate::sources::registry::index::{IndexPackage, RegistryDependency};
19-
use crate::sources::{PathSource, SourceConfigMap, CRATES_IO_REGISTRY};
19+
use crate::sources::{PathSource, CRATES_IO_REGISTRY};
2020
use crate::util::cache_lock::CacheLockMode;
2121
use crate::util::context::JobsConfig;
2222
use crate::util::errors::CargoResult;
@@ -174,97 +174,6 @@ fn create_package(
174174
return Ok(dst);
175175
}
176176

177-
/// Determine which registry the packages are for.
178-
///
179-
/// The registry only affects the built packages if there are dependencies within the
180-
/// packages that we're packaging: if we're packaging foo-bin and foo-lib, and foo-bin
181-
/// depends on foo-lib, then the foo-lib entry in foo-bin's lockfile will depend on the
182-
/// registry that we're building packages for.
183-
fn infer_registry(
184-
gctx: &GlobalContext,
185-
pkgs: &[&Package],
186-
reg_or_index: Option<RegistryOrIndex>,
187-
) -> CargoResult<SourceId> {
188-
let reg_or_index = match reg_or_index {
189-
Some(r) => r,
190-
None => {
191-
if pkgs[1..].iter().all(|p| p.publish() == pkgs[0].publish()) {
192-
// If all packages have the same publish settings, we take that as the default.
193-
match pkgs[0].publish().as_deref() {
194-
Some([unique_pkg_reg]) => RegistryOrIndex::Registry(unique_pkg_reg.to_owned()),
195-
None | Some([]) => RegistryOrIndex::Registry(CRATES_IO_REGISTRY.to_owned()),
196-
Some([reg, ..]) if pkgs.len() == 1 => {
197-
// For backwards compatibility, avoid erroring if there's only one package.
198-
// The registry doesn't affect packaging in this case.
199-
RegistryOrIndex::Registry(reg.to_owned())
200-
}
201-
Some(regs) => {
202-
let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect();
203-
regs.sort();
204-
regs.dedup();
205-
// unwrap: the match block ensures that there's more than one reg.
206-
let (last_reg, regs) = regs.split_last().unwrap();
207-
bail!(
208-
"--registry is required to disambiguate between {} or {} registries",
209-
regs.join(", "),
210-
last_reg
211-
)
212-
}
213-
}
214-
} else {
215-
let common_regs = pkgs
216-
.iter()
217-
// `None` means "all registries", so drop them instead of including them
218-
// in the intersection.
219-
.filter_map(|p| p.publish().as_deref())
220-
.map(|p| p.iter().collect::<HashSet<_>>())
221-
.reduce(|xs, ys| xs.intersection(&ys).cloned().collect())
222-
.unwrap_or_default();
223-
if common_regs.is_empty() {
224-
bail!("conflicts between `package.publish` fields in the selected packages");
225-
} else {
226-
bail!(
227-
"--registry is required because not all `package.publish` settings agree",
228-
);
229-
}
230-
}
231-
}
232-
};
233-
234-
// Validate the registry against the packages' allow-lists. For backwards compatibility, we
235-
// skip this if only a single package is being published (because in that case the registry
236-
// doesn't affect the packaging step).
237-
if pkgs.len() > 1 {
238-
if let RegistryOrIndex::Registry(reg_name) = &reg_or_index {
239-
for pkg in pkgs {
240-
if let Some(allowed) = pkg.publish().as_ref() {
241-
if !allowed.iter().any(|a| a == reg_name) {
242-
bail!(
243-
"`{}` cannot be packaged.\n\
244-
The registry `{}` is not listed in the `package.publish` value in Cargo.toml.",
245-
pkg.name(),
246-
reg_name
247-
);
248-
}
249-
}
250-
}
251-
}
252-
}
253-
254-
let sid = match reg_or_index {
255-
RegistryOrIndex::Index(url) => SourceId::for_registry(&url)?,
256-
RegistryOrIndex::Registry(reg) if reg == CRATES_IO_REGISTRY => SourceId::crates_io(gctx)?,
257-
RegistryOrIndex::Registry(reg) => SourceId::alt_registry(gctx, &reg)?,
258-
};
259-
260-
// Load source replacements that are built-in to Cargo.
261-
let sid = SourceConfigMap::empty(gctx)?
262-
.load(sid, &HashSet::new())?
263-
.replaced_source_id();
264-
265-
Ok(sid)
266-
}
267-
268177
/// Packages an entire workspace.
269178
///
270179
/// Returns the generated package files. If `opts.list` is true, skips
@@ -293,19 +202,28 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult<Vec<Fi
293202
// below, and will be validated during the verification step.
294203
}
295204

205+
let deps = local_deps(pkgs.iter().map(|(p, f)| ((*p).clone(), f.clone())));
296206
let just_pkgs: Vec<_> = pkgs.iter().map(|p| p.0).collect();
297-
let publish_reg = infer_registry(ws.gctx(), &just_pkgs, opts.reg_or_index.clone())?;
298-
debug!("packaging for registry {publish_reg}");
207+
208+
// The publish registry doesn't matter unless there are local dependencies,
209+
// so only try to get one if we need it. If they explicitly passed a
210+
// registry on the CLI, we check it no matter what.
211+
let sid = if deps.has_no_dependencies() && opts.reg_or_index.is_none() {
212+
None
213+
} else {
214+
let sid = get_registry(ws.gctx(), &just_pkgs, opts.reg_or_index.clone())?;
215+
debug!("packaging for registry {}", sid);
216+
Some(sid)
217+
};
299218

300219
let mut local_reg = if ws.gctx().cli_unstable().package_workspace {
301220
let reg_dir = ws.target_dir().join("package").join("tmp-registry");
302-
Some(TmpRegistry::new(ws.gctx(), reg_dir, publish_reg)?)
221+
sid.map(|sid| TmpRegistry::new(ws.gctx(), reg_dir, sid))
222+
.transpose()?
303223
} else {
304224
None
305225
};
306226

307-
let deps = local_deps(pkgs.iter().map(|(p, f)| ((*p).clone(), f.clone())));
308-
309227
// Packages need to be created in dependency order, because dependencies must
310228
// be added to our local overlay before we can create lockfiles that depend on them.
311229
let sorted_pkgs = deps.sort();
@@ -325,7 +243,9 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult<Vec<Fi
325243
} else {
326244
let tarball = create_package(ws, &pkg, ar_files, local_reg.as_ref())?;
327245
if let Some(local_reg) = local_reg.as_mut() {
328-
local_reg.add_package(ws, &pkg, &tarball)?;
246+
if pkg.publish() != &Some(Vec::new()) {
247+
local_reg.add_package(ws, &pkg, &tarball)?;
248+
}
329249
}
330250
outputs.push((pkg, opts, tarball));
331251
}
@@ -343,6 +263,46 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult<Vec<Fi
343263
Ok(outputs.into_iter().map(|x| x.2).collect())
344264
}
345265

266+
/// Determine which registry the packages are for.
267+
///
268+
/// The registry only affects the built packages if there are dependencies within the
269+
/// packages that we're packaging: if we're packaging foo-bin and foo-lib, and foo-bin
270+
/// depends on foo-lib, then the foo-lib entry in foo-bin's lockfile will depend on the
271+
/// registry that we're building packages for.
272+
fn get_registry(
273+
gctx: &GlobalContext,
274+
pkgs: &[&Package],
275+
reg_or_index: Option<RegistryOrIndex>,
276+
) -> CargoResult<SourceId> {
277+
let reg_or_index = match reg_or_index.clone() {
278+
Some(r) => Some(r),
279+
None => infer_registry(pkgs)?,
280+
};
281+
282+
// Validate the registry against the packages' allow-lists.
283+
let reg = reg_or_index
284+
.clone()
285+
.unwrap_or_else(|| RegistryOrIndex::Registry(CRATES_IO_REGISTRY.to_owned()));
286+
if let RegistryOrIndex::Registry(reg_name) = reg {
287+
for pkg in pkgs {
288+
if let Some(allowed) = pkg.publish().as_ref() {
289+
// If allowed is empty (i.e. package.publish is false), we let it slide.
290+
// This allows packaging unpublishable packages (although packaging might
291+
// fail later if the unpublishable package is a dependency of something else).
292+
if !allowed.is_empty() && !allowed.iter().any(|a| a == &reg_name) {
293+
bail!(
294+
"`{}` cannot be packaged.\n\
295+
The registry `{}` is not listed in the `package.publish` value in Cargo.toml.",
296+
pkg.name(),
297+
reg_name
298+
);
299+
}
300+
}
301+
}
302+
}
303+
Ok(ops::registry::get_source_id(gctx, reg_or_index.as_ref())?.replacement)
304+
}
305+
346306
/// Just the part of the dependency graph that's between the packages we're packaging.
347307
/// (Is the package name a good key? Does it uniquely identify packages?)
348308
#[derive(Clone, Debug, Default)]
@@ -359,6 +319,12 @@ impl LocalDependencies {
359319
.map(|name| self.packages[&name].clone())
360320
.collect()
361321
}
322+
323+
pub fn has_no_dependencies(&self) -> bool {
324+
self.graph
325+
.iter()
326+
.all(|node| self.graph.edges(node).next().is_none())
327+
}
362328
}
363329

364330
/// Build just the part of the dependency graph that's between the given packages,

src/cargo/ops/registry/mod.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use cargo_credential::{Operation, Secret};
1919
use crates_io::Registry;
2020
use url::Url;
2121

22-
use crate::core::{PackageId, SourceId};
22+
use crate::core::{Package, PackageId, SourceId};
2323
use crate::sources::source::Source;
2424
use crate::sources::{RegistrySource, SourceConfigMap};
2525
use crate::util::auth;
@@ -191,7 +191,7 @@ fn registry(
191191
///
192192
/// The return value is a pair of `SourceId`s: The first may be a built-in replacement of
193193
/// crates.io (such as index.crates.io), while the second is always the original source.
194-
fn get_source_id(
194+
pub(crate) fn get_source_id(
195195
gctx: &GlobalContext,
196196
reg_or_index: Option<&RegistryOrIndex>,
197197
) -> CargoResult<RegistrySourceIds> {
@@ -322,3 +322,52 @@ pub(crate) struct RegistrySourceIds {
322322
/// User-defined source replacement is not applied.
323323
pub(crate) replacement: SourceId,
324324
}
325+
326+
/// If this set of packages has an unambiguous publish registry, find it.
327+
pub(crate) fn infer_registry(pkgs: &[&Package]) -> CargoResult<Option<RegistryOrIndex>> {
328+
// Ignore "publish = false" packages while inferring the registry.
329+
let publishable_pkgs: Vec<_> = pkgs
330+
.iter()
331+
.filter(|p| p.publish() != &Some(Vec::new()))
332+
.collect();
333+
334+
let Some((first, rest)) = publishable_pkgs.split_first() else {
335+
return Ok(None);
336+
};
337+
338+
// If all packages have the same publish settings, we take that as the default.
339+
if rest.iter().all(|p| p.publish() == first.publish()) {
340+
match publishable_pkgs[0].publish().as_deref() {
341+
Some([unique_pkg_reg]) => {
342+
Ok(Some(RegistryOrIndex::Registry(unique_pkg_reg.to_owned())))
343+
}
344+
None | Some([]) => Ok(None),
345+
Some(regs) => {
346+
let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect();
347+
regs.sort();
348+
regs.dedup();
349+
// unwrap: the match block ensures that there's more than one reg.
350+
let (last_reg, regs) = regs.split_last().unwrap();
351+
bail!(
352+
"--registry is required to disambiguate between {} or {} registries",
353+
regs.join(", "),
354+
last_reg
355+
)
356+
}
357+
}
358+
} else {
359+
let common_regs = publishable_pkgs
360+
.iter()
361+
// `None` means "all registries", so drop them instead of including them
362+
// in the intersection.
363+
.filter_map(|p| p.publish().as_deref())
364+
.map(|p| p.iter().collect::<HashSet<_>>())
365+
.reduce(|xs, ys| xs.intersection(&ys).cloned().collect())
366+
.unwrap_or_default();
367+
if common_regs.is_empty() {
368+
bail!("conflicts between `package.publish` fields in the selected packages");
369+
} else {
370+
bail!("--registry is required because not all `package.publish` settings agree",);
371+
}
372+
}
373+
}

0 commit comments

Comments
 (0)