use super::crate_spec::CrateSpec;
use super::system::NixSystem;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NixFlakeConfig {
pub crates: Vec<CrateSpec>,
pub rust_version: String,
pub features: HashMap<String, Vec<String>>,
pub systems: Vec<NixSystem>,
pub description: String,
pub gpu_support: bool,
pub include_dev_shell: bool,
pub include_checks: bool,
}
impl NixFlakeConfig {
pub fn new(description: impl Into<String>) -> Self {
Self {
crates: Vec::new(),
rust_version: "1.75.0".to_string(),
features: HashMap::new(),
systems: NixSystem::all(),
description: description.into(),
gpu_support: false,
include_dev_shell: true,
include_checks: true,
}
}
pub fn sovereign_stack() -> Self {
let mut config = Self::new("PAIML Sovereign ML Stack - Air-gapped deployment ready");
config.crates = vec![
CrateSpec::crates_io("trueno", "0.2"),
CrateSpec::crates_io("aprender", "0.1"),
CrateSpec::crates_io("renacer", "0.1"),
CrateSpec::crates_io("entrenar", "0.2"),
CrateSpec::crates_io("realizar", "0.1"),
];
config.features.insert("trueno".to_string(), vec!["simd".to_string()]);
config.features.insert("entrenar".to_string(), vec!["full".to_string()]);
config.rust_version = "1.75.0".to_string();
config.include_dev_shell = true;
config.include_checks = true;
config
}
pub fn add_crate(mut self, spec: CrateSpec) -> Self {
self.crates.push(spec);
self
}
pub fn with_features(
mut self,
crate_name: impl Into<String>,
features: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.features.insert(crate_name.into(), features.into_iter().map(Into::into).collect());
self
}
pub fn with_rust_version(mut self, version: impl Into<String>) -> Self {
self.rust_version = version.into();
self
}
pub fn with_systems(mut self, systems: Vec<NixSystem>) -> Self {
self.systems = systems;
self
}
pub fn with_gpu_support(mut self, enabled: bool) -> Self {
self.gpu_support = enabled;
self
}
pub fn generate_flake_nix(&self) -> String {
let systems_list: Vec<&str> = self.systems.iter().map(NixSystem::as_str).collect();
let systems_str =
systems_list.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(" ");
let crate_names: Vec<&str> = self.crates.iter().map(|c| c.name.as_str()).collect();
let mut flake = String::new();
flake.push_str(&format!(
r#"# Nix Flake for PAIML Sovereign Stack
# {}
# Generated by entrenar sovereign deployment tooling
{{
description = "{}";
inputs = {{
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay = {{
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
}};
crane = {{
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
}};
flake-utils.url = "github:numtide/flake-utils";
}};
outputs = {{ self, nixpkgs, rust-overlay, crane, flake-utils, ... }}:
flake-utils.lib.eachSystem [ {} ] (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {{
inherit system overlays;
}};
rustToolchain = pkgs.rust-bin.stable."{}" .default.override {{
extensions = [ "rust-src" "rust-analyzer" ];
}};
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
"#,
chrono::Utc::now().format("%Y-%m-%d"),
self.description,
systems_str,
self.rust_version,
));
flake.push_str(" buildInputs = with pkgs; [\n");
flake.push_str(" openssl\n");
flake.push_str(" pkg-config\n");
if self.gpu_support {
flake.push_str(" # GPU support\n");
flake.push_str(" cudatoolkit\n");
flake.push_str(" cudnn\n");
}
flake.push_str(" ];\n\n");
flake.push_str(&format!(
r" commonArgs = {{
src = craneLib.cleanCargoSource ./.;
inherit buildInputs;
nativeBuildInputs = with pkgs; [ pkg-config ];
}};
# Build dependencies first (for caching)
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
# Main packages
{}",
self.generate_package_definitions(&crate_names),
));
flake.push_str(&format!(
r"
in {{
packages = {{
{} default = {};
}};
",
self.generate_packages_attr(&crate_names),
crate_names.first().unwrap_or(&"entrenar"),
));
if self.include_dev_shell {
flake.push_str(&format!(
r"
devShells.default = pkgs.mkShell {{
inputsFrom = [ {} ];
buildInputs = with pkgs; [
rustToolchain
rust-analyzer
cargo-watch
cargo-edit
cargo-expand
];
}};
",
crate_names.first().unwrap_or(&"entrenar"),
));
}
if self.include_checks {
flake.push_str(
r#"
checks = {
clippy = craneLib.cargoClippy (commonArgs // {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- -D warnings";
});
test = craneLib.cargoNextest (commonArgs // {
inherit cargoArtifacts;
partitions = 1;
partitionType = "count";
});
fmt = craneLib.cargoFmt {
src = craneLib.cleanCargoSource ./.;
};
};
"#,
);
}
flake.push_str(" });\n}\n");
flake
}
fn generate_package_definitions(&self, crate_names: &[&str]) -> String {
use std::fmt::Write;
let mut result = String::new();
for name in crate_names {
let features = self
.features
.get(*name)
.map(|f| {
let feature_list = f.join(",");
format!(r#"cargoExtraArgs = "--features {feature_list}";"#)
})
.unwrap_or_default();
let _ = writeln!(
&mut result,
" {name} = craneLib.buildPackage (commonArgs // {{\n\
inherit cargoArtifacts;\n\
pname = \"{name}\";\n\
{features}\n\
}});\n"
);
}
result
}
fn generate_packages_attr(&self, crate_names: &[&str]) -> String {
use std::fmt::Write;
let mut result = String::new();
for name in crate_names {
let _ = writeln!(&mut result, " {name} = {name};");
}
result
}
pub fn generate_cachix_config(&self) -> String {
format!(
r#"# Cachix configuration for PAIML Sovereign Stack
# Push: cachix push paiml $(nix-build)
# Use: cachix use paiml
{{
"name": "paiml",
"signing_key_path": "$HOME/.config/cachix/cachix.dhall",
"binary_caches": [
{{
"url": "https://paiml.cachix.org",
"public_signing_keys": ["paiml.cachix.org-1:..."]
}}
],
"crates": {:?},
"rust_version": "{}",
"generated": "{}"
}}
"#,
self.crates.iter().map(|c| &c.name).collect::<Vec<_>>(),
self.rust_version,
chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ"),
)
}
pub fn minimal_flake(crate_name: &str, crate_path: &str) -> String {
format!(
r#"{{
description = "{crate_name} - PAIML component";
inputs = {{
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
crane.url = "github:ipetkov/crane";
flake-utils.url = "github:numtide/flake-utils";
}};
outputs = {{ self, nixpkgs, rust-overlay, crane, flake-utils, ... }}:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {{
inherit system;
overlays = [ (import rust-overlay) ];
}};
craneLib = crane.mkLib pkgs;
in {{
packages.default = craneLib.buildPackage {{
src = ./{crate_path};
pname = "{crate_name}";
}};
devShells.default = pkgs.mkShell {{
inputsFrom = [ self.packages.${{system}}.default ];
buildInputs = with pkgs; [ rust-analyzer ];
}};
}});
}}
"#
)
}
}
impl Default for NixFlakeConfig {
fn default() -> Self {
Self::sovereign_stack()
}
}