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 commands: Vec<String>,
26 pub params: Vec<PlanParam>,
27 pub shell: Option<String>,
28 pub shell_fallback: bool,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct PlanParam {
33 pub name: String,
34 pub default_value: Option<String>,
35 pub value: Option<String>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
39pub struct ExecutionPlan {
40 pub nodes: Vec<ExecutionNode>,
41 pub shell: Option<String>,
42 pub working_dir: PathBuf,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum PlanError {
47 UnknownTask(String),
48 HelperTask(String),
49 TaskUnavailable(String),
50 MissingRequiredParameter(String),
51 UnknownParameter {
52 task: String,
53 name: String,
54 },
55 DuplicateOverride(String),
56 CyclicDependency(String),
57 TooManyArguments {
58 task: String,
59 expected: usize,
60 got: usize,
61 },
62}
63
64impl fmt::Display for PlanError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Self::UnknownTask(task) => write!(f, "task '{task}' is not defined"),
68 Self::HelperTask(task) => {
69 write!(f, "helper task '{task}' cannot be invoked directly")
70 }
71 Self::TaskUnavailable(task) => {
72 write!(f, "task '{task}' is not available for this environment")
73 }
74 Self::MissingRequiredParameter(name) => {
75 write!(f, "missing required parameter '{{{{{name}}}}}'")
76 }
77 Self::UnknownParameter { task, name } => {
78 write!(f, "unknown parameter '{name}' for task '{task}'")
79 }
80 Self::DuplicateOverride(name) => write!(f, "duplicate parameter override '{name}'"),
81 Self::CyclicDependency(path) => write!(f, "cyclic dependency detected: {path}"),
82 Self::TooManyArguments {
83 task,
84 expected,
85 got,
86 } => write!(
87 f,
88 "too many arguments for task '{task}'; expected at most {expected}, got {got}"
89 ),
90 }
91 }
92}
93
94impl std::error::Error for PlanError {}
95
96pub fn build_execution_plan(document: &DocumentAst, invocation: Invocation<'_>) -> ExecutionPlan {
105 let working_dir = std::env::current_dir().unwrap_or_default();
106 try_build_execution_plan_in_dir(document, invocation, working_dir).unwrap_or_default()
107}
108
109pub fn try_build_execution_plan(
118 document: &DocumentAst,
119 invocation: Invocation<'_>,
120) -> Result<ExecutionPlan, PlanError> {
121 let working_dir = std::env::current_dir().unwrap_or_default();
122 try_build_execution_plan_in_dir(document, invocation, working_dir)
123}
124
125pub fn try_build_execution_plan_in_dir(
135 document: &DocumentAst,
136 invocation: Invocation<'_>,
137 working_dir: PathBuf,
138) -> Result<ExecutionPlan, PlanError> {
139 let Invocation::Task {
140 target,
141 args,
142 overrides,
143 } = invocation;
144
145 let tasks = build_task_index(document);
146 let root = resolve_root_task(&tasks, target)?;
147 let overrides = merge_parameter_inputs(args, overrides, root)?;
148 let ordered = expand_execution_order(root, &overrides, &tasks)?;
149
150 Ok(ExecutionPlan {
151 nodes: build_execution_nodes(ordered),
152 shell: document_shell(document),
153 working_dir,
154 })
155}
156
157pub fn select_root_task_variant<'a>(
166 document: &'a DocumentAst,
167 target: &str,
168) -> Result<&'a TaskAst, PlanError> {
169 resolve_root_task_in_document(document, target)
170}