Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .github/workflows/build-upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- "pixi-build-python-v[0-9]+.[0-9]+.[0-9]+"
- "pixi-build-rattler-build-v[0-9]+.[0-9]+.[0-9]+"
- "pixi-build-rust-v[0-9]+.[0-9]+.[0-9]+"
- "pixi-build-zig-v[0-9]+.[0-9]+.[0-9]+"
- "pixi-build-mojo-v[0-9]+.[0-9]+.[0-9]+"
- "pixi-build-ros-v[0-9]+.[0-9]+.[0-9]+"
- "py-pixi-build-backend-v[0-9]+.[0-9]+.[0-9]+"
Expand Down
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
This repository contains backend implementations designed to facilitate the building of pixi projects directly from their source code. These backends aim to enhance the functionality of Pixi, a cross-platform, multi-language package manager and workflow tool built on the foundation of the conda ecosystem.

## Available Build Backends
The idea is that a backend should be able to build a certain type of so
The idea is that a backend should be able to build a certain type of project.
The repository provides the following build backends:

1. **pixi-build-python**: A backend tailored for building Python-based projects.
2. **pixi-build-cmake**: A backend designed for projects utilizing CMake as their build system.
3. **pixi-build-rattler-build**: A backend for building [`recipe.yaml`](https://rattler.build/latest/) directly
4. **pixi-build-rust**: A backend for building Rust projects.
5. **pixi-build-zig**: A backend for building Zig projects.


These backends are located in the `crates/*` directory of the repository.
Expand Down
28 changes: 28 additions & 0 deletions crates/pixi-build-zig/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "pixi-build-zig"
version = "0.1.0"
description = "A Zig build backend for Pixi"
documentation = "https://prefix-dev.github.io/pixi-build-backends/backends/pixi-build-zig/"
repository.workspace = true
license.workspace = true
edition.workspace = true

[dependencies]
fs-err = { workspace = true }
indexmap = { workspace = true }
miette = { workspace = true }
minijinja = { workspace = true, features = ["json"] }
rattler_conda_types = { workspace = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["macros"] }
pixi-build-backend = { workspace = true }
pixi_build_types = { workspace = true }

recipe-stage0 = { workspace = true }

[dev-dependencies]
insta = { workspace = true, features = ["yaml", "redactions", "filters"] }
rstest = { workspace = true }
serde_json = { workspace = true }
tempfile = { workspace = true }
10 changes: 10 additions & 0 deletions crates/pixi-build-zig/pixi.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package.build.backend]
name = "pixi-build-rust"
version = "*"
channels = [
"https://prefix.dev/pixi-build-backends",
"https://prefix.dev/conda-forge",
]

[package.run-dependencies]
pixi-build-api-version = ">=2,<3"
10 changes: 10 additions & 0 deletions crates/pixi-build-zig/src/build_script.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{%- macro env(key) -%}
{%- if is_bash %}{{ "$" ~key }}{% else %}{{ "%" ~ key ~ "%" }}{% endif -%}
{%- endmacro -%}
{%- set install_prefix = env("LIBRARY_PREFIX") if not is_bash else env("PREFIX") -%}

zig build --prefix {{ install_prefix }}{% if extra_args | length > 0 %} {{ extra_args | join(" ") }}{% endif %}
{%- if not is_bash %}

if errorlevel 1 exit 1
{%- endif %}
56 changes: 56 additions & 0 deletions crates/pixi-build-zig/src/build_script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use minijinja::Environment;
use serde::Serialize;

#[derive(Serialize)]
pub struct BuildScriptContext {
/// Any additional args to pass to `zig build`
pub extra_args: Vec<String>,

/// The platform that is running the build.
pub is_bash: bool,
}

impl BuildScriptContext {
pub fn render(&self) -> String {
let env = Environment::new();
let template = env
.template_from_str(include_str!("build_script.j2"))
.unwrap();
template.render(self).unwrap().trim().to_string()
}
}

#[cfg(test)]
mod test {
use rstest::*;

#[rstest]
fn test_build_script(#[values(true, false)] is_bash: bool) {
let context = super::BuildScriptContext {
extra_args: vec![],
is_bash,
};
let script = context.render();

let mut settings = insta::Settings::clone_current();
settings.set_snapshot_suffix(if is_bash { "bash" } else { "cmdexe" });
settings.bind(|| {
insta::assert_snapshot!(script);
});
}

#[rstest]
fn test_build_script_with_extra_args(#[values(true, false)] is_bash: bool) {
let context = super::BuildScriptContext {
extra_args: vec!["-Doptimize=ReleaseFast".to_string()],
is_bash,
};
let script = context.render();

let mut settings = insta::Settings::clone_current();
settings.set_snapshot_suffix(if is_bash { "bash" } else { "cmdexe" });
settings.bind(|| {
insta::assert_snapshot!(script);
});
}
}
164 changes: 164 additions & 0 deletions crates/pixi-build-zig/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use indexmap::IndexMap;
use pixi_build_backend::generated_recipe::BackendConfig;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct ZigBackendConfig {
/// Extra args to pass for zig build
#[serde(default)]
pub extra_args: Vec<String>,
/// Environment Variables
#[serde(default)]
pub env: IndexMap<String, String>,
/// If set, internal state will be logged as files in that directory
pub debug_dir: Option<PathBuf>,
/// Extra input globs to include in addition to the default ones
#[serde(default)]
pub extra_input_globs: Vec<String>,
}

