verifyJars function
Checks classPath — jars and class directories — against spec's
required, forbidden and content rules, without starting a JVM.
The point is to fail at provisioning, naming the artifact, rather than
with a NoClassDefFoundError deep inside a library the first time the one
code path that needs the class runs.
Implementation
JarVerification verifyJars(JavaRuntimeSpec spec, List<String> classPath) {
final entries = <String>{};
final contentFound = <String>{};
final unreadable = <String, String>{};
var jarCount = 0;
for (final path in classPath) {
try {
if (FileSystemEntity.isDirectorySync(path)) {
_scanDirectory(spec, path, entries, contentFound);
continue;
}
if (!File(path).existsSync()) {
unreadable[path] = 'does not exist';
continue;
}
final jar = JarFile.open(path);
// Read the content before counting the jar in: a jar whose entries
// cannot be read is unreadable, not half-counted.
final found = <String>{
for (final MapEntry(key: name, value: wanted)
in spec.requiredResourceContent.entries)
if (jar.contains(name) && jar.readText(name)!.contains(wanted)) name,
};
jarCount++;
entries.addAll(jar.names);
contentFound.addAll(found);
} on FormatException catch (e) {
unreadable[path] = e.message;
} on FileSystemException catch (e) {
unreadable[path] = e.message;
}
}
bool hasClass(String className) =>
entries.contains('${className.replaceAll('.', '/')}.class');
return JarVerification(
jarCount: jarCount,
missingClasses: {
for (final MapEntry(:key, :value) in spec.requiredClasses.entries)
if (!hasClass(key)) key: value,
},
missingResources: [
for (final resource in {
...spec.requiredResources,
...spec.requiredResourceContent.keys,
})
if (!entries.contains(resource)) resource,
],
wrongResourceContent: {
for (final MapEntry(:key, :value) in spec.requiredResourceContent.entries)
if (entries.contains(key) && !contentFound.contains(key)) key: value,
},
forbiddenFound: {
for (final MapEntry(:key, :value) in spec.forbiddenClasses.entries)
if (hasClass(key)) key: value,
},
unreadable: unreadable,
);
}