ranim_cli/
workspace.rs

1use std::sync::Arc;
2
3use anyhow::{Context, Result};
4use ignore::gitignore::Gitignore;
5use krates::{Kid, KrateDetails, Krates};
6use log::{debug, error};
7
8use crate::cli::Args;
9
10pub struct Workspace {
11    pub krates: Krates,
12    pub ignore: Gitignore,
13    pub cargo_toml: cargo_toml::Manifest,
14}
15
16impl Workspace {
17    pub fn current() -> Result<Arc<Workspace>> {
18        let krates = {
19            let cmd = krates::Cmd::new();
20            let mut builder = krates::Builder::new();
21            builder.workspace(true);
22
23            builder.build(cmd, |_| {})
24        }
25        .context("Failed to build crate graph")?;
26
27        let workspace_root = krates.workspace_root().as_std_path().to_path_buf();
28        let workspace_root = &workspace_root;
29
30        let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(workspace_root);
31        ignore_builder.add(workspace_root.join(".gitignore"));
32        let ignore = ignore_builder
33            .build()
34            .context("Failed to build ignore file")?;
35
36        let cargo_toml = cargo_toml::Manifest::from_path(workspace_root.join("Cargo.toml"))
37            .context("Failed to load Cargo.toml")?;
38
39        let workspace = Self {
40            krates,
41            ignore,
42            cargo_toml,
43        };
44        let workspace = Arc::new(workspace);
45
46        debug!("loaded workspace at {workspace_root:?}");
47
48        Ok(workspace)
49    }
50
51    pub fn main_package(&self) -> Result<Kid> {
52        let current_dir = std::env::current_dir().unwrap();
53        let current_dir = current_dir.as_path();
54
55        let mut closest_parent = None;
56        for member in self.krates.workspace_members() {
57            if let krates::Node::Krate { id, krate, .. } = member {
58                let member_path = krate.manifest_path.parent().unwrap();
59                if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) {
60                    let len = path.components().count();
61                    match closest_parent {
62                        Some((_, closest_parent_len)) => {
63                            if len < closest_parent_len {
64                                closest_parent = Some((id, len));
65                            }
66                        }
67                        None => {
68                            closest_parent = Some((id, len));
69                        }
70                    }
71                }
72            }
73        }
74
75        let kid = closest_parent
76        .map(|(id, _)| id)
77        .with_context(|| {
78            let dylib_targets = self.krates.workspace_members().filter_map(|krate|match krate {
79                krates::Node::Krate { krate, .. } if krate.targets.iter().any(|t| t.kind.contains(&krates::cm::TargetKind::DyLib))=> {
80                    Some(format!("- {}", krate.name))
81                }
82                _ => None
83            }).collect::<Vec<_>>();
84            format!("Failed to find a dylib package to build.\nYou need to either run ranim from inside a dylib crate or specify a dylib package to build with the `--package` flag. Try building again with one of the dylib packages in the workspace:\n{}", dylib_targets.join("\n"))
85
86        })?;
87
88        Ok(kid.clone())
89    }
90
91    pub fn get_package(&self, package_name: &str) -> Result<Kid> {
92        let mut workspace_members = self.krates.workspace_members();
93        let kid = workspace_members.find_map(|node| {
94            if let krates::Node::Krate { id, krate, .. } = node
95                && krate.name == package_name
96            {
97                return Some(id);
98            }
99            None
100        });
101
102        let Some(kid) = kid else {
103            error!("Failed to find package {package_name} in the workspace.");
104            let packages = self
105                .krates
106                .workspace_members()
107                .filter_map(|package| {
108                    if let krates::Node::Krate { krate, .. } = package {
109                        Some(krate.name())
110                    } else {
111                        None
112                    }
113                })
114                .collect::<Vec<_>>();
115            error!("Available packages: {packages:?}");
116            anyhow::bail!("Failed to find package {package_name} in the workspace.");
117        };
118
119        Ok(kid.clone())
120    }
121}
122
123/// Get the target package.
124///
125/// This combines the info from args and workspace:
126/// - If `--package` is specified, use that.
127/// - Otherwise, use workspace's main package.
128pub fn get_target_package(workspace: &Workspace, args: &Args) -> (Kid, String) {
129    let kid = if let Some(package) = args.package.as_ref() {
130        workspace.get_package(package)
131    } else {
132        workspace.main_package()
133    }
134    .expect("no package");
135    let package_name =
136        if let krates::Node::Krate { krate, .. } = workspace.krates.node_for_kid(&kid).unwrap() {
137            krate.name.to_string()
138        } else {
139            unreachable!()
140        };
141    (kid, package_name)
142}