Further fancy stuff.

Added a changelog command to render a changelog for a given module from the JIRA tickets. Some internal refactorings and enhancements to the model to be able to work with artifact versions, branches, tags.
This commit is contained in:
Oliver Gierke
2014-03-31 18:06:01 +02:00
parent 092663da01
commit 5d553123c1
40 changed files with 1515 additions and 293 deletions

37
pom.xml
View File

@@ -97,43 +97,6 @@
</configuration>
</plugin>
<!-- <plugin> -->
<!-- <groupId>org.apache.maven.plugins</groupId> -->
<!-- <artifactId>maven-dependency-plugin</artifactId> -->
<!-- <executions> -->
<!-- <execution> -->
<!-- <id>copy-dependencies</id> -->
<!-- <phase>prepare-package</phase> -->
<!-- <goals> -->
<!-- <goal>copy-dependencies</goal> -->
<!-- </goals> -->
<!-- <configuration> -->
<!-- <outputDirectory>${project.build.directory}/lib</outputDirectory> -->
<!-- <overWriteReleases>true</overWriteReleases> -->
<!-- <overWriteSnapshots>true</overWriteSnapshots> -->
<!-- <overWriteIfNewer>true</overWriteIfNewer> -->
<!-- </configuration> -->
<!-- </execution> -->
<!-- </executions> -->
<!-- </plugin> -->
<!-- <plugin> -->
<!-- <groupId>org.apache.maven.plugins</groupId> -->
<!-- <artifactId>maven-jar-plugin</artifactId> -->
<!-- <configuration> -->
<!-- <archive> -->
<!-- <manifest> -->
<!-- <addClasspath>true</addClasspath> -->
<!-- <useUniqueVersions>false</useUniqueVersions> -->
<!-- <classpathPrefix>lib/</classpathPrefix> -->
<!-- <mainClass>${jar.mainclass}</mainClass> -->
<!-- </manifest> -->
<!-- <manifestEntries> -->
<!-- <version>${project.version}</version> -->
<!-- </manifestEntries> -->
<!-- </archive> -->
<!-- </configuration> -->
<!-- </plugin> -->
</plugins>
</build>

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2014 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.data.release.announcement;
import org.springframework.data.release.cli.StaticResources;
import org.springframework.data.release.maven.Artifact;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
public class AnnouncementOperations {
/**
* Returns the project list and links to be included in the release announcement for the given {@link Train} and
* {@link Iteration}.
*
* @param train must not be {@literal null}.
* @param iteration must not be {@literal null}.
* @return
*/
public String getProjectBulletpoints(Train train, Iteration iteration) {
Assert.notNull(train, "Train must not be null!");
Assert.notNull(iteration, "Iteration must not be null!");
StringBuilder builder = new StringBuilder();
for (ModuleIteration module : train.getModuleIterations(iteration, ReleaseTrains.BUILD)) {
Project project = module.getProject();
builder.append("* ");
builder.append(project.getFullName()).append(" ");
builder.append(module.getVersion()).append(" ").append(module.getIteration().getName());
builder.append(" - ");
Artifact artifact = new Artifact(module);
builder.append(getMarkDownLink("Artifacts", artifact.getRootUrl()));
builder.append(" - ");
StaticResources resources = new StaticResources(module);
builder.append(getMarkDownLink("JavaDocs", resources.getJavaDocUrl())).append(" - ");
builder.append(getMarkDownLink("Documentation", resources.getDocumentationUrl())).append(" - ");
builder.append(getMarkDownLink("Changelog", resources.getChangelogUrl()));
builder.append("\n");
}
return builder.toString();
}
private String getMarkDownLink(String name, String url) {
return String.format("[%s](%s)", name, url);
}
public static void main(String[] args) {
AnnouncementOperations operations = new AnnouncementOperations();
System.out.println(operations.getProjectBulletpoints(ReleaseTrains.DIJKSTRA, Iteration.M1));
}
}

View File

