1use crate::shapes::{FilePath, OneOrMany};
2use crate::{config_enum, config_struct, config_unit_enum, config_untagged_enum, generate_switch};
3use schematic::schema::{StringType, UnionType};
4use schematic::{Config, ConfigEnum, Schema, SchemaBuilder, Schematic, ValidateError};
5use std::env::consts;
6
7fn validate_interactive<C>(
8 enabled: &bool,
9 options: &PartialTaskOptionsConfig,
10 _ctx: &C,
11 _finalize: bool,
12) -> Result<(), ValidateError> {
13 if *enabled && options.persistent.is_some_and(|v| v) {
14 return Err(ValidateError::new(
15 "an interactive task cannot be persistent",
16 ));
17 }
18
19 Ok(())
20}
21
22config_enum!(
23 #[serde(expecting = "expected `args`, `env`, or a boolean")]
25 pub enum TaskOptionAffectedFilesPattern {
26 Args,
28 Env,
30 #[serde(untagged)]
32 Enabled(bool),
33 }
34);
35
36generate_switch!(TaskOptionAffectedFilesPattern, ["args", "env"]);
37
38impl Default for TaskOptionAffectedFilesPattern {
39 fn default() -> Self {
40 Self::Enabled(true)
41 }
42}
43
44config_struct!(
45 #[derive(Config)]
47 pub struct TaskOptionAffectedFilesConfig {
48 pub pass: TaskOptionAffectedFilesPattern,
50
51 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub pass_inputs_when_no_match: Option<bool>,
55 }
56);
57
58config_untagged_enum!(
59 #[derive(Config)]
60 pub enum TaskOptionAffectedFilesEntry {
61 Pattern(TaskOptionAffectedFilesPattern),
62
63 #[setting(nested)]
64 Object(TaskOptionAffectedFilesConfig),
65 }
66);
67
68config_enum!(
69 #[serde(expecting = "expected `local`, `remote`, or a boolean")]
71 pub enum TaskOptionCache {
72 Local,
74 Remote,
76 #[serde(untagged)]
78 Enabled(bool),
79 }
80);
81
82generate_switch!(TaskOptionCache, ["local", "remote"]);
83
84impl TaskOptionCache {
85 pub fn is_local_enabled(&self) -> bool {
86 matches!(self, Self::Enabled(true) | Self::Local)
87 }
88
89 pub fn is_remote_enabled(&self) -> bool {
90 matches!(self, Self::Enabled(true) | Self::Remote)
91 }
92}
93
94config_untagged_enum!(
95 pub enum TaskOptionEnvFile {
97 Enabled(bool),
99 File(FilePath),
101 Files(Vec<FilePath>),
103 }
104);
105
106impl Schematic for TaskOptionEnvFile {
107 fn schema_name() -> Option<String> {
108 Some("TaskOptionEnvFile".into())
109 }
110
111 fn build_schema(mut schema: SchemaBuilder) -> Schema {
112 schema.union(UnionType::new_any([
113 schema.infer::<bool>(),
114 schema.infer::<String>(),
115 schema.infer::<Vec<String>>(),
116 ]))
117 }
118}
119
120config_enum!(
121 #[serde(expecting = "expected `always`, `affected`, `only`, `skip`, or a boolean")]
123 pub enum TaskOptionRunInCI {
124 Always,
126 Affected,
128 Only,
130 Skip,
132 #[serde(untagged)]
134 Enabled(bool),
135 }
136);
137
138generate_switch!(TaskOptionRunInCI, ["always", "affected", "only", "skip"]);
139
140config_unit_enum!(
141 #[derive(ConfigEnum)]
143 pub enum TaskMergeStrategy {
144 #[default]
145 Append,
146 Prepend,
147 Preserve,
148 Replace,
149 }
150);
151
152config_unit_enum!(
153 #[derive(ConfigEnum)]
155 pub enum TaskOutputStyle {
156 #[default]
157 Buffer,
158 BufferOnlyFailure,
159 Hash,
160 None,
161 Stream,
162 }
163);
164
165config_enum!(
166 #[derive(ConfigEnum, Copy)]
168 pub enum TaskOperatingSystem {
169 Linux,
170 #[serde(alias = "mac")]
171 Macos,
172 #[serde(alias = "win")]
173 Windows,
174 }
175);
176
177impl TaskOperatingSystem {
178 pub fn is_current_system(&self) -> bool {
179 let os = consts::OS;
180
181 match self {
182 Self::Linux => os == "linux" || os.ends_with("bsd"),
183 Self::Macos => os == "macos",
184 Self::Windows => os == "windows",
185 }
186 }
187}
188
189config_unit_enum!(
190 #[derive(ConfigEnum)]
193 pub enum TaskPriority {
194 Critical = 0,
195 High = 1,
196 #[default]
197 Normal = 2,
198 Low = 3,
199 }
200);
201
202impl TaskPriority {
203 pub fn get_level(&self) -> u8 {
204 *self as u8
205 }
206}
207
208config_unit_enum!(
209 #[derive(ConfigEnum)]
211 pub enum TaskUnixShell {
212 #[default]
213 Bash,
214 Elvish,
215 Fish,
216 Ion,
217 Murex,
218 #[serde(alias = "nushell")]
219 Nu,
220 #[serde(alias = "powershell")]
221 Pwsh,
222 Xonsh,
223 Zsh,
224 }
225);
226
227config_unit_enum!(
228 #[derive(ConfigEnum)]
230 pub enum TaskWindowsShell {
231 Bash,
232 Elvish,
233 Fish,
234 Murex,
235 #[serde(alias = "nushell")]
236 Nu,
237 #[default]
238 #[serde(alias = "powershell")]
239 Pwsh,
240 Xonsh,
241 }
242);
243
244config_struct!(
245 #[derive(Config)]
247 #[serde(default)]
248 pub struct TaskOptionsConfig {
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub affected_files: Option<TaskOptionAffectedFilesEntry>,
252
253 #[serde(skip_serializing_if = "Option::is_none")]
256 pub allow_failure: Option<bool>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
261 pub cache: Option<TaskOptionCache>,
262
263 #[serde(skip_serializing_if = "Option::is_none")]
266 pub cache_key: Option<String>,
267
268 #[serde(skip_serializing_if = "Option::is_none")]
272 pub cache_lifetime: Option<String>,
273
274 #[serde(skip_serializing_if = "Option::is_none")]
277 pub env_file: Option<TaskOptionEnvFile>,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
283 pub infer_inputs: Option<bool>,
284
285 #[setting(validate = validate_interactive)]
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub interactive: Option<bool>,
291
292 #[serde(skip_serializing_if = "Option::is_none")]
296 pub internal: Option<bool>,
297
298 #[serde(skip_serializing_if = "Option::is_none")]
302 pub merge: Option<TaskMergeStrategy>,
303
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub merge_args: Option<TaskMergeStrategy>,
307
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub merge_deps: Option<TaskMergeStrategy>,
311
312 #[serde(skip_serializing_if = "Option::is_none")]
314 pub merge_env: Option<TaskMergeStrategy>,
315
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub merge_inputs: Option<TaskMergeStrategy>,
319
320 #[serde(skip_serializing_if = "Option::is_none")]
322 pub merge_outputs: Option<TaskMergeStrategy>,
323
324 #[serde(skip_serializing_if = "Option::is_none")]
327 pub merge_toolchains: Option<TaskMergeStrategy>,
328
329 #[serde(skip_serializing_if = "Option::is_none")]
333 pub mutex: Option<String>,
334
335 #[serde(skip_serializing_if = "Option::is_none")]
337 pub os: Option<OneOrMany<TaskOperatingSystem>>,
338
339 #[setting(env = "MOON_OUTPUT_STYLE")]
341 #[serde(skip_serializing_if = "Option::is_none")]
342 pub output_style: Option<TaskOutputStyle>,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
348 pub persistent: Option<bool>,
349
350 #[serde(skip_serializing_if = "Option::is_none")]
353 pub priority: Option<TaskPriority>,
354
355 #[setting(env = "MOON_RETRY_COUNT")]
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub retry_count: Option<u8>,
359
360 #[serde(skip_serializing_if = "Option::is_none")]
363 pub run_deps_in_parallel: Option<bool>,
364
365 #[setting(rename = "runInCI")]
368 #[serde(skip_serializing_if = "Option::is_none")]
369 pub run_in_ci: Option<TaskOptionRunInCI>,
370
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub run_from_workspace_root: Option<bool>,
374
375 #[serde(skip_serializing_if = "Option::is_none")]
378 pub shell: Option<bool>,
379
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub timeout: Option<u64>,
383
384 #[serde(skip_serializing_if = "Option::is_none")]
387 pub unix_shell: Option<TaskUnixShell>,
388
389 #[serde(skip_serializing_if = "Option::is_none")]
392 pub windows_shell: Option<TaskWindowsShell>,
393 }
394);