moon_config/
task_config.rs1use crate::shapes::{Input, OneOrMany, Output};
2use crate::task_options_config::{PartialTaskOptionsConfig, TaskOptionsConfig};
3use crate::{config_enum, config_struct, config_unit_enum};
4use indexmap::IndexMap;
5use moon_common::Id;
6use moon_target::{Target, TargetScope};
7use schematic::{Config, ConfigEnum, ValidateError, merge};
8
9pub type EnvMap = IndexMap<String, Option<String>>;
10
11fn validate_command<C>(
12 command: &PartialTaskArgs,
13 task: &PartialTaskConfig,
14 _ctx: &C,
15 _finalize: bool,
16) -> Result<(), ValidateError> {
17 let invalid = match command {
18 PartialTaskArgs::Noop => false,
19 PartialTaskArgs::String(args) => {
20 let mut parts = args.split(' ');
21 let cmd = parts.next();
22 cmd.is_none() || cmd.unwrap().is_empty()
23 }
24 PartialTaskArgs::List(args) => args.is_empty() || args[0].is_empty(),
25 };
26
27 if invalid && task.script.is_none() {
28 return Err(ValidateError::new(
29 "a command is required; use \"noop\" otherwise",
30 ));
31 }
32
33 Ok(())
34}
35
36pub(crate) fn validate_deps<D, C>(
37 deps: &[PartialTaskDependency],
38 _task: &D,
39 _context: &C,
40 _finalize: bool,
41) -> Result<(), ValidateError> {
42 for (i, dep) in deps.iter().enumerate() {
43 let scope;
44
45 match dep {
46 PartialTaskDependency::Object(cfg) => {
47 if let Some(target) = &cfg.target {
48 scope = &target.scope;
49 } else {
50 return Err(ValidateError::with_segment(
51 "a target field is required",
52 schematic::PathSegment::Index(i),
53 ));
54 }
55 }
56 PartialTaskDependency::Target(target) => {
57 scope = &target.scope;
58 }
59 };
60
61 if matches!(scope, TargetScope::All) {
62 return Err(ValidateError::with_segment(
63 "target scope not supported as a task dependency",
64 schematic::PathSegment::Index(i),
65 ));
66 }
67 }
68
69 Ok(())
70}
71
72config_enum!(
73 #[derive(ConfigEnum, Copy)]
75 pub enum TaskPreset {
76 Utility,
77 Server,
78 }
79);
80
81impl TaskPreset {
82 pub fn get_type(&self) -> TaskType {
83 TaskType::Run
84 }
85}
86
87config_unit_enum!(
88 #[derive(ConfigEnum)]
90 pub enum TaskType {
91 Build,
92 Run,
93 #[default]
94 Test,
95 }
96);
97
98config_enum!(
99 #[derive(Config)]
101 #[serde(untagged)]
102 pub enum TaskArgs {
103 #[setting(default, null)]
105 Noop,
106 String(String),
108 List(Vec<String>),
110 }
111);
112
113config_unit_enum!(
114 #[derive(ConfigEnum)]
116 pub enum TaskDependencyType {
117 Cleanup,
118 #[default]
119 Required,
120 Optional,
121 }
122);
123
124config_struct!(
125 #[derive(Config)]
127 pub struct TaskDependencyConfig {
128 pub args: Vec<String>,
130
131 pub env: EnvMap,
133
134 pub target: Target,
136
137 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub optional: Option<bool>,
141 }
142);
143
144impl TaskDependencyConfig {
145 pub fn new(target: Target) -> Self {
146 Self {
147 target,
148 ..Default::default()
149 }
150 }
151
152 pub fn optional(mut self) -> Self {
153 self.optional = Some(true);
154 self
155 }
156
157 pub fn required(mut self) -> Self {
158 self.optional = Some(false);
159 self
160 }
161}
162
163config_enum!(
164 #[derive(Config)]
166 #[serde(untagged)]
167 pub enum TaskDependency {
168 Target(Target),
170
171 #[setting(nested)]
173 Object(TaskDependencyConfig),
174 }
175);
176
177impl TaskDependency {
178 pub fn into_config(self) -> TaskDependencyConfig {
179 match self {
180 Self::Object(config) => config,
181 Self::Target(target) => TaskDependencyConfig::new(target),
182 }
183 }
184}
185
186config_struct!(
187 #[derive(Config)]
189 pub struct TaskConfig {
190 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub extends: Option<Id>,
193
194 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub description: Option<String>,
198
199 #[setting(nested, validate = validate_command)]
203 pub command: TaskArgs,
204
205 #[setting(nested)]
208 pub args: TaskArgs,
209
210 #[setting(nested, validate = validate_deps, alias = "dependsOn")]
214 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub deps: Option<Vec<TaskDependency>>,
216
217 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub env: Option<EnvMap>,
221
222 #[setting(skip, merge = merge::append_vec)]
224 pub global_inputs: Vec<Input>,
225
226 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub inputs: Option<Vec<Input>>,
236
237 #[serde(default, skip_serializing_if = "Option::is_none")]
240 pub outputs: Option<Vec<Output>>,
241
242 #[setting(nested)]
244 pub options: TaskOptionsConfig,
245
246 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub preset: Option<TaskPreset>,
249
250 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub script: Option<String>,
256
257 #[setting(alias = "toolchain")]
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub toolchains: Option<OneOrMany<Id>>,
262
263 #[setting(rename = "type")]
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub type_of: Option<TaskType>,
268 }
269);