@@ -58,7 +58,6 @@ public class JiraCommands implements CommandMarker {
@CliCommand(value = "jira tickets")
public String jira(
//
@CliOption(key = { "", "train" }, mandatory = true, help = "The name of the release train.") String trainName, //
@CliOption(key = "iteration", mandatory = true, help = "An iteration key (one of M1, RC1, GA).") String iterationName, //
@CliOption(key = "for-current-user", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean forCurrentUser) {
@@ -72,4 +71,13 @@ public class JiraCommands implements CommandMarker {
return connector.getTicketsFor(train, iteration, forCurrentUser ? credentials : null).toString();
}
@CliCommand("changelog")
public String changelog(@CliOption(key = { "", "module" }, mandatory = true) String moduleName, @CliOption(
key = { "iteration" }, mandatory = true) String iterationName) {
Train dijkstra = ReleaseTrains.DIJKSTRA;
return connector.getChangelogFor(dijkstra, dijkstra.getModule(moduleName),
dijkstra.getIterations().getIterationByName(iterationName)).toString();
}
}

View File

@@ -15,19 +15,22 @@
*/
package org.springframework.data.release.cli;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.git.Tags;
import org.springframework.data.release.maven.MavenOperations;
import org.springframework.data.release.maven.Pom;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Module;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.stereotype.Component;
/**
@@ -37,19 +40,25 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ReleaseCommands implements CommandMarker {
private final MavenOperations mavenOperations;
private final MavenOperations maven;
private final GitOperations git;
@CliCommand("release predict")
public String predictTrainAndIteration() throws IOException {
public String predictTrainAndIteration() throws Exception {
Project commons = ReleaseTrains.COMMONS;
Pom pom = mavenOperations.getMavenProject(commons);
Pom pom = maven.getMavenProject(commons);
Tags tags = git.getTags(commons);
ArtifactVersion version = tags.getLatest().toArtifactVersion();
System.out.println(version);
for (Train train : ReleaseTrains.TRAINS) {
Module module = train.getModule(commons);
if (!pom.getVersion().startsWith(module.getVersion().toMajorMinorBugfix())) {
if (!pom.getVersion().toString().startsWith(module.getVersion().toMajorMinorBugfix())) {
continue;
}
@@ -58,4 +67,36 @@ public class ReleaseCommands implements CommandMarker {
return null;
}
/**
* Triggers the distribution of release artifacts for all projects.
*
* @param trainName
* @param iterationName
* @throws Exception
*/
@CliCommand("release distribute")
public void distribute(@CliOption(key = { "", "train" }, mandatory = true) String trainName, @CliOption(
key = "iteration", mandatory = true) String iterationName) throws Exception {
Train train = ReleaseTrains.getTrainByName(trainName);
Iteration iteration = train.getIteration(iterationName);
git.checkout(train, iteration);
maven.triggerDistributionBuild(train, iteration);
}
@CliCommand("release prepare")
public void prepare(@CliOption(key = { "", "train" }, mandatory = true) String trainName, @CliOption(
key = "iteration", mandatory = true) String iterationName) throws Exception {
Train train = ReleaseTrains.getTrainByName(trainName);
Iteration iteration = train.getIteration(iterationName);
git.prepare(train, iteration);
for (Module module : train) {
maven.prepareProject(train, iteration, module.getProject());
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2014 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.data.release.cli;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
/**
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public class StaticResources {
private static final String URL_TEMPLATE = "http://docs.spring.io/spring-data/%s/docs/%s";
private final String baseUrl;
public StaticResources(ModuleIteration module) {
Project project = module.getProject();
ArtifactVersion version = ArtifactVersion.from(module);
this.baseUrl = String.format(URL_TEMPLATE, project.getName().toLowerCase(), version);
}
public String getDocumentationUrl() {
return baseUrl.concat("/reference/htmlsingle");
}
public String getJavaDocUrl() {
return baseUrl.concat("/api");
}
public String getChangelogUrl() {
return baseUrl.concat("/changelog.txt");
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2014 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.data.release.git;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.IterationVersion;
import org.springframework.data.release.model.Version;
import org.springframework.util.Assert;
/**
* Value type to represent an SCM branch.
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
@EqualsAndHashCode
public class Branch {
private static final Branch MASTER = new Branch("master");
private final String name;
/**
* Creates a new {@link Branch} from the given {@link IterationVersion}.
*
* @param iterationVersion must not be {@literal null}.
* @return
*/
public static Branch from(IterationVersion iterationVersion) {
Assert.notNull(iterationVersion, "Iteration versoin must not be null!");
Version version = iterationVersion.getVersion();
if (iterationVersion.getIteration().isServiceIteration()) {
return new Branch(version.toString().concat(".x"));
}
return MASTER;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return name;
}
}

View File

@@ -15,13 +15,13 @@
*/
package org.springframework.data.release.git;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
@@ -35,20 +35,30 @@ import org.springframework.util.StringUtils;
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GiCommands implements CommandMarker {
private final GitOperations gitOperations;
private final GitOperations git;
@CliCommand("git checkout")
public void checkout(@CliOption(key = { "", "train" }, mandatory = true) String trainName, @CliOption(
key = "iteration", mandatory = true) String iterationName) throws Exception {
Train train = ReleaseTrains.getTrainByName(trainName);
Iteration iteration = train.getIteration(iterationName);
git.checkout(train, iteration);
}
@CliCommand("git update")
public void checkout(@CliOption(key = { "train" }, mandatory = true) String trainName) throws IOException,
public void checkout(@CliOption(key = { "", "train" }, mandatory = true) String trainName) throws Exception,
InterruptedException {
gitOperations.update(ReleaseTrains.getProjectByName(trainName));
git.update(ReleaseTrains.getTrainByName(trainName));
}
@CliCommand("git tags")
public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws IOException {
public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws Exception {
Project project = ReleaseTrains.getProjectByName(projectName);
return StringUtils.collectionToDelimitedString(gitOperations.getTags(project), "\n");
return StringUtils.collectionToDelimitedString(git.getTags(project).asList(), "\n");
}
}

View File

@@ -19,21 +19,28 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.CommandExecution;
import org.springframework.data.release.io.CommandResult;
import org.springframework.data.release.io.OsCommandOperations;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Module;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Train;
import org.springframework.shell.support.logging.HandlerUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* Component to execut Git related operations.
*
* @author Oliver Gierke
*/
@Component
@@ -50,20 +57,66 @@ public class GitOperations {
return new GitProject(project, server);
}
public void update(Train train) throws IOException, InterruptedException {
/**
* Checks out all projects of the given {@link Train} at the tags for the given {@link Iteration}.
*
* @param train
* @param iteration
* @throws Exception
*/
public void checkout(Train train, Iteration iteration) throws Exception {
List<CommandExecution> executions = new ArrayList<>();
update(train);
for (ModuleIteration module : train.getModuleIterations(iteration)) {
Project project = module.getProject();
ArtifactVersion artifactVersion = ArtifactVersion.from(module);
Tag tag = findTagFor(project, artifactVersion);
if (tag == null) {
throw new IllegalStateException(String.format("No tag found for version %s of project %s, aborting.",
artifactVersion, project));
}
osCommandOperations.executeCommand(String.format("git checkout %s", tag), project).get();
}
LOGGER.info(String.format("Successfully checked out iteration %s for release train %s.", iteration.getName(),
train.getName()));
}
public void prepare(Train train, Iteration iteration) throws Exception {
for (ModuleIteration module : train.getModuleIterations(iteration)) {
Branch branch = Branch.from(module);
update(module.getProject());
String checkoutCommand = String.format("git checkout %s", branch);
osCommandOperations.executeCommand(checkoutCommand, module.getProject()).get();
String updateCommand = String.format("git pull origin %s", branch);
osCommandOperations.executeCommand(updateCommand, module.getProject()).get();
}
}
public void update(Train train) throws Exception {
List<Future<CommandResult>> executions = new ArrayList<>();
for (Module module : train) {
executions.add(update(module.getProject()));
}
for (CommandExecution execution : executions) {
execution.waitForResult();
for (Future<CommandResult> execution : executions) {
execution.get();
}
}
public CommandExecution update(Project project) throws IOException {
public Future<CommandResult> update(Project project) throws Exception {
GitProject gitProject = new GitProject(project, server);
String repositoryName = gitProject.getRepositoryName();
@@ -71,28 +124,54 @@ public class GitOperations {
if (workspace.hasProjectDirectory(project)) {
LOGGER.info(String.format("Found existing repository %s. Obtaining latest changes…", repositoryName));
return osCommandOperations.executeCommand("git pull origin master", project);
return osCommandOperations.executeCommand("git checkout master && git fetch --tags && git pull origin master",
project);
} else {
File projectDirectory = workspace.getProjectDirectory(project);
LOGGER.info(String.format("No repository found for project %s. Cloning repository from %s…", repositoryName,
gitProject.getProjectUri()));
return osCommandOperations.executeCommand(String.format("git clone %s %s", gitProject.getProjectUri(),
projectDirectory.getName()));
File projectDirectory = workspace.getProjectDirectory(project);
String command = String.format("git clone %s %s", gitProject.getProjectUri(), projectDirectory.getName());
return osCommandOperations.executeCommand(command);
}
}
public List<Tag> getTags(Project project) throws IOException {
public Tags getTags(Project project) throws Exception {
CommandExecution command = osCommandOperations.executeCommand("git tag -l", project);
String result = osCommandOperations.executeForResult("git tag -l", project);
List<Tag> tags = new ArrayList<>();
for (String line : command.waitAndGetOutput().split("\n")) {
tags.add(new Tag(line));
for (String line : result.split("\n")) {
if (!StringUtils.isEmpty(line)) {
tags.add(new Tag(line));
}
}
return tags;
return new Tags(tags);
}
/**
* Returns the {@link Tag} that represents the {@link ArtifactVersion} of the given {@link Project}.
*
* @param project
* @param version
* @return
* @throws IOException
*/
private Tag findTagFor(Project project, ArtifactVersion version) throws Exception {
for (Tag tag : getTags(project)) {
if (tag.toArtifactVersion().equals(version)) {
return tag;
}
}
return null;
}
}

View File

@@ -32,10 +32,20 @@ public class GitProject {
private final Project project;
private final GitServer server;
/**
* Returns the name of the repository the project is using.
*
* @return
*/
public String getRepositoryName() {
return String.format("%s-%s", PROJECT_PREFIX, project.getName().toLowerCase());
}
/**
* Returns the URI of the {@link Project}'s repository.
*
* @return
*/
public String getProjectUri() {
return server.getUri() + getRepositoryName();
}

View File

@@ -15,13 +15,51 @@
*/
package org.springframework.data.release.git;
import lombok.Value;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.ArtifactVersion;
/**
* Value object to represent an SCM tag.
*
* @author Oliver Gierke
*/
@Value
public class Tag {
@RequiredArgsConstructor
@EqualsAndHashCode
public class Tag implements Comparable<Tag> {
private final String name;
/**
* Returns the part of the name of the tag that is suitable to derive a version from the tag. Will transparently strip
* a {@code v} prefix from the name.
*
* @return
*/
private String getVersionSource() {
return name.startsWith("v") ? name.substring(1) : name;
}
public ArtifactVersion toArtifactVersion() {
return ArtifactVersion.parse(getVersionSource());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return name;
}
/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Tag that) {
return that.name.compareTo(this.name);
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2014 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.data.release.git;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import lombok.EqualsAndHashCode;
import org.springframework.util.Assert;
/**
* Value object to represent a collection of {@link Tag}s.
*
* @author Oliver Gierke
*/
@EqualsAndHashCode
public class Tags implements Iterable<Tag> {
private final List<Tag> tags;
/**
* Creates a new {@link Tags} instance for the given {@link List} of {@link Tag}s.
*
* @param source must not be {@literal null}.
*/
Tags(List<Tag> source) {
Assert.notNull(source, "Tags must not be null!");
List<Tag> tags = new ArrayList<>(source);
Collections.sort(tags);
this.tags = Collections.unmodifiableList(tags);
}
/**
* Returns the latest {@link Tag}.
*
* @return
*/
public Tag getLatest() {
return tags.get(0);
}
/**
* Returns all {@link Tag}s as {@link List}.
*
* @return
*/
public List<Tag> asList() {
return tags;
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<Tag> iterator() {
return tags.iterator();
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2014 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.data.release.io;
import java.io.StringWriter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author Oliver Gierke
*/
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CommandExecution {
private final DefaultExecuteResultHandler resultHandler;
private final StringWriter writer;
private String output;
public Exception getException() {
return resultHandler.getException();
}
public int getExitValue() {
return resultHandler.getExitValue();
}
public String waitAndGetOutput() {
if (output != null) {
return output;
}
try {
waitForResult();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
this.output = writer.toString();
IOUtils.closeQuietly(writer);
return output;
}
public void waitForResult() throws InterruptedException {
resultHandler.waitFor();
}
}

View File

@@ -15,22 +15,19 @@
*/
package org.springframework.data.release.io;
import java.io.File;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import lombok.Value;
/**
* @author Oliver Gierke
*/
public class OsConfigurationIntegrationTests {
@Value
public class CommandResult {
@Test
@Ignore
public void testname() {
private final int status;
private final String output;
private final Exception exception;
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OsConfiguration.class);
File file = new File(context.getEnvironment().getProperty("io.workDir"));
public boolean hasError() {
return status != 0;
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2011-2012 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.data.release.io;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.output.WriterOutputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.model.Project;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.shell.support.logging.HandlerUtils;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link OsCommandOperations} interface.
*
* @author Stefan Schmidt
* @author Oliver Gierke
* @since 1.2.0
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class CommonsExecOsCommandOperations implements OsCommandOperations {
private static final Logger LOGGER = HandlerUtils.getLogger(GitOperations.class);
private static final String PREFIX_TEMPLATE = "%s > %s";
private static final Map<String, String> ENVIRONMENT = new HashMap<>();
private final Workspace workspace;
/*
* (non-Javadoc)
* @see org.springframework.shell.commands.OsOperations#executeCommand(java.lang.String)
*/
@Async
@Override
public Future<CommandResult> executeCommand(String command) throws IOException {
return executeCommand((String) null, command);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, org.springframework.data.release.model.Project)
*/
@Async
@Override
public Future<CommandResult> executeCommand(String command, Project project) throws IOException {
LOGGER.info(String.format(PREFIX_TEMPLATE, project.getName(), command));
return executeCommand(command, workspace.getProjectDirectory(project), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeAndListen(org.springframework.data.release.model.Project, java.lang.String)
*/
@Override
public Future<CommandResult> executeWithOutput(String command, Project project) throws IOException {
LOGGER.info(String.format(PREFIX_TEMPLATE, project.getName(), command));
return executeCommand(command, workspace.getProjectDirectory(project), false);
}
private Future<CommandResult> executeCommand(String subfolder, String command) throws IOException {
File workingDirectory = workspace.getWorkingDirectory();
File executionDirectory = subfolder == null ? workingDirectory : new File(workingDirectory, subfolder);
return executeCommand(command, executionDirectory, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeForResult(java.lang.String, org.springframework.data.release.model.Project)
*/
@Async
@Override
public String executeForResult(String command, Project project) throws Exception {
return executeCommand(command, workspace.getProjectDirectory(project), true).get().getOutput();
}
private Future<CommandResult> executeCommand(String command, File executionDirectory, boolean silent)
throws IOException {
StringWriter writer = new StringWriter();
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
try (WriterOutputStream outputStream = new WriterOutputStream(writer)) {
String outerCommand = "/bin/bash -lc";
CommandLine outer = CommandLine.parse(outerCommand);
outer.addArgument(command, false);
DefaultExecutor executor = new DefaultExecutor();
executor.setWorkingDirectory(executionDirectory);
executor.setStreamHandler(new PumpStreamHandler(silent ? outputStream : System.out, null));
executor.execute(outer, ENVIRONMENT, resultHandler);
resultHandler.waitFor();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return new AsyncResult<CommandResult>(new CommandResult(resultHandler.getExitValue(), writer.toString(),
resultHandler.getException()));
}
/**
* Adds {@code JAVA_HOME} to the ENVIRONMENT variables lookuing up the path to a Java 7.
*
* @throws Exception
*/
@PostConstruct
public void initialize() throws Exception {
String javaHome = executeCommand("/usr/libexec/java_home -F -v 1.7 -a x86_64 -d64").get().getOutput();
if (javaHome.endsWith("\n")) {
javaHome = javaHome.substring(0, javaHome.length() - 1);
}
ENVIRONMENT.put("JAVA_HOME", javaHome);
}
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2011-2012 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.data.release.io;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.output.WriterOutputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.model.Project;
import org.springframework.stereotype.Component;
/**
* Implementation of {@link OsCommandOperations} interface.
*
* @author Stefan Schmidt
* @author Oliver Gierke
* @since 1.2.0
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class IoConfigAwareOsCommandOperations implements OsCommandOperations {
private final Workspace workspace;
/*
* (non-Javadoc)
* @see org.springframework.shell.commands.OsOperations#executeCommand(java.lang.String)
*/
public CommandExecution executeCommand(String command) throws IOException {
return executeCommand(command, (String) null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, org.springframework.data.release.model.Project)
*/
@Override
public CommandExecution executeCommand(String command, Project project) throws IOException {
return executeCommand(command, workspace.getProjectDirectory(project));
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.OsCommandOperations#executeCommand(java.lang.String, java.io.File)
*/
@Override
public CommandExecution executeCommand(String command, String subfolder) throws IOException {
File workingDirectory = workspace.getWorkingDirectory();
File executionDirectory = subfolder == null ? workingDirectory : new File(workingDirectory, subfolder);
return executeCommand(command, executionDirectory);
}
private CommandExecution executeCommand(String command, File executionDirectory) throws IOException {
StringWriter writer = new StringWriter();
WriterOutputStream outputStream = new WriterOutputStream(writer);
CommandLine commandLine = CommandLine.parse(command);
DefaultExecuteResultHandler executeResultHandler = new DefaultExecuteResultHandler();
DefaultExecutor executor = new DefaultExecutor();
executor.setWorkingDirectory(executionDirectory);
executor.setStreamHandler(new PumpStreamHandler(outputStream, null));
executor.execute(commandLine, executeResultHandler);
return new CommandExecution(executeResultHandler, writer);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.release.io;
import java.io.IOException;
import java.util.concurrent.Future;
import org.springframework.data.release.model.Project;
@@ -33,9 +34,11 @@ public interface OsCommandOperations {
* @param command the command to execute
* @throws IOException if an error occurs
*/
CommandExecution executeCommand(String command) throws IOException;
Future<CommandResult> executeCommand(String command) throws IOException;
CommandExecution executeCommand(String command, Project project) throws IOException;
Future<CommandResult> executeCommand(String command, Project project) throws IOException;
CommandExecution executeCommand(String command, String subfolder) throws IOException;
Future<CommandResult> executeWithOutput(String command, Project project) throws IOException;
String executeForResult(String command, Project project) throws Exception;
}

View File

@@ -28,8 +28,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.release.model.Project;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* Abstraction of the workspace that is used to work with the {@link Project}'s repositories, execute builds, etc.
*
* @author Oliver Gierke
*/
@Component
@@ -40,28 +43,61 @@ public class Workspace {
private final Environment environment;
/**
* Returns the current working directory.
*
* @return
*/
public File getWorkingDirectory() {
String workDir = environment.getProperty("io.workDir");
return new File(workDir.replace("~", System.getProperty("user.home")));
}
/**
* Returns the directory for the given {@link Project}.
*
* @param project must not be {@literal null}.
* @return
*/
public File getProjectDirectory(Project project) {
Assert.notNull(project, "Project must not be null!");
return new File(getWorkingDirectory(), project.getName());
}
/**
* Returns whether the project directory for the given project already exists.
*
* @param project must not be {@literal null}.
* @return
*/
public boolean hasProjectDirectory(Project project) {
Assert.notNull(project, "Project must not be null!");
return getProjectDirectory(project).exists();
}
/**
* Returns a file with the given name relative to the working directory for the given {@link Project}.
*
* @param name must not be {@literal null} or empty.
* @param project must not be {@literal null}.
* @return
*/
public File getFile(String name, Project project) {
return new File(new File(getWorkingDirectory(), project.getName()), name);
}
public boolean exists(String subfolder) {
return new File(getWorkingDirectory(), subfolder).exists();
Assert.hasText(name, "Filename must not be null or empty!");
Assert.notNull(project, "Project must not be null!");
return new File(getProjectDirectory(project), name);
}
/**
* Initializes the working directory and creates the folders if necessary.
*
* @throws IOException
*/
@PostConstruct
public void setUp() throws IOException {

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2014 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.data.release.jira;
import java.util.Date;
import java.util.Locale;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Module;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.shell.support.util.OsUtils;
/**
* @author Oliver Gierke
*/
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@EqualsAndHashCode
public class Changelog {
private final Module module;
private final Iteration iteration;
private final Tickets tickets;
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
String headline = String.format("Changes in version %s (%s)", module.getVersion(),
new DateFormatter("YYYY-MM-dd").print(new Date(), Locale.US));
StringBuilder builder = new StringBuilder(headline).append(OsUtils.LINE_SEPARATOR);
for (int i = 0; i < headline.length(); i++) {
builder.append("-");
}
builder.append(OsUtils.LINE_SEPARATOR);
for (Ticket ticket : tickets) {
String summary = ticket.getSummary();
builder.append("* ").append(ticket.getId()).append(" - ").append(summary);
if (!summary.endsWith(".")) {
builder.append(".");
}
builder.append(OsUtils.LINE_SEPARATOR);
}
return builder.toString();
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.release.jira;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Module;
import org.springframework.data.release.model.Train;
/**
@@ -37,4 +38,6 @@ public interface JiraConnector {
void verifyBeforeRelease(Train train, Iteration iteration);
void closeIteration(Train train, Iteration iteration, Credentials credentials);
Changelog getChangelogFor(Train train, Module module, Iteration iteration);
}

View File

@@ -22,6 +22,7 @@ import lombok.Value;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Module;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.util.StringUtils;
@@ -49,6 +50,10 @@ class JqlQuery {
for (Module module : train) {
if (ReleaseTrains.BUILD.equals(module.getProject())) {
continue;
}
JiraVersion version = new JiraVersion(module, train, iteration);
parts.add(String.format(PROJECT_VERSION_TEMPLATE, module.getProject().getKey(), version));
}

View File

@@ -28,6 +28,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Module;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -145,4 +147,16 @@ class RestJiraConnector implements JiraConnector {
// - mark version as releases
// - if no next version exists, create
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#getChangelogFor(org.springframework.data.release.model.Module, org.springframework.data.release.model.Iteration)
*/
@Override
public Changelog getChangelogFor(Train train, Module module, Iteration iteration) {
Tickets tickets = getTicketsFor(ReleaseTrains.DIJKSTRA, iteration, null);
return new Changelog(module, iteration, tickets);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2014 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.data.release.maven;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.util.Assert;
/**
* Value object to represent a Maven {@link Artifact}.
*
* @author Oliver Gierke
*/
public class Artifact {
private static final GroupId GROUP_ID = new GroupId("org.springframework.data");
private final ModuleIteration module;
private final Repository repository;
private final ArtifactVersion version;
/**
* Creates a new {@link Artifact} for the given {@link ModuleIteration}.
*
* @param module must not be {@literal null}.
*/
public Artifact(ModuleIteration module) {
Assert.notNull(module, "Module iteration must not be null!");
this.module = module;
this.repository = new Repository(module.getIteration());
this.version = ArtifactVersion.from(module);
}
/**
* Returns the Maven artifact identifier.
*
* @return
*/
public String getArtifactId() {
String artifactId = String.format("spring-data-%s", module.getProject().getName().toLowerCase());
return ReleaseTrains.REST.equals(module.getProject()) ? artifactId.concat("-webmvc") : artifactId;
}
/**
* Returns the URL pointing to the artifacts.
*
* @return
*/
public String getRootUrl() {
return String.format("%s/%s/%s/%s", repository.getUrl(), GROUP_ID.asPath(), getArtifactId(), version);
}
}

View File

@@ -17,21 +17,17 @@ package org.springframework.data.release.maven;
import lombok.Value;
import org.springframework.data.release.model.Version;
/**
* Value object to represent an artifacts group identifier.
*
* @author Oliver Gierke
*/
@Value
public class MavenVersion {
class GroupId {
private final Version version;
private final String value;
public String getReleaseVersion() {
return String.format("%s.RELEASE", version.toMajorMinorBugfix());
}
public String getSnapshotVersion() {
return String.format("%s.BUILD-SNAPSHOT", version.toMajorMinorBugfix());
public String asPath() {
return value.replace('.', '/');
}
}

View File

@@ -17,23 +17,56 @@ package org.springframework.data.release.maven;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.release.model.ArtifactVersion;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.XBProjector;
import org.xmlbeam.XBProjector.Flags;
import org.xmlbeam.config.DefaultXMLFactoriesConfig;
import org.xmlbeam.config.DefaultXMLFactoriesConfig.NamespacePhilosophy;
import org.xmlbeam.types.DefaultTypeConverter;
import org.xmlbeam.types.TypeConverter;
/**
* @author Oliver Gierke
*/
@Configuration
public class MavenConfig {
class MavenConfig {
@Bean
public ProjectionFactory projectionFactory() {
TypeConverter converter = new DefaultTypeConverter().setConversionForType(ArtifactVersion.class,
new ArtifactVersionConverter());
DefaultXMLFactoriesConfig config = new DefaultXMLFactoriesConfig();
config.setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC);
return new XBProjector(config, Flags.TO_STRING_RENDERS_XML);
XBProjector projector = new XBProjector(config, Flags.TO_STRING_RENDERS_XML);
projector.config().setTypeConverter(converter);
return projector;
}
/**
* Custom converter to be able to use {@link ArtifactVersion} directly from within an XmlBeam projection.
*
* @author Oliver Gierke
*/
private static class ArtifactVersionConverter extends DefaultTypeConverter.Conversion<ArtifactVersion> {
private static final long serialVersionUID = 1L;
public ArtifactVersionConverter() {
super(null);
}
/*
* (non-Javadoc)
* @see org.xmlbeam.types.DefaultTypeConverter.Conversion#convert(java.lang.String)
*/
@Override
public ArtifactVersion convert(String data) {
return ArtifactVersion.parse(data);
}
}
}

View File

@@ -17,14 +17,24 @@ package org.springframework.data.release.maven;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.CommandResult;
import org.springframework.data.release.io.OsCommandOperations;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.shell.support.logging.HandlerUtils;
import org.springframework.stereotype.Component;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.io.XBFileIO;
/**
* @author Oliver Gierke
@@ -33,14 +43,91 @@ import org.xmlbeam.ProjectionFactory;
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class MavenOperations {
private static final Logger LOGGER = HandlerUtils.getLogger(MavenOperations.class);
private static final String POM_XML = "pom.xml";
private final Workspace workspace;
private final ProjectionFactory projectionFactory;
private final OsCommandOperations os;
public Pom getMavenProject(Project project) throws IOException {
File file = workspace.getFile(POM_XML, project);
return projectionFactory.io().file(file).read(Pom.class);
}
public void prepareProject(Train train, Iteration iteration, Project project) throws IOException {
if (ReleaseTrains.BUILD.equals(project)) {
return;
}
ArtifactVersion commonsVersion = train.getModuleVersion(ReleaseTrains.COMMONS, iteration);
ArtifactVersion buildVersion = train.getModuleVersion(ReleaseTrains.BUILD, iteration);
Repository repository = new Repository(iteration);
File file = workspace.getFile(POM_XML, project);
XBFileIO io = projectionFactory.io().file(file);
Pom pom = io.read(Pom.class);
if (!project.equals(ReleaseTrains.COMMONS)) {
pom.setProperty("spring.data.commons", commonsVersion);
}
pom.setParentVersion(buildVersion);
pom.setRepositoryId(repository.getSnapshotId(), repository.getId());
pom.setRepositoryUrl(repository.getId(), repository.getUrl());
io.write(pom);
}
/**
* Triggers building the distribution artifacts for all Maven projects of the given {@link Train}.
*
* @param train
* @param iteration
* @throws IOException
* @throws InterruptedException
*/
public void triggerDistributionBuild(Train train, Iteration iteration) throws Exception {
for (ModuleIteration moduleIteration : train.getModuleIterations(iteration)) {
Project project = moduleIteration.getProject();
if (ReleaseTrains.BUILD.equals(project)) {
continue;
}
if (!isMavenProject(project)) {
LOGGER.info(String.format("Skipping project %s as no pom.xml could be found in the working directory!",
project.getFullName()));
continue;
}
LOGGER.info(String.format("Triggering distribution build for %s…", project.getFullName()));
ArtifactVersion version = ArtifactVersion.from(moduleIteration);
String command = "mvn clean deploy -DskipTests -Pdistribute";
if (version.isMilestoneVersion()) {
command = command.concat(",milestone");
} else if (version.isReleaseVersion()) {
command = command.concat(",release");
}
CommandResult result = os.executeWithOutput(command, moduleIteration.getProject()).get();
if (result.hasError()) {
throw result.getException();
}
LOGGER.info(String.format("Successfully finished distribution build for %s!", project));
}
}
private boolean isMavenProject(Project project) {
return workspace.getFile(POM_XML, project).exists();
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.release.maven;
import lombok.Value;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Module;
/**
@@ -35,7 +36,7 @@ public class MavenProject {
return String.format("spring-data-%s", module.getProject().getName().toLowerCase());
}
public MavenVersion getVersion() {
return new MavenVersion(module.getVersion());
public ArtifactVersion getReleaseVersion() {
return new ArtifactVersion(module.getVersion());
}
}

View File

@@ -15,7 +15,9 @@
*/
package org.springframework.data.release.maven;
import org.springframework.data.release.model.ArtifactVersion;
import org.xmlbeam.annotation.XBRead;
import org.xmlbeam.annotation.XBValue;
import org.xmlbeam.annotation.XBWrite;
/**
@@ -27,7 +29,7 @@ public interface Pom {
Artifact getArtifactId();
@XBRead("/project/version")
String getVersion();
ArtifactVersion getVersion();
@XBWrite("/project/version")
void setVersion(String version);
@@ -35,6 +37,18 @@ public interface Pom {
@XBRead("/project/repositories/repository[id=\"spring-libs-snapshot\"]")
Repository getSpringRepository();
@XBWrite("/project/parent/version")
void setParentVersion(ArtifactVersion version);
@XBWrite("/project/properties/{0}")
void setProperty(String property, @XBValue ArtifactVersion value);
@XBWrite("/project/repositories/repository[id={0}]/id")
void setRepositoryId(String oldId, @XBValue String newId);
@XBWrite("/project/repositories/repository[id={0}]/url")
void setRepositoryUrl(String id, @XBValue String url);
public interface Repository {
@XBRead("child::id")

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2014 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.data.release.maven;
import org.springframework.data.release.model.Iteration;
/**
* @author Oliver Gierke
*/
public class Repository {
private static final String BASE = "http://repo.spring.io/libs-";
private final String id;
private final String url;
public Repository(Iteration iteration) {
this.id = iteration.isPublicVersion() ? "spring-libs-release" : "spring-libs-milestone";
this.url = iteration.isPublicVersion() ? BASE.concat("release") : BASE.concat("milestone");
}
public String getId() {
return id;
}
public String getSnapshotId() {
return "spring-libs-snapshot";
}
public String getUrl() {
return url;
}
public String getSnapshotUrl() {
return BASE.concat("snapshot");
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2014 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.data.release.model;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.springframework.util.Assert;
/**
* Value object to represent version of a particular artifact.
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
@EqualsAndHashCode
public class ArtifactVersion implements Comparable<ArtifactVersion> {
private static final String RELEASE_SUFFIX = "RELEASE";
private static final String MILESTONE_SUFFIX = "M\\d|RC\\d";
private static final String SNAPSHOT_SUFFIX = "BUILD-SNAPSHOT";
private static final String VALID_SUFFIX = String.format("%s|%s|%s", RELEASE_SUFFIX, MILESTONE_SUFFIX,
SNAPSHOT_SUFFIX);
private final Version version;
private final String suffix;
/**
* Creates a new {@link ArtifactVersion} from the given logical {@link Version}.
*
* @param version must not be {@literal null}.
*/
public ArtifactVersion(Version version) {
Assert.notNull(version, "Version must not be null!");
this.version = version;
this.suffix = RELEASE_SUFFIX;
}
/**
* Parses the given {@link String} into an {@link ArtifactVersion}.
*
* @param source must not be {@literal null} or empty.
* @return
*/
public static ArtifactVersion parse(String source) {
Assert.hasText(source, "Version source must not be null or empty!");
int suffixStart = source.lastIndexOf('.');
Version version = Version.parse(source.substring(0, suffixStart));
String suffix = source.substring(suffixStart + 1);
Assert.isTrue(suffix.matches(VALID_SUFFIX), "Invalid version suffix!");
return new ArtifactVersion(version, suffix);
}
/**
* Creates a new {@link ArtifactVersion} from the given {@link IterationVersion}.
*
* @param iterationVersion must not be {@literal null}.
* @return
*/
public static ArtifactVersion from(IterationVersion iterationVersion) {
Assert.notNull(iterationVersion, "IterationVersion must not be null!");
Version version = iterationVersion.getVersion();
String iterationName = iterationVersion.getIteration().getName();
if (iterationName.equals("GA")) {
return new ArtifactVersion(version, RELEASE_SUFFIX);
}
if (iterationName.startsWith("SR")) {
int bugfixDigits = Integer.parseInt(iterationName.substring(2, iterationName.length()));
return new ArtifactVersion(version.withBugfix(bugfixDigits), RELEASE_SUFFIX);
}
return new ArtifactVersion(version, iterationName);
}
/**
* Returns the release version for the current a
*
* @return
*/
public ArtifactVersion getReleaseVersion() {
return new ArtifactVersion(version, RELEASE_SUFFIX);
}
public ArtifactVersion getSnapshotVersion() {
return new ArtifactVersion(version, SNAPSHOT_SUFFIX);
}
public boolean isReleaseVersion() {
return suffix.equals(RELEASE_SUFFIX);
}
public boolean isMilestoneVersion() {
return suffix.matches(MILESTONE_SUFFIX);
}
public ArtifactVersion getNextDevelopmentVersion() {
if (suffix.equals(SNAPSHOT_SUFFIX)) {
return this;
}
if (suffix.equals(RELEASE_SUFFIX)) {
return new ArtifactVersion(version.nextBugfix(), SNAPSHOT_SUFFIX);
}
return new ArtifactVersion(version, SNAPSHOT_SUFFIX);
}
/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ArtifactVersion that) {
return this.toString().compareTo(that.toString());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s.%s", version.toMajorMinorBugfix(), suffix);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2014 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.data.release.model;
/**
* @author Oliver Gierke
*/
public interface IterationVersion {
Version getVersion();
Iteration getIteration();
}

View File

@@ -15,13 +15,15 @@
*/
package org.springframework.data.release.model;
import lombok.Value;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
/**
* @author Oliver Gierke
*/
@Value
public class ModuleIteration {
@RequiredArgsConstructor
@EqualsAndHashCode
public class ModuleIteration implements IterationVersion {
private final Module module;
private final Iteration iteration;
@@ -31,6 +33,23 @@ public class ModuleIteration {
return module.getProject().getKey();
}
public Project getProject() {
return module.getProject();
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.model.IterationVersion#getVersion()
*/
@Override
public Version getVersion() {
return module.getVersion();
}
public Iteration getIteration() {
return module.hasCustomFirstIteration() ? module.getCustomFirstIteration() : this.iteration;
}
public String getJiraVersionName() {
Iteration iteration = module.hasCustomFirstIteration() ? module.getCustomFirstIteration() : this.iteration;

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.release.model;
import java.util.Arrays;
import java.util.List;
import lombok.Value;
/**
@@ -25,10 +28,16 @@ public class Project {
private final ProjectKey key;
private final String name;
private final List<Project> dependencies;
public Project(String key, String name) {
public Project(String key, String name, Project... dependencies) {
this.key = new ProjectKey(key);
this.name = name;
this.dependencies = Arrays.asList(dependencies);
}
public String getFullName() {
return "Spring Data ".concat(name);
}
}

View File

@@ -26,28 +26,29 @@ public class ReleaseTrains {
public static final List<Train> TRAINS;
public static final Train CODD, DIJKSTRA, EVANS, FOWLER;
public static final Project COMMONS;
public static final Project COMMONS, BUILD, REST;
private static final Project JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE, REST;
private static final Project JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE;
private static final List<Project> PROJECTS;
static {
COMMONS = new Project("DATACMNS", "Commons");
JPA = new Project("DATAJPA", "JPA");
MONGO_DB = new Project("DATAMONGO", "MongoDB");
NEO4J = new Project("DATAGRAPH", "Neo4j");
SOLR = new Project("DATASOLR", "Solr");
COUCHBASE = new Project("DATACOUCH", "Couchbase");
CASSANDRA = new Project("DATACASS", "Cassandra");
ELASTICSEARCH = new Project("DATAES", "Elasticsearch");
BUILD = new Project("DATABUILD", "Build");
COMMONS = new Project("DATACMNS", "Commons", BUILD);
JPA = new Project("DATAJPA", "JPA", COMMONS);
MONGO_DB = new Project("DATAMONGO", "MongoDB", COMMONS);
NEO4J = new Project("DATAGRAPH", "Neo4j", COMMONS);
SOLR = new Project("DATASOLR", "Solr", COMMONS);
COUCHBASE = new Project("DATACOUCH", "Couchbase", COMMONS);
CASSANDRA = new Project("DATACASS", "Cassandra", COMMONS);
ELASTICSEARCH = new Project("DATAES", "Elasticsearch", COMMONS);
REDIS = new Project("DATAREDIS", "Redis");
GEMFIRE = new Project("SGF", "Gemfire");
GEMFIRE = new Project("SGF", "Gemfire", COMMONS);
REST = new Project("DATAREST", "REST");
REST = new Project("DATAREST", "REST", COMMONS, JPA, MONGO_DB, NEO4J, GEMFIRE);
PROJECTS = Arrays.asList(COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS, GEMFIRE,
REST);
PROJECTS = Arrays.asList(BUILD, COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS,
GEMFIRE, REST);
CODD = codd();
DIJKSTRA = dijkstra();
@@ -69,19 +70,21 @@ public class ReleaseTrains {
private static Train codd() {
Module build = new Module(BUILD, "1.3");
Module commons = new Module(COMMONS, "1.7");
Module jpa = new Module(JPA, "1.5");
Module mongoDb = new Module(MONGO_DB, "1.4");
Module neo4j = new Module(NEO4J, "3.0");
Module solr = new Module(SOLR, "1.1");
Module rest = new Module(REST, "2.1");
Module rest = new Module(REST, "2.0");
return new Train("Codd", commons, jpa, mongoDb, neo4j, solr, rest);
return new Train("Codd", build, commons, jpa, mongoDb, neo4j, solr, rest);
}
private static Train dijkstra() {
Module build = new Module(BUILD, "1.4");
Module commons = new Module(COMMONS, "1.8");
Module jpa = new Module(JPA, "1.6");
Module mongoDb = new Module(MONGO_DB, "1.5");
@@ -95,8 +98,8 @@ public class ReleaseTrains {
Module rest = new Module(REST, "2.1");
return new Train("Dijkstra", commons, jpa, mongoDb, neo4j, solr, couchbase, cassandra, elasticsearch, gemfire,
redis, rest);
return new Train("Dijkstra", build, commons, jpa, mongoDb, neo4j, solr, couchbase, cassandra, elasticsearch,
gemfire, redis, rest);
}
public static Train getTrainByName(String name) {

View File

@@ -92,6 +92,37 @@ public class Train implements Iterable<Module> {
return new Train(name, nextModules);
}
public Iterable<ModuleIteration> getModuleIterations(Iteration iteration) {
return getModuleIterations(iteration, new Project[0]);
}
public Iterable<ModuleIteration> getModuleIterations(Iteration iteration, Project... exclusions) {
List<ModuleIteration> iterations = new ArrayList<>(modules.size());
List<Project> exclusionList = Arrays.asList(exclusions);
for (Module module : this) {
if (exclusionList.contains(module.getProject())) {
continue;
}
iterations.add(new ModuleIteration(module, iteration, this));
}
return iterations;
}
public Iteration getIteration(String name) {
return iterations.getIterationByName(name);
}
public ArtifactVersion getModuleVersion(Project project, Iteration iteration) {
Module module = getModule(project);
return ArtifactVersion.from(new ModuleIteration(module, iteration, this));
}
@Override
public String toString() {

View File

@@ -117,6 +117,14 @@ public class Version implements Comparable<Version> {
return new Version(this.major, this.minor + 1);
}
public Version nextBugfix() {
return new Version(this.major, this.minor, this.bugfix + 1);
}
public Version withBugfix(int bugfix) {
return new Version(this.major, this.minor, bugfix);
}
public String toMajorMinorBugfix() {
return String.format("%s.%s.%s", major, minor, bugfix);
}

View File

@@ -18,11 +18,11 @@ package org.springframework.data.release.cli;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.model.ReleaseTrains;
/**
* @author Oliver Gierke
@@ -30,9 +30,13 @@ import org.springframework.data.release.AbstractIntegrationTests;
public class ReleaseCommandsIntegrationTests extends AbstractIntegrationTests {
@Autowired ReleaseCommands releaseCommands;
@Autowired GitOperations git;
@Test
public void foo() throws IOException {
public void predictsReleasTrainCorrectly() throws Exception {
git.update(ReleaseTrains.DIJKSTRA);
assertThat(releaseCommands.predictTrainAndIteration(), is("Dijkstra"));
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2014 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.data.release.git;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.IterationVersion;
import org.springframework.data.release.model.SimpleIterationVersion;
import org.springframework.data.release.model.Version;
/**
* @author Oliver Gierke
*/
public class BranchUnitTests {
@Test
public void testname() {
IterationVersion iterationVersion = new SimpleIterationVersion(new Version(1, 4), Iteration.RC1);
assertThat(Branch.from(iterationVersion).toString(), is("master"));
}
@Test
public void createsBugfixBranchForServiceRelease() {
IterationVersion iterationVersion = new SimpleIterationVersion(new Version(1, 4), Iteration.SR1);
assertThat(Branch.from(iterationVersion).toString(), is("1.4.x"));
}
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.data.release.git;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests;
@@ -32,14 +29,14 @@ public class GitOperationsIntegrationTests extends AbstractIntegrationTests {
@Autowired GitOperations gitOperations;
@Test
public void testname() throws IOException, InterruptedException {
public void testname() throws Exception {
gitOperations.update(ReleaseTrains.CODD);
}
@Test
public void showTags() throws IOException {
public void showTags() throws Exception {
List<Tag> tags = gitOperations.getTags(ReleaseTrains.COMMONS);
System.out.println(StringUtils.collectionToDelimitedString(tags, "\n"));
Tags tags = gitOperations.getTags(ReleaseTrains.COMMONS);
System.out.println(StringUtils.collectionToDelimitedString(tags.asList(), "\n"));
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2014 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.data.release.maven;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.IterationVersion;
import org.springframework.data.release.model.SimpleIterationVersion;
import org.springframework.data.release.model.Version;
/**
* @author Oliver Gierke
*/
public class MavenVersionUnitTests {
@Test(expected = IllegalArgumentException.class)
public void rejectsInvalidVersionSuffix() {
ArtifactVersion.parse("1.4.5.GA");
}
@Test
public void parsesReleaseVersionCorrectly() {
ArtifactVersion version = ArtifactVersion.parse("1.4.5.RELEASE");
assertThat(version.isReleaseVersion(), is(true));
assertThat(version.getNextDevelopmentVersion(), is(ArtifactVersion.parse("1.4.6.BUILD-SNAPSHOT")));
}
@Test
public void createsMilestoneVersionCorrectly() {
ArtifactVersion version = ArtifactVersion.parse("1.4.5.M1");
assertThat(version.isReleaseVersion(), is(false));
assertThat(version.isMilestoneVersion(), is(true));
}
@Test
public void createsReleaseVersionByDefault() {
ArtifactVersion version = new ArtifactVersion(new Version(1, 4, 5));
assertThat(version.isReleaseVersion(), is(true));
assertThat(version.toString(), is("1.4.5.RELEASE"));
}
@Test
public void createsMilestoneVersionFromIteration() {
IterationVersion oneFourMilestoneOne = new SimpleIterationVersion(new Version(1, 4), Iteration.M1);
ArtifactVersion version = ArtifactVersion.from(oneFourMilestoneOne);
assertThat(version.isMilestoneVersion(), is(true));
assertThat(version.toString(), is("1.4.0.M1"));
}
@Test
public void createsReleaseVersionFromIteration() {
IterationVersion oneFourGA = new SimpleIterationVersion(new Version(1, 4), Iteration.GA);
ArtifactVersion version = ArtifactVersion.from(oneFourGA);
assertThat(version.isReleaseVersion(), is(true));
assertThat(version.toString(), is("1.4.0.RELEASE"));
}
@Test
public void createsServiceReleaseVersionFromIteration() {
IterationVersion oneFourServiceReleaseTwo = new SimpleIterationVersion(new Version(1, 4), Iteration.SR2);
ArtifactVersion version = ArtifactVersion.from(oneFourServiceReleaseTwo);
assertThat(version.isReleaseVersion(), is(true));
assertThat(version.toString(), is("1.4.2.RELEASE"));
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2014 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.data.release.model;
import lombok.Value;
@Value
public class SimpleIterationVersion implements IterationVersion {
private final Version version;
private final Iteration iteration;
}