|
11 | 11 | import org.elasticsearch.gradle.VersionProperties; |
12 | 12 | import org.elasticsearch.gradle.util.GradleUtils; |
13 | 13 | import org.gradle.api.Action; |
| 14 | +import org.gradle.api.GradleException; |
14 | 15 | import org.gradle.api.Named; |
15 | 16 | import org.gradle.api.Plugin; |
16 | 17 | import org.gradle.api.Project; |
|
23 | 24 | import org.gradle.api.file.FileCollection; |
24 | 25 | import org.gradle.api.logging.Logger; |
25 | 26 | import org.gradle.api.plugins.JavaPlugin; |
| 27 | +import org.gradle.api.tasks.InputDirectory; |
26 | 28 | import org.gradle.api.tasks.Internal; |
27 | 29 | import org.gradle.api.tasks.SourceSet; |
| 30 | +import org.gradle.api.tasks.TaskProvider; |
28 | 31 | import org.gradle.api.tasks.compile.JavaCompile; |
29 | 32 | import org.gradle.process.CommandLineArgumentProvider; |
30 | 33 |
|
31 | 34 | import java.io.File; |
| 35 | +import java.io.IOException; |
| 36 | +import java.io.UncheckedIOException; |
| 37 | +import java.lang.module.ModuleDescriptor.Provides; |
| 38 | +import java.lang.module.ModuleFinder; |
| 39 | +import java.lang.module.ModuleReference; |
| 40 | +import java.net.URI; |
| 41 | +import java.nio.file.FileSystems; |
32 | 42 | import java.nio.file.Files; |
| 43 | +import java.nio.file.Path; |
33 | 44 | import java.util.ArrayList; |
34 | 45 | import java.util.Arrays; |
| 46 | +import java.util.Comparator; |
35 | 47 | import java.util.HashSet; |
36 | 48 | import java.util.List; |
| 49 | +import java.util.Map; |
37 | 50 | import java.util.Set; |
| 51 | +import java.util.function.Predicate; |
38 | 52 | import java.util.stream.Collectors; |
39 | 53 | import java.util.stream.Stream; |
40 | 54 | import java.util.stream.StreamSupport; |
41 | 55 |
|
| 56 | +import static java.util.stream.Collectors.toSet; |
| 57 | + |
42 | 58 | /** |
43 | | - * The Java Module Compile Path Plugin, i.e. --module-path, ---module-version |
| 59 | + * The Java Module Plugin. |
| 60 | + * |
| 61 | + * Supports the following: |
| 62 | + * 1) Module Compile Path and version, i.e. --module-path, ---module-version |
| 63 | + * 2) Additional module specific check tasks, e.g. module version, module services |
44 | 64 | */ |
45 | | -public class ElasticsearchJavaModulePathPlugin implements Plugin<Project> { |
| 65 | +public class ElasticsearchJavaModulePlugin implements Plugin<Project> { |
| 66 | + |
46 | 67 | @Override |
47 | 68 | public void apply(Project project) { |
48 | 69 | project.getPluginManager().apply(JavaPlugin.class); |
49 | 70 | configureCompileModulePath(project); |
| 71 | + |
| 72 | + if (hasModuleInfoDotJava(project)) { |
| 73 | + TaskProvider<Task> checkModuleVersionTask = registerCheckModuleVersionTask(project, VersionProperties.getElasticsearch()); |
| 74 | + TaskProvider<Task> checkModuleServicesTask = registerCheckModuleServicesTask(project); |
| 75 | + TaskProvider<Task> checkTask = project.getTasks().named("check"); |
| 76 | + checkTask.configure(task -> { |
| 77 | + task.dependsOn(checkModuleVersionTask); |
| 78 | + task.dependsOn(checkModuleServicesTask); |
| 79 | + }); |
| 80 | + } |
50 | 81 | } |
51 | 82 |
|
52 | 83 | // List of root tasks, by name, whose compileJava task should not use the module path. These are test related sources. |
@@ -202,4 +233,83 @@ static String pathToString(String path) { |
202 | 233 | static boolean isIdea() { |
203 | 234 | return System.getProperty("idea.sync.active", "false").equals("true"); |
204 | 235 | } |
| 236 | + |
| 237 | + // -- check tasks |
| 238 | + |
| 239 | + private static final Predicate<ModuleReference> isESModule = mref -> mref.descriptor().name().startsWith("org.elasticsearch"); |
| 240 | + |
| 241 | + private static final Class<ElasticsearchJavaModulePlugin> THIS_CLASS = ElasticsearchJavaModulePlugin.class; |
| 242 | + |
| 243 | + /** Checks that all expected Elasticsearch modules have the expected versions. */ |
| 244 | + private static TaskProvider<Task> registerCheckModuleVersionTask(Project project, String expectedVersion) { |
| 245 | + return project.getTasks().register("checkModuleVersion", task -> { |
| 246 | + task.doLast(new Action<>() { |
| 247 | + @InputDirectory |
| 248 | + File distroRoot = project.file(new File(project.getBuildDir(), "distributions")); |
| 249 | + |
| 250 | + @Override |
| 251 | + public void execute(Task task) { |
| 252 | + for (ModuleReference mref : esModulesFor(distroRoot)) { |
| 253 | + task.getLogger().info("%s checking module version for %s".formatted(task.toString(), mref.descriptor().name())); |
| 254 | + String mVersion = mref.descriptor() |
| 255 | + .rawVersion() |
| 256 | + .orElseThrow(() -> new GradleException("no version found in module " + mref.descriptor().name())); |
| 257 | + if (mVersion.equals(expectedVersion) == false) { |
| 258 | + throw new GradleException("Expected version [" + expectedVersion + "], in " + mref.descriptor()); |
| 259 | + } |
| 260 | + } |
| 261 | + } |
| 262 | + }); |
| 263 | + }); |
| 264 | + } |
| 265 | + |
| 266 | + /** Checks that ES modules have, at least, the META-INF services declared. */ |
| 267 | + private static TaskProvider<Task> registerCheckModuleServicesTask(Project project) { |
| 268 | + return project.getTasks().register("checkModuleServices", task -> { |
| 269 | + task.doLast(new Action<>() { |
| 270 | + @InputDirectory |
| 271 | + File distroRoot = project.file(new File(project.getBuildDir(), "distributions")); |
| 272 | + |
| 273 | + @Override |
| 274 | + public void execute(Task task) { |
| 275 | + for (ModuleReference mref : esModulesFor(distroRoot)) { |
| 276 | + URI uri = URI.create("jar:" + mref.location().get()); |
| 277 | + Set<String> modServices = mref.descriptor().provides().stream().map(Provides::service).collect(toSet()); |
| 278 | + try (var fileSystem = FileSystems.newFileSystem(uri, Map.of(), THIS_CLASS.getClassLoader())) { |
| 279 | + Path servicesRoot = fileSystem.getPath("/META-INF/services"); |
| 280 | + if (Files.exists(servicesRoot)) { |
| 281 | + Files.walk(servicesRoot) |
| 282 | + .filter(Files::isRegularFile) |
| 283 | + .map(p -> servicesRoot.relativize(p)) |
| 284 | + .map(Path::toString) |
| 285 | + .peek(s -> task.getLogger().info("%s checking service %s".formatted(task.toString(), s))) |
| 286 | + .forEach(service -> { |
| 287 | + if (modServices.contains(service) == false) { |
| 288 | + throw new GradleException( |
| 289 | + "Expected provides %s in module %s with provides %s.".formatted( |
| 290 | + service, |
| 291 | + mref.descriptor().name(), |
| 292 | + mref.descriptor().provides() |
| 293 | + ) |
| 294 | + ); |
| 295 | + } |
| 296 | + }); |
| 297 | + } |
| 298 | + } catch (IOException e) { |
| 299 | + throw new UncheckedIOException(e); |
| 300 | + } |
| 301 | + } |
| 302 | + } |
| 303 | + }); |
| 304 | + }); |
| 305 | + } |
| 306 | + |
| 307 | + private static List<ModuleReference> esModulesFor(File filePath) { |
| 308 | + return ModuleFinder.of(filePath.toPath()) |
| 309 | + .findAll() |
| 310 | + .stream() |
| 311 | + .filter(isESModule) |
| 312 | + .sorted(Comparator.comparing(ModuleReference::descriptor)) |
| 313 | + .toList(); |
| 314 | + } |
205 | 315 | } |
0 commit comments