From 9f49c1bbd1aaaccd84c7ad20a920ce2152f48916 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 1 Feb 2024 10:28:09 +0100 Subject: [PATCH] Upgrade to GraalVM SDK 23.0.2 The Maven plugin was using the dependency at compile time, when it should only have been used at runtime. Unfortunately, if the dependency is added as a runtime only dependency, then the Maven plugin loading system will fail. Therefore, this commit changes how the JUnit Platform Native dependency is resolved by the plugin to do it in a similar way as what is done in the Gradle plugin, by resolving it at runtime. --- .../org.graalvm.build.publishing.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- .../build/maven/GenerateRuntimeMetadata.java | 70 ++++++++++ .../org.graalvm.build.maven-plugin.gradle.kts | 17 +++ native-maven-plugin/build.gradle.kts | 4 +- .../buildtools/maven/NativeTestMojo.java | 127 ++++++++++++++---- 6 files changed, 192 insertions(+), 30 deletions(-) create mode 100644 native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/GenerateRuntimeMetadata.java 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; } } }