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