Skip to content

Commit 107fd06

Browse files
authored
Option to generate META-INF/services files (#272)
1 parent a8275f3 commit 107fd06

5 files changed

Lines changed: 178 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Java Module Dependencies Gradle Plugin - Changelog
22

3+
## Version 1.12
4+
* [#245](https://github.com/gradlex-org/java-module-dependencies/issues/195) Add option to generate META-INF/services configuration files
5+
* [#245](https://github.com/gradlex-org/java-module-dependencies/issues/175) Improve parsing of module-info
6+
* Update module name mappings
7+
38
## Version 1.11
49
* [#245](https://github.com/gradlex-org/java-module-dependencies/issues/245) Add 'allLocalModules' access to extension
510
* [#245](https://github.com/gradlex-org/java-module-dependencies/issues/247) Defining a 'versions' project in settings supports applying additional plugins

README.MD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,17 @@ org-junit-jupiter-api = "5.7.2"
297297
- Note that the TOML notation does not support `.` as separater in the Module Names, but allows you to use `_` or `-` instead.
298298
- _If_ you use a catalog with a custom name (not `libs`), you can tell the plugin using `versionCatalogName.set("customName")`.
299299

300+
## Generate META-INF/services configuration files
301+
302+
Turn on the following option to generate service provider configuration files in `META_INF/services` for all
303+
`provides ... with ...` directives in module-info files.
304+
305+
```kotlin
306+
javaModuleDependencies {
307+
generateMetaInfServices()
308+
}
309+
```
310+
300311
## Find the latest stable version of a Module
301312

302313
The `recommendModuleVersions` help task prints the latest available versions of the Modules you require.

src/main/java/org/gradlex/javamodule/dependencies/JavaModuleDependenciesExtension.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,13 @@
4646
import org.gradle.api.provider.Provider;
4747
import org.gradle.api.provider.ProviderFactory;
4848
import org.gradle.api.tasks.SourceSet;
49+
import org.gradle.api.tasks.SourceSetContainer;
50+
import org.gradle.api.tasks.TaskContainer;
4951
import org.gradle.api.tasks.TaskProvider;
52+
import org.gradle.language.jvm.tasks.ProcessResources;
5053
import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo;
5154
import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfoCache;
55+
import org.gradlex.javamodule.dependencies.tasks.MetaInfServicesGenerate;
5256
import org.gradlex.javamodule.dependencies.tasks.SyntheticModuleInfoFoldersGenerate;
5357
import org.jspecify.annotations.Nullable;
5458

@@ -144,6 +148,31 @@ private Provider<Map<String, String>> parsedModulesProperties() {
144148
});
145149
}
146150

151+
/**
152+
* If a module-info.java contains 'provides' directives, generate the corresponding META_INF/services entries for backward compatibility to load the module on the classpath.
153+
*/
154+
public void generateMetaInfServices() {
155+
SourceSetContainer sourceSets = getProject().getExtensions().getByType(SourceSetContainer.class);
156+
sourceSets.all(sourceSet -> {
157+
ModuleInfo moduleInfo = getModuleInfoCache().get().get(sourceSet, getProviders());
158+
if (!moduleInfo.getProvides().isEmpty()) {
159+
String taskName = sourceSet.getTaskName("generate", "MetaInfServices");
160+
Provider<Directory> destinationDirectory =
161+
getLayout().getBuildDirectory().dir("tmp/" + taskName);
162+
Provider<MetaInfServicesGenerate> generateMetaInfServices = getTasks()
163+
.register(taskName, MetaInfServicesGenerate.class, t -> {
164+
t.getModuleInfo().convention(moduleInfo);
165+
t.getDestinationDirectory().convention(destinationDirectory);
166+
});
167+
getTasks()
168+
.named(
169+
sourceSet.getProcessResourcesTaskName(),
170+
ProcessResources.class,
171+
t -> t.from(generateMetaInfServices));
172+
}
173+
});
174+
}
175+
147176
/**
148177
* Converts 'Module Name' to GA coordinates that can be used in
149178
* dependency declarations as String: "group:name"
@@ -562,4 +591,7 @@ private <T> Provider<T> errorIfNotFound(Provider<String> moduleName) {
562591

563592
@Inject
564593
protected abstract ConfigurationContainer getConfigurations();
594+
595+
@Inject
596+
protected abstract TaskContainer getTasks();
565597
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package org.gradlex.javamodule.dependencies.tasks;
3+
4+
import java.io.IOException;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.util.List;
8+
import java.util.Map;
9+
import org.gradle.api.DefaultTask;
10+
import org.gradle.api.file.DirectoryProperty;
11+
import org.gradle.api.provider.Property;
12+
import org.gradle.api.tasks.CacheableTask;
13+
import org.gradle.api.tasks.Input;
14+
import org.gradle.api.tasks.Optional;
15+
import org.gradle.api.tasks.OutputDirectory;
16+
import org.gradle.api.tasks.TaskAction;
17+
import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo;
18+
19+
@CacheableTask
20+
public abstract class MetaInfServicesGenerate extends DefaultTask {
21+
22+
@Input
23+
@Optional
24+
public abstract Property<ModuleInfo> getModuleInfo();
25+
26+
@OutputDirectory
27+
public abstract DirectoryProperty getDestinationDirectory();
28+
29+
@TaskAction
30+
public void generateServiceProviderConfigurationFiles() throws IOException {
31+
Map<String, List<String>> serviceProvides = getModuleInfo().get().getProvides();
32+
33+
Path services = getDestinationDirectory()
34+
.dir("META-INF/services")
35+
.get()
36+
.getAsFile()
37+
.toPath();
38+
39+
Files.createDirectories(services);
40+
41+
for (String service : serviceProvides.keySet()) {
42+
Path configurationFile = services.resolve(service);
43+
String serviceProvider = String.join("\n", serviceProvides.get(service));
44+
Files.write(configurationFile, serviceProvider.getBytes());
45+
}
46+
}
47+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package org.gradlex.javamodule.dependencies.test.provides;
3+
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
import org.gradle.testkit.runner.BuildTask;
7+
import org.gradle.testkit.runner.TaskOutcome;
8+
import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild;
9+
import org.junit.jupiter.api.Test;
10+
11+
class GenerateMetaInfServicesTest {
12+
13+
GradleBuild build = new GradleBuild(true);
14+
15+
@Test
16+
void generates_meta_inf_services_files() {
17+
build.libBuildFile.appendText(
18+
"""
19+
javaModuleDependencies { generateMetaInfServices() }
20+
dependencies.constraints {
21+
implementation("org.junit.platform:junit-platform-engine:6.0.2")
22+
testFixturesImplementation("org.junit.platform:junit-platform-engine:6.0.2")
23+
}
24+
""");
25+
build.file("lib/src/main/java/abc/lib/NoOpTestEngine.java")
26+
.writeText(
27+
"""
28+
package abc.lib;
29+
import org.junit.platform.engine.*;
30+
public class NoOpTestEngine implements TestEngine {
31+
public String getId() { return "noop"; }
32+
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return null; };
33+
public void execute(ExecutionRequest request) {};
34+
}
35+
""");
36+
build.file("lib/src/testFixtures/java/abc/lib/fixtures/NoOpTestEngine.java")
37+
.writeText(
38+
"""
39+
package abc.lib.fixtures;
40+
import org.junit.platform.engine.*;
41+
public class NoOpTestEngine implements TestEngine {
42+
public String getId() { return "noop"; }
43+
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return null; };
44+
public void execute(ExecutionRequest request) {};
45+
}
46+
""");
47+
48+
build.libModuleInfoFile.writeText(
49+
"""
50+
module abc.lib {
51+
requires org.junit.platform.engine;
52+
provides org.junit.platform.engine.TestEngine
53+
with abc.lib.NoOpTestEngine;
54+
}
55+
""");
56+
build.file("lib/src/testFixtures/java/module-info.java")
57+
.writeText(
58+
"""
59+
module abc.lib.test.fixtures {
60+
requires org.junit.platform.engine;
61+
provides org.junit.platform.engine.TestEngine
62+
with abc.lib.fixtures.NoOpTestEngine;
63+
}
64+
""");
65+
66+
var result = build.runner("jar", "testFixturesJar").build();
67+
BuildTask libMain = result.task(":lib:generateMetaInfServices");
68+
BuildTask libTestFixtures = result.task(":lib:generateTestFixturesMetaInfServices");
69+
BuildTask appMain = result.task(":app:generateMetaInfServices");
70+
assertThat(libMain).isNotNull();
71+
assertThat(libMain.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
72+
assertThat(libTestFixtures).isNotNull();
73+
assertThat(libTestFixtures.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
74+
assertThat(appMain).isNull();
75+
76+
assertThat(build.file("lib/build/resources/main/META-INF/services/org.junit.platform.engine.TestEngine")
77+
.getAsPath())
78+
.hasContent("abc.lib.NoOpTestEngine");
79+
assertThat(build.file("lib/build/resources/testFixtures/META-INF/services/org.junit.platform.engine.TestEngine")
80+
.getAsPath())
81+
.hasContent("abc.lib.fixtures.NoOpTestEngine");
82+
}
83+
}

0 commit comments

Comments
 (0)