11use 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 } ;
47use serde:: { Deserialize , Serialize } ;
8+ use sha2:: { Sha256 , Sha512 } ;
9+ use walkdir:: WalkDir ;
510
611use 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
31105impl Metadata {
0 commit comments