diff --git a/release-tools/application-local.template b/release-tools/application-local.template new file mode 100644 index 0000000..57d53ff --- /dev/null +++ b/release-tools/application-local.template @@ -0,0 +1,11 @@ +# Git +git.username= +git.author= +git.email= +git.password= + +# Maven +maven.mavenHome= + +# Deployment +deployment.password= diff --git a/release-tools/lombok.config b/release-tools/lombok.config new file mode 100644 index 0000000..71bb610 --- /dev/null +++ b/release-tools/lombok.config @@ -0,0 +1 @@ +lombok.nonNull.exceptionType = IllegalArgumentException diff --git a/release-tools/pom.xml b/release-tools/pom.xml index bc65060..56efef4 100644 --- a/release-tools/pom.xml +++ b/release-tools/pom.xml @@ -8,12 +8,13 @@ org.springframework.boot spring-boot-starter-parent - 1.3.0.RELEASE + 1.3.2.RELEASE 1.8 org.springframework.data.release.Application + 1.3.0.BUILD-SNAPSHOT @@ -50,9 +51,9 @@ - org.xmlbeam - xmlprojector - 1.4.7 + org.xmlbeam + xmlprojector + 1.4.7 @@ -64,7 +65,7 @@ org.projectlombok lombok - 1.16.4 + 1.16.6 provided @@ -81,7 +82,7 @@ org.eclipse.jgit org.eclipse.jgit - 4.0.1.201506240215-r + 4.2.0.201601211800-r @@ -89,6 +90,19 @@ spring-boot-configuration-processor true + + + org.apache.maven.shared + maven-invoker + 2.2 + + + + org.jgrapht + jgrapht-core + 0.9.1 + + diff --git a/release-tools/readme.md b/release-tools/readme.md index 8d34a9a..b3cc1a4 100644 --- a/release-tools/readme.md +++ b/release-tools/readme.md @@ -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` diff --git a/release-tools/src/main/java/org/springframework/data/release/Streamable.java b/release-tools/src/main/java/org/springframework/data/release/Streamable.java new file mode 100644 index 0000000..701cb77 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/Streamable.java @@ -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 extends Iterable { + + /** + * Creates a non-parallel {@link Stream} of the underlying {@link Iterable}. + * + * @return will never be {@literal null}. + */ + public default Stream stream() { + return StreamSupport.stream(spliterator(), false); + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/announcement/AnnouncementCommands.java b/release-tools/src/main/java/org/springframework/data/release/announcement/AnnouncementCommands.java new file mode 100644 index 0000000..61536ed --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/announcement/AnnouncementCommands.java @@ -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)); + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java b/release-tools/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java index 41c6b47..9e3bf08 100644 --- a/release-tools/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/announcement/AnnouncementOperations.java @@ -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))); - } } diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/MavenConfig.java b/release-tools/src/main/java/org/springframework/data/release/build/BuildConfiguration.java similarity index 66% rename from release-tools/src/main/java/org/springframework/data/release/maven/MavenConfig.java rename to release-tools/src/main/java/org/springframework/data/release/build/BuildConfiguration.java index 7e10646..e5d5ca7 100644 --- a/release-tools/src/main/java/org/springframework/data/release/maven/MavenConfig.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/BuildConfiguration.java @@ -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 buildSystems(List 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); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/build/BuildOperations.java b/release-tools/src/main/java/org/springframework/data/release/build/BuildOperations.java new file mode 100644 index 0000000..2ab0e5e --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/build/BuildOperations.java @@ -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 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 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 List doWithBuildSystem(TrainIteration iteration, + BiFunction 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 doWithBuildSystem(ModuleIteration module, BiFunction function) { + return function.apply(buildSystems.getPluginFor(module.getProject(), () -> new IllegalStateException( + String.format("No build system plugin found for project %s!", module.getProject()))), module); + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/build/BuildSystem.java b/release-tools/src/main/java/org/springframework/data/release/build/BuildSystem.java new file mode 100644 index 0000000..133d56e --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/build/BuildSystem.java @@ -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 { + + /** + * @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); +} diff --git a/release-tools/src/main/java/org/springframework/data/release/build/GradleBuildSystem.java b/release-tools/src/main/java/org/springframework/data/release/build/GradleBuildSystem.java new file mode 100644 index 0000000..42ebb6c --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/build/GradleBuildSystem.java @@ -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(); + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/GroupId.java b/release-tools/src/main/java/org/springframework/data/release/build/GroupId.java similarity index 91% rename from release-tools/src/main/java/org/springframework/data/release/maven/GroupId.java rename to release-tools/src/main/java/org/springframework/data/release/build/GroupId.java index 1f47335..e9d4892 100644 --- a/release-tools/src/main/java/org/springframework/data/release/maven/GroupId.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/GroupId.java @@ -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; diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/Artifact.java b/release-tools/src/main/java/org/springframework/data/release/build/MavenArtifact.java similarity index 83% rename from release-tools/src/main/java/org/springframework/data/release/maven/Artifact.java rename to release-tools/src/main/java/org/springframework/data/release/build/MavenArtifact.java index e033592..0dde854 100644 --- a/release-tools/src/main/java/org/springframework/data/release/maven/Artifact.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/MavenArtifact.java @@ -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!"); diff --git a/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java b/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java new file mode 100644 index 0000000..6ef0a67 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java @@ -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 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 callback) { + execute(file, Pom.class, callback); + } + + private void execute(File file, Class type, Consumer 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); + } + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/build/MavenProperties.java b/release-tools/src/main/java/org/springframework/data/release/build/MavenProperties.java new file mode 100644 index 0000000..7c7460b --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/build/MavenProperties.java @@ -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 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; + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/build/MavenRuntime.java b/release-tools/src/main/java/org/springframework/data/release/build/MavenRuntime.java new file mode 100644 index 0000000..9127240 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/build/MavenRuntime.java @@ -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 arguments) { + + DefaultInvocationRequest request = new DefaultInvocationRequest(); + request.setJavaHome(os.getJavaHome()); + request.setShellEnvironmentInherited(true); + request.setBaseDirectory(workspace.getProjectDirectory(project)); + + List 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); + } + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/ParentPom.java b/release-tools/src/main/java/org/springframework/data/release/build/ParentPom.java similarity index 87% rename from release-tools/src/main/java/org/springframework/data/release/maven/ParentPom.java rename to release-tools/src/main/java/org/springframework/data/release/build/ParentPom.java index c632da6..44d8024 100644 --- a/release-tools/src/main/java/org/springframework/data/release/maven/ParentPom.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/ParentPom.java @@ -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); } diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/Pom.java b/release-tools/src/main/java/org/springframework/data/release/build/Pom.java similarity index 85% rename from release-tools/src/main/java/org/springframework/data/release/maven/Pom.java rename to release-tools/src/main/java/org/springframework/data/release/build/Pom.java index 0687c2e..63deca5 100644 --- a/release-tools/src/main/java/org/springframework/data/release/maven/Pom.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/Pom.java @@ -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()); + } } } diff --git a/release-tools/src/main/java/org/springframework/data/release/build/PomUpdater.java b/release-tools/src/main/java/org/springframework/data/release/build/PomUpdater.java new file mode 100644 index 0000000..56e2e04 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/build/PomUpdater.java @@ -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()); + } + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/Repository.java b/release-tools/src/main/java/org/springframework/data/release/build/Repository.java similarity index 68% rename from release-tools/src/main/java/org/springframework/data/release/maven/Repository.java rename to release-tools/src/main/java/org/springframework/data/release/build/Repository.java index d8a8e93..5119163 100644 --- a/release-tools/src/main/java/org/springframework/data/release/maven/Repository.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/Repository.java @@ -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() { diff --git a/release-tools/src/main/java/org/springframework/data/release/cli/IssueTrackerCommands.java b/release-tools/src/main/java/org/springframework/data/release/cli/IssueTrackerCommands.java index f2c0ebd..6d4742a 100644 --- a/release-tools/src/main/java/org/springframework/data/release/cli/IssueTrackerCommands.java +++ b/release-tools/src/main/java/org/springframework/data/release/cli/IssueTrackerCommands.java @@ -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 tracker, JiraConnector jira, diff --git a/release-tools/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java b/release-tools/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java index a8b4268..952dd07 100644 --- a/release-tools/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java +++ b/release-tools/src/main/java/org/springframework/data/release/cli/ReleaseCommands.java @@ -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 = 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); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java b/release-tools/src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java index 6850c2a..5856ecd 100644 --- a/release-tools/src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java +++ b/release-tools/src/main/java/org/springframework/data/release/cli/TrainIterationConverter.java @@ -70,6 +70,6 @@ public class TrainIterationConverter implements Converter { } } - return false; + return true; } } diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/ArtifactoryClient.java b/release-tools/src/main/java/org/springframework/data/release/deployment/ArtifactoryClient.java new file mode 100644 index 0000000..5d7cffa --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/deployment/ArtifactoryClient.java @@ -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; + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentConfiguration.java b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentConfiguration.java new file mode 100644 index 0000000..634a471 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentConfiguration.java @@ -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); + } + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentInformation.java b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentInformation.java new file mode 100644 index 0000000..4d0d380 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentInformation.java @@ -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 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 getBuildInfoParameters() { + + Map parameters = new HashMap<>(); + parameters.put("buildNumber", buildNumber); + parameters.put("buildName", getBuildName()); + + return parameters; + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentOperations.java b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentOperations.java new file mode 100644 index 0000000..eab57cd --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentOperations.java @@ -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); + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java new file mode 100644 index 0000000..fad0f0a --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java @@ -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()); + } + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/Errors.java b/release-tools/src/main/java/org/springframework/data/release/deployment/Errors.java new file mode 100644 index 0000000..92f0d3d --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/deployment/Errors.java @@ -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 errors = new ArrayList<>(); + private List messages = new ArrayList<>(); + + public List 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); + } + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/docs/DocumentationOperations.java b/release-tools/src/main/java/org/springframework/data/release/docs/DocumentationOperations.java index 2ac390d..9025d19 100644 --- a/release-tools/src/main/java/org/springframework/data/release/docs/DocumentationOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/docs/DocumentationOperations.java @@ -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; }); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/git/Branch.java b/release-tools/src/main/java/org/springframework/data/release/git/Branch.java index b71988a..ec42c29 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/Branch.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/Branch.java @@ -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() diff --git a/release-tools/src/main/java/org/springframework/data/release/git/Commit.java b/release-tools/src/main/java/org/springframework/data/release/git/Commit.java index d7bfd98..b063881 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/Commit.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/Commit.java @@ -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 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(); } diff --git a/release-tools/src/main/java/org/springframework/data/release/git/GitCommands.java b/release-tools/src/main/java/org/springframework/data/release/git/GitCommands.java index 62f4cfb..bb25caf 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/GitCommands.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/GitCommands.java @@ -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); } diff --git a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java index 9db5f6b..f903188 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java @@ -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 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 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; + } } diff --git a/release-tools/src/main/java/org/springframework/data/release/git/GitProperties.java b/release-tools/src/main/java/org/springframework/data/release/git/GitProperties.java index fa73c59..c35f460 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/GitProperties.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/GitProperties.java @@ -57,4 +57,8 @@ public class GitProperties { public CredentialsProvider getCredentials() { return new UsernamePasswordCredentialsProvider(username, password); } + + public String getAuthenticationHeader() { + return username.concat(":").concat(password); + } } diff --git a/release-tools/src/main/java/org/springframework/data/release/git/VersionTags.java b/release-tools/src/main/java/org/springframework/data/release/git/VersionTags.java index 635e3a0..2b77eb3 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/VersionTags.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/VersionTags.java @@ -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 { +public class VersionTags implements Streamable { private final List tags; diff --git a/release-tools/src/main/java/org/springframework/data/release/gradle/GradleOperations.java b/release-tools/src/main/java/org/springframework/data/release/gradle/GradleOperations.java deleted file mode 100644 index 4198b87..0000000 --- a/release-tools/src/main/java/org/springframework/data/release/gradle/GradleOperations.java +++ /dev/null @@ -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(); - } -} diff --git a/release-tools/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java b/release-tools/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java index 4eed8fa..674c807 100644 --- a/release-tools/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/io/CommonsExecOsCommandOperations.java @@ -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 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); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/io/IoProperties.java b/release-tools/src/main/java/org/springframework/data/release/io/IoProperties.java index aca10cd..fdb5a71 100644 --- a/release-tools/src/main/java/org/springframework/data/release/io/IoProperties.java +++ b/release-tools/src/main/java/org/springframework/data/release/io/IoProperties.java @@ -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"))); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/io/OsCommandOperations.java b/release-tools/src/main/java/org/springframework/data/release/io/OsOperations.java similarity index 95% rename from release-tools/src/main/java/org/springframework/data/release/io/OsCommandOperations.java rename to release-tools/src/main/java/org/springframework/data/release/io/OsOperations.java index 8d18e35..6ef74a2 100644 --- a/release-tools/src/main/java/org/springframework/data/release/io/OsCommandOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/io/OsOperations.java @@ -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 executeWithOutput(String command, Project project) throws IOException; String executeForResult(String command, Project project) throws Exception; + + File getJavaHome(); } diff --git a/release-tools/src/main/java/org/springframework/data/release/io/Workspace.java b/release-tools/src/main/java/org/springframework/data/release/io/Workspace.java index dfdeed3..3f4f292 100644 --- a/release-tools/src/main/java/org/springframework/data/release/io/Workspace.java +++ b/release-tools/src/main/java/org/springframework/data/release/io/Workspace.java @@ -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 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; } diff --git a/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssue.java b/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssue.java index 10578d5..3a8f738 100644 --- a/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssue.java +++ b/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssue.java @@ -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()); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java b/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java index 540b1bb..54bbc04 100644 --- a/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java +++ b/release-tools/src/main/java/org/springframework/data/release/jira/GitHubIssueTracker.java @@ -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 exchange = operations - .exchange(MILESTONE_URI, HttpMethod.GET, null, MILESTONES_TYPE, parameters).getBody(); + List 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) { diff --git a/release-tools/src/main/java/org/springframework/data/release/jira/JiraConfiguration.java b/release-tools/src/main/java/org/springframework/data/release/jira/IssueTrackerConfiguration.java similarity index 73% rename from release-tools/src/main/java/org/springframework/data/release/jira/JiraConfiguration.java rename to release-tools/src/main/java/org/springframework/data/release/jira/IssueTrackerConfiguration.java index 6a87c67..5b92b03 100644 --- a/release-tools/src/main/java/org/springframework/data/release/jira/JiraConfiguration.java +++ b/release-tools/src/main/java/org/springframework/data/release/jira/IssueTrackerConfiguration.java @@ -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 issueTrackers(List 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); + } } diff --git a/release-tools/src/main/java/org/springframework/data/release/jira/Jira.java b/release-tools/src/main/java/org/springframework/data/release/jira/Jira.java index ca50090..10c02d3 100644 --- a/release-tools/src/main/java/org/springframework/data/release/jira/Jira.java +++ b/release-tools/src/main/java/org/springframework/data/release/jira/Jira.java @@ -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"; diff --git a/release-tools/src/main/java/org/springframework/data/release/jira/JiraVersion.java b/release-tools/src/main/java/org/springframework/data/release/jira/JiraVersion.java index 5928d33..3e08b41 100644 --- a/release-tools/src/main/java/org/springframework/data/release/jira/JiraVersion.java +++ b/release-tools/src/main/java/org/springframework/data/release/jira/JiraVersion.java @@ -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(); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/jira/JqlQuery.java b/release-tools/src/main/java/org/springframework/data/release/jira/JqlQuery.java index d54d1c6..b8dd9e2 100644 --- a/release-tools/src/main/java/org/springframework/data/release/jira/JqlQuery.java +++ b/release-tools/src/main/java/org/springframework/data/release/jira/JqlQuery.java @@ -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; diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/MavenOperations.java b/release-tools/src/main/java/org/springframework/data/release/maven/MavenOperations.java deleted file mode 100644 index 7de4df7..0000000 --- a/release-tools/src/main/java/org/springframework/data/release/maven/MavenOperations.java +++ /dev/null @@ -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: - *
    - *
  1. Updates the BOM POM.
  2. - *
  3. Updates the dependency version to Spring Data Commons to the current release version for all projects depending - * on it.
  4. - *
  5. Switches to the Spring release Maven repository.
  6. - *
