From 95014539ee9032a4852a12bd48a8a16cc6049381 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 30 Aug 2019 10:58:00 +0200 Subject: [PATCH] Abstracted bom parsing --- .../internal/buildsystem/BomParser.java | 84 +++----------- .../buildsystem/CompositeBomParser.java | 56 ++++++++++ .../buildsystem/CustomGradleParser.java | 104 ++++++++++++++++++ .../internal/buildsystem/GradleBomParser.java | 33 ++++++ .../internal/buildsystem/MavenBomParser.java | 100 +++++++++++++++++ .../buildsystem/ProjectPomUpdater.java | 3 +- ...serTests.java => MavenBomParserTests.java} | 54 +++++---- 7 files changed, 338 insertions(+), 96 deletions(-) create mode 100644 spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CompositeBomParser.java create mode 100644 spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CustomGradleParser.java create mode 100644 spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/GradleBomParser.java create mode 100644 spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/MavenBomParser.java rename spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/buildsystem/{BomParserTests.java => MavenBomParserTests.java} (70%) diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/BomParser.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/BomParser.java index 4290cac5..70410461 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/BomParser.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/BomParser.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -17,80 +17,22 @@ package org.springframework.cloud.release.internal.buildsystem; import java.io.File; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import org.apache.maven.model.Model; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.cloud.release.internal.ReleaserProperties; /** - * Parses the poms for a given project and populates versions from a release train. - * - * @author Marcin Grzejszczak + * Parses the bom and returns all parsed versions. */ -class BomParser { +public interface BomParser { - private static final Logger log = LoggerFactory.getLogger(BomParser.class); + /** + * @param clonedBom - location of the cloned BOM repository + * @return {@code true} - when this BOM parser can be applied + */ + boolean isApplicable(File clonedBom); - private final File thisProjectRoot; - - private final String thisTrainBomLocation; - - private final Pattern versionPattern; - - private final ReleaserProperties properties; - - BomParser(ReleaserProperties properties, File thisProjectRoot) { - this.thisProjectRoot = thisProjectRoot; - this.thisTrainBomLocation = properties.getPom().getThisTrainBom(); - this.versionPattern = Pattern.compile(properties.getPom().getBomVersionPattern()); - this.properties = properties; - } - - // the BOM contains all versions of projects and its parent MUST be Spring Cloud - // Dependencies Parent - VersionsFromBom versionsFromBom() { - Model model = PomReader.pom(thisProjectRoot, this.thisTrainBomLocation); - if (model == null) { - return VersionsFromBom.EMPTY_VERSION; - } - Set projects = model.getProperties().entrySet().stream() - .filter(propertyMatchesVersionPattern()).map(toProject()) - .collect(Collectors.toSet()); - String releaseTrainProjectVersion = model.getVersion(); - projects.add( - new Project(this.properties.getMetaRelease().getReleaseTrainProjectName(), - releaseTrainProjectVersion)); - VersionsFromBom bomVersions = new VersionsFromBom(this.properties, projects); - return new VersionsFromBom(properties, bomVersions, customVersions(projects)); - } - - private VersionsFromBom customVersions(Set projects) { - CustomBomParser parser = CustomBomParser.parser(this.thisProjectRoot, - this.properties, projects); - return parser.parseBom(this.thisProjectRoot, this.properties); - } - - private Predicate> propertyMatchesVersionPattern() { - return entry -> this.versionPattern.matcher(entry.getKey().toString()).matches(); - } - - private Function, Project> toProject() { - return entry -> { - Matcher matcher = this.versionPattern.matcher(entry.getKey().toString()); - // you have to first match to get info about the group - matcher.matches(); - String name = matcher.group(1); - return new Project(name, entry.getValue().toString()); - }; - } + /** + * @param thisProjectRoot - root of the clone project + * @return versions from BOM + */ + VersionsFromBom versionsFromBom(File thisProjectRoot); } diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CompositeBomParser.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CompositeBomParser.java new file mode 100644 index 00000000..af179900 --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CompositeBomParser.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2019 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 + * + * 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.springframework.cloud.release.internal.buildsystem; + +import java.io.File; + +import org.springframework.cloud.release.internal.ReleaserProperties; +import org.springframework.cloud.release.internal.ReleaserPropertiesAware; + +class CompositeBomParser implements BomParser, ReleaserPropertiesAware { + + private ReleaserProperties properties; + + CompositeBomParser(ReleaserProperties releaserProperties) { + this.properties = releaserProperties; + } + + @Override + public boolean isApplicable(File clonedBom) { + return new MavenBomParser(this.properties).isApplicable(clonedBom) + || new GradleBomParser().isApplicable(clonedBom); + } + + @Override + public VersionsFromBom versionsFromBom(File thisProjectRoot) { + return firstMatching(thisProjectRoot).versionsFromBom(thisProjectRoot); + } + + private BomParser firstMatching(File thisProjectRoot) { + BomParser gradle = new GradleBomParser(); + if (new GradleBomParser().isApplicable(thisProjectRoot)) { + return gradle; + } + return new MavenBomParser(this.properties); + } + + @Override + public void setReleaserProperties(ReleaserProperties properties) { + this.properties = properties; + } + +} diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CustomGradleParser.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CustomGradleParser.java new file mode 100644 index 00000000..314a1ba7 --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/CustomGradleParser.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013-2019 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 + * + * 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.springframework.cloud.release.internal.buildsystem; + +import java.io.File; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.cloud.release.internal.ReleaserProperties; +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * Allows to pass in some additional gradle files parser. + */ +public interface CustomGradleParser { + + /** + * Different projects can have different parsers. This method will tell whether the + * current parser should be applied or not. + * @param thisProjectRoot - location of the cloned project + * @param properties - releaser properties + * @param projects - parsed projects from the BOM + * @return {@code true} if the parser should be applied. + */ + boolean isApplicable(File thisProjectRoot, ReleaserProperties properties, + Set projects); + + /** + * When parsing a part of the BOM pom, one can add custom logic to perform project + * specific parsing. + * @param thisProjectRoot - location of the cloned project + * @param properties - releaser properties + * @return - versions retrieved from the BOM. Can be + * {@link VersionsFromBom#EMPTY_VERSION} if nothing was found. + */ + VersionsFromBom parseBom(File thisProjectRoot, ReleaserProperties properties); + + /** + * Allows to hook in custom logic for versions setting. + * @param projects - set of projects + * @param projectName - name of the project + * @param version - version of the project + * @return - a new collection with the modified versions from bom + */ + default Set setVersion(Set projects, String projectName, + String version) { + return new LinkedHashSet<>(projects); + } + + List PARSERS = SpringFactoriesLoader + .loadFactories(CustomGradleParser.class, null); + + CustomGradleParser NO_OP = new CustomGradleParser() { + @Override + public boolean isApplicable(File thisProjectRoot, ReleaserProperties properties, + Set projects) { + return true; + } + + @Override + public VersionsFromBom parseBom(File thisProjectRoot, + ReleaserProperties properties) { + return VersionsFromBom.EMPTY_VERSION; + } + + }; + + static CustomGradleParser parser(File thisProjectRoot, ReleaserProperties properties, + Set projects) { + for (CustomGradleParser parser : PARSERS) { + if (parser.isApplicable(thisProjectRoot, properties, projects)) { + return parser; + } + } + return NO_OP; + } + + static CustomGradleParser parser(ReleaserProperties properties) { + return parser(new File(properties.getWorkingDir()), properties, + Collections.emptySet()); + } + + static CustomGradleParser parser(ReleaserProperties properties, + Set projects) { + return parser(new File(properties.getWorkingDir()), properties, projects); + } + +} diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/GradleBomParser.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/GradleBomParser.java new file mode 100644 index 00000000..72941079 --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/GradleBomParser.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2019 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 + * + * 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.springframework.cloud.release.internal.buildsystem; + +import java.io.File; + +class GradleBomParser implements BomParser { + + @Override + public boolean isApplicable(File clonedBom) { + return new File(clonedBom, "build.gradle").exists(); + } + + @Override + public VersionsFromBom versionsFromBom(File thisProjectRoot) { + return null; + } + +} diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/MavenBomParser.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/MavenBomParser.java new file mode 100644 index 00000000..83399e69 --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/MavenBomParser.java @@ -0,0 +1,100 @@ +/* + * Copyright 2013-2019 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.cloud.release.internal.buildsystem; + +import java.io.File; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.maven.model.Model; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.release.internal.ReleaserProperties; + +/** + * Parses the poms for a given project and populates versions from a release train. + * + * @author Marcin Grzejszczak + */ +class MavenBomParser implements BomParser { + + private static final Logger log = LoggerFactory.getLogger(MavenBomParser.class); + + private final String thisTrainBomLocation; + + private final Pattern versionPattern; + + private final ReleaserProperties properties; + + MavenBomParser(ReleaserProperties properties) { + this.thisTrainBomLocation = properties.getPom().getThisTrainBom(); + this.versionPattern = Pattern.compile(properties.getPom().getBomVersionPattern()); + this.properties = properties; + } + + @Override + public boolean isApplicable(File clonedBom) { + return new File(clonedBom, "pom.xml").exists(); + } + + // the BOM contains all versions of projects and its parent MUST be Spring Cloud + // Dependencies Parent + @Override + public VersionsFromBom versionsFromBom(File thisProjectRoot) { + Model model = PomReader.pom(thisProjectRoot, this.thisTrainBomLocation); + if (model == null) { + return VersionsFromBom.EMPTY_VERSION; + } + Set projects = model.getProperties().entrySet().stream() + .filter(propertyMatchesVersionPattern()).map(toProject()) + .collect(Collectors.toSet()); + String releaseTrainProjectVersion = model.getVersion(); + projects.add( + new Project(this.properties.getMetaRelease().getReleaseTrainProjectName(), + releaseTrainProjectVersion)); + VersionsFromBom bomVersions = new VersionsFromBom(this.properties, projects); + return new VersionsFromBom(properties, bomVersions, + customVersions(thisProjectRoot, projects)); + } + + private VersionsFromBom customVersions(File thisProjectRoot, Set projects) { + CustomBomParser parser = CustomBomParser.parser(thisProjectRoot, this.properties, + projects); + return parser.parseBom(thisProjectRoot, this.properties); + } + + private Predicate> propertyMatchesVersionPattern() { + return entry -> this.versionPattern.matcher(entry.getKey().toString()).matches(); + } + + private Function, Project> toProject() { + return entry -> { + Matcher matcher = this.versionPattern.matcher(entry.getKey().toString()); + // you have to first match to get info about the group + matcher.matches(); + String name = matcher.group(1); + return new Project(name, entry.getValue().toString()); + }; + } + +} diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/ProjectPomUpdater.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/ProjectPomUpdater.java index 71611cd2..7f314542 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/ProjectPomUpdater.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/buildsystem/ProjectPomUpdater.java @@ -96,8 +96,7 @@ public class ProjectPomUpdater implements ReleaserPropertiesAware { VersionsFromBom versionsFromBom = CACHE.computeIfAbsent(branch, s -> { File clonedBom = this.gitRepo.cloneReleaseTrainProject(); this.gitRepo.checkout(clonedBom, branch); - BomParser releaseTrainBomParser = new BomParser(this.properties, clonedBom); - return releaseTrainBomParser.versionsFromBom(); + return new CompositeBomParser(this.properties).versionsFromBom(clonedBom); }); if (updateFixedVersions) { log.info("Will update the following versions manually [{}]", diff --git a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/buildsystem/BomParserTests.java b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/buildsystem/MavenBomParserTests.java similarity index 70% rename from spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/buildsystem/BomParserTests.java rename to spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/buildsystem/MavenBomParserTests.java index 61fac89d..81585bbe 100644 --- a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/buildsystem/BomParserTests.java +++ b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/buildsystem/MavenBomParserTests.java @@ -31,7 +31,7 @@ import static org.assertj.core.api.BDDAssertions.thenThrownBy; /** * @author Marcin Grzejszczak */ -public class BomParserTests { +public class MavenBomParserTests { File springCloudReleaseProject; @@ -45,9 +45,11 @@ public class BomParserTests { @Test public void should_throw_exception_when_boot_pom_is_missing() { - BomParser parser = new BomParser(this.properties, new File(".")); + MavenBomParser parser = new MavenBomParser(this.properties); + File file = new File("."); - thenThrownBy(parser::versionsFromBom).isInstanceOf(IllegalStateException.class) + thenThrownBy(() -> parser.versionsFromBom(file)) + .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Pom is not present"); } @@ -55,27 +57,28 @@ public class BomParserTests { public void should_throw_exception_when_null_is_passed_to_boot() { this.properties.getPom().setPomWithBootStarterParent(null); this.properties.getPom().setThisTrainBom(null); - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - thenThrownBy(parser::versionsFromBom).isInstanceOf(IllegalStateException.class) + thenThrownBy(() -> parser.versionsFromBom(this.springCloudReleaseProject)) + .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Pom is not present"); } @Test public void should_throw_exception_when_boot_version_is_missing_in_pom() { this.properties.getPom().setPomWithBootStarterParent("pom.xml"); - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - thenThrownBy(parser::versionsFromBom).isInstanceOf(IllegalStateException.class) - .hasMessageContaining( + thenThrownBy(() -> parser.versionsFromBom(this.springCloudReleaseProject)) + .isInstanceOf(IllegalStateException.class).hasMessageContaining( "The pom doesn't have a [spring-boot-starter-parent] artifact id"); } @Test public void should_populate_sc_release_version() { - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - String scReleaseVersion = parser.versionsFromBom() + String scReleaseVersion = parser.versionsFromBom(this.springCloudReleaseProject) .versionForProject("spring-cloud-release"); then(scReleaseVersion).isEqualTo("Dalston.BUILD-SNAPSHOT"); @@ -83,18 +86,20 @@ public class BomParserTests { @Test public void should_populate_boot_version() { - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - String bootVersion = parser.versionsFromBom().versionForProject("spring-boot"); + String bootVersion = parser.versionsFromBom(this.springCloudReleaseProject) + .versionForProject("spring-boot"); then(bootVersion).isEqualTo("1.5.1.BUILD-SNAPSHOT"); } @Test public void should_throw_exception_when_cloud_pom_is_missing() { - BomParser parser = new BomParser(this.properties, new File(".")); + MavenBomParser parser = new MavenBomParser(this.properties); - thenThrownBy(parser::versionsFromBom).isInstanceOf(IllegalStateException.class) + thenThrownBy(() -> parser.versionsFromBom(new File("."))) + .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Pom is not present"); } @@ -102,9 +107,10 @@ public class BomParserTests { public void should_throw_exception_when_null_is_passed_to_cloud() { this.properties.getPom().setPomWithBootStarterParent(null); this.properties.getPom().setThisTrainBom(null); - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - thenThrownBy(parser::versionsFromBom).isInstanceOf(IllegalStateException.class) + thenThrownBy(() -> parser.versionsFromBom(this.springCloudReleaseProject)) + .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Pom is not present"); } @@ -112,18 +118,19 @@ public class BomParserTests { public void should_throw_exception_when_cloud_version_is_missing_in_pom() { this.properties.getPom().setPomWithBootStarterParent("pom.xml"); this.properties.getPom().setThisTrainBom("pom.xml"); - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - thenThrownBy(parser::versionsFromBom).isInstanceOf(IllegalStateException.class) - .hasMessageContaining( + thenThrownBy(() -> parser.versionsFromBom(this.springCloudReleaseProject)) + .isInstanceOf(IllegalStateException.class).hasMessageContaining( "The pom doesn't have a [spring-cloud-dependencies-parent] artifact id"); } @Test public void should_populate_cloud_version() { - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - VersionsFromBom cloudVersionsFromBom = parser.versionsFromBom(); + VersionsFromBom cloudVersionsFromBom = parser + .versionsFromBom(this.springCloudReleaseProject); then(cloudVersionsFromBom.versionForProject("spring-cloud-build")) .isEqualTo("1.3.1.BUILD-SNAPSHOT"); @@ -132,9 +139,10 @@ public class BomParserTests { @Test public void should_populate_boot_and_cloud_version() { - BomParser parser = new BomParser(this.properties, this.springCloudReleaseProject); + MavenBomParser parser = new MavenBomParser(this.properties); - VersionsFromBom cloudVersionsFromBom = parser.versionsFromBom(); + VersionsFromBom cloudVersionsFromBom = parser + .versionsFromBom(this.springCloudReleaseProject); then(cloudVersionsFromBom.versionForProject("spring-boot")) .isEqualTo("1.5.1.BUILD-SNAPSHOT");