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_echo, document_preview, document_shell,
9 merge_parameter_inputs, 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 echo: bool,
42 pub preview: bool,
43 pub shell: Option<String>,
44 pub working_dir: PathBuf,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum PlanError {
49 UnknownTask(String),
50 HelperTask(String),
51 TaskUnavailable(String),
52 MissingRequiredParameter(String),
53 UnknownParameter {
54 task: String,
55 name: String,
56 },
57 DuplicateOverride(String),
58 CyclicDependency(String),
59 TooManyArguments {
60 task: String,
61 expected: usize,
62 got: usize,
63 },
64}
65
66impl fmt::Display for PlanError {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::UnknownTask(task) => write!(f, "task '{task}' is not defined"),
70 Self::HelperTask(task) => {
71 write!(f, "helper task '{task}' cannot be invoked directly")
72 }
73 Self::TaskUnavailable(task) => {
74 write!(f, "task '{task}' is not available for this environment")
75 }
76 Self::MissingRequiredParameter(name) => {
77 write!(f, "missing required parameter '{{{{{name}}}}}'")
78 }
79 Self::UnknownParameter { task, name } => {
80 write!(f, "unknown parameter '{name}' for task '{task}'")
81 }
82 Self::DuplicateOverride(name) => write!(f, "duplicate parameter override '{name}'"),
83 Self::CyclicDependency(path) => write!(f, "cyclic dependency detected: {path}"),
84 Self::TooManyArguments {
85 task,
86 expected,
87 got,
88 } => write!(
89 f,
90 "too many arguments for task '{task}'; expected at most {expected}, got {got}"
91 ),
92 }
93 }
94}
95
96impl std::error::Error for PlanError {}
97
98pub fn build_execution_plan(document: &DocumentAst, invocation: Invocation<'_>) -> ExecutionPlan {
107 let working_dir = std::env::current_dir().unwrap_or_default();
108 try_build_execution_plan_in_dir(document, invocation, working_dir).unwrap_or_default()
109}
110
111pub fn try_build_execution_plan(
120 document: &DocumentAst,
121 invocation: Invocation<'_>,
122) -> Result<ExecutionPlan, PlanError> {
123 let working_dir = std::env::current_dir().unwrap_or_default();
124 try_build_execution_plan_in_dir(document, invocation, working_dir)
125}
126
127pub fn try_build_execution_plan_in_dir(
137 document: &DocumentAst,
138 invocation: Invocation<'_>,
139 working_dir: PathBuf,
140) -> Result<ExecutionPlan, PlanError> {
141 let Invocation::Task {
142 target,
143 args,
144 overrides,
145 } = invocation;
146
147 let tasks = build_task_index(document);
148 let root = resolve_root_task(&tasks, target)?;
149 let overrides = merge_parameter_inputs(args, overrides, root)?;
150 let ordered = expand_execution_order(root, &overrides, &tasks)?;
151
152 Ok(ExecutionPlan {
153 nodes: build_execution_nodes(ordered),
154 echo: document_echo(document),
155 preview: document_preview(document),
156 shell: document_shell(document),
157 working_dir,
158 })
159}
160
161pub fn select_root_task_variant<'a>(
170 document: &'a DocumentAst,
171 target: &str,
172) -> Result<&'a TaskAst, PlanError> {
173 resolve_root_task_in_document(document, target)
174}