moon_config/project/
task_options_config.rs

1use crate::portable_path::FilePath;
2use crate::shapes::{InputPath, OneOrMany};
3use crate::{config_enum, config_struct, config_unit_enum, generate_switch};
4use schematic::schema::{StringType, UnionType};
5use schematic::{Config, ConfigEnum, Schema, SchemaBuilder, Schematic, ValidateError};
6use std::env::consts;
7use std::str::FromStr;
8
9fn validate_interactive<C>(
10    enabled: &bool,
11    options: &PartialTaskOptionsConfig,
12    _ctx: &C,
13    _finalize: bool,
14) -> Result<(), ValidateError> {
15    if *enabled && options.persistent.is_some_and(|v| v) {
16        return Err(ValidateError::new(
17            "an interactive task cannot be persistent",
18        ));
19    }
20
21    Ok(())
22}
23
24config_enum!(
25    /// The pattern in which affected files will be passed to the affected task.
26    #[serde(expecting = "expected `args`, `env`, or a boolean")]
27    pub enum TaskOptionAffectedFiles {
28        /// Passed as command line arguments.
29        Args,
30        /// Passed as environment variables.
31        Env,
32        /// Passed as command line arguments and environment variables.
33        #[serde(untagged)]
34        Enabled(bool),
35    }
36);
37
38generate_switch!(TaskOptionAffectedFiles, ["args", "env"]);
39
40config_enum!(
41    /// The pattern in which a task is dependent on a `.env` file.
42    #[serde(
43        untagged,
44        expecting = "expected a boolean, a file path, or a list of file paths"
45    )]
46    pub enum TaskOptionEnvFile {
47        /// Uses an `.env` file in the project root.
48        Enabled(bool),
49        /// Explicit path to an `.env` file.
50        File(FilePath),
51        /// List of explicit `.env` file paths.
52        Files(Vec<FilePath>),
53    }
54);
55
56impl TaskOptionEnvFile {
57    pub fn to_input_paths(&self) -> Option<Vec<InputPath>> {
58        match self {
59            TaskOptionEnvFile::Enabled(true) => Some(vec![InputPath::ProjectFile(".env".into())]),
60            TaskOptionEnvFile::Enabled(false) => None,
61            TaskOptionEnvFile::File(path) => {
62                InputPath::from_str(path.as_str()).ok().map(|p| vec![p])
63            }
64            TaskOptionEnvFile::Files(paths) => Some(
65                paths
66                    .iter()
67                    .flat_map(|p| InputPath::from_str(p.as_str()).ok())
68                    .collect(),
69            ),
70        }
71    }
72}
73
74impl Schematic for TaskOptionEnvFile {
75    fn schema_name() -> Option<String> {
76        Some("TaskOptionEnvFile".into())
77    }
78
79    fn build_schema(mut schema: SchemaBuilder) -> Schema {
80        schema.union(UnionType::new_any([
81            schema.infer::<bool>(),
82            schema.infer::<String>(),
83            schema.infer::<Vec<String>>(),
84        ]))
85    }
86}
87
88config_enum!(
89    /// The pattern in which to run the task automatically in CI.
90    #[serde(expecting = "expected `always`, `affected`, or a boolean")]
91    pub enum TaskOptionRunInCI {
92        /// Always run, regardless of affected.
93        Always,
94        /// Only run if affected by touched files.
95        Affected,
96        /// Either affected, or don't run at all.
97        #[serde(untagged)]
98        Enabled(bool),
99    }
100);
101
102generate_switch!(TaskOptionRunInCI, ["always", "affected"]);
103
104config_unit_enum!(
105    /// The strategy in which to merge a specific task option.
106    #[derive(ConfigEnum)]
107    pub enum TaskMergeStrategy {
108        #[default]
109        Append,
110        Prepend,
111        Preserve,
112        Replace,
113    }
114);
115
116config_unit_enum!(
117    /// The style in which task output will be printed to the console.
118    #[derive(ConfigEnum)]
119    pub enum TaskOutputStyle {
120        #[default]
121        Buffer,
122        BufferOnlyFailure,
123        Hash,
124        None,
125        Stream,
126    }
127);
128
129config_enum!(
130    /// The operating system in which to only run this task on.
131    #[derive(ConfigEnum, Copy)]
132    pub enum TaskOperatingSystem {
133        Linux,
134        #[serde(alias = "mac")]
135        Macos,
136        #[serde(alias = "win")]
137        Windows,
138    }
139);
140
141impl TaskOperatingSystem {
142    pub fn is_current_system(&self) -> bool {
143        let os = consts::OS;
144
145        match self {
146            Self::Linux => os == "linux" || os.ends_with("bsd"),
147            Self::Macos => os == "macos",
148            Self::Windows => os == "windows",
149        }
150    }
151}
152
153config_unit_enum!(
154    /// The priority levels a task can be bucketed into.
155    #[derive(ConfigEnum)]
156    pub enum TaskPriority {
157        Critical = 0,
158        High = 1,
159        #[default]
160        Normal = 2,
161        Low = 3,
162    }
163);
164
165impl TaskPriority {
166    pub fn get_level(&self) -> u8 {
167        *self as u8
168    }
169}
170
171config_unit_enum!(
172    /// A list of available shells on Unix.
173    #[derive(ConfigEnum)]
174    pub enum TaskUnixShell {
175        #[default]
176        Bash,
177        Elvish,
178        Fish,
179        Ion,
180        Murex,
181        #[serde(alias = "nushell")]
182        Nu,
183        #[serde(alias = "powershell")]
184        Pwsh,
185        Xonsh,
186        Zsh,
187    }
188);
189
190config_unit_enum!(
191    /// A list of available shells on Windows.
192    #[derive(ConfigEnum)]
193    pub enum TaskWindowsShell {
194        Bash,
195        Elvish,
196        Fish,
197        Murex,
198        #[serde(alias = "nushell")]
199        Nu,
200        #[default]
201        #[serde(alias = "powershell")]
202        Pwsh,
203        Xonsh,
204    }
205);
206
207config_struct!(
208    /// Options to control task inheritance and execution.
209    #[derive(Config)]
210    pub struct TaskOptionsConfig {
211        /// The pattern in which affected files will be passed to the task.
212        pub affected_files: Option<TaskOptionAffectedFiles>,
213
214        /// When affected and no files are matching, pass the task inputs
215        /// as arguments to the command, instead of `.`.
216        pub affected_pass_inputs: Option<bool>,
217
218        /// Allows the task to fail without failing the entire pipeline.
219        pub allow_failure: Option<bool>,
220
221        /// Caches the `outputs` of the task. Defaults to `true` if outputs
222        /// are configured for the task.
223        pub cache: Option<bool>,
224
225        /// A custom key to include in the cache hashing process. Can be
226        /// used to invalidate local and remote caches.
227        pub cache_key: Option<String>,
228
229        /// Lifetime to cache the task itself, in the format of "1h", "30m", etc.
230        /// If not defined, caches live forever, or until inputs change.
231        pub cache_lifetime: Option<String>,
232
233        /// Loads and sets environment variables from the `.env` file when
234        /// running the task.
235        pub env_file: Option<TaskOptionEnvFile>,
236
237        /// Automatically infer inputs from file groups or environment variables
238        /// that were utilized within `command`, `script`, `args`, and `env`.
239        pub infer_inputs: Option<bool>,
240
241        /// Marks the task as interactive, so that it will run in isolation,
242        /// and have direct access to stdin.
243        #[setting(validate = validate_interactive)]
244        pub interactive: Option<bool>,
245
246        /// Marks the task as internal, which disables it from begin ran
247        /// from the command line, but can be depended on.
248        pub internal: Option<bool>,
249
250        /// The default strategy to use when merging `args`, `deps`, `env`,
251        /// `inputs`, or `outputs` with an inherited task. Can be overridden
252        /// with the other field-specific merge options.
253        pub merge: Option<TaskMergeStrategy>,
254
255        /// The strategy to use when merging `args` with an inherited task.
256        pub merge_args: Option<TaskMergeStrategy>,
257
258        /// The strategy to use when merging `deps` with an inherited task.
259        pub merge_deps: Option<TaskMergeStrategy>,
260
261        /// The strategy to use when merging `env` with an inherited task.
262        pub merge_env: Option<TaskMergeStrategy>,
263
264        /// The strategy to use when merging `inputs` with an inherited task.
265        pub merge_inputs: Option<TaskMergeStrategy>,
266
267        /// The strategy to use when merging `outputs` with an inherited task.
268        pub merge_outputs: Option<TaskMergeStrategy>,
269
270        /// Creates an exclusive lock on a virtual resource, preventing other
271        /// tasks using the same resource from running concurrently.
272        pub mutex: Option<String>,
273
274        /// The operating system in which to only run this task on.
275        pub os: Option<OneOrMany<TaskOperatingSystem>>,
276
277        /// The style in which task output will be printed to the console.
278        #[setting(env = "MOON_OUTPUT_STYLE")]
279        pub output_style: Option<TaskOutputStyle>,
280
281        /// Marks the task as persistent (continuously running). This is ideal
282        /// for watchers, servers, or never-ending processes.
283        pub persistent: Option<bool>,
284
285        /// Marks the task with a certain priority, which determines the order
286        /// in which it is ran within the pipeline.
287        pub priority: Option<TaskPriority>,
288
289        /// The number of times a failing task will be retried to succeed.
290        #[setting(env = "MOON_RETRY_COUNT")]
291        pub retry_count: Option<u8>,
292
293        /// Runs direct task dependencies (via `deps`) in sequential order.
294        /// This _does not_ apply to indirect or transient dependencies.
295        pub run_deps_in_parallel: Option<bool>,
296
297        /// Whether to run the task in CI or not, when executing `moon ci` or `moon run`.
298        #[serde(rename = "runInCI")]
299        pub run_in_ci: Option<TaskOptionRunInCI>,
300
301        /// Runs the task from the workspace root, instead of the project root.
302        pub run_from_workspace_root: Option<bool>,
303
304        /// Runs the task within a shell. When not defined, runs the task
305        /// directly while relying on `PATH` resolution.
306        pub shell: Option<bool>,
307
308        /// The maximum time in seconds that a task can run before being cancelled.
309        pub timeout: Option<u64>,
310
311        /// The shell to run the task in when on a Unix-based machine.
312        pub unix_shell: Option<TaskUnixShell>,
313
314        /// The shell to run the task in when on a Windows machine.
315        pub windows_shell: Option<TaskWindowsShell>,
316    }
317);