Skip to main content

tracel_xtask/utils/
workspace.rs

1use serde_json::Value;
2use std::{path::Path, process::Command};
3
4const MEMBER_PATH_PREFIX: &str = if cfg!(target_os = "windows") {
5    "path+file:///"
6} else {
7    "path+file://"
8};
9
10pub enum WorkspaceMemberType {
11    Crate,
12    Example,
13}
14
15#[derive(Debug)]
16#[allow(dead_code)]
17pub struct WorkspaceMember {
18    pub name: String,
19    pub path: String,
20}
21
22impl WorkspaceMember {
23    fn new(name: String, path: String) -> Self {
24        Self { name, path }
25    }
26}
27
28/// Get workspace crates
29pub fn get_workspace_members(w_type: WorkspaceMemberType) -> Vec<WorkspaceMember> {
30    // Run `cargo metadata` command to get project metadata
31    let output = Command::new("cargo")
32        .arg("metadata")
33        .output()
34        .expect("Failed to execute command");
35    // Parse the JSON output
36    let metadata: Value = serde_json::from_slice(&output.stdout).expect("Failed to parse JSON");
37    // Extract workspace members from the metadata
38
39    metadata["workspace_members"]
40        .as_array()
41        .expect("Expected an array of workspace members")
42        .iter()
43        .filter_map(|member| {
44            let member_str = member.as_str()?;
45            let has_whitespace = member_str.chars().any(|c| c.is_whitespace());
46            let (name, path) = if has_whitespace {
47                parse_workspace_member0(member_str)?
48            } else {
49                parse_workspace_member1(member_str)?
50            };
51            match w_type {
52                WorkspaceMemberType::Crate if !path.contains("examples/") => {
53                    Some(WorkspaceMember::new(name.to_string(), path.to_string()))
54                }
55                WorkspaceMemberType::Example if path.contains("examples/") => {
56                    Some(WorkspaceMember::new(name.to_string(), path.to_string()))
57                }
58                _ => None,
59            }
60        })
61        .collect()
62}
63
64/// Legacy cargo metadata format for member specs (rust < 1.77)
65/// Example:
66/// "backend-comparison 0.13.0 (path+file:///Users/username/burn/backend-comparison)"
67fn parse_workspace_member0(specs: &str) -> Option<(String, String)> {
68    let parts: Vec<_> = specs.split_whitespace().collect();
69    let (name, path) = (parts.first()?.to_owned(), parts.last()?.to_owned());
70    // skip the first character because it is a '('
71    let path = path
72        .chars()
73        .skip(1)
74        .collect::<String>()
75        .replace(MEMBER_PATH_PREFIX, "")
76        .replace(')', "");
77    Some((name.to_string(), path.to_string()))
78}
79
80/// Cargo metadata format for member specs (rust >= 1.77)
81/// Example:
82/// "path+file:///Users/username/burn/backend-comparison#0.13.0"
83fn parse_workspace_member1(specs: &str) -> Option<(String, String)> {
84    let no_prefix = specs.replace(MEMBER_PATH_PREFIX, "").replace(')', "");
85    let path = Path::new(no_prefix.split_once('#')?.0);
86    let name = path.file_name()?.to_str()?;
87    let path = path.to_str()?;
88    Some((name.to_string(), path.to_string()))
89}