Skip to content
Open
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
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