A way too big commit.

Switched to Apache Maven Invoker for running Maven on the projects.

Added JGraphT to correctly sort dependencies in topological order.

Upgraded to Spring Boot 1.3.2, Lombok 1.16.6, jGit 4.2.0. Upgraded to Spring Plugin 1.3 to benefit from lazy exception creation using lambdas. Added Lombok configuration to throw IllegalArgumentException on null checks.
This commit is contained in:
Oliver Gierke
2016-02-10 12:01:06 +01:00
parent 243561571b
commit 2efc0bcbfc
79 changed files with 2776 additions and 729 deletions

View File

@@ -0,0 +1,11 @@
# Git
git.username=
git.author=
git.email=
git.password=
# Maven
maven.mavenHome=
# Deployment
deployment.password=

View File

@@ -0,0 +1 @@
lombok.nonNull.exceptionType = IllegalArgumentException

View File

@@ -8,12 +8,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
<version>1.3.2.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<jar.mainclass>org.springframework.data.release.Application</jar.mainclass>
<spring-plugin.version>1.3.0.BUILD-SNAPSHOT</spring-plugin.version>
</properties>
<dependencies>
@@ -50,9 +51,9 @@
</dependency>
<dependency>
<groupId>org.xmlbeam</groupId>
<artifactId>xmlprojector</artifactId>
<version>1.4.7</version>
<groupId>org.xmlbeam</groupId>
<artifactId>xmlprojector</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
@@ -64,7 +65,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.4</version>
<version>1.16.6</version>
<scope>provided</scope>
</dependency>
@@ -81,7 +82,7 @@
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>4.0.1.201506240215-r</version>
<version>4.2.0.201601211800-r</version>
</dependency>
<dependency>
@@ -89,6 +90,19 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-core</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<build>

View File

