diff --git a/src/cli/info.rs b/src/cli/info.rs index 4f8209399..3c33983fa 100644 --- a/src/cli/info.rs +++ b/src/cli/info.rs @@ -195,7 +195,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { let project_info = project.map(|p| ProjectInfo { manifest_path: p.root().to_path_buf().join("pixi.toml"), - tasks: p.manifest.tasks.keys().cloned().collect(), + tasks: p + .tasks(Some(Platform::current())) + .into_keys() + .map(|k| k.to_string()) + .collect(), package_count: dependency_count(&p).ok(), environment_size, last_updated: last_updated(p.lock_file_path()).ok(), diff --git a/src/cli/init.rs b/src/cli/init.rs index 7bbae74b3..17e0b4ec8 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -19,6 +19,10 @@ pub struct Args { /// Channels to use in the project. #[arg(short, long = "channel", id = "channel")] pub channels: Option>, + + /// Platforms that the project supports. + #[arg(short, long = "platform", id = "platform")] + pub platforms: Vec, } /// The default channels to use for a new project. @@ -35,7 +39,7 @@ description = "Add a short description here" authors = ["{{ author[0] }} <{{ author[1] }}>"] {%- endif %} channels = [{%- if channels %}"{{ channels|join("\", \"") }}"{%- endif %}] -platforms = ["{{ platform }}"] +platforms = ["{{ platforms|join("\", \"") }}"] [tasks] @@ -90,7 +94,11 @@ pub async fn execute(args: Args) -> miette::Result<()> { .collect() }; - let platform = Platform::current(); + let platforms = if args.platforms.is_empty() { + vec![Platform::current().to_string()] + } else { + args.platforms + }; let rv = env .render_named_str( @@ -101,7 +109,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { version, author, channels, - platform + platforms }, ) .unwrap(); diff --git a/src/cli/run.rs b/src/cli/run.rs index 9f7bc5b69..ec485f5cf 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -46,18 +46,35 @@ pub fn order_tasks( ) -> miette::Result)>> { let tasks: Vec<_> = tasks.iter().map(|c| c.to_string()).collect(); - // Find the command in the project. + // Find the command in the tasks. let (task_name, task, additional_args) = tasks .first() + // First search in the target specific tasks .and_then(|cmd_name| { - project.task_opt(cmd_name).map(|cmd| { - ( - Some(cmd_name.clone()), - cmd.clone(), - tasks[1..].iter().cloned().collect_vec(), - ) + project + .target_specific_tasks(Platform::current()) + .get(cmd_name.as_str()) + .map(|&cmd| { + ( + Some(cmd_name.clone()), + cmd.clone(), + tasks[1..].iter().cloned().collect_vec(), + ) + }) + }) + // If it isn't found in the target specific tasks try to find it in the default tasks. + .or_else(|| { + tasks.first().and_then(|cmd_name| { + project.task_opt(cmd_name).map(|cmd| { + ( + Some(cmd_name.clone()), + cmd.clone(), + tasks[1..].iter().cloned().collect_vec(), + ) + }) }) }) + // When no task is found, just execute the command. .unwrap_or_else(|| { ( None, @@ -93,11 +110,14 @@ pub fn order_tasks( for dependency in depends_on.iter() { if !added.contains(dependency) { let cmd = project - .task_opt(dependency) - .ok_or_else(|| miette::miette!("failed to find dependency {}", dependency))? - .clone(); - - s1.push_back((cmd, Vec::new())); + .target_specific_tasks(Platform::current()) + .get(dependency.as_str()) + .copied() + // If there is no target specific task try to find it in the default tasks. + .or_else(|| project.task_opt(dependency)) + .ok_or_else(|| miette::miette!("failed to find dependency {}", dependency))?; + + s1.push_back((cmd.clone(), Vec::new())); added.insert(dependency.clone()); } } diff --git a/src/cli/task.rs b/src/cli/task.rs index 65578f567..3378117c3 100644 --- a/src/cli/task.rs +++ b/src/cli/task.rs @@ -2,6 +2,7 @@ use crate::task::{Alias, CmdArgs, Execute, Task}; use crate::Project; use clap::Parser; use itertools::Itertools; +use rattler_conda_types::Platform; use std::path::PathBuf; #[derive(Parser, Debug)] @@ -28,9 +29,13 @@ pub enum Operation { pub struct RemoveArgs { /// Task names to remove pub names: Vec, + + /// The platform for which the task should be removed + #[arg(long, short)] + pub platform: Option, } -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Clone)] #[clap(arg_required_else_help = true)] pub struct AddArgs { /// Task name @@ -44,9 +49,13 @@ pub struct AddArgs { #[clap(long)] #[clap(num_args = 1..)] pub depends_on: Option>, + + /// The platform for which the task should be added + #[arg(long, short)] + pub platform: Option, } -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Clone)] #[clap(arg_required_else_help = true)] pub struct AliasArgs { /// Alias name @@ -55,6 +64,10 @@ pub struct AliasArgs { /// Depends on these tasks to execute #[clap(required = true, num_args = 1..)] pub depends_on: Vec, + + /// The platform for which the alias should be added + #[arg(long, short)] + pub platform: Option, } impl From for Task { @@ -105,9 +118,9 @@ pub fn execute(args: Args) -> miette::Result<()> { let mut project = Project::load_or_else_discover(args.manifest_path.as_deref())?; match args.operation { Operation::Add(args) => { - let name = args.name.clone(); - let task: Task = args.into(); - project.add_task(&name, task.clone())?; + let name = &args.name; + let task: Task = args.clone().into(); + project.add_task(name, task.clone(), args.platform)?; eprintln!( "{}Added task {}: {}", console::style(console::Emoji("✔ ", "+")).green(), @@ -118,7 +131,20 @@ pub fn execute(args: Args) -> miette::Result<()> { Operation::Remove(args) => { let mut to_remove = Vec::new(); for name in args.names.iter() { - if project.task_opt(name).is_none() { + if let Some(platform) = args.platform { + if !project + .target_specific_tasks(platform) + .contains_key(name.as_str()) + { + eprintln!( + "{}Task '{}' does not exist on {}", + console::style(console::Emoji("❌ ", "X")).red(), + console::style(&name).bold(), + console::style(platform.as_str()).bold(), + ); + continue; + } + } else if !project.tasks(None).contains_key(name.as_str()) { eprintln!( "{}Task {} does not exist", console::style(console::Emoji("❌ ", "X")).red(), @@ -126,8 +152,9 @@ pub fn execute(args: Args) -> miette::Result<()> { ); continue; } + // Check if task has dependencies - let depends_on = project.task_depends_on(name); + let depends_on = project.task_names_depending_on(name); if !depends_on.is_empty() && !args.names.contains(name) { eprintln!( "{}: {}", @@ -141,11 +168,11 @@ pub fn execute(args: Args) -> miette::Result<()> { ); } // Safe to remove - to_remove.push(name); + to_remove.push((name, args.platform)); } - for name in to_remove { - project.remove_task(name)?; + for (name, platform) in to_remove { + project.remove_task(name, platform)?; eprintln!( "{}Removed task {} ", console::style(console::Emoji("❌ ", "X")).yellow(), @@ -154,9 +181,9 @@ pub fn execute(args: Args) -> miette::Result<()> { } } Operation::Alias(args) => { - let name = args.alias.clone(); - let task: Task = args.into(); - project.add_task(&name, task.clone())?; + let name = &args.alias; + let task: Task = args.clone().into(); + project.add_task(name, task.clone(), args.platform)?; eprintln!( "{} Added alias {}: {}", console::style("@").blue(), @@ -165,7 +192,7 @@ pub fn execute(args: Args) -> miette::Result<()> { ); } Operation::List => { - let tasks = project.tasks(); + let tasks = project.task_names(Some(Platform::current())); if tasks.is_empty() { eprintln!("No tasks found",); } else { diff --git a/src/project/manifest.rs b/src/project/manifest.rs index 9756f5cbf..dbad37204 100644 --- a/src/project/manifest.rs +++ b/src/project/manifest.rs @@ -196,6 +196,10 @@ pub struct TargetMetadata { /// Additional information to activate an environment. #[serde(default)] pub activation: Option, + + /// Target specific tasks to run in the environment + #[serde(default)] + pub tasks: HashMap, } /// Describes the contents of the `[package]` section of the project manifest. @@ -456,4 +460,25 @@ mod test { toml_edit::de::from_str::(&contents).expect("parsing should succeed!") ); } + + #[test] + fn test_target_specific_tasks() { + let contents = format!( + r#" + {PROJECT_BOILERPLATE} + [tasks] + test = "test multi" + + [target.win-64.tasks] + test = "test win" + + [target.linux-64.tasks] + test = "test linux" + "# + ); + + assert_debug_snapshot!( + toml_edit::de::from_str::(&contents).expect("parsing should succeed!") + ); + } } diff --git a/src/project/mod.rs b/src/project/mod.rs index 7ac1d7716..9d2aa6b01 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -11,6 +11,7 @@ use rattler_conda_types::{ Channel, ChannelConfig, MatchSpec, NamelessMatchSpec, Platform, Version, }; use rattler_virtual_packages::VirtualPackage; +use std::collections::{HashMap, HashSet}; use std::{ env, fs, path::{Path, PathBuf}, @@ -120,18 +121,57 @@ impl Project { }) } - /// Get all tasks defined in the project - pub fn tasks(&self) -> Vec<&String> { - self.manifest.tasks.keys().collect() + /// Returns all tasks defined in the project for the given platform + pub fn task_names(&self, platform: Option) -> Vec<&String> { + let mut all_tasks = HashSet::new(); + + // Get all non-target specific tasks + all_tasks.extend(self.manifest.tasks.keys()); + + // Gather platform-specific tasks and overwrite the keys if they're double. + if let Some(platform) = platform { + for target_metadata in self.target_specific_metadata(platform) { + all_tasks.extend(target_metadata.tasks.keys()); + } + } + Vec::from_iter(all_tasks) + } + + /// Returns a hashmap of the tasks that should run on the given platform. + pub fn tasks(&self, platform: Option) -> HashMap<&str, &Task> { + let mut all_tasks = HashMap::default(); + + // Gather non-target specific tasks + all_tasks.extend(self.manifest.tasks.iter().map(|(k, v)| (k.as_str(), v))); + + // Gather platform-specific tasks and overwrite them if they're double. + if let Some(platform) = platform { + for target_metadata in self.target_specific_metadata(platform) { + all_tasks.extend(target_metadata.tasks.iter().map(|(k, v)| (k.as_str(), v))); + } + } + + all_tasks + } + + /// Returns a hashmap of the tasks that should run only the given platform. + pub fn target_specific_tasks(&self, platform: Platform) -> HashMap<&str, &Task> { + let mut tasks = HashMap::default(); + // Gather platform-specific tasks and overwrite them if they're double. + for target_metadata in self.target_specific_metadata(platform) { + tasks.extend(target_metadata.tasks.iter().map(|(k, v)| (k.as_str(), v))); + } + + tasks } - /// Find task dependencies - pub fn task_depends_on(&self, name: impl AsRef) -> Vec<&String> { - let task = self.manifest.tasks.get(name.as_ref()); + /// Returns names of the tasks that depend on the given task. + pub fn task_names_depending_on(&self, name: impl AsRef) -> Vec<&str> { + let mut tasks = self.tasks(Some(Platform::current())); + let task = tasks.remove(name.as_ref()); if task.is_some() { - self.manifest - .tasks - .iter() + tasks + .into_iter() .filter(|(_, c)| c.depends_on().contains(&name.as_ref().to_string())) .map(|(name, _)| name) .collect() @@ -141,22 +181,28 @@ impl Project { } /// Add a task to the project - pub fn add_task(&mut self, name: impl AsRef, task: Task) -> miette::Result<()> { - if self.manifest.tasks.contains_key(name.as_ref()) { - miette::bail!("task {} already exists", name.as_ref()); + pub fn add_task( + &mut self, + name: impl AsRef, + task: Task, + platform: Option, + ) -> miette::Result<()> { + let table = if let Some(platform) = platform { + if self + .target_specific_tasks(platform) + .contains_key(name.as_ref()) + { + miette::bail!("task {} already exists", name.as_ref()); + } + ensure_toml_target_table(&mut self.doc, platform, "tasks")? + } else { + self.doc["tasks"] + .or_insert(Item::Table(Table::new())) + .as_table_mut() + .ok_or_else(|| { + miette::miette!("target table in {} is malformed", consts::PROJECT_MANIFEST) + })? }; - - let tasks_table = &mut self.doc["tasks"]; - // If it doesnt exist create a proper table - if tasks_table.is_none() { - *tasks_table = Item::Table(Table::new()); - } - - // Cast the item into a table - let tasks_table = tasks_table.as_table_like_mut().ok_or_else(|| { - miette::miette!("tasks in {} are malformed", consts::PROJECT_MANIFEST) - })?; - let depends_on = task.depends_on(); for depends in depends_on { @@ -170,7 +216,7 @@ impl Project { } // Add the task to the table - tasks_table.insert(name.as_ref(), task_as_toml(task.clone())); + table.insert(name.as_ref(), task_as_toml(task.clone())); self.manifest.tasks.insert(name.as_ref().to_string(), task); @@ -180,19 +226,27 @@ impl Project { } /// Remove a task from the project, and the tasks that depend on it - pub fn remove_task(&mut self, name: impl AsRef) -> miette::Result<()> { - self.manifest - .tasks + pub fn remove_task( + &mut self, + name: impl AsRef, + platform: Option, + ) -> miette::Result<()> { + self.tasks(platform) .get(name.as_ref()) .ok_or_else(|| miette::miette!("task {} does not exist", name.as_ref()))?; - let tasks_table = &mut self.doc["tasks"]; - if tasks_table.is_none() { - miette::bail!("internal data-structure inconsistent with toml"); - } - let tasks_table = tasks_table.as_table_like_mut().ok_or_else(|| { - miette::miette!("tasks in {} are malformed", consts::PROJECT_MANIFEST) - })?; + // Get the task table either from the target platform or the default tasks. + let tasks_table = if let Some(platform) = platform { + ensure_toml_target_table(&mut self.doc, platform, "tasks")? + } else { + let tasks_table = &mut self.doc["tasks"]; + if tasks_table.is_none() { + miette::bail!("internal data-structure inconsistent with toml"); + } + tasks_table.as_table_like_mut().ok_or_else(|| { + miette::miette!("tasks in {} are malformed", consts::PROJECT_MANIFEST) + })? + }; // If it does not exist in toml, consider this ok as we want to remove it anyways tasks_table.remove(name.as_ref()); @@ -634,7 +688,7 @@ impl Project { Ok(full_paths) } - /// Get the task with the specified name or `None` if no such task exists. + /// Get the default task with the specified name or `None` if no such task exists. pub fn task_opt(&self, name: &str) -> Option<&Task> { self.manifest.tasks.get(name) } @@ -656,6 +710,48 @@ pub fn find_project_root() -> Option { .map(Path::to_path_buf) } +/// Ensures that the specified TOML target table exists within a given document, +/// and inserts it if not. +/// Returns the final target table (`table_name`) inside the platform-specific table if everything +/// goes as expected. +pub fn ensure_toml_target_table<'a>( + doc: &'a mut Document, + platform: Platform, + table_name: &str, +) -> miette::Result<&'a mut Table> { + // Create target table + let target = doc["target"] + .or_insert(Item::Table(Table::new())) + .as_table_mut() + .ok_or_else(|| { + miette::miette!("target table in {} is malformed", consts::PROJECT_MANIFEST) + })?; + target.set_dotted(true); + + // Create platform table on target table + let platform_table = doc["target"][platform.as_str()] + .or_insert(Item::Table(Table::new())) + .as_table_mut() + .ok_or_else(|| { + miette::miette!( + "platform table in {} is malformed", + consts::PROJECT_MANIFEST + ) + })?; + platform_table.set_dotted(true); + + // Return final table on target platform table. + platform_table[table_name] + .or_insert(Item::Table(Table::new())) + .as_table_mut() + .ok_or_else(|| { + miette::miette!( + "platform table in {} is malformed", + consts::PROJECT_MANIFEST + ) + }) +} + #[cfg(test)] mod tests { use super::*; @@ -903,4 +999,33 @@ mod tests { assert_debug_snapshot!(project.activation_scripts(Platform::Win64).unwrap()); assert_debug_snapshot!(project.activation_scripts(Platform::OsxArm64).unwrap()); } + #[test] + fn test_target_specific_tasks() { + // Using known files in the project so the test succeed including the file check. + let file_contents = r#" + [tasks] + test = "test multi" + + [target.win-64.tasks] + test = "test win" + + [target.linux-64.tasks] + test = "test linux" + "#; + + let manifest = toml_edit::de::from_str::(&format!( + "{PROJECT_BOILERPLATE}\n{file_contents}" + )) + .unwrap(); + let project = Project { + root: Default::default(), + source: "".to_string(), + doc: Default::default(), + manifest, + }; + + assert_debug_snapshot!(project.tasks(Some(Platform::Osx64))); + assert_debug_snapshot!(project.tasks(Some(Platform::Win64))); + assert_debug_snapshot!(project.tasks(Some(Platform::Linux64))); + } } diff --git a/src/project/snapshots/pixi__project__manifest__test__activation_scripts.snap b/src/project/snapshots/pixi__project__manifest__test__activation_scripts.snap index 5f4b68c37..a18aa0f82 100644 --- a/src/project/snapshots/pixi__project__manifest__test__activation_scripts.snap +++ b/src/project/snapshots/pixi__project__manifest__test__activation_scripts.snap @@ -59,6 +59,7 @@ ProjectManifest { ), }, ), + tasks: {}, }, PixiSpanned { span: Some( @@ -81,6 +82,7 @@ ProjectManifest { ), }, ), + tasks: {}, }, }, activation: Some( diff --git a/src/project/snapshots/pixi__project__manifest__test__target_specific.snap b/src/project/snapshots/pixi__project__manifest__test__target_specific.snap index 36c517bef..5465accbb 100644 --- a/src/project/snapshots/pixi__project__manifest__test__target_specific.snap +++ b/src/project/snapshots/pixi__project__manifest__test__target_specific.snap @@ -71,6 +71,7 @@ ProjectManifest { host_dependencies: None, build_dependencies: None, activation: None, + tasks: {}, }, PixiSpanned { span: Some( @@ -104,6 +105,7 @@ ProjectManifest { host_dependencies: None, build_dependencies: None, activation: None, + tasks: {}, }, }, activation: None, diff --git a/src/project/snapshots/pixi__project__manifest__test__target_specific_tasks.snap b/src/project/snapshots/pixi__project__manifest__test__target_specific_tasks.snap new file mode 100644 index 000000000..a7df5bef0 --- /dev/null +++ b/src/project/snapshots/pixi__project__manifest__test__target_specific_tasks.snap @@ -0,0 +1,84 @@ +--- +source: src/project/manifest.rs +expression: "toml_edit::de::from_str::(&contents).expect(\"parsing should succeed!\")" +--- +ProjectManifest { + project: ProjectMetadata { + name: "foo", + version: Version { + version: [[0], [0], [1], [0]], + local: [], + }, + description: None, + authors: [], + channels: [], + platforms: PixiSpanned { + span: Some( + 121..123, + ), + value: [], + }, + license: None, + license_file: None, + readme: None, + homepage: None, + repository: None, + documentation: None, + }, + tasks: { + "test": Plain( + "test multi", + ), + }, + system_requirements: SystemRequirements { + windows: None, + unix: None, + macos: None, + linux: None, + cuda: None, + libc: None, + archspec: None, + }, + dependencies: {}, + host_dependencies: None, + build_dependencies: None, + target: { + PixiSpanned { + span: Some( + 206..212, + ), + value: Platform( + Win64, + ), + }: TargetMetadata { + dependencies: {}, + host_dependencies: None, + build_dependencies: None, + activation: None, + tasks: { + "test": Plain( + "test win", + ), + }, + }, + PixiSpanned { + span: Some( + 271..279, + ), + value: Platform( + Linux64, + ), + }: TargetMetadata { + dependencies: {}, + host_dependencies: None, + build_dependencies: None, + activation: None, + tasks: { + "test": Plain( + "test linux", + ), + }, + }, + }, + activation: None, +} diff --git a/src/project/snapshots/pixi__project__tests__target_specific_tasks-2.snap b/src/project/snapshots/pixi__project__tests__target_specific_tasks-2.snap new file mode 100644 index 000000000..17f08b85f --- /dev/null +++ b/src/project/snapshots/pixi__project__tests__target_specific_tasks-2.snap @@ -0,0 +1,9 @@ +--- +source: src/project/mod.rs +expression: "project.tasks(Platform::Win64)" +--- +{ + "test": Plain( + "test win", + ), +} diff --git a/src/project/snapshots/pixi__project__tests__target_specific_tasks-3.snap b/src/project/snapshots/pixi__project__tests__target_specific_tasks-3.snap new file mode 100644 index 000000000..51e0aeab2 --- /dev/null +++ b/src/project/snapshots/pixi__project__tests__target_specific_tasks-3.snap @@ -0,0 +1,9 @@ +--- +source: src/project/mod.rs +expression: "project.tasks(Platform::Linux64)" +--- +{ + "test": Plain( + "test linux", + ), +} diff --git a/src/project/snapshots/pixi__project__tests__target_specific_tasks.snap b/src/project/snapshots/pixi__project__tests__target_specific_tasks.snap new file mode 100644 index 000000000..fb8e066e7 --- /dev/null +++ b/src/project/snapshots/pixi__project__tests__target_specific_tasks.snap @@ -0,0 +1,9 @@ +--- +source: src/project/mod.rs +expression: "project.tasks(Platform::Osx64)" +--- +{ + "test": Plain( + "test multi", + ), +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 6d764c5fd..0955d85ba 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -129,6 +129,19 @@ impl PixiControl { args: init::Args { path: self.project_path().to_path_buf(), channels: None, + platforms: Vec::new(), + }, + } + } + + /// Initialize pixi project inside a temporary directory. Returns a [`InitBuilder`]. To execute + /// the command and await the result call `.await` on the return value. + pub fn init_with_platforms(&self, platforms: Vec) -> InitBuilder { + InitBuilder { + args: init::Args { + path: self.project_path().to_path_buf(), + channels: None, + platforms, }, } } @@ -197,32 +210,39 @@ pub struct TasksControl<'a> { impl TasksControl<'_> { /// Add a task - pub fn add(&self, name: impl ToString) -> TaskAddBuilder { + pub fn add(&self, name: impl ToString, platform: Option) -> TaskAddBuilder { TaskAddBuilder { manifest_path: Some(self.pixi.manifest_path()), args: AddArgs { name: name.to_string(), commands: vec![], depends_on: None, + platform: platform, }, } } /// Remove a task - pub async fn remove(&self, name: impl ToString) -> miette::Result<()> { + pub async fn remove( + &self, + name: impl ToString, + platform: Option, + ) -> miette::Result<()> { task::execute(task::Args { manifest_path: Some(self.pixi.manifest_path()), operation: task::Operation::Remove(task::RemoveArgs { names: vec![name.to_string()], + platform, }), }) } /// Alias one or multiple tasks - pub fn alias(&self, name: impl ToString) -> TaskAliasBuilder { + pub fn alias(&self, name: impl ToString, platform: Option) -> TaskAliasBuilder { TaskAliasBuilder { manifest_path: Some(self.pixi.manifest_path()), args: AliasArgs { + platform: platform, alias: name.to_string(), depends_on: vec![], }, diff --git a/tests/task_tests.rs b/tests/task_tests.rs index 1233ea50b..e72571359 100644 --- a/tests/task_tests.rs +++ b/tests/task_tests.rs @@ -1,6 +1,7 @@ use crate::common::PixiControl; use pixi::cli::run::Args; use pixi::task::{CmdArgs, Task}; +use rattler_conda_types::Platform; mod common; @@ -11,7 +12,7 @@ pub async fn add_remove_task() { // Simple task pixi.tasks() - .add("test") + .add("test", None) .with_commands(["echo hello"]) .execute() .unwrap(); @@ -21,7 +22,7 @@ pub async fn add_remove_task() { assert!(matches!(task, Task::Plain(s) if s == "echo hello")); // Remove the task - pixi.tasks().remove("test").await.unwrap(); + pixi.tasks().remove("test", None).await.unwrap(); assert_eq!(pixi.project().unwrap().manifest.tasks.len(), 0); } @@ -32,12 +33,12 @@ pub async fn add_command_types() { // Add a command with dependencies pixi.tasks() - .add("test") + .add("test", None) .with_commands(["echo hello"]) .execute() .unwrap(); pixi.tasks() - .add("test2") + .add("test2", None) .with_commands(["echo hello", "echo bonjour"]) .with_depends_on(["test"]) .execute() @@ -50,7 +51,7 @@ pub async fn add_command_types() { // Create an alias pixi.tasks() - .alias("testing") + .alias("testing", None) .with_depends_on(["test"]) .execute() .unwrap(); @@ -65,19 +66,19 @@ async fn test_alias() { pixi.init().without_channels().await.unwrap(); pixi.tasks() - .add("hello") + .add("hello", None) .with_commands(["echo hello"]) .execute() .unwrap(); pixi.tasks() - .add("world") + .add("world", None) .with_commands(["echo world"]) .execute() .unwrap(); pixi.tasks() - .add("helloworld") + .add("helloworld", None) .with_depends_on(["hello", "world"]) .execute() .unwrap(); @@ -93,3 +94,42 @@ async fn test_alias() { assert_eq!(result.exit_code, 0); assert_eq!(result.stdout, "hello\nworld\n"); } + +#[tokio::test] +pub async fn add_remove_target_specific_task() { + let pixi = PixiControl::new().unwrap(); + pixi.init_with_platforms(vec!["win-64".to_string()]) + .await + .unwrap(); + + // Simple task + pixi.tasks() + .add("test", Some(Platform::Win64)) + .with_commands(["echo only_on_windows"]) + .execute() + .unwrap(); + + let project = pixi.project().unwrap(); + let task = *project.tasks(Some(Platform::Win64)).get("test").unwrap(); + assert!(matches!(task, Task::Plain(s) if s == "echo only_on_windows")); + + // Simple task + pixi.tasks() + .add("test", None) + .with_commands(["echo hello"]) + .execute() + .unwrap(); + + // Remove the task + pixi.tasks() + .remove("test", Some(Platform::Win64)) + .await + .unwrap(); + assert_eq!( + pixi.project() + .unwrap() + .target_specific_tasks(Platform::Win64) + .len(), + 0 + ); +}