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}' does not exist"),
68 Self::HelperTask(task) => {
69 write!(f, "helper task '{task}' cannot be run directly")
70 }
71 Self::TaskUnavailable(task) => {
72 write!(f, "task '{task}' is not available on this system")
73 }
74 Self::MissingRequiredParameter(name) => {
75 write!(f, "parameter '{{{{{name}}}}}' is required")
76 }
77 Self::UnknownParameter { task, name } => {
78 write!(f, "task '{task}' has no parameter named '{name}'")
79 }
80 Self::DuplicateOverride(name) => {
81 write!(f, "parameter '{name}' was given more than once")
82 }
83 Self::CyclicDependency(path) => write!(f, "dependency loop: {path}"),
84 Self::TooManyArguments {
85 task,
86 expected,
87 got,
88 } => match expected {
89 0 => write!(f, "task '{task}' accepts no arguments, but got {got}"),
90 1 => write!(f, "task '{task}' accepts 1 argument, but got {got}"),
91 _ => write!(
92 f,
93 "task '{task}' accepts {expected} arguments, but got {got}"
94 ),
95 },
96 }
97 }
98}
99
100impl std::error::Error for PlanError {}
101
102pub fn build_execution_plan(document: &DocumentAst, invocation: Invocation<'_>) -> ExecutionPlan {
111 let working_dir = std::env::current_dir().unwrap_or_default();
112 try_build_execution_plan_in_dir(document, invocation, working_dir).unwrap_or_default()
113}
114
115pub fn try_build_execution_plan(
124 document: &DocumentAst,
125 invocation: Invocation<'_>,
126) -> Result<ExecutionPlan, PlanError> {
127 let working_dir = std::env::current_dir().unwrap_or_default();
128 try_build_execution_plan_in_dir(document, invocation, working_dir)
129}
130
131pub fn try_build_execution_plan_in_dir(
141 document: &DocumentAst,
142 invocation: Invocation<'_>,
143 working_dir: PathBuf,
144) -> Result<ExecutionPlan, PlanError> {
145 let Invocation::Task {
146 target,
147 args,
148 overrides,
149 } = invocation;
150
151 let tasks = build_task_index(document);
152 let root = resolve_root_task(&tasks, target)?;
153 let overrides = merge_parameter_inputs(args, overrides, root)?;
154 let ordered = expand_execution_order(root, &overrides, &tasks)?;
155
156 Ok(ExecutionPlan {
157 nodes: build_execution_nodes(ordered),
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}