Skip to content

Commit 085e977

Browse files
committed
Java module precommit plugin and task
1 parent 9ec8153 commit 085e977

File tree

4 files changed

+189
-110
lines changed

4 files changed

+189
-110
lines changed

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaModulePlugin.java

Lines changed: 1 addition & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import org.elasticsearch.gradle.VersionProperties;
1212
import org.elasticsearch.gradle.util.GradleUtils;
1313
import org.gradle.api.Action;
14-
import org.gradle.api.GradleException;
1514
import org.gradle.api.Named;
1615
import org.gradle.api.Plugin;
1716
import org.gradle.api.Project;
@@ -24,60 +23,31 @@
2423
import org.gradle.api.file.FileCollection;
2524
import org.gradle.api.logging.Logger;
2625
import org.gradle.api.plugins.JavaPlugin;
27-
import org.gradle.api.tasks.InputDirectory;
2826
import org.gradle.api.tasks.Internal;
2927
import org.gradle.api.tasks.SourceSet;
30-
import org.gradle.api.tasks.TaskProvider;
3128
import org.gradle.api.tasks.compile.JavaCompile;
3229
import org.gradle.process.CommandLineArgumentProvider;
3330

3431
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;
4232
import java.nio.file.Files;
43-
import java.nio.file.Path;
4433
import java.util.ArrayList;
4534
import java.util.Arrays;
46-
import java.util.Comparator;
4735
import java.util.HashSet;
4836
import java.util.List;
49-
import java.util.Map;
5037
import java.util.Set;
51-
import java.util.function.Predicate;
5238
import java.util.stream.Collectors;
5339
import java.util.stream.Stream;
5440
import java.util.stream.StreamSupport;
5541

56-
import static java.util.stream.Collectors.toSet;
57-
5842
/**
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
43+
* The Java Module Compile Path Plugin, i.e. --module-path, ---module-version
6444
*/
6545
public class ElasticsearchJavaModulePlugin implements Plugin<Project> {
6646

6747
@Override
6848
public void apply(Project project) {
6949
project.getPluginManager().apply(JavaPlugin.class);
7050
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-
}
8151
}
8252

