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
4,400 changes: 3,929 additions & 471 deletions kernel-builder/Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions kernel-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ base32 = "0.5"
clap = { version = "4", features = ["derive"] }
eyre = "0.6.12"
git2 = "0.20"
huggingface-hub = { git = "https://github.com/huggingface/huggingface_hub_rust.git", package = "huggingface-hub", features = ["xet"] }
tokio = { version = "1", features = ["rt"] }
itertools = "0.13"
minijinja = "2.5"
minijinja-embed = "2.5"
Expand Down
89 changes: 89 additions & 0 deletions kernel-builder/src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::{fmt::Display, path::PathBuf, str::FromStr};

use clap::Args;
use eyre::{bail, Context, Result};

#[derive(Clone, Debug, Default)]
pub enum BuildTarget {
#[default]
BuildAndCopy,
BuildAndUpload,
Ci,
Default,
}

impl Display for BuildTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuildTarget::BuildAndCopy => write!(f, "build-and-copy"),
BuildTarget::BuildAndUpload => write!(f, "build-and-upload"),
BuildTarget::Ci => write!(f, "ci"),
BuildTarget::Default => write!(f, "default"),
}
}
}

impl FromStr for BuildTarget {
type Err = String;

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"build-and-copy" => Ok(BuildTarget::BuildAndCopy),
"build-and-upload" => Ok(BuildTarget::BuildAndUpload),
"ci" => Ok(BuildTarget::Ci),
"default" => Ok(BuildTarget::Default),
_ => Err(format!(
"unknown target `{s}`, expected one of: build-and-copy, build-and-upload, ci, default"
)),
}
}
}

#[derive(Debug, Args)]
pub struct BuildArgs {
/// Directory of the kernel project (defaults to current directory).
#[arg(value_name = "KERNEL_DIR", default_value = ".")]
pub kernel_dir: PathBuf,

/// Nix flake target to run.
#[arg(long, default_value = "build-and-copy")]
pub target: BuildTarget,
Copy link
Member

@danieldk danieldk Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think top-level

kernel-builder build
kernel-builder build-and-copy
kernel-builder build-and-upload

are nicer, easier to discover. I think we can use #[command(flatten)] to share arguments between different build commands, something like:

// ...
Build {
  #[command(flatten)]
  pub common_build_args: CommonBuildArgs,
  // ...
}


/// Maximum number of Nix build jobs.
#[arg(long, default_value = "4")]
pub max_jobs: u32,

/// Number of CPU cores per build job.
#[arg(long, default_value = "4")]
pub cores: u32,

/// Additional arguments passed through to `nix run`.
#[arg(last = true)]
pub nix_args: Vec<String>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we want this?

}

pub fn run_build(args: BuildArgs) -> Result<()> {
let flake_ref = format!(".#{}", args.target);

let mut cmd = std::process::Command::new("nix");
cmd.args([
"run",
"-L",
"--max-jobs",
&args.max_jobs.to_string(),
"--cores",
&args.cores.to_string(),
]);
cmd.args(&args.nix_args);
cmd.arg(&flake_ref);
cmd.current_dir(&args.kernel_dir);

let status = cmd
.status()
.wrap_err("Cannot run `nix`. Is Nix installed?")?;

if !status.success() {
bail!("Build failed with exit code {}", status.code().unwrap_or(1));
}
Ok(())
}
11 changes: 11 additions & 0 deletions kernel-builder/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,17 @@ impl Backend {
Backend::Xpu,
]
}

pub const fn as_str(&self) -> &'static str {
match self {
Backend::Cpu => "cpu",
Backend::Cuda => "cuda",
Backend::Metal => "metal",
Backend::Neuron => "neuron",
Backend::Rocm => "rocm",
Backend::Xpu => "xpu",
}
}
}

impl Display for Backend {
Expand Down
58 changes: 58 additions & 0 deletions kernel-builder/src/hf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::{fs, path::PathBuf};

use eyre::{Context, Result};

/// Build a tokio runtime for one-shot async calls.
pub fn runtime() -> Result<tokio::runtime::Runtime> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("Cannot create async runtime")
}

/// Build an HF API client.
pub fn api() -> Result<huggingface_hub::HfApi> {
huggingface_hub::HfApi::new().context("Cannot create Hugging Face API client")
}

/// Resolve the HF username of the currently logged-in user via `whoami`.
pub fn whoami_username() -> Result<String> {
let rt = runtime()?;
let api = api()?;

rt.block_on(async { api.whoami().await.map(|user| user.username) })
.map_err(|_| {
eyre::eyre!(
"Not logged in to Hugging Face. Run `hf auth login` first, \
or use --name <owner/repo> to skip auto-detection."
)
})
}

/// Resolve the HF access token using the standard resolution order:
/// `HF_TOKEN` env → `HF_TOKEN_PATH` file → `$HF_HOME/token` → `~/.cache/huggingface/token`.
pub fn token() -> Option<String> {
if let Ok(token) = std::env::var("HF_TOKEN") {
if !token.is_empty() {
return Some(token);
}
}

let token_path = std::env::var("HF_TOKEN_PATH")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let hf_home = std::env::var("HF_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.unwrap_or_default();
PathBuf::from(home).join(".cache/huggingface")
});
hf_home.join("token")
});

fs::read_to_string(token_path)
.ok()
.map(|t| t.trim().to_owned())
}
Loading
Loading