- * 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() { - - @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() { - - @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() { - - @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 void execute(File file, PomCallback 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 { - public void doWith(T pom); - } -} diff --git a/release-tools/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java b/release-tools/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java index 04a9013..b270720 100644 --- a/release-tools/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/misc/ReleaseOperations.java @@ -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."); } - } + }); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/maven/MavenProject.java b/release-tools/src/main/java/org/springframework/data/release/model/ArtifactCoordinate.java similarity index 52% rename from release-tools/src/main/java/org/springframework/data/release/maven/MavenProject.java rename to release-tools/src/main/java/org/springframework/data/release/model/ArtifactCoordinate.java index c010c31..46f8693 100644 --- a/release-tools/src/main/java/org/springframework/data/release/maven/MavenProject.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/ArtifactCoordinate.java @@ -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]); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/ArtifactCoordinates.java b/release-tools/src/main/java/org/springframework/data/release/model/ArtifactCoordinates.java new file mode 100644 index 0000000..b46f9d8 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/model/ArtifactCoordinates.java @@ -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 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 artifacts = new ArrayList<>(coordinates); + artifacts.add(coordinate); + + return new ArtifactCoordinates(groupId, artifacts); + } + +} diff --git a/release-tools/src/main/java/org/springframework/data/release/model/IterationVersion.java b/release-tools/src/main/java/org/springframework/data/release/model/IterationVersion.java index 09ee3bc..9016003 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/IterationVersion.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/IterationVersion.java @@ -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(); } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Module.java b/release-tools/src/main/java/org/springframework/data/release/model/Module.java index 73bc537..84e9b31 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/Module.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/Module.java @@ -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; diff --git a/release-tools/src/main/java/org/springframework/data/release/model/ModuleIteration.java b/release-tools/src/main/java/org/springframework/data/release/model/ModuleIteration.java index 815be00..eaec3c4 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/ModuleIteration.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/ModuleIteration.java @@ -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()); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Modules.java b/release-tools/src/main/java/org/springframework/data/release/model/Modules.java new file mode 100644 index 0000000..4965380 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/model/Modules.java @@ -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 { + + private final Collection modules; + + public Iterator iterator() { + return modules.iterator(); + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Phase.java b/release-tools/src/main/java/org/springframework/data/release/model/Phase.java index e35b9fa..65d13c0 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/Phase.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/Phase.java @@ -20,5 +20,5 @@ package org.springframework.data.release.model; */ public enum Phase { - PREPARE, CLEANUP; + PREPARE, CLEANUP, MAINTENANCE; } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Project.java b/release-tools/src/main/java/org/springframework/data/release/model/Project.java index 9df3281..a1919b3 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/Project.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/Project.java @@ -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 dependencies; private final Tracker tracker; - private final @Getter List additionalArtifacts; + private final @Getter ArtifactCoordinates additionalArtifacts; Project(String key, String name, List dependencies) { - this(key, name, Tracker.JIRA, dependencies, Collections.emptyList()); + this(key, name, Tracker.JIRA, dependencies, ArtifactCoordinates.NONE); } - Project(String key, String name, List dependencies, List additionalArtifacts) { + Project(String key, String name, List dependencies, ArtifactCoordinates additionalArtifacts) { this(key, name, Tracker.JIRA, dependencies, additionalArtifacts); } - Project(String key, String name, Tracker tracker, List dependencies, List additionalArtifacts) { + Project(String key, String name, Tracker tracker, List 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 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)); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Projects.java b/release-tools/src/main/java/org/springframework/data/release/model/Projects.java index 351cfcd..dc18ae9 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/Projects.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/Projects.java @@ -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 projects = Arrays.asList(BUILD, COMMONS, JPA, MONGO_DB, NEO4J, SOLR, COUCHBASE, CASSANDRA, + ELASTICSEARCH, REDIS, GEMFIRE, REST, KEY_VALUE, ENVERS); + + DefaultDirectedGraph graph = new DefaultDirectedGraph<>(DefaultEdge.class); + + projects.forEach(project -> { + + graph.addVertex(project); + + project.getDependencies().forEach(dependency -> { + graph.addEdge(project, dependency); + }); + }); + + Iterator iterator = new TopologicalOrderIterator<>(graph); + List 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!")); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/ReleaseTrains.java b/release-tools/src/main/java/org/springframework/data/release/model/ReleaseTrains.java index 21489f7..3f560a5 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/ReleaseTrains.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/ReleaseTrains.java @@ -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 diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Train.java b/release-tools/src/main/java/org/springframework/data/release/model/Train.java index 788a724..1cc23b3 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/Train.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/Train.java @@ -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 { +public class Train implements Streamable { private final String name;; - private final Collection 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 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 { 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 { 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 { 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 = getModule(project); - return ArtifactVersion.of(new ModuleIteration(module, iteration, this)); + return ArtifactVersion.of(new ModuleIteration(module, new TrainIteration(this, iteration))); } /* diff --git a/release-tools/src/main/java/org/springframework/data/release/model/TrainIteration.java b/release-tools/src/main/java/org/springframework/data/release/model/TrainIteration.java index 7c5ae52..71b5f61 100644 --- a/release-tools/src/main/java/org/springframework/data/release/model/TrainIteration.java +++ b/release-tools/src/main/java/org/springframework/data/release/model/TrainIteration.java @@ -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 { +public class TrainIteration implements Streamable { private final Train train; private final Iteration iteration; @@ -49,16 +52,29 @@ public class TrainIteration implements Iterable { return train.getModuleIteration(iteration, project.getName()); } - public Iterable getModulesExcept(Project... exclusions) { + public List 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 { public String toString() { return String.format("%s %s", train.getName(), iteration.getName()); } + } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/UpdateInformation.java b/release-tools/src/main/java/org/springframework/data/release/model/UpdateInformation.java new file mode 100644 index 0000000..0e40d90 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/model/UpdateInformation.java @@ -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!"); + } +} diff --git a/release-tools/src/main/java/org/springframework/data/release/model/VersionAware.java b/release-tools/src/main/java/org/springframework/data/release/model/VersionAware.java new file mode 100644 index 0000000..17a4c88 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/model/VersionAware.java @@ -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(); +} diff --git a/release-tools/src/main/java/org/springframework/data/release/utils/ExecutionUtils.java b/release-tools/src/main/java/org/springframework/data/release/utils/ExecutionUtils.java index 840e182..cc05c98 100644 --- a/release-tools/src/main/java/org/springframework/data/release/utils/ExecutionUtils.java +++ b/release-tools/src/main/java/org/springframework/data/release/utils/ExecutionUtils.java @@ -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 void run(Iterable iterable, ConsumerWithException consumer) { + public static void run(Streamable streamable, ConsumerWithException 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 Collection runAndReturn(Iterable iterable, Function function) { + public static Collection runAndReturn(Streamable streamable, Function 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().// diff --git a/release-tools/src/main/java/org/springframework/data/release/utils/Logger.java b/release-tools/src/main/java/org/springframework/data/release/utils/Logger.java index cd357b3..fdbd384 100644 --- a/release-tools/src/main/java/org/springframework/data/release/utils/Logger.java +++ b/release-tools/src/main/java/org/springframework/data/release/utils/Logger.java @@ -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))); } } diff --git a/release-tools/src/main/java/org/springframework/data/release/utils/StreamUtils.java b/release-tools/src/main/java/org/springframework/data/release/utils/StreamUtils.java new file mode 100644 index 0000000..33b3fc6 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/utils/StreamUtils.java @@ -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 Function wrap(FunctionWithException function) { + return it -> { + try { + return function.apply(it); + } catch (Exception o_O) { + throw new RuntimeException(o_O); + } + }; + } + + public interface FunctionWithException { + S apply(T source) throws Exception; + } +} diff --git a/release-tools/src/main/resources/application.properties b/release-tools/src/main/resources/application.properties index f759759..40e639e 100644 --- a/release-tools/src/main/resources/application.properties +++ b/release-tools/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/release-tools/src/test/java/org/springframework/data/release/AbstractIntegrationTests.java b/release-tools/src/test/java/org/springframework/data/release/AbstractIntegrationTests.java index 9faa6c4..ccb79c1 100644 --- a/release-tools/src/test/java/org/springframework/data/release/AbstractIntegrationTests.java +++ b/release-tools/src/test/java/org/springframework/data/release/AbstractIntegrationTests.java @@ -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 {} diff --git a/release-tools/src/test/java/org/springframework/data/release/ApplicationTests.java b/release-tools/src/test/java/org/springframework/data/release/ApplicationTests.java new file mode 100644 index 0000000..a269533 --- /dev/null +++ b/release-tools/src/test/java/org/springframework/data/release/ApplicationTests.java @@ -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() {} +} diff --git a/release-tools/src/test/java/org/springframework/data/release/ArtifactUnitTests.java b/release-tools/src/test/java/org/springframework/data/release/ArtifactUnitTests.java index 13dda41..260dcb5 100644 --- a/release-tools/src/test/java/org/springframework/data/release/ArtifactUnitTests.java +++ b/release-tools/src/test/java/org/springframework/data/release/ArtifactUnitTests.java @@ -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"))); diff --git a/release-tools/src/test/java/org/springframework/data/release/maven/MavenIntegrationTests.java b/release-tools/src/test/java/org/springframework/data/release/build/MavenIntegrationTests.java similarity index 64% rename from release-tools/src/test/java/org/springframework/data/release/maven/MavenIntegrationTests.java rename to release-tools/src/test/java/org/springframework/data/release/build/MavenIntegrationTests.java index d85930d..aaa8d8e 100644 --- a/release-tools/src/test/java/org/springframework/data/release/maven/MavenIntegrationTests.java +++ b/release-tools/src/test/java/org/springframework/data/release/build/MavenIntegrationTests.java @@ -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); + } } diff --git a/release-tools/src/test/java/org/springframework/data/release/deployment/DeploymentInformationIntegrationTests.java b/release-tools/src/test/java/org/springframework/data/release/deployment/DeploymentInformationIntegrationTests.java new file mode 100644 index 0000000..18e3d3a --- /dev/null +++ b/release-tools/src/test/java/org/springframework/data/release/deployment/DeploymentInformationIntegrationTests.java @@ -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")); + } +} diff --git a/release-tools/src/test/java/org/springframework/data/release/deployment/DeploymentOperationsIntegrationTests.java b/release-tools/src/test/java/org/springframework/data/release/deployment/DeploymentOperationsIntegrationTests.java new file mode 100644 index 0000000..4f747a9 --- /dev/null +++ b/release-tools/src/test/java/org/springframework/data/release/deployment/DeploymentOperationsIntegrationTests.java @@ -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); + } + } +} diff --git a/release-tools/src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.java b/release-tools/src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.java index a6f1b7b..986f2a7 100644 --- a/release-tools/src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.java +++ b/release-tools/src/test/java/org/springframework/data/release/model/ModuleIterationUnitTests.java @@ -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)")); } } diff --git a/release-tools/src/test/java/org/springframework/data/release/model/ProjectUnitTests.java b/release-tools/src/test/java/org/springframework/data/release/model/ProjectUnitTests.java new file mode 100644 index 0000000..2e53c91 --- /dev/null +++ b/release-tools/src/test/java/org/springframework/data/release/model/ProjectUnitTests.java @@ -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 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)); + } +} diff --git a/release-tools/src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java b/release-tools/src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java index 203c090..ad6292b 100644 --- a/release-tools/src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java +++ b/release-tools/src/test/java/org/springframework/data/release/model/SimpleIterationVersion.java @@ -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(); + } } diff --git a/release-tools/src/test/java/org/springframework/data/release/model/TrainsUnitTest.java b/release-tools/src/test/java/org/springframework/data/release/model/TrainsUnitTest.java index f52650e..6ae3e7a 100644 --- a/release-tools/src/test/java/org/springframework/data/release/model/TrainsUnitTest.java +++ b/release-tools/src/test/java/org/springframework/data/release/model/TrainsUnitTest.java @@ -39,4 +39,18 @@ public class TrainsUnitTest { public void addsNewlyAddedModule() { assertThat(ReleaseTrains.HOPPER.getModule(Projects.ENVERS), is(notNullValue())); } + + @Test + public void testname() { + + Iterable iterations = ReleaseTrains.HOPPER.getModuleIterations(Iteration.M1); + + for (ModuleIteration iteration : iterations) { + System.out.println(iteration); + } + + System.out.println(); + + iterations.forEach(System.out::println); + } } diff --git a/release-tools/src/test/java/org/springframework/data/release/model/UpdateInformationUnitTests.java b/release-tools/src/test/java/org/springframework/data/release/model/UpdateInformationUnitTests.java new file mode 100644 index 0000000..5284f7d --- /dev/null +++ b/release-tools/src/test/java/org/springframework/data/release/model/UpdateInformationUnitTests.java @@ -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")); + } +} diff --git a/release-tools/src/test/resources/application-test.properties b/release-tools/src/test/resources/application-test.properties index 4982dd4..6dd9cd1 100644 --- a/release-tools/src/test/resources/application-test.properties +++ b/release-tools/src/test/resources/application-test.properties @@ -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-