Skip to content

Commit 8101411

Browse files
committed
Add hashes to metadata
1 parent 42d031c commit 8101411

7 files changed

Lines changed: 174 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

kernel-builder/src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ use init::{run_init, InitArgs};
2525
mod list_variants;
2626
use list_variants::list_variants;
2727

28+
mod sign;
29+
use sign::sign;
30+
2831
mod upload;
2932
use upload::{run_upload, RepoTypeArg, UploadArgs};
3033

@@ -201,6 +204,12 @@ enum Commands {
201204
arch: bool,
202205
},
203206

207+
/// Sign the builds of a kernel.
208+
Sign {
209+
#[arg(name = "KERNEL_DIR")]
210+
kernel_dir: Option<PathBuf>,
211+
},
212+
204213
/// Spawn a kernel test shell.
205214
Testshell {
206215
#[arg(name = "KERNEL_DIR")]
@@ -362,6 +371,7 @@ fn main() -> Result<()> {
362371
variant,
363372
),
364373
Commands::ListVariants { kernel_dir, arch } => list_variants(kernel_dir, arch),
374+
Commands::Sign { kernel_dir } => sign(kernel_dir),
365375
Commands::Testshell {
366376
kernel_dir,
367377
variant,

kernel-builder/src/pyproject/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub fn write_metadata(
4949
archs: None,
5050
backend_type: *backend,
5151
},
52+
source_digest: None,
5253
};
5354

5455
serde_json::to_writer_pretty(writer, &metadata)?;

kernel-builder/src/sign.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use std::{
2+
fs::File,
3+
io::{BufReader, BufWriter},
4+
path::PathBuf,
5+
};
6+
7+
use eyre::{Context, Result};
8+
use kernels_data::metadata::{DigestAlgorithm, Metadata, SourceDigest};
9+
10+
use crate::util::{check_or_infer_kernel_dir, discover_variants};
11+
12+
pub fn sign(kernel_dir: Option<PathBuf>) -> Result<()> {
13+
let kernel_dir = check_or_infer_kernel_dir(kernel_dir)?;
14+
let (build_dir, variants) = discover_variants(&kernel_dir)?;
15+
16+
for variant in variants {
17+
let variant_path = build_dir.join(&variant);
18+
let metadata_path = variant_path.join("metadata.json");
19+
20+
eprintln!(
21+
"Signing variant `{}`...",
22+
variant.file_name().unwrap().to_string_lossy()
23+
);
24+
25+
let f = File::open(&metadata_path).context(format!(
26+
"Cannot open `{}` for reading",
27+
metadata_path.to_string_lossy()
28+
))?;
29+
let mut metadata: Metadata = serde_json::from_reader(BufReader::new(f))?;
30+
31+
let source_digest = SourceDigest::update_hashes(DigestAlgorithm::SHA256, &variant_path)?;
32+
metadata.source_digest = Some(source_digest);
33+
34+
let f = File::create(&metadata_path).context(format!(
35+
"Cannot open `{}` for writing file hashes",
36+
metadata_path.to_string_lossy()
37+
))?;
38+
serde_json::to_writer_pretty(BufWriter::new(f), &metadata).context(format!(
39+
"Cannot write updated metadata to `{}`",
40+
metadata_path.to_string_lossy()
41+
))?;
42+
}
43+
44+
Ok(())
45+
}

kernel-builder/src/util.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,41 @@ pub(crate) fn check_or_infer_target_dir(
4949
}
5050
}
5151

