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
123pub 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}