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
15 changes: 9 additions & 6 deletions docs/source/kernel-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,16 @@ metadata. Currently the following top-level keys are supported:
- `provenance` (`dict`, optional): provenance of the build, used to flag
non-reproducible (dirty) builds. It contains two optional sub-objects:
- `kernel-builder`: the `kernel-builder` that produced the build, with its
`version` (`str`), the `sha` (`str`) of the `kernel-builder` source it was
built from (when known), and a `dirty` (`bool`) flag that is `true` when
`version` (`str`), the `commit` (`str`) of the `kernel-builder` source it
was built from (when known), and a `dirty` (`bool`) flag that is `true` when
`kernel-builder` was built from a source tree with uncommitted changes.
- `kernel`: the kernel source that was built, with its commit `sha` (`str`)
- `kernel`: the kernel source that was built, with its `commit` (`str`)
and a `dirty` (`bool`) flag that is `true` when the kernel source had
uncommitted changes.

A `commit` is the object identifier of the Git commit: the hexadecimal
encoding of its SHA-1 or SHA-256 hash.

When either `dirty` flag is set, the kernel was built from uncommitted
sources and cannot be reliably reproduced.

Expand All @@ -100,10 +103,10 @@ metadata. Currently the following top-level keys are supported:
> builds are not flagged as dirty. Local `create-pyproject` runs only
> consider changes to tracked files.