impl BackendConfig for ZigBackendConfig {
fn debug_dir(&self) -> Option<&Path> {
self.debug_dir.as_deref()
}

/// Merge this configuration with a target-specific configuration.
/// Target-specific values override base values using the following rules:
/// - extra_args: Platform-specific completely replaces base
/// - env: Platform env vars override base, others merge
/// - debug_dir: Not allowed to have target specific value
/// - extra_input_globs: Platform-specific completely replaces base
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");
}

Ok(Self {
extra_args: if target_config.extra_args.is_empty() {
self.extra_args.clone()
} else {
target_config.extra_args.clone()
},
env: {
let mut merged_env = self.env.clone();
merged_env.extend(target_config.env.clone());
merged_env
},
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()
},
})
}
}

#[cfg(test)]
mod tests {
use super::ZigBackendConfig;
use pixi_build_backend::generated_recipe::BackendConfig;
use serde_json::json;
use std::path::PathBuf;

#[test]
fn test_ensure_deseralize_from_empty() {
let json_data = json!({});
serde_json::from_value::<ZigBackendConfig>(json_data).unwrap();
}

#[test]
fn test_merge_with_target_config() {
let mut base_env = indexmap::IndexMap::new();
base_env.insert("BASE_VAR".to_string(), "base_value".to_string());
base_env.insert("SHARED_VAR".to_string(), "base_shared".to_string());

let base_config = ZigBackendConfig {
extra_args: vec!["--base-arg".to_string()],
env: base_env,
debug_dir: Some(PathBuf::from("/base/debug")),
extra_input_globs: vec!["*.base".to_string()],
};

let mut target_env = indexmap::IndexMap::new();
target_env.insert("TARGET_VAR".to_string(), "target_value".to_string());
target_env.insert("SHARED_VAR".to_string(), "target_shared".to_string());

let target_config = ZigBackendConfig {
extra_args: vec!["--target-arg".to_string()],
env: target_env,
debug_dir: None,
extra_input_globs: vec!["*.target".to_string()],
};

let merged = base_config
.merge_with_target_config(&target_config)
.unwrap();

// extra_args should be completely overridden
assert_eq!(merged.extra_args, vec!["--target-arg".to_string()]);

// env should merge with target taking precedence
assert_eq!(merged.env.get("BASE_VAR"), Some(&"base_value".to_string()));
assert_eq!(
merged.env.get("TARGET_VAR"),
Some(&"target_value".to_string())
);
assert_eq!(
merged.env.get("SHARED_VAR"),
Some(&"target_shared".to_string())
);

// debug_dir should use base value
assert_eq!(merged.debug_dir, Some(PathBuf::from("/base/debug")));

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

#[test]
fn test_merge_with_empty_target_config() {
let mut base_env = indexmap::IndexMap::new();
base_env.insert("BASE_VAR".to_string(), "base_value".to_string());

let base_config = ZigBackendConfig {
extra_args: vec!["--base-arg".to_string()],
env: base_env,
debug_dir: Some(PathBuf::from("/base/debug")),
extra_input_globs: vec!["*.base".to_string()],
};

let empty_target_config = ZigBackendConfig::default();

let merged = base_config
.merge_with_target_config(&empty_target_config)
.unwrap();

// Should keep base values when target is empty
assert_eq!(merged.extra_args, vec!["--base-arg".to_string()]);
assert_eq!(merged.env.get("BASE_VAR"), Some(&"base_value".to_string()));
assert_eq!(merged.debug_dir, Some(PathBuf::from("/base/debug")));
assert_eq!(merged.extra_input_globs, vec!["*.base".to_string()]);
}

#[test]
fn test_merge_target_debug_dir_error() {
let base_config = ZigBackendConfig {
debug_dir: Some(PathBuf::from("/base/debug")),
..Default::default()
};

let target_config = ZigBackendConfig {
debug_dir: Some(PathBuf::from("/target/debug")),
..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("`debug_dir` cannot have a target specific value"));
}
}
Loading
Loading