From c8bf4096de6b5fdeca04c7c2002330c33a4c95a6 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 3 Jun 2025 16:01:51 -0500 Subject: [PATCH] feat(publish): Stabilize multi-package publishing A user will now be able to use flags like `--workspace` with `cargo publish`. `cargo package` will now also work with those flags without having to pass `--no-verify --exclude-lockfile`. Many release tools have come out that solve this problem. They will still need a lot of the logic that went into that for other parts of the release process. However, a cargo-native solution allows for: - Verification during dry-run - Better strategies for waiting for the publish timeout `cargo publish` is non-atomic at this time. If there is a server side error, network error, or rate limit during the publish, the workspace will be left in a partially published state. Verification is done before any publishing so that won't affect things. There are multiple strategies we can employ for improving this over time, including - atomic publish - `--idempotent` (#13397) - leave this to release tools to manage This includes support for `--dry-run` verification. As release tools didn't have a way to do this before, users may be surprised at how slow this is because a `cargo build` is done instead of a `cargo check`. This is being tracked in #14941. This adds to `cargo package` the `--registry` and `--index` flags to help with resolving dependencies when depending on a package being packaged at that moment. These flags are only needed when a `cargo package --workspace` operation would have failed before due to inability to find a locally created dependency. Regarding the publish timeout, `cargo publish --workspace` publishes packages in batches and we only timeout if nothing in the batch has finished being published within the timeout, deferring the rest to the next wait-for-publish. So for example, if you have packages `a`, `b`, `c` then we'll wait up to 60 seconds and if only `a` and `b` were ready in that time, we'll then wait another 60 seconds for `c`. During testing, users ran into issues with `.crate` checksums that we've not been able to reproduce since: - https://github.com/rust-lang/cargo/issues/1169#issuecomment-2567995987 - #14396 By stabilizing this, Cargo's behavior becomes dependent on an overlay registry. When generating a lockfile or verifying a package, we overlay the locally generated `.crate` files on top of the registry so the registry appears as it would and everything works. If there is a conflict with a version, the local version wins which is important for the dry-run mode of release tools as they won't have bumped the version yet. Our concern for the overlay registry is dependency confusion attacks. Considering this is not accessible for general user operations, this should be fine. Fixes #1169 Fixes #10948 --- src/bin/cargo/commands/package.rs | 20 +- src/bin/cargo/commands/publish.rs | 21 +- src/cargo/core/features.rs | 6 +- src/cargo/ops/cargo_package/mod.rs | 4 +- src/cargo/ops/registry/publish.rs | 23 +- src/doc/man/cargo-publish.md | 59 +- src/doc/man/generated_txt/cargo-publish.txt | 26 +- src/doc/src/commands/cargo-publish.md | 29 +- src/etc/man/cargo-publish.1 | 24 +- tests/testsuite/cargo/z_help/stdout.term.svg | 44 +- .../cargo_package/help/stdout.term.svg | 4 +- .../cargo_publish/help/stdout.term.svg | 4 +- tests/testsuite/package.rs | 586 ++---------------- tests/testsuite/publish.rs | 232 +++---- tests/testsuite/workspaces.rs | 6 +- 15 files changed, 192 insertions(+), 896 deletions(-) diff --git a/src/bin/cargo/commands/package.rs b/src/bin/cargo/commands/package.rs index d48f32ca8..ae556eda2 100644 --- a/src/bin/cargo/commands/package.rs +++ b/src/bin/cargo/commands/package.rs @@ -7,8 +7,8 @@ use cargo::ops::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_index("Registry index URL to prepare the package for") + .arg_registry("Registry to prepare the package for") .arg( flag( "list", @@ -57,22 +57,6 @@ 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() { diff --git a/src/bin/cargo/commands/publish.rs b/src/bin/cargo/commands/publish.rs index 42b6377c7..57eff8d03 100644 --- a/src/bin/cargo/commands/publish.rs +++ b/src/bin/cargo/commands/publish.rs @@ -20,8 +20,8 @@ pub fn cli() -> Command { .arg_silent_suggestion() .arg_package_spec_no_all( "Package(s) to publish", - "Publish all packages in the workspace (unstable)", - "Don't publish specified packages (unstable)", + "Publish all packages in the workspace", + "Don't publish specified packages", ) .arg_features() .arg_parallel() @@ -45,23 +45,6 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { .into()); } - let unstable = gctx.cli_unstable(); - let enabled = unstable.package_workspace; - if args.get_flag("workspace") { - unstable.fail_if_stable_opt_custom_z("--workspace", 10948, "package-workspace", enabled)?; - } - if args._value_of("exclude").is_some() { - unstable.fail_if_stable_opt_custom_z("--exclude", 10948, "package-workspace", enabled)?; - } - if args._values_of("package").len() > 1 { - unstable.fail_if_stable_opt_custom_z( - "--package (multiple occurrences)", - 10948, - "package-workspace", - enabled, - )?; - } - ops::publish( &ws, &PublishOpts { diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 1e9dd73ba..5c13b1a6e 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -845,7 +845,6 @@ unstable_cli_options!( next_lockfile_bump: bool, no_embed_metadata: bool = ("Avoid embedding metadata in library artifacts"), 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_hint_mostly_unused: bool = ("Enable the `hint-mostly-unused` setting in profiles to mark a crate as mostly unused."), profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"), @@ -942,6 +941,9 @@ const STABILIZED_CHECK_CFG: &str = const STABILIZED_DOCTEST_XCOMPILE: &str = "Doctest cross-compiling is now always enabled."; +const STABILIZED_PACKAGE_WORKSPACE: &str = + "Workspace packaging and publishing (a.k.a. `-Zpackage-workspace`) is now always enabled."; + fn deserialize_comma_separated_list<'de, D>( deserializer: D, ) -> Result>, D::Error> @@ -1323,6 +1325,7 @@ impl CliUnstable { "lints" => stabilized_warn(k, "1.74", STABILIZED_LINTS), "registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH), "check-cfg" => stabilized_warn(k, "1.80", STABILIZED_CHECK_CFG), + "package-workspace" => stabilized_warn(k, "1.89", STABILIZED_PACKAGE_WORKSPACE), // Unstable features // Sorted alphabetically: @@ -1366,7 +1369,6 @@ impl CliUnstable { "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?, "no-embed-metadata" => self.no_embed_metadata = 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-hint-mostly-unused" => self.profile_hint_mostly_unused = parse_empty(k, v)?, diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs index 692b96244..469233939 100644 --- a/src/cargo/ops/cargo_package/mod.rs +++ b/src/cargo/ops/cargo_package/mod.rs @@ -262,7 +262,7 @@ fn do_package<'a>( let deps = local_deps(pkgs.iter().map(|(p, f)| ((*p).clone(), f.clone()))); let just_pkgs: Vec<_> = pkgs.iter().map(|p| p.0).collect(); - let mut local_reg = if ws.gctx().cli_unstable().package_workspace { + let mut local_reg = { // The publish registry doesn't matter unless there are local dependencies that will be // resolved, // so only try to get one if we need it. If they explicitly passed a @@ -279,8 +279,6 @@ fn do_package<'a>( let reg_dir = ws.build_dir().join("package").join("tmp-registry"); sid.map(|sid| TmpRegistry::new(ws.gctx(), reg_dir, sid)) .transpose()? - } else { - None }; // Packages need to be created in dependency order, because dependencies must diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs index f6707ea91..f9839bb35 100644 --- a/src/cargo/ops/registry/publish.rs +++ b/src/cargo/ops/registry/publish.rs @@ -69,18 +69,8 @@ pub struct PublishOpts<'gctx> { } pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { - let multi_package_mode = ws.gctx().cli_unstable().package_workspace; let specs = opts.to_publish.to_package_id_specs(ws)?; - if !multi_package_mode { - if specs.len() > 1 { - bail!("the `-p` argument must be specified to select a single package to publish") - } - if Packages::Default == opts.to_publish && ws.is_virtual() { - bail!("the `-p` argument must be specified in the root of a virtual workspace") - } - } - let member_ids: Vec<_> = ws.members().map(|p| p.package_id()).collect(); // Check that the specs match members. for spec in &specs { @@ -97,13 +87,12 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { // If `--workspace` is passed, // the intent is more like "publish all publisable packages in this workspace", // so skip `publish=false` packages. - let allow_unpublishable = multi_package_mode - && match &opts.to_publish { - Packages::Default => ws.is_virtual(), - Packages::All(_) => true, - Packages::OptOut(_) => true, - Packages::Packages(_) => false, - }; + let allow_unpublishable = match &opts.to_publish { + Packages::Default => ws.is_virtual(), + Packages::All(_) => true, + Packages::OptOut(_) => true, + Packages::Packages(_) => false, + }; if !unpublishable.is_empty() && !allow_unpublishable { bail!( "{} cannot be published.\n\ diff --git a/src/doc/man/cargo-publish.md b/src/doc/man/cargo-publish.md index d2caeffb9..88ff22f38 100644 --- a/src/doc/man/cargo-publish.md +++ b/src/doc/man/cargo-publish.md @@ -68,64 +68,7 @@ which defaults to `crates-io`. {{/options}} -### Package Selection - -By default, when no package selection options are given, the packages selected -depend on the selected manifest file (based on the current working directory if -`--manifest-path` is not given). If the manifest is the root of a workspace then -the workspaces default members are selected, otherwise only the package defined -by the manifest will be selected. - -The default members of a workspace can be set explicitly with the -`workspace.default-members` key in the root manifest. If this is not set, a -virtual workspace will include all workspace members (equivalent to passing -`--workspace`), and a non-virtual workspace will include only the root crate itself. - -Selecting more than one package is unstable and available only on the -[nightly channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) -and requires the `-Z package-workspace` flag to enable. -See for more information. - - -{{#options}} - -{{#option "`-p` _spec_..." "`--package` _spec_..."}} -{{actionverb}} only the specified packages. See {{man "cargo-pkgid" 1}} for the -SPEC format. This flag may be specified multiple times and supports common Unix -glob patterns like `*`, `?` and `[]`. However, to avoid your shell accidentally -expanding glob patterns before Cargo handles them, you must use single quotes or -double quotes around each pattern. - -Selecting more than one package with this option is unstable and available only -on the -[nightly channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) -and requires the `-Z package-workspace` flag to enable. -See for more information. -{{/option}} - -{{#option "`--workspace`" }} -{{actionverb}} all members in the workspace. - -This option is unstable and available only on the -[nightly channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) -and requires the `-Z package-workspace` flag to enable. -See for more information. -{{/option}} - -{{#option "`--exclude` _SPEC_..." }} -Exclude the specified packages. Must be used in conjunction with the -`--workspace` flag. This flag may be specified multiple times and supports -common Unix glob patterns like `*`, `?` and `[]`. However, to avoid your shell -accidentally expanding glob patterns before Cargo handles them, you must use -single quotes or double quotes around each pattern. - -This option is unstable and available only on the -[nightly channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) -and requires the `-Z package-workspace` flag to enable. -See for more information. -{{/option}} - -{{/options}} +{{> section-package-selection }} ### Compilation Options diff --git a/src/doc/man/generated_txt/cargo-publish.txt b/src/doc/man/generated_txt/cargo-publish.txt index 61271435e..770d91375 100644 --- a/src/doc/man/generated_txt/cargo-publish.txt +++ b/src/doc/man/generated_txt/cargo-publish.txt @@ -85,12 +85,6 @@ OPTIONS passing --workspace), and a non-virtual workspace will include only the root crate itself. - Selecting more than one package is unstable and available only on the - nightly channel - and - requires the -Z package-workspace flag to enable. See - for more information. - -p spec…, --package spec… Publish only the specified packages. See cargo-pkgid(1) for the SPEC format. This flag may be specified multiple times and supports @@ -99,21 +93,11 @@ OPTIONS them, you must use single quotes or double quotes around each pattern. - Selecting more than one package with this option is unstable and - available only on the nightly channel - and - requires the -Z package-workspace flag to enable. See - for more - information. - --workspace Publish all members in the workspace. - This option is unstable and available only on the nightly channel - and - requires the -Z package-workspace flag to enable. See - for more - information. + --all + Deprecated alias for --workspace. --exclude SPEC… Exclude the specified packages. Must be used in conjunction with the @@ -123,12 +107,6 @@ OPTIONS handles them, you must use single quotes or double quotes around each pattern. - This option is unstable and available only on the nightly channel - and - requires the -Z package-workspace flag to enable. See - for more - information. - Compilation Options --target triple Publish for the given architecture. The default is the host diff --git a/src/doc/src/commands/cargo-publish.md b/src/doc/src/commands/cargo-publish.md index 58778a0ce..4e2484afa 100644 --- a/src/doc/src/commands/cargo-publish.md +++ b/src/doc/src/commands/cargo-publish.md @@ -88,12 +88,6 @@ The default members of a workspace can be set explicitly with the virtual workspace will include all workspace members (equivalent to passing `--workspace`), and a non-virtual workspace will include only the root crate itself. -Selecting more than one package is unstable and available only on the -[nightly channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) -and requires the `-Z package-workspace` flag to enable. -See for more information. - -
-p spec
@@ -102,20 +96,15 @@ See for more information. SPEC format. This flag may be specified multiple times and supports common Unix glob patterns like *, ? and []. However, to avoid your shell accidentally expanding glob patterns before Cargo handles them, you must use single quotes or -double quotes around each pattern.

