diff --git a/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java b/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java index 96173e8826182..55ae9bc97a2f6 100644 --- a/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java +++ b/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; public class Utils { @@ -42,6 +43,10 @@ public class Utils { "jdk.localedata" // noise, change here are not interesting ); + public static final Predicate DEFAULT_MODULE_PREDICATE = m -> EXCLUDED_MODULES.contains(m) == false + && m.contains(".internal.") == false + && m.contains(".incubator.") == false; + private static Map> findModuleExports(FileSystem fs) throws IOException { var modulesExports = new HashMap>(); try (var stream = Files.walk(fs.getPath("modules"))) { @@ -69,20 +74,20 @@ public interface JdkModuleConsumer { } public static void walkJdkModules(JdkModuleConsumer c) throws IOException { + walkJdkModules(DEFAULT_MODULE_PREDICATE, c); + } + public static void walkJdkModules(Predicate modulePredicate, JdkModuleConsumer c) throws IOException { FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); var moduleExports = Utils.findModuleExports(fs); - try (var stream = Files.walk(fs.getPath("modules"))) { var modules = stream.filter(x -> x.toString().endsWith(".class")) .collect(Collectors.groupingBy(x -> x.subpath(1, 2).toString())); for (var kv : modules.entrySet()) { var moduleName = kv.getKey(); - if (Utils.EXCLUDED_MODULES.contains(moduleName) == false - && moduleName.contains(".internal.") == false - && moduleName.contains(".incubator.") == false) { + if (modulePredicate.test(moduleName)) { var thisModuleExports = moduleExports.get(moduleName); c.accept(moduleName, kv.getValue(), thisModuleExports); } diff --git a/libs/entitlement/tools/jdk-api-extractor/README.md b/libs/entitlement/tools/jdk-api-extractor/README.md index 48e62d4a8981e..ea3627da07a60 100644 --- a/libs/entitlement/tools/jdk-api-extractor/README.md +++ b/libs/entitlement/tools/jdk-api-extractor/README.md @@ -13,7 +13,10 @@ Usage example: diff libs/entitlement/tools/jdk-api-extractor/api-jdk24.tsv libs/entitlement/tools/jdk-api-extractor/api-jdk25.tsv ``` -To review the diff of deprecations (by means of `@Deprecated`), use `--deprecations-only` as 2nd argument. +### Optional arguments: + +- `--deprecations-only`: reports public deprecations (by means of `@Deprecated`) +- `--include-incubator`: include incubator modules (e.g. `jdk.incubator.vector`) ```bash ./gradlew :libs:entitlement:tools:jdk-api-extractor:run -Druntime.java=24 --args="deprecations-jdk24.tsv --deprecations-only" diff --git a/libs/entitlement/tools/jdk-api-extractor/build.gradle b/libs/entitlement/tools/jdk-api-extractor/build.gradle index 97c87e8d0c46c..104f2d31a4d23 100644 --- a/libs/entitlement/tools/jdk-api-extractor/build.gradle +++ b/libs/entitlement/tools/jdk-api-extractor/build.gradle @@ -16,8 +16,16 @@ ext { javaMainClass = "org.elasticsearch.entitlement.tools.jdkapi.JdkApiExtractor" } +def addIncubatorModules = { + file("${buildParams.runtimeJavaHome.get()}/jmods") + .listFiles() + .findAll { it.name.endsWith('.jmod') && it.name.contains('.incubator.') } + .collectMany { ['--add-modules', it.name[0..-6]] } +} + application { mainClass.set(javaMainClass) + applicationDefaultJvmArgs = addIncubatorModules() } tasks.named("run").configure { diff --git a/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java b/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java index 0e84fc9e15192..fbdb3fbf76747 100644 --- a/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java +++ b/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java @@ -20,12 +20,16 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.objectweb.asm.Opcodes.ACC_DEPRECATED; @@ -49,15 +53,24 @@ public class JdkApiExtractor { new AccessibleMethod("close", "()V", true, false, true) ); + private static String DEPRECATIONS_ONLY = "--deprecations-only"; + private static String INCLUDE_INCUBATOR = "--include-incubator"; + + private static Set OPTIONAL_ARGS = Set.of(DEPRECATIONS_ONLY, INCLUDE_INCUBATOR); + public static void main(String[] args) throws IOException { validateArgs(args); - boolean deprecationsOnly = args.length == 2 && args[1].equals("--deprecations-only"); + boolean deprecationsOnly = optionalArgs(args).anyMatch(DEPRECATIONS_ONLY::equals); Map> accessibleImplementationsByClass = new TreeMap<>(); Map> accessibleForOverridesByClass = new TreeMap<>(); Map> deprecationsByClass = new TreeMap<>(); - Utils.walkJdkModules((moduleName, moduleClasses, moduleExports) -> { + Predicate modulePredicate = Utils.DEFAULT_MODULE_PREDICATE.or( + m -> optionalArgs(args).anyMatch(INCLUDE_INCUBATOR::equals) && m.contains(".incubator.") + ); + + Utils.walkJdkModules(modulePredicate, (moduleName, moduleClasses, moduleExports) -> { var visitor = new AccessibleClassVisitor( moduleExports, accessibleImplementationsByClass, @@ -87,16 +100,37 @@ private static String internalClassName(Path clazz, String moduleName) { return relativePath.substring(0, relativePath.length() - ".class".length()); } + private static Stream optionalArgs(String[] args) { + return Arrays.stream(args).skip(1); + } + @SuppressForbidden(reason = "cli tool printing to standard err/out") private static void validateArgs(String[] args) { - boolean valid = args.length == 1 || (args.length == 2 && "--deprecations-only".equals(args[1])); - + boolean valid = args.length > 0 && optionalArgs(args).allMatch(OPTIONAL_ARGS::contains); + if (valid && isWritableOutputPath(args[0]) == false) { + valid = false; + System.err.println("invalid output path: " + args[0]); + } if (valid == false) { - System.err.println("usage: [--deprecations-only]"); + String optionalArgs = OPTIONAL_ARGS.stream().collect(Collectors.joining("] [", " [", "]")); + System.err.println("usage: " + optionalArgs); System.exit(1); } } + private static boolean isWritableOutputPath(String pathStr) { + try { + Path path = Paths.get(pathStr); + if (Files.exists(path) && Files.isRegularFile(path)) { + return Files.isWritable(path); + } + Path parent = path.toAbsolutePath().getParent(); + return parent != null && Files.isDirectory(parent) && Files.isWritable(parent); + } catch (Exception e) { + return false; + } + } + @SuppressForbidden(reason = "cli tool printing to standard err/out") private static void writeFile(Path path, Map> methods) throws IOException { System.out.println("Writing result for " + Runtime.version() + " to " + path.toAbsolutePath());