diff --git a/build-logic/common-plugins/src/main/kotlin/org.graalvm.build.publishing.gradle.kts b/build-logic/common-plugins/src/main/kotlin/org.graalvm.build.publishing.gradle.kts index ef318324f..fe3308a26 100644 --- a/build-logic/common-plugins/src/main/kotlin/org.graalvm.build.publishing.gradle.kts +++ b/build-logic/common-plugins/src/main/kotlin/org.graalvm.build.publishing.gradle.kts @@ -58,7 +58,7 @@ val mavenExtension = project.extensions.create("maven").also { it.description.convention(project.description) } -val publishingTasks = tasks.withType() +val publishingTasks = tasks.withType() .matching { it.name.endsWith("ToCommonRepository") } val repositoryElements by configurations.creating { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c7af45952..d74829b0a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ maven = "3.8.6" mavenAnnotations = "3.6.4" mavenEmbedder = "3.8.6" mavenWagon = "3.4.3" -graalvm = "22.3.5" +graalvm = "23.0.2" jackson = "2.13.5" junitPlatform = "1.10.0" junitJupiter = "5.10.0" diff --git a/native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/GenerateRuntimeMetadata.java b/native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/GenerateRuntimeMetadata.java new file mode 100644 index 000000000..8d93f2fd8 --- /dev/null +++ b/native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/GenerateRuntimeMetadata.java @@ -0,0 +1,70 @@ +/* + * Copyright 2003-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.graalvm.build.maven; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public abstract class GenerateRuntimeMetadata extends DefaultTask { + @Input + public abstract Property getClassName(); + + @Input + public abstract MapProperty getMetadata(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @TaskAction + public void generateClass() throws IOException { + String fqcn = getClassName().get(); + Map metadata = getMetadata().get(); + File outputDir = getOutputDirectory().getAsFile().get(); + String packageName = fqcn.substring(0, fqcn.lastIndexOf(".")); + String packagePath = packageName.replace(".", "/"); + String className = fqcn.substring(fqcn.lastIndexOf(".") + 1); + Path outputPath = outputDir.toPath().resolve(packagePath); + Files.createDirectories(outputPath); + Path outputFile = outputPath.resolve(className + ".java"); + try (PrintWriter writer = new PrintWriter(outputFile.toFile(), StandardCharsets.UTF_8)) { + writer.println("package " + packageName + ";"); + writer.println(); + writer.println("public abstract class " + className + " {"); + writer.println(" private " + className + "() { }"); + writer.println(); + for (Map.Entry entry : metadata.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + writer.println(" public static final String " + key + " = \"" + value + "\";"); + } + writer.println(); + writer.println("}"); + } + } +} diff --git a/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-plugin.gradle.kts b/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-plugin.gradle.kts index 774d0944f..6ceac28e4 100644 --- a/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-plugin.gradle.kts +++ b/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-plugin.gradle.kts @@ -1,4 +1,5 @@ import org.graalvm.build.maven.GeneratePluginDescriptor +import org.graalvm.build.maven.GenerateRuntimeMetadata import org.gradle.api.publish.maven.tasks.GenerateMavenPom import org.gradle.api.tasks.Copy import org.gradle.kotlin.dsl.register @@ -73,6 +74,22 @@ val generatePluginDescriptor = tasks.register("generat outputDirectory.set(project.layout.buildDirectory.dir("generated/maven-plugin")) } +val writeConstants = tasks.register("writeRuntimeMetadata") { + className.set("org.graalvm.buildtools.maven.RuntimeMetadata") + outputDirectory.set(layout.buildDirectory.dir("generated/runtime-metadata")) + metadata.put("GROUP_ID", project.group as String) + metadata.put("VERSION", project.version as String) + metadata.put("JUNIT_PLATFORM_NATIVE_ARTIFACT_ID", "junit-platform-native") +} + +sourceSets { + main { + java { + srcDir(writeConstants) + } + } +} + tasks { jar { from(generatePluginDescriptor) diff --git a/native-maven-plugin/build.gradle.kts b/native-maven-plugin/build.gradle.kts index 78032343d..dfe02041e 100644 --- a/native-maven-plugin/build.gradle.kts +++ b/native-maven-plugin/build.gradle.kts @@ -60,11 +60,9 @@ maven { } dependencies { - implementation(libs.junitPlatformNative) implementation(libs.utils) implementation(libs.jackson.databind) implementation(libs.jvmReachabilityMetadata) - implementation(libs.graalvm.svm) implementation(libs.plexus.utils) implementation(libs.plexus.xml) @@ -177,4 +175,6 @@ tasks { tasks.withType().configureEach { configFile = layout.projectDirectory.dir("../config/checkstyle.xml").asFile + // generated code + exclude("**/RuntimeMetadata*") } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java index aaf95fbe7..a59009a57 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java @@ -50,32 +50,43 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResolutionException; +import org.eclipse.aether.resolution.DependencyResult; import org.graalvm.buildtools.utils.NativeImageConfigurationUtils; -import org.graalvm.junit.platform.JUnitPlatformFeature; -import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; -import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.graalvm.buildtools.utils.NativeImageConfigurationUtils.NATIVE_TESTS_EXE; /** * This goal builds and runs native tests. + * * @author Sebastien Deleuze */ @Mojo(name = "test", defaultPhase = LifecyclePhase.TEST, threadSafe = true, - requiresDependencyResolution = ResolutionScope.TEST, - requiresDependencyCollection = ResolutionScope.TEST) + requiresDependencyResolution = ResolutionScope.TEST, + requiresDependencyCollection = ResolutionScope.TEST) public class NativeTestMojo extends AbstractNativeImageMojo { @Parameter(property = "skipTests", defaultValue = "false") @@ -89,31 +100,39 @@ protected void populateApplicationClasspath() throws MojoExecutionException { super.populateApplicationClasspath(); imageClasspath.add(Paths.get(project.getBuild().getTestOutputDirectory())); project.getBuild() - .getTestResources() - .stream() - .map(FileSet::getDirectory) - .map(Paths::get) - .forEach(imageClasspath::add); + .getTestResources() + .stream() + .map(FileSet::getDirectory) + .map(Paths::get) + .forEach(imageClasspath::add); } @Override protected List getDependencyScopes() { return Arrays.asList( - Artifact.SCOPE_COMPILE, - Artifact.SCOPE_RUNTIME, - Artifact.SCOPE_TEST, - Artifact.SCOPE_COMPILE_PLUS_RUNTIME + Artifact.SCOPE_COMPILE, + Artifact.SCOPE_RUNTIME, + Artifact.SCOPE_TEST, + Artifact.SCOPE_COMPILE_PLUS_RUNTIME ); } @Override protected void addDependenciesToClasspath() throws MojoExecutionException { super.addDependenciesToClasspath(); + Set modules = new HashSet<>(); + //noinspection SimplifyStreamApiCallChains pluginArtifacts.stream() - .filter(it -> it.getGroupId().startsWith(NativeImageConfigurationUtils.MAVEN_GROUP_ID) || it.getGroupId().startsWith("org.junit")) - .map(it -> it.getFile().toPath()) - .forEach(imageClasspath::add); - findNativePlatformJar().ifPresent(imageClasspath::add); + // do not use peek as Stream implementations are free to discard it + .map(a -> { + modules.add(new Module(a.getGroupId(), a.getArtifactId())); + return a; + }) + .filter(it -> it.getGroupId().startsWith(NativeImageConfigurationUtils.MAVEN_GROUP_ID) || it.getGroupId().startsWith("org.junit")) + .map(it -> it.getFile().toPath()) + .forEach(imageClasspath::add); + var jars = findJunitPlatformNativeJars(modules); + imageClasspath.addAll(jars); } @Override @@ -142,7 +161,7 @@ public void execute() throws MojoExecutionException { systemProperties = new HashMap<>(); } systemProperties.put("junit.platform.listeners.uid.tracking.output.dir", - NativeExtension.testIdsDirectory(outputDirectory.getAbsolutePath())); + NativeExtension.testIdsDirectory(outputDirectory.getAbsolutePath())); imageName = NATIVE_TESTS_EXE; mainClass = "org.graalvm.junit.platform.NativeImageJUnitLauncher"; @@ -248,15 +267,71 @@ private static Stream findFiles(Path dir, String prefix) throws IOExceptio return Stream.empty(); } return Files.find(dir, Integer.MAX_VALUE, - (path, basicFileAttributes) -> (basicFileAttributes.isRegularFile() - && path.getFileName().toString().startsWith(prefix))); + (path, basicFileAttributes) -> (basicFileAttributes.isRegularFile() + && path.getFileName().toString().startsWith(prefix))); } - private static Optional findNativePlatformJar() { + private List findJunitPlatformNativeJars(Set modulesAlreadyOnClasspath) { + RepositorySystemSession repositorySession = mavenSession.getRepositorySession(); + DefaultRepositorySystemSession newSession = new DefaultRepositorySystemSession(repositorySession); + CollectRequest collectRequest = new CollectRequest(); + List repositories = project.getRemoteProjectRepositories(); + collectRequest.setRepositories(repositories); + DefaultArtifact artifact = new DefaultArtifact( + RuntimeMetadata.GROUP_ID, + RuntimeMetadata.JUNIT_PLATFORM_NATIVE_ARTIFACT_ID, + null, + "jar", + RuntimeMetadata.VERSION + ); + Dependency dependency = new Dependency(artifact, "runtime"); + collectRequest.addDependency(dependency); + DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null); + DependencyResult dependencyResult; try { - return Optional.of(new File(JUnitPlatformFeature.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toPath()); - } catch (URISyntaxException e) { - return Optional.empty(); + dependencyResult = repositorySystem.resolveDependencies(newSession, dependencyRequest); + } catch (DependencyResolutionException e) { + return Collections.emptyList(); + } + return dependencyResult.getArtifactResults() + .stream() + .map(ArtifactResult::getArtifact) + .filter(a -> !modulesAlreadyOnClasspath.contains(new Module(a.getGroupId(), a.getArtifactId()))) + .map(a -> a.getFile().toPath()) + .collect(Collectors.toList()); + } + + private static final class Module { + private final String groupId; + private final String artifactId; + + private Module(String groupId, String artifactId) { + this.groupId = groupId; + this.artifactId = artifactId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Module module = (Module) o; + + if (!groupId.equals(module.groupId)) { + return false; + } + return artifactId.equals(module.artifactId); + } + + @Override + public int hashCode() { + int result = groupId.hashCode(); + result = 31 * result + artifactId.hashCode(); + return result; } } }