Skip to main content

moon_config/
task_config.rs

1use 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    /// Preset options to inherit.
74    #[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    /// The type of task.
89    #[derive(ConfigEnum)]
90    pub enum TaskType {
91        Build,
92        Run,
93        #[default]
94        Test,
95    }
96);
97
98config_enum!(
99    /// Configures a command and its arguments to execute.
100    #[derive(Config)]
101    #[serde(untagged)]
102    pub enum TaskArgs {
103        /// No value defined; no operation.
104        #[setting(default, null)]
105        Noop,
106        /// A command and arguments as a string. Will be parsed into a list.
107        String(String),
108        /// A command and arguments as a list of individual values.
109        List(Vec<String>),
110    }
111);
112
113config_unit_enum!(
114    /// The task-to-task relationship of the dependency.
115    #[derive(ConfigEnum)]
116    pub enum TaskDependencyType {
117        Cleanup,
118        #[default]
119        Required,
120        Optional,
121    }
122);
123
124config_struct!(
125    /// Expanded information about a task dependency.
126    #[derive(Config)]
127    pub struct TaskDependencyConfig {
128        /// Additional arguments to pass to this dependency when it's ran.
129        pub args: Vec<String>,
130
131        /// A map of environment variables specific to this dependency.
132        pub env: EnvMap,
133
134        /// The target of the depended on task.
135        pub target: Target,
136
137        /// Marks the dependency as optional when being inherited from the top-level.
138        /// @since 1.20.0
139        #[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    /// Configures another task that this task depends on.
165    #[derive(Config)]
166    #[serde(untagged)]
167    pub enum TaskDependency {
168        /// A task referenced by target.
169        Target(Target),
170
171        /// A task referenced by target, with additional parameters to pass through.
172        #[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    /// Configures a task to be ran within the action pipeline.
188    #[derive(Config)]
189    pub struct TaskConfig {
190        /// Extends settings from a sibling task by identifier.
191        #[serde(default, skip_serializing_if = "Option::is_none")]
192        pub extends: Option<Id>,
193
194        /// A human-readable description about the task.
195        /// @since 1.22.0
196        #[serde(default, skip_serializing_if = "Option::is_none")]
197        pub description: Option<String>,
198
199        /// The command line to execute when the task is ran.
200        /// Supports the command (executable) with or without arguments.
201        /// Can be defined as a string, or a list of individual arguments.
202        #[setting(nested, validate = validate_command)]
203        pub command: TaskArgs,
204
205        /// Arguments to pass to the command when it's ran. Can be
206        /// defined as a string, or a list of individual arguments.
207        #[setting(nested)]
208        pub args: TaskArgs,
209
210        /// Other tasks that this task depends on, and must run to completion
211        /// before this task is ran. Can depend on sibling tasks, or tasks in
212        /// other projects, using targets.
213        #[setting(nested, validate = validate_deps, alias = "dependsOn")]
214        #[serde(default, skip_serializing_if = "Option::is_none")]
215        pub deps: Option<Vec<TaskDependency>>,
216
217        /// A map of environment variables that will be set in the child
218        /// process when the task is ran.
219        #[serde(default, skip_serializing_if = "Option::is_none")]
220        pub env: Option<EnvMap>,
221
222        /// Internal only. Inputs defined through task inheritance.
223        #[setting(skip, merge = merge::append_vec)]
224        pub global_inputs: Vec<Input>,
225
226        /// A list of inputs that will be hashing and compared against changed files
227        /// to determine affected status. If affected, the task will run, otherwise
228        /// it will exit early. An input can be a literal file path, a glob pattern,
229        /// environment variable, and more.
230        ///
231        /// When not provided, all files within the project are considered inputs.
232        /// When an empty list, no files are considered. Otherwise, an
233        /// explicit list of inputs are considered.
234        #[serde(default, skip_serializing_if = "Option::is_none")]
235        pub inputs: Option<Vec<Input>>,
236
237        /// A list of outputs that will be created when the task has successfully ran.
238        /// An output can be a literal file path, or a glob pattern.
239        #[serde(default, skip_serializing_if = "Option::is_none")]
240        pub outputs: Option<Vec<Output>>,
241
242        /// Options to control task inheritance, execution, and more.
243        #[setting(nested)]
244        pub options: TaskOptionsConfig,
245
246        /// The preset to apply for the task. Will inherit default options.
247        #[serde(default, skip_serializing_if = "Option::is_none")]
248        pub preset: Option<TaskPreset>,
249
250        /// A script to run within a shell. A script is anything from a single command,
251        /// to multiple commands, or shell specific syntax. Does not support
252        /// arguments, merging, or inheritance. This overrides `command` and `args`.
253        /// @since 1.27.0
254        #[serde(default, skip_serializing_if = "Option::is_none")]
255        pub script: Option<String>,
256
257        /// A toolchain, or list of toolchains, in which the task will inherit
258        /// functionality from.
259        #[setting(alias = "toolchain")]
260        #[serde(default, skip_serializing_if = "Option::is_none")]
261        pub toolchains: Option<OneOrMany<Id>>,
262
263        /// The type of task, primarily used for categorical reasons. When not provided,
264        /// will be automatically determined based on configured outputs.
265        #[setting(rename = "type")]
266        #[serde(default, skip_serializing_if = "Option::is_none")]
267        pub type_of: Option<TaskType>,
268    }
269);