verifyJars function

JarVerification verifyJars(
  1. JavaRuntimeSpec spec,
  2. List<String> classPath
)

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,
  );
}