mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +00:00
Add more context to publish-failed error message (#15879)
## What does this PR try to resolve? This PR fixes issue #15754 where workspace publish failures provided non-actionable error messages. Previously, when publishing a workspace failed (due to rate limiting or other errors), users only saw generic errors like `[ERROR] failed to publish to registry` without knowing which package failed or what packages remained to be published. ## How to test and review this PR? **Testing**: Run `cargo test workspace_publish_rate_limit_error` to see the improved behavior, then `cargo test publish` to ensure all tests pass. **Review**: - **Commit 1**: Adds test demonstrating current problematic behavior - **Commit 2**: Implements fix and updates test to expect improved output The fix transforms error messages from generic `failed to publish to registry` to actionable `failed to publish 'package_a' v0.1.0; the following crates have not been published yet: package_b v0.1.0, package_c v0.1.0`. This improvement applies consistently across all publish error scenarios, giving users clear information about what went wrong and what remains to be done.
This commit is contained in:
commit
0bfd820f83
@ -210,7 +210,8 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
|
|||||||
// `b`, and we uploaded `a` and `b` but only confirmed `a`, then on
|
// `b`, and we uploaded `a` and `b` but only confirmed `a`, then on
|
||||||
// the following pass through the outer loop nothing will be ready for
|
// the following pass through the outer loop nothing will be ready for
|
||||||
// upload.
|
// upload.
|
||||||
for pkg_id in plan.take_ready() {
|
let mut ready = plan.take_ready();
|
||||||
|
while let Some(pkg_id) = ready.pop_first() {
|
||||||
let (pkg, (_features, tarball)) = &pkg_dep_graph.packages[&pkg_id];
|
let (pkg, (_features, tarball)) = &pkg_dep_graph.packages[&pkg_id];
|
||||||
opts.gctx.shell().status("Uploading", pkg.package_id())?;
|
opts.gctx.shell().status("Uploading", pkg.package_id())?;
|
||||||
|
|
||||||
@ -236,6 +237,19 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
|
|||||||
)?));
|
)?));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let workspace_context = || {
|
||||||
|
let mut remaining = ready.clone();
|
||||||
|
remaining.extend(plan.iter());
|
||||||
|
if !remaining.is_empty() {
|
||||||
|
format!(
|
||||||
|
"\n\nnote: the following crates have not been published yet:\n {}",
|
||||||
|
remaining.into_iter().join("\n ")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
transmit(
|
transmit(
|
||||||
opts.gctx,
|
opts.gctx,
|
||||||
ws,
|
ws,
|
||||||
@ -244,6 +258,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
|
|||||||
&mut registry,
|
&mut registry,
|
||||||
source_ids.original,
|
source_ids.original,
|
||||||
opts.dry_run,
|
opts.dry_run,
|
||||||
|
workspace_context,
|
||||||
)?;
|
)?;
|
||||||
to_confirm.insert(pkg_id);
|
to_confirm.insert(pkg_id);
|
||||||
|
|
||||||
@ -632,6 +647,7 @@ fn transmit(
|
|||||||
registry: &mut Registry,
|
registry: &mut Registry,
|
||||||
registry_id: SourceId,
|
registry_id: SourceId,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
|
workspace_context: impl Fn() -> String,
|
||||||
) -> CargoResult<()> {
|
) -> CargoResult<()> {
|
||||||
let new_crate = prepare_transmit(gctx, ws, pkg, registry_id)?;
|
let new_crate = prepare_transmit(gctx, ws, pkg, registry_id)?;
|
||||||
|
|
||||||
@ -641,9 +657,15 @@ fn transmit(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let warnings = registry
|
let warnings = registry.publish(&new_crate, tarball).with_context(|| {
|
||||||
.publish(&new_crate, tarball)
|
format!(
|
||||||
.with_context(|| format!("failed to publish to registry at {}", registry.host()))?;
|
"failed to publish {} v{} to registry at {}{}",
|
||||||
|
pkg.name(),
|
||||||
|
pkg.version(),
|
||||||
|
registry.host(),
|
||||||
|
workspace_context()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !warnings.invalid_categories.is_empty() {
|
if !warnings.invalid_categories.is_empty() {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
|
@ -2260,7 +2260,7 @@ fn api_error_json() {
|
|||||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||||
|
|
||||||
Caused by:
|
Caused by:
|
||||||
the remote server responded with an error (status 403 Forbidden): you must be logged in
|
the remote server responded with an error (status 403 Forbidden): you must be logged in
|
||||||
@ -2308,7 +2308,7 @@ fn api_error_200() {
|
|||||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||||
|
|
||||||
Caused by:
|
Caused by:
|
||||||
the remote server responded with an [ERROR] max upload size is 123
|
the remote server responded with an [ERROR] max upload size is 123
|
||||||
@ -2356,7 +2356,7 @@ fn api_error_code() {
|
|||||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||||
|
|
||||||
Caused by:
|
Caused by:
|
||||||
failed to get a 200 OK response, got 400
|
failed to get a 200 OK response, got 400
|
||||||
@ -2413,7 +2413,7 @@ fn api_curl_error() {
|
|||||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||||
|
|
||||||
Caused by:
|
Caused by:
|
||||||
[52] Server returned nothing (no headers, no data) (Empty reply from server)
|
[52] Server returned nothing (no headers, no data) (Empty reply from server)
|
||||||
@ -2461,7 +2461,7 @@ fn api_other_error() {
|
|||||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||||
|
|
||||||
Caused by:
|
Caused by:
|
||||||
invalid response body from server
|
invalid response body from server
|
||||||
@ -3608,7 +3608,7 @@ fn invalid_token() {
|
|||||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||||
|
|
||||||
Caused by:
|
Caused by:
|
||||||
token contains invalid characters.
|
token contains invalid characters.
|
||||||
@ -4402,3 +4402,110 @@ See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for
|
|||||||
"#]])
|
"#]])
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cargo_test]
|
||||||
|
fn workspace_publish_rate_limit_error() {
|
||||||
|
let registry = registry::RegistryBuilder::new()
|
||||||
|
.http_api()
|
||||||
|
.http_index()
|
||||||
|
.add_responder("/api/v1/crates/new", |_req, _| {
|
||||||
|
// For simplicity, let's just return rate limit error for all requests
|
||||||
|
// This simulates hitting rate limit during workspace publish
|
||||||
|
Response {
|
||||||
|
code: 429,
|
||||||
|
headers: vec!["Retry-After: 3600".to_string()],
|
||||||
|
body: format!(
|
||||||
|
"You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email help@crates.io to have your limit increased."
|
||||||
|
).into_bytes(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let p = project()
|
||||||
|
.file(
|
||||||
|
"Cargo.toml",
|
||||||
|
r#"
|
||||||
|
[workspace]
|
||||||
|
members = ["package_a", "package_b", "package_c"]
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.file("src/lib.rs", "")
|
||||||
|
.file(
|
||||||
|
"package_a/Cargo.toml",
|
||||||
|
r#"
|
||||||
|
[package]
|
||||||
|
name = "package_a"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2015"
|
||||||
|
license = "MIT"
|
||||||
|
description = "package a"
|
||||||
|
repository = "https://github.com/test/package_a"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.file("package_a/src/lib.rs", "")
|
||||||
|
.file(
|
||||||
|
"package_b/Cargo.toml",
|
||||||
|
r#"
|
||||||
|
[package]
|
||||||
|
name = "package_b"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2015"
|
||||||
|
license = "MIT"
|
||||||
|
description = "package b"
|
||||||
|
repository = "https://github.com/test/package_b"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.file("package_b/src/lib.rs", "")
|
||||||
|
.file(
|
||||||
|
"package_c/Cargo.toml",
|
||||||
|
r#"
|
||||||
|
[package]
|
||||||
|
name = "package_c"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2015"
|
||||||
|
license = "MIT"
|
||||||
|
description = "package c"
|
||||||
|
repository = "https://github.com/test/package_c"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
package_a = { version = "0.1.0", path = "../package_a" }
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.file("package_c/src/lib.rs", "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// This demonstrates the improved error message after the fix
|
||||||
|
// The user now knows which package failed and what packages remain to be published
|
||||||
|
p.cargo("publish --workspace --no-verify")
|
||||||
|
.replace_crates_io(registry.index_url())
|
||||||
|
.with_status(101)
|
||||||
|
.with_stderr_data(str![[r#"
|
||||||
|
[UPDATING] crates.io index
|
||||||
|
[PACKAGING] package_a v0.1.0 ([ROOT]/foo/package_a)
|
||||||
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
|
[PACKAGING] package_b v0.1.0 ([ROOT]/foo/package_b)
|
||||||
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
|
[PACKAGING] package_c v0.1.0 ([ROOT]/foo/package_c)
|
||||||
|
[UPDATING] crates.io index
|
||||||
|
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
|
[UPLOADING] package_a v0.1.0 ([ROOT]/foo/package_a)
|
||||||
|
[ERROR] failed to publish package_a v0.1.0 to registry at http://127.0.0.1:[..]/
|
||||||
|
|
||||||
|
[NOTE] the following crates have not been published yet:
|
||||||
|
package_b v0.1.0 ([ROOT]/foo/package_b)
|
||||||
|
package_c v0.1.0 ([ROOT]/foo/package_c)
|
||||||
|
|
||||||
|
Caused by:
|
||||||
|
failed to get a 200 OK response, got 429
|
||||||
|
headers:
|
||||||
|
HTTP/1.1 429
|
||||||
|
Content-Length: 172
|
||||||
|
Connection: close
|
||||||
|
Retry-After: 3600
|
||||||
|
|
||||||
|
body:
|
||||||
|
You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email help@crates.io to have your limit increased.
|
||||||
|
|
||||||
|
"#]])
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user