Skip to content
Open
35 changes: 35 additions & 0 deletions crates/pixi_build_backend/src/specs_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pub fn convert_variant_to_pixi_build_types(
pub fn to_rattler_build_selector(selector: &TargetSelector, platform_kind: PlatformKind) -> String {
match selector {
TargetSelector::Platform(p) => format!("{platform_kind}_platform == '{p}'"),
// Expression selectors are passed through directly to rattler-build.
TargetSelector::Expression(expr) => expr.clone(),
_ => selector.to_string(),
}
}
Expand Down Expand Up @@ -494,6 +496,39 @@ mod test {
assert_eq!(match_spec.to_string(), "python");
}

#[test]
fn test_to_rattler_build_selector_platform() {
let selector = TargetSelector::Platform("linux-64".to_string());
assert_eq!(
to_rattler_build_selector(&selector, PlatformKind::Host),
"host_platform == 'linux-64'"
);
}

#[test]
fn test_to_rattler_build_selector_expression_passthrough() {
let selector = TargetSelector::Expression("host_platform == build_platform".to_string());
assert_eq!(
to_rattler_build_selector(&selector, PlatformKind::Host),
"host_platform == build_platform"
);

let selector2 = TargetSelector::Expression("target_platform != 'linux-64'".to_string());
assert_eq!(
to_rattler_build_selector(&selector2, PlatformKind::Build),
"target_platform != 'linux-64'"
);
}

#[test]
fn test_to_rattler_build_selector_family() {
let selector = TargetSelector::Unix;
assert_eq!(
to_rattler_build_selector(&selector, PlatformKind::Host),
"unix"
);
}

#[test]
fn test_binary_package_conversion_preserves_condition() {
use rattler_conda_types::{MatchSpecCondition, ParseMatchSpecOptions, RepodataRevision};
Expand Down
1 change: 1 addition & 0 deletions crates/pixi_build_backend/src/traits/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ impl TargetSelector for pbt::TargetSelector {
pbt::TargetSelector::Unix => platform.is_unix(),
pbt::TargetSelector::Win => platform.is_windows(),
pbt::TargetSelector::MacOs => platform.is_osx(),
pbt::TargetSelector::Expression(_) => false,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/pixi_build_backend_passthrough/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ fn matches_target_selector(selector: &TargetSelector, platform: Platform) -> boo
TargetSelector::Win => platform.is_windows(),
TargetSelector::MacOs => platform.is_osx(),
TargetSelector::Platform(target_platform) => target_platform == platform.as_str(),
TargetSelector::Expression(_) => false,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/pixi_build_type_conversions/src/project_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ pub fn to_target_selector_v1(selector: &TargetSelector) -> pbt::TargetSelector {
TargetSelector::Linux => pbt::TargetSelector::Linux,
TargetSelector::Win => pbt::TargetSelector::Win,
TargetSelector::MacOs => pbt::TargetSelector::MacOs,
TargetSelector::Expression(expr) => pbt::TargetSelector::Expression(expr.clone()),
}
}

Expand Down
42 changes: 33 additions & 9 deletions crates/pixi_build_types/src/project_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,13 @@ impl IsDefault for ProjectModel {
}
}

/// Represents a target selector. Currently, we only support explicit platform
/// selection.
/// Represents a target selector.
///
/// In addition to explicit platform selection, the `Expression` variant allows
/// arbitrary selector expressions wrapped in `if(...)` (e.g.
/// `"if(host_platform == build_platform)"`) that are passed through directly to
/// rattler-build. The stored string is the bare inner expression without the
/// `if(...)` wrapper.
#[derive(Debug, Clone, DeserializeFromStr, SerializeDisplay, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum TargetSelector {
Expand All @@ -96,7 +101,10 @@ pub enum TargetSelector {
Win,
MacOs,
Platform(String),
// TODO: Add minijinja coolness here.
/// A free-form selector expression passed through to rattler-build.
/// Written by users as `if(<expression>)`; the stored value is the bare
/// inner expression.
Expression(String),
}

impl Display for TargetSelector {
Expand All @@ -107,23 +115,35 @@ impl Display for TargetSelector {
TargetSelector::Win => write!(f, "win"),
TargetSelector::MacOs => write!(f, "macos"),
TargetSelector::Platform(p) => write!(f, "{p}"),
TargetSelector::Expression(expr) => write!(f, "if({expr})"),
}
}
}
impl FromStr for TargetSelector {
type Err = Infallible;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"unix" => Ok(TargetSelector::Unix),
"linux" => Ok(TargetSelector::Linux),
"win" => Ok(TargetSelector::Win),
"macos" => Ok(TargetSelector::MacOs),
_ => Ok(TargetSelector::Platform(s.to_string())),
// Expression selectors are wrapped in `if(...)`, e.g.
// `if(host_platform == build_platform)`.
if let Some(inner) = strip_if_wrapper(s) {
return Ok(TargetSelector::Expression(inner.to_string()));
}
Ok(match s {
"unix" => TargetSelector::Unix,
"linux" => TargetSelector::Linux,
"win" => TargetSelector::Win,
"macos" => TargetSelector::MacOs,
_ => TargetSelector::Platform(s.to_string()),
})
}
}

/// If `s` is wrapped as `if(<expr>)`, return the trimmed inner expression.
fn strip_if_wrapper(s: &str) -> Option<&str> {
let inner = s.strip_prefix("if(")?.strip_suffix(')')?;
Some(inner.trim())
}

/// A collect of targets including a default target.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
Expand Down Expand Up @@ -601,6 +621,10 @@ impl Hash for TargetSelector {
4u8.hash(state);
p.hash(state);
}
TargetSelector::Expression(expr) => {
5u8.hash(state);
expr.hash(state);
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion crates/pixi_manifest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ pub use spec_type::SpecType;
pub use system_requirements::{
GLIBC_FAMILY, LibCFamilyAndVersion, LibCSystemRequirement, MUSL_FAMILY, SystemRequirements,
};
pub use target::{PackageTarget, TargetSelector, Targets, WorkspaceTarget};
pub use target::{
PackageTarget, ParseTargetSelectorError, TargetSelector, Targets, WorkspaceTarget,
};
pub use task::{Task, TaskName};
use thiserror::Error;
pub use warning::{Warning, WarningWithSource, WithWarnings};
Expand Down
Loading
Loading