From 3ed65f23834176aa1be9618a2f43ef2cfe066429 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 25 Jan 2022 12:11:19 +0000 Subject: [PATCH] Avoid running Maven plugin ITs for each s-b-dependencies change Closes gh-28781 --- .../build/mavenplugin/MavenPluginPlugin.java | 141 +++++++++++++++++- .../spring-boot-maven-plugin/build.gradle | 9 +- .../maven/EclipseM2eIntegrationTests.java | 5 +- .../boot/maven/MavenBuild.java | 16 +- .../boot/maven/SpringBootDependenciesBom.java | 80 ---------- .../springframework/boot/maven/Versions.java | 60 ++++++++ 6 files changed, 206 insertions(+), 105 deletions(-) delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/SpringBootDependenciesBom.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/Versions.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java index 18fcffb4932..18cb7be8be3 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -17,16 +17,31 @@ package org.springframework.boot.build.mavenplugin; import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Arrays; +import java.util.Properties; +import java.util.function.BiConsumer; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; import io.spring.javaformat.formatter.FileEdit; import io.spring.javaformat.formatter.FileFormatter; import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; @@ -41,6 +56,8 @@ import org.gradle.api.attributes.DocsType; import org.gradle.api.attributes.Usage; import org.gradle.api.file.CopySpec; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; @@ -50,8 +67,11 @@ import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Copy; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -60,10 +80,15 @@ import org.gradle.api.tasks.TaskExecutionException; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.external.javadoc.StandardJavadocDocletOptions; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.MavenRepositoryPlugin; import org.springframework.boot.build.test.IntegrationTestPlugin; +import org.springframework.core.CollectionFactory; +import org.springframework.util.Assert; /** * Plugin for building Spring Boot's Maven Plugin. @@ -88,6 +113,7 @@ public class MavenPluginPlugin implements Plugin { generateHelpMojoTask); addDocumentPluginGoalsTask(project, generatePluginDescriptorTask); addPrepareMavenBinariesTask(project); + addExtractVersionPropertiesTask(project); } private void configurePomPackaging(Project project) { @@ -240,6 +266,14 @@ public class MavenPluginPlugin implements Plugin { return input.replace("{{version}}", project.getVersion().toString()); } + private void addExtractVersionPropertiesTask(Project project) { + ExtractVersionProperties extractVersionProperties = project.getTasks().create("extractVersionProperties", + ExtractVersionProperties.class); + extractVersionProperties.setEffectiveBoms(project.getConfigurations().create("versionProperties")); + extractVersionProperties.getDestination().set(project.getLayout().getBuildDirectory().dir("generated-resources") + .map((dir) -> dir.file("extracted-versions.properties"))); + } + public static class FormatHelpMojoSourceTask extends DefaultTask { private Task generator; @@ -361,4 +395,109 @@ public class MavenPluginPlugin implements Plugin { } + public static class ExtractVersionProperties extends DefaultTask { + + private final RegularFileProperty destination; + + private FileCollection effectiveBoms; + + public ExtractVersionProperties() { + this.destination = getProject().getObjects().fileProperty(); + } + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + public FileCollection getEffectiveBoms() { + return this.effectiveBoms; + } + + public void setEffectiveBoms(FileCollection effectiveBoms) { + this.effectiveBoms = effectiveBoms; + } + + @OutputFile + public RegularFileProperty getDestination() { + return this.destination; + } + + @TaskAction + public void extractVersionProperties() { + EffectiveBom effectiveBom = new EffectiveBom(this.effectiveBoms.getSingleFile()); + Properties versions = extractVersionProperties(effectiveBom); + writeProperties(versions); + } + + private void writeProperties(Properties versions) { + File outputFile = this.destination.getAsFile().get(); + outputFile.getParentFile().mkdirs(); + try (Writer writer = new FileWriter(outputFile)) { + versions.store(writer, null); + } + catch (IOException ex) { + throw new GradleException("Failed to write extracted version properties", ex); + } + } + + private Properties extractVersionProperties(EffectiveBom effectiveBom) { + Properties versions = CollectionFactory.createSortedProperties(true); + versions.setProperty("project.version", effectiveBom.version()); + effectiveBom.property("log4j2.version", versions::setProperty); + effectiveBom.property("maven-jar-plugin.version", versions::setProperty); + effectiveBom.property("maven-war-plugin.version", versions::setProperty); + effectiveBom.property("build-helper-maven-plugin.version", versions::setProperty); + effectiveBom.property("spring-framework.version", versions::setProperty); + effectiveBom.property("jakarta-servlet.version", versions::setProperty); + effectiveBom.property("kotlin.version", versions::setProperty); + return versions; + } + + } + + private static final class EffectiveBom { + + private final Document document; + + private final XPath xpath; + + private EffectiveBom(File bomFile) { + this.document = loadDocument(bomFile); + this.xpath = XPathFactory.newInstance().newXPath(); + } + + private Document loadDocument(File bomFile) { + try { + try (InputStream inputStream = new FileInputStream(bomFile)) { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + return builder.parse(inputStream); + } + } + catch (ParserConfigurationException | SAXException | IOException ex) { + throw new IllegalStateException(ex); + } + } + + private String version() { + return get("version"); + } + + private void property(String name, BiConsumer handler) { + handler.accept(name, get("properties/" + name)); + } + + private String get(String expression) { + try { + Node node = (Node) this.xpath.compile("/project/" + expression).evaluate(this.document, + XPathConstants.NODE); + String text = (node != null) ? node.getTextContent() : null; + Assert.hasLength(text, () -> "No result for expression " + expression); + return text; + } + catch (XPathExpressionException ex) { + throw new IllegalStateException(ex); + } + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/build.gradle index a2fe33e5f09..a8e7b881fb1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/build.gradle @@ -26,8 +26,6 @@ dependencies { compileOnly("org.apache.maven.plugin-tools:maven-plugin-annotations") compileOnly("org.sonatype.plexus:plexus-build-api") - dependenciesBom(project(path: ":spring-boot-project:spring-boot-dependencies", configuration: "effectiveBom")) - implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform")) implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-tools")) implementation("org.apache.maven.shared:maven-common-artifact-filters") { @@ -62,11 +60,8 @@ dependencies { testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.springframework:spring-core") -} -task syncSpringBootDependenciesBom(type: Sync) { - destinationDir = file("${buildDir}/generated-resources/org/springframework/boot/maven") - from configurations.dependenciesBom + versionProperties(project(path: ":spring-boot-project:spring-boot-dependencies", configuration: "effectiveBom")) } syncDocumentationSourceForAsciidoctor { @@ -77,7 +72,7 @@ syncDocumentationSourceForAsciidoctor { sourceSets { intTest { - output.dir("${buildDir}/generated-resources", builtBy: "syncSpringBootDependenciesBom") + output.dir("${buildDir}/generated-resources", builtBy: "extractVersionProperties") } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/EclipseM2eIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/EclipseM2eIntegrationTests.java index 6672c557e6f..255127377e7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/EclipseM2eIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/EclipseM2eIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -36,8 +36,7 @@ class EclipseM2eIntegrationTests { @Test // gh-21992 void pluginPomIncludesOptionalShadeDependency() throws Exception { - SpringBootDependenciesBom bom = new SpringBootDependenciesBom(); - String version = bom.get("version"); + String version = new Versions().get("project.version"); File repository = new File("build/int-test-maven-repository"); File pluginDirectory = new File(repository, "org/springframework/boot/spring-boot-maven-plugin/" + version); File[] pomFiles = pluginDirectory.listFiles(this::isPomFile); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/MavenBuild.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/MavenBuild.java index 05641cb73a2..93ab76e2aca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/MavenBuild.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/MavenBuild.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -87,25 +87,13 @@ class MavenBuild { private Map getPomReplacements() { Map replacements = new HashMap<>(); - SpringBootDependenciesBom bom = new SpringBootDependenciesBom(); replacements.put("java.version", "1.8"); replacements.put("project.groupId", "org.springframework.boot"); replacements.put("project.artifactId", "spring-boot-maven-plugin"); - replacements.put("project.version", bom.get("version")); - putReplacement(replacements, bom, "log4j2.version"); - putReplacement(replacements, bom, "maven-jar-plugin.version"); - putReplacement(replacements, bom, "maven-war-plugin.version"); - putReplacement(replacements, bom, "build-helper-maven-plugin.version"); - putReplacement(replacements, bom, "spring-framework.version"); - putReplacement(replacements, bom, "jakarta-servlet.version"); - putReplacement(replacements, bom, "kotlin.version"); + replacements.putAll(new Versions().asMap()); return Collections.unmodifiableMap(replacements); } - private void putReplacement(Map replacements, SpringBootDependenciesBom bom, String property) { - replacements.put(property, bom.get("properties/" + property)); - } - MavenBuild project(String project) { this.projectDir = new File("src/intTest/projects/" + project); return this; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/SpringBootDependenciesBom.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/SpringBootDependenciesBom.java deleted file mode 100644 index 9a3e0893562..00000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/SpringBootDependenciesBom.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-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.springframework.boot.maven; - -import java.io.IOException; -import java.io.InputStream; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; - -import org.springframework.util.Assert; - -/** - * Provides access to values in the spring-boot-dependencies effective BOM. - * - * @author Phillip Webb - */ -class SpringBootDependenciesBom { - - private static final String XML = "spring-boot-dependencies-effective-bom.xml"; - - private final Document document; - - private final XPath xpath; - - SpringBootDependenciesBom() { - this.document = loadDocument(); - this.xpath = XPathFactory.newInstance().newXPath(); - } - - private Document loadDocument() { - try { - try (InputStream inputStream = getClass().getResourceAsStream(XML)) { - DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = builderFactory.newDocumentBuilder(); - return builder.parse(inputStream); - } - } - catch (ParserConfigurationException | SAXException | IOException ex) { - throw new IllegalStateException(ex); - } - } - - String get(String expression) { - try { - Node node = (Node) this.xpath.compile("/project/" + expression).evaluate(this.document, - XPathConstants.NODE); - String text = (node != null) ? node.getTextContent() : null; - Assert.hasLength(text, () -> "No result for expression " + expression); - return text; - } - catch (XPathExpressionException ex) { - throw new IllegalStateException(ex); - } - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/Versions.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/Versions.java new file mode 100644 index 00000000000..40f9a08a6b4 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/Versions.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2022 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.springframework.boot.maven; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * Provides access to various versions. + * + * @author Andy Wilkinson + */ +class Versions { + + private final Map versions; + + Versions() { + this.versions = loadVersions(); + } + + private static Map loadVersions() { + try (InputStream input = Versions.class.getClassLoader().getResourceAsStream("extracted-versions.properties")) { + Properties properties = new Properties(); + properties.load(input); + Map versions = new HashMap<>(); + properties.forEach((key, value) -> versions.put((String) key, (String) value)); + return versions; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + String get(String name) { + return this.versions.get(name); + } + + Map asMap() { + return Collections.unmodifiableMap(this.versions); + } + +}