8353
// List of root tasks, by name, whose compileJava task should not use the module path. These are test related sources.
@@ -233,83 +203,4 @@ static String pathToString(String path) {
233203
static boolean isIdea() {
234204
return System.getProperty("idea.sync.active", "false").equals("true");
235205
}
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-
}
315206
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/InternalPrecommitTasks.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static void create(Project project, boolean withProductiveCode) {
4444
project.getPluginManager().apply(ThirdPartyAuditPrecommitPlugin.class);
4545
project.getPluginManager().apply(DependencyLicensesPrecommitPlugin.class);
4646
project.getPluginManager().apply(SplitPackagesAuditPrecommitPlugin.class);
47+
project.getPluginManager().apply(JavaModulePrecommitPlugin.class);
4748
}
4849
}
4950
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.gradle.internal.precommit;
10+
11+
import org.elasticsearch.gradle.internal.InternalPlugin;
12+
import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitPlugin;
13+
import org.elasticsearch.gradle.util.GradleUtils;
14+
import org.gradle.api.Project;
15+
import org.gradle.api.Task;
16+
import org.gradle.api.tasks.SourceSet;
17+
import org.gradle.api.tasks.TaskProvider;
18+
19+
public class JavaModulePrecommitPlugin extends PrecommitPlugin implements InternalPlugin {
20+
21+
public static final String TASK_NAME = "validateModule";
22+
23+
@Override
24+
public TaskProvider<? extends Task> createTask(Project project) {
25+
26+
TaskProvider<JavaModulePrecommitTask> task = project.getTasks().register(TASK_NAME, JavaModulePrecommitTask.class);
27+
task.configure(t -> {
28+
SourceSet mainSourceSet = GradleUtils.getJavaSourceSets(project).findByName(SourceSet.MAIN_SOURCE_SET_NAME);
29+
t.getSrcDirs().set(project.provider(() -> mainSourceSet.getAllSource().getSrcDirs()));
30+
t.setClassesDirs(mainSourceSet.getOutput().getClassesDirs());
31+
t.setResourcesDirs(mainSourceSet.getOutput().getResourcesDir());
32+
});
33+
return task;
34+
}
35+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.gradle.internal.precommit;
10+
11+
import org.elasticsearch.gradle.VersionProperties;
12+
import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTask;
13+
import org.gradle.api.GradleException;
14+
import org.gradle.api.file.FileCollection;
15+
import org.gradle.api.model.ObjectFactory;
16+
import org.gradle.api.provider.SetProperty;
17+
import org.gradle.api.tasks.InputFiles;
18+
import org.gradle.api.tasks.Internal;
19+
import org.gradle.api.tasks.PathSensitive;
20+
import org.gradle.api.tasks.PathSensitivity;
21+
import org.gradle.api.tasks.SkipWhenEmpty;
22+
import org.gradle.api.tasks.TaskAction;
23+
24+
import java.io.File;
25+
import java.io.IOException;
26+
import java.io.UncheckedIOException;
27+
import java.lang.module.ModuleDescriptor;
28+
import java.lang.module.ModuleFinder;
29+
import java.lang.module.ModuleReference;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
import java.util.Comparator;
33+
import java.util.Objects;
34+
import java.util.Set;
35+
36+
import javax.inject.Inject;
37+
38+
import static java.util.stream.Collectors.toSet;
39+
40+
public class JavaModulePrecommitTask extends PrecommitTask {
41+
42+
private static final String expectedVersion = VersionProperties.getElasticsearch();
43+
44+
private final SetProperty<File> srcDirs;
45+
46+
private FileCollection classesDirs;
47+
48+
private File resourcesDir;
49+
50+
@Inject
51+
public JavaModulePrecommitTask(ObjectFactory objectFactory) {
52+
srcDirs = objectFactory.setProperty(File.class);
53+
}
54+
55+
@Internal
56+
public FileCollection getClassesDirs() {
57+
return this.classesDirs;
58+
}
59+
60+
public void setClassesDirs(FileCollection classesDirs) {
61+
Objects.requireNonNull(classesDirs, "classesDirs");
62+
this.classesDirs = classesDirs;
63+
}
64+
65+
@Internal
66+
public File getResourcesDir() {
67+
return this.resourcesDir;
68+
}
69+
70+
public void setResourcesDirs(File resourcesDirs) {
71+
Objects.requireNonNull(resourcesDirs, "resourcesDirs");
72+
this.resourcesDir = resourcesDirs;
73+
}
74+
75+
@InputFiles
76+
@SkipWhenEmpty
77+
@PathSensitive(PathSensitivity.RELATIVE)
78+
public SetProperty<File> getSrcDirs() {
79+
return srcDirs;
80+
}
81+
82+
@TaskAction
83+
public void checkModule() {
84+
if (hasModuleInfoDotJava(getSrcDirs()) == false) {
85+
return; // non-modular project, no validation required
86+
}
87+
ModuleReference mod = esModuleFor(getClassesDirs().getSingleFile());
88+
getLogger().info("%s checking module %s".formatted(this, mod));
89+
checkModuleVersion(mod);
90+
checkModuleNamePrefix(mod);
91+
checkModuleServices(mod);
92+
}
93+
94+
private void checkModuleVersion(ModuleReference mref) {
95+
getLogger().info("%s checking module version for %s".formatted(this, mref.descriptor().name()));
96+
String mVersion = mref.descriptor()
97+
.rawVersion()
98+
.orElseThrow(() -> new GradleException("no version found in module " + mref.descriptor().name()));
99+
if (mVersion.equals(expectedVersion) == false) {
100+
throw new GradleException("Expected version [" + expectedVersion + "], in " + mref.descriptor());
101+
}
102+
}
103+
104+
private void checkModuleNamePrefix(ModuleReference mref) {
105+
getLogger().info("%s checking module name prefix for %s".formatted(this, mref.descriptor().name()));
106+
if (mref.descriptor().name().startsWith("org.elasticsearch.") == false) {
107+
throw new GradleException("Expected name starting with \"org.elasticsearch.\", in " + mref.descriptor());
108+
}
109+
}
110+
111+
private void checkModuleServices(ModuleReference mref) {
112+
getLogger().info("%s checking module services for %s".formatted(this, mref.descriptor().name()));
113+
Set<String> modServices = mref.descriptor().provides().stream().map(ModuleDescriptor.Provides::service).collect(toSet());
114+
Path servicesRoot = getResourcesDir().toPath().resolve("META-INF").resolve("services");
115+
getLogger().info("%s servicesRoot %s".formatted(this, servicesRoot));
116+
if (Files.exists(servicesRoot)) {
117+
try {
118+
Files.walk(servicesRoot)
119+
.filter(Files::isRegularFile)
120+
.map(p -> servicesRoot.relativize(p))
121+
.map(Path::toString)
122+
.peek(s -> getLogger().info("%s checking service %s".formatted(this, s)))
123+
.forEach(service -> {
124+
if (modServices.contains(service) == false) {
125+
throw new GradleException(
126+
"Expected provides %s in module %s with provides %s.".formatted(
127+
service,
128+
mref.descriptor().name(),
129+
mref.descriptor().provides()
130+
)
131+
);
132+
}
133+
});
134+
} catch (IOException e) {
135+
throw new UncheckedIOException(e);
136+
}
137+
}
138+
}
139+
140+
private static boolean hasModuleInfoDotJava(SetProperty<File> srcDirs) {
141+
return srcDirs.get().stream().map(dir -> dir.toPath().resolve("module-info.java")).anyMatch(Files::exists);
142+
}
143+
144+
private static ModuleReference esModuleFor(File filePath) {
145+
return ModuleFinder.of(filePath.toPath())
146+
.findAll()
147+
.stream()
148+
.sorted(Comparator.comparing(ModuleReference::descriptor))
149+
.findFirst()
150+
.orElseThrow(() -> new GradleException("module not found in " + filePath));
151+
}
152+
}

0 commit comments

Comments
 (0)