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