guild_cli/commands/
run.rs1use std::collections::HashSet;
2use std::path::Path;
3
4use colored::Colorize;
5
6use crate::config::{ProjectName, TargetName, WorkspaceConfig};
7use crate::discovery::{discover_projects, find_workspace_root};
8use crate::error::RunnerError;
9use crate::graph::{ProjectGraph, TaskGraph};
10use crate::runner::{RunResult, TaskRunner};
11
12pub async fn run_target(
17 cwd: &Path,
18 target: &str,
19 project: Option<&str>,
20) -> Result<RunResult, RunnerError> {
21 let target_name: TargetName = target.parse().map_err(|e| RunnerError::InvalidTarget {
23 target: target.to_string(),
24 reason: format!("{e}"),
25 })?;
26
27 let root = find_workspace_root(cwd).map_err(|e| RunnerError::WorkspaceNotFound {
29 path: cwd.to_path_buf(),
30 reason: format!("{e}"),
31 })?;
32
33 let workspace = WorkspaceConfig::from_file(&root.join("guild.toml")).map_err(|e| {
34 RunnerError::ConfigError {
35 path: root.join("guild.toml"),
36 reason: format!("{e}"),
37 }
38 })?;
39
40 let all_projects = discover_projects(&workspace).map_err(|e| RunnerError::ConfigError {
42 path: root.clone(),
43 reason: format!("{e}"),
44 })?;
45
46 if all_projects.is_empty() {
47 return Ok(RunResult {
48 success_count: 0,
49 failure_count: 0,
50 skipped_count: 0,
51 cached_count: 0,
52 task_results: vec![],
53 total_duration: std::time::Duration::ZERO,
54 });
55 }
56
57 let full_project_graph =
59 ProjectGraph::build(all_projects.clone()).map_err(|e| RunnerError::GraphError {
60 reason: format!("{e}"),
61 })?;
62
63 let (project_graph, scoped_project) = if let Some(project_name) = project {
65 let project_name: ProjectName =
66 project_name
67 .parse()
68 .map_err(|e| RunnerError::InvalidTarget {
69 target: project_name.to_string(),
70 reason: format!("{e}"),
71 })?;
72
73 if full_project_graph.get(&project_name).is_none() {
75 return Err(RunnerError::ProjectNotFound {
76 name: project_name.to_string(),
77 });
78 }
79
80 let required_projects = collect_upstream_projects(&full_project_graph, &project_name);
82
83 let filtered_projects: Vec<_> = all_projects
85 .into_iter()
86 .filter(|p| required_projects.contains(p.name()))
87 .collect();
88
89 let filtered_graph =
90 ProjectGraph::build(filtered_projects).map_err(|e| RunnerError::GraphError {
91 reason: format!("{e}"),
92 })?;
93
94 (filtered_graph, Some(project_name))
95 } else {
96 (full_project_graph, None)
97 };
98
99 let task_graph =
101 TaskGraph::build(&project_graph, &target_name).map_err(|e| RunnerError::GraphError {
102 reason: format!("{e}"),
103 })?;
104
105 let task_count = task_graph.len();
106
107 if task_count == 0 {
108 let scope_msg = scoped_project
109 .map(|p| format!(" for project '{p}'"))
110 .unwrap_or_default();
111 println!(
112 "{} No projects have target '{target}'{scope_msg}",
113 "warning:".yellow().bold()
114 );
115 return Ok(RunResult {
116 success_count: 0,
117 failure_count: 0,
118 skipped_count: 0,
119 cached_count: 0,
120 task_results: vec![],
121 total_duration: std::time::Duration::ZERO,
122 });
123 }
124
125 let scope_msg = scoped_project
127 .as_ref()
128 .map(|p| format!(" (scoped to {p})"))
129 .unwrap_or_default();
130 println!(
131 "\n{} Running {} task{} for target '{}'{}\n",
132 "guild".cyan().bold(),
133 task_count,
134 if task_count == 1 { "" } else { "s" },
135 target,
136 scope_msg
137 );
138
139 let concurrency = std::thread::available_parallelism()
141 .map(|n| n.get())
142 .unwrap_or(4);
143 let runner = TaskRunner::new(concurrency, root);
144
145 let result = runner.run(task_graph, &project_graph).await?;
147
148 print_summary(&result, target);
150
151 Ok(result)
152}
153
154fn collect_upstream_projects(graph: &ProjectGraph, start: &ProjectName) -> HashSet<ProjectName> {
156 let mut result = HashSet::new();
157 let mut to_visit = vec![start.clone()];
158
159 while let Some(name) = to_visit.pop() {
160 if result.contains(&name) {
161 continue;
162 }
163 result.insert(name.clone());
164
165 if let Some(deps) = graph.dependencies(&name) {
166 for dep in deps {
167 if !result.contains(dep) {
168 to_visit.push(dep.clone());
169 }
170 }
171 }
172 }
173
174 result
175}
176
177fn print_summary(result: &RunResult, target: &str) {
179 println!();
180
181 let duration_str = format!("{:.2}s", result.total_duration.as_secs_f64());
182
183 if result.is_success() {
184 if result.success_count == 0 {
185 println!(
186 "{} No tasks executed for target '{target}'",
187 "warning:".yellow().bold()
188 );
189 } else {
190 println!(
191 "{} {} {} completed successfully in {}",
192 "Success".green().bold(),
193 result.success_count,
194 if result.success_count == 1 {
195 "task"
196 } else {
197 "tasks"
198 },
199 duration_str
200 );
201 }
202 } else {
203 let mut parts = Vec::new();
204
205 if result.success_count > 0 {
206 parts.push(format!("{} {}", result.success_count, "succeeded".green()));
207 }
208
209 parts.push(format!("{} {}", result.failure_count, "failed".red()));
210
211 if result.skipped_count > 0 {
212 parts.push(format!("{} {}", result.skipped_count, "skipped".yellow()));
213 }
214
215 println!(
216 "{} {} in {}",
217 "Failed".red().bold(),
218 parts.join(", "),
219 duration_str
220 );
221 }
222}