Added gradle substitution feature

fixes #41
This commit is contained in:
Marcin Grzejszczak
2017-08-21 12:16:18 +02:00
parent 2a877352fb
commit 580b2a7e46
14 changed files with 318 additions and 17 deletions

View File

@@ -105,6 +105,10 @@ the `spring-cloud-cli.version` to `1.0.0.RELEASE` regardless of what was set in
- `releaser.maven.publish-docs-commands` - Command to be executed to deploy a built project. If present `{{version}}` will be replaced by the proper version.
Defaults to the standard Spring Cloud wget and execution of ghpages.
- `releaser.maven.wait-time-in-minutes` - Max wait time in minutes for the process to finish. Defaults to `20`
- `releaser.gradle.gradle-props-substitution` - a map containing a `key` which is a property key inside `gradle.properties` and a `value` of
a project name. E.g. in `gradle.properties` you have `foo=1.0.0.BUILD-SNAPSHOT` and you would like `spring-cloud-contract` version to
be set there. Just provide a mapping for the `gradle-props-substition` looking like this `foo=spring-cloud-contract` and the result
(e.g for sc-contract version `2.0.0.RELEASE`) will be an updated `gradle.properties` with entry `foo=2.0.0.RELEASE`
- `releaser.pom.branch` - Which branch of Spring Cloud Release should be checked out. Defaults to "master",
- `releaser.pom.ignored-pom-regex` - List of regular expressions of ignored poms. Defaults to test projects and samples.,
- `releaser.working-dir` - By default Releaser assumes running the program from the current working directory.

View File

@@ -95,6 +95,10 @@ the `spring-cloud-cli.version` to `1.0.0.RELEASE` regardless of what was set in
- `releaser.maven.publish-docs-commands` - Command to be executed to deploy a built project. If present `{{version}}` will be replaced by the proper version.
Defaults to the standard Spring Cloud wget and execution of ghpages.
- `releaser.maven.wait-time-in-minutes` - Max wait time in minutes for the process to finish. Defaults to `20`
- `releaser.gradle.gradle-props-substitution` - a map containing a `key` which is a property key inside `gradle.properties` and a `value` of
a project name. E.g. in `gradle.properties` you have `foo=1.0.0.BUILD-SNAPSHOT` and you would like `spring-cloud-contract` version to
be set there. Just provide a mapping for the `gradle-props-substition` looking like this `foo=spring-cloud-contract` and the result
(e.g for sc-contract version `2.0.0.RELEASE`) will be an updated `gradle.properties` with entry `foo=2.0.0.RELEASE`
- `releaser.pom.branch` - Which branch of Spring Cloud Release should be checked out. Defaults to "master",
- `releaser.pom.ignored-pom-regex` - List of regular expressions of ignored poms. Defaults to test projects and samples.,
- `releaser.working-dir` - By default Releaser assumes running the program from the current working directory.

View File

