24
24
*/
25
25
package com .oracle .svm .hosted ;
26
26
27
+ import static jdk .graal .compiler .util .Digest .DigestBuilder ;
28
+
27
29
import java .io .File ;
28
30
import java .io .IOException ;
29
31
import java .lang .module .Configuration ;
36
38
import java .lang .reflect .Method ;
37
39
import java .net .URI ;
38
40
import java .net .URISyntaxException ;
41
+ import java .net .URL ;
39
42
import java .nio .channels .ClosedByInterruptException ;
43
+ import java .nio .charset .StandardCharsets ;
40
44
import java .nio .file .FileSystem ;
41
45
import java .nio .file .FileSystems ;
46
+ import java .nio .file .FileVisitOption ;
42
47
import java .nio .file .FileVisitResult ;
43
48
import java .nio .file .FileVisitor ;
44
49
import java .nio .file .Files ;
52
57
import java .util .Collections ;
53
58
import java .util .Comparator ;
54
59
import java .util .Deque ;
60
+ import java .util .EnumSet ;
61
+ import java .util .Enumeration ;
55
62
import java .util .HashSet ;
56
63
import java .util .LinkedHashMap ;
57
64
import java .util .LinkedHashSet ;
67
74
import java .util .concurrent .TimeUnit ;
68
75
import java .util .concurrent .atomic .LongAdder ;
69
76
import java .util .function .BiConsumer ;
77
+ import java .util .jar .JarEntry ;
78
+ import java .util .jar .JarFile ;
70
79
import java .util .stream .Collector ;
71
80
import java .util .stream .Collectors ;
72
81
import java .util .stream .Stream ;
82
+ import java .util .zip .ZipFile ;
73
83
74
84
import org .graalvm .collections .EconomicMap ;
75
85
import org .graalvm .collections .EconomicSet ;
@@ -116,6 +126,7 @@ public final class NativeImageClassLoaderSupport {
116
126
private final List <Path > buildmp ;
117
127
118
128
private final Set <Path > imageProvidedJars ;
129
+ private PathDigests pathDigests ;
119
130
120
131
private final EconomicMap <URI , EconomicSet <String >> classes ;
121
132
private final EconomicMap <URI , EconomicSet <String >> packages ;
@@ -300,6 +311,47 @@ public NativeImageClassLoader getClassLoader() {
300
311
return classLoader ;
301
312
}
302
313
314
+ public Optional <PathDigests > getPathDigests (boolean free ) {
315
+ Optional <PathDigests > res = Optional .ofNullable (pathDigests );
316
+ if (free ) {
317
+ pathDigests = null ;
318
+ }
319
+ return res ;
320
+ }
321
+
322
+ public void initializePathDigests (Path digestIgnoreRelativePath ) {
323
+ List <Path > digestIgnorePaths = new ArrayList <>();
324
+ if (digestIgnoreRelativePath != null ) {
325
+ try {
326
+ Enumeration <URL > urls = classLoader .getResources (digestIgnoreRelativePath .toString ());
327
+ while (urls .hasMoreElements ()) {
328
+ URI uri = urls .nextElement ().toURI ();
329
+ if ("jar" .equalsIgnoreCase (uri .getScheme ())) {
330
+ String uriString = uri .toString ();
331
+ String fileUriString = uriString .substring ("jar:" .length (), uriString .indexOf ("!/" ));
332
+ uri = URI .create (fileUriString );
333
+ }
334
+ digestIgnorePaths .add (Path .of (uri ));
335
+ }
336
+ } catch (URISyntaxException | IOException e ) {
337
+ throw UserError .abort ("Error while looking for %s in %s" , digestIgnoreRelativePath , classLoader );
338
+ }
339
+ }
340
+
341
+ pathDigests = new PathDigests (filterIgnoredPathEntries (imagecp , digestIgnorePaths ), filterIgnoredPathEntries (imagemp , digestIgnorePaths ));
342
+ }
343
+
344
+ private List <Path > filterIgnoredPathEntries (List <Path > pathEntries , List <Path > digestIgnorePaths ) {
345
+ return pathEntries .stream ().filter (pathEntry -> {
346
+ for (Path p : digestIgnorePaths ) {
347
+ if (p .startsWith (pathEntry )) {
348
+ return false ;
349
+ }
350
+ }
351
+ return true ;
352
+ }).toList ();
353
+ }
354
+
303
355
public LibGraalLoader getLibGraalLoader () {
304
356
VMError .guarantee (libGraalLoader != null , "Invalid access to libGraalLoader before getting set up" );
305
357
return libGraalLoader .orElse (null );
@@ -932,13 +984,18 @@ private void initModule(ModuleReference moduleReference, boolean moduleRequiresI
932
984
if (ModuleLayer .boot ().equals (module .getLayer ())) {
933
985
builderURILocations .add (container );
934
986
}
987
+ final boolean isInImageModulePathOfLayeredBuild = pathDigests != null && pathDigests .mpDigests .containsKey (container );
988
+ final boolean isJar = ClasspathUtils .isJar (Path .of (container ));
935
989
moduleReader .list ().forEach (moduleResource -> {
936
990
char fileSystemSeparatorChar = '/' ;
937
991
String className = extractClassName (moduleResource , fileSystemSeparatorChar );
938
992
if (className != null ) {
939
993
currentlyProcessedEntry = moduleReferenceLocation + fileSystemSeparatorChar + moduleResource ;
940
994
executor .execute (() -> handleClassFileName (container , module , className , includeUnconditionally , moduleRequiresInit , preserveModule ));
941
995
}
996
+ if (isInImageModulePathOfLayeredBuild ) {
997
+ executor .execute (() -> PathDigests .storePathFileDigest (container , moduleResource , isJar , pathDigests .mpDigests ));
998
+ }
942
999
entriesProcessed .increment ();
943
1000
});
944
1001
} catch (IOException e ) {
@@ -962,7 +1019,7 @@ private void loadClassesFromPath(Path path) {
962
1019
}
963
1020
if (probeJarFileSystem != null ) {
964
1021
try (FileSystem jarFileSystem = probeJarFileSystem ) {
965
- loadClassesFromPath (container , jarFileSystem .getPath ("/" ), null , Collections .emptySet (), includeUnconditionally , includeAllMetadata );
1022
+ loadClassesFromPath (container , jarFileSystem .getPath ("/" ), null , Collections .emptySet (), includeUnconditionally , includeAllMetadata , true );
966
1023
}
967
1024
}
968
1025
} catch (ClosedByInterruptException ignored ) {
@@ -973,20 +1030,21 @@ private void loadClassesFromPath(Path path) {
973
1030
} else {
974
1031
URI container = path .toUri ();
975
1032
loadClassesFromPath (container , path , ClassUtil .CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT , ClassUtil .CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES , includeUnconditionally ,
976
- includeAllMetadata );
1033
+ includeAllMetadata , false );
977
1034
}
978
1035
}
979
1036
980
1037
private static final String CLASS_EXTENSION = ".class" ;
981
1038
982
- private void loadClassesFromPath (URI container , Path root , Path excludeRoot , Set <Path > excludes , boolean includeUnconditionally , boolean includeAllMetadata ) {
1039
+ private void loadClassesFromPath (URI container , Path root , Path excludeRoot , Set <Path > excludes , boolean includeUnconditionally , boolean includeAllMetadata , boolean isJar ) {
983
1040
boolean useFilter = root .equals (excludeRoot );
984
1041
if (useFilter ) {
985
1042
String excludesStr = excludes .stream ().map (Path ::toString ).collect (Collectors .joining (", " ));
986
1043
LogUtils .warning ("Using directory %s on classpath is discouraged. Reading classes/resources from directories %s will be suppressed." , excludeRoot , excludesStr );
987
1044
}
988
1045
FileVisitor <Path > visitor = new SimpleFileVisitor <>() {
989
1046
private final char fileSystemSeparatorChar = root .getFileSystem ().getSeparator ().charAt (0 );
1047
+ private final boolean isInImageClassPathOfLayeredBuild = pathDigests != null && pathDigests .getCpDigests ().containsKey (container );
990
1048
991
1049
@ Override
992
1050
public FileVisitResult preVisitDirectory (Path dir , BasicFileAttributes attrs ) throws IOException {
@@ -1007,6 +1065,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
1007
1065
currentlyProcessedEntry = file .toUri ().toString ();
1008
1066
executor .execute (() -> handleClassFileName (container , null , className , includeUnconditionally , true , includeAllMetadata ));
1009
1067
}
1068
+ if (isInImageClassPathOfLayeredBuild ) {
1069
+ executor .execute (() -> PathDigests .storePathFileDigest (container , fileName , isJar , pathDigests .cpDigests ));
1070
+ }
1010
1071
entriesProcessed .increment ();
1011
1072
return FileVisitResult .CONTINUE ;
1012
1073
}
@@ -1019,7 +1080,7 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) {
1019
1080
};
1020
1081
1021
1082
try {
1022
- Files .walkFileTree (root , visitor );
1083
+ Files .walkFileTree (root , EnumSet . of ( FileVisitOption . FOLLOW_LINKS ), Integer . MAX_VALUE , visitor );
1023
1084
} catch (IOException ex ) {
1024
1085
throw VMError .shouldNotReachHere (ex );
1025
1086
}
@@ -1372,4 +1433,54 @@ public Set<IncludeOptionsSupport.PackageOptionValue> packages() {
1372
1433
return packages .keySet ();
1373
1434
}
1374
1435
}
1436
+
1437
+ public static final class PathDigests {
1438
+ private final EconomicMap <URI , List <String >> cpDigests = EconomicMap .create ();
1439
+ private final EconomicMap <URI , List <String >> mpDigests = EconomicMap .create ();
1440
+
1441
+ private PathDigests (List <Path > imagecp , List <Path > imagemp ) {
1442
+ imagecp .stream ()
1443
+ .map (Path ::toUri )
1444
+ .forEach (path -> cpDigests .put (path , new ArrayList <>()));
1445
+ imagemp .stream ()
1446
+ .map (Path ::toUri )
1447
+ .forEach (path -> mpDigests .put (path , new ArrayList <>()));
1448
+ }
1449
+
1450
+ private static void storePathFileDigest (URI container , String resource , boolean isJar , EconomicMap <URI , List <String >> digests ) {
1451
+ byte [] fileContent ;
1452
+ try {
1453
+ if (isJar ) {
1454
+ try (JarFile jarFile = new JarFile (new File (container ), true , ZipFile .OPEN_READ , JarFile .runtimeVersion ())) {
1455
+ JarEntry jarEntry = jarFile .getJarEntry (resource );
1456
+ fileContent = jarFile .getInputStream (jarEntry ).readAllBytes ();
1457
+ }
1458
+ } else {
1459
+ Path resourcePath = Path .of (container ).resolve (resource );
1460
+ if (!resourcePath .toFile ().isFile ()) {
1461
+ return ;
1462
+ }
1463
+ fileContent = Files .readAllBytes (resourcePath );
1464
+ }
1465
+ } catch (IOException e ) {
1466
+ throw UserError .abort ("Image builder cannot read file: " + resource );
1467
+ }
1468
+
1469
+ DigestBuilder db = new DigestBuilder ();
1470
+ db .update (fileContent );
1471
+ db .update (resource .getBytes (StandardCharsets .UTF_8 ));
1472
+ List <String > containerDigests = digests .get (container );
1473
+ synchronized (containerDigests ) {
1474
+ containerDigests .add (new String (db .digest (), StandardCharsets .UTF_8 ));
1475
+ }
1476
+ }
1477
+
1478
+ public EconomicMap <URI , List <String >> getCpDigests () {
1479
+ return cpDigests ;
1480
+ }
1481
+
1482
+ public EconomicMap <URI , List <String >> getMpDigests () {
1483
+ return mpDigests ;
1484
+ }
1485
+ }
1375
1486
}
0 commit comments