-

Selecting more than one package with this option is unstable and available only -on the -nightly channel -and requires the -Z package-workspace flag to enable. -See https://github.com/rust-lang/cargo/issues/10948 for more information. +double quotes around each pattern.

--workspace
-
Publish all members in the workspace.

-

This option is unstable and available only on the -nightly channel -and requires the -Z package-workspace flag to enable. -See https://github.com/rust-lang/cargo/issues/10948 for more information.

+
Publish all members in the workspace.
+ + +
--all
+
Deprecated alias for --workspace.
--exclude SPEC
@@ -123,11 +112,7 @@ See https://github.com --workspace flag. This flag may be specified multiple times and supports common Unix glob patterns like *, ? and []. However, to avoid your shell accidentally expanding glob patterns before Cargo handles them, you must use -single quotes or double quotes around each pattern.

-

This option is unstable and available only on the -nightly channel -and requires the -Z package-workspace flag to enable. -See https://github.com/rust-lang/cargo/issues/10948 for more information. +single quotes or double quotes around each pattern.

diff --git a/src/etc/man/cargo-publish.1 b/src/etc/man/cargo-publish.1 index 6f9fd22d4..462c94bb6 100644 --- a/src/etc/man/cargo-publish.1 +++ b/src/etc/man/cargo-publish.1 @@ -100,11 +100,6 @@ The default members of a workspace can be set explicitly with the virtual workspace will include all workspace members (equivalent to passing \fB\-\-workspace\fR), and a non\-virtual workspace will include only the root crate itself. .sp -Selecting more than one package is unstable and available only on the -\fInightly channel\fR -and requires the \fB\-Z package\-workspace\fR flag to enable. -See for more information. -.sp \fB\-p\fR \fIspec\fR\[u2026], \fB\-\-package\fR \fIspec\fR\[u2026] .RS 4 @@ -113,22 +108,16 @@ SPEC format. This flag may be specified multiple times and supports common Unix glob patterns like \fB*\fR, \fB?\fR and \fB[]\fR\&. However, to avoid your shell accidentally expanding glob patterns before Cargo handles them, you must use single quotes or double quotes around each pattern. -.sp -Selecting more than one package with this option is unstable and available only -on the -\fInightly channel\fR -and requires the \fB\-Z package\-workspace\fR flag to enable. -See for more information. .RE .sp \fB\-\-workspace\fR .RS 4 Publish all members in the workspace. +.RE .sp -This option is unstable and available only on the -\fInightly channel\fR -and requires the \fB\-Z package\-workspace\fR flag to enable. -See for more information. +\fB\-\-all\fR +.RS 4 +Deprecated alias for \fB\-\-workspace\fR\&. .RE .sp \fB\-\-exclude\fR \fISPEC\fR\[u2026] @@ -138,11 +127,6 @@ Exclude the specified packages. Must be used in conjunction with the common Unix glob patterns like \fB*\fR, \fB?\fR and \fB[]\fR\&. However, to avoid your shell accidentally expanding glob patterns before Cargo handles them, you must use single quotes or double quotes around each pattern. -.sp -This option is unstable and available only on the -\fInightly channel\fR -and requires the \fB\-Z package\-workspace\fR flag to enable. -See for more information. .RE .SS "Compilation Options" .sp diff --git a/tests/testsuite/cargo/z_help/stdout.term.svg b/tests/testsuite/cargo/z_help/stdout.term.svg index b0dc5c541..8f424558d 100644 --- a/tests/testsuite/cargo/z_help/stdout.term.svg +++ b/tests/testsuite/cargo/z_help/stdout.term.svg @@ -1,4 +1,4 @@ - +