> **Note:** The kernel `sha`/`dirty` are captured at the moment
> `create-pyproject` runs, so they describe the source tree as it was *then*.
> **Note:** The kernel `commit`/`dirty` are captured at the moment
> `create-pyproject` runs, so they describe the source tree as it was _then_.
> Running `create-pyproject` and committing afterwards is bad practice: the
> recorded provenance keeps pointing at the pre-commit state (a stale `sha`,
> recorded provenance keeps pointing at the pre-commit state (a stale `commit`,
> and `dirty: true` if the tree was dirty) even though the committed source
> differs. Generate the metadata from the final, committed source instead.

Expand Down
11 changes: 6 additions & 5 deletions kernel-builder/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::PathBuf;

mod card;
mod check_abi;
use check_abi::{run_check_abi, CheckAbiArgs};

use clap::{Args, CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use eyre::{Context, Result};
use kernels_data::git::Oid;

mod card;
mod check_abi;
use check_abi::{run_check_abi, CheckAbiArgs};

mod completions;
use completions::print_completions;
Expand Down Expand Up @@ -195,7 +196,7 @@ enum Commands {
/// metadata. When absent, it is detected from the kernel's git
/// repository (used by Nix builds where the source has no `.git`).
#[arg(long)]
kernel_sha: Option<String>,
kernel_sha: Option<Oid>,

/// Mark the kernel source as having uncommitted changes in the build
/// metadata. Only meaningful together with `--kernel-sha`.
Expand Down
14 changes: 9 additions & 5 deletions kernel-builder/src/pyproject/common.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::path::PathBuf;
use std::str::FromStr;

use eyre::Result;
use itertools::Itertools;

use kernels_data::config::{Backend, Build};
use kernels_data::metadata::{GitHash, KernelBuilderVersion, Metadata, Provenance};
use kernels_data::git::{GitStatus, Oid};
use kernels_data::metadata::{KernelBuilderVersion, Metadata, Provenance};
Comment on lines -7 to +9

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.

Cool segregation. How does this handle metadata in the case where metadata also includes GitHash?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Old serialized GitHash will be deserialized into GitStatus.


use crate::pyproject::ops_identifier::KernelIdentifier;
use crate::pyproject::FileSet;
Expand All @@ -17,10 +19,12 @@ static ADD_BUILD_METADATA_PY: &str = include_str!("templates/torch/add_build_met
/// build sandboxes without a `.git` (e.g. Nix), the derivation supplies it via
/// `built`'s `BUILT_OVERRIDE_hf_kernel_builder_GIT_*` environment variables.
pub(crate) fn kernel_builder_version() -> KernelBuilderVersion {
let git = crate::built_info::GIT_COMMIT_HASH.map(|sha| GitHash {
sha: sha.to_owned(),
dirty: crate::built_info::GIT_DIRTY.unwrap_or(false),
});
let git = crate::built_info::GIT_COMMIT_HASH
.and_then(|sha| Oid::from_str(sha).ok())
.map(|commit| GitStatus {
commit,
dirty: crate::built_info::GIT_DIRTY.unwrap_or(false),
});
KernelBuilderVersion {
version: env!("CARGO_PKG_VERSION").to_owned(),
git,
Expand Down
11 changes: 6 additions & 5 deletions kernel-builder/src/pyproject/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::{

use eyre::{bail, Result};
use kernels_data::config::{Build, Framework};
use kernels_data::metadata::{GitHash, Provenance};
use kernels_data::git::{GitStatus, Oid};
use kernels_data::metadata::Provenance;
use minijinja::Environment;

use crate::{
Expand Down Expand Up @@ -49,7 +50,7 @@ pub fn create_pyproject(
target_dir: Option<PathBuf>,
force: bool,
unique_id: Option<String>,
kernel_sha: Option<String>,
kernel_sha: Option<Oid>,
kernel_dirty: bool,
) -> Result<()> {
let kernel_dir = check_or_infer_kernel_dir(kernel_dir)?;
Expand All @@ -62,11 +63,11 @@ pub fn create_pyproject(
// `kernel-builder` provenance is always the one baked into this binary at
// compile time.
let kernel = kernel_sha
.map(|sha| GitHash {
sha,
.map(|commit| GitStatus {
commit,
dirty: kernel_dirty,
})
.or_else(|| ops_identifier::git_hash(&kernel_dir));
.or_else(|| ops_identifier::git_status(&kernel_dir));
let provenance = Provenance {
kernel_builder: common::kernel_builder_version(),
kernel,
Expand Down
16 changes: 10 additions & 6 deletions kernel-builder/src/pyproject/ops_identifier.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::path::Path;
use std::str::FromStr;

use eyre::{Result, WrapErr};
use git2::Repository;
use kernels_data::config::Backend;
use kernels_data::metadata::GitHash;
use kernels_data::git::{GitStatus, Oid};
use rand::Rng;

pub fn random_identifier() -> String {
Expand All @@ -29,18 +30,21 @@ pub fn git_identifier(target_dir: impl AsRef<Path>) -> Result<String> {
Ok(if dirty { format!("{rev}_dirty") } else { rev })
}

pub fn git_hash(target_dir: impl AsRef<Path>) -> Option<GitHash> {
pub fn git_status(target_dir: impl AsRef<Path>) -> Option<GitStatus> {
let repo = Repository::discover(target_dir.as_ref()).ok()?;
let head = repo.head().ok()?;
let commit = head.peel_to_commit().ok()?;
let sha = commit.id().to_string();
let commit_oid = Oid::from_str(&commit.id().to_string()).ok()?;

let mut status_options = git2::StatusOptions::new();
status_options.include_untracked(false); // Ignore untracked files (like generated CMake files)
status_options.exclude_submodules(true);
let dirty = !repo.statuses(Some(&mut status_options)).ok()?.is_empty();

Some(GitHash { sha, dirty })
Some(GitStatus {
commit: commit_oid,
dirty,
})
}

/// Uniquely identifies a kernel for the purpose of ops-name generation.
Expand Down Expand Up @@ -98,8 +102,8 @@ mod tests {
}

#[test]
fn git_hash_is_none_in_non_git_dir() {
fn git_status_is_none_in_non_git_dir() {
let tmp = tempfile::tempdir().unwrap();
assert!(git_hash(tmp.path()).is_none());
assert!(git_status(tmp.path()).is_none());
}
}
2 changes: 1 addition & 1 deletion kernel-builder/src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ mod tests {
const METADATA_V3: &str = r#"{"name": "test-kernel", "id": "kernel_id", "version": 3, "license": "Apache-2.0", "python-depends": [], "backend": {"type": "cuda"}}"#;
const METADATA_V0: &str = r#"{"name": "test-kernel", "id": "kernel_id", "version": 0, "license": "Apache-2.0", "python-depends": [], "backend": {"type": "cuda"}}"#;

const METADATA_DIRTY: &str = r#"{"name": "test-kernel", "id": "kernel_id", "version": 1, "license": "Apache-2.0", "python-depends": [], "backend": {"type": "cuda"}, "provenance": {"kernel-builder": {"version": "0.1.0", "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "dirty": false}, "kernel": {"sha": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "dirty": true}}}"#;
const METADATA_DIRTY: &str = r#"{"name": "test-kernel", "id": "kernel_id", "version": 1, "license": "Apache-2.0", "python-depends": [], "backend": {"type": "cuda"}, "provenance": {"kernel-builder": {"version": "0.1.0", "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "dirty": false}, "kernel": {"commit": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "dirty": true}}}"#;

#[test]
fn test_dirty_variant_names() {
Expand Down
16 changes: 8 additions & 8 deletions kernels-data/bindings/python/kernels_data.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ __all__ = [
"BackendInfo",
"Provenance",
"DigestAlgorithm",
"GitHash",
"GitStatus",
"KernelBuilderVersion",
"KernelDependency",
"KernelName",
Expand Down Expand Up @@ -70,12 +70,12 @@ class BackendInfo:
def __repr__(self) -> str: ...

@final
class GitHash:
"""Git provenance (commit SHA and dirty state) of a source tree."""
class GitStatus:
"""The state of a git working tree."""

@property
def sha(self) -> str:
"""Full 40-character commit SHA."""
def commit(self) -> str:
"""Identifier of the `HEAD` commit, as lowercase hexadecimal digits."""
...

@property
Expand All @@ -95,8 +95,8 @@ class KernelBuilderVersion:
...

@property
def git(self) -> Optional[GitHash]:
"""Commit SHA and dirty state of the `kernel-builder` source, when known."""
def git(self) -> Optional[GitStatus]:
"""Git state of the `kernel-builder` source, when known."""
...

def __repr__(self) -> str: ...
Expand All @@ -111,7 +111,7 @@ class Provenance:
...

@property
def kernel(self) -> Optional[GitHash]:
def kernel(self) -> Optional[GitStatus]:
"""Git provenance of the kernel source that was built."""
...

Expand Down
41 changes: 23 additions & 18 deletions kernels-data/bindings/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::str::FromStr;

use kernels_data::config::{Backend, KernelDependency, KernelName, KernelVersion};
use kernels_data::digest::{Digest, DigestAlgorithm, DigestViolation};
use kernels_data::metadata::{BackendInfo, GitHash, KernelBuilderVersion, Metadata, Provenance};
use kernels_data::git::{GitStatus, Oid};
use kernels_data::metadata::{BackendInfo, KernelBuilderVersion, Metadata, Provenance};
use kernels_data::version::Version;
use pyo3::Bound as PyBound;
use pyo3::exceptions::{PyException, PyOSError, PyRuntimeError, PyValueError};
Expand Down Expand Up @@ -193,27 +194,27 @@ impl PyBackendInfo {
}
}

#[pyclass(name = "GitHash", frozen)]
#[pyclass(name = "GitStatus", frozen)]
#[derive(Clone, Debug)]
struct PyGitHash {
sha: String,
struct PyGitStatus {
commit: Oid,
dirty: bool,
}

impl From<GitHash> for PyGitHash {
fn from(g: GitHash) -> Self {
impl From<GitStatus> for PyGitStatus {
fn from(status: GitStatus) -> Self {
Self {
sha: g.sha,
dirty: g.dirty,
commit: status.commit,
dirty: status.dirty,
}
}
}

#[pymethods]
impl PyGitHash {
impl PyGitStatus {
#[getter]
fn sha(&self) -> &str {
&self.sha
fn commit(&self) -> &str {
self.commit.as_str()
}

#[getter]
Expand All @@ -222,15 +223,19 @@ impl PyGitHash {
}

fn __repr__(&self) -> String {
format!("GitHash(sha={:?}, dirty={})", self.sha, self.dirty)
format!(
"GitStatus(commit={:?}, dirty={})",
self.commit.as_str(),
self.dirty
)
}
}

#[pyclass(name = "KernelBuilderVersion", frozen)]
#[derive(Clone, Debug)]
struct PyKernelBuilderVersion {
version: String,
git: Option<PyGitHash>,
git: Option<PyGitStatus>,
}

impl From<KernelBuilderVersion> for PyKernelBuilderVersion {
Expand All @@ -249,9 +254,9 @@ impl PyKernelBuilderVersion {
&self.version
}

/// Commit SHA + dirty state of the `kernel-builder` source, when known.
/// Git state of the `kernel-builder` source, when known.
#[getter]
fn git(&self) -> Option<PyGitHash> {
fn git(&self) -> Option<PyGitStatus> {
self.git.clone()
}

Expand All @@ -270,7 +275,7 @@ impl PyKernelBuilderVersion {
#[derive(Clone, Debug)]
struct PyProvenance {
kernel_builder: PyKernelBuilderVersion,
kernel: Option<PyGitHash>,
kernel: Option<PyGitStatus>,
}

impl From<Provenance> for PyProvenance {
Expand All @@ -290,7 +295,7 @@ impl PyProvenance {
}

#[getter]
fn kernel(&self) -> Option<PyGitHash> {
fn kernel(&self) -> Option<PyGitStatus> {
self.kernel.clone()
}

Expand Down Expand Up @@ -745,7 +750,7 @@ fn kernels_data_py(m: &PyBound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyBackend>()?;
m.add_class::<PyBackendInfo>()?;
m.add_class::<PyProvenance>()?;
m.add_class::<PyGitHash>()?;
m.add_class::<PyGitStatus>()?;
m.add_class::<PyKernelBuilderVersion>()?;
m.add_class::<PyKernelName>()?;
m.add_class::<PyKernelVersion>()?;
Expand Down
Loading
Loading