11package org .photonvision .tools ;
22
33import com .google .gson .GsonBuilder ;
4+ import java .io .BufferedInputStream ;
45import java .io .File ;
56import java .io .FileInputStream ;
67import java .io .IOException ;
8+ import java .io .OutputStream ;
79import java .nio .charset .Charset ;
810import java .nio .file .Files ;
911import java .nio .file .Path ;
12+ import java .security .DigestInputStream ;
1013import java .security .MessageDigest ;
1114import java .security .NoSuchAlgorithmException ;
12- import java .util .ArrayList ;
1315import java .util .HashMap ;
16+ import java .util .HexFormat ;
1417import java .util .List ;
1518import java .util .Map ;
1619import javax .inject .Inject ;
17- import org .codehaus .groovy .runtime .EncodingGroovyMethods ;
1820import org .gradle .api .DefaultTask ;
1921import org .gradle .api .file .Directory ;
2022import org .gradle .api .file .DirectoryProperty ;
2628import org .gradle .api .tasks .TaskAction ;
2729
2830public class HashNativeResources extends DefaultTask {
31+
32+ /**
33+ * Architecture-specific information containing file hashes for a specific CPU architecture (e.g.,
34+ * x86-64, arm64).
35+ */
36+ public record ArchInfo (Map <String , String > fileHashes ) {}
37+
38+ /**
39+ * Platform-specific information containing architectures for a specific OS platform (e.g., linux,
40+ * windows).
41+ */
42+ public record PlatformInfo (Map <String , ArchInfo > architectures ) {}
43+
44+ /** Overall resource information to be serialized */
45+ public record ResourceInformation (
46+ // Combined MD5 hash of all native resource files
47+ String hash ,
48+ // Platform-specific native libraries organized by platform then architecture
49+ Map <String , PlatformInfo > platforms ,
50+ // List of supported versions for these native resources
51+ List <String > versions ) {}
52+
2953 private final DirectoryProperty inputDirectory ;
3054 private final RegularFileProperty hashFile ;
3155 private final RegularFileProperty versionsInput ;
@@ -55,16 +79,13 @@ public HashNativeResources() {
5579
5680 @ TaskAction
5781 public void execute () throws NoSuchAlgorithmException , IOException {
58- MessageDigest hash = MessageDigest .getInstance ("MD5" );
82+ MessageDigest combinedHash = MessageDigest .getInstance ("MD5" );
5983
6084 Directory directory = inputDirectory .get ();
6185
6286 Path inputPath = directory .getAsFile ().toPath ();
6387
64- Map <String , Object > platforms = new HashMap <>();
65-
66- byte [] buffer = new byte [0xFFFF ];
67- int readBytes = 0 ;
88+ Map <String , PlatformInfo > platforms = new HashMap <>();
6889
6990 for (File file : directory .getAsFileTree ()) {
7091 if (!file .isFile ()) {
@@ -73,44 +94,49 @@ public void execute() throws NoSuchAlgorithmException, IOException {
7394
7495 Path path = inputPath .relativize (file .toPath ());
7596
76- try (FileInputStream is = new FileInputStream (file )) {
77- while ((readBytes = is .read (buffer )) != -1 ) {
78- hash .update (buffer , 0 , readBytes );
79- }
97+ // Compute individual file hash
98+ MessageDigest fileHash = MessageDigest .getInstance ("MD5" );
99+ try (var dis =
100+ new DigestInputStream (
101+ new DigestInputStream (new BufferedInputStream (new FileInputStream (file )), fileHash ),
102+ combinedHash )) {
103+ dis .transferTo (OutputStream .nullOutputStream ());
80104 }
81105
82106 String platform = path .getName (0 ).toString ();
83107 String arch = path .getName (1 ).toString ();
84108
85109 String strPath = "/" + path .toString ().replace ("\\ " , "/" );
86-
87- @ SuppressWarnings ( "unchecked" ) // This will always be the correct type
88- Map < String , List < String >> platformMap = ( Map < String , List < String >>) platforms .get (platform );
89- if (platformMap == null ) {
90- platformMap = new HashMap <>();
91- List < String > archFiles = new ArrayList <>( );
92- archFiles . add ( strPath );
93- platformMap .put (arch , archFiles );
94- platforms .put (platform , platformMap );
110+ String hexFileHash = HexFormat . of (). formatHex ( fileHash . digest ());
111+
112+ PlatformInfo platformInfo = platforms .get (platform );
113+ if (platformInfo == null ) {
114+ Map < String , String > fileHashes = new HashMap <>();
115+ fileHashes . put ( strPath , hexFileHash );
116+ Map < String , ArchInfo > architectures = new HashMap <>( );
117+ architectures .put (arch , new ArchInfo ( fileHashes ) );
118+ platforms .put (platform , new PlatformInfo ( architectures ) );
95119 } else {
96- List <String > archFiles = platformMap .get (arch );
97- if (archFiles == null ) {
98- archFiles = new ArrayList <>();
99- archFiles .add (strPath );
100- platformMap .put (arch , archFiles );
120+ Map <String , ArchInfo > architectures = platformInfo .architectures ();
121+ ArchInfo archInfo = architectures .get (arch );
122+ if (archInfo == null ) {
123+ Map <String , String > fileHashes = new HashMap <>();
124+ fileHashes .put (strPath , hexFileHash );
125+ architectures .put (arch , new ArchInfo (fileHashes ));
101126 } else {
102- archFiles . add ( strPath );
127+ archInfo . fileHashes (). put ( strPath , hexFileHash );
103128 }
104129 }
105130 }
106131
107- var versions = Files .readAllLines (versionsInput .get ().getAsFile ().toPath ());
132+ String hash = HexFormat .of ().formatHex (combinedHash .digest ());
133+
134+ List <String > versions = Files .readAllLines (versionsInput .get ().getAsFile ().toPath ());
135+ ResourceInformation output = new ResourceInformation (hash , platforms , versions );
108136
109- platforms .put ("hash" , EncodingGroovyMethods .encodeHex (hash .digest ()).toString ());
110- platforms .put ("versions" , versions );
111137 GsonBuilder builder = new GsonBuilder ();
112138 builder .setPrettyPrinting ();
113- var json = builder .create ().toJson (platforms );
139+ var json = builder .create ().toJson (output );
114140 Files .writeString (hashFile .get ().getAsFile ().toPath (), json , Charset .defaultCharset ());
115141 }
116142}
0 commit comments