Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions crates/pixi-build-rattler-build/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub struct RattlerBuildBackendConfig {
/// Extra input globs to include in addition to the default ones
#[serde(default)]
pub extra_input_globs: Vec<String>,
/// Enable experimental features in rattler-build (e.g., cache support for multi-output recipes)
#[serde(default)]
pub experimental: Option<bool>,
}

impl BackendConfig for RattlerBuildBackendConfig {
Expand All @@ -23,18 +26,24 @@ impl BackendConfig for RattlerBuildBackendConfig {
/// Target-specific values override base values using the following rules:
/// - debug_dir: Not allowed to have target specific value
/// - extra_input_globs: Platform-specific completely replaces base
/// - experimental: Not allowed to have target specific value
fn merge_with_target_config(&self, target_config: &Self) -> miette::Result<Self> {
if target_config.debug_dir.is_some() {
miette::bail!("`debug_dir` cannot have a target specific value");
}

if target_config.experimental.is_some() {
miette::bail!("`experimental` cannot have a target specific value");
}

Ok(Self {
debug_dir: self.debug_dir.clone(),
extra_input_globs: if target_config.extra_input_globs.is_empty() {
self.extra_input_globs.clone()
} else {
target_config.extra_input_globs.clone()
},
experimental: self.experimental,
})
}
}
Expand All @@ -57,11 +66,13 @@ mod tests {
let base_config = RattlerBuildBackendConfig {
debug_dir: Some(PathBuf::from("/base/debug")),
extra_input_globs: vec!["*.base".to_string()],
experimental: Some(false),
};

let target_config = RattlerBuildBackendConfig {
debug_dir: None,
extra_input_globs: vec!["*.target".to_string()],
experimental: None, // Not specified in target
};

let merged = base_config
Expand All @@ -73,13 +84,17 @@ mod tests {

// extra_input_globs should be completely overridden
assert_eq!(merged.extra_input_globs, vec!["*.target".to_string()]);

// experimental should be preserved from base
assert_eq!(merged.experimental, Some(false));
}

#[test]
fn test_merge_with_empty_target_config() {
let base_config = RattlerBuildBackendConfig {
debug_dir: Some(PathBuf::from("/base/debug")),
extra_input_globs: vec!["*.base".to_string()],
experimental: Some(true),
};

let empty_target_config = RattlerBuildBackendConfig::default();
Expand All @@ -91,6 +106,8 @@ mod tests {
// Should keep base values when target is empty
assert_eq!(merged.debug_dir, Some(PathBuf::from("/base/debug")));
assert_eq!(merged.extra_input_globs, vec!["*.base".to_string()]);
// experimental should be true when base has it enabled
assert_eq!(merged.experimental, Some(true));
}

#[test]
Expand All @@ -110,4 +127,94 @@ mod tests {
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("`debug_dir` cannot have a target specific value"));
}

#[test]
fn test_merge_target_experimental_error() {
// Test that setting experimental in target config returns an error (even if false)
let base_config = RattlerBuildBackendConfig {
experimental: None,
..Default::default()
};

// Test with experimental = true in target
let target_config = RattlerBuildBackendConfig {
experimental: Some(true),
..Default::default()
};

let result = base_config.merge_with_target_config(&target_config);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("`experimental` cannot have a target specific value"));

// Test with experimental = false in target (should also error)
let target_config = RattlerBuildBackendConfig {
experimental: Some(false),
..Default::default()
};

let result = base_config.merge_with_target_config(&target_config);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("`experimental` cannot have a target specific value"));
}

#[test]
fn test_merge_experimental_from_base() {
// Test that experimental value from base config is preserved
let base = RattlerBuildBackendConfig {
experimental: Some(true),
..Default::default()
};
let target = RattlerBuildBackendConfig {
experimental: None, // Not specified in target
..Default::default()
};
let merged = base.merge_with_target_config(&target).unwrap();
assert_eq!(merged.experimental, Some(true));

// Test with experimental false in base
let base = RattlerBuildBackendConfig {
experimental: Some(false),
..Default::default()
};
let target = RattlerBuildBackendConfig {
experimental: None, // Not specified in target
..Default::default()
};
let merged = base.merge_with_target_config(&target).unwrap();
assert_eq!(merged.experimental, Some(false));

// Test with experimental None in base (default)
let base = RattlerBuildBackendConfig {
experimental: None,
..Default::default()
};
let target = RattlerBuildBackendConfig {
experimental: None,
..Default::default()
};
let merged = base.merge_with_target_config(&target).unwrap();
assert_eq!(merged.experimental, None);
}

#[test]
fn test_deserialize_experimental() {
let json_data = json!({
"experimental": true
});
let config: RattlerBuildBackendConfig = serde_json::from_value(json_data).unwrap();
assert_eq!(config.experimental, Some(true));

let json_data = json!({
"experimental": false
});
let config: RattlerBuildBackendConfig = serde_json::from_value(json_data).unwrap();
assert_eq!(config.experimental, Some(false));

// Test that not specifying experimental results in None
let json_data = json!({});
let config: RattlerBuildBackendConfig = serde_json::from_value(json_data).unwrap();
assert_eq!(config.experimental, None);
}
}
4 changes: 2 additions & 2 deletions crates/pixi-build-rattler-build/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Protocol for RattlerBuildBackend {
build_platform,
hash: None,
variant: Default::default(),
experimental: false,
experimental: self.config.experimental.unwrap_or(false),
allow_undefined: false,
recipe_path: Some(self.recipe_source.path.clone()),
};
Expand Down Expand Up @@ -299,7 +299,7 @@ impl Protocol for RattlerBuildBackend {
build_platform,
hash: None,
variant: Default::default(),
experimental: false,
experimental: self.config.experimental.unwrap_or(false),
allow_undefined: false,
recipe_path: Some(self.recipe_source.path.clone()),
};
Expand Down
15 changes: 15 additions & 0 deletions docs/backends/pixi-build-rattler-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ a = { path = "../a" }

The rattler-build backend supports the following TOML configuration options:

### `experimental`

- **Type**: `Boolean`
- **Default**: `false`
- **Target Merge Behavior**: Not allowed - must be set at root level only

Enables experimental features in rattler-build. This is required for certain advanced features like the `cache:` functionality for multi-output recipes.

```toml
[package.build.config]
experimental = true
```

Note: This option cannot be set in target-specific configurations. It must be set at the root `[package.build.config]` level only.

### `debug-dir`

The backend always writes JSON-RPC request/response logs and the generated intermediate recipe to the `debug` subdirectory inside the work directory (for example `<work_directory>/debug`). The deprecated `debug-dir` configuration option is ignored; if it is still set in a manifest the backend emits a warning to make the change explicit.
Expand Down
Loading