52+
/// Discover build variant directories (contain `metadata.json`).
53+
/// Checks `result` symlink (Nix store output) first, then falls back to `build/`.
54+
pub(crate) fn discover_variants(kernel_dir: &Path) -> Result<(PathBuf, Vec<PathBuf>)> {
55+
let candidates = [
56+
kernel_dir.join("result"),
57+
kernel_dir.join("build"),
58+
kernel_dir.to_path_buf(),
59+
];
60+
61+
for candidate in &candidates {
62+
if !candidate.is_dir() {
63+
continue;
64+
}
65+
66+
let mut variants: Vec<PathBuf> = fs::read_dir(candidate)
67+
.wrap_err_with(|| format!("Cannot read `{}`", candidate.display()))?
68+
.filter_map(|e| e.ok())
69+
.map(|e| e.path())
70+
.filter(|p| p.is_dir() && p.join("metadata.json").is_file())
71+
.collect();
72+
73+
if !variants.is_empty() {
74+
variants.sort();
75+
return Ok((candidate.clone(), variants));
76+
}
77+
}
78+
79+
bail!(
80+
"No build variants found in `{}`, `{}`, or `{}`",
81+
candidates[0].display(),
82+
candidates[1].display(),
83+
candidates[2].display(),
84+
);
85+
}
86+
5287
pub(crate) fn parse_and_validate(kernel_dir: impl AsRef<Path>) -> Result<BuildCompat> {
5388
let build_toml = kernel_dir.as_ref().join("build.toml");
5489
let mut toml_data = String::new();

kernels-data/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ license = "Apache-2.0"
88
repository = "https://github.com/huggingface/kernels"
99

1010
[dependencies]
11+
base64 = "0.22"
12+
digest = "0.11"
1113
eyre = "0.6.12"
1214
itertools = "0.13"
1315
regex = "1"
1416
serde = { version = "1", features = ["derive"] }
1517
serde_json = "1"
1618
serde-value = "0.7"
19+
sha2 = "0.11"
20+
walkdir = "2"
1721
thiserror = "1"
1822
url = { version = "2", features = ["serde"] }

kernels-data/src/metadata.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
use std::str::FromStr;
2+
use std::{collections::BTreeMap, fs, path::Path};
23

3-
use eyre::Result;
4+
use base64::prelude::{BASE64_STANDARD, Engine as _};
5+
use digest::{Digest, DynDigest};
6+
use eyre::{Context, Result};
47
use serde::{Deserialize, Serialize};
8+
use sha2::{Sha256, Sha512};
9+
use walkdir::WalkDir;
510

611
use crate::config::{Backend, KernelName};
712

@@ -26,6 +31,75 @@ pub struct Metadata {
2631
pub upstream: Option<url::Url>,
2732
pub python_depends: Vec<String>,
2833
pub backend: BackendInfo,
34+
#[serde(skip_serializing_if = "Option::is_none")]
35+
pub source_digest: Option<SourceDigest>,
36+
}
37+
38+
#[derive(Debug, Deserialize, Serialize)]
39+
pub struct SourceDigest {
40+
algorithm: DigestAlgorithm,
41+
files: BTreeMap<String, String>,
42+
}
43+
44+
impl SourceDigest {
45+
pub fn update_hashes(
46+
digest_algorithm: DigestAlgorithm,
47+
variant_path: impl AsRef<Path>,
48+
) -> Result<Self> {
49+
let variant_path = variant_path.as_ref();
50+
51+
let mut files = BTreeMap::new();
52+
for entry in WalkDir::new(variant_path) {
53+
let entry = entry.wrap_err("Failed to read directory entry for hashing")?;
54+
if !entry.file_type().is_file()
55+
|| entry.path().extension().and_then(|e| e.to_str()) == Some("pyc")
56+
{
57+
continue;
58+
}
59+
60+
let path = entry.path();
61+
62+
// Read and hash contents.
63+
let contents = fs::read(path)
64+
.wrap_err_with(|| format!("Cannot read `{}` for hashing", path.display()))?;
65+
let mut hasher: Box<dyn DynDigest> = digest_algorithm.into();
66+
hasher.update(&contents);
67+
68+
let relative_path = path.strip_prefix(variant_path).wrap_err_with(|| {
69+
format!("Cannot strip prefix from `{}` for hashing", path.display())
70+
})?;
71+
72+
// Normalize Windows directory separators.
73+
let relative_path_str = relative_path.to_string_lossy().replace('\\', "/");
74+
75+
let hash_base64 = BASE64_STANDARD.encode(hasher.finalize_reset());
76+
77+
files.insert(relative_path_str, hash_base64);
78+
}
79+
80+
Ok(SourceDigest {
81+
files,
82+
algorithm: digest_algorithm,
83+
})
84+
}
85+
}
86+
87+
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
88+
pub enum DigestAlgorithm {
89+
#[serde(rename = "sha256")]
90+
SHA256,
91+
92+
#[serde(rename = "sha512")]
93+
SHA512,
94+
}
95+
96+
impl From<DigestAlgorithm> for Box<dyn DynDigest> {
97+
fn from(digest_algorithm: DigestAlgorithm) -> Box<dyn DynDigest> {
98+
match digest_algorithm {
99+
DigestAlgorithm::SHA256 => Box::new(Sha256::new()),
100+
DigestAlgorithm::SHA512 => Box::new(Sha512::new()),
101+
}
102+
}
29103
}
30104

31105
impl Metadata {

0 commit comments

Comments
 (0)