diff --git a/crates/pixi_cli/src/init.rs b/crates/pixi_cli/src/init.rs index a3720a560d..1bed0a4e2e 100644 --- a/crates/pixi_cli/src/init.rs +++ b/crates/pixi_cli/src/init.rs @@ -299,7 +299,12 @@ pub async fn execute(args: Args) -> miette::Result<()> { let version = "0.1.0"; let author = get_default_author(); let platforms = if args.platforms.is_empty() { - vec![Platform::current().to_string()] + let config_platforms = config.default_platforms(); + if config_platforms.is_empty() { + vec![Platform::current().to_string()] + } else { + config_platforms + } } else { args.platforms.clone() }; @@ -744,4 +749,72 @@ mod tests { ); } } + + #[tokio::test] + async fn test_default_platforms_usage() { + use pixi_config::Config; + use tempfile::tempdir; + + let temp_dir = tempdir().unwrap(); + let config_dir = temp_dir.path().join(".pixi"); + fs_err::create_dir_all(&config_dir).unwrap(); + let config_file = config_dir.join("config.toml"); + + // Create a config with default platforms + let config_content = r#"default-platforms = ["win-64", "linux-64", "osx-64"]"#; + fs_err::write(&config_file, config_content).unwrap(); + + // Parse the config + let (config, _) = Config::from_toml(config_content, Some(&config_file)).unwrap(); + + // Test that default_platforms() returns the expected values + assert_eq!( + config.default_platforms(), + vec!["win-64", "linux-64", "osx-64"] + ); + + // Test init args with empty platforms should use config defaults + let args = Args { + path: temp_dir.path().to_path_buf(), + channels: None, + platforms: vec![], // Empty platforms + env_file: None, + format: None, + pyproject_toml: false, + scm: None, + }; + + // Test the platform selection logic matches what's in execute() + let platforms = if args.platforms.is_empty() { + let config_platforms = config.default_platforms(); + if config_platforms.is_empty() { + vec![Platform::current().to_string()] + } else { + config_platforms + } + } else { + args.platforms.clone() + }; + + assert_eq!(platforms, vec!["win-64", "linux-64", "osx-64"]); + + // Test with explicit platforms should override config + let args_with_platforms = Args { + platforms: vec!["linux-aarch64".to_string()], + ..args + }; + + let platforms_override = if args_with_platforms.platforms.is_empty() { + let config_platforms = config.default_platforms(); + if config_platforms.is_empty() { + vec![Platform::current().to_string()] + } else { + config_platforms + } + } else { + args_with_platforms.platforms.clone() + }; + + assert_eq!(platforms_override, vec!["linux-aarch64"]); + } } diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index 311754426c..fab93b95e5 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -628,6 +628,11 @@ pub struct Config { #[serde(skip_serializing_if = "Vec::is_empty")] pub default_channels: Vec, + #[serde(default)] + #[serde(alias = "default_platforms")] // BREAK: remove to stop supporting snake_case alias + #[serde(skip_serializing_if = "Vec::is_empty")] + pub default_platforms: Vec, + /// Path to the file containing the authentication token. #[serde(default)] #[serde(alias = "authentication_override_file")] // BREAK: remove to stop supporting snake_case alias @@ -736,6 +741,7 @@ impl Default for Config { fn default() -> Self { Self { default_channels: Vec::new(), + default_platforms: Vec::new(), authentication_override_file: None, tls_no_verify: None, mirrors: HashMap::new(), @@ -1339,6 +1345,11 @@ impl Config { } else { other.default_channels }, + default_platforms: if other.default_platforms.is_empty() { + self.default_platforms + } else { + other.default_platforms + }, tls_no_verify: other.tls_no_verify.or(self.tls_no_verify), authentication_override_file: other .authentication_override_file @@ -1384,6 +1395,11 @@ impl Config { } } + /// Retrieve the value for the default_platforms field (defaults to empty). + pub fn default_platforms(&self) -> Vec { + self.default_platforms.clone() + } + /// Retrieve the value for the tls_no_verify field (defaults to false). pub fn tls_no_verify(&self) -> bool { self.tls_no_verify.unwrap_or(false) @@ -2102,6 +2118,7 @@ UNUSED = "unused" let mut config = Config::default(); let other = Config { default_channels: vec![NamedChannelOrUrl::from_str("conda-forge").unwrap()], + default_platforms: vec!["linux-64".to_string()], channel_config: ChannelConfig::default_with_root_dir(PathBuf::from("/root/dir")), tls_no_verify: Some(true), detached_environments: Some(DetachedEnvironments::Path(PathBuf::from("/path/to/envs"))), @@ -2854,4 +2871,78 @@ UNUSED = "unused" } ); } + + #[test] + fn test_config_parse_default_platforms() { + // Test parsing default_platforms from TOML + let toml = r#"default-platforms = ["win-64", "linux-64", "osx-64"]"#; + let (config, _) = Config::from_toml(toml, None).unwrap(); + assert_eq!( + config.default_platforms, + vec!["win-64", "linux-64", "osx-64"] + ); + assert_eq!( + config.default_platforms(), + vec!["win-64", "linux-64", "osx-64"] + ); + + // Test empty default_platforms (should return empty vec) + let toml = r#"default-platforms = []"#; + let (config, _) = Config::from_toml(toml, None).unwrap(); + assert_eq!(config.default_platforms, Vec::::new()); + assert_eq!(config.default_platforms(), Vec::::new()); + + // Test missing default_platforms (should default to empty) + let toml = r#"tls-no-verify = true"#; + let (config, _) = Config::from_toml(toml, None).unwrap(); + assert_eq!(config.default_platforms, Vec::::new()); + assert_eq!(config.default_platforms(), Vec::::new()); + } + + #[test] + fn test_config_merge_default_platforms() { + // Test that higher priority config overrides lower priority for default_platforms + let base_config = Config { + default_platforms: vec!["linux-64".to_string()], + ..Config::default() + }; + + let override_config = Config { + default_platforms: vec!["win-64".to_string(), "osx-64".to_string()], + ..Config::default() + }; + + let merged = base_config.merge_config(override_config); + assert_eq!(merged.default_platforms, vec!["win-64", "osx-64"]); + + // Test that empty override doesn't change base + let base_config = Config { + default_platforms: vec!["linux-64".to_string()], + ..Config::default() + }; + + let empty_override = Config::default(); + + let merged = base_config.merge_config(empty_override); + assert_eq!(merged.default_platforms, vec!["linux-64"]); + } + + #[test] + fn test_config_parse_combined_defaults() { + // Test that both default_channels and default_platforms work together + let toml = r#" +default-channels = ["conda-forge", "bioconda"] +default-platforms = ["win-64", "linux-64"] +"#; + let (config, _) = Config::from_toml(toml, None).unwrap(); + + assert_eq!( + config.default_channels, + vec![ + NamedChannelOrUrl::from_str("conda-forge").unwrap(), + NamedChannelOrUrl::from_str("bioconda").unwrap() + ] + ); + assert_eq!(config.default_platforms, vec!["win-64", "linux-64"]); + } } diff --git a/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap b/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap index 4f6d1a1445..ed88c14211 100644 --- a/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap +++ b/crates/pixi_config/src/snapshots/pixi_config__tests__config_merge_multiple.snap @@ -14,6 +14,7 @@ Config { "defaults", ), ], + default_platforms: [], authentication_override_file: None, tls_no_verify: Some( false, diff --git a/docs/reference/pixi_configuration.md b/docs/reference/pixi_configuration.md index b5da22b144..319a37e03d 100644 --- a/docs/reference/pixi_configuration.md +++ b/docs/reference/pixi_configuration.md @@ -56,6 +56,7 @@ For backwards compatibility, the following configuration options can still be wr in `snake_case`: - `default_channels` + - `default_platforms` - `change_ps1` - `tls_no_verify` - `authentication_override_file` @@ -77,6 +78,18 @@ This defaults to only conda-forge. The `default-channels` are only used when initializing a new project. Once initialized the `channels` are used from the project manifest. +### `default-platforms` + +The default platforms to select when running `pixi init`. +This defaults to the current platform if not specified. + +```toml title="config.toml" +default-platforms = ["win-64", "linux-64", "osx-64"] +``` + +!!! note +The `default-platforms` are only used when initializing a new project. You can override this by explicitly providing platforms with the `--platform` flag. + ### `shell` - `change-ps1`: When set to `false`, the `(pixi)` prefix in the shell prompt is removed.