From 3002a3a4ed674ec91d29511108b4c89788ed2a58 Mon Sep 17 00:00:00 2001 From: Slawomir Jaranowski Date: Tue, 19 Mar 2024 20:50:40 +0100 Subject: [PATCH] [MJAR-62] Set Build-Jdk according to used toolchain --- pom.xml | 39 +++++--- src/it/MJAR-62-toolchain/invoker.properties | 18 ++++ src/it/MJAR-62-toolchain/pom.xml | 68 +++++++++++++ .../src/main/java/myproject/HelloWorld.java | 36 +++++++ src/it/MJAR-62-toolchain/verify.groovy | 26 +++++ .../maven/plugins/jar/AbstractJarMojo.java | 19 ++++ .../jar/ToolchainsJdkSpecification.java | 99 +++++++++++++++++++ .../apache/maven/plugins/jar/JarMojoTest.java | 20 ++-- .../plugins/jar/ToolchainsJdkVersionTest.java | 88 +++++++++++++++++ 9 files changed, 392 insertions(+), 21 deletions(-) create mode 100644 src/it/MJAR-62-toolchain/invoker.properties create mode 100644 src/it/MJAR-62-toolchain/pom.xml create mode 100644 src/it/MJAR-62-toolchain/src/main/java/myproject/HelloWorld.java create mode 100644 src/it/MJAR-62-toolchain/verify.groovy create mode 100644 src/main/java/org/apache/maven/plugins/jar/ToolchainsJdkSpecification.java create mode 100644 src/test/java/org/apache/maven/plugins/jar/ToolchainsJdkVersionTest.java diff --git a/pom.xml b/pom.xml index 667ea5c..12f3007 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,18 @@ 2022-09-12T15:53:21Z + + + + org.junit + junit-bom + 5.10.1 + pom + import + + + + org.apache.maven @@ -123,33 +135,31 @@ - junit - junit - 4.13.2 + org.junit.jupiter + junit-jupiter-api test org.apache.maven.plugin-testing maven-plugin-testing-harness - 3.3.0 + 4.0.0-alpha-2 test - org.apache.maven - maven-compat - ${mavenVersion} + org.mockito + mockito-core + 4.11.0 test - commons-io - commons-io - 2.15.1 + org.mockito + mockito-junit-jupiter + 4.11.0 test org.codehaus.plexus plexus-xml - 3.0.0 test @@ -178,6 +188,13 @@ + + + + org.eclipse.sisu + sisu-maven-plugin + + diff --git a/src/it/MJAR-62-toolchain/invoker.properties b/src/it/MJAR-62-toolchain/invoker.properties new file mode 100644 index 0000000..9904dcc --- /dev/null +++ b/src/it/MJAR-62-toolchain/invoker.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. + +invoker.toolchain.jdk.version = 17 \ No newline at end of file diff --git a/src/it/MJAR-62-toolchain/pom.xml b/src/it/MJAR-62-toolchain/pom.xml new file mode 100644 index 0000000..610d560 --- /dev/null +++ b/src/it/MJAR-62-toolchain/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + org.apache.maven.plugins + mjar-62 + MJAR-62 + jar + 1.0-SNAPSHOT + + jar plugin it + + + + + junit + junit + 4.13.2 + + + + + + + org.apache.maven.plugins + maven-toolchains-plugin + 3.1.0 + + + + toolchain + + + + + + + 17 + + + + + + + org.apache.maven.plugins + maven-jar-plugin + @project.version@ + + + + diff --git a/src/it/MJAR-62-toolchain/src/main/java/myproject/HelloWorld.java b/src/it/MJAR-62-toolchain/src/main/java/myproject/HelloWorld.java new file mode 100644 index 0000000..fd0ad83 --- /dev/null +++ b/src/it/MJAR-62-toolchain/src/main/java/myproject/HelloWorld.java @@ -0,0 +1,36 @@ +package myproject; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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. + */ + +/** + * The classic Hello World App. + */ +public class HelloWorld { + + /** + * Main method. + * + * @param args Not used + */ + public static void main( String[] args ) + { + System.out.println( "Hi!" ); + } +} \ No newline at end of file diff --git a/src/it/MJAR-62-toolchain/verify.groovy b/src/it/MJAR-62-toolchain/verify.groovy new file mode 100644 index 0000000..4fdf714 --- /dev/null +++ b/src/it/MJAR-62-toolchain/verify.groovy @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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. + */ + +import java.util.jar.JarFile + +def mrjar = new JarFile(new File(basedir, 'target/mjar-62-1.0-SNAPSHOT.jar')) +def manifest = mrjar.manifest.mainAttributes + +assert manifest.getValue("Build-Jdk-Spec") == "17" +assert manifest.getValue("Build-Tool-Jdk-Spec") == System.getProperty("java.specification.version") diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java index ab2a50f..47b8bde 100644 --- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java +++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java @@ -22,6 +22,7 @@ import java.nio.file.FileSystems; import java.util.Arrays; import java.util.Map; +import java.util.Optional; import org.apache.maven.archiver.MavenArchiveConfiguration; import org.apache.maven.archiver.MavenArchiver; @@ -34,6 +35,7 @@ import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.shared.model.fileset.FileSet; import org.apache.maven.shared.model.fileset.util.FileSetManager; +import org.apache.maven.toolchain.ToolchainManager; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.jar.JarArchiver; @@ -53,6 +55,12 @@ public abstract class AbstractJarMojo extends AbstractMojo { private static final String SEPARATOR = FileSystems.getDefault().getSeparator(); + @Component + private ToolchainsJdkSpecification toolchainsJdkSpecification; + + @Component + private ToolchainManager toolchainManager; + /** * List of files to include. Specified as fileset patterns which are relative to the input directory whose contents * is being packaged into the JAR. @@ -250,6 +258,17 @@ public File createArchive() throws MojoExecutionException { archiver.setArchiver((JarArchiver) archivers.get(archiverName)); archiver.setOutputFile(jarFile); + Optional.ofNullable(toolchainManager.getToolchainFromBuildContext("jdk", session)) + .ifPresent(toolchain -> toolchainsJdkSpecification + .getJDKSpecification(toolchain) + .ifPresent(jdkSpec -> { + archive.addManifestEntry("Build-Jdk-Spec", jdkSpec); + archive.addManifestEntry( + "Build-Tool-Jdk-Spec", System.getProperty("java.specification.version")); + archiver.setBuildJdkSpecDefaultEntry(false); + getLog().info("Set Build-Jdk-Spec based on toolchain in maven-jar-plugin " + toolchain); + })); + // configure for Reproducible Builds based on outputTimestamp value archiver.configureReproducibleBuild(outputTimestamp); diff --git a/src/main/java/org/apache/maven/plugins/jar/ToolchainsJdkSpecification.java b/src/main/java/org/apache/maven/plugins/jar/ToolchainsJdkSpecification.java new file mode 100644 index 0000000..8872632 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/jar/ToolchainsJdkSpecification.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.maven.plugins.jar; + +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.maven.toolchain.Toolchain; +import org.codehaus.plexus.util.cli.CommandLineException; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Component provided JDK specification based on toolchains. + */ +@Named +@Singleton +class ToolchainsJdkSpecification { + + private final Logger logger = LoggerFactory.getLogger(ToolchainsJdkSpecification.class); + + private final Map cache = new HashMap<>(); + + public synchronized Optional getJDKSpecification(Toolchain toolchain) { + Optional javacPath = getJavacPath(toolchain); + return javacPath.map(path -> cache.computeIfAbsent(path, this::getSpecForPath)); + } + + private Optional getJavacPath(Toolchain toolchain) { + return Optional.ofNullable(toolchain.findTool("javac")).map(Paths::get).map(this::getCanonicalPath); + } + + private Path getCanonicalPath(Path path) { + try { + return path.toRealPath(); + } catch (IOException e) { + if (path.getParent() != null) { + return getCanonicalPath(path.getParent()).resolve(path.getFileName()); + } else { + throw new UncheckedIOException(e); + } + } + } + + private String getSpecForPath(Path path) { + try { + Commandline cl = new Commandline(path.toString()); + cl.createArg().setValue("-version"); + CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer(); + CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer(); + CommandLineUtils.executeCommandLine(cl, out, err); + String version = out.getOutput().trim(); + if (version.isEmpty()) { + version = err.getOutput().trim(); + } + if (version.startsWith("javac ")) { + version = version.substring(6); + if (version.startsWith("1.")) { + version = version.substring(0, 3); + } else { + version = version.substring(0, 2); + } + return version; + } else { + logger.warn("Unrecognized output form " + path + " -version - " + version); + return null; + } + } catch (CommandLineException | IndexOutOfBoundsException e) { + logger.warn("Failed to execute: " + path + " - " + e.getMessage()); + return null; + } + } +} diff --git a/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java b/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java index 11c0696..b8b57e8 100644 --- a/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/jar/JarMojoTest.java @@ -18,28 +18,28 @@ */ package org.apache.maven.plugins.jar; -import java.io.File; +import org.apache.maven.plugin.testing.junit5.InjectMojo; +import org.apache.maven.plugin.testing.junit5.MojoTest; +import org.junit.jupiter.api.Test; -import org.apache.maven.plugin.testing.AbstractMojoTestCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Test for {@link JarMojo} * * @version $Id$ */ -public class JarMojoTest extends AbstractMojoTestCase { - private File testPom = new File(getBasedir(), "src/test/resources/unit/jar-basic-test/pom.xml"); +@MojoTest +class JarMojoTest { /** * Tests the discovery and configuration of the mojo. - * - * @throws Exception in case of an error */ - public void testJarTestEnvironment() throws Exception { - JarMojo mojo = (JarMojo) lookupMojo("jar", testPom); - + @Test + @InjectMojo(goal = "jar", pom = "classpath:/unit/jar-basic-test/pom.xml") + void testJarTestEnvironment(JarMojo mojo) { assertNotNull(mojo); - assertEquals("foo", mojo.getProject().getGroupId()); } } diff --git a/src/test/java/org/apache/maven/plugins/jar/ToolchainsJdkVersionTest.java b/src/test/java/org/apache/maven/plugins/jar/ToolchainsJdkVersionTest.java new file mode 100644 index 0000000..0d504ed --- /dev/null +++ b/src/test/java/org/apache/maven/plugins/jar/ToolchainsJdkVersionTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.maven.plugins.jar; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; +import java.util.Optional; + +import org.apache.maven.toolchain.Toolchain; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ToolchainsJdkVersionTest { + + @Mock + private Toolchain toolchain; + + private final ToolchainsJdkSpecification toolchainsJdkSpecification = new ToolchainsJdkSpecification(); + + @Test + void shouldReturnCorrectSpec() { + + Path javacPath = getJavacPath(); + Assumptions.assumeTrue(Files.isExecutable(javacPath)); + + when(toolchain.findTool("javac")).thenReturn(javacPath.toString()); + + Optional jdkVersion = toolchainsJdkSpecification.getJDKSpecification(toolchain); + Assertions.assertTrue(jdkVersion.isPresent()); + Assertions.assertEquals(System.getProperty("java.specification.version"), jdkVersion.get()); + } + + @Test + void shouldReturnEmptySpec() { + + when(toolchain.findTool("javac")).thenReturn(null); + + Optional jdkVersion = toolchainsJdkSpecification.getJDKSpecification(toolchain); + Assertions.assertFalse(jdkVersion.isPresent()); + } + + private String getJavaCName() { + if (System.getProperty("os.name", "").toLowerCase(Locale.ROOT).startsWith("win")) { + return "javac.exe"; + } else { + return "javac"; + } + } + + private Path getJavacPath() { + String javaCName = getJavaCName(); + + String javaHome = System.getProperty("java.home"); + + Path javacPath = Paths.get(javaHome, "bin", javaCName); + if (Files.isExecutable(javacPath)) { + return javacPath; + } + + // try with jre + return Paths.get(javaHome, "../bin", javaCName); + } +}