Skip to content
Merged
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
7 changes: 3 additions & 4 deletions kernel-builder/src/card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ use std::{
};

use eyre::{bail, Context, Result};
use kernels_data::config::Build;
use minijinja::{context, Environment};
use rustpython_parser::{ast, Parse};

use kernels_data::config::Build;

use crate::util::{check_or_infer_kernel_dir, parse_build};
use crate::util::check_or_infer_kernel_dir;

fn extract_all(kernel_dir: &Path, module_name: &str) -> Option<Vec<String>> {
let init_path = ["torch-ext", "tvm-ffi-ext"]
Expand Down Expand Up @@ -147,7 +146,7 @@ fn render_card(build: &Build, kernel_dir: &Path) -> Result<String> {

pub fn fill_card(kernel_dir: Option<PathBuf>, output: Option<PathBuf>) -> Result<()> {
let kernel_dir = check_or_infer_kernel_dir(kernel_dir)?;
let build = parse_build(&kernel_dir)?;
let build = Build::open(&kernel_dir)?;
let content = render_card(&build, &kernel_dir)?;

match output {
Expand Down
11 changes: 4 additions & 7 deletions kernel-builder/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::path::PathBuf;
use clap::{Args, CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use eyre::{Context, Result};
use kernels_data::config::{v5, Build, BuildCompat};
use kernels_data::git::Oid;

mod card;
Expand Down Expand Up @@ -37,20 +38,16 @@ use upload::{run_upload, RepoTypeArg, UploadArgs};
mod pyproject;
use pyproject::{clean_pyproject, create_pyproject};

use kernels_data::config::{v5, Build, BuildCompat};

mod nix;

mod skills;

mod util;
use util::{check_or_infer_kernel_dir, parse_and_validate};
use util::check_or_infer_kernel_dir;

mod validate_builds;
use validate_builds::check_builds;

use crate::util::parse_and_validate_compat;

/// Build-time metadata gathered by the [`built`](https://crates.io/crates/built)
/// crate (see `build.rs`), including the `kernel-builder` git provenance that is
/// recorded in the build metadata of the kernels it builds.
Expand Down Expand Up @@ -470,13 +467,13 @@ fn main() -> Result<()> {

fn check_config(kernel_dir: Option<PathBuf>) -> Result<()> {
let kernel_dir = check_or_infer_kernel_dir(kernel_dir)?;
parse_and_validate(kernel_dir)?;
Build::open(kernel_dir)?;
Ok(())
}

fn update_build(kernel_dir: Option<PathBuf>) -> Result<()> {
let kernel_dir = check_or_infer_kernel_dir(kernel_dir)?;
let build_compat: BuildCompat = parse_and_validate_compat(&kernel_dir)?;
let build_compat = BuildCompat::open(&kernel_dir)?;

if matches!(build_compat, BuildCompat::V5(_)) {
return Ok(());
Expand Down
6 changes: 3 additions & 3 deletions kernel-builder/src/pyproject/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use minijinja::Environment;

use crate::{
pyproject::ops_identifier::KernelIdentifier,
util::{check_or_infer_kernel_dir, check_or_infer_target_dir, parse_build},
util::{check_or_infer_kernel_dir, check_or_infer_target_dir},
};

pub(crate) mod common;
Expand Down Expand Up @@ -55,7 +55,7 @@ pub fn create_pyproject(
) -> Result<()> {
let kernel_dir = check_or_infer_kernel_dir(kernel_dir)?;
let target_dir = check_or_infer_target_dir(&kernel_dir, target_dir)?;
let build = parse_build(&kernel_dir)?;
let build = Build::open(&kernel_dir)?;

// Assemble build provenance. Prefer an explicitly provided kernel git
// provenance (e.g. passed by Nix builds, where the source tree has no
Expand Down Expand Up @@ -89,7 +89,7 @@ pub fn clean_pyproject(
) -> Result<()> {
let kernel_dir = check_or_infer_kernel_dir(kernel_dir)?;
let target_dir = check_or_infer_target_dir(&kernel_dir, target_dir)?;
let build = parse_build(&kernel_dir)?;
let build = Build::open(&kernel_dir)?;
// Provenance is irrelevant when computing the set of files to clean.
let kernel_id = KernelIdentifier::new(&kernel_dir, build.general.name.python_name(), unique_id);

Expand Down
5 changes: 3 additions & 2 deletions kernel-builder/src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ use hf_hub::{
HFError, HFRepositorySync, RepoType, RepoTypeKernel, RepoTypeModel,
};
use indicatif::{ProgressBar, ProgressStyle};
use kernels_data::config::Build;
use kernels_data::metadata::Metadata;
use serde::Serialize;
use walkdir::WalkDir;

use crate::{
hf::{self, repo_handle},
util::{check_or_infer_kernel_dir, discover_variants, parse_build},
util::{check_or_infer_kernel_dir, discover_variants},
};

/// Bridges `ProgressHandler` events to an `indicatif::ProgressBar`.
Expand Down Expand Up @@ -146,7 +147,7 @@ fn get_repo_and_branch(
branch: Option<String>,
variants: &[PathBuf],
) -> Result<(String, Option<String>)> {
let build = parse_build(kernel_dir);
let build = Build::open(kernel_dir);

let build_branch = build
.as_ref()
Expand Down
43 changes: 1 addition & 42 deletions kernel-builder/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
use std::env::current_dir;
use std::fs::{self, File};
use std::io::Read;
use std::fs;
use std::path::{Path, PathBuf};

use eyre::{bail, ensure, Context, Result};

use kernels_data::config::{Build, BuildCompat, CurrentConfig};

pub(crate) fn parse_build(kernel_dir: impl AsRef<Path>) -> Result<Build> {
let build_compat = parse_and_validate(kernel_dir)?;
Ok(build_compat.into())
}

pub(crate) fn check_or_infer_kernel_dir(kernel_dir: Option<impl AsRef<Path>>) -> Result<PathBuf> {
match kernel_dir {
Some(kernel_dir) => {
Expand Down Expand Up @@ -45,39 +37,6 @@ pub(crate) fn check_or_infer_target_dir(
}
}

pub(crate) fn parse_and_validate(kernel_dir: impl AsRef<Path>) -> Result<CurrentConfig> {
// Only v4 is auto-upgraded to v5 on load; older editions must be migrated
// explicitly with `update-build`.
match parse_and_validate_compat(kernel_dir)? {
BuildCompat::V5(build) => Ok(build),
BuildCompat::V4(build) => {
eprintln!(
"⚠️ build.toml uses the legacy v4 format; upgrading to edition 5 in memory. \
Run `kernel-builder update-build` to persist the upgrade."
);
Ok(Build::from(build).into())
}
BuildCompat::V3(_) => bail!(
"build.toml uses an unsupported legacy format; migrate it with \
`kernel-builder update-build`"
),
}
}

pub(crate) fn parse_and_validate_compat(kernel_dir: impl AsRef<Path>) -> Result<BuildCompat> {
let build_toml = kernel_dir.as_ref().join("build.toml");
let mut toml_data = String::new();
File::open(&build_toml)
.wrap_err_with(|| format!("Cannot open {} for reading", build_toml.to_string_lossy()))?
.read_to_string(&mut toml_data)
.wrap_err_with(|| format!("Cannot read from {}", build_toml.to_string_lossy()))?;

let config: BuildCompat = toml::from_str(&toml_data)
.wrap_err_with(|| format!("Cannot parse TOML in {}", build_toml.to_string_lossy()))?;

Ok(config)
}

/// Discover build variant directories (contain `metadata.json`).
/// Checks `result` symlink (Nix store output) first, then falls back to `build/`.
pub(crate) fn discover_variants(kernel_dir: &Path) -> Result<(PathBuf, Vec<PathBuf>)> {
Expand Down
4 changes: 2 additions & 2 deletions kernels-data/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde-value = "0.7"
sha2 = "0.11"
walkdir = "2"
thiserror = "1"
toml = "0.8"
url = { version = "2", features = ["serde"] }
walkdir = "2"

[dev-dependencies]
tempfile = "3"
toml = "0.8"
35 changes: 35 additions & 0 deletions kernels-data/bindings/python/kernels_data.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ from typing import Optional, final
__all__ = [
"Backend",
"BackendInfo",
"Build",
"General",
"Provenance",
"DigestAlgorithm",
"GitStatus",
Expand Down Expand Up @@ -324,6 +326,39 @@ class KernelDependency:
def __eq__(self, value: object, /) -> bool: ...
def __hash__(self) -> int: ...

@final
class General:
"""General kernel configuration common to all backends."""

@property
def backends(self) -> list[Backend]:
"""Backends the kernel supports."""
...

def __repr__(self) -> str: ...

@final
class Build:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice!

"""Parsed and validated `build.toml` configuration for a kernel."""

@staticmethod
def open(kernel_dir: os.PathLike[str] | str) -> "Build":
"""Parse and validate the `build.toml` in `kernel_dir`.

Raises:
ValueError: If the build configuration cannot be parsed or validated.
"""
...

@property
def general(self) -> General:
"""General kernel configuration."""
...

def all_kernel_depends(self, backend: Backend) -> list[KernelDependency]:
"""Get the general + backend-specific kernel dependencies for `backend`."""
...

@final
class Metadata:
"""Parsed `metadata.json` for a kernel build variant."""
Expand Down
74 changes: 74 additions & 0 deletions kernels-data/bindings/python/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::path::PathBuf;

use kernels_data::config::{Build, General};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;

use crate::{PyBackend, PyKernelDependency};

/// General kernel configuration common to all backends.
#[pyclass(name = "General", frozen)]
#[derive(Clone, Debug)]
pub(crate) struct PyGeneral {
backends: Vec<PyBackend>,
}

impl From<&General> for PyGeneral {
fn from(general: &General) -> Self {
Self {
backends: general.backends.iter().copied().map(Into::into).collect(),
}
}
}

#[pymethods]
impl PyGeneral {
#[getter]
fn backends(&self) -> Vec<PyBackend> {
self.backends.clone()
}

fn __repr__(&self) -> String {
format!("General(backends={:?})", self.backends)
}
}

/// Parsed and validated `build.toml` configuration for a kernel.
#[pyclass(name = "Build", frozen)]
pub(crate) struct PyBuild {
inner: Build,
}

#[pymethods]
impl PyBuild {
/// Parse and validate the `build.toml` in `kernel_dir`.
///
/// Raises `ValueError` if the build configuration cannot be parsed or
/// validated.
#[staticmethod]
fn open(kernel_dir: PathBuf) -> PyResult<PyBuild> {
Build::open(&kernel_dir)
.map(|inner| PyBuild { inner })
.map_err(|err| {
PyValueError::new_err(format!(
"Cannot parse build configuration in `{}`: {err:#}",
kernel_dir.display()
))
})
}

#[getter]
fn general(&self) -> PyGeneral {
PyGeneral::from(&self.inner.general)
}

/// Get the general + backend-specific kernel dependencies for `backend`.
fn all_kernel_depends(&self, backend: PyBackend) -> Vec<PyKernelDependency> {
self.inner
.general
.all_kernel_depends(backend.into())
.into_iter()
.map(Into::into)
.collect()
}
}
10 changes: 8 additions & 2 deletions kernels-data/bindings/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ use pyo3::Bound as PyBound;
use pyo3::exceptions::{PyException, PyOSError, PyRuntimeError, PyValueError};
use pyo3::prelude::*;

mod config;

use config::{PyBuild, PyGeneral};

/// A dotted numeric version (e.g. `12.8.0`). Trailing zeros are stripped
/// during normalization.
#[pyclass(name = "Version", frozen, eq, ord, hash)]
Expand Down Expand Up @@ -76,7 +80,7 @@ impl PyKernelName {
/// Kernel backend (hardware target).
#[pyclass(name = "Backend", eq, frozen, hash)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum PyBackend {
pub(crate) enum PyBackend {
#[pyo3(name = "CANN")]
Cann,
#[pyo3(name = "CPU")]
Expand Down Expand Up @@ -349,7 +353,7 @@ impl PyKernelVersion {
/// A dependency on another kernel.
#[pyclass(name = "KernelDependency", frozen, eq, hash)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct PyKernelDependency {
pub(crate) struct PyKernelDependency {
repo_id: String,
version: PyKernelVersion,
}
Expand Down Expand Up @@ -755,6 +759,8 @@ fn kernels_data_py(m: &PyBound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyKernelName>()?;
m.add_class::<PyKernelVersion>()?;
m.add_class::<PyKernelDependency>()?;
m.add_class::<PyGeneral>()?;
m.add_class::<PyBuild>()?;
m.add_class::<PyMetadata>()?;
m.add_class::<PyVersion>()?;
m.add_class::<PyDigestAlgorithm>()?;
Expand Down
Loading
Loading