diff --git a/src/bin/cargo/commands/report.rs b/src/bin/cargo/commands/report.rs index e31edb19133..c2d03e86058 100644 --- a/src/bin/cargo/commands/report.rs +++ b/src/bin/cargo/commands/report.rs @@ -31,7 +31,8 @@ pub fn cli() -> Command { subcommand("timings") .about("Reports the build timings of previous sessions (unstable)") .arg_manifest_path() - .arg(flag("open", "Opens the timing report in a browser")), + .arg(flag("open", "Opens the timing report in a browser")) + .arg(opt("id", "Session ID to report on").value_name("ID")), ) .subcommand( subcommand("sessions") @@ -47,7 +48,8 @@ pub fn cli() -> Command { .subcommand( subcommand("rebuilds") .about("Reports rebuild reasons from previous sessions (unstable)") - .arg_manifest_path(), + .arg_manifest_path() + .arg(opt("id", "Session ID to report on").value_name("ID")), ) } @@ -120,8 +122,16 @@ fn timings_opts<'a>( args: &ArgMatches, ) -> CargoResult> { let open_result = args.get_flag("open"); + let id = args + .get_one::("id") + .map(|s| s.parse()) + .transpose()?; - Ok(ops::ReportTimingsOptions { open_result, gctx }) + Ok(ops::ReportTimingsOptions { + open_result, + gctx, + id, + }) } fn sessions_opts(args: &ArgMatches) -> CargoResult { @@ -131,6 +141,11 @@ fn sessions_opts(args: &ArgMatches) -> CargoResult { Ok(ops::ReportSessionsOptions { limit }) } -fn rebuilds_opts(_args: &ArgMatches) -> CargoResult { - Ok(ops::ReportRebuildsOptions {}) +fn rebuilds_opts(args: &ArgMatches) -> CargoResult { + let id = args + .get_one::("id") + .map(|s| s.parse()) + .transpose()?; + + Ok(ops::ReportRebuildsOptions { id }) } diff --git a/src/cargo/ops/cargo_report/rebuilds.rs b/src/cargo/ops/cargo_report/rebuilds.rs index cb95a1c714d..8d0495d1d2c 100644 --- a/src/cargo/ops/cargo_report/rebuilds.rs +++ b/src/cargo/ops/cargo_report/rebuilds.rs @@ -20,7 +20,7 @@ use crate::core::compiler::UnitIndex; use crate::core::compiler::fingerprint::DirtyReason; use crate::core::compiler::fingerprint::FsStatus; use crate::core::compiler::fingerprint::StaleItem; -use crate::ops::cargo_report::util::list_log_files; +use crate::ops::cargo_report::util::find_log_file; use crate::ops::cargo_report::util::unit_target_description; use crate::util::log_message::FingerprintStatus; use crate::util::log_message::LogMessage; @@ -30,21 +30,32 @@ use crate::util::style; const DEFAULT_DISPLAY_LIMIT: usize = 5; -pub struct ReportRebuildsOptions {} +pub struct ReportRebuildsOptions { + pub id: Option, +} pub fn report_rebuilds( gctx: &GlobalContext, ws: Option<&Workspace<'_>>, - _opts: ReportRebuildsOptions, + opts: ReportRebuildsOptions, ) -> CargoResult<()> { - let Some((log, run_id)) = list_log_files(gctx, ws)?.next() else { + let Some((log, run_id)) = find_log_file(gctx, ws, opts.id.as_ref())? else { let context = if let Some(ws) = ws { format!(" for workspace at `{}`", ws.root().display()) } else { String::new() }; - let title = format!("no sessions found{context}"); - let note = "run command with `-Z build-analysis` to generate log files"; + let (title, note) = if let Some(id) = &opts.id { + ( + format!("session `{id}` not found{context}"), + "run `cargo report sessions` to list available sessions", + ) + } else { + ( + format!("no sessions found{context}"), + "run command with `-Z build-analysis` to generate log files", + ) + }; let report = [Level::ERROR .primary_title(title) .element(Level::NOTE.message(note))]; diff --git a/src/cargo/ops/cargo_report/timings.rs b/src/cargo/ops/cargo_report/timings.rs index b2734716da9..c596f8fa74b 100644 --- a/src/cargo/ops/cargo_report/timings.rs +++ b/src/cargo/ops/cargo_report/timings.rs @@ -26,7 +26,7 @@ use crate::core::compiler::timings::report::aggregate_sections; use crate::core::compiler::timings::report::compute_concurrency; use crate::core::compiler::timings::report::round_to_centisecond; use crate::core::compiler::timings::report::write_html; -use crate::ops::cargo_report::util::list_log_files; +use crate::ops::cargo_report::util::find_log_file; use crate::ops::cargo_report::util::unit_target_description; use crate::util::log_message::FingerprintStatus; use crate::util::log_message::LogMessage; @@ -38,6 +38,7 @@ pub struct ReportTimingsOptions<'gctx> { /// Whether to attempt to open the browser after the report is generated pub open_result: bool, pub gctx: &'gctx GlobalContext, + pub id: Option, } /// Collects sections data for later post-processing through [`aggregate_sections`]. @@ -53,14 +54,23 @@ pub fn report_timings( ws: Option<&Workspace<'_>>, opts: ReportTimingsOptions<'_>, ) -> CargoResult<()> { - let Some((log, run_id)) = list_log_files(gctx, ws)?.next() else { + let Some((log, run_id)) = find_log_file(gctx, ws, opts.id.as_ref())? else { let context = if let Some(ws) = ws { format!(" for workspace at `{}`", ws.root().display()) } else { String::new() }; - let title = format!("no sessions found{context}"); - let note = "run command with `-Z build-analysis` to generate log files"; + let (title, note) = if let Some(id) = &opts.id { + ( + format!("session `{id}` not found{context}"), + "run `cargo report sessions` to list available sessions", + ) + } else { + ( + format!("no sessions found{context}"), + "run command with `-Z build-analysis` to generate log files", + ) + }; let report = [Level::ERROR .primary_title(title) .element(Level::NOTE.message(note))]; diff --git a/src/cargo/ops/cargo_report/util.rs b/src/cargo/ops/cargo_report/util.rs index 682eac2aa30..64bf872da0d 100644 --- a/src/cargo/ops/cargo_report/util.rs +++ b/src/cargo/ops/cargo_report/util.rs @@ -60,6 +60,24 @@ pub fn list_log_files( Ok(Box::new(walk)) } +pub fn find_log_file( + gctx: &GlobalContext, + ws: Option<&Workspace<'_>>, + id: Option<&RunId>, +) -> CargoResult> { + match id { + Some(requested_id) => { + for (path, run_id) in list_log_files(gctx, ws)? { + if run_id.to_string() == requested_id.to_string() { + return Ok(Some((path, run_id))); + } + } + Ok(None) + } + None => Ok(list_log_files(gctx, ws)?.next()), + } +} + pub fn unit_target_description(target: &Target, mode: CompileMode) -> String { // This is pretty similar to how the current `core::compiler::timings` // renders `core::manifest::Target`. However, our target is diff --git a/tests/testsuite/cargo_report_rebuilds/mod.rs b/tests/testsuite/cargo_report_rebuilds/mod.rs index 8aada08d77f..35fda5df3da 100644 --- a/tests/testsuite/cargo_report_rebuilds/mod.rs +++ b/tests/testsuite/cargo_report_rebuilds/mod.rs @@ -563,3 +563,84 @@ Root rebuilds: "#]]) .run(); } + +#[cargo_test] +fn with_session_id() { + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "0.0.0")) + .file("src/lib.rs", "") + .build(); + + // First session: fresh build (1 new unit) + p.cargo("check -Zbuild-analysis") + .env("CARGO_BUILD_ANALYSIS_ENABLED", "true") + .masquerade_as_nightly_cargo(&["build-analysis"]) + .run(); + + let first_log = paths::log_file(0); + let first_session_id = first_log.file_stem().unwrap().to_str().unwrap(); + + p.change_file("src/lib.rs", "// touched"); + + // Second session: rebuild (1 unit rebuilt) + p.cargo("check -Zbuild-analysis") + .env("CARGO_BUILD_ANALYSIS_ENABLED", "true") + .masquerade_as_nightly_cargo(&["build-analysis"]) + .run(); + + let _ = paths::log_file(1); + + // With --id, should use the first session (not the most recent second) + p.cargo(&format!( + "report rebuilds --id {first_session_id} -Zbuild-analysis" + )) + .masquerade_as_nightly_cargo(&["build-analysis"]) + .with_stderr_data(str![[r#" +Session: [..] +Status: 0 units rebuilt, 0 cached, 1 new + + +"#]]) + .run(); +} + +#[cargo_test] +fn session_id_not_found() { + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "0.0.0")) + .file("src/lib.rs", "") + .build(); + + p.cargo("check -Zbuild-analysis") + .env("CARGO_BUILD_ANALYSIS_ENABLED", "true") + .masquerade_as_nightly_cargo(&["build-analysis"]) + .run(); + + p.cargo("report rebuilds --id 20260101T000000000Z-0000000000000000 -Zbuild-analysis") + .masquerade_as_nightly_cargo(&["build-analysis"]) + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] session `20260101T000000000Z-0000000000000000` not found for workspace at `[ROOT]/foo` + | + = [NOTE] run `cargo report sessions` to list available sessions + +"#]]) + .run(); +} + +#[cargo_test] +fn invalid_session_id_format() { + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "0.0.0")) + .file("src/lib.rs", "") + .build(); + + p.cargo("report rebuilds --id invalid-session-id -Zbuild-analysis") + .masquerade_as_nightly_cargo(&["build-analysis"]) + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] expect run ID in format `20060724T012128000Z-<16-char-hex>`, got `invalid-session-id` + +"#]]) + .run(); +} diff --git a/tests/testsuite/cargo_report_timings/help/stdout.term.svg b/tests/testsuite/cargo_report_timings/help/stdout.term.svg index 0fd20351a5d..00b692de7cd 100644 --- a/tests/testsuite/cargo_report_timings/help/stdout.term.svg +++ b/tests/testsuite/cargo_report_timings/help/stdout.term.svg @@ -1,4 +1,4 @@ - +