Skip to content

Commit

Permalink
Index Java runtime classes in Maven and Gradle plugins (#2002)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar authored Sep 30, 2024
1 parent 7778ba8 commit ceaf605
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 34 deletions.
8 changes: 2 additions & 6 deletions tools/gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ plugins {

group = "io.smallrye"

if (JavaVersion.current().isJava9Compatible()) {
compileJava.options.compilerArgs.addAll(['--release', '8'])
}

compileJava {
options.encoding = 'UTF-8'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
sourceCompatibility = '11'
targetCompatibility = '11'
}

compileTestJava {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class Configs implements SmallryeOpenApiProperties {
final MapProperty<String, String> scanResourceClasses;
final Property<String> outputFileTypeFilter;
final Property<String> encoding;
final ListProperty<String> includeStandardJavaModules;

Configs(ObjectFactory objects) {
configProperties = objects.fileProperty();
Expand Down Expand Up @@ -99,6 +100,7 @@ class Configs implements SmallryeOpenApiProperties {
scanResourceClasses = objects.mapProperty(String.class, String.class);
outputFileTypeFilter = objects.property(String.class).convention("ALL");
encoding = objects.property(String.class).convention(StandardCharsets.UTF_8.name());
includeStandardJavaModules = objects.listProperty(String.class);
}

Configs(ObjectFactory objects, SmallryeOpenApiExtension ext) {
Expand Down Expand Up @@ -137,6 +139,7 @@ class Configs implements SmallryeOpenApiProperties {
scanResourceClasses = objects.mapProperty(String.class, String.class).convention(ext.getScanResourceClasses());
outputFileTypeFilter = objects.property(String.class).convention(ext.getOutputFileTypeFilter());
encoding = objects.property(String.class).convention(ext.getEncoding());
includeStandardJavaModules = objects.listProperty(String.class).convention(ext.getIncludeStandardJavaModules());
}

Config asMicroprofileConfig() {
Expand Down Expand Up @@ -349,4 +352,7 @@ public Property<String> getEncoding() {
return encoding;
}

public ListProperty<String> getIncludeStandardJavaModules() {
return includeStandardJavaModules;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -32,12 +37,24 @@ public GradleDependencyIndexCreator(Logger logger) {
this.logger = logger;
}

IndexView createIndex(Set<File> dependencies, FileCollection classesDirs)
IndexView createIndex(SmallryeOpenApiTask task)
throws IOException {

Set<File> dependencies = task.getScanDependenciesDisable().get().booleanValue()
? Collections.emptySet()
: task.getClasspath().getFiles();
FileCollection classesDirs = task.getClassesDirs();

List<Entry<File, Duration>> indexDurations = new ArrayList<>();
List<IndexView> indexes = new ArrayList<>();

List<String> includeStandardJavaModules = task.getIncludeStandardJavaModules().getOrElse(Collections.emptyList());
logger.info("includeStandardJavaModules: " + includeStandardJavaModules);

for (String moduleName : includeStandardJavaModules) {
indexes.add(indexJdkModule(moduleName));
}

for (File f : classesDirs.getFiles()) {
indexes.add(indexModuleClasses(f));
}
Expand Down Expand Up @@ -65,6 +82,37 @@ IndexView createIndex(Set<File> dependencies, FileCollection classesDirs)
return CompositeIndex.create(indexes);
}

private Index indexJdkModule(String moduleName) throws IOException {
Indexer indexer = new Indexer();
FileSystem jrt = FileSystems.getFileSystem(URI.create("jrt:/"));

for (Path root : jrt.getRootDirectories()) {
try (var walker = Files.walk(root)) {
walker
.filter(path -> path.startsWith("/modules/" + moduleName))
.filter(path -> path.getFileName().toString().endsWith(".class"))
.map(path -> {
try {
return Files.newInputStream(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
})
.forEach(stream -> {
try {
indexer.index(stream);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

return indexer.complete();
}

private Index index(File artifact) throws IOException {
Result result = JarIndexer.createJarIndex(artifact, new Indexer(), false,
false, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,9 @@ public interface SmallryeOpenApiProperties {
* Output encoding for openapi document.
*/
Property<String> getEncoding();

/**
* List of standard Java modules that should be made available to annotation scanning for introspection.
*/
ListProperty<String> getIncludeStandardJavaModules();
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Stream;

import javax.inject.Inject;
Expand All @@ -33,6 +31,7 @@
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
Expand Down Expand Up @@ -93,13 +92,7 @@ public SmallryeOpenApiTask(
public void generate() {
try {
clearOutput();

Set<File> dependencies = properties.scanDependenciesDisable.get().booleanValue()
? Collections.emptySet()
: classpath.getFiles();

IndexView index = new GradleDependencyIndexCreator(getLogger()).createIndex(dependencies,
classesDirs);
IndexView index = new GradleDependencyIndexCreator(getLogger()).createIndex(this);
SmallRyeOpenAPI openAPI = generateOpenAPI(index, resourcesSrcDirs);
write(openAPI);
} catch (Exception ex) {
Expand All @@ -108,6 +101,16 @@ public void generate() {
}
}

@Internal
FileCollection getClasspath() {
return this.classpath;
}

@Internal
FileCollection getClassesDirs() {
return this.classesDirs;
}

private SmallRyeOpenAPI generateOpenAPI(IndexView index, FileCollection resourcesSrcDirs) {
return SmallRyeOpenAPI.builder()
.withConfig(properties.asMicroprofileConfig())
Expand Down Expand Up @@ -460,4 +463,11 @@ public Property<String> getOutputFileTypeFilter() {
public Property<String> getEncoding() {
return properties.encoding;
}

@Input
@Optional
@Override
public ListProperty<String> getIncludeStandardJavaModules() {
return properties.includeStandardJavaModules;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
import org.gradle.testfixtures.ProjectBuilder;
import org.gradle.testkit.runner.GradleRunner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDir;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.TextNode;

import io.smallrye.openapi.api.OpenApiConfig.OperationIdStrategy;

Expand Down Expand Up @@ -138,7 +141,7 @@ void taskPropertiesInheritance() {
}

@Test
void simpleProject(@TempDir Path buildDir) throws Exception {
void simpleProject(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path buildDir) throws Exception {
// "Simple" Gradle project
smokeProject(buildDir, false, SmallryeOpenApiPlugin.TASK_NAME);
}
Expand All @@ -156,13 +159,13 @@ void simpleProjectWithJustJsonOutput(@TempDir Path buildDir) throws Exception {
}

@Test
void quarkusProjectGenApiOnly(@TempDir Path buildDir) throws Exception {
void quarkusProjectGenApiOnly(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path buildDir) throws Exception {
// Quarkus Gradle project, just call the generateOpenApiSpec task
smokeProject(buildDir, true, SmallryeOpenApiPlugin.TASK_NAME);
}

@Test
void quarkusProject(@TempDir Path buildDir) throws Exception {
void quarkusProject(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path buildDir) throws Exception {
// Quarkus Gradle project, perform a "full Quarkus build"
smokeProject(buildDir, true, "quarkusBuild");
}
Expand Down Expand Up @@ -190,8 +193,8 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou
"}",
"",
"dependencies {",
" implementation(\"javax.ws.rs:javax.ws.rs-api:2.1.1\")",
" implementation(\"org.eclipse.microprofile.openapi:microprofile-openapi-api:3.0\")",
" implementation(\"jakarta.ws.rs:jakarta.ws.rs-api:3.1.0\")",
" implementation(\"org.eclipse.microprofile.openapi:microprofile-openapi-api:4.0.1\")",
"}",
"",
"smallryeOpenApi {",
Expand All @@ -210,6 +213,7 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou
" operationIdStrategy.set(OperationIdStrategy.METHOD)",
" filter.set(\"testcases.CustomOASFilter\")",
" outputFileTypeFilter.set(\"" + outputFileTypeFilter + "\")",
" includeStandardJavaModules.set([ \"java.base\" ])",
"}"));

Path javaDir = Paths.get("src/main/java/testcases");
Expand All @@ -218,12 +222,13 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou
Files.write(buildDir.resolve(javaDir.resolve("DummyJaxRs.java")),
asList("package testcases;",
"",
"import javax.ws.rs.GET;",
"import javax.ws.rs.Path;",
"import javax.ws.rs.Produces;",
"import javax.ws.rs.core.MediaType;",
"import jakarta.ws.rs.GET;",
"import jakarta.ws.rs.Path;",
"import jakarta.ws.rs.Produces;",
"import jakarta.ws.rs.core.MediaType;",
"import org.eclipse.microprofile.openapi.annotations.Operation;",
"import org.eclipse.microprofile.openapi.annotations.media.Content;",
"import org.eclipse.microprofile.openapi.annotations.media.Schema;",
"import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;",
"import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;",
"",
Expand All @@ -235,10 +240,11 @@ void smokeProject(Path buildDir, boolean withQuarkus, String taskName, String ou
" @APIResponses({",
" @APIResponse(",
" description = \"Dummy get thing.\",",
" content = @Content(mediaType = \"application/text\")",
" content = @Content(mediaType = \"application/text\",",
" schema = @Schema(implementation = java.util.concurrent.TimeUnit.class))",
" )})",
" public String dummyThing() {",
" return \"foo\";",
" public java.util.concurrent.TimeUnit dummyThing() {",
" return java.util.concurrent.TimeUnit.HOURS;",
" }",
"}"));

Expand Down Expand Up @@ -309,6 +315,9 @@ private static void checkGeneratedFiles(Path buildDir, String expectedOutputFile

JsonNode paths = root.get("paths");
assertThat(paths.get("/mypath").get("get").get("operationId").asText()).isEqualTo("dummyThing");

JsonNode schemas = root.get("components").get("schemas");
assertThat(((ArrayNode) schemas.get("TimeUnit").get("enum"))).contains(new TextNode("HOURS"));
}

if ("YAML".equals(expectedOutputFileType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ public class GenerateSchemaMojo extends AbstractMojo {
@Parameter(defaultValue = "jar", property = "includeDependenciesTypes")
private List<String> includeDependenciesTypes;

/**
* List of standard Java modules that should be made available to annotation
* scanning for introspection.
*/
@Parameter(property = "includeStandardJavaModules")
private List<String> includeStandardJavaModules;

/**
* Skip execution of the plugin.
*/
Expand Down Expand Up @@ -279,7 +286,7 @@ public void execute() throws MojoExecutionException {
if (!skip) {
try {
IndexView index = mavenDependencyIndexCreator.createIndex(mavenProject, scanDependenciesDisable,
includeDependenciesScopes, includeDependenciesTypes);
includeDependenciesScopes, includeDependenciesTypes, includeStandardJavaModules);
SmallRyeOpenAPI openAPI = generateOpenAPI(index);
write(openAPI);
} catch (Exception ex) {
Expand Down
Loading

0 comments on commit ceaf605

Please sign in to comment.