@@ -1,8 +1,11 @@
1. Add an `application-local.properties` to the project root and add the following properties:
- `git.username` - Your GitHub username.
- `git.password` - Your GitHub password.
- `git.password` - Your GitHub password or API key.
- `git.author` - Your full name (used for preparing commits).
- `git.email` - Your email (used for preparing commits).
- `maven.mavenHome` - Pointing to the location of your Maven installation.
- `deployment.api-key` - The API key to use for artifact promotion.
- `deployment.password` - The password of the deployment user (buildmaster).
2. Run `mvn package appassembler:assemble && sh target/appassembler/bin/spring-data-release-shell`

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2016 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;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* SImple interface to ease streamability of {@link Iterable}s.
*
* @author Oliver Gierke
*/
public interface Streamable<T> extends Iterable<T> {
/**
* Creates a non-parallel {@link Stream} of the underlying {@link Iterable}.
*
* @return will never be {@literal null}.
*/
public default Stream<T> stream() {
return StreamSupport.stream(spliterator(), false);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2015 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 lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
/**
* Commands to create markup to be used in announcing blog posts.
*
* @author Oliver Gierke
*/
@CliComponent
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class AnnouncementCommands implements CommandMarker {
private final @NonNull AnnouncementOperations operations;
@CliCommand("announcement")
public void distribute(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
System.out.println(operations.getProjectBulletpoints(iteration));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,26 +17,22 @@ package org.springframework.data.release.announcement;
import static org.springframework.data.release.model.Projects.*;
import org.springframework.data.release.build.MavenArtifact;
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.data.release.model.TrainIteration;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@Component
public class AnnouncementOperations {
/**
* Returns the project list and links to be included in the release announcement for the given {@link Train} and
* {@link Iteration}.
* Returns the project list and links to be included in the release announcement for the given {@link TrainIteration}.
*
* @param train must not be {@literal null}.
* @param iteration must not be {@literal null}.
* @return
*/
@@ -46,16 +42,16 @@ public class AnnouncementOperations {
StringBuilder builder = new StringBuilder();
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
iteration.getModulesExcept(BUILD).forEach(module -> {
Project project = module.getProject();
builder.append("* ");
builder.append(project.getFullName()).append(" ");
builder.append(module.getVersionString());
builder.append(module.getShortVersionString());
builder.append(" - ");
Artifact artifact = new Artifact(module);
MavenArtifact artifact = new MavenArtifact(module);
builder.append(getMarkDownLink("Artifacts", artifact.getRootUrl()));
builder.append(" - ");
@@ -67,18 +63,12 @@ public class AnnouncementOperations {
builder.append(getMarkDownLink("Changelog", resources.getChangelogUrl()));
builder.append("\n");
}
});
return builder.toString();
}
private String getMarkDownLink(String name, String url) {
private static 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(new TrainIteration(ReleaseTrains.GOSLING, Iteration.SR1)));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2016 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.
@@ -13,10 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.build;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.release.model.Project;
import org.springframework.plugin.core.OrderAwarePluginRegistry;
import org.springframework.plugin.core.PluginRegistry;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.XBProjector;
import org.xmlbeam.XBProjector.Flags;
@@ -24,10 +29,17 @@ import org.xmlbeam.config.DefaultXMLFactoriesConfig;
import org.xmlbeam.config.DefaultXMLFactoriesConfig.NamespacePhilosophy;
/**
* Spring configuration for build related components.
*
* @author Oliver Gierke
*/
@Configuration
class MavenConfig {
class BuildConfiguration {
@Bean
public PluginRegistry<BuildSystem, Project> buildSystems(List<? extends BuildSystem> buildSystems) {
return OrderAwarePluginRegistry.create(buildSystems);
}
@Bean
public ProjectionFactory projectionFactory() {
@@ -36,8 +48,6 @@ class MavenConfig {
config.setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC);
config.setOmitXMLDeclaration(false);
XBProjector projector = new XBProjector(config, Flags.TO_STRING_RENDERS_XML);
return projector;
return new XBProjector(config, Flags.TO_STRING_RENDERS_XML);
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2016 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.build;
import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class BuildOperations {
private final PluginRegistry<BuildSystem, Project> buildSystems;
/**
* Updates all inter-project dependencies based on the given {@link TrainIteration} and release {@link Phase}.
*
* @param iteration must not be {@literal null}.
* @param phase must not be {@literal null}.
* @throws Exception
*/
public void updateProjectDescriptors(TrainIteration iteration, Phase phase) throws Exception {
Assert.notNull(iteration, "Train iteration must not be null!");
Assert.notNull(phase, "Phase must not be null!");
UpdateInformation updateInformation = new UpdateInformation(iteration, phase);
doWithBuildSystem(iteration, (system, it) -> system.updateProjectDescriptors(it, updateInformation));
}
/**
* Triggers the distribution builds for all modules particitpating in the given {@link TrainIteration}.
*
* @param iteration must not be {@literal null}.
*/
public void distributeResources(TrainIteration iteration) {
Assert.notNull(iteration, "Train iteration must not be null!");
doWithBuildSystem(iteration, BuildSystem::triggerDistributionBuild);
}
/**
* Performs the release build for all modules in the given {@link TrainIteration}.
*
* @param iteration must not be {@literal null}.
* @return
*/
public List<DeploymentInformation> performRelease(TrainIteration iteration) {
return iteration.stream().map(this::performRelease).collect(Collectors.toList());
}
/**
* Performs the release build for the given {@link ModuleIteration}.
*
* @param module must not be {@literal null}.
* @return
*/
public DeploymentInformation performRelease(ModuleIteration module) {
prepareVersion(module, Phase.PREPARE);
return buildAndDeployRelease(module);
}
/**
* Prepares the versions of the given {@link TrainIteration} depending on the given {@link Phase}.
*
* @param iteration must not be {@literal null}.
* @param phase must not be {@literal null}.
*/
public void prepareVersions(TrainIteration iteration, Phase phase) {
Assert.notNull(iteration, "Train iteration must not be null!");
Assert.notNull(phase, "Phase must not be null!");
doWithBuildSystem(iteration, (system, module) -> system.prepareVersion(module, phase));
}
/**
* Prepares the version of the given {@link ModuleIteration} depending on the given {@link Phase}.
*
* @param iteration must not be {@literal null}.
* @param phase must not be {@literal null}.
* @return
*/
public ModuleIteration prepareVersion(ModuleIteration iteration, Phase phase) {
Assert.notNull(iteration, "Module iteration must not be null!");
Assert.notNull(phase, "Phase must not be null!");
return doWithBuildSystem(iteration, (system, module) -> system.prepareVersion(module, phase));
}
/**
* Builds the release for the given {@link ModuleIteration} and deploys it to the staging repository.
*
* @param module must not be {@literal null}.
* @return
*/
public DeploymentInformation buildAndDeployRelease(ModuleIteration module) {
return doWithBuildSystem(module, BuildSystem::deploy);
}
/**
* Selects the build system for each {@link ModuleIteration} contained in the given {@link TrainIteration} and
* executes the given function for it.
*
* @param iteration must not be {@literal null}.
* @param function must not be {@literal null}.
* @return
*/
private <T> List<T> doWithBuildSystem(TrainIteration iteration,
BiFunction<BuildSystem, ModuleIteration, T> function) {
return iteration.stream()//
.map(module -> doWithBuildSystem(module, function))//
.collect(Collectors.toList());
}
/**
* Selects the build system for the module contained in the given {@link ModuleIteration} and executes the given
* function with it.
*
* @param module must not be {@literal null}.
* @param function must not be {@literal null}.
* @return
*/
private <T> T doWithBuildSystem(ModuleIteration module, BiFunction<BuildSystem, ModuleIteration, T> function) {
return function.apply(buildSystems.getPluginFor(module.getProject(), () -> new IllegalStateException(
String.format("No build system plugin found for project %s!", module.getProject()))), module);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2016 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.build;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.plugin.core.Plugin;
/**
* Plugin interface to back different build systems.
*
* @author Oliver Gierke
*/
interface BuildSystem extends Plugin<Project> {
/**
* @param iteration
* @param train
* @param phase
* @throws Exception
*/
ModuleIteration updateProjectDescriptors(ModuleIteration iteration, UpdateInformation updateInformation);
ModuleIteration prepareVersion(ModuleIteration module, Phase phase);
ModuleIteration triggerDistributionBuild(ModuleIteration module);
DeploymentInformation deploy(ModuleIteration module);
}

View File

@@ -0,0 +1,171 @@
/*
* 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.build;
import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.deployment.DeploymentProperties;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
/**
* Gradle specific operations.
*
* @author Oliver Gierke
*/
@Component
@Order(200)
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
class GradleBuildSystem implements BuildSystem {
private static final String BUILD_GRADLE = "build.gradle";
private static final String GRADLE_PROPERTIES = "gradle.properties";
private static final String COMMONS_PROPERTY = "springDataCommonsVersion";
private static final String BUILD_PROPERTY = "springDataBuildVersion";
private final Workspace workspace;
private final Logger logger;
private final DeploymentProperties properties;
/**
* Updates all Gradle projects contained in the release.
*
* @param iteration
* @param phase
* @throws Exception
*/
public void updateProject(TrainIteration iteration, final Phase phase) throws Exception {
UpdateInformation updateInformation = new UpdateInformation(iteration, phase);
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
updateProjectDescriptors(module, updateInformation);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#updateProjectDescriptors(org.springframework.data.release.model.ModuleIteration, org.springframework.data.release.model.UpdateInformation)
*/
@Override
public ModuleIteration updateProjectDescriptors(ModuleIteration iteration, UpdateInformation updateInformation) {
Project project = iteration.getProject();
Repository repository = new Repository(iteration.getIteration());
ArtifactVersion commonsVersion = updateInformation.getIteration().getModuleVersion(COMMONS);
ArtifactVersion buildVersion = updateInformation.getIteration().getModuleVersion(BUILD);
workspace.processFile(GRADLE_PROPERTIES, project, (line, number) -> {
if (line.contains(COMMONS_PROPERTY)) {
ArtifactVersion version = updateInformation.getPhase().equals(Phase.PREPARE) ? commonsVersion
: commonsVersion.getNextDevelopmentVersion();
logger.log(project, "Setting Spring Data Commons version in %s to %s.", GRADLE_PROPERTIES, version);
return String.format("%s=%s", COMMONS_PROPERTY, version);
}
if (line.contains(BUILD_PROPERTY)) {
ArtifactVersion version = updateInformation.getPhase().equals(Phase.PREPARE) ? buildVersion
: buildVersion.getNextDevelopmentVersion();
logger.log(project, "Setting Spring Data Build version in %s to %s.", GRADLE_PROPERTIES, version);
return String.format("%s=%s", BUILD_PROPERTY, version);
}
return line;
});
workspace.processFile(BUILD_GRADLE, project, (line, number) -> {
String snapshotUrl = repository.getSnapshotUrl();
String releaseUrl = repository.getUrl();
String message = "Switching to Spring repository %s";
switch (updateInformation.getPhase()) {
case CLEANUP:
logger.log(project, message, snapshotUrl);
return line.contains(releaseUrl) ? line.replace(releaseUrl, snapshotUrl) : line;
case PREPARE:
default:
logger.log(project, message, releaseUrl);
return line.contains(snapshotUrl) ? line.replace(snapshotUrl, releaseUrl) : line;
}
});
return iteration;
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#prepareVersion(org.springframework.data.release.model.ModuleIteration, org.springframework.data.release.model.Phase)
*/
@Override
public ModuleIteration prepareVersion(ModuleIteration module, Phase phase) {
return module;
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#triggerDistributionBuild(org.springframework.data.release.model.ModuleIteration)
*/
@Override
public ModuleIteration triggerDistributionBuild(ModuleIteration module) {
return module;
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#deploy(org.springframework.data.release.model.ModuleIteration)
*/
@Override
public DeploymentInformation deploy(ModuleIteration module) {
return new DeploymentInformation(module, properties);
}
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Project project) {
return isGradleProject(project);
}
/**
* Returns whether the given project is a Gradle project (checks for the presence of a build.gradle file).
*
* @param project
* @return
*/
private boolean isGradleProject(Project project) {
return workspace.getFile(BUILD_GRADLE, project).exists();
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.build;
import lombok.Value;
@@ -22,7 +22,7 @@ import lombok.Value;
*
* @author Oliver Gierke
*/
@Value
@Value(staticConstructor = "of")
class GroupId {
private final String value;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 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.
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.build;
import static org.springframework.data.release.model.Projects.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -24,25 +25,25 @@ import org.springframework.data.release.model.ModuleIteration;
import org.springframework.util.Assert;
/**
* Value object to represent a Maven {@link Artifact}.
* Value object to represent a Maven artifact.
*
* @author Oliver Gierke
*/
@EqualsAndHashCode
public class Artifact {
public class MavenArtifact {
private static final GroupId GROUP_ID = new GroupId("org.springframework.data");
private static final GroupId GROUP_ID = GroupId.of("org.springframework.data");
private final ModuleIteration module;
private final Repository repository;
private final @Getter ArtifactVersion version;
/**
* Creates a new {@link Artifact} for the given {@link ModuleIteration}.
* Creates a new {@link MavenArtifact} for the given {@link ModuleIteration}.
*
* @param module must not be {@literal null}.
*/
public Artifact(ModuleIteration module) {
public MavenArtifact(ModuleIteration module) {
Assert.notNull(module, "Module iteration must not be null!");

View File

@@ -0,0 +1,304 @@
/*
* 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.build;
import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.deployment.DeploymentProperties;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.data.release.utils.ExecutionUtils;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.io.XBFileIO;
/**
* @author Oliver Gierke
*/
@Component
@Order(100)
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
class MavenBuildSystem implements BuildSystem {
private static final String POM_XML = "pom.xml";
private final Workspace workspace;
private final ProjectionFactory projectionFactory;
private final Logger logger;
private final MavenRuntime mvn;
private final DeploymentProperties properties;
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#updateProjectDescriptors(org.springframework.data.release.model.ModuleIteration, org.springframework.data.release.model.TrainIteration, org.springframework.data.release.model.Phase)
*/
@Override
public ModuleIteration updateProjectDescriptors(ModuleIteration module, UpdateInformation information) {
PomUpdater updater = new PomUpdater(logger, information, module.getProject());
if (updater.isBuildProject()) {
updateBom(information);
updateParentPom(information);
} else {
execute(workspace.getFile(POM_XML, updater.getProject()), pom -> {
updater.updateDependencyProperties(pom);
updater.updateParentVersion(pom);
updater.updateRepository(pom);
});
}
return module;
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#triggerDistributionBuild(org.springframework.data.release.model.ModuleIteration)
*/
@Override
public ModuleIteration triggerDistributionBuild(ModuleIteration module) {
Project project = module.getProject();
if (BUILD.equals(project)) {
return module;
}
if (!isMavenProject(project)) {
logger.log(project, "Skipping project as no pom.xml could be found in the working directory!");
return module;
}
logger.log(project, "Triggering distribution build…");
ArtifactVersion version = ArtifactVersion.of(module);
String profile = "-Pdistribute";
if (version.isMilestoneVersion()) {
profile = profile.concat(",milestone");
} else if (version.isReleaseVersion()) {
profile = profile.concat(",release");
}
mvn.execute(project, "clean", "deploy", "-DskipTests", profile);
logger.log(project, "Successfully finished distribution build!");
return module;
}
private void updateBom(UpdateInformation updateInformation) {
TrainIteration iteration = updateInformation.getIteration();
logger.log(BUILD, "Updating BOM pom.xml…");
execute(workspace.getFile("bom/pom.xml", BUILD), pom -> {
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
ArtifactVersion version = updateInformation.getProjectVersionToSet(module.getProject());
logger.log(BUILD, "%s", module);
pom.setDependencyManagementVersion(new MavenArtifact(module).getArtifactId(), version);
module.getProject().doWithAdditionalArtifacts(
additionalArtifact -> pom.setDependencyManagementVersion(additionalArtifact.getArtifactId(), version));
}
});
}
private void updateParentPom(UpdateInformation information) {
// Fix version of shared resources to to-be-released version.
execute(workspace.getFile("parent/pom.xml", BUILD), ParentPom.class, pom -> {
logger.log(BUILD, "Setting shared resources version to %s.", information.getParentVersionToSet());
pom.setSharedResourcesVersion(information.getParentVersionToSet());
logger.log(BUILD, "Setting releasetrain property to %s.", information.getReleaseTrainVersion());
pom.setReleaseTrain(information.getReleaseTrainVersion());
});
}
public boolean isMavenProject(ModuleIteration module) {
Project project = module.getProject();
if (!isMavenProject(project)) {
logger.log(module, "No pom.xml file found, skipping project.");
return false;
}
return true;
}
/**
* Triggers building the distribution artifacts for all Maven projects of the given {@link Train}.
*
* @param iteration
* @throws Exception
*/
public void triggerDistributionBuild(TrainIteration iteration) throws Exception {
ExecutionUtils.run(iteration, module -> {
Project project = module.getProject();
if (BUILD.equals(project)) {
return;
}
if (!isMavenProject(project)) {
logger.log(project, "Skipping project as no pom.xml could be found in the working directory!");
return;
}
logger.log(project, "Triggering distribution build…");
ArtifactVersion version = ArtifactVersion.of(module);
String profile = "-Pdistribute";
if (version.isMilestoneVersion()) {
profile = profile.concat(",milestone");
} else if (version.isReleaseVersion()) {
profile = profile.concat(",release");
}
mvn.execute(project, "clean", "deploy", "-DskipTests", profile);
logger.log(project, "Successfully finished distribution build!");
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#prepareVersion(org.springframework.data.release.model.ModuleIteration, org.springframework.data.release.model.Phase)
*/
@Override
public ModuleIteration prepareVersion(ModuleIteration module, Phase phase) {
Project project = module.getProject();
UpdateInformation information = new UpdateInformation(module.getTrainIteration(), phase);
mvn.execute(project, "versions-set",
"-DnewVersion=".concat(information.getProjectVersionToSet(project).toString()));
if (BUILD.equals(project)) {
mvn.execute(project, "versions-set", //
"-DnewVersion=".concat(information.getReleaseTrainVersion()), //
"-DgroupId=org.springframework.data", //
"-DartifactId=spring-data-releasetrain");
}
return module;
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#deploy(org.springframework.data.release.model.ModuleIteration)
*/
@Override
public DeploymentInformation deploy(ModuleIteration module) {
Assert.notNull(module, "Module must not be null!");
DeploymentInformation information = new DeploymentInformation(module, properties);
List<String> arguments = new ArrayList<>();
arguments.add("clean");
arguments.add("deploy");
arguments.add("-Prelease");
arguments.add("-DskipTests");
arguments.add("-Dartifactory.server=".concat(properties.getServer().getUri()));
arguments.add("-Dartifactory.staging-repository=".concat(properties.getStagingRepository()));
arguments.add("-Dartifactory.username=".concat(properties.getUsername()));
arguments.add("-Dartifactory.password=".concat(properties.getPassword()));
arguments.add("-Dartifactory.build-name=\"".concat(information.getBuildName()).concat("\""));
arguments.add("-Dartifactory.build-number=".concat(information.getBuildNumber()));
mvn.execute(module.getProject(), arguments);
return information;
}
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Project project) {
return isMavenProject(project);
}
private void updateVersion(ModuleIteration iteration, File workingDirectory) {
ArtifactVersion version = ArtifactVersion.of(iteration);
mvn.execute(iteration.getProject(), "versions-set", "-DnewVersion=".concat(version.toString()));
}
private boolean isMavenProject(Project project) {
return workspace.getFile(POM_XML, project).exists();
}
private void execute(File file, Consumer<Pom> callback) {
execute(file, Pom.class, callback);
}
private <T extends Pom> void execute(File file, Class<T> type, Consumer<T> callback) {
XBFileIO io = projectionFactory.io().file(file);
try {
T pom = (T) io.read(type);
callback.accept(pom);
io.write(pom);
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2015 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.build;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* Maven configuration properties.
*
* @author Oliver Gierke
*/
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "maven")
class MavenProperties {
private File mavenHome;
private File localRepository;
private Map<String, String> plugins;
/**
* Configures the local Maven repository location to use. In case the given folder does not already exists it's
* created.
*
* @param localRepository must not be {@literal null} or empty.
*/
public void setLocalRepository(String localRepository) {
Assert.hasText(localRepository, "Local repository must not be null!");
log.info("Using {} as local Maven repository!", localRepository);
this.localRepository = new File(localRepository.replace("~", System.getProperty("user.home")));
if (!this.localRepository.exists()) {
this.localRepository.mkdirs();
}
}
/**
* Returns the fully-qualified plugin goal for the given local one.
*
* @param goal must not be {@literal null} or empty.
* @return
*/
public String getFullyQualifiedPlugin(String goal) {
Assert.hasText(goal, "Goal must not be null or empty!");
return plugins.containsKey(goal) ? plugins.get(goal) : goal;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2015 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.build;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.OsOperations;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
/**
* @author Oliver Gierke
*/
@Slf4j
@Component
class MavenRuntime {
private final Invoker invoker;
private final Workspace workspace;
private final OsOperations os;
private final Logger logger;
private final MavenProperties properties;
/**
* Creates a new {@link MavenRuntime} for the given {@link Workspace} and Maven home.
*
* @param workspace must not be {@literal null}.
* @param os must not be {@literal null}.
* @param logger must not be {@literal null}.
* @param properties must not be {@literal null}.
*/
@Autowired
public MavenRuntime(Workspace workspace, OsOperations os, Logger logger, MavenProperties properties) {
this.workspace = workspace;
this.os = os;
this.logger = logger;
this.properties = properties;
this.invoker = new DefaultInvoker();
this.invoker.setMavenHome(properties.getMavenHome());
this.invoker.setOutputHandler(line -> log.info(line));
this.invoker.setErrorHandler(line -> log.info(line));
File localRepository = properties.getLocalRepository();
if (localRepository != null) {
this.invoker.setLocalRepositoryDirectory(localRepository);
}
}
public void execute(Project project, String... arguments) {
execute(project, Arrays.asList(arguments));
}
public void execute(Project project, List<String> arguments) {
DefaultInvocationRequest request = new DefaultInvocationRequest();
request.setJavaHome(os.getJavaHome());
request.setShellEnvironmentInherited(true);
request.setBaseDirectory(workspace.getProjectDirectory(project));
List<String> goals = new ArrayList<>();
goals.add(properties.getFullyQualifiedPlugin(arguments.get(0)));
goals.addAll(arguments.subList(1, arguments.size()));
request.setGoals(goals);
logger.log(project, "Executing mvn %s", goals.stream().collect(Collectors.joining(" ")));
try {
InvocationResult result = invoker.execute(request);
if (result.getExitCode() != 0) {
throw new RuntimeException(result.getExecutionException());
}
} catch (MavenInvocationException o_O) {
throw new RuntimeException(o_O);
}
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.build;
import org.springframework.data.release.model.ArtifactVersion;
import org.xmlbeam.annotation.XBValue;
@@ -24,6 +24,9 @@ import org.xmlbeam.annotation.XBWrite;
*/
public interface ParentPom extends Pom {
@XBWrite("/project/properties/releasetrain")
void setReleaseTrain(@XBValue String releaseTrain);
@XBWrite("/project/profiles/profile[id=\"distribute\"]/dependencies/dependency/version")
void setSharedResourcesVersion(@XBValue ArtifactVersion value);
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.build;
import org.springframework.data.release.model.ArtifactVersion;
import org.xmlbeam.annotation.XBRead;
@@ -26,13 +26,16 @@ import org.xmlbeam.annotation.XBWrite;
public interface Pom {
@XBRead("/project")
Artifact getArtifactId();
Artifact getArtifact();
@XBRead("/project/version")
String getRawVersion();
@XBRead("/project/version")
ArtifactVersion getVersion();
@XBWrite("/project/version")
void setVersion(String version);
void setVersion(ArtifactVersion version);
@XBWrite("/project/parent/version")
void setParentVersion(ArtifactVersion version);
@@ -73,12 +76,20 @@ public interface Pom {
public interface Artifact {
@XBRead("child::groupId")
String getGroupId();
GroupId getGroupId();
@XBRead("child::artifactId")
String getArtifactId();
@XBRead("child::version")
String getVersion();
default String getArtifactPath() {
return "/".concat(getGroupId().asPath()).concat("/").concat(getArtifactId());
}
default String getPath() {
return getArtifactPath().concat(getVersion());
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2015 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.build;
import static org.springframework.data.release.model.Phase.*;
import static org.springframework.data.release.model.Projects.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.data.release.utils.Logger;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@RequiredArgsConstructor
class PomUpdater {
private final Logger logger;
private final UpdateInformation information;
private final @Getter Project project;
public boolean isBuildProject() {
return BUILD.equals(project);
}
public void updateArtifactVersion(Pom pom) {
ArtifactVersion version = information.getProjectVersionToSet(project);
logger.log(project, "Updated project version to %s.", version);
pom.setVersion(version);
}
public void updateDependencyProperties(Pom pom) {
project.getDependencies().forEach(dependency -> {
String dependencyProperty = dependency.getDependencyProperty();
if (pom.getProperty(dependencyProperty) == null) {
return;
}
ArtifactVersion version = information.getProjectVersionToSet(dependency);
logger.log(project, "Updating %s dependency version property %s to %s.", dependency.getFullName(),
dependencyProperty, version);
pom.setProperty(dependencyProperty, version);
});
}
/**
* Updates the version of the parent project in the given {@link Pom}.
*
* @param pom must not be {@literal null}.
*/
public void updateParentVersion(Pom pom) {
Assert.notNull(pom, "Pom must not be null!");
ArtifactVersion version = information.getParentVersionToSet();
logger.log(project, "Updating Spring Data Build Parent version to %s.", version);
pom.setParentVersion(version);
}
/**
* Updates the repository section in the given {@link Pom}.
*
* @param pom must not be {@literal null}.
*/
public void updateRepository(Pom pom) {
Assert.notNull(pom, "Pom must not be null!");
String message = "Switching to Spring repository %s (%s).";
Repository repository = information.getRepository();
if (PREPARE.equals(information.getPhase())) {
logger.log(project, message, repository.getId(), repository.getUrl());
pom.setRepositoryId(repository.getSnapshotId(), repository.getId());
pom.setRepositoryUrl(repository.getId(), repository.getUrl());
} else {
logger.log(project, message, repository.getSnapshotId(), repository.getSnapshotUrl());
pom.setRepositoryId(repository.getId(), repository.getSnapshotId());
pom.setRepositoryUrl(repository.getSnapshotId(), repository.getSnapshotUrl());
}
}
}

View File

@@ -13,35 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.build;
import lombok.Value;
import org.springframework.data.release.model.Iteration;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@Value
public class Repository {
private static final String ID_BASE = "spring-libs-";
private static final String BASE = "https://repo.spring.io/libs-";
private final String id;
private final String url;
String id, url;
public Repository(Iteration iteration) {
this.id = iteration.isPublic() ? "spring-libs-release" : "spring-libs-milestone";
this.url = iteration.isPublic() ? BASE.concat("release") : BASE.concat("milestone");
}
public String getId() {
return id;
Assert.notNull(iteration, "Iteration must not be null!");
this.id = ID_BASE.concat(iteration.isPublic() ? "release" : "milestone");
this.url = BASE.concat(iteration.isPublic() ? "release" : "milestone");
}
public String getSnapshotId() {
return "spring-libs-snapshot";
}
public String getUrl() {
return url;
return ID_BASE.concat("snapshot");
}
public String getSnapshotUrl() {

View File

@@ -45,8 +45,9 @@ public class IssueTrackerCommands implements CommandMarker {
private final Credentials credentials;
/**
* @param tracker
* @param environment
* @param tracker must not be {@literal null}.
* @param jira must not be {@literal null}.
* @param environment must not be {@literal null}.
*/
@Autowired
public IssueTrackerCommands(PluginRegistry<IssueTracker, Project> tracker, JiraConnector jira,

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 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.
@@ -19,35 +19,39 @@ import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.docs.DocumentationOperations;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.build.BuildOperations;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.deployment.DeploymentOperations;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.git.VersionTags;
import org.springframework.data.release.gradle.GradleOperations;
import org.springframework.data.release.maven.MavenOperations;
import org.springframework.data.release.misc.ReleaseOperations;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
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;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@Component
@CliComponent
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class ReleaseCommands implements CommandMarker {
private final MavenOperations maven;
private final GradleOperations gradle;
private final GitOperations git;
private final ReleaseOperations misc;
private final DocumentationOperations docs;
private final DeploymentOperations deployment;
private final BuildOperations build;
@CliCommand("release predict")
public String predictTrainAndIteration() throws Exception {
@@ -64,20 +68,6 @@ public class ReleaseCommands implements CommandMarker {
findFirst().map(Train::getName).orElse(null);
}
/**
* Triggers the distribution of release artifacts for all projects.
*
* @param trainName
* @param iterationName
* @throws Exception
*/
@CliCommand("release distribute")
public void distribute(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
git.checkout(iteration);
maven.triggerDistributionBuild(iteration);
}
/**
* Prepares the release of the given iteration of the given train.
*
@@ -92,22 +82,73 @@ public class ReleaseCommands implements CommandMarker {
misc.prepareChangelogs(iteration);
misc.updateResources(iteration);
docs.updateDockbookIncludes(iteration);
maven.updatePom(iteration, Phase.PREPARE);
gradle.updateProject(iteration, Phase.PREPARE);
build.updateProjectDescriptors(iteration, Phase.PREPARE);
git.commit(iteration, "Prepare %s.", null);
git.commit(iteration, "Prepare %s.");
}
@CliCommand(value = "release build")
public void buildRelease(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
@CliOption(key = "project", mandatory = false) String projectName) throws Exception {
if (projectName != null) {
Project project = Projects.byName(projectName);
ModuleIteration module = iteration.getModule(project);
DeploymentInformation information = build.performRelease(module);
deployment.promote(information);
} else {
List<DeploymentInformation> deploymentInformation = build.performRelease(iteration);
git.commit(iteration, "Release version %s.");
deploymentInformation.forEach(deployment::promote);
build.prepareVersions(iteration, Phase.CLEANUP);
git.commit(iteration, "Prepare next development iteration.");
}
}
/**
* Concludes the release of the given {@link TrainIteration}.
*
* @param iteration
* @throws Exception
*/
@CliCommand(value = "release conclude")
public void conclude(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
Assert.notNull(iteration, "Train iteration must not be null!");
// Tag release
git.tagRelease(iteration);
maven.updatePom(iteration, Phase.CLEANUP);
gradle.updateProject(iteration, Phase.CLEANUP);
// Prepare master branch
build.updateProjectDescriptors(iteration, Phase.CLEANUP);
git.commit(iteration, "After release cleanups.");
git.commit(iteration, "After release cleanups.", null);
// Prepare maintenance branches
git.checkout(iteration);
git.createMaintenanceBranches(iteration);
build.updateProjectDescriptors(iteration, Phase.MAINTENANCE);
build.prepareVersions(iteration, Phase.MAINTENANCE);
git.commit(iteration, "Prepare next development iteration.");
}
/**
* Triggers the distribution of release artifacts for all projects.
*
* @param trainName
* @param iterationName
* @throws Exception
*/
@CliCommand("release distribute")
public void distribute(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
git.checkout(iteration);
build.distributeResources(iteration);
}
}

View File

@@ -70,6 +70,6 @@ public class TrainIterationConverter implements Converter<TrainIteration> {
}
}
return false;
return true;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2015-2016 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.deployment;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.io.IOException;
import java.net.URI;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.utils.Logger;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestOperations;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A client to interact with Artifactory.
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
class ArtifactoryClient {
private final RestOperations template;
private final Logger logger;
private final DeploymentProperties properties;
/**
* Triggers the promotion of the artifacts identified by the given {@link DeploymentInformation}.
*
* @param information must not be {@literal null}.
*/
public void promote(DeploymentInformation information) {
Assert.notNull(information, "DeploymentInformation must not be null!");
ModuleIteration module = information.getModule();
URI uri = properties.getServer().getPromotionResource(information);
logger.log(module, "Promoting %s %s from %s to %s.", information.getBuildName(), information.getBuildNumber(),
properties.getStagingRepository(), information.getTargetRepository());
try {
template.postForEntity(uri, new PromotionRequest(information.getTargetRepository()), String.class);
} catch (HttpClientErrorException o_O) {
handle(o_O, module);
}
}
private void handle(HttpClientErrorException o_O, ModuleIteration module) {
try {
logger.log(module, "Promotion failed!");
Errors errors = new ObjectMapper().readValue(o_O.getResponseBodyAsByteArray(), Errors.class);
errors.getErrors().forEach(error -> logger.log(module, error));
errors.getMessages().forEach(message -> logger.log(module, message));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void deleteArtifacts(DeploymentInformation information) {
template.delete(properties.getServer().getDeleteBuildResource(information));
}
@Value
private static class PromotionRequest {
String targetRepo;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2015-2016 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.deployment;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.release.utils.Logger;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;
/**
* Configuration to set up deployment components.
*
* @author Oliver Gierke
*/
@Configuration
class DeploymentConfiguration {
@Autowired DeploymentProperties properties;
@Bean
public ArtifactoryClient client(Logger logger) {
return new ArtifactoryClient(artifactoryRestTemplate(), logger, properties);
}
@Bean
public RestTemplate artifactoryRestTemplate() {
RestTemplate template = new RestTemplate();
template.setInterceptors(Arrays.asList(new AuthenticatingClientHttpRequestInterceptor(properties.getApiKey())));
return template;
}
@RequiredArgsConstructor
private static class AuthenticatingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
private final @NonNull String apiKey;
/*
* (non-Javadoc)
* @see org.springframework.http.client.ClientHttpRequestInterceptor#intercept(org.springframework.http.HttpRequest, byte[], org.springframework.http.client.ClientHttpRequestExecution)
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().add("X-Api-Key", apiKey);
return execution.execute(request, body);
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2015-2016 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.deployment;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.web.util.UriTemplate;
/**
* Information about a deployment.
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public class DeploymentInformation {
private static UriTemplate REPOSITORY_TEMPLATE = new UriTemplate(
"artifactory::default::{server};build.number={buildNumber};build.name={buildName}");
private final @Getter @NonNull ModuleIteration module;
private final @NonNull DeploymentProperties properties;
/**
* Returns a unique build number for this particular deployment.
*/
private final @Getter String buildNumber = String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
/**
* Returns the name of the build.
*
* @return
*/
public String getBuildName() {
return module.getProject().getFullName().concat(" - Release");
}
/**
* Returns the name of the repository to deploy to.
*
* @return
*/
public String getTargetRepository() {
return properties.getRepositoryPrefix()
.concat(module.getIteration().isPublic() ? "libs-release-local" : "libs-milestone-local");
}
/**
* Returns the full URL to be used as deployment target.
*
* @return
*/
public String getDeploymentTargetUrl() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("server", properties.getStagingRepositoryUrl());
parameters.putAll(getBuildInfoParameters());
return REPOSITORY_TEMPLATE.expand(parameters).toString();
}
/**
* Returns a {@link Map} to expand a URI template to access the build information.
*
* @return
*/
public Map<String, Object> getBuildInfoParameters() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("buildNumber", buildNumber);
parameters.put("buildName", getBuildName());
return parameters;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2015-2016 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.deployment;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* Deployment functionality.
*
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class DeploymentOperations {
private final ArtifactoryClient client;
/**
* Promotes the artifacts identified by the given {@link DeploymentInformation}.
*
* @param information must not be {@literal null}.
*/
public void promote(DeploymentInformation information) {
Assert.notNull(information, "DeploymentInformation must not be null!");
client.promote(information);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2015-2016 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.deployment;
import lombok.Data;
import java.net.URI;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.util.UriTemplate;
/**
* @author Oliver Gierke
*/
@Data
@Component
@ConfigurationProperties(prefix = "deployment")
public class DeploymentProperties {
/**
* The Artifactory host.
*/
private Server server;
/**
* The deployer's username.
*/
private String username;
private String apiKey;
/**
* The deployer's password.
*/
private String password;
/**
* The repository to deploy the artifacts to.
*/
private String stagingRepository;
private String repositoryPrefix = "";
public String getStagingRepository() {
return repositoryPrefix.concat(stagingRepository);
}
/**
* Returns the URI of the staging repository.
*
* @return
*/
public String getStagingRepositoryUrl() {
return server.getUri().toString().concat("/").concat(stagingRepository);
}
@Data
public static class Server {
private static final String PROMOTION_RESOURCE = "/api/build/promote/{buildName}/{buildNumber}";
private static final String DELETE_BUILD_RESOURCE = "/api/build/{buildName}?buildNumbers={buildNumber}&artifacts=1";
private String uri;
/**
* Returns the URI to the resource that a promotion can be triggered at.
*
* @param information must not be {@literal null}.
* @return
*/
public URI getPromotionResource(DeploymentInformation information) {
Assert.notNull(information, "DeploymentInformation must not be null!");
return new UriTemplate(uri.concat(PROMOTION_RESOURCE)).expand(information.getBuildInfoParameters());
}
public URI getDeleteBuildResource(DeploymentInformation information) {
return new UriTemplate(uri.concat(DELETE_BUILD_RESOURCE)).expand(information.getBuildInfoParameters());
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2015 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.deployment;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author Oliver Gierke
*/
@Data
public class Errors {
private List<Error> errors = new ArrayList<>();
private List<Message> messages = new ArrayList<>();
public List<Error> getErrors(Errors this) {
return errors;
}
@Data
static class Error {
private String message;
private int status;
public String toString() {
return String.format("%s - %s", status, message);
}
}
@Data
static class Message {
private String level, message;
public String toString() {
return String.format("%s - %s", level, message);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 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.
@@ -16,6 +16,7 @@
package org.springframework.data.release.docs;
import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,7 +25,6 @@ import org.springframework.data.release.git.GitProject;
import org.springframework.data.release.git.Tag;
import org.springframework.data.release.git.VersionTags;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.io.Workspace.LineCallback;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
@@ -34,7 +34,7 @@ import org.springframework.stereotype.Component;
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class DocumentationOperations {
private static final String INDEX_LOCATION = "/src/docbkx/index.xml";
@@ -61,16 +61,12 @@ public class DocumentationOperations {
continue;
}
workspace.processFile(INDEX_LOCATION, project, new LineCallback() {
workspace.processFile(INDEX_LOCATION, project, (line, number) -> {
@Override
public String doWith(String line, long number) {
boolean isInclude = line.contains("xi:include");
boolean containsGitRepo = line.contains(gitProject.getRepositoryName());
boolean isInclude = line.contains("xi:include");
boolean containsGitRepo = line.contains(gitProject.getRepositoryName());
return isInclude && containsGitRepo ? line.replace(previousTag.toString(), newTag.toString()) : line;
}
return isInclude && containsGitRepo ? line.replace(previousTag.toString(), newTag.toString()) : line;
});
}
}

View File

@@ -15,11 +15,13 @@
*/
package org.springframework.data.release.git;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.IterationVersion;
import org.springframework.data.release.model.Version;
import org.springframework.data.release.model.VersionAware;
import org.springframework.util.Assert;
/**
@@ -27,8 +29,8 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
@EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
class Branch {
public static final Branch MASTER = new Branch("master");
@@ -43,17 +45,30 @@ class Branch {
*/
public static Branch from(IterationVersion iterationVersion) {
Assert.notNull(iterationVersion, "Iteration versoin must not be null!");
Assert.notNull(iterationVersion, "Iteration version must not be null!");
Version version = iterationVersion.getVersion();
if (iterationVersion.getIteration().isServiceIteration()) {
return new Branch(version.toString().concat(".x"));
if (iterationVersion.isServiceIteration()) {
return from((VersionAware) iterationVersion);
}
return MASTER;
}
public static Branch from(VersionAware versioned) {
return from(versioned.getVersion());
}
private static Branch from(Version version) {
return from(version.toString().concat(".x"));
}
private static Branch from(String name) {
int slashIndex = name.lastIndexOf('/');
return new Branch(slashIndex != -1 ? name.substring(slashIndex + 1) : name);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()

View File

@@ -18,6 +18,8 @@ package org.springframework.data.release.git;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import java.util.Optional;
import org.springframework.data.release.jira.Ticket;
/**
@@ -29,7 +31,7 @@ public class Commit {
private final Ticket ticket;
private final String summary;
private final String details;
private final Optional<String> details;
/*
* (non-Javadoc)
@@ -42,10 +44,10 @@ public class Commit {
builder.append(ticket.getId()).append(" - ").append(summary).append("\n");
if (details != null) {
details.ifPresent(it -> {
builder.append("\n");
builder.append(details).append("\n");
}
builder.append(it).append("\n");
});
return builder.toString();
}

View File

@@ -37,7 +37,12 @@ public class GitCommands implements CommandMarker {
private final GitOperations git;
@CliCommand("git checkout")
@CliCommand("git co train")
public void checkout(@CliOption(key = "", mandatory = true) Train train) throws Exception {
git.checkout(train);
}
@CliCommand("git co")
public void checkout(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
git.checkout(iteration);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
@@ -86,14 +86,49 @@ public class GitOperations {
logger.log(module, "git reset --hard origin/%s", branch);
git.reset().//
setMode(ResetType.HARD).//
setRef("origin/".concat(branch.toString())).//
call();
reset(git, branch.toString());
}
});
}
/**
* Checks out all projects of the given {@link TrainIteration}.
*
* @param iteration
* @throws Exception
*/
public void checkout(Train train) {
update(train);
ExecutionUtils.run(train, module -> {
try (Git git = new Git(getRepository(module.getProject()))) {
Branch branch = Branch.from(module);
CheckoutCommand command = git.checkout().setName(branch.toString());
if (!branchExists(module.getProject(), branch)) {
logger.log(module.getProject(), "git checkout -b %s --track origin/%s", branch, branch);
command.setCreateBranch(true).//
setStartPoint("origin/".concat(branch.toString())).//
call();
} else {
logger.log(module.getProject(), "git checkout %s", branch);
command.call();
}
logger.log(module.getProject(), "git reset --hard origin/%s", branch);
reset(git, branch.toString());
}
});
logger.log(train, "Successfully checked out projects.");
}
/**
* Checks out all projects of the given {@link TrainIteration}.
*
@@ -120,7 +155,7 @@ public class GitOperations {
logger.log(module, "git checkout %s", tag);
git.checkout().setStartPoint(tag.toString());
git.checkout().setStartPoint(tag.toString()).call();
}
});
@@ -197,12 +232,13 @@ public class GitOperations {
if (workspace.hasProjectDirectory(project)) {
logger.log(project, "Found existing repository %s. Obtaining latest changes…", repositoryName);
logger.log(project, "git checkout master && git reset --hard && git fetch --tags && git pull origin master");
logger.log(project,
"git checkout master && git reset --hard origin/master && git fetch --tags && git pull origin master");
checkout(project, Branch.MASTER);
git.reset().setMode(ResetType.HARD).call();
git.fetch().setTagOpt(TagOpt.FETCH_TAGS);
reset(git, "master");
git.fetch().setTagOpt(TagOpt.FETCH_TAGS).call();
git.pull().call();
} else {
@@ -256,6 +292,18 @@ public class GitOperations {
});
}
/**
* Commits all changes currently made to all modules of the given {@link TrainIteration}. The summary can contain a
* single {@code %s} placeholder which the version of the current module will get replace into.
*
* @param iteration must not be {@literal null}.
* @param summary must not be {@literal null} or empty.
* @throws Exception
*/
public void commit(TrainIteration iteration, String summary) throws Exception {
commit(iteration, summary, Optional.empty());
}
/**
* Commits all changes currently made to all modules of the given {@link TrainIteration}. The summary can contain a
* single {@code %s} placeholder which the version of the current module will get replace into.
@@ -265,7 +313,7 @@ public class GitOperations {
* @param details can be {@literal null} or empty.
* @throws Exception
*/
public void commit(TrainIteration iteration, String summary, String details) throws Exception {
public void commit(TrainIteration iteration, String summary, Optional<String> details) throws Exception {
Assert.notNull(iteration, "Train iteration must not be null!");
Assert.hasText(summary, "Summary must not be null or empty!");
@@ -273,19 +321,22 @@ public class GitOperations {
ExecutionUtils.run(iteration, module -> commit(module, expandSummary(summary, module, iteration), details));
}
private static String expandSummary(String summary, ModuleIteration module, TrainIteration iteration) {
if (!summary.contains("%s")) {
return summary;
}
return String.format(summary,
ArtifactVersion.of(module).toString().concat(String.format(" (%s)", iteration.toString())));
/**
* Commits the given files for the given {@link ModuleIteration} using the given summary for the commit message. If no
* files are given, all pending changes are committed.
*
* @param module must not be {@literal null}.
* @param summary must not be {@literal null} or empty.
* @param files can be empty.
* @throws Exception
*/
public void commit(ModuleIteration module, String summary, File... files) throws Exception {
commit(module, summary, Optional.empty(), files);
}
/**
* Commits the given files for the given {@link ModuleIteration} using the given summary and details for the commit
* message. If no files are given, all pending changes are commited.
* message. If no files are given, all pending changes are committed.
*
* @param module must not be {@literal null}.
* @param summary must not be {@literal null} or empty.
@@ -293,7 +344,7 @@ public class GitOperations {
* @param files can be empty.
* @throws Exception
*/
public void commit(ModuleIteration module, String summary, String details, File... files) throws Exception {
public void commit(ModuleIteration module, String summary, Optional<String> details, File... files) throws Exception {
Assert.notNull(module, "Module iteration must not be null!");
Assert.hasText(summary, "Summary must not be null or empty!");
@@ -318,6 +369,55 @@ public class GitOperations {
}
}
public void checkout(Project project, Branch branch) throws Exception {
try (Git git = new Git(getRepository(project))) {
Ref ref = git.getRepository().getRef(branch.toString());
CheckoutCommand checkout = git.checkout().setName(branch.toString());
if (ref == null) {
checkout.setCreateBranch(true).//
setUpstreamMode(SetupUpstreamMode.TRACK).//
setStartPoint("origin/".concat(branch.toString()));
}
checkout.call();
}
}
public void createMaintenanceBranches(TrainIteration iteration) throws Exception {
checkout(iteration);
ExecutionUtils.run(iteration, module -> {
Branch branch = createMaintenanceBranch(module);
checkout(module.getProject(), branch);
});
}
private Branch createMaintenanceBranch(ModuleIteration module) throws Exception {
try (Git git = new Git(getRepository(module.getProject()))) {
Branch branch = Branch.from(module);
git.branchCreate().setName(branch.toString()).call();
return branch;
}
}
/**
* Returns the {@link ObjectId} of the commit that is considered the release commit. It is identified by the summary
* starting with the release ticket identifier, followed by a dash separated by spaces and the key word
* {@code Release}. To prevent skimming through the entire Git history, we expect such a commit to be found within the
* 50 most recent commits.
*
* @param module
* @return
* @throws Exception
*/
private ObjectId getReleaseHash(ModuleIteration module) throws Exception {
Project project = module.getProject();
@@ -329,9 +429,7 @@ public class GitOperations {
for (RevCommit commit : git.log().setMaxCount(50).call()) {
String summary = commit.getShortMessage();
if (summary.startsWith(trigger)) {
if (commit.getShortMessage().startsWith(trigger)) {
return commit.getId();
}
}
@@ -351,36 +449,17 @@ public class GitOperations {
*/
private Tag findTagFor(Project project, ArtifactVersion version) {
return StreamSupport.stream(getTags(project).spliterator(), false).//
return getTags(project).stream().//
filter(tag -> tag.toArtifactVersion().map(it -> it.equals(version)).orElse(false)).//
findFirst().orElseThrow(() -> new IllegalArgumentException(
String.format("No tag found for version %s of project %s!", version, project)));
}
public void checkout(Project project, Branch branch) throws Exception {
try (Git git = new Git(getRepository(project))) {
Ref ref = git.getRepository().getRef(branch.toString());
CheckoutCommand checkout = git.checkout().setName(branch.toString());
if (ref == null) {
checkout.setCreateBranch(true).//
setUpstreamMode(SetupUpstreamMode.TRACK).//
setStartPoint("origin/".concat(branch.toString()));
}
checkout.call();
}
}
private Repository getRepository(Project project) throws IOException {
return FileRepositoryBuilder.create(workspace.getFile(".git", project));
}
public void clone(Project project) throws Exception {
private void clone(Project project) throws Exception {
Git git = Git.cloneRepository().//
setURI(getGitProject(project).getProjectUri()).//
@@ -390,4 +469,23 @@ public class GitOperations {
git.checkout().setName(Branch.MASTER.toString()).//
call();
}
private boolean branchExists(Project project, Branch branch) {
try (Git git = new Git(getRepository(project))) {
return git.getRepository().getRef(branch.toString()) != null;
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
}
private static void reset(Git git, String name) throws Exception {
git.reset().setMode(ResetType.HARD).setRef("origin/".concat(name)).call();
}
private static String expandSummary(String summary, ModuleIteration module, TrainIteration iteration) {
return summary.contains("%s") ? String.format(summary, module.getMediumVersionString()) : summary;
}
}

View File

@@ -57,4 +57,8 @@ public class GitProperties {
public CredentialsProvider getCredentials() {
return new UsernamePasswordCredentialsProvider(username, password);
}
public String getAuthenticationHeader() {
return username.concat(":").concat(password);
}
}

View File

@@ -21,6 +21,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.release.Streamable;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.util.Assert;
@@ -31,7 +32,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
*/
@EqualsAndHashCode
public class VersionTags implements Iterable<Tag> {
public class VersionTags implements Streamable<Tag> {
private final List<Tag> tags;

View File

@@ -1,139 +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.gradle;
import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.io.Workspace.LineCallback;
import org.springframework.data.release.maven.Repository;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
/**
* Gradle specific operations.
*
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class GradleOperations {
private static final String BUILD_GRADLE = "build.gradle";
private static final String GRADLE_PROPERTIES = "gradle.properties";
private static final String COMMONS_PROPERTY = "springDataCommonsVersion";
private static final String BUILD_PROPERTY = "springDataBuildVersion";
private final Workspace workspace;
private final Logger logger;
/**
* Updates all Gradle projects contained in the release.
*
* @param iteration
* @param phase
* @throws Exception
*/
public void updateProject(TrainIteration iteration, final Phase phase) throws Exception {
final Repository repository = new Repository(iteration.getIteration());
final ArtifactVersion commonsVersion = iteration.getModuleVersion(COMMONS);
final ArtifactVersion buildVersion = iteration.getModuleVersion(BUILD);
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
final Project project = module.getProject();
if (!isGradleProject(project)) {
continue;
}
workspace.processFile(GRADLE_PROPERTIES, project, new LineCallback() {
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.Workspace.LineCallback#doWith(java.lang.String, long)
*/
@Override
public String doWith(String line, long number) {
if (line.contains(COMMONS_PROPERTY)) {
ArtifactVersion version = phase.equals(Phase.PREPARE) ? commonsVersion
: commonsVersion.getNextDevelopmentVersion();
logger.log(project, "Setting Spring Data Commons version in %s to %s.", GRADLE_PROPERTIES, version);
return String.format("%s=%s", COMMONS_PROPERTY, version);
}
if (line.contains(BUILD_PROPERTY)) {
ArtifactVersion version = phase.equals(Phase.PREPARE) ? buildVersion
: buildVersion.getNextDevelopmentVersion();
logger.log(project, "Setting Spring Data Build version in %s to %s.", GRADLE_PROPERTIES, version);
return String.format("%s=%s", BUILD_PROPERTY, version);
}
return line;
}
});
workspace.processFile(BUILD_GRADLE, project, new LineCallback() {
/*
* (non-Javadoc)
* @see org.springframework.data.release.io.Workspace.LineCallback#doWith(java.lang.String, long)
*/
@Override
public String doWith(String line, long number) {
String snapshotUrl = repository.getSnapshotUrl();
String releaseUrl = repository.getUrl();
String message = "Switching to Spring repository %s";
switch (phase) {
case CLEANUP:
logger.log(project, message, snapshotUrl);
return line.contains(releaseUrl) ? line.replace(releaseUrl, snapshotUrl) : line;
case PREPARE:
default:
logger.log(project, message, releaseUrl);
return line.contains(snapshotUrl) ? line.replace(snapshotUrl, releaseUrl) : line;
}
}
});
}
}
/**
* Returns whether the given project is a Gradle project (checks for the presence of a build.gradle file).
*
* @param project
* @return
*/
private boolean isGradleProject(Project project) {
return workspace.getFile(BUILD_GRADLE, project).exists();
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.release.io;
import lombok.RequiredArgsConstructor;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
@@ -35,22 +33,35 @@ import org.springframework.data.release.utils.Logger;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* Implementation of {@link OsCommandOperations} interface.
* Implementation of {@link OsOperations} interface.
*
* @author Stefan Schmidt
* @author Oliver Gierke
* @since 1.2.0
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
class CommonsExecOsCommandOperations implements OsCommandOperations {
class CommonsExecOsCommandOperations implements OsOperations {
private static final Map<String, String> ENVIRONMENT = new HashMap<>();
private final Workspace workspace;
private final Logger logger;
private final File javaHome;
@Autowired
public CommonsExecOsCommandOperations(Workspace workspace, Logger logger, IoProperties properties) throws Exception {
this.workspace = workspace;
this.logger = logger;
this.javaHome = detectJavaHome(properties);
Assert.isTrue(javaHome.exists(), String.format("Java home %s does not exist!", javaHome.getAbsolutePath()));
ENVIRONMENT.put("JAVA_HOME", javaHome.getAbsolutePath());
}
/*
* (non-Javadoc)
@@ -133,20 +144,24 @@ class CommonsExecOsCommandOperations implements OsCommandOperations {
new CommandResult(resultHandler.getExitValue(), writer.toString(), resultHandler.getException()));
}
/**
* Adds {@code JAVA_HOME} to the ENVIRONMENT variables looking up the path to a Java 7.
*
* @throws Exception
*/
// @PostConstruct
public void initialize() throws Exception {
public File getJavaHome() {
return javaHome;
}
String javaHome = executeCommand("/usr/libexec/java_home -F -v 1.8 -a x86_64 -d64").get().getOutput();
private File detectJavaHome(IoProperties properties) throws Exception {
if (javaHome.endsWith("\n")) {
javaHome = javaHome.substring(0, javaHome.length() - 1);
File javaHome = properties.getJavaHome();
if (javaHome != null) {
return javaHome;
}
ENVIRONMENT.put("JAVA_HOME", javaHome);
String javaHomePath = executeCommand("/usr/libexec/java_home -F -v 1.8 -a x86_64 -d64").get().getOutput();
if (javaHomePath.endsWith("\n")) {
javaHomePath = javaHomePath.substring(0, javaHomePath.length() - 1);
}
return new File(javaHomePath);
}
}

View File

@@ -30,17 +30,19 @@ import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "io")
public class IoProperties {
class IoProperties {
private String workDir;
private File workDir, javaHome;
public void setWorkDir(String workDir) {
this.workDir = workDir.replace("~", System.getProperty("user.home"));
log.info(String.format("Using %s as working directory!", workDir));
this.workDir = new File(workDir.replace("~", System.getProperty("user.home")));
}
public File getWorkDir() {
return new File(workDir);
public void setJavaHome(String javaHome) {
log.info(String.format("Using %s as Java home!", javaHome));
this.workDir = new File(javaHome.replace("~", System.getProperty("user.home")));
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.release.io;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Future;
@@ -26,7 +27,7 @@ import org.springframework.data.release.model.Project;
* @author Stefan Schmidt
* @since 1.2.0
*/
public interface OsCommandOperations {
public interface OsOperations {
/**
* Attempts the execution of a commands and delegates the output to the standard logger.
@@ -41,4 +42,6 @@ public interface OsCommandOperations {
Future<CommandResult> executeWithOutput(String command, Project project) throws IOException;
String executeForResult(String command, Project project) throws Exception;
File getJavaHome();
}

View File

@@ -15,17 +15,23 @@
*/
package org.springframework.data.release.io;
import static org.springframework.data.release.utils.StreamUtils.*;
import lombok.RequiredArgsConstructor;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Scanner;
import java.util.stream.Stream;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.release.model.Project;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@@ -44,6 +50,7 @@ public class Workspace {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final IoProperties ioProperties;
private final ResourcePatternResolver resolver;
/**
* Returns the current working directory.
@@ -93,7 +100,23 @@ public class Workspace {
return new File(getProjectDirectory(project), name);
}
public boolean processFile(String filename, Project project, LineCallback callback) throws Exception {
public Stream<File> getFiles(String pattern, Project project) {
File projectDirectory = getProjectDirectory(project);
String patternToLookup = String.format("file:%s/%s", projectDirectory.getAbsolutePath(), pattern);
try {
return Arrays.stream(resolver.getResources(patternToLookup)).map(wrap(Resource::getFile));
} catch (IOException o_O) {
throw new RuntimeException(o_O);
}
}
public boolean processFiles(String pattern, Project project, LineCallback callback) {
return false;
}
public boolean processFile(String filename, Project project, LineCallback callback) {
File file = getFile(filename, project);
@@ -115,9 +138,13 @@ public class Workspace {
builder.append(result).append("\n");
}
}
writeContentToFile(filename, project, builder.toString());
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
writeContentToFile(filename, project, builder.toString());
return true;
}

View File

@@ -33,6 +33,6 @@ class GitHubIssue {
}
public boolean isReleaseTicket(ModuleIteration module) {
return title.contains("Release") && title.contains(module.getVersionString());
return title.contains("Release") && title.contains(module.getShortVersionString());
}
}

View File

@@ -18,18 +18,20 @@ package org.springframework.data.release.jira;
import lombok.RequiredArgsConstructor;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.release.git.GitProject;
import org.springframework.data.release.git.GitProperties;
import org.springframework.data.release.git.GitServer;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
@@ -39,16 +41,16 @@ import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriTemplate;
/**
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
@RequiredArgsConstructor
class GitHubIssueTracker implements IssueTracker {
private static final String MILESTONE_URI = "https://api.github.com/repos/spring-projects/{repoName}/milestones?state={state}";
@@ -59,6 +61,7 @@ class GitHubIssueTracker implements IssueTracker {
private final RestOperations operations;
private final Logger logger;
private final GitProperties properties;
/*
* (non-Javadoc)
@@ -112,7 +115,9 @@ class GitHubIssueTracker implements IssueTracker {
parameters.put("repoName", repositoryName);
parameters.put("id", milestone.getNumber());
return operations.exchange(URI_TEMPLATE, HttpMethod.GET, null, ISSUES_TYPE, parameters).getBody();
return operations
.exchange(URI_TEMPLATE, HttpMethod.GET, new HttpEntity<>(getAuthenticationHeaders()), ISSUES_TYPE, parameters)
.getBody();
}
private GitHubMilestone findMilestone(ModuleIteration module, String repositoryName) {
@@ -127,13 +132,13 @@ class GitHubIssueTracker implements IssueTracker {
logger.log(module, "Looking up milestone from %s…", milestoneUri);
List<GitHubMilestone> exchange = operations
.exchange(MILESTONE_URI, HttpMethod.GET, null, MILESTONES_TYPE, parameters).getBody();
List<GitHubMilestone> exchange = operations.exchange(MILESTONE_URI, HttpMethod.GET,
new HttpEntity<>(getAuthenticationHeaders()), MILESTONES_TYPE, parameters).getBody();
GitHubMilestone milestone = null;
for (GitHubMilestone candidate : exchange) {
if (candidate.getTitle().contains(module.getVersionString())) {
if (candidate.getTitle().contains(module.getShortVersionString())) {
milestone = candidate;
}
}
@@ -144,7 +149,19 @@ class GitHubIssueTracker implements IssueTracker {
}
}
throw new IllegalStateException(String.format("No milestone found containing %s!", module.getVersionString()));
throw new IllegalStateException(String.format("No milestone found containing %s!", module.getShortVersionString()));
}
private HttpHeaders getAuthenticationHeaders() {
byte[] encodedAuth = Base64.getEncoder()
.encode(properties.getAuthenticationHeader().getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authHeader);
return headers;
}
public static void main(String[] args) {

View File

@@ -23,10 +23,13 @@ import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.release.git.GitProperties;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.utils.Logger;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.plugin.core.config.EnablePluginRegistries;
import org.springframework.plugin.core.OrderAwarePluginRegistry;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.DeserializationFeature;
@@ -37,15 +40,18 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
@Configuration
@EnableCaching
@PropertySource(value = "file:jira.properties", ignoreResourceNotFound = true)
@EnablePluginRegistries({ IssueTracker.class })
class JiraConfiguration {
class IssueTrackerConfiguration {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
@Bean
public PluginRegistry<IssueTracker, Project> issueTrackers(List<? extends IssueTracker> plugins) {
return OrderAwarePluginRegistry.create(plugins);
}
@Bean
public ObjectMapper jacksonObjectMapper() {
@@ -69,4 +75,14 @@ class JiraConfiguration {
return template;
}
@Bean
public Jira jira(Logger logger) {
return new Jira(restTemplate(), logger);
}
@Bean
public GitHubIssueTracker github(Logger logger, GitProperties properties) {
return new GitHubIssueTracker(restTemplate(), logger, properties);
}
}

View File

@@ -24,7 +24,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
@@ -40,15 +39,13 @@ import org.springframework.data.release.utils.Logger;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriTemplate;
/**
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
@RequiredArgsConstructor
class Jira implements JiraConnector {
private static final String JIRA_HOST = "https://jira.spring.io";

View File

@@ -17,10 +17,7 @@ package org.springframework.data.release.jira;
import lombok.Value;
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.Train;
/**
* @author Oliver Gierke
@@ -28,16 +25,7 @@ import org.springframework.data.release.model.Train;
@Value
class JiraVersion {
private final Module module;
private final Train train;
private final Iteration iteration;
public JiraVersion(ModuleIteration moduleIteration) {
this.module = moduleIteration.getModule();
this.iteration = moduleIteration.getIteration();
this.train = moduleIteration.getTrain();
}
private ModuleIteration module;
/*
* (non-Javadoc)
@@ -45,15 +33,6 @@ class JiraVersion {
*/
@Override
public String toString() {
Iteration iteration = this.iteration.isInitialIteration() && module.hasCustomFirstIteration() ? module
.getCustomFirstIteration() : this.iteration;
if (iteration.isServiceIteration()) {
return String.format("%s.%s (%s %s)", module.getVersion(), iteration.getBugfixValue(), train.getName(),
iteration.getName());
}
return String.format("%s %s (%s)", module.getVersion(), iteration.getName(), train.getName());
return module.getMediumVersionString();
}
}

View File

@@ -17,11 +17,11 @@ package org.springframework.data.release.jira;
import static org.springframework.data.release.model.Projects.*;
import lombok.Value;
import java.util.ArrayList;
import java.util.List;
import lombok.Value;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.util.StringUtils;

View File

@@ -1,251 +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.maven;
import static org.springframework.data.release.model.Phase.*;
import static org.springframework.data.release.model.Projects.*;
import lombok.RequiredArgsConstructor;
import java.io.File;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.GenericTypeResolver;
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.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.ExecutionUtils;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.io.XBFileIO;
/**
* @author Oliver Gierke
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class MavenOperations {
private static final String POM_XML = "pom.xml";
private final Workspace workspace;
private final ProjectionFactory projectionFactory;
private final OsCommandOperations os;
private final Logger logger;
public Pom getMavenProject(Project project) throws IOException {
File file = workspace.getFile(POM_XML, project);
return projectionFactory.io().file(file).read(Pom.class);
}
/**
* Updates the POM files for all Maven projects contained in the iteration:
* <ol>
* <li>Updates the BOM POM.</li>
* <li>Updates the dependency version to Spring Data Commons to the current release version for all projects depending
* on it.</li>
* <li>Switches to the Spring release Maven repository.</li>
* </ol>
* If {@link Phase} is {@link Phase#CLEANUP} the changes will be rolled back.
*
* @param iteration must not be {@literal null}.
* @param phase must not be {@literal null}.
* @throws Exception
*/
public void updatePom(TrainIteration iteration, final Phase phase) throws Exception {
Assert.notNull(iteration, "Train iteration must not be null!");
Assert.notNull(phase, "Phase must not be null!");
updateBomPom(iteration, phase);
final Repository repository = new Repository(iteration.getIteration());
final ArtifactVersion buildVersion = iteration.getModuleVersion(BUILD);
final ArtifactVersion nextBuildVersion = buildVersion.getNextDevelopmentVersion();
// Fix version of shared resources to to-be-released version.
execute(workspace.getFile("parent/pom.xml", BUILD), new PomCallback<ParentPom>() {
@Override
public void doWith(ParentPom pom) {
pom.setSharedResourcesVersion(phase.equals(PREPARE) ? buildVersion : nextBuildVersion);
}
});
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
final Project project = module.getProject();
if (!isMavenProject(project)) {
logger.log(module, "No pom.xml file found, skipping project.");
continue;
}
execute(workspace.getFile(POM_XML, project), new PomCallback<Pom>() {
@Override
public void doWith(Pom pom) {
for (Project dependency : project.getDependencies()) {
String dependencyProperty = dependency.getDependencyProperty();
if (pom.getProperty(dependencyProperty) == null) {
continue;
}
ArtifactVersion dependencyVersion = iteration.getModuleVersion(dependency);
ArtifactVersion version = CLEANUP.equals(phase) ? dependencyVersion.getNextDevelopmentVersion()
: dependencyVersion;
logger.log(project, "Updating %s dependency version property %s to %s.", dependency.getFullName(),
dependencyProperty, version);
pom.setProperty(dependencyProperty, version);
}
ArtifactVersion version = CLEANUP.equals(phase) ? nextBuildVersion : buildVersion;
logger.log(project, "Updating Spring Data Build Parent version to %s.", version);
pom.setParentVersion(version);
updateRepository(project, pom, repository, phase);
}
});
}
}
/**
* 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(TrainIteration iteration) throws Exception {
ExecutionUtils.run(iteration, module -> {
Project project = module.getProject();
if (BUILD.equals(project)) {
return;
}
if (!isMavenProject(project)) {
logger.log(project, "Skipping project as no pom.xml could be found in the working directory!");
return;
}
logger.log(project, "Triggering distribution build…");
ArtifactVersion version = ArtifactVersion.of(module);
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, module.getProject()).get();
if (result.hasError()) {
throw result.getException();
}
logger.log(project, "Successfully finished distribution build!");
});
}
private boolean isMavenProject(Project project) {
return workspace.getFile(POM_XML, project).exists();
}
private void updateBomPom(final TrainIteration iteration, final Phase phase) throws Exception {
File bomPomFile = workspace.getFile("bom/pom.xml", BUILD);
logger.log(BUILD, "Updating BOM pom.xml…");
execute(bomPomFile, new PomCallback<Pom>() {
@Override
public void doWith(Pom pom) {
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
Artifact artifact = new Artifact(module);
ArtifactVersion version = artifact.getVersion();
version = PREPARE.equals(phase) ? version : version.getNextDevelopmentVersion();
logger.log(BUILD, "%s", module);
pom.setDependencyManagementVersion(artifact.getArtifactId(), version);
for (String additionalArtifact : module.getProject().getAdditionalArtifacts()) {
pom.setDependencyManagementVersion(additionalArtifact, version);
}
}
}
});
}
private void updateRepository(Project project, Pom pom, Repository repository, Phase phase) {
String message = "Switching to Spring repository %s (%s).";
if (PREPARE.equals(phase)) {
logger.log(project, message, repository.getId(), repository.getUrl());
pom.setRepositoryId(repository.getSnapshotId(), repository.getId());
pom.setRepositoryUrl(repository.getId(), repository.getUrl());
} else {
logger.log(project, message, repository.getSnapshotId(), repository.getSnapshotUrl());
pom.setRepositoryId(repository.getId(), repository.getSnapshotId());
pom.setRepositoryUrl(repository.getSnapshotId(), repository.getSnapshotUrl());
}
}
@SuppressWarnings("unchecked")
private <T extends Pom> void execute(File file, PomCallback<T> callback) throws Exception {
XBFileIO io = projectionFactory.io().file(file);
Class<?> typeArgument = GenericTypeResolver.resolveTypeArgument(callback.getClass(), PomCallback.class);
T pom = (T) io.read(typeArgument);
callback.doWith(pom);
io.write(pom);
}
private interface PomCallback<T extends Pom> {
public void doWith(T pom);
}
}

View File

@@ -24,7 +24,6 @@ import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.io.Workspace.LineCallback;
import org.springframework.data.release.jira.Changelog;
import org.springframework.data.release.jira.IssueTracker;
import org.springframework.data.release.model.Iteration;
@@ -77,27 +76,23 @@ public class ReleaseOperations {
for (String location : CHANGELOG_LOCATIONS) {
boolean processed = workspace.processFile(location, module.getProject(), new LineCallback() {
boolean processed = workspace.processFile(location, module.getProject(), (line, number) -> {
@Override
public String doWith(String line, long number) {
if (line.startsWith("=")) {
if (line.startsWith("=")) {
StringBuilder builder = new StringBuilder();
builder.append(line).append("\n\n");
builder.append(changelog.toString());
StringBuilder builder = new StringBuilder();
builder.append(line).append("\n\n");
builder.append(changelog.toString());
return builder.toString();
} else {
return line;
}
return builder.toString();
} else {
return line;
}
});
if (processed) {
git.commit(module, "Updated changelog.", null);
git.commit(module, "Updated changelog.");
logger.log(module.getProject(), "Updated changelog %s.", location);
}
@@ -107,25 +102,14 @@ public class ReleaseOperations {
public void updateResources(TrainIteration iteration) throws Exception {
for (final ModuleIteration module : iteration) {
iteration.stream().forEach(module -> {
boolean processed = workspace.processFile("src/main/resources/notice.txt", module.getProject(),
new LineCallback() {
@Override
public String doWith(String line, long number) {
if (number != 0) {
return line;
}
return module.toString();
}
});
(line, number) -> number != 0 ? line : module.toString());
if (processed) {
logger.log(module, "Updated notice.txt.");
}
}
});
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2015 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.
@@ -13,30 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.model;
import lombok.Value;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Module;
/**
* @author Oliver Gierke
*/
@Value
class MavenProject {
@Value(staticConstructor = "of")
public class ArtifactCoordinate {
private final Module module;
String groupId, artifactId;
public String getGroupId() {
return "org.springframework.data";
}
public static ArtifactCoordinate from(String coordinate) {
public String getArtifactId() {
return String.format("spring-data-%s", module.getProject().getName().toLowerCase());
}
public ArtifactVersion getReleaseVersion() {
return ArtifactVersion.of(module.getVersion());
String[] parts = coordinate.split(":");
return new ArtifactCoordinate(parts[0], parts[1]);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2015 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.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public class ArtifactCoordinates {
public static ArtifactCoordinates NONE = forGroupId("org.springframework.data");
private final String groupId;
private final @Getter(AccessLevel.PACKAGE) List<ArtifactCoordinate> coordinates;
public static ArtifactCoordinates forGroupId(String groupId) {
return new ArtifactCoordinates(groupId, new ArrayList<>());
}
public ArtifactCoordinates artifacts(String... artifactIds) {
return new ArtifactCoordinates(groupId,
Arrays.stream(artifactIds)//
.map(artifactId -> ArtifactCoordinate.of(groupId, artifactId))//
.collect(Collectors.toList()));
}
public ArtifactCoordinates artifact(ArtifactCoordinate coordinate) {
List<ArtifactCoordinate> artifacts = new ArrayList<>(coordinates);
artifacts.add(coordinate);
return new ArtifactCoordinates(groupId, artifacts);
}
}

View File

@@ -20,9 +20,9 @@ package org.springframework.data.release.model;
*
* @author Oliver Gierke
*/
public interface IterationVersion {
Version getVersion();
public interface IterationVersion extends VersionAware {
Iteration getIteration();
boolean isServiceIteration();
}

View File

@@ -23,7 +23,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
*/
@Value
public class Module {
public class Module implements VersionAware {
private final Project project;
private final Version version;

View File

@@ -27,8 +27,14 @@ import lombok.RequiredArgsConstructor;
public class ModuleIteration implements IterationVersion {
private final @Getter Module module;
private final Iteration iteration;
private final @Getter Train train;
private final @Getter TrainIteration trainIteration;
/**
* @return the train
*/
public Train getTrain() {
return trainIteration.getTrain();
}
public ProjectKey getProjectKey() {
return module.getProject().getKey();
@@ -52,20 +58,33 @@ public class ModuleIteration implements IterationVersion {
* @see org.springframework.data.release.model.IterationVersion#getIteration()
*/
public Iteration getIteration() {
return this.iteration.isInitialIteration() && this.module.hasCustomFirstIteration() ? module
.getCustomFirstIteration() : this.iteration;
return trainIteration.getIteration().isInitialIteration() && this.module.hasCustomFirstIteration()
? module.getCustomFirstIteration() : this.trainIteration.getIteration();
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.model.IterationVersion#isServiceIteration()
*/
@Override
public boolean isServiceIteration() {
return getIteration().isServiceIteration();
}
/**
* Returns the {@link String} representation of the logical version of the {@link ModuleIteration}.
* Returns the {@link String} representation of the logical version of the {@link ModuleIteration}. This will
* abbreviate trailing zeros and not include the release train name.
*
* @return
*/
public String getVersionString() {
public String getShortVersionString() {
StringBuilder builder = new StringBuilder();
builder.append(ArtifactVersion.of(this).toShortString());
Iteration iteration = trainIteration.getIteration();
if (!iteration.isServiceIteration()) {
builder.append(" ").append(iteration.getName());
}
@@ -73,12 +92,41 @@ public class ModuleIteration implements IterationVersion {
return builder.toString();
}
public String getMediumVersionString() {
StringBuilder builder = new StringBuilder();
builder.append(ArtifactVersion.of(this).toShortString());
Iteration iteration = trainIteration.getIteration();
if (iteration.isServiceIteration()) {
builder.append(" (").append(trainIteration.toString());
} else {
builder.append(" ").append(iteration.getName()).append(" (");
builder.append(trainIteration.getTrain().getName());
}
return builder.append(")").toString();
}
/**
* Returns the {@link String} representation of the logical version of the {@link ModuleIteration}. This will use the
* technical version string and append the train iteration.
*
* @return
*/
public String getFullVersionString() {
String result = ArtifactVersion.of(this).toString();
return result.concat(" (").concat(trainIteration.toString()).concat(")");
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s %s", module.getProject().getFullName(), getVersionString());
return String.format("%s %s", module.getProject().getFullName(), getShortVersionString());
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2016 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.RequiredArgsConstructor;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.data.release.Streamable;
/**
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public class Modules implements Streamable<Module> {
private final Collection<Module> modules;
public Iterator<Module> iterator() {
return modules.iterator();
}
}

View File

@@ -20,5 +20,5 @@ package org.springframework.data.release.model;
*/
public enum Phase {
PREPARE, CLEANUP;
PREPARE, CLEANUP, MAINTENANCE;
}

View File

@@ -19,8 +19,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.util.Assert;
@@ -35,17 +35,18 @@ public class Project {
private final @Getter String name;
private final @Getter List<Project> dependencies;
private final Tracker tracker;
private final @Getter List<String> additionalArtifacts;
private final @Getter ArtifactCoordinates additionalArtifacts;
Project(String key, String name, List<Project> dependencies) {
this(key, name, Tracker.JIRA, dependencies, Collections.emptyList());
this(key, name, Tracker.JIRA, dependencies, ArtifactCoordinates.NONE);
}
Project(String key, String name, List<Project> dependencies, List<String> additionalArtifacts) {
Project(String key, String name, List<Project> dependencies, ArtifactCoordinates additionalArtifacts) {
this(key, name, Tracker.JIRA, dependencies, additionalArtifacts);
}
Project(String key, String name, Tracker tracker, List<Project> dependencies, List<String> additionalArtifacts) {
Project(String key, String name, Tracker tracker, List<Project> dependencies,
ArtifactCoordinates additionalArtifacts) {
this.key = new ProjectKey(key);
this.name = name;
@@ -66,6 +67,10 @@ public class Project {
return "springdata.".concat(name.toLowerCase());
}
public void doWithAdditionalArtifacts(Consumer<ArtifactCoordinate> consumer) {
additionalArtifacts.getCoordinates().forEach(consumer);
}
/**
* Returns whether the current project depends on the given one.
*
@@ -75,6 +80,7 @@ public class Project {
public boolean dependsOn(Project project) {
Assert.notNull(project, "Project must not be null!");
return dependencies.contains(project);
return dependencies.stream().anyMatch(dependency -> dependency.equals(project) || dependency.dependsOn(project));
}
}

View File

@@ -15,10 +15,16 @@
*/
package org.springframework.data.release.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.traverse.TopologicalOrderIterator;
/**
* @author Oliver Gierke
*/
@@ -30,26 +36,60 @@ public class Projects {
static {
BUILD = new Project("DATABUILD", "Build", Tracker.GITHUB, Collections.emptyList(), Collections.emptyList());
BUILD = new Project("DATABUILD", "Build", Tracker.GITHUB, Collections.emptyList(),
ArtifactCoordinates.forGroupId("org.springframework.data.build")
.artifacts("spring-data-build-parent", "spring-data-build-resources")
.artifact(ArtifactCoordinate.of("org.springframework.data", "spring-data-releasetrain")));
COMMONS = new Project("DATACMNS", "Commons", Arrays.asList(BUILD));
JPA = new Project("DATAJPA", "JPA", Arrays.asList(COMMONS));
MONGO_DB = new Project("DATAMONGO", "MongoDB", Arrays.asList(COMMONS),
Arrays.asList("spring-data-mongodb-cross-store", "spring-data-mongodb-log4j"));
ArtifactCoordinates.NONE.artifacts("spring-data-mongodb-cross-store", "spring-data-mongodb-log4j"));
NEO4J = new Project("DATAGRAPH", "Neo4j", Arrays.asList(COMMONS));
SOLR = new Project("DATASOLR", "Solr", Arrays.asList(COMMONS));
COUCHBASE = new Project("DATACOUCH", "Couchbase", Arrays.asList(COMMONS));
CASSANDRA = new Project("DATACASS", "Cassandra", Arrays.asList(COMMONS), Arrays.asList("spring-cql"));
CASSANDRA = new Project("DATACASS", "Cassandra", Arrays.asList(COMMONS),
ArtifactCoordinates.NONE.artifacts("spring-cql"));
ELASTICSEARCH = new Project("DATAES", "Elasticsearch", Arrays.asList(COMMONS));
REDIS = new Project("DATAREDIS", "Redis", Collections.emptyList());
GEMFIRE = new Project("SGF", "Gemfire", Arrays.asList(COMMONS));
REST = new Project("DATAREST", "REST", Arrays.asList(COMMONS, JPA, MONGO_DB, NEO4J, GEMFIRE, SOLR, CASSANDRA),
Arrays.asList("spring-data-rest-core", "spring-data-rest-core", "spring-data-rest-hal-browser"));
ArtifactCoordinates.NONE.artifacts("spring-data-rest-core", "spring-data-rest-core",
"spring-data-rest-hal-browser"));
KEY_VALUE = new Project("DATAKV", "KeyValue", Arrays.asList(COMMONS));
ENVERS = new Project("DATAENV", "Envers", Arrays.asList(JPA, COMMONS));
ENVERS = new Project("DATAENV", "Envers", Tracker.GITHUB, Arrays.asList(JPA, COMMONS), ArtifactCoordinates.NONE);
PROJECTS = Arrays.asList(BUILD, COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, ELASTICSEARCH, REDIS,
GEMFIRE, REST, KEY_VALUE, ENVERS);
List<Project> projects = Arrays.asList(BUILD, COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA,
ELASTICSEARCH, REDIS, GEMFIRE, REST, KEY_VALUE, ENVERS);
DefaultDirectedGraph<Project, DefaultEdge> graph = new DefaultDirectedGraph<>(DefaultEdge.class);
projects.forEach(project -> {
graph.addVertex(project);
project.getDependencies().forEach(dependency -> {
graph.addEdge(project, dependency);
});
});
Iterator<Project> iterator = new TopologicalOrderIterator<>(graph);
List<Project> intermediate = new ArrayList<>(projects.size());
while (iterator.hasNext()) {
intermediate.add(iterator.next());
}
Collections.reverse(intermediate);
PROJECTS = Collections.unmodifiableList(intermediate);
}
public static Project byName(String name) {
return PROJECTS.stream().//
filter(project -> project.getName().equalsIgnoreCase(name)).//
findFirst().orElseThrow(() -> new IllegalArgumentException("No project named %s available!"));
}
}

View File

@@ -36,8 +36,8 @@ public class ReleaseTrains {
EVANS = DIJKSTRA.next("Evans", Transition.MINOR);
FOWLER = EVANS.next("Fowler", Transition.MINOR);
GOSLING = FOWLER.next("Gosling", Transition.MINOR, new Module(KEY_VALUE, "1.0"));
HOPPER = GOSLING.next("Hopper", Transition.MINOR, new Module(NEO4J, "4.1"), new Module(COUCHBASE, "2.1"),
new Module(ENVERS, "1.0"));
HOPPER = GOSLING.next("Hopper", Transition.MINOR, new Module(SOLR, "2.0"), new Module(ENVERS, "1.0"),
new Module(NEO4J, "4.1"), new Module(COUCHBASE, "2.1"));
// Trains

View File

@@ -23,13 +23,13 @@ import lombok.Value;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.data.release.Streamable;
import org.springframework.shell.support.util.OsUtils;
import org.springframework.util.Assert;
@@ -37,23 +37,20 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
*/
@Value
public class Train implements Iterable<Module> {
public class Train implements Streamable<Module> {
private final String name;;
private final Collection<Module> modules;
private final Modules modules;
private final Iterations iterations;
public Train(String name, Module... modules) {
this.name = name;
this.modules = Arrays.asList(modules);
this.iterations = Iterations.DEFAULT;
this(name, Arrays.asList(modules));
}
public Train(String name, Collection<Module> modules) {
this.name = name;
this.modules = Collections.unmodifiableCollection(modules);
this.modules = new Modules(modules);
this.iterations = Iterations.DEFAULT;
}
@@ -66,6 +63,12 @@ public class Train implements Iterable<Module> {
return modules.iterator();
}
public boolean contains(Project project) {
return modules.stream().//
anyMatch(module -> module.getProject().equals(project));
}
public Module getModule(String name) {
return modules.stream().//
@@ -98,7 +101,7 @@ public class Train implements Iterable<Module> {
return modules.stream().//
filter(module -> module.hasName(moduleName)).//
findFirst().//
map(module -> new ModuleIteration(module, iteration, this)).//
map(module -> new ModuleIteration(module, new TrainIteration(this, iteration))).//
orElseThrow(
() -> new IllegalArgumentException(String.format("No module found with module name %s!", moduleName)));
}
@@ -113,7 +116,8 @@ public class Train implements Iterable<Module> {
return modules.stream().//
filter(module -> !exclusionList.contains(module.getProject())).//
map(module -> new ModuleIteration(module, iteration, this)).//
map(module -> new ModuleIteration(module, new TrainIteration(this, iteration))).//
sorted().//
collect(Collectors.toList());
}
@@ -125,7 +129,7 @@ public class Train implements Iterable<Module> {
Module module = getModule(project);
return ArtifactVersion.of(new ModuleIteration(module, iteration, this));
return ArtifactVersion.of(new ModuleIteration(module, new TrainIteration(this, iteration)));
}
/*

View File

@@ -18,12 +18,15 @@ package org.springframework.data.release.model;
import lombok.Value;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.release.Streamable;
/**
* @author Oliver Gierke
*/
@Value
public class TrainIteration implements Iterable<ModuleIteration> {
public class TrainIteration implements Streamable<ModuleIteration> {
private final Train train;
private final Iteration iteration;
@@ -49,16 +52,29 @@ public class TrainIteration implements Iterable<ModuleIteration> {
return train.getModuleIteration(iteration, project.getName());
}
public Iterable<ModuleIteration> getModulesExcept(Project... exclusions) {
public List<ModuleIteration> getModulesExcept(Project... exclusions) {
return train.getModuleIterations(iteration, exclusions);
}
public boolean contains(Project project) {
return train.contains(project);
}
public ModuleIteration getPreviousIteration(ModuleIteration module) {
Iteration previousIteration = train.getIterations().getPreviousIteration(iteration);
return train.getModuleIteration(previousIteration, module.getProject().getName());
}
/**
* Returns the version string to be used for the train iteration.
*
* @return
*/
public String toVersionString() {
return toString().replace(' ', '-');
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
@@ -67,4 +83,5 @@ public class TrainIteration implements Iterable<ModuleIteration> {
public String toString() {
return String.format("%s %s", train.getName(), iteration.getName());
}
}

View File

@@ -0,0 +1,94 @@
package org.springframework.data.release.model;
import static org.springframework.data.release.model.Projects.*;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.build.Repository;
import org.springframework.util.Assert;
/**
* Value object to expose update information for a given {@link TrainIteration} and phase.
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public class UpdateInformation {
private final @NonNull @Getter TrainIteration iteration;
private final @NonNull @Getter Phase phase;
/**
* Returns the {@link ArtifactVersion} to be set for the given {@link Project}.
*
* @param dependency must not be {@literal null}.
* @return will never be {@literal null}.
*/
public ArtifactVersion getProjectVersionToSet(Project dependency) {
Assert.notNull(dependency, "Project must not be null!");
ArtifactVersion dependencyVersion = iteration.getModuleVersion(dependency);
switch (phase) {
case PREPARE:
return dependencyVersion;
case CLEANUP:
return dependencyVersion.getNextDevelopmentVersion();
case MAINTENANCE:
return dependencyVersion.getNextBugfixVersion();
}
throw new IllegalStateException("Unexpected phase detected " + phase + " detected!");
}
/**
* Returns the {@link ArtifactVersion} to be set for the parent reference.
*
* @return will never be {@literal null}.
*/
public ArtifactVersion getParentVersionToSet() {
ArtifactVersion version = iteration.getModuleVersion(BUILD);
switch (phase) {
case PREPARE:
return version;
case CLEANUP:
return version.getNextDevelopmentVersion();
case MAINTENANCE:
return version.getNextBugfixVersion();
}
throw new IllegalStateException("Unexpected phase detected " + phase + " detected!");
}
/**
* Returns the {@link Repository} to use (milestone or release).
*
* @return will never be {@literal null}.
*/
public Repository getRepository() {
return new Repository(iteration.getIteration());
}
/**
* Returns the version {@link String} to be used to describe the release train.
*
* @return will never be {@literal null}.
*/
public String getReleaseTrainVersion() {
switch (phase) {
case PREPARE:
return iteration.toVersionString();
case CLEANUP:
case MAINTENANCE:
return iteration.getTrain().getName().concat("-BUILD-SNAPSHOT");
}
throw new IllegalStateException("Unexpected phase detected " + phase + " detected!");
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2015 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 VersionAware {
Version getVersion();
}

View File

@@ -19,8 +19,8 @@ import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.data.release.Streamable;
import org.springframework.util.Assert;
/**
@@ -35,15 +35,15 @@ public class ExecutionUtils {
* all executions to complete before returning. Exceptions being thrown in the {@link ConsumerWithException} will be
* converted into {@link RuntimeException}s.
*
* @param iterable must not be {@literal null}.
* @param streamable must not be {@literal null}.
* @param consumer must not be {@literal null}.
*/
public static <T> void run(Iterable<T> iterable, ConsumerWithException<T> consumer) {
public static <T> void run(Streamable<T> streamable, ConsumerWithException<T> consumer) {
Assert.notNull(iterable, "Iterable must not be null!");
Assert.notNull(streamable, "Streamable must not be null!");
Assert.notNull(consumer, "Consumer must not be null!");
StreamSupport.stream(iterable.spliterator(), false).//
streamable.stream().//
map(it -> CompletableFuture.runAsync(() -> {
try {
consumer.accept(it);
@@ -55,19 +55,19 @@ public class ExecutionUtils {
}
/**
* Runs the given {@link Function} for each element in the given {@link Iterable} in parallel waiting for all
* Runs the given {@link Function} for each element in the given {@link Streamable} in parallel waiting for all
* executions to complete before returning the results.
*
* @param iterable must not be {@literal null}.
* @param streamable must not be {@literal null}.
* @param function must not be {@literal null}.
* @return
*/
public static <T, S> Collection<S> runAndReturn(Iterable<T> iterable, Function<T, S> function) {
public static <T, S> Collection<S> runAndReturn(Streamable<T> streamable, Function<T, S> function) {
Assert.notNull(iterable, "Iterable must not be null!");
Assert.notNull(streamable, "Iterable must not be null!");
Assert.notNull(function, "Consumer must not be null!");
return StreamSupport.stream(iterable.spliterator(), false).//
return streamable.stream().//
map(it -> CompletableFuture.supplyAsync(() -> function.apply(it))).//
collect(Collectors.toList()).//
stream().//

View File

@@ -32,23 +32,23 @@ public class Logger {
private final java.util.logging.Logger LOGGER = HandlerUtils.getLogger(getClass());
public void log(ModuleIteration module, String template, Object... args) {
public void log(ModuleIteration module, Object template, Object... args) {
log(module.getProject(), template, args);
}
public void log(Project project, String template, Object... args) {
public void log(Project project, Object template, Object... args) {
log(project.getName(), template, args);
}
public void log(TrainIteration iteration, String template, Object... args) {
public void log(TrainIteration iteration, Object template, Object... args) {
log(iteration.toString(), template, args);
}
public void log(Train train, String template, Object... args) {
public void log(Train train, Object template, Object... args) {
log(train.getName(), template, args);
}
private void log(String context, String template, Object... args) {
LOGGER.info(String.format(PREFIX_TEMPLATE, context, String.format(template, args)));
private void log(String context, Object template, Object... args) {
LOGGER.info(String.format(PREFIX_TEMPLATE, context, String.format(template.toString(), args)));
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2016 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.utils;
import java.util.function.Function;
/**
* @author Oliver Gierke
*/
public interface StreamUtils {
public static <T, S> Function<T, S> wrap(FunctionWithException<T, S> function) {
return it -> {
try {
return function.apply(it);
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
};
}
public interface FunctionWithException<T, S> {
S apply(T source) throws Exception;
}
}

View File

@@ -1 +1,12 @@
io.work-dir=~/temp/spring-data-shell
maven.release-command=install -DskipTests
#maven.plugins.deploy=org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy
maven.plugins.versions-set=org.codehaus.mojo:versions-maven-plugin:2.2:set
maven.plugins.artifactory-deploy=org.jfrog.buildinfo:artifactory-maven-plugin:2.4.0:publish
deployment.server.uri=https://repo.spring.io
deployment.staging-repository=libs-staging-local
deployment.username=buildmaster
#deployment.password <- local

View File

@@ -23,7 +23,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Oliver Gierke
*/
@ActiveProfiles({ "test", "local" })
@ActiveProfiles({ "local", "test" })
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public abstract class AbstractIntegrationTests {}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2016 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;
import org.junit.Test;
/**
* @author Oliver Gierke
*/
public class ApplicationTests extends AbstractIntegrationTests {
@Test
public void bootstrapsApplication() {}
}

View File

@@ -19,7 +19,7 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.release.maven.Artifact;
import org.springframework.data.release.build.MavenArtifact;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ReleaseTrains;
@@ -32,7 +32,7 @@ public class ArtifactUnitTests {
@Test
public void testname() {
Artifact artifact = new Artifact(ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "JPA"));
MavenArtifact artifact = new MavenArtifact(ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "JPA"));
assertThat(artifact.getArtifactId(), is("spring-data-jpa"));
assertThat(artifact.getVersion(), is(ArtifactVersion.of("1.6.0.M1")));

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.maven;
package org.springframework.data.release.build;
import java.io.IOException;
@@ -21,8 +21,16 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.git.GitOperations;
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.Phase;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.io.XBFileIO;
@@ -33,6 +41,8 @@ public class MavenIntegrationTests extends AbstractIntegrationTests {
@Autowired Workspace workspace;
@Autowired ProjectionFactory projection;
@Autowired MavenBuildSystem maven;
@Autowired GitOperations git;
@Test
public void modifiesParentPomCorrectly() throws IOException {
@@ -42,7 +52,7 @@ public class MavenIntegrationTests extends AbstractIntegrationTests {
ParentPom pom = io.read(ParentPom.class);
pom.setSharedResourcesVersion(ArtifactVersion.of("1.2.0.RELEASE"));
// System.out.println(projection.asString(pom));
System.out.println(projection.asString(pom));
}
@Test
@@ -57,4 +67,17 @@ public class MavenIntegrationTests extends AbstractIntegrationTests {
// System.out.println(projection.asString(pom));
}
@Test
public void testname() throws Exception {
git.update(ReleaseTrains.HOPPER);
TrainIteration iteration = new TrainIteration(ReleaseTrains.HOPPER, Iteration.M1);
ModuleIteration build = iteration.getModule(Projects.BUILD);
UpdateInformation information = new UpdateInformation(iteration, Phase.PREPARE);
maven.updateProjectDescriptors(build, information);
maven.prepareVersion(build, Phase.PREPARE);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2015-2016 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.deployment;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.TrainIteration;
/**
* Integration test for {@link DeploymentInformation}.
*
* @author Oliver Gierke
*/
public class DeploymentInformationIntegrationTests extends AbstractIntegrationTests {
@Autowired DeploymentProperties properties;
@Test
public void createsDeploymentInformation() {
TrainIteration iteration = new TrainIteration(ReleaseTrains.HOPPER, Iteration.M1);
ModuleIteration buildModule = iteration.getModule(Projects.BUILD);
DeploymentInformation information = new DeploymentInformation(buildModule, properties);
assertThat(information.getDeploymentTargetUrl(), containsString(properties.getServer().getUri().toString()));
assertThat(information.getBuildName(), is("Spring Data Build - Release"));
assertThat(information.getTargetRepository(), is("test-libs-milestone-local"));
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2015 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.deployment;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.build.BuildOperations;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
/**
* @author Oliver Gierke
*/
public class DeploymentOperationsIntegrationTests extends AbstractIntegrationTests {
@Autowired GitOperations git;
@Autowired BuildOperations build;
@Autowired DeploymentOperations deployment;
@Autowired ArtifactoryClient client;
@Test
public void testname() {
Train train = ReleaseTrains.HOPPER;
ModuleIteration buildModule = train.getModuleIteration(Iteration.M1, "build");
git.update(train);
build.prepareVersion(buildModule, Phase.PREPARE);
DeploymentInformation information = build.buildAndDeployRelease(buildModule);
try {
deployment.promote(information);
} finally {
client.deleteArtifacts(information);
}
}
}

View File

@@ -31,7 +31,9 @@ public class ModuleIterationUnitTests {
TrainIteration iteration = new TrainIteration(ReleaseTrains.DIJKSTRA, Iteration.M1);
ModuleIteration module = iteration.getModule(Projects.JPA);
assertThat(module.getVersionString(), is("1.6 M1"));
assertThat(module.getShortVersionString(), is("1.6 M1"));
assertThat(module.getMediumVersionString(), is("1.6 M1 (Dijkstra)"));
assertThat(module.getFullVersionString(), is("1.6.0.M1 (Dijkstra M1)"));
}
@Test
@@ -40,6 +42,8 @@ public class ModuleIterationUnitTests {
TrainIteration iteration = new TrainIteration(ReleaseTrains.DIJKSTRA, Iteration.SR1);
ModuleIteration module = iteration.getModule(Projects.JPA);
assertThat(module.getVersionString(), is("1.6.1"));
assertThat(module.getShortVersionString(), is("1.6.1"));
assertThat(module.getMediumVersionString(), is("1.6.1 (Dijkstra SR1)"));
assertThat(module.getFullVersionString(), is("1.6.1.RELEASE (Dijkstra SR1)"));
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2015 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
/**
* @author Oliver Gierke
*/
public class ProjectUnitTests {
@Test
public void testname() {
List<Project> projects = new ArrayList<>(Projects.PROJECTS);
// Collections.reverse(projects);
// Collections.sort(projects);
projects.stream().map(Project::getName).forEach(System.out::println);
System.out.println();
assertThat(projects.get(0), is(Projects.BUILD));
assertThat(projects.get(1), is(Projects.COMMONS));
}
}

View File

@@ -22,4 +22,13 @@ public class SimpleIterationVersion implements IterationVersion {
private final Version version;
private final Iteration iteration;
/*
* (non-Javadoc)
* @see org.springframework.data.release.model.IterationVersion#isServiceIteration()
*/
@Override
public boolean isServiceIteration() {
return iteration.isServiceIteration();
}
}

View File

@@ -39,4 +39,18 @@ public class TrainsUnitTest {
public void addsNewlyAddedModule() {
assertThat(ReleaseTrains.HOPPER.getModule(Projects.ENVERS), is(notNullValue()));
}
@Test
public void testname() {
Iterable<ModuleIteration> iterations = ReleaseTrains.HOPPER.getModuleIterations(Iteration.M1);
for (ModuleIteration iteration : iterations) {
System.out.println(iteration);
}
System.out.println();
iterations.forEach(System.out::println);
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2015 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
/**
* Unit tests for {@link UpdateInformation}.
*
* @author Oliver Gierke
*/
public class UpdateInformationUnitTests {
TrainIteration hopperM1 = new TrainIteration(ReleaseTrains.HOPPER, Iteration.M1);
@Test(expected = IllegalArgumentException.class)
public void rejectsNullTrainIteration() {
new UpdateInformation(null, Phase.CLEANUP);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullPhase() {
new UpdateInformation(hopperM1, null);
}
@Test
public void exposesMilestoneRepositoryForMilestone() {
assertThat(new UpdateInformation(hopperM1, Phase.PREPARE).getRepository().getId(), is("spring-libs-milestone"));
}
@Test
public void exposesReleaseRepositoryForGA() {
Arrays.asList(Iteration.GA, Iteration.SR1).forEach(iteration -> {
TrainIteration trainIteration = new TrainIteration(ReleaseTrains.HOPPER, iteration);
assertThat(new UpdateInformation(trainIteration, Phase.PREPARE).getRepository().getId(),
is("spring-libs-release"));
});
}
@Test
public void calculatesProjectVersionToSetCorrectly() {
UpdateInformation updateInformation = new UpdateInformation(hopperM1, Phase.PREPARE);
assertThat(updateInformation.getProjectVersionToSet(Projects.JPA).toString(), is("1.10.0.M1"));
updateInformation = new UpdateInformation(hopperM1, Phase.CLEANUP);
assertThat(updateInformation.getProjectVersionToSet(Projects.JPA).toString(), is("1.10.0.BUILD-SNAPSHOT"));
}
}

View File

@@ -1,3 +1,14 @@
io.work-dir=~/temp/shell-test
# Logging
logging.level.org.springframework=WARN
logging.level.org.springframework.data.release=INFO
logging.level.org.springframework.web.client=TRACE
# Workspace
io.work-dir=~/temp/shell-test
# Maven
maven.localRepository=~/temp/.m2/repository
# Deployment
deployment.server.uri=https://repo.spring.io
deployment.repository-prefix=test-