Abstracted bom parsing

This commit is contained in:
Marcin Grzejszczak
2019-08-30 10:58:00 +02:00
parent 3c2b870568
commit 95014539ee
7 changed files with 338 additions and 96 deletions

View File

@@ -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<Project> 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<Project> projects) {
CustomBomParser parser = CustomBomParser.parser(this.thisProjectRoot,
this.properties, projects);
return parser.parseBom(this.thisProjectRoot, this.properties);
}
private Predicate<Map.Entry<Object, Object>> propertyMatchesVersionPattern() {
return entry -> this.versionPattern.matcher(entry.getKey().toString()).matches();
}
private Function<Map.Entry<Object, Object>, 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);
}

View File

@@ -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;
}
}

View File

@@ -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<Project> 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<Project> setVersion(Set<Project> projects, String projectName,
String version) {
return new LinkedHashSet<>(projects);
}
List<CustomGradleParser> PARSERS = SpringFactoriesLoader
.loadFactories(CustomGradleParser.class, null);
CustomGradleParser NO_OP = new CustomGradleParser() {
@Override
public boolean isApplicable(File thisProjectRoot, ReleaserProperties properties,
Set<Project> projects) {
return true;
}
@Override
public VersionsFromBom parseBom(File thisProjectRoot,
ReleaserProperties properties) {
return VersionsFromBom.EMPTY_VERSION;
}
};
static CustomGradleParser parser(File thisProjectRoot, ReleaserProperties properties,
Set<Project> 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<Project> projects) {
return parser(new File(properties.getWorkingDir()), properties, projects);
}
}

View File

@@ -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;
}
}

View File

@@ -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<Project> 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<Project> projects) {
CustomBomParser parser = CustomBomParser.parser(thisProjectRoot, this.properties,
projects);
return parser.parseBom(thisProjectRoot, this.properties);
}
private Predicate<Map.Entry<Object, Object>> propertyMatchesVersionPattern() {
return entry -> this.versionPattern.matcher(entry.getKey().toString()).matches();
}
private Function<Map.Entry<Object, Object>, 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());
};
}
}

View File

@@ -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 [{}]",

View File

@@ -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");