1use std::fmt;
2use std::path::PathBuf;
3
4use only_semantic::{DocumentAst, TaskAst};
5
6use crate::dag::expand_execution_order;
7use crate::resolve::{
8 build_execution_nodes, build_task_index, document_shell, merge_parameter_inputs,
9 resolve_root_task, resolve_root_task_in_document,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Invocation<'a> {
14 Task {
15 target: &'a str,
16 args: Vec<&'a str>,
17 overrides: Vec<(&'a str, &'a str)>,
18 },
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct ExecutionNode {
23 pub stage: usize,
24 pub name: String,
25 pub steps: Vec<ExecutionStep>,
26 pub params: Vec<PlanParam>,
27 pub shell: Option<String>,
28 pub shell_fallback: bool,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum ExecutionStep {
33 Command(String),
34 CommandBlock { source: String, line_count: usize },
35}
36
37impl ExecutionStep {
38 pub fn source(&self) -> &str {
39 match self {
40 Self::Command(command) => command,
41 Self::CommandBlock { source, .. } => source,
42 }
43 }
44
45 pub fn is_block(&self) -> bool {
46 matches!(self, Self::CommandBlock { .. })
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct PlanParam {
52 pub name: String,
53 pub default_value: Option<String>,
54 pub value: Option<String>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Default)]
58pub struct ExecutionPlan {
59 pub nodes: Vec<ExecutionNode>,
60 pub shell: Option<String>,
61 pub working_dir: PathBuf,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum PlanError {
66 UnknownTask(String),
67 HelperTask(String),
68 TaskUnavailable(String),
69 MissingRequiredParameter(String),
70 UnknownParameter {
71 task: String,
72 name: String,
73 },
74 DuplicateOverride(String),
75 CyclicDependency(String),
76 TooManyArguments {
77 task: String,
78 expected: usize,
79 got: usize,
80 },
81}
82
83impl fmt::Display for PlanError {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Self::UnknownTask(task) => write!(f, "task '{task}' does not exist"),
87 Self::HelperTask(task) => {
88 write!(f, "helper task '{task}' cannot be run directly")
89 }
90 Self::TaskUnavailable(task) => {
91 write!(f, "task '{task}' is not available on this system")
92 }
93 Self::MissingRequiredParameter(name) => {
94 write!(f, "parameter '{{{{{name}}}}}' is required")
95 }
96 Self::UnknownParameter { task, name } => {
97 write!(f, "task '{task}' has no parameter named '{name}'")
98 }
99 Self::DuplicateOverride(name) => {
100 write!(f, "parameter '{name}' was given more than once")
101 }
102 Self::CyclicDependency(path) => write!(f, "dependency loop: {path}"),
103 Self::TooManyArguments {
104 task,
105 expected,
106 got,
107 } => match expected {
108 0 => write!(f, "task '{task}' accepts no arguments, but got {got}"),
109 1 => write!(f, "task '{task}' accepts 1 argument, but got {got}"),
110 _ => write!(
111 f,
112 "task '{task}' accepts {expected} arguments, but got {got}"
113 ),
114 },
115 }
116 }
117}
118
119impl std::error::Error for PlanError {}
120
121pub fn build_execution_plan(document: &DocumentAst, invocation: Invocation<'_>) -> ExecutionPlan {
130 let working_dir = std::env::current_dir().unwrap_or_default();
131 try_build_execution_plan_in_dir(document, invocation, working_dir).unwrap_or_default()
132}
133
134pub fn try_build_execution_plan(
143 document: &DocumentAst,
144 invocation: Invocation<'_>,
145) -> Result<ExecutionPlan, PlanError> {
146 let working_dir = std::env::current_dir().unwrap_or_default();
147 try_build_execution_plan_in_dir(document, invocation, working_dir)
148}
149
150pub fn try_build_execution_plan_in_dir(
160 document: &DocumentAst,
161 invocation: Invocation<'_>,
162 working_dir: PathBuf,
163) -> Result<ExecutionPlan, PlanError> {
164 let Invocation::Task {
165 target,
166 args,
167 overrides,
168 } = invocation;
169
170 let tasks = build_task_index(document);
171 let root = resolve_root_task(&tasks, target)?;
172 let overrides = merge_parameter_inputs(args, overrides, root)?;
173 let ordered = expand_execution_order(root, &overrides, &tasks)?;
174
175 Ok(ExecutionPlan {
176 nodes: build_execution_nodes(ordered),
177 shell: document_shell(document),
178 working_dir,
179 })
180}
181
182pub fn select_root_task_variant<'a>(
191 document: &'a DocumentAst,
192 target: &str,
193) -> Result<&'a TaskAst, PlanError> {
194 resolve_root_task_in_document(document, target)
195}