Skip to main content

ic_testkit/artifacts/
workspace.rs

1use std::{
2    io,
3    path::{Path, PathBuf},
4    process::Command,
5};
6
7/// Ask Cargo for the workspace owning a crate manifest directory.
8///
9/// Cargo owns membership, explicit `package.workspace`, exclusions and nested
10/// independent workspaces. This performs an offline `locate-project`, without
11/// resolving dependencies or installing a toolchain. It never guesses from the
12/// directory layout or falls back to the supplied directory.
13///
14/// # Errors
15/// Returns filesystem, Cargo invocation or invalid Cargo response errors.
16pub fn workspace_root_for(crate_manifest_dir: impl AsRef<Path>) -> io::Result<PathBuf> {
17    let manifest = crate_manifest_dir
18        .as_ref()
19        .canonicalize()?
20        .join("Cargo.toml");
21    let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
22    let output = Command::new(cargo)
23        .env("RUSTUP_AUTO_INSTALL", "0")
24        .args([
25            "--offline",
26            "locate-project",
27            "--workspace",
28            "--message-format=json",
29        ])
30        .arg("--manifest-path")
31        .arg(manifest)
32        .output()?;
33    if !output.status.success() {
34        return Err(io::Error::new(
35            io::ErrorKind::InvalidInput,
36            format!(
37                "Cargo workspace discovery failed ({}): {}",
38                output.status,
39                String::from_utf8_lossy(&output.stderr).trim()
40            ),
41        ));
42    }
43    let response: serde_json::Value = serde_json::from_slice(&output.stdout)
44        .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
45    let manifest = response
46        .get("root")
47        .and_then(serde_json::Value::as_str)
48        .map(Path::new)
49        .filter(|path| {
50            path.is_absolute() && path.file_name() == Some(std::ffi::OsStr::new("Cargo.toml"))
51        })
52        .ok_or_else(|| {
53            io::Error::new(
54                io::ErrorKind::InvalidData,
55                "Cargo returned no absolute workspace manifest",
56            )
57        })?;
58    manifest.parent().map(Path::to_owned).ok_or_else(|| {
59        io::Error::new(
60            io::ErrorKind::InvalidData,
61            "Cargo workspace manifest has no parent",
62        )
63    })
64}
65
66/// Return `<workspace>/target/<name>` for isolated host-side test artifacts.
67#[must_use]
68pub fn test_target_dir(workspace_root: &Path, name: &str) -> PathBuf {
69    workspace_root.join("target").join(name)
70}