cargo/ops/cargo_report/
util.rs

1//! Common utilities for `cargo report` commands.
2
3use std::ffi::OsStr;
4use std::path::PathBuf;
5
6use crate::CargoResult;
7use crate::GlobalContext;
8use crate::core::Workspace;
9use crate::core::compiler::CompileMode;
10use crate::util::BuildLogger;
11use crate::util::log_message::Target;
12use crate::util::logger::RunId;
13
14/// Lists log files by calling a callback for each valid log file.
15///
16/// * Yield log files from new to old
17/// * If in a workspace, select only the log files associated with the workspace
18pub fn list_log_files(
19    gctx: &GlobalContext,
20    ws: Option<&Workspace<'_>>,
21) -> CargoResult<Box<dyn Iterator<Item = (PathBuf, RunId)>>> {
22    let log_dir = gctx.home().join("log");
23    let log_dir = log_dir.as_path_unlocked();
24
25    if !log_dir.exists() {
26        return Ok(Box::new(std::iter::empty()));
27    }
28
29    let walk = walkdir::WalkDir::new(log_dir)
30        .follow_links(true)
31        .sort_by(|a, b| a.file_name().cmp(b.file_name()).reverse())
32        .min_depth(1)
33        .max_depth(1)
34        .into_iter()
35        .filter_map(|entry| {
36            let entry = entry.ok()?;
37            let path = entry.path();
38
39            // We only accept JSONL/NDJSON files.
40            if !entry.file_type().is_file() {
41                return None;
42            }
43            if entry.path().extension() != Some(OsStr::new("jsonl")) {
44                return None;
45            }
46
47            // ...and the file name must follow RunId format
48            let run_id = path.file_stem()?.to_str()?.parse::<RunId>().ok()?;
49            Some((path.to_path_buf(), run_id))
50        });
51
52    let ws_run_id = ws.map(BuildLogger::generate_run_id);
53
54    let walk = walk.filter(move |(_, id)| {
55        ws_run_id
56            .as_ref()
57            .map_or(true, |ws_id| id.same_workspace(ws_id))
58    });
59
60    Ok(Box::new(walk))
61}
62
63pub fn find_log_file(
64    gctx: &GlobalContext,
65    ws: Option<&Workspace<'_>>,
66    id: Option<&RunId>,
67) -> CargoResult<Option<(std::path::PathBuf, RunId)>> {
68    match id {
69        Some(requested_id) => {
70            for (path, run_id) in list_log_files(gctx, ws)? {
71                if run_id.to_string() == requested_id.to_string() {
72                    return Ok(Some((path, run_id)));
73                }
74            }
75            Ok(None)
76        }
77        None => Ok(list_log_files(gctx, ws)?.next()),
78    }
79}
80
81pub fn unit_target_description(target: &Target, mode: CompileMode) -> String {
82    // This is pretty similar to how the current `core::compiler::timings`
83    // renders `core::manifest::Target`. However, our target is
84    // a simplified type so we cannot reuse the same logic here.
85    let mut target_str =
86        if target.kind == "lib" && matches!(mode, CompileMode::Build | CompileMode::Check { .. }) {
87            // Special case for brevity, since most dependencies hit this path.
88            "".to_string()
89        } else if target.kind == "build-script" {
90            " build-script".to_string()
91        } else {
92            format!(r#" {} "{}""#, target.name, target.kind)
93        };
94
95    match mode {
96        CompileMode::Test => target_str.push_str(" (test)"),
97        CompileMode::Build => {}
98        CompileMode::Check { test: true } => target_str.push_str(" (check-test)"),
99        CompileMode::Check { test: false } => target_str.push_str(" (check)"),
100        CompileMode::Doc { .. } => target_str.push_str(" (doc)"),
101        CompileMode::Doctest => target_str.push_str(" (doc test)"),
102        CompileMode::Docscrape => target_str.push_str(" (doc scrape)"),
103        CompileMode::RunCustomBuild => target_str.push_str(" (run)"),
104    }
105
106    target_str
107}