@@ -5,6 +5,7 @@ import java.lang.invoke.MethodHandles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.release.internal.gradle.GradleUpdater;
import org.springframework.cloud.release.internal.template.TemplateGenerator;
import org.springframework.cloud.release.internal.git.ProjectGitUpdater;
import org.springframework.cloud.release.internal.pom.ProjectPomUpdater;
@@ -22,13 +23,16 @@ public class Releaser {
private final ProjectBuilder projectBuilder;
private final ProjectGitUpdater projectGitUpdater;
private final TemplateGenerator templateGenerator;
private final GradleUpdater gradleUpdater;
public Releaser(ProjectPomUpdater projectPomUpdater, ProjectBuilder projectBuilder,
ProjectGitUpdater projectGitUpdater, TemplateGenerator templateGenerator) {
ProjectGitUpdater projectGitUpdater, TemplateGenerator templateGenerator,
GradleUpdater gradleUpdater) {
this.projectPomUpdater = projectPomUpdater;
this.projectBuilder = projectBuilder;
this.projectGitUpdater = projectGitUpdater;
this.templateGenerator = templateGenerator;
this.gradleUpdater = gradleUpdater;
}
public Projects retrieveVersionsFromSCRelease() {
@@ -38,6 +42,7 @@ public class Releaser {
public void updateProjectFromScRelease(File project, Projects versions,
ProjectVersion versionFromScRelease) {
this.projectPomUpdater.updateProjectFromSCRelease(project, versions, versionFromScRelease);
this.gradleUpdater.updateProjectFromSCRelease(project, versions);
ProjectVersion changedVersion = new ProjectVersion(project);
log.info("\n\nProject was successfully updated to [{}]", changedVersion);
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.cloud.release.internal;
import edu.emory.mathcs.backport.java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -22,8 +24,6 @@ import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import edu.emory.mathcs.backport.java.util.Arrays;
/**
* @author Marcin Grzejszczak
*/
@@ -42,6 +42,8 @@ public class ReleaserProperties {
private Maven maven = new Maven();
private Gradle gradle = new Gradle();
private Map<String, String> fixedVersions = new HashMap<>();
public static class Git {
@@ -84,8 +86,8 @@ public class ReleaserProperties {
public void setOauthToken(String oauthToken) {
this.oauthToken = oauthToken;
}
}
}
public static class Pom {
/**
@@ -118,8 +120,8 @@ public class ReleaserProperties {
public void setIgnoredPomRegex(List<String> ignoredPomRegex) {
this.ignoredPomRegex = ignoredPomRegex;
}
}
}
public static class Maven {
/**
@@ -179,8 +181,51 @@ public class ReleaserProperties {
public void setPublishDocsCommands(String[] publishDocsCommands) {
this.publishDocsCommands = publishDocsCommands;
}
}
public static class Gradle {
/**
* A mapping that should be applied to {@code gradle.properties} in order
* to perform a substitution of properties. The mapping is from a property
* inside {@code gradle.properties} to the projects name. Example
*
* In {@code gradle.properties} you have {@code verifierVersion=1.0.0} . You
* want this property to get updated with the value of {@code spring-cloud-contract}
* version. Then it's enough to do the mapping like this for this Releaser's property:
* {@code verifierVersion=spring-cloud-contract}
*/
private Map<String, String> gradlePropsSubstitution = new HashMap<>();
/**
* List of regular expressions of ignored gradle props.
* Defaults to test projects and samples.
*/
@SuppressWarnings("unchecked")
private List<String> ignoredGradleRegex = Arrays.asList(new String[] {
"^.*spring-cloud-contract-maven-plugin/src/test/projects/.*$",
"^.*spring-cloud-contract-maven-plugin/target/.*$",
"^.*samples/standalone/[a-z]+/.*$"
});
public Map<String, String> getGradlePropsSubstitution() {
return this.gradlePropsSubstitution;
}
public void setGradlePropsSubstitution(
Map<String, String> gradlePropsSubstitution) {
this.gradlePropsSubstitution = gradlePropsSubstitution;
}
public List<String> getIgnoredGradleRegex() {
return this.ignoredGradleRegex;
}
public void setIgnoredGradleRegex(List<String> ignoredGradleRegex) {
this.ignoredGradleRegex = ignoredGradleRegex;
}
}
public String getWorkingDir() {
return StringUtils.hasText(this.workingDir) ?
this.workingDir : System.getProperty("user.dir");
@@ -214,6 +259,14 @@ public class ReleaserProperties {
this.maven = maven;
}
public Gradle getGradle() {
return this.gradle;
}
public void setGradle(Gradle gradle) {
this.gradle = gradle;
}
public Map<String, String> getFixedVersions() {
return this.fixedVersions;
}

View File

@@ -0,0 +1,130 @@
package org.springframework.cloud.release.internal.gradle;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.pom.Projects;
/**
* @author Marcin Grzejszczak
*/
public class GradleUpdater {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final ReleaserProperties properties;
public GradleUpdater(ReleaserProperties properties) {
this.properties = properties;
}
/**
* For the given root folder (typically the working directory) performs the whole
* flow of updating {@code gradle.properties} with values from Spring Cloud Release project.
* Remember to pass the mapping from a property name inside {@code gradle.properties} to
* the project name via {@link ReleaserProperties.Gradle#gradlePropsSubstitution}
* @param projectRoot - root folder with project to update
* @param projects - versions of projects used to update poms
*/
public void updateProjectFromSCRelease(File projectRoot, Projects projects) {
processAllGradleProps(projectRoot, projects);
}
private void processAllGradleProps(File projectRoot, Projects projects) {
try {
Files.walkFileTree(projectRoot.toPath(), new GradlePropertiesWalker(this.properties, projects));
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}
private class GradlePropertiesWalker extends SimpleFileVisitor<Path> {
private static final String GRADLE_PROPERTIES = "gradle.properties";
private final ReleaserProperties properties;
private final Projects projects;
private GradlePropertiesWalker(ReleaserProperties properties, Projects projects) {
this.properties = properties;
this.projects = projects;
}
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attr) {
File file = path.toFile();
if (GRADLE_PROPERTIES.equals(file.getName())) {
if (pathIgnored(file)) {
log.debug("Ignoring file [{}] since it's on a list of patterns to ignore", file);
return FileVisitResult.CONTINUE;
}
log.info("Will process the file [{}] and update its gradle properties", file);
final String fileContents = asString(path);
final AtomicReference<String> changedString = new AtomicReference<>(fileContents);
Properties props = loadProps(file);
final Map<String, String> substitution = this.properties.getGradle()
.getGradlePropsSubstitution();
props.entrySet().stream().forEach(entry -> {
if (substitution.containsKey(entry.getKey())) {
Object projectName = substitution.get(entry.getKey());
ProjectVersion value = this.projects.forName((String) projectName);
log.info("Replacing [{}->{}] with [{}->{}]", entry.getKey(), entry.getValue(), entry.getKey(), value);
changedString.set(changedString.get().replace(entry.getKey() + "=" + entry.getValue(),
entry.getKey() + "=" + value));
}
});
storeString(path, changedString.get());
}
return FileVisitResult.CONTINUE;
}
private Properties loadProps(File file) {
Properties props = new Properties();
try {
props.load(new FileInputStream(file));
}
catch (IOException e) {
throw new IllegalStateException(e);
}
return props;
}
private boolean pathIgnored(File file) {
String path = file.getPath();
return this.properties.getGradle().getIgnoredGradleRegex().stream().anyMatch(path::matches);
}
private String asString(Path path) {
try {
return new String(Files.readAllBytes(path));
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}
private void storeString(Path path, String content) {
try {
Files.write(path, content.getBytes());
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@@ -66,7 +66,7 @@ public class ProjectPomUpdater {
/**
* For the given root folder (typically the working directory) performs the whole
* flow of updating {@code pom.xml} with values from Spring Cloud Release project.
* @param projectRoot - root folder with project to update
* @param projectRoot - root folder with project to update
* @param projects - versions of projects used to update poms
* @param versionFromScRelease - version for the built project taken from Spring Cloud Release project
*/
@@ -179,7 +179,6 @@ public class ProjectPomUpdater {
}
}
private enum Assertion {
ASSERT_SNAPSHOTS, IGNORE_ASSERTION
}

View File

@@ -1,5 +1,7 @@
package org.springframework.cloud.release.internal.pom;
import edu.emory.mathcs.backport.java.util.Arrays;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
@@ -15,10 +17,21 @@ public class Projects extends HashSet<ProjectVersion> {
addAll(versions);
}
@SuppressWarnings("unchecked")
public Projects(ProjectVersion... versions) {
addAll(new HashSet<ProjectVersion>(Arrays.asList(versions)));
}
public ProjectVersion forFile(File projectRoot) {
final ProjectVersion thisProject = new ProjectVersion(projectRoot);
return this.stream().filter(projectVersion -> projectVersion.projectName.equals(thisProject.projectName))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Project with name [" + thisProject.projectName + "] is not present"));
}
public ProjectVersion forName(String projectName) {
return this.stream().filter(projectVersion -> projectVersion.projectName.equals(projectName))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Project with name [" + projectName + "] is not present"));
}
}

View File

@@ -3,26 +3,23 @@ package org.springframework.cloud.release.internal;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cloud.release.internal.template.TemplateGenerator;
import org.springframework.cloud.release.internal.git.ProjectGitUpdater;
import org.springframework.cloud.release.internal.gradle.GradleUpdater;
import org.springframework.cloud.release.internal.pom.ProjectPomUpdater;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.project.ProjectBuilder;
import org.springframework.cloud.release.internal.template.TemplateGenerator;
import static org.mockito.BDDMockito.then;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
/**
@@ -35,6 +32,7 @@ public class ReleaserTests {
@Mock ProjectBuilder projectBuilder;
@Mock ProjectGitUpdater projectGitUpdater;
@Mock TemplateGenerator templateGenerator;
@Mock GradleUpdater gradleUpdater;
File pom;
@Before
@@ -45,7 +43,7 @@ public class ReleaserTests {
Releaser releaser(Supplier<ProjectVersion> originalVersionSupplier) {
return new Releaser(this.projectPomUpdater, this.projectBuilder,
this.projectGitUpdater, this.templateGenerator) {
this.projectGitUpdater, this.templateGenerator, this.gradleUpdater) {
@Override ProjectVersion originalVersion(File project) {
return originalVersionSupplier.get();
}
@@ -54,7 +52,7 @@ public class ReleaserTests {
Releaser releaser() {
return new Releaser(this.projectPomUpdater, this.projectBuilder,
this.projectGitUpdater, this.templateGenerator);
this.projectGitUpdater, this.templateGenerator, this.gradleUpdater);
}
@Test

View File

@@ -0,0 +1,67 @@
package org.springframework.cloud.release.internal.gradle;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.pom.Projects;
import org.springframework.util.FileSystemUtils;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
public class GradleUpdaterTests {
@Rule public TemporaryFolder tmp = new TemporaryFolder();
File temporaryFolder;
@Before
public void setup() throws Exception {
this.temporaryFolder = this.tmp.newFolder();
FileSystemUtils.copyRecursively(file("/projects/"), this.temporaryFolder);
}
@Test
public void should_substitute_values_in_gradle_properties() throws IOException {
File projectRoot = tmpFile("gradleproject");
ReleaserProperties properties = new ReleaserProperties();
Map<String, String> props = new HashMap<String, String>() {{
put("foo", "spring-cloud-contract");
put("bar", "spring-cloud-sleuth");
}};
properties.getGradle().setGradlePropsSubstitution(props);
Projects projects = new Projects(
new ProjectVersion("spring-cloud-contract", "1.0.0"),
new ProjectVersion("spring-cloud-sleuth", "2.0.0")
);
new GradleUpdater(properties).updateProjectFromSCRelease(projectRoot, projects);
then(asString(tmpFile("gradleproject/gradle.properties")))
.contains("foo=1.0.0");
then(asString(tmpFile("gradleproject/child/gradle.properties")))
.contains("bar=2.0.0");
}
private File file(String relativePath) throws URISyntaxException {
return new File(GradleUpdaterTests.class.getResource(relativePath).toURI());
}
private File tmpFile(String relativePath) {
return new File(this.temporaryFolder, relativePath);
}
private String asString(File file) throws IOException {
return new String(Files.readAllBytes(file.toPath()));
}
}

View File

@@ -27,7 +27,16 @@ public class ProjectsTests {
}
@Test
public void should_throw_exception_when_project_is_not_present() {
public void should_find_a_project_by_name() {
Set<ProjectVersion> projectVersions = new HashSet<>();
projectVersions.add(new ProjectVersion("spring-cloud-starter-build", "1.0.0"));
Projects projects = new Projects(projectVersions);
then(projects.forName("spring-cloud-starter-build").version).isEqualTo("1.0.0");
}
@Test
public void should_throw_exception_when_project_is_not_present_when_searching_by_file() {
Set<ProjectVersion> projectVersions = new HashSet<>();
projectVersions.add(new ProjectVersion("foo", "1.0.0"));
Projects projects = new Projects(projectVersions);
@@ -37,6 +46,17 @@ public class ProjectsTests {
.hasMessageContaining("Project with name [spring-cloud-starter-build] is not present");
}
@Test
public void should_throw_exception_when_project_is_not_present() {
Set<ProjectVersion> projectVersions = new HashSet<>();
projectVersions.add(new ProjectVersion("foo", "1.0.0"));
Projects projects = new Projects(projectVersions);
thenThrownBy(() -> projects.forName("spring-cloud-starter-build"))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Project with name [spring-cloud-starter-build] is not present");
}
private File file(String relativePath) {
try {
return new File(ProjectsTests.class.getResource(relativePath).toURI());

View File

@@ -0,0 +1,2 @@
foo=bar
ignored=true

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.release.internal.spring;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.release.internal.Releaser;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.gradle.GradleUpdater;
import org.springframework.cloud.release.internal.template.TemplateGenerator;
import org.springframework.cloud.release.internal.project.ProjectBuilder;
import org.springframework.cloud.release.internal.git.ProjectGitUpdater;
@@ -32,6 +33,7 @@ class ReleaserConfiguration {
@Bean SpringReleaser releaser(ReleaserProperties properties) {
ProjectPomUpdater pomUpdater = new ProjectPomUpdater(properties);
return new SpringReleaser(new Releaser(pomUpdater, new ProjectBuilder(properties, pomUpdater),
new ProjectGitUpdater(properties), new TemplateGenerator(properties)), properties);
new ProjectGitUpdater(properties), new TemplateGenerator(properties),
new GradleUpdater(properties)), properties);
}
}

View File

@@ -17,6 +17,7 @@ import org.springframework.cloud.release.internal.Releaser;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.git.GitTestUtils;
import org.springframework.cloud.release.internal.git.ProjectGitUpdater;
import org.springframework.cloud.release.internal.gradle.GradleUpdater;
import org.springframework.cloud.release.internal.pom.ProjectPomUpdater;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.pom.TestPomReader;
@@ -216,8 +217,9 @@ public class AcceptanceTests {
TestProjectGitUpdater gitUpdater = new TestProjectGitUpdater(properties,
expectedVersion);
TemplateGenerator templateGenerator = new TemplateGenerator(properties);
GradleUpdater gradleUpdater = new GradleUpdater(properties);
Releaser releaser = new Releaser(pomUpdater, projectBuilder, gitUpdater,
templateGenerator);
templateGenerator, gradleUpdater);
this.gitUpdater = gitUpdater;
return releaser;
}