@@ -1,149 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<context version="7.2.2.230">
|
||||
<scope type="Project" name="spring-data-release-cli">
|
||||
<workspace>
|
||||
<element type="JavaRootDirectory" name="src/main/java">
|
||||
<reference name="Project|spring-data-release-cli::BuildUnit|spring-data-release-cli"/>
|
||||
</element>
|
||||
<element type="JavaRootDirectory" name="target/classes">
|
||||
<reference name="Project|spring-data-release-cli::BuildUnit|spring-data-release-cli"/>
|
||||
</element>
|
||||
</workspace>
|
||||
<physical>
|
||||
<element type="BuildUnit" name="spring-data-release-cli"/>
|
||||
</physical>
|
||||
</scope>
|
||||
<scope type="External" name="External">
|
||||
<element type="TypeFilter" name="Filter">
|
||||
<element type="IncludeTypePattern" name="**"/>
|
||||
<element type="ExcludeTypePattern" name="java.io.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.lang.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.lang.annotation.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.lang.ref.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.math.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.nio.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.nio.channels.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.nio.channels.spi.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.nio.charset.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.nio.charset.spi.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.text.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.util.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.util.jar.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.util.prefs.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.util.regex.*"/>
|
||||
<element type="ExcludeTypePattern" name="java.util.zip.*"/>
|
||||
</element>
|
||||
</scope>
|
||||
<scope type="Global" name="Global">
|
||||
<element type="Configuration" name="Configuration"/>
|
||||
<element type="TypeFilter" name="Filter">
|
||||
<element type="IncludeTypePattern" name="**"/>
|
||||
</element>
|
||||
<itemDefinition>
|
||||
<item type="ThresholdItem">
|
||||
<property value="NumberOfNotAssignedTypes" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="Project" name="NamedElementGroup"/>
|
||||
<property value="0" name="UpperValue"/>
|
||||
</item>
|
||||
<item type="ThresholdItem">
|
||||
<property value="NumberOfParameters" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="Method" name="NamedElementGroup"/>
|
||||
<property value="7" name="UpperValue"/>
|
||||
</item>
|
||||
<item type="ThresholdItem">
|
||||
<property value="NumberOfAccessibleTypes" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="Namespace" name="NamedElementGroup"/>
|
||||
<property value="30" name="UpperValue"/>
|
||||
</item>
|
||||
<item type="ThresholdItem">
|
||||
<property value="NormalizedCumulativeComponentDependency" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="Project" name="NamedElementGroup"/>
|
||||
<property value="7" name="UpperValue"/>
|
||||
</item>
|
||||
<item type="ThresholdItem">
|
||||
<property value="LinesOfCode" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="SourceFile" name="NamedElementGroup"/>
|
||||
<property value="700" name="UpperValue"/>
|
||||
</item>
|
||||
<item type="ThresholdItem">
|
||||
<property value="NumberOfInternalTypes" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="Namespace" name="NamedElementGroup"/>
|
||||
<property value="50" name="UpperValue"/>
|
||||
</item>
|
||||
<item type="ThresholdItem">
|
||||
<property value="NumberOfMethods" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="Type" name="NamedElementGroup"/>
|
||||
<property value="50" name="UpperValue"/>
|
||||
</item>
|
||||
<item type="ThresholdItem">
|
||||
<property value="ModifiedCyclomaticComplexity" name="AttributeId"/>
|
||||
<property value="" name="LowerValue"/>
|
||||
<property value="Method" name="NamedElementGroup"/>
|
||||
<property value="20" name="UpperValue"/>
|
||||
</item>
|
||||
</itemDefinition>
|
||||
</scope>
|
||||
<attributeConfiguration type="AttributeConfiguration">
|
||||
<property value="disabled" name="NumberOfWorkspaceWarnings"/>
|
||||
<property value="disabled" name="DistributedOverPathContainers"/>
|
||||
<property value="disabled" name="MaxAccumulatedExtendedCyclomaticComplexityPerType"/>
|
||||
<property value="disabled" name="CyclomaticComplexity"/>
|
||||
<property value="disabled" name="NumberOfCyclicProjects"/>
|
||||
<property value="disabled" name="NumberOfTypesWithImplementation"/>
|
||||
<property value="disabled" name="NumberOfNamedInterfaces"/>
|
||||
<property value="disabled" name="NumberOfCyclicSubsystems"/>
|
||||
<property value="disabled" name="AverageAccumulatedCyclomaticComplexityPerType"/>
|
||||
<property value="disabled" name="NumberOfAbstractTypes"/>
|
||||
<property value="disabled" name="NormalizedCumulativeTypeDependency"/>
|
||||
<property value="disabled" name="NumberOfVerticalSlices"/>
|
||||
<property value="disabled" name="NumberOfTasks"/>
|
||||
<property value="disabled" name="NumberOfWarnings"/>
|
||||
<property value="disabled" name="NumberOfRefactorings"/>
|
||||
<property value="disabled" name="AverageExtendedCyclomaticComplexityPerMethod"/>
|
||||
<property value="disabled" name="NumberOfCyclicNamespaces"/>
|
||||
<property value="disabled" name="MaxExtendedCyclomaticComplexityPerMethod"/>
|
||||
<property value="disabled" name="Level"/>
|
||||
<property value="disabled" name="NumberOfVerticalSliceGroups"/>
|
||||
<property value="disabled" name="NumberOfNameParts"/>
|
||||
<property value="disabled" name="NumberOfCyclicVerticalSliceGroups"/>
|
||||
<property value="disabled" name="AccumulatedExtendedCyclomaticComplexity"/>
|
||||
<property value="disabled" name="NumberOfCyclicLayerGroups"/>
|
||||
<property value="disabled" name="NumberOfLayerGroups"/>
|
||||
<property value="disabled" name="NumberOfAssertions"/>
|
||||
<property value="disabled" name="NumberOfDuplicateCodeBlocksWarnings"/>
|
||||
<property value="disabled" name="AverageAccumulatedExtendedCyclomaticComplexityPerType"/>
|
||||
<property value="disabled" name="ExtendedCyclomaticComplexity"/>
|
||||
<property value="disabled" name="NumberOfLayers"/>
|
||||
<property value="disabled" name="DependsUpon"/>
|
||||
<property value="disabled" name="NumberOfMethodsWithImplementation"/>
|
||||
<property value="disabled" name="NumberOfCyclicVerticalSlices"/>
|
||||
<property value="disabled" name="MaxAccumulatedCyclomaticComplexityPerType"/>
|
||||
<property value="disabled" name="AccumulatedCyclomaticComplexity"/>
|
||||
<property value="disabled" name="MaxCyclomaticComplexityPerMethod"/>
|
||||
<property value="disabled" name="JdkVersion"/>
|
||||
<property value="disabled" name="NumberOfSubsystems"/>
|
||||
<property value="disabled" name="NumberOfCyclicDirectories"/>
|
||||
<property value="disabled" name="NumberOfIgnoredWarnings"/>
|
||||
<property value="disabled" name="AverageAssertionUsage"/>
|
||||
<property value="disabled" name="AverageTypeDependency"/>
|
||||
<property value="disabled" name="RelativeAverageTypeDependency"/>
|
||||
<property value="disabled" name="AverageCyclomaticComplexityPerMethod"/>
|
||||
<property value="disabled" name="NumberOfExcludedSourceFiles"/>
|
||||
<property value="disabled" name="NumberOfCyclicWarnings"/>
|
||||
<property value="disabled" name="Timestamp"/>
|
||||
<property value="disabled" name="NumberOfExcludedTargetFiles"/>
|
||||
<property value="disabled" name="NumberOfIgnoredViolations"/>
|
||||
<property value="disabled" name="NumberOfCyclicLayers"/>
|
||||
<property value="disabled" name="NumberOfCyclicSourceFiles"/>
|
||||
<property value="disabled" name="NumberOfMetricWarnings"/>
|
||||
<property value="disabled" name="NumberOfAbstractMethods"/>
|
||||
<property value="disabled" name="NumberOfFixWarnings"/>
|
||||
</attributeConfiguration>
|
||||
</context>
|
||||
@@ -1,32 +0,0 @@
|
||||
# Git
|
||||
git.username=
|
||||
git.author=
|
||||
git.email=
|
||||
git.password=
|
||||
github.api.url=https://api.github.com
|
||||
|
||||
# IO
|
||||
|
||||
# Optionally set the JavaHome path used for exeuting maven goals (eg. /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/)
|
||||
# If the property does not exist, is empty or points to an invalid directory the OS default JavaHome will be used.
|
||||
#io.javaHome=
|
||||
|
||||
# Maven
|
||||
maven.mavenHome=
|
||||
maven.console-logger=false
|
||||
maven.parallelize=true
|
||||
|
||||
# Deployment
|
||||
# Must be always the encrypted password taken from the Artifactory GUI/Profile view
|
||||
deployment.username=
|
||||
deployment.password=
|
||||
deployment.api-key=
|
||||
|
||||
# GPG
|
||||
gpg.keyname=
|
||||
gpg.password=
|
||||
gpg.executable=/usr/local/bin/gpg2
|
||||
|
||||
# A GitHub token with user:email, read:user and read:org scopes.
|
||||
# User needs to be part of the Spring team on GitHub as well.
|
||||
sagan.key=
|
||||
@@ -1,2 +0,0 @@
|
||||
lombok.nonNull.exceptionType = IllegalArgumentException
|
||||
lombok.anyConstructor.addConstructorProperties = true
|
||||
@@ -1,163 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-release-cli</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.4.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<spring-plugin.version>2.0.0.RELEASE</spring-plugin.version>
|
||||
<lombok.version>1.18.18</lombok.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-parameter-names</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.10.8</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell</artifactId>
|
||||
<version>1.2.0.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xmlbeam</groupId>
|
||||
<artifactId>xmlprojector</artifactId>
|
||||
<version>1.4.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.plist</groupId>
|
||||
<artifactId>dd-plist</artifactId>
|
||||
<version>1.23</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.plugin</groupId>
|
||||
<artifactId>spring-plugin-core</artifactId>
|
||||
<version>${spring-plugin.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
<version>5.6.0.201912101111-r</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
<artifactId>maven-invoker</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jgrapht</groupId>
|
||||
<artifactId>jgrapht-core</artifactId>
|
||||
<version>0.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock</artifactId>
|
||||
<version>2.26.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>29.0-jre</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.name}</finalName>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -1,99 +0,0 @@
|
||||
## General Notes
|
||||
|
||||
* Use the command `help` to get a list of all commands in the release tools.
|
||||
* After fixing a problem use `workspace cleanup` to cleanup any mess left behind by the previous step.
|
||||
|
||||
## One Time Setup
|
||||
|
||||
### Infrastructure requirements
|
||||
|
||||
- Ensure you have the credentials for `buildmaster` accounts on https://repo.spring.io.
|
||||
- Ensure yoiu have the credentials for https://oss.sonatype.org (to deploy and promote GA and service releases, need deployment permissions for `org.springframework.data`) in `settings.xml` for server with id `sonatype`.
|
||||
|
||||
Both are available in the Spring/Pivotal Last Pass repository.
|
||||
|
||||
### Prepare local configuration and credentials
|
||||
|
||||
Add an `application-local.properties` to the project root and add the following properties:
|
||||
|
||||
- `git.username` - Your GitHub username.
|
||||
- `git.password` - Your GitHub Password (or API key with scopes: `public_repo, read:org, repo:status, repo_deployment, user` when using 2FA).
|
||||
- `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.username` - Your Artifactory user.
|
||||
- `deployment.api-key` - The Artifactory API key to use for artifact promotion.
|
||||
- `deployment.password` - The encrypted Artifactory password..
|
||||
- `gpg.keyname` - The GPG key name.
|
||||
- `gpg.password` - The password of your GPG key.
|
||||
- `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2`
|
||||
or `/usr/local/bin/gpg`.
|
||||
- `sagan.key` - Sagan authentication token. Must be a valid GitHub token. Can be the same
|
||||
as `git.password` when using a GitHub token as password.
|
||||
|
||||
After that, run the `verify` command (`$ verify`) to verify your settings (authentication,
|
||||
correct Maven, Java, and GPG setup).
|
||||
|
||||
See `application-local.template` for details.
|
||||
|
||||
## The release process
|
||||
|
||||
|
||||
| Action | Command |
|
||||
|--------|---------|
|
||||
| Build and execute the release shell | `mvn package && java -jar target/spring-data-release-cli.jar` |
|
||||
| | *All following commands are run in the release shell* |
|
||||
| **Pre-release checks** | |
|
||||
| Ensure all work on CVEs potentially contained in the release is done (incl. backports etc.) | N.A. |
|
||||
| Upgrade dependencies in Spring Data Build parent pom (mind minor/major version rules) | N.A. |
|
||||
| All release tickets are present | `$ tracker releasetickets $trainIteration` |
|
||||
| Review open tickets for release | N.A. |
|
||||
| Self-assign release tickets | `$ tracker prepare $trainIteration` |
|
||||
| Announce release preparations to mailing list (https://groups.google.com/forum/#!forum/spring-data-dev) | N.A. |
|
||||
| **Release the binaries** ||
|
||||
| | `$ release prepare $trainIteration` |
|
||||
| Build the artefacts and push them to the apropriate maven repository | `$ release build $trainIteration` |
|
||||
| |`$ release conclude $trainIteration` |
|
||||
| Push the created commits to GitHub |`$ github push $trainIteration` |
|
||||
| Push new maintanance branches if the release version was a GA release (`X.Y.0` version)|`$ git push $trainIteration.next`|
|
||||
| Distribute documentation and static resources from tag |`$ release distribute $trainIteration`|
|
||||
| **Post-release tasks** ||
|
||||
|Close JIRA tickets and GitHub release tickets.|`$ tracker close $trainIteration`|
|
||||
|Create new release versions and tickets for upcoming version|`$ tracker setup-next $trainIteration.next`|
|
||||
| Update versions in Sagan. `$targets` is given as comma separated lists of code names, without spaces. E.g. `Moore,Neumann` | `$ sagan update $releasetrains`|
|
||||
| Create list of docs for release announcements | `$ announcement $trainIteration`|
|
||||
| Announce release (Blog, Twitter) and notify downstream dependency projects as needed. | N.A. |
|
||||
|
||||
### Utilities
|
||||
|
||||
#### GitHub Labels
|
||||
|
||||
`ProjectLabelConfiguration` contains a per-project configuration which labels should be present in a project. To apply that configuration (create or update), use:
|
||||
|
||||
```
|
||||
$ github update labels $project
|
||||
```
|
||||
|
||||
#### Dependency Upgrade
|
||||
|
||||
`ProjectDependencies` contains a per-project configuration of dependencies.
|
||||
|
||||
Workflow:
|
||||
|
||||
* Check for dependency upgrades `$ dependency check $trainIteration`
|
||||
|
||||
Reports upgradable dependencies for Build and Modules and
|
||||
creates `dependency-upgrade-build.properties` file.
|
||||
Edit `dependency-upgrade-build.properties` to specify the dependency version to upgrade.
|
||||
Removing a line will omit that dependency upgrade.
|
||||
|
||||
* Apply dependency upgrade with `$ dependency upgrade $trainIteration`. Applies dependency
|
||||
upgrades currently only to Spring Data Build.
|
||||
* Report store-specific dependencies to Spring Boot's current upgrade
|
||||
ticket ([sample](https://github.com/spring-projects/spring-boot/issues/24036)) `$ dependency report $trainIteration`
|
||||
|
||||
#### CI Properties Distribution
|
||||
|
||||
To distribute `ci/pipeline.properties` across all modules use:
|
||||
|
||||
`$ infra distribute ci-properties $trainIteration`
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.shell.support.logging.HandlerUtils;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
SpringApplication application = new SpringApplication(Application.class);
|
||||
application.setAdditionalProfiles("local");
|
||||
|
||||
try {
|
||||
BootShim bs = new BootShim(args, application.run(args));
|
||||
bs.run();
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} finally {
|
||||
HandlerUtils.flushAllHandlers(Logger.getLogger(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package org.springframework.data.release;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.shell.CommandLine;
|
||||
import org.springframework.shell.ShellException;
|
||||
import org.springframework.shell.SimpleShellCommandLineOptions;
|
||||
import org.springframework.shell.core.ExitShellRequest;
|
||||
import org.springframework.shell.core.JLineShellComponent;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
public class BootShim {
|
||||
|
||||
private static StopWatch sw = new StopWatch("Spring Shell");
|
||||
private static CommandLine commandLine;
|
||||
private ConfigurableApplicationContext ctx;
|
||||
|
||||
public BootShim(String[] args, ConfigurableApplicationContext context) {
|
||||
this.ctx = context;
|
||||
|
||||
try {
|
||||
commandLine = SimpleShellCommandLineOptions.parseCommandLine(args);
|
||||
} catch (IOException var5) {
|
||||
throw new ShellException(var5.getMessage(), var5);
|
||||
}
|
||||
|
||||
this.configureApplicationContext(this.ctx);
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner((BeanDefinitionRegistry) this.ctx);
|
||||
if (commandLine.getDisableInternalCommands()) {
|
||||
scanner.scan(new String[] { "org.springframework.shell.converters", "org.springframework.shell.plugin.support" });
|
||||
} else {
|
||||
scanner.scan(new String[] { "org.springframework.shell.commands", "org.springframework.shell.converters",
|
||||
"org.springframework.shell.plugin.support" });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return this.ctx;
|
||||
}
|
||||
|
||||
private void configureApplicationContext(ConfigurableApplicationContext annctx) {
|
||||
this.createAndRegisterBeanDefinition(annctx, CustomShellComponent.class, "shell");
|
||||
annctx.getBeanFactory().registerSingleton("commandLine", commandLine);
|
||||
}
|
||||
|
||||
protected void createAndRegisterBeanDefinition(GenericApplicationContext annctx, Class<?> clazz) {
|
||||
this.createAndRegisterBeanDefinition(annctx, clazz, (String) null);
|
||||
}
|
||||
|
||||
protected void createAndRegisterBeanDefinition(ConfigurableApplicationContext annctx, Class<?> clazz, String name) {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition();
|
||||
rbd.setBeanClass(clazz);
|
||||
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) annctx.getBeanFactory();
|
||||
if (name != null) {
|
||||
bf.registerBeanDefinition(name, rbd);
|
||||
} else {
|
||||
bf.registerBeanDefinition(clazz.getSimpleName(), rbd);
|
||||
}
|
||||
}
|
||||
|
||||
public ExitShellRequest run() {
|
||||
sw.start();
|
||||
String[] commandsToExecuteAndThenQuit = commandLine.getShellCommandsToExecute();
|
||||
JLineShellComponent shell = (JLineShellComponent) this.ctx.getBean("shell", JLineShellComponent.class);
|
||||
ExitShellRequest exitShellRequest;
|
||||
if (null != commandsToExecuteAndThenQuit) {
|
||||
boolean successful = false;
|
||||
exitShellRequest = ExitShellRequest.FATAL_EXIT;
|
||||
String[] arr$ = commandsToExecuteAndThenQuit;
|
||||
int len$ = commandsToExecuteAndThenQuit.length;
|
||||
|
||||
for (int i$ = 0; i$ < len$; ++i$) {
|
||||
String cmd = arr$[i$];
|
||||
successful = shell.executeCommand(cmd).isSuccess();
|
||||
if (!successful) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (successful) {
|
||||
exitShellRequest = ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
} else {
|
||||
shell.start();
|
||||
shell.promptLoop();
|
||||
exitShellRequest = shell.getExitShellRequest();
|
||||
if (exitShellRequest == null) {
|
||||
exitShellRequest = ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
|
||||
shell.waitForComplete();
|
||||
}
|
||||
|
||||
sw.stop();
|
||||
if (shell.isDevelopmentMode()) {
|
||||
System.out.println("Total execution time: " + sw.getLastTaskTimeMillis() + " ms");
|
||||
}
|
||||
|
||||
return exitShellRequest;
|
||||
}
|
||||
|
||||
public JLineShellComponent getJLineShellComponent() {
|
||||
return (JLineShellComponent) this.ctx.getBean("shell", JLineShellComponent.class);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Component
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CliComponent {
|
||||
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.shell.core.ExecutionProcessor;
|
||||
import org.springframework.shell.core.ExecutionStrategy;
|
||||
import org.springframework.shell.core.JLineShellComponent;
|
||||
import org.springframework.shell.core.SimpleExecutionStrategy;
|
||||
import org.springframework.shell.event.ParseResult;
|
||||
import org.springframework.shell.support.logging.HandlerUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Extension of {@link JLineShellComponent} to customize the {@link ExecutionStrategy} to one that can deal with package
|
||||
* protected command classes.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @see https://github.com/spring-projects/spring-shell/pull/93
|
||||
*/
|
||||
class CustomShellComponent extends JLineShellComponent {
|
||||
|
||||
private final ExecutionStrategy executionStrategy = new CustomExecutionStrategy();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.JLineShellComponent#getExecutionStrategy()
|
||||
*/
|
||||
@Override
|
||||
protected ExecutionStrategy getExecutionStrategy() {
|
||||
return executionStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectively a copy of {@link SimpleExecutionStrategy} but with the tweaks provided in PR 93 for Spring shell to
|
||||
* enable execution of package protected command classes.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @see https://github.com/spring-projects/spring-shell/pull/93
|
||||
*/
|
||||
static class CustomExecutionStrategy implements ExecutionStrategy {
|
||||
|
||||
private static final Logger logger = HandlerUtils.getLogger(SimpleExecutionStrategy.class);
|
||||
|
||||
private final Class<?> mutex = SimpleExecutionStrategy.class;
|
||||
|
||||
public Object execute(ParseResult parseResult) throws RuntimeException {
|
||||
Assert.notNull(parseResult, "Parse result required");
|
||||
synchronized (mutex) {
|
||||
Assert.isTrue(isReadyForCommands(), "SimpleExecutionStrategy not yet ready for commands");
|
||||
Object target = parseResult.getInstance();
|
||||
if (target instanceof ExecutionProcessor) {
|
||||
ExecutionProcessor processor = ((ExecutionProcessor) target);
|
||||
parseResult = processor.beforeInvocation(parseResult);
|
||||
try {
|
||||
Object result = invoke(parseResult);
|
||||
processor.afterReturningInvocation(parseResult, result);
|
||||
return result;
|
||||
} catch (Throwable th) {
|
||||
processor.afterThrowingInvocation(parseResult, th);
|
||||
return handleThrowable(th);
|
||||
}
|
||||
} else {
|
||||
return invoke(parseResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object invoke(ParseResult parseResult) {
|
||||
try {
|
||||
Method method = parseResult.getMethod();
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return ReflectionUtils.invokeMethod(method, parseResult.getInstance(), parseResult.getArguments());
|
||||
} catch (Throwable th) {
|
||||
logger.severe("Command failed");
|
||||
return handleThrowable(th);
|
||||
}
|
||||
}
|
||||
|
||||
private Object handleThrowable(Throwable th) {
|
||||
if (th instanceof Error) {
|
||||
throw ((Error) th);
|
||||
}
|
||||
if (th instanceof RuntimeException) {
|
||||
throw ((RuntimeException) th);
|
||||
}
|
||||
throw new RuntimeException(th);
|
||||
}
|
||||
|
||||
public boolean isReadyForCommands() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void terminate() {}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release;
|
||||
|
||||
import org.springframework.shell.core.CommandMarker;
|
||||
import org.springframework.shell.core.ExecutionProcessor;
|
||||
import org.springframework.shell.event.ParseResult;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
/**
|
||||
* Base class for command implementations who want to get their execution time logged.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public abstract class TimedCommand implements ExecutionProcessor, CommandMarker {
|
||||
|
||||
private StopWatch watch;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.ExecutionProcessor#beforeInvocation(org.springframework.shell.event.ParseResult)
|
||||
*/
|
||||
@Override
|
||||
public ParseResult beforeInvocation(ParseResult invocationContext) {
|
||||
|
||||
watch = new StopWatch();
|
||||
watch.start();
|
||||
|
||||
return invocationContext;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.ExecutionProcessor#afterReturningInvocation(org.springframework.shell.event.ParseResult, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void afterReturningInvocation(ParseResult invocationContext, Object result) {
|
||||
stopAndLog();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.ExecutionProcessor#afterThrowingInvocation(org.springframework.shell.event.ParseResult, java.lang.Throwable)
|
||||
*/
|
||||
@Override
|
||||
public void afterThrowingInvocation(ParseResult invocationContext, Throwable thrown) {
|
||||
stopAndLog();
|
||||
}
|
||||
|
||||
private void stopAndLog() {
|
||||
|
||||
watch.stop();
|
||||
System.out.println(String.format("Took: %.2f sec.", watch.getTotalTimeSeconds()));
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.announcement;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
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
|
||||
class AnnouncementCommands extends TimedCommand {
|
||||
|
||||
private final @NonNull AnnouncementOperations operations;
|
||||
|
||||
@CliCommand("announcement")
|
||||
public void announce(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
|
||||
System.out.println(operations.getProjectBulletpoints(iteration));
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.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.model.Project;
|
||||
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 TrainIteration}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getProjectBulletpoints(TrainIteration iteration) {
|
||||
|
||||
Assert.notNull(iteration, "Iteration must not be null!");
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
iteration.getModulesExcept(BUILD, BOM).forEach(module -> {
|
||||
|
||||
Project project = module.getProject();
|
||||
|
||||
builder.append("* ");
|
||||
builder.append(project.getFullName()).append(" ");
|
||||
builder.append("`");
|
||||
builder.append(module.getShortVersionString());
|
||||
builder.append("`");
|
||||
builder.append(" - ");
|
||||
|
||||
MavenArtifact artifact = new MavenArtifact(module);
|
||||
|
||||
builder.append(getMarkDownLink("Artifacts", artifact.getRootUrl()));
|
||||
builder.append(" - ");
|
||||
|
||||
StaticResources resources = new StaticResources(module);
|
||||
|
||||
builder.append(getMarkDownLink("Javadoc", resources.getJavaDocUrl())).append(" - ");
|
||||
builder.append(getMarkDownLink("Documentation", resources.getDocumentationUrl())).append(" - ");
|
||||
builder.append(getMarkDownLink("Changelog", resources.getChangelogUrl()));
|
||||
|
||||
builder.append("\n");
|
||||
});
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String getMarkDownLink(String name, String url) {
|
||||
return String.format("[%s](%s)", name, url);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.cli.TrainIterationConverter;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.io.Workspace;
|
||||
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.data.release.utils.Logger;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class BuildCommands extends TimedCommand {
|
||||
|
||||
@NonNull BuildOperations build;
|
||||
@NonNull Workspace workspace;
|
||||
@NonNull GitOperations git;
|
||||
@NonNull Logger logger;
|
||||
|
||||
/**
|
||||
* Removes all Spring Data artifacts from the local repository.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@CliCommand("build purge artifacts")
|
||||
public void purge() throws IOException {
|
||||
|
||||
logger.log("Workspace", "Cleaning up workspace directory at %s.",
|
||||
workspace.getWorkingDirectory().getAbsolutePath());
|
||||
|
||||
workspace.purge(build.getLocalRepository(),
|
||||
path -> build.getLocalRepository().relativize(path).startsWith("org/springframework/data"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a build for all modules of the given {@link TrainIteration}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @param projectKey can be {@literal null} or empty.
|
||||
*/
|
||||
@CliCommand("build")
|
||||
public void build(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
|
||||
@CliOption(key = "module") String projectKey) {
|
||||
|
||||
Assert.notNull(iteration, "Train iteration must not be null!");
|
||||
Optional<Project> project = Projects.byName(projectKey);
|
||||
|
||||
project.ifPresent(it -> build.triggerBuild(iteration.getModule(it)));
|
||||
|
||||
if (!project.isPresent()) {
|
||||
git.prepare(iteration);
|
||||
iteration.forEach(build::triggerBuild);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trainOrIteration must not be {@literal null}. Accepts release train names and train iterations.
|
||||
*/
|
||||
@CliCommand("build-distribute")
|
||||
public void buildDistribute(@CliOption(key = "", mandatory = true) String trainOrIteration) {
|
||||
|
||||
Assert.hasText(trainOrIteration, "Train or iteration must not be null or empty!");
|
||||
|
||||
if (trainOrIteration.contains(" ")) {
|
||||
|
||||
TrainIteration trainIteration = new TrainIterationConverter().convertFromText(trainOrIteration,
|
||||
TrainIteration.class, null);
|
||||
|
||||
Assert.notNull(trainIteration, "TrainIteration must not be null!");
|
||||
git.prepare(trainIteration);
|
||||
build.distributeResources(trainIteration);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Train train = ReleaseTrains.getTrainByName(trainOrIteration);
|
||||
|
||||
Assert.notNull(train, "Train must not be null!");
|
||||
|
||||
git.checkout(train);
|
||||
build.distributeResources(train);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.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.XBProjector;
|
||||
import org.xmlbeam.XBProjector.Flags;
|
||||
import org.xmlbeam.config.DefaultXMLFactoriesConfig;
|
||||
import org.xmlbeam.config.DefaultXMLFactoriesConfig.NamespacePhilosophy;
|
||||
|
||||
/**
|
||||
* Spring configuration for build related components.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class BuildConfiguration {
|
||||
|
||||
@Bean
|
||||
public PluginRegistry<BuildSystem, Project> buildSystems(List<? extends BuildSystem> buildSystems) {
|
||||
return OrderAwarePluginRegistry.create(buildSystems);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public XBProjector projectionFactory() {
|
||||
|
||||
DefaultXMLFactoriesConfig config = new DefaultXMLFactoriesConfig();
|
||||
config.setNamespacePhilosophy(NamespacePhilosophy.AGNOSTIC);
|
||||
config.setOmitXMLDeclaration(false);
|
||||
config.setPrettyPrinting(false);
|
||||
|
||||
return new XBProjector(config, Flags.TO_STRING_RENDERS_XML);
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import org.springframework.data.release.infra.InfrastructureOperations;
|
||||
import org.springframework.data.release.io.Workspace;
|
||||
import org.springframework.data.release.model.JavaVersion;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.ProjectAware;
|
||||
import org.springframework.data.release.utils.ListWrapperCollector;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Build executor service.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
class BuildExecutor {
|
||||
|
||||
private final @NonNull PluginRegistry<BuildSystem, Project> buildSystems;
|
||||
private final MavenProperties mavenProperties;
|
||||
private final ExecutorService executor;
|
||||
private final Workspace workspace;
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the build system for each module contained in the given iteration and executes the given function for it
|
||||
* considering pre-requites, honoring the order.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @param function must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T, M extends ProjectAware> Summary<T> doWithBuildSystemOrdered(Streamable<M> iteration,
|
||||
BiFunction<BuildSystem, M, T> function) {
|
||||
return doWithBuildSystem(iteration, function, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the build system for each module contained in the given iteration and executes the given function for it
|
||||
* considering pre-requites, without considering the execution order.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @param function must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T, M extends ProjectAware> Summary<T> doWithBuildSystemAnyOrder(Streamable<M> iteration,
|
||||
BiFunction<BuildSystem, M, T> function) {
|
||||
return doWithBuildSystem(iteration, function, false);
|
||||
}
|
||||
|
||||
private <T, M extends ProjectAware> Summary<T> doWithBuildSystem(Streamable<M> iteration,
|
||||
BiFunction<BuildSystem, M, T> function, boolean considerDependencyOrder) {
|
||||
|
||||
Map<Project, CompletableFuture<T>> results = new ConcurrentHashMap<>();
|
||||
|
||||
// Add here projects that should be skipped because of a partial deployment to e.g. Sonatype.
|
||||
Set<Project> skip = new HashSet<>(Arrays.asList());
|
||||
|
||||
skip.forEach(it -> results.put(it, CompletableFuture.completedFuture(null)));
|
||||
|
||||
for (M moduleIteration : iteration) {
|
||||
|
||||
if (skip.contains(moduleIteration.getProject())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (considerDependencyOrder) {
|
||||
Set<Project> dependencies = moduleIteration.getProject().getDependencies();
|
||||
for (Project dependency : dependencies) {
|
||||
|
||||
CompletableFuture<T> futureResult = results.get(dependency);
|
||||
|
||||
if (futureResult == null) {
|
||||
|
||||
if (!iteration.stream().map(ProjectAware::getProject).anyMatch(project -> project.equals(dependency))) {
|
||||
throw new IllegalStateException(moduleIteration.getProject().getName() + " requires "
|
||||
+ dependency.getName() + " which is not part of the Iteration. Please fix Projects/Iterations setup");
|
||||
}
|
||||
|
||||
throw new IllegalStateException("No future result for " + dependency.getName() + ", required by "
|
||||
+ moduleIteration.getProject().getName());
|
||||
}
|
||||
|
||||
futureResult.join();
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture<T> result = run(moduleIteration, function);
|
||||
results.put(moduleIteration.getProject(), result);
|
||||
}
|
||||
|
||||
return iteration.stream()//
|
||||
.map(module -> {
|
||||
|
||||
CompletableFuture<T> future = results.get(module.getProject());
|
||||
|
||||
try {
|
||||
return new ExecutionResult<T>(module.getProject(), future.get());
|
||||
}
|
||||
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
return new ExecutionResult<T>(module.getProject(), e.getCause());
|
||||
}
|
||||
|
||||
}) //
|
||||
.collect(toSummaryCollector());
|
||||
}
|
||||
|
||||
private <T, M extends ProjectAware> CompletableFuture<T> run(M module, BiFunction<BuildSystem, M, T> function) {
|
||||
|
||||
Assert.notNull(module, "Module must not be null!");
|
||||
|
||||
CompletableFuture<T> result = new CompletableFuture<>();
|
||||
Supplier<IllegalStateException> exception = () -> new IllegalStateException(
|
||||
String.format("No build system plugin found for project %s!", module.getProject()));
|
||||
|
||||
BuildSystem buildSystem = buildSystems.getPluginFor(module.getProject(), exception)
|
||||
.withJavaVersion(detectJavaVersion(module.getProject()));
|
||||
|
||||
Runnable runnable = () -> {
|
||||
|
||||
try {
|
||||
|
||||
result.complete(function.apply(buildSystem, module));
|
||||
} catch (Exception e) {
|
||||
result.completeExceptionally(e);
|
||||
}
|
||||
};
|
||||
|
||||
executor.execute(runnable);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public JavaVersion detectJavaVersion(Project project) {
|
||||
|
||||
File ciProperties = workspace.getFile(InfrastructureOperations.CI_PROPERTIES, project);
|
||||
|
||||
if (!ciProperties.exists()) {
|
||||
throw new IllegalStateException(String.format("Cannot find %s for project %s", ciProperties, project));
|
||||
}
|
||||
|
||||
Properties properties = new Properties();
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(ciProperties)) {
|
||||
properties.load(fis);
|
||||
}
|
||||
|
||||
return JavaVersion.fromDockerTag(properties.getProperty("java.main.tag"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new collector to toSummaryCollector {@link ExecutionResult} as {@link Summary} using the {@link Stream}
|
||||
* API.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static <T> Collector<ExecutionResult<T>, ?, Summary<T>> toSummaryCollector() {
|
||||
return ListWrapperCollector.collectInto(Summary::new);
|
||||
}
|
||||
|
||||
public static class ExecutionResult<T> {
|
||||
|
||||
private final Project project;
|
||||
private final T result;
|
||||
private final Throwable failure;
|
||||
|
||||
public ExecutionResult(Project project, Throwable failure) {
|
||||
this.project = project;
|
||||
this.result = null;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
public ExecutionResult(Project project, T result) {
|
||||
this.project = project;
|
||||
this.result = result;
|
||||
this.failure = null;
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%-14s - %s", project.getName(),
|
||||
isSuccessful() ? "🆗 Successful" : "🧨 Error: " + failure.getMessage());
|
||||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return this.failure == null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Summary<T> {
|
||||
|
||||
private final List<ExecutionResult<T>> executions;
|
||||
|
||||
public Summary(List<ExecutionResult<T>> executions) {
|
||||
this.executions = executions;
|
||||
|
||||
if (!isSuccessful()) {
|
||||
throw new BuildFailed(this);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ExecutionResult<T>> getExecutions() {
|
||||
return executions;
|
||||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return this.executions.stream().allMatch(ExecutionResult::isSuccessful);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("Execution summary");
|
||||
builder.append(IOUtils.LINE_SEPARATOR);
|
||||
builder.append(executions.stream().map(it -> "\t" + it).collect(Collectors.joining(IOUtils.LINE_SEPARATOR)));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class BuildFailed extends RuntimeException {
|
||||
|
||||
public BuildFailed(Summary<?> summary) {
|
||||
super(summary.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.util.VisibleForTesting;
|
||||
|
||||
import org.springframework.data.release.deployment.DeploymentInformation;
|
||||
import org.springframework.data.release.model.Module;
|
||||
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.Train;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class BuildOperations {
|
||||
|
||||
private final @NonNull PluginRegistry<BuildSystem, Project> buildSystems;
|
||||
private final @NonNull Logger logger;
|
||||
private final @NonNull MavenProperties properties;
|
||||
private final @NonNull BuildExecutor executor;
|
||||
|
||||
/**
|
||||
* 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 = UpdateInformation.of(iteration, phase);
|
||||
|
||||
BuildExecutor.Summary<ModuleIteration> summary = executor.doWithBuildSystemOrdered(iteration,
|
||||
(system, it) -> system.updateProjectDescriptors(it, updateInformation));
|
||||
|
||||
logger.log(iteration, "Update Project Descriptors done: %s", summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the distribution builds for all modules participating 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!");
|
||||
|
||||
distributeResources(iteration.getTrain());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the distribution builds for all modules participating in the given {@link Train}.
|
||||
*
|
||||
* @param train must not be {@literal null}.
|
||||
*/
|
||||
public void distributeResources(Train train) {
|
||||
|
||||
Assert.notNull(train, "Train must not be null!");
|
||||
|
||||
BuildExecutor.Summary<Module> summary = executor.doWithBuildSystemAnyOrder(train,
|
||||
BuildSystem::triggerDistributionBuild);
|
||||
|
||||
logger.log(train, "Distribution build: %s", summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the distribution builds for the given module.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
*/
|
||||
public void distributeResources(ModuleIteration iteration) {
|
||||
|
||||
Assert.notNull(iteration, "ModuleIteration must not be null!");
|
||||
|
||||
doWithBuildSystem(iteration, BuildSystem::triggerDistributionBuild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the release build for all modules in the given {@link TrainIteration}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<DeploymentInformation> performRelease(TrainIteration iteration) {
|
||||
|
||||
BuildExecutor.Summary<DeploymentInformation> summary = executor.doWithBuildSystemOrdered(iteration,
|
||||
(buildSystem, moduleIteration) -> performRelease(moduleIteration));
|
||||
|
||||
logger.log(iteration, "Release: %s", summary);
|
||||
|
||||
return summary.getExecutions().stream().map(BuildExecutor.ExecutionResult::getResult).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) {
|
||||
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!");
|
||||
|
||||
BuildExecutor.Summary<ModuleIteration> summary = executor.doWithBuildSystemOrdered(iteration,
|
||||
(system, module) -> system.prepareVersion(module, phase));
|
||||
|
||||
logger.log(iteration, "Prepare versions: %s", summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@VisibleForTesting
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Path} of the local artifact repository.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Path getLocalRepository() {
|
||||
return properties.getLocalRepository().toPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a normal build for the given {@link ModuleIteration}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ModuleIteration triggerBuild(ModuleIteration module) {
|
||||
return doWithBuildSystem(module, BuildSystem::triggerBuild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the pre-release checks for all modules of the given {@link TrainIteration}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
*/
|
||||
public void runPreReleaseChecks(TrainIteration iteration) {
|
||||
|
||||
Assert.notNull(iteration, "Train iteration must not be null!");
|
||||
|
||||
executor.doWithBuildSystemAnyOrder(iteration, BuildSystem::triggerPreReleaseCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies Java version presence and that the project can be build using Maven.
|
||||
*/
|
||||
public void verify() {
|
||||
|
||||
Project project = Projects.BUILD;
|
||||
BuildSystem buildSystem = buildSystems.getRequiredPluginFor(project);
|
||||
|
||||
buildSystem.withJavaVersion(executor.detectJavaVersion(project)).verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the build system for the module contained in the given {@link ModuleIteration} and executes the given
|
||||
* function with it.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param function must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private <T> T doWithBuildSystem(ModuleIteration module, BiFunction<BuildSystem, ModuleIteration, T> function) {
|
||||
|
||||
Assert.notNull(module, "ModuleIteration must not be null!");
|
||||
|
||||
Supplier<IllegalStateException> exception = () -> new IllegalStateException(
|
||||
String.format("No build system plugin found for project %s!", module.getProject()));
|
||||
|
||||
BuildSystem buildSystem = buildSystems.getPluginFor(module.getProject(), exception);
|
||||
|
||||
return function.apply(buildSystem.withJavaVersion(executor.detectJavaVersion(module.getProject())), module);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import org.springframework.data.release.deployment.DeploymentInformation;
|
||||
import org.springframework.data.release.model.JavaVersion;
|
||||
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.ProjectAware;
|
||||
import org.springframework.plugin.core.Plugin;
|
||||
|
||||
/**
|
||||
* Plugin interface to back different build systems.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
interface BuildSystem extends Plugin<Project> {
|
||||
|
||||
/**
|
||||
* Updates the project descriptors for the given {@link ModuleIteration} using the given {@link UpdateInformation}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @param updateInformation must not be {@literal null}.
|
||||
*/
|
||||
<M extends ProjectAware> M updateProjectDescriptors(M iteration, UpdateInformation updateInformation);
|
||||
|
||||
/**
|
||||
* Prepares the project descriptor of the {@link ModuleIteration} for the given release {@link Phase}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param phase must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
ModuleIteration prepareVersion(ModuleIteration module, Phase phase);
|
||||
|
||||
/**
|
||||
* Deploy artifacts for the given {@link ModuleIteration} and return the {@link DeploymentInformation}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
DeploymentInformation deploy(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Runs the distribution build.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<M extends ProjectAware> M triggerDistributionBuild(M module);
|
||||
|
||||
<M extends ProjectAware> M triggerBuild(M module);
|
||||
|
||||
/**
|
||||
* Triggers the pre-release checks for the given {@link ModuleIteration}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<M extends ProjectAware> M triggerPreReleaseCheck(M module);
|
||||
|
||||
/**
|
||||
* Verify general functionality and correctness of the build setup.
|
||||
*/
|
||||
void verify();
|
||||
|
||||
/**
|
||||
* Prepare the build system with a Java version.
|
||||
*
|
||||
* @param javaVersion
|
||||
* @return
|
||||
*/
|
||||
BuildSystem withJavaVersion(JavaVersion javaVersion);
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.release.model.Masked;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent a Maven command line.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Value
|
||||
class CommandLine {
|
||||
|
||||
@NonNull List<Goal> goals;
|
||||
@NonNull List<Argument> arguments;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CommandLine} for the given {@link Goal} and {@link Argument}s.
|
||||
*
|
||||
* @param goal must not be {@literal null}.
|
||||
* @param argument must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static CommandLine of(Goal goal, Argument... argument) {
|
||||
return new CommandLine(Collections.singletonList(goal), Arrays.asList(argument));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CommandLine} for the given {@link Goal}s and {@link Argument}s.
|
||||
*
|
||||
* @param goal must not be {@literal null}.
|
||||
* @param argument must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static CommandLine of(Goal first, Goal second, Argument... argument) {
|
||||
return new CommandLine(Arrays.asList(first, second), Arrays.asList(argument));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link CommandLine} with the given {@link Argument} added in case the given {@link BooleanSupplier}
|
||||
* evaluates to {@literal true}.
|
||||
*
|
||||
* @param argument must not be {@literal null}.
|
||||
* @param condition must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public CommandLine conditionalAnd(Argument argument, BooleanSupplier condition) {
|
||||
return condition.getAsBoolean() ? and(argument) : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link CommandLine} with the given {@link Argument} added.
|
||||
*
|
||||
* @param argument must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public CommandLine and(Argument argument) {
|
||||
|
||||
Assert.notNull(argument, "Argument must not be null!");
|
||||
|
||||
List<Argument> newArguments = new ArrayList<Argument>(arguments.size() + 1);
|
||||
newArguments.addAll(arguments);
|
||||
newArguments.add(argument);
|
||||
|
||||
return new CommandLine(goals, newArguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the current {@link CommandLine} as a plain {@link List} of {@link String}s using the given {@link Function}
|
||||
* to expand the {@link Goal}s.
|
||||
*
|
||||
* @param goalExpansion must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<String> toCommandLine(Function<Goal, String> goalExpansion) {
|
||||
|
||||
Stream<String> goalStream = goals.stream().map(goalExpansion);
|
||||
Stream<String> argumentStream = arguments.stream().map(it -> it.toCommandLineArgument());
|
||||
|
||||
return Stream.concat(goalStream, argumentStream).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
Stream<String> goalStream = goals.stream().map(it -> it.getGoal());
|
||||
Stream<String> argumentStream = arguments.stream().map(Object::toString);
|
||||
|
||||
return Stream.concat(goalStream, argumentStream).collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Maven goal to invoke. Can be a custom one but also one of the predefined instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Value(staticConstructor = "goal")
|
||||
public static class Goal {
|
||||
|
||||
public static final Goal CLEAN = Goal.goal("clean");
|
||||
public static final Goal INSTALL = Goal.goal("install");
|
||||
public static final Goal DEPLOY = Goal.goal("deploy");
|
||||
public static final Goal VALIDATE = Goal.goal("validate");
|
||||
public static final Goal VERIFY = Goal.goal("verify");
|
||||
|
||||
String goal;
|
||||
}
|
||||
|
||||
@Value
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class Argument {
|
||||
|
||||
public static Argument SKIP_TESTS = Argument.arg("skipTests");
|
||||
|
||||
@NonNull String name;
|
||||
@NonNull Optional<ArgumentValue<?>> value;
|
||||
|
||||
private Argument(String name, ArgumentValue<?> value) {
|
||||
this(name, Optional.of(value));
|
||||
}
|
||||
|
||||
static Argument of(String name) {
|
||||
return new Argument(name, Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the given comma-separated profiles for the {@link CommandLine}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static Argument profile(String name, String... others) {
|
||||
|
||||
Assert.hasText(name, "Profiles must not be null or empty!");
|
||||
Assert.notNull(others, "Other profiles must not be null!");
|
||||
|
||||
String profiles = Stream.concat(Stream.of(name), Arrays.stream(others)).collect(Collectors.joining(","));
|
||||
|
||||
return Argument.of("-P".concat(profiles));
|
||||
}
|
||||
|
||||
public static Argument arg(String name) {
|
||||
return Argument.of("-D".concat(name));
|
||||
}
|
||||
|
||||
public static Argument debug() {
|
||||
return Argument.of("-X");
|
||||
}
|
||||
|
||||
public Argument withValue(Object value) {
|
||||
return new Argument(name, ArgumentValue.of(value));
|
||||
}
|
||||
|
||||
public Argument withQuotedValue(Object value) {
|
||||
return new Argument(name, ArgumentValue.of(value, it -> String.format("\"%s\"", it.toString())));
|
||||
}
|
||||
|
||||
public Argument withValue(Masked masked) {
|
||||
return new Argument(name, ArgumentValue.of(masked));
|
||||
}
|
||||
|
||||
public String toCommandLineArgument() {
|
||||
return toNameValuePair(value.map(ArgumentValue::toCommandLine));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toNameValuePair(value.map(Object::toString));
|
||||
}
|
||||
|
||||
private String toNameValuePair(Optional<String> source) {
|
||||
|
||||
return source//
|
||||
.map(it -> String.format("%s=%s", name, it))//
|
||||
.orElse(name);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
private static class ArgumentValue<T> {
|
||||
|
||||
private final @NonNull T value;
|
||||
private final @NonNull Optional<Function<T, String>> preparer;
|
||||
private final @NonNull Optional<Function<T, String>> toString;
|
||||
|
||||
public static <T> ArgumentValue<T> of(T value) {
|
||||
return new ArgumentValue<>(value, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public static <T> ArgumentValue<T> of(T value, Function<T, String> preparer) {
|
||||
return new ArgumentValue<>(value, Optional.of(preparer), Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link ArgumentValue} for the given {@link Masked} value.
|
||||
*
|
||||
* @param masked must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static <T extends Masked> ArgumentValue<T> of(T masked) {
|
||||
return new ArgumentValue<>(masked, Optional.empty(), Optional.of(it -> it.masked()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link String} variant of the argument value.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String toCommandLine() {
|
||||
return preparer.map(it -> it.apply(value)).orElseGet(() -> value.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return toString.map(it -> it.apply(value)).orElseGet(() -> toCommandLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Value object to represent an artifacts group identifier.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Value(staticConstructor = "of")
|
||||
class GroupId {
|
||||
|
||||
private final String value;
|
||||
|
||||
public String asPath() {
|
||||
return value.replace('.', '/');
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import static org.springframework.data.release.model.Projects.*;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent a Maven artifact.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class MavenArtifact {
|
||||
|
||||
private static final GroupId GROUP_ID = GroupId.of("org.springframework.data");
|
||||
|
||||
private final Project project;
|
||||
private final @Getter Repository repository;
|
||||
private final @Getter ArtifactVersion version;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MavenArtifact} for the given {@link ModuleIteration}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
public MavenArtifact(ModuleIteration module) {
|
||||
|
||||
Assert.notNull(module, "Module iteration must not be null!");
|
||||
|
||||
this.project = module.getModule().getProject();
|
||||
this.repository = new Repository(module.getIteration());
|
||||
this.version = ArtifactVersion.of(module);
|
||||
}
|
||||
|
||||
public MavenArtifact(Project project, ArtifactVersion version) {
|
||||
|
||||
this.project = project;
|
||||
this.repository = new Repository(version);
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return GROUP_ID.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Maven artifact identifier.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getArtifactId() {
|
||||
|
||||
String artifactId = String.format("spring-data-%s", project.getName().toLowerCase());
|
||||
|
||||
return REST.equals(project) ? artifactId.concat("-webmvc") : artifactId;
|
||||
}
|
||||
|
||||
public ArtifactVersion getNextDevelopmentVersion() {
|
||||
return version.getNextDevelopmentVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL pointing to the artifacts.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getRootUrl() {
|
||||
return String.format("%s/%s/%s/%s", repository.getUrl(), GROUP_ID.asPath(), getArtifactId(), version);
|
||||
}
|
||||
}
|
||||
@@ -1,464 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import static org.springframework.data.release.build.CommandLine.Argument.*;
|
||||
import static org.springframework.data.release.build.CommandLine.Goal.*;
|
||||
import static org.springframework.data.release.model.Projects.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.release.build.CommandLine.Argument;
|
||||
import org.springframework.data.release.build.CommandLine.Goal;
|
||||
import org.springframework.data.release.build.Pom.Artifact;
|
||||
import org.springframework.data.release.deployment.DefaultDeploymentInformation;
|
||||
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.Gpg;
|
||||
import org.springframework.data.release.model.JavaVersion;
|
||||
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.ProjectAware;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.xmlbeam.ProjectionFactory;
|
||||
import org.xmlbeam.XBProjector;
|
||||
import org.xmlbeam.dom.DOMAccess;
|
||||
import org.xmlbeam.io.XBStreamInput;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@Order(100)
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class MavenBuildSystem implements BuildSystem {
|
||||
|
||||
static String POM_XML = "pom.xml";
|
||||
|
||||
Workspace workspace;
|
||||
ProjectionFactory projectionFactory;
|
||||
Logger logger;
|
||||
MavenRuntime mvn;
|
||||
DeploymentProperties properties;
|
||||
Gpg gpg;
|
||||
|
||||
@Override
|
||||
public BuildSystem withJavaVersion(JavaVersion javaVersion) {
|
||||
return new MavenBuildSystem(workspace, projectionFactory, logger, mvn.withJavaVersion(javaVersion), properties,
|
||||
gpg);
|
||||
}
|
||||
|
||||
/*
|
||||
* (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 <M extends ProjectAware> M updateProjectDescriptors(M module, UpdateInformation information) {
|
||||
|
||||
PomUpdater updater = new PomUpdater(logger, information, module.getProject());
|
||||
|
||||
if (updater.isBuildProject()) {
|
||||
|
||||
if (information.isBomInBuildProject()) {
|
||||
updateBom(information, "bom/pom.xml", BUILD);
|
||||
}
|
||||
|
||||
updateParentPom(information);
|
||||
|
||||
} else if (updater.isBomProject()) {
|
||||
updateBom(information, "bom/pom.xml", BOM);
|
||||
} else {
|
||||
|
||||
doWithProjection(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.Module)
|
||||
*/
|
||||
@Override
|
||||
public <M extends ProjectAware> M triggerDistributionBuild(M module) {
|
||||
|
||||
Project project = module.getProject();
|
||||
|
||||
if (BUILD.equals(project)) {
|
||||
return module;
|
||||
}
|
||||
|
||||
if (BOM.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…");
|
||||
|
||||
mvn.execute(project, CommandLine.of(Goal.CLEAN, Goal.DEPLOY, //
|
||||
SKIP_TESTS, profile("distribute"), Argument.of("-B"),
|
||||
arg("artifactory.server").withValue(properties.getServer().getUri()),
|
||||
arg("artifactory.distribution-repository").withValue(properties.getDistributionRepository()),
|
||||
arg("artifactory.username").withValue(properties.getUsername()),
|
||||
arg("artifactory.password").withValue(properties.getPassword())));
|
||||
|
||||
mvn.execute(project, CommandLine.of(Goal.CLEAN, Goal.DEPLOY, //
|
||||
SKIP_TESTS, profile("distribute-schema"), Argument.of("-B"),
|
||||
arg("artifactory.server").withValue(properties.getServer().getUri()),
|
||||
arg("artifactory.distribution-repository").withValue(properties.getDistributionRepository()),
|
||||
arg("artifactory.username").withValue(properties.getUsername()),
|
||||
arg("artifactory.password").withValue(properties.getPassword())));
|
||||
|
||||
logger.log(project, "Successfully finished distribution build!");
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
private void updateBom(UpdateInformation updateInformation, String file, Project project) {
|
||||
|
||||
TrainIteration iteration = updateInformation.getTrain();
|
||||
|
||||
logger.log(BUILD, "Updating BOM pom.xml…");
|
||||
|
||||
doWithProjection(workspace.getFile(file, project), pom -> {
|
||||
|
||||
for (ModuleIteration module : iteration.getModulesExcept(BUILD, BOM)) {
|
||||
|
||||
ArtifactVersion version = updateInformation.getProjectVersionToSet(module.getProject());
|
||||
|
||||
logger.log(project, "%s", module);
|
||||
|
||||
String moduleArtifactId = new MavenArtifact(module).getArtifactId();
|
||||
pom.setDependencyManagementVersion(moduleArtifactId, version);
|
||||
logger.log(project, "Updated managed dependency version for %s to %s!", moduleArtifactId, version);
|
||||
|
||||
module.getProject().doWithAdditionalArtifacts(additionalArtifact -> {
|
||||
|
||||
String artifactId = additionalArtifact.getArtifactId();
|
||||
Artifact artifact = pom.getManagedDependency(artifactId);
|
||||
|
||||
if (artifact != null) {
|
||||
pom.setDependencyManagementVersion(artifactId, version);
|
||||
logger.log(project, "Updated managed dependency version for %s to %s!", artifactId, version);
|
||||
} else {
|
||||
logger.log(project, "Artifact %s not found, skipping update!", artifactId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (updateInformation.getPhase().equals(Phase.PREPARE)) {
|
||||
|
||||
// Make sure we have no snapshot leftovers
|
||||
List<Artifact> snapshotDependencies = pom.getSnapshotDependencies();
|
||||
|
||||
if (!snapshotDependencies.isEmpty()) {
|
||||
throw new IllegalStateException(String.format("Found snapshot dependencies %s!", snapshotDependencies));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateParentPom(UpdateInformation information) {
|
||||
|
||||
// Fix version of shared resources to to-be-released version.
|
||||
doWithProjection(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;
|
||||
}
|
||||
|
||||
/*
|
||||
* (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 = UpdateInformation.of(module.getTrainIteration(), phase);
|
||||
|
||||
CommandLine goals = CommandLine.of(goal("versions:set"), goal("versions:commit"));
|
||||
|
||||
if (BOM.equals(project)) {
|
||||
|
||||
mvn.execute(project, goals.and(arg("newVersion").withValue(information.getReleaseTrainVersion())) //
|
||||
.and(arg("generateBackupPoms").withValue("false")));
|
||||
|
||||
mvn.execute(project, goals.and(arg("newVersion").withValue(information.getReleaseTrainVersion())) //
|
||||
.and(arg("generateBackupPoms").withValue("false")) //
|
||||
.and(arg("processAllModules").withValue("true")) //
|
||||
.and(Argument.of("-pl").withValue("bom")));
|
||||
|
||||
} else {
|
||||
mvn.execute(project, goals.and(arg("newVersion").withValue(information.getProjectVersionToSet(project)))
|
||||
.and(arg("generateBackupPoms").withValue("false")));
|
||||
}
|
||||
|
||||
if (BUILD.equals(project)) {
|
||||
|
||||
if (!module.getTrain().usesCalver()) {
|
||||
mvn.execute(project, goals.and(arg("newVersion").withValue(information.getReleaseTrainVersion())) //
|
||||
.and(arg("generateBackupPoms").withValue("false")) //
|
||||
.and(arg("groupId").withValue("org.springframework.data")) //
|
||||
.and(arg("artifactId").withValue("spring-data-releasetrain")));
|
||||
}
|
||||
|
||||
mvn.execute(project, CommandLine.of(Goal.INSTALL));
|
||||
}
|
||||
|
||||
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 DefaultDeploymentInformation(module, properties);
|
||||
|
||||
deployToArtifactory(module, information);
|
||||
deployToMavenCentral(module);
|
||||
|
||||
return information;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.build.BuildSystem#triggerBuild(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
public <M extends ProjectAware> M triggerBuild(M module) {
|
||||
|
||||
CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.INSTALL)//
|
||||
.conditionalAnd(SKIP_TESTS, () -> module.getProject().skipTests());
|
||||
|
||||
mvn.execute(module.getProject(), arguments);
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.build.BuildSystem#triggerPreReleaseCheck(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
public <M extends ProjectAware> M triggerPreReleaseCheck(M module) {
|
||||
|
||||
mvn.execute(module.getProject(), CommandLine.of(Goal.CLEAN, Goal.VALIDATE, profile("pre-release")));
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Project project) {
|
||||
return isMavenProject(project);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.build.BuildSystem#verify()
|
||||
*/
|
||||
@Override
|
||||
public void verify() {
|
||||
|
||||
logger.log(BUILD, "Verifying Maven Build System…");
|
||||
|
||||
CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.VERIFY, //
|
||||
profile("central"), //
|
||||
SKIP_TESTS, //
|
||||
arg("gpg.executable").withValue(gpg.getExecutable()), //
|
||||
arg("gpg.keyname").withValue(gpg.getKeyname()), //
|
||||
arg("gpg.password").withValue(gpg.getPassword()));
|
||||
|
||||
mvn.execute(BUILD, arguments);
|
||||
|
||||
mvn.execute(BUILD, CommandLine.of(Goal.goal("nexus-staging:rc-list-profiles"), //
|
||||
profile("central")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers Maven commands to deploy module artifacts to Spring Artifactory.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param information must not be {@literal null}.
|
||||
*/
|
||||
private void deployToArtifactory(ModuleIteration module, DeploymentInformation information) {
|
||||
|
||||
Assert.notNull(module, "Module iteration must not be null!");
|
||||
Assert.notNull(information, "Deployment information must not be null!");
|
||||
|
||||
if (!module.getIteration().isPreview()) {
|
||||
logger.log(module, "Not a preview version (milestone or release candidate). Skipping Artifactory deployment.");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(module, "Deploying artifacts to Spring Artifactory…");
|
||||
|
||||
CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.DEPLOY, //
|
||||
profile("ci,release,artifactory"), //
|
||||
SKIP_TESTS, //
|
||||
arg("artifactory.server").withValue(properties.getServer().getUri()),
|
||||
arg("artifactory.staging-repository").withValue(properties.getStagingRepository()),
|
||||
arg("artifactory.username").withValue(properties.getUsername()),
|
||||
arg("artifactory.password").withValue(properties.getPassword()),
|
||||
arg("artifactory.build-name").withQuotedValue(information.getBuildName()),
|
||||
arg("artifactory.build-number").withValue(information.getBuildNumber()));
|
||||
|
||||
mvn.execute(module.getProject(), arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers Maven commands to deploy to Sonatype's OSS Nexus if the given {@link ModuleIteration} refers to a version
|
||||
* that has to be publicly released.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
private void deployToMavenCentral(ModuleIteration module) {
|
||||
|
||||
Assert.notNull(module, "Module iteration must not be null!");
|
||||
|
||||
if (!module.getIteration().isPublic()) {
|
||||
|
||||
logger.log(module, "Skipping deployment to Maven Central as it's not a public version!");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(module, "Deploying artifacts to Sonatype OSS Nexus…");
|
||||
|
||||
CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.DEPLOY, //
|
||||
profile("ci,release,central"), //
|
||||
SKIP_TESTS, //
|
||||
arg("gpg.executable").withValue(gpg.getExecutable()), //
|
||||
arg("gpg.keyname").withValue(gpg.getKeyname()), //
|
||||
arg("gpg.password").withValue(gpg.getPassword()));
|
||||
|
||||
mvn.execute(module.getProject(), arguments);
|
||||
}
|
||||
|
||||
private boolean isMavenProject(Project project) {
|
||||
return workspace.getFile(POM_XML, project).exists();
|
||||
}
|
||||
|
||||
private void doWithProjection(File file, Consumer<Pom> callback) {
|
||||
doWithProjection(file, Pom.class, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Move XML file callbacks using the {@link ProjectionFactory} to {@link Workspace}.
|
||||
*/
|
||||
private <T extends Pom> void doWithProjection(File file, Class<T> type, Consumer<T> callback) {
|
||||
|
||||
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
|
||||
byte[] content = doWithProjection((XBProjector) projectionFactory, bis, type, callback);
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
fos.write(content);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static <T extends Pom> byte[] doWithProjection(XBProjector projector, InputStream stream, Class<T> type,
|
||||
Consumer<T> callback) throws IOException {
|
||||
|
||||
XBStreamInput io = projector.io().stream(stream);
|
||||
T pom = io.read(type);
|
||||
callback.accept(pom);
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
projector.config().createTransformer().transform(new DOMSource(((DOMAccess) pom).getDOMNode()),
|
||||
new StreamResult(writer));
|
||||
} catch (TransformerException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
String s = writer.toString();
|
||||
|
||||
if (s.contains("standalone=\"no\"?><")) {
|
||||
s = s.replaceAll(Pattern.quote("standalone=\"no\"?><"), "standalone=\"no\"?>" + IOUtils.LINE_SEPARATOR + "<");
|
||||
}
|
||||
|
||||
if (!s.endsWith(IOUtils.LINE_SEPARATOR)) {
|
||||
s += IOUtils.LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
return s.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Maven configuration properties.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "maven")
|
||||
class MavenProperties {
|
||||
|
||||
private File mavenHome;
|
||||
private File localRepository;
|
||||
private Map<String, String> plugins;
|
||||
private boolean consoleLogger = true;
|
||||
private boolean parallelize = false;
|
||||
|
||||
/**
|
||||
* 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("~", FileUtils.getUserDirectoryPath()));
|
||||
|
||||
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!");
|
||||
|
||||
if (goal.startsWith("-")) {
|
||||
return goal;
|
||||
}
|
||||
|
||||
String[] parts = goal.split(":");
|
||||
|
||||
if (parts.length != 2 || !plugins.containsKey(parts[0])) {
|
||||
return goal;
|
||||
}
|
||||
|
||||
return plugins.get(parts[0]).concat(":").concat(parts[1]);
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
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.InvocationRequest;
|
||||
import org.apache.maven.shared.invoker.InvocationResult;
|
||||
import org.apache.maven.shared.invoker.Invoker;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.release.io.JavaRuntimes;
|
||||
import org.springframework.data.release.io.Workspace;
|
||||
import org.springframework.data.release.model.JavaVersion;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.shell.support.util.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
class MavenRuntime {
|
||||
|
||||
private final Workspace workspace;
|
||||
private final Logger logger;
|
||||
private final MavenProperties properties;
|
||||
private final JavaRuntimes.JdkInstallation jdk;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MavenRuntime} for the given {@link Workspace} and Maven home.
|
||||
*
|
||||
* @param workspace must not be {@literal null}.
|
||||
* @param logger must not be {@literal null}.
|
||||
* @param properties must not be {@literal null}.
|
||||
*/
|
||||
@Autowired
|
||||
public MavenRuntime(Workspace workspace, Logger logger, MavenProperties properties) {
|
||||
this(workspace, logger, properties, JavaVersion.JAVA_8);
|
||||
}
|
||||
|
||||
private MavenRuntime(Workspace workspace, Logger logger, MavenProperties properties,
|
||||
JavaVersion requiredJavaVersion) {
|
||||
|
||||
this.workspace = workspace;
|
||||
this.logger = logger;
|
||||
this.properties = properties;
|
||||
this.jdk = JavaRuntimes.Selector.from(requiredJavaVersion).notGraalVM().getRequiredJdkInstallation();
|
||||
}
|
||||
|
||||
public MavenRuntime withJavaVersion(JavaVersion javaVersion) {
|
||||
return new MavenRuntime(workspace, logger, properties, javaVersion);
|
||||
}
|
||||
|
||||
public void execute(Project project, CommandLine arguments) {
|
||||
|
||||
logger.log(project, "📦 Executing mvn %s", arguments.toString());
|
||||
|
||||
try (MavenLogger mavenLogger = getLogger(project, arguments.getGoals())) {
|
||||
|
||||
Invoker invoker = new DefaultInvoker();
|
||||
invoker.setMavenHome(properties.getMavenHome());
|
||||
invoker.setOutputHandler(mavenLogger::info);
|
||||
invoker.setErrorHandler(mavenLogger::warn);
|
||||
|
||||
File localRepository = properties.getLocalRepository();
|
||||
|
||||
if (localRepository != null) {
|
||||
invoker.setLocalRepositoryDirectory(localRepository);
|
||||
}
|
||||
|
||||
File javaHome = getJavaHome();
|
||||
mavenLogger.info(String.format("Java Home: %s", jdk));
|
||||
mavenLogger.info(String.format("Executing: mvn %s", arguments));
|
||||
|
||||
InvocationRequest request = new DefaultInvocationRequest();
|
||||
request.setJavaHome(javaHome);
|
||||
request.setShellEnvironmentInherited(true);
|
||||
request.setBaseDirectory(workspace.getProjectDirectory(project));
|
||||
request.setBatchMode(true);
|
||||
|
||||
request.setGoals(arguments.toCommandLine(it -> properties.getFullyQualifiedPlugin(it.getGoal())));
|
||||
|
||||
InvocationResult result = invoker.execute(request);
|
||||
|
||||
if (result.getExitCode() != 0) {
|
||||
logger.warn(project, "🙈 Failed execution mvn %s", arguments.toString());
|
||||
|
||||
throw new IllegalStateException("🙈 Failed execution mvn " + arguments.toString(),
|
||||
result.getExecutionException());
|
||||
}
|
||||
logger.log(project, "🆗 Successful execution mvn %s", arguments.toString());
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private File getJavaHome() {
|
||||
return jdk.getHome().getAbsoluteFile();
|
||||
}
|
||||
|
||||
private MavenLogger getLogger(Project project, List<CommandLine.Goal> goals) {
|
||||
|
||||
if (this.properties.isConsoleLogger()) {
|
||||
return new SlfLogger(log, project);
|
||||
}
|
||||
|
||||
return new FileLogger(log, project, this.workspace.getLogsDirectory(), goals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maven Logging Forwarder.
|
||||
*/
|
||||
interface MavenLogger extends Closeable {
|
||||
|
||||
void info(String message);
|
||||
|
||||
void warn(String message);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
static class SlfLogger implements MavenLogger {
|
||||
|
||||
private final org.slf4j.Logger logger;
|
||||
private final String logPrefix;
|
||||
|
||||
SlfLogger(org.slf4j.Logger logger, Project project) {
|
||||
this.logger = logger;
|
||||
this.logPrefix = StringUtils.padRight(project.getName(), 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
logger.info(logPrefix + ": " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message) {
|
||||
logger.warn(logPrefix + ": " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
static class FileLogger implements MavenLogger {
|
||||
|
||||
private final PrintWriter printWriter;
|
||||
private final FileOutputStream outputStream;
|
||||
|
||||
FileLogger(org.slf4j.Logger logger, Project project, File logsDirectory, List<CommandLine.Goal> goals) {
|
||||
|
||||
if (!logsDirectory.exists()) {
|
||||
logsDirectory.mkdirs();
|
||||
}
|
||||
|
||||
String goalNames = goals.stream().map(CommandLine.Goal::getGoal).collect(Collectors.joining("-"));
|
||||
|
||||
String filename = String.format("mvn-%s-%s.log", project.getName(), goalNames).replace(':', '.');
|
||||
|
||||
try {
|
||||
File file = new File(logsDirectory, filename);
|
||||
logger.info("Routing Maven output to " + file.getCanonicalPath());
|
||||
outputStream = new FileOutputStream(file, true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
printWriter = new PrintWriter(outputStream, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
printWriter.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message) {
|
||||
printWriter.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
printWriter.close();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.xmlbeam.annotation.XBValue;
|
||||
import org.xmlbeam.annotation.XBWrite;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface ParentPom extends Pom {
|
||||
|
||||
@XBWrite("/project/properties/releasetrain")
|
||||
void setReleaseTrain(@XBValue String releaseTrain);
|
||||
|
||||
@XBWrite("/project/profiles/profile[id=\"distribute\"]/dependencies/dependency[artifactId=\"spring-data-build-resources\"]/version")
|
||||
void setSharedResourcesVersion(@XBValue ArtifactVersion value);
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.xmlbeam.annotation.XBRead;
|
||||
import org.xmlbeam.annotation.XBValue;
|
||||
import org.xmlbeam.annotation.XBWrite;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface Pom {
|
||||
|
||||
@XBRead("/project")
|
||||
Artifact getArtifact();
|
||||
|
||||
@XBRead("/project/version")
|
||||
String getRawVersion();
|
||||
|
||||
@XBRead("/project/version")
|
||||
ArtifactVersion getVersion();
|
||||
|
||||
@XBWrite("/project/version")
|
||||
void setVersion(ArtifactVersion version);
|
||||
|
||||
@XBWrite("/project/parent/version")
|
||||
void setParentVersion(ArtifactVersion version);
|
||||
|
||||
@XBRead("/project/properties/{0}")
|
||||
String getProperty(String property);
|
||||
|
||||
@XBWrite("/project/properties/{0}")
|
||||
void setProperty(String property, @XBValue ArtifactVersion value);
|
||||
|
||||
@XBWrite("/project/properties/{0}")
|
||||
void setProperty(String property, @XBValue String value);
|
||||
|
||||
@XBWrite("/project/repositories/repository[id=\"{0}\"]/id")
|
||||
void setRepositoryId(String oldId, @XBValue String newId);
|
||||
|
||||
@XBWrite("/project/repositories/repository[id=\"{0}\"]/url")
|
||||
void setRepositoryUrl(String id, @XBValue String url);
|
||||
|
||||
/**
|
||||
* Sets the version of the dependency with the given artifact identifier to the given {@link ArtifactVersion}.
|
||||
*
|
||||
* @param artifactId
|
||||
* @param version
|
||||
*/
|
||||
@XBWrite("/project/dependencies/dependency[artifactId=\"{0}\"]/version")
|
||||
Pom setDependencyVersion(String artifactId, @XBValue ArtifactVersion version);
|
||||
|
||||
@XBRead("/project/dependencies/dependency[artifactId=\"{0}\"]/version")
|
||||
String getDependencyVersion(String artifactId);
|
||||
|
||||
@XBWrite("/project/dependencyManagement/dependencies/dependency[artifactId=\"{0}\"]/version")
|
||||
Pom setDependencyManagementVersion(String artifactId, @XBValue ArtifactVersion version);
|
||||
|
||||
@XBRead("/project/dependencyManagement/dependencies/dependency[artifactId=\"{0}\"]")
|
||||
Artifact getManagedDependency(String artifactId);
|
||||
|
||||
@XBRead("//dependency[substring(version, string-length(version) - string-length('-SNAPSHOT') + 1) = '-SNAPSHOT']")
|
||||
List<Artifact> getSnapshotDependencies();
|
||||
|
||||
public interface Repository {
|
||||
|
||||
@XBRead("child::id")
|
||||
String getId();
|
||||
|
||||
@XBRead("child::url")
|
||||
String getUrl();
|
||||
}
|
||||
|
||||
public interface Artifact {
|
||||
|
||||
@XBRead("child::groupId")
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.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.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 boolean isBomProject() {
|
||||
return BOM.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
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-";
|
||||
|
||||
String id, url;
|
||||
|
||||
public Repository(Iteration iteration) {
|
||||
|
||||
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 Repository(ArtifactVersion version) {
|
||||
|
||||
String suffix = getSuffixFor(version);
|
||||
|
||||
this.id = ID_BASE.concat(suffix);
|
||||
this.url = BASE.concat(suffix);
|
||||
}
|
||||
|
||||
public String getSnapshotId() {
|
||||
return ID_BASE.concat("snapshot");
|
||||
}
|
||||
|
||||
public String getSnapshotUrl() {
|
||||
return BASE.concat("snapshot");
|
||||
}
|
||||
|
||||
private static String getSuffixFor(ArtifactVersion version) {
|
||||
|
||||
if (version.isSnapshotVersion()) {
|
||||
return "snapshot";
|
||||
}
|
||||
|
||||
if (version.isMilestoneVersion() || version.isReleaseCandidateVersion()) {
|
||||
return "milestone";
|
||||
}
|
||||
|
||||
if (version.isReleaseVersion()) {
|
||||
return "release";
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Unsupported ArtifactVersion %s!", version));
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.build;
|
||||
|
||||
import static org.springframework.data.release.model.Projects.*;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.Phase;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to expose update information for a given {@link TrainIteration} and phase.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class UpdateInformation {
|
||||
|
||||
private final @NonNull @Getter TrainIteration train;
|
||||
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 = train.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 = train.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(train.getIteration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version {@link String} to be used to describe the release train.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public String getReleaseTrainVersion() {
|
||||
|
||||
boolean usesCalver = train.getTrain().usesCalver();
|
||||
|
||||
switch (phase) {
|
||||
case PREPARE:
|
||||
return train.getReleaseTrainNameAndVersion();
|
||||
case MAINTENANCE:
|
||||
if (usesCalver) {
|
||||
return String.format("%s-SNAPSHOT", train.getNextBugfixName());
|
||||
}
|
||||
|
||||
case CLEANUP:
|
||||
|
||||
if (usesCalver) {
|
||||
|
||||
if (train.getIteration().isGAIteration()) {
|
||||
return String.format("%s-SNAPSHOT", train.getNextIterationName());
|
||||
}
|
||||
|
||||
return String.format("%s-SNAPSHOT", train.getNextBugfixName());
|
||||
}
|
||||
|
||||
return String.format("%s-BUILD-SNAPSHOT", train.getName());
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Unexpected phase detected " + phase + " detected!");
|
||||
}
|
||||
|
||||
public boolean isBomInBuildProject() {
|
||||
return !train.getTrain().usesCalver();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.model.ReleaseTrains;
|
||||
import org.springframework.data.release.model.Train;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@CliComponent
|
||||
class ModelCommands extends TimedCommand {
|
||||
|
||||
@CliCommand(value = "trains", help = "Displays all release trains or contents of them if a name is provided")
|
||||
public String train(@CliOption(key = { "", "train" }) Train train) {
|
||||
|
||||
return train != null ? train.toString()
|
||||
: ReleaseTrains.TRAINS.stream().map(Train::getName).collect(Collectors.joining(", "));
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.shell.core.Completion;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell.core.MethodTarget;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
public class ProjectConverter implements Converter<Project> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#supports(java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> type, String optionContext) {
|
||||
return Project.class.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#convertFromText(java.lang.String, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Project convertFromText(String value, Class<?> targetType, String optionContext) {
|
||||
return Projects.requiredByName(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#getAllPossibleValues(java.util.List, java.lang.Class, java.lang.String, java.lang.String, org.springframework.shell.core.MethodTarget)
|
||||
*/
|
||||
@Override
|
||||
public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData,
|
||||
String optionContext, MethodTarget target) {
|
||||
|
||||
for (Project project : Projects.all()) {
|
||||
completions.add(new Completion(project.getName()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import static org.springframework.data.release.model.Projects.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
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.issues.IssueTrackerCommands;
|
||||
import org.springframework.data.release.issues.github.GitHubCommands;
|
||||
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.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class ReleaseCommands extends TimedCommand {
|
||||
|
||||
@NonNull GitOperations git;
|
||||
@NonNull ReleaseOperations misc;
|
||||
@NonNull DeploymentOperations deployment;
|
||||
@NonNull BuildOperations build;
|
||||
@NonNull IssueTrackerCommands tracker;
|
||||
@NonNull GitHubCommands gitHub;
|
||||
|
||||
@CliCommand("release predict")
|
||||
public String predictTrainAndIteration() {
|
||||
|
||||
return git.getTags(COMMONS).getLatest().toArtifactVersion().//
|
||||
map(ReleaseCommands::getTrainNameForCommonsVersion).//
|
||||
orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Composite command to ship a full release.
|
||||
*
|
||||
* @param iteration
|
||||
* @throws Exception
|
||||
*/
|
||||
@CliCommand(value = "ship-it")
|
||||
public void shipIt(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
|
||||
|
||||
tracker.trackerPrepare(iteration);
|
||||
|
||||
prepare(iteration);
|
||||
|
||||
buildRelease(iteration, null);
|
||||
|
||||
conclude(iteration);
|
||||
|
||||
gitHub.push(iteration);
|
||||
|
||||
distribute(iteration, null);
|
||||
|
||||
tracker.closeIteration(iteration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the release of the given iteration of the given train.
|
||||
*
|
||||
* @param iteration
|
||||
* @throws Exception
|
||||
*/
|
||||
@CliCommand(value = "release prepare", help = "Prepares the release of the iteration of the given train.")
|
||||
public void prepare(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
|
||||
|
||||
git.prepare(iteration);
|
||||
|
||||
build.runPreReleaseChecks(iteration);
|
||||
|
||||
misc.updateResources(iteration);
|
||||
build.updateProjectDescriptors(iteration, Phase.PREPARE);
|
||||
git.commit(iteration, "Prepare %s.");
|
||||
|
||||
build.prepareVersions(iteration, Phase.PREPARE);
|
||||
git.commit(iteration, "Release version %s.");
|
||||
}
|
||||
|
||||
@CliCommand(value = "release build")
|
||||
public void buildRelease(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
|
||||
@CliOption(key = "project", mandatory = false) String projectName) {
|
||||
|
||||
if (!iteration.getIteration().isPublic()) {
|
||||
deployment.verifyAuthentication();
|
||||
}
|
||||
|
||||
if (projectName != null) {
|
||||
|
||||
Project project = Projects.requiredByName(projectName);
|
||||
ModuleIteration module = iteration.getModule(project);
|
||||
|
||||
DeploymentInformation information = build.performRelease(module);
|
||||
deployment.promote(information);
|
||||
|
||||
} else {
|
||||
build.performRelease(iteration).forEach(deployment::promote);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
if (iteration.getTrain().isAlwaysUseBranch()) {
|
||||
setupMaintenanceVersions(iteration);
|
||||
} else {
|
||||
|
||||
build.prepareVersions(iteration, Phase.CLEANUP);
|
||||
git.commit(iteration, "Prepare next development iteration.");
|
||||
|
||||
// Prepare main branch
|
||||
build.updateProjectDescriptors(iteration, Phase.CLEANUP);
|
||||
git.commit(iteration, "After release cleanups.");
|
||||
|
||||
// Prepare maintenance branches
|
||||
if (iteration.getIteration().isGAIteration()) {
|
||||
|
||||
// Create bugfix branches
|
||||
git.createMaintenanceBranches(iteration);
|
||||
|
||||
// Set project version to maintenance once
|
||||
setupMaintenanceVersions(iteration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupMaintenanceVersions(TrainIteration iteration) throws Exception {
|
||||
|
||||
// Set project version to maintenance once
|
||||
build.prepareVersions(iteration, Phase.MAINTENANCE);
|
||||
git.commit(iteration, "Prepare next development iteration.");
|
||||
|
||||
// Update inter-project dependencies and repositories
|
||||
build.updateProjectDescriptors(iteration, Phase.MAINTENANCE);
|
||||
git.commit(iteration, "After release cleanups.");
|
||||
|
||||
// Back to main branch
|
||||
git.checkout(iteration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the distribution of release artifacts for all projects.
|
||||
*
|
||||
* @param iteration
|
||||
* @throws Exception
|
||||
*/
|
||||
@CliCommand("release distribute")
|
||||
public void distribute(@CliOption(key = "", mandatory = true) TrainIteration iteration,
|
||||
@CliOption(key = "project", mandatory = false) String projectName) {
|
||||
|
||||
git.checkout(iteration);
|
||||
|
||||
if (projectName != null) {
|
||||
Project project = Projects.requiredByName(projectName);
|
||||
ModuleIteration module = iteration.getModule(project);
|
||||
|
||||
build.distributeResources(module);
|
||||
} else {
|
||||
build.distributeResources(iteration);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTrainNameForCommonsVersion(ArtifactVersion version) {
|
||||
|
||||
return ReleaseTrains.TRAINS.stream().//
|
||||
filter(train -> version.toString().startsWith(train.getModule(COMMONS).getVersion().toString())).//
|
||||
findFirst().map(Train::getName).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import org.springframework.boot.SpringBootVersion;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.shell.plugin.BannerProvider;
|
||||
import org.springframework.shell.support.util.FileUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
@Component
|
||||
class SpringDataReleaseCliBannerProvider implements BannerProvider {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.plugin.NamedProvider#getProviderName()
|
||||
*/
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return "Spring Data Release Shell";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.plugin.BannerProvider#getBanner()
|
||||
*/
|
||||
@Override
|
||||
public String getBanner() {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append(FileUtils.readBanner(SpringDataReleaseCliBannerProvider.class, "banner.txt"));
|
||||
builder.append(getVersion()).append(IOUtils.LINE_SEPARATOR);
|
||||
builder.append(IOUtils.LINE_SEPARATOR);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.plugin.BannerProvider#getVersion()
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "1.0 on Spring Boot " + SpringBootVersion.getVersion();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.plugin.BannerProvider#getWelcomeMessage()
|
||||
*/
|
||||
@Override
|
||||
public String getWelcomeMessage() {
|
||||
return "Welcome to the Spring Data Release Shell!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.shell.plugin.PromptProvider;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
class SpringDataReleaseCliPromptProvider implements PromptProvider {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.plugin.PromptProvider#getPrompt()
|
||||
*/
|
||||
@Override
|
||||
public String getPrompt() {
|
||||
return "$ ";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.plugin.NamedProvider#getProviderName()
|
||||
*/
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return "spring-data-release-cli";
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
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.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.DocumentationMetadata;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class StaticResources {
|
||||
|
||||
private final DocumentationMetadata metadata;
|
||||
|
||||
private final String releaseUrl;
|
||||
|
||||
public StaticResources(ModuleIteration module) {
|
||||
|
||||
this.metadata = DocumentationMetadata.of(module.getProject(), ArtifactVersion.of(module), false);
|
||||
|
||||
Project project = module.getProject();
|
||||
GitProject gitProject = GitProject.of(project);
|
||||
Tag tag = VersionTags.empty(module.getProject()).createTag(module);
|
||||
|
||||
this.releaseUrl = String.format("%s/releases/tag/%s", gitProject.getProjectUri(), tag.getName());
|
||||
}
|
||||
|
||||
public String getDocumentationUrl() {
|
||||
return metadata.getReferenceDocUrl();
|
||||
}
|
||||
|
||||
public String getJavaDocUrl() {
|
||||
return metadata.getApiDocUrl();
|
||||
}
|
||||
|
||||
public String getChangelogUrl() {
|
||||
return releaseUrl;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.ReleaseTrains;
|
||||
import org.springframework.data.release.model.Train;
|
||||
import org.springframework.shell.core.Completion;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell.core.MethodTarget;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
public class TrainConverter implements Converter<Train> {
|
||||
|
||||
private static final Pattern CALVER = Pattern.compile("(\\d{4})(\\.(\\d))+");
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#supports(java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> type, String optionContext) {
|
||||
return Train.class.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#convertFromText(java.lang.String, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Train convertFromText(String value, Class<?> targetType, String optionContext) {
|
||||
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (CALVER.matcher(value).matches()) {
|
||||
|
||||
ArtifactVersion version = ArtifactVersion.of(value);
|
||||
return ReleaseTrains.getTrainByCalver(version.getVersion());
|
||||
}
|
||||
|
||||
return ReleaseTrains.getTrainByName(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#getAllPossibleValues(java.util.List, java.lang.Class, java.lang.String, java.lang.String, org.springframework.shell.core.MethodTarget)
|
||||
*/
|
||||
@Override
|
||||
public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData,
|
||||
String optionContext, MethodTarget target) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
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.Completion;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell.core.MethodTarget;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
public class TrainIterationConverter implements Converter<TrainIteration> {
|
||||
|
||||
private static final Pattern CALVER = Pattern.compile("(\\d{4})(\\.(\\d+))+(-M(\\d)|-RC(\\d))?");
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#supports(java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> type, String optionContext) {
|
||||
return TrainIteration.class.equals(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#convertFromText(java.lang.String, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public TrainIteration convertFromText(String value, Class<?> targetType, String optionContext) {
|
||||
|
||||
if (CALVER.matcher(value).matches()) {
|
||||
|
||||
ArtifactVersion version = ArtifactVersion.of(value);
|
||||
Train train = ReleaseTrains.getTrainByCalver(version.getVersion());
|
||||
|
||||
if (version.isReleaseVersion()) {
|
||||
if (version.isBugFixVersion()) {
|
||||
return train.getIteration("SR" + version.getVersion().getBugfix());
|
||||
}
|
||||
return train.getIteration(Iteration.GA);
|
||||
}
|
||||
|
||||
return train.getIteration(version.getSuffix());
|
||||
}
|
||||
|
||||
String[] parts = value.split(" ");
|
||||
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException(String.format("Cannot resolve TrainIteration from '%s'", value));
|
||||
}
|
||||
|
||||
Train train = ReleaseTrains.getTrainByName(parts[0].trim());
|
||||
|
||||
return train.getIteration(parts[1].trim());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.shell.core.Converter#getAllPossibleValues(java.util.List, java.lang.Class, java.lang.String, java.lang.String, org.springframework.shell.core.MethodTarget)
|
||||
*/
|
||||
@Override
|
||||
public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData,
|
||||
String optionContext, MethodTarget target) {
|
||||
|
||||
for (Train train : ReleaseTrains.TRAINS) {
|
||||
|
||||
for (Iteration iteration : train.getIterations()) {
|
||||
|
||||
TrainIteration trainIteration = train.getIteration(iteration.getName());
|
||||
|
||||
completions.add(new Completion(trainIteration.toString()));
|
||||
|
||||
if (trainIteration.getTrain().usesCalver()) {
|
||||
completions.add(new Completion(trainIteration.getCalver().toMajorMinorBugfix()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.cli;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.build.BuildOperations;
|
||||
import org.springframework.data.release.deployment.DeploymentOperations;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.issues.github.GitHub;
|
||||
import org.springframework.data.release.sagan.SaganClient;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
|
||||
/**
|
||||
* Commands to verify a correct Release Tools Setup.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class VerifyCommands extends TimedCommand {
|
||||
|
||||
@NonNull GitOperations git;
|
||||
@NonNull GitHub github;
|
||||
@NonNull DeploymentOperations deployment;
|
||||
@NonNull BuildOperations build;
|
||||
@NonNull SaganClient saganClient;
|
||||
@NonNull Logger logger;
|
||||
|
||||
@CliCommand("verify")
|
||||
public void verifyReleaseTools() {
|
||||
|
||||
// Git checkout build
|
||||
git.verify();
|
||||
|
||||
// Maven interaction
|
||||
build.verify();
|
||||
|
||||
// Artifactory verification
|
||||
deployment.verifyAuthentication();
|
||||
|
||||
// GitHub verification
|
||||
github.verifyAuthentication();
|
||||
|
||||
// Sagan Verification
|
||||
saganClient.verifyAuthentication();
|
||||
|
||||
logger.log("Verify", "All settings are verified. You can ship a release now.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.deployment;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@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(), properties.getStagingRepository()), String.class);
|
||||
} catch (HttpClientErrorException o_O) {
|
||||
handle(message -> logger.warn(information.getModule(), message), "Promotion failed!", o_O);
|
||||
}
|
||||
}
|
||||
|
||||
public void verify() {
|
||||
|
||||
URI verificationResource = properties.getServer().getVerificationResource();
|
||||
|
||||
try {
|
||||
|
||||
logger.log("Artifactory", "Verifying authentication using a GET call to %s.", verificationResource);
|
||||
|
||||
template.getForEntity(verificationResource, String.class);
|
||||
|
||||
logger.log("Artifactory", "Authentication verified!");
|
||||
|
||||
} catch (HttpClientErrorException o_O) {
|
||||
handle(message -> logger.log("Artifactory Client", message), "Authentication verification failed!", o_O);
|
||||
throw new IllegalStateException("Authentication verification failed!");
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(Consumer<Object> logger, String message, HttpClientErrorException o_O) {
|
||||
|
||||
try {
|
||||
|
||||
logger.accept(message);
|
||||
|
||||
Errors errors = new ObjectMapper().readValue(o_O.getResponseBodyAsByteArray(), Errors.class);
|
||||
errors.getErrors().forEach(logger);
|
||||
errors.getMessages().forEach(logger);
|
||||
|
||||
} catch (IOException e) {
|
||||
o_O.addSuppressed(e);
|
||||
throw new RuntimeException(o_O.getResponseBodyAsString(), o_O);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteArtifacts(DeploymentInformation information) {
|
||||
template.delete(properties.getServer().getDeleteBuildResource(information));
|
||||
}
|
||||
|
||||
@Value
|
||||
static class PromotionRequest {
|
||||
String targetRepo, sourceRepo;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.deployment;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
|
||||
/**
|
||||
* Commands to interact with Artifactory.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
class ArtifactoryCommands extends TimedCommand {
|
||||
|
||||
private final @NonNull DeploymentOperations deployment;
|
||||
|
||||
@CliCommand(value = "artifactory verify", help = "Verifies authentication at Artifactory.")
|
||||
public void verify() {
|
||||
deployment.verifyAuthentication();
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.deployment;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
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(access = AccessLevel.PACKAGE)
|
||||
public class DefaultDeploymentInformation implements 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;
|
||||
private final @Getter String buildNumber;
|
||||
|
||||
public DefaultDeploymentInformation(ModuleIteration module, DeploymentProperties properties) {
|
||||
this(module, properties, String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.deployment.DeploymentInformation#getBuildName()
|
||||
*/
|
||||
@Override
|
||||
public String getBuildName() {
|
||||
return module.getProject().getFullName().concat(" - Release");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.deployment.DeploymentInformation#getTargetRepository()
|
||||
*/
|
||||
@Override
|
||||
public String getTargetRepository() {
|
||||
return properties.getRepositoryPrefix()
|
||||
.concat(module.getIteration().isPublic() ? "libs-release-local" : "libs-milestone-local");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.deployment.DeploymentInformation#getDeploymentTargetUrl()
|
||||
*/
|
||||
@Override
|
||||
public String getDeploymentTargetUrl() {
|
||||
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("server", properties.getStagingRepositoryUrl());
|
||||
parameters.putAll(getBuildInfoParameters());
|
||||
|
||||
return REPOSITORY_TEMPLATE.expand(parameters).toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.deployment.DeploymentInformation#getBuildInfoParameters()
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getBuildInfoParameters() {
|
||||
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("buildNumber", getBuildNumber());
|
||||
parameters.put("buildName", getBuildName());
|
||||
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.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(proxyBeanMethods = false)
|
||||
class DeploymentConfiguration {
|
||||
|
||||
@Autowired DeploymentProperties properties;
|
||||
|
||||
@Bean
|
||||
public ArtifactoryClient client(Logger logger, RestTemplate artifactoryRestTemplate) {
|
||||
return new ArtifactoryClient(artifactoryRestTemplate, logger, properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestTemplate artifactoryRestTemplate() {
|
||||
|
||||
RestTemplate template = new RestTemplate();
|
||||
template.setInterceptors(Arrays.asList(new AuthenticatingClientHttpRequestInterceptor(properties)));
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class AuthenticatingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private final @NonNull DeploymentProperties properties;
|
||||
|
||||
/*
|
||||
* (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", properties.getApiKey());
|
||||
request.getHeaders().add("Authentication", properties.getCredentials().toString());
|
||||
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.deployment;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface DeploymentInformation {
|
||||
|
||||
/**
|
||||
* Returns the name of the build.
|
||||
*
|
||||
* @return will never be {@literal null} or empty.
|
||||
*/
|
||||
String getBuildName();
|
||||
|
||||
/**
|
||||
* Returns a unique build number for this particular deployment.
|
||||
*
|
||||
* @return will never be {@literal null} or empty.
|
||||
*/
|
||||
String getBuildNumber();
|
||||
|
||||
/**
|
||||
* Returns the full URL to be used as deployment target.
|
||||
*
|
||||
* @return will never be {@literal null} or empty.
|
||||
*/
|
||||
String getDeploymentTargetUrl();
|
||||
|
||||
/**
|
||||
* Returns the name of the repository to deploy to.
|
||||
*
|
||||
* @return will never be {@literal null} or empty.
|
||||
*/
|
||||
String getTargetRepository();
|
||||
|
||||
/**
|
||||
* Returns the {@link ModuleIteration} the deployment information was created for.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
ModuleIteration getModule();
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} to expand a URI template to access the build information.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Map<String, Object> getBuildInfoParameters();
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.deployment;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Deployment functionality.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DeploymentOperations {
|
||||
|
||||
private final ArtifactoryClient client;
|
||||
private final Logger logger;
|
||||
|
||||
public void verifyAuthentication() {
|
||||
client.verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!");
|
||||
|
||||
if (information.getModule().getIteration().isPublic()) {
|
||||
logger.log(information.getModule(),
|
||||
"Skipping build promotion as it's a public version and was staged to OSS Sonatype.");
|
||||
return;
|
||||
}
|
||||
|
||||
client.promote(information);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls back the given {@link DeploymentInformation}.
|
||||
*
|
||||
* @param information must not be {@literal null}.
|
||||
*/
|
||||
public void rollback(DeploymentInformation information) {
|
||||
|
||||
Assert.notNull(information, "DeploymentInformation must not be null!");
|
||||
|
||||
if (information.getModule().getIteration().isPublic()) {
|
||||
logger.log(information.getModule(),
|
||||
"Skipping build rollback as it's a public version and was deployed to Maven Central directly,");
|
||||
return;
|
||||
}
|
||||
|
||||
client.deleteArtifacts(information);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.deployment;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.data.release.model.Password;
|
||||
import org.springframework.data.release.utils.HttpBasicCredentials;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@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 Password password;
|
||||
|
||||
/**
|
||||
* The repository to deploy the artifacts to.
|
||||
*/
|
||||
private String stagingRepository;
|
||||
|
||||
/**
|
||||
* The repository to deploy docs/schemas to.
|
||||
*/
|
||||
private String distributionRepository;
|
||||
|
||||
private String repositoryPrefix = "";
|
||||
|
||||
public String getStagingRepository() {
|
||||
return repositoryPrefix.concat(stagingRepository);
|
||||
}
|
||||
|
||||
public String getDistributionRepository() {
|
||||
return repositoryPrefix.concat(distributionRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the staging repository.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getStagingRepositoryUrl() {
|
||||
return server.getUri().toString().concat("/").concat(stagingRepository);
|
||||
}
|
||||
|
||||
public HttpBasicCredentials getCredentials() {
|
||||
return new HttpBasicCredentials(username, password);
|
||||
}
|
||||
|
||||
@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 static final String VERIFICATION_RESOURCE = "/api/storage/temp-private-local";
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
public URI getVerificationResource() {
|
||||
return URI.create(uri.concat(VERIFICATION_RESOURCE));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.deployment;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Data
|
||||
public class Errors {
|
||||
|
||||
private List<Error> errors = new ArrayList<>();
|
||||
private List<Message> messages = new ArrayList<>();
|
||||
|
||||
public List<Error> getErrors(Errors this) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Error {
|
||||
|
||||
private String message;
|
||||
private int status;
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s - %s", status, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Message {
|
||||
|
||||
private String level, message;
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s - %s", level, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bouncycastle.util.Iterable;
|
||||
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Train;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
class BackportTargets implements Iterable<Branch> {
|
||||
|
||||
private final @Getter Branch source;
|
||||
private final Set<Branch> targets;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BackportTargets} instance for the given {@link ModuleIteration} and
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param targets must not be {@literal null}.
|
||||
*/
|
||||
public BackportTargets(ModuleIteration module, Collection<Train> targets) {
|
||||
|
||||
Assert.notNull(module, "Module iteration must not be null!");
|
||||
Assert.notNull(targets, "Target trains must not be null!");
|
||||
|
||||
this.source = Branch.from(module);
|
||||
|
||||
Stream<Branch> branches = targets.stream().map(target -> target.getModuleIfAvailable(module.getProject()))//
|
||||
.flatMap(o -> o.map(Stream::of).orElse(Stream.empty()))//
|
||||
.map(Branch::from);
|
||||
|
||||
this.targets = Stream.concat(branches, source.isMainBranch() ? Stream.empty() : Stream.of(Branch.MAIN))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Branch> iterator() {
|
||||
return targets.iterator();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.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.Tracker;
|
||||
import org.springframework.data.release.model.Version;
|
||||
import org.springframework.data.release.model.VersionAware;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value type to represent an SCM branch.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Branch implements Comparable<Branch> {
|
||||
|
||||
public static final Branch MAIN = new Branch("main");
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Branch} from the given {@link IterationVersion}.
|
||||
*
|
||||
* @param iterationVersion must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Branch from(IterationVersion iterationVersion) {
|
||||
|
||||
Assert.notNull(iterationVersion, "Iteration version must not be null!");
|
||||
|
||||
if (iterationVersion.isBranchVersion()) {
|
||||
return from((VersionAware) iterationVersion);
|
||||
}
|
||||
|
||||
return MAIN;
|
||||
}
|
||||
|
||||
public static Branch from(VersionAware versioned) {
|
||||
return from(versioned.getVersion());
|
||||
}
|
||||
|
||||
public static Branch from(Version version) {
|
||||
return from(version.toString().concat(".x"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Branch} from the given name. Uses the local part of it only.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static Branch from(String name) {
|
||||
|
||||
int slashIndex = name.lastIndexOf('/');
|
||||
|
||||
return new Branch(slashIndex != -1 ? name.substring(slashIndex + 1) : name);
|
||||
}
|
||||
|
||||
public boolean isMainBranch() {
|
||||
return MAIN.equals(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current branch is an issue branch for the given {@link Tracker}.
|
||||
*
|
||||
* @param tracker must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean isIssueBranch(Tracker tracker) {
|
||||
|
||||
Assert.notNull(tracker, "Tracker must not be null!");
|
||||
return name.matches(tracker.getTicketPattern());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Branch o) {
|
||||
return name.compareToIgnoreCase(o.name);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent a collection of {@link Branch}es.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class Branches implements Iterable<Branch> {
|
||||
|
||||
private final List<Branch> branches;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Branches} instance for the given {@link List} of {@link Branch}es.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
*/
|
||||
Branches(List<Branch> source) {
|
||||
|
||||
Assert.notNull(source, "Tags must not be null!");
|
||||
|
||||
this.branches = source.stream().//
|
||||
sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link Branch}es as {@link List}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Branch> asList() {
|
||||
return branches;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Branch> iterator() {
|
||||
return branches.iterator();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class Commit {
|
||||
|
||||
private final Ticket ticket;
|
||||
|
||||
@Getter
|
||||
private final String summary;
|
||||
private final Optional<String> details;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
|
||||
builder.append(summary);
|
||||
|
||||
if (!summary.endsWith(".")) {
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
details.ifPresent(it -> {
|
||||
builder.append("\n");
|
||||
builder.append("\n");
|
||||
builder.append(it);
|
||||
});
|
||||
|
||||
builder.append("\n\nSee ").append(ticket.getId());
|
||||
|
||||
return builder.toString();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.issues.Changelog;
|
||||
import org.springframework.data.release.issues.IssueTracker;
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
import org.springframework.data.release.issues.TicketReference;
|
||||
import org.springframework.data.release.issues.Tickets;
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
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.data.release.utils.ExecutionUtils;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.shell.support.table.Table;
|
||||
import org.springframework.shell.support.table.TableHeader;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@CliComponent
|
||||
@SuppressWarnings("deprecation")
|
||||
@RequiredArgsConstructor
|
||||
class GitCommands extends TimedCommand {
|
||||
|
||||
private final PluginRegistry<IssueTracker, Project> trackers;
|
||||
private final @NonNull GitOperations git;
|
||||
private final @NonNull Executor executor;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@CliCommand("git update")
|
||||
public void update(@CliOption(key = { "", "train" }, mandatory = true) String trainName)
|
||||
throws Exception, InterruptedException {
|
||||
git.update(ReleaseTrains.getTrainByName(trainName));
|
||||
}
|
||||
|
||||
@CliCommand("git tags")
|
||||
public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws Exception {
|
||||
|
||||
Project project = ReleaseTrains.getProjectByName(projectName);
|
||||
|
||||
return StringUtils.collectionToDelimitedString(git.getTags(project).asList(), "\n");
|
||||
}
|
||||
|
||||
@CliCommand("git previous")
|
||||
public String previous(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
|
||||
return git.getPreviousIteration(iteration).toString();
|
||||
}
|
||||
|
||||
@CliCommand("git changelog")
|
||||
public String changelog(@CliOption(key = "", mandatory = true) TrainIteration iteration,
|
||||
@CliOption(key = "module") String moduleName) {
|
||||
|
||||
TrainIteration previousIteration = git.getPreviousIteration(iteration);
|
||||
|
||||
if (StringUtils.hasText(moduleName)) {
|
||||
|
||||
ModuleIteration module = iteration.getModule(Projects.requiredByName(moduleName));
|
||||
List<TicketReference> ticketRefs = git.getTicketReferencesBetween(module.getProject(), previousIteration,
|
||||
iteration);
|
||||
|
||||
Changelog changelog = Changelog.of(module, toTickets(module, ticketRefs));
|
||||
return String.format("%s %s%n%s", module.getModule().getProject().getFullName(), ArtifactVersion.of(module),
|
||||
changelog.toString(false, " "));
|
||||
}
|
||||
|
||||
return ExecutionUtils
|
||||
.runAndReturn(executor, iteration, module -> changelog(iteration, module.getModule().getProject().getName())) //
|
||||
.stream() //
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private Tickets toTickets(ModuleIteration module, List<TicketReference> ticketReferences) {
|
||||
|
||||
IssueTracker issueTracker = trackers.getRequiredPluginFor(module.getProject(),
|
||||
() -> String.format("No issue tracker found for project %s!", module.getProject()));
|
||||
|
||||
List<String> ticketIds = ticketReferences.stream().map(TicketReference::getId).collect(Collectors.toList());
|
||||
|
||||
List<Ticket> tickets = new ArrayList<>(issueTracker.findTickets(module, ticketIds).getTickets());
|
||||
|
||||
return new Tickets(tickets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all projects contained in the given {@link Train}.
|
||||
*
|
||||
* @param iteration
|
||||
* @throws Exception
|
||||
*/
|
||||
@CliCommand("git reset")
|
||||
public void reset(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
|
||||
git.reset(iteration);
|
||||
}
|
||||
|
||||
@CliCommand("git prepare")
|
||||
public void prepare(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
|
||||
git.prepare(iteration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes all changes of all modules of the given {@link TrainIteration} to the remote server. If {@code tags} is
|
||||
* given, only the tags are pushed.
|
||||
*
|
||||
* @param iteration
|
||||
* @param tags
|
||||
* @throws Exception
|
||||
*/
|
||||
@CliCommand("git push")
|
||||
public void push(//
|
||||
@CliOption(key = "", mandatory = true) TrainIteration iteration, //
|
||||
@CliOption(key = "tags", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") String tags)
|
||||
throws Exception {
|
||||
|
||||
boolean pushTags = Boolean.parseBoolean(tags);
|
||||
|
||||
if (pushTags) {
|
||||
git.pushTags(iteration.getTrain());
|
||||
} else {
|
||||
git.push(iteration);
|
||||
}
|
||||
}
|
||||
|
||||
@CliCommand("git remove tags")
|
||||
public void removeTags(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
git.removeTags(iteration);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the branches with their tickets of the git repository.
|
||||
*
|
||||
* @param projectName
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@CliCommand("git issuebranches")
|
||||
public Table issuebranches(@CliOption(key = { "" }, mandatory = true) String projectName,
|
||||
@CliOption(key = "resolved", unspecifiedDefaultValue = "false", specifiedDefaultValue = "true") Boolean resolved)
|
||||
throws Exception {
|
||||
|
||||
Project project = ReleaseTrains.getProjectByName(projectName);
|
||||
TicketBranches ticketBranches = git.listTicketBranches(project);
|
||||
|
||||
Table table = new Table();
|
||||
table.addHeader(1, new TableHeader("Branch"));
|
||||
table.addHeader(2, new TableHeader("Status"));
|
||||
table.addHeader(3, new TableHeader("Description"));
|
||||
|
||||
ticketBranches.stream().sorted().//
|
||||
filter(branch -> ticketBranches.hasTicketFor(branch, resolved)).//
|
||||
forEachOrdered(branch -> {
|
||||
|
||||
Optional<Ticket> ticket = ticketBranches.findTicket(branch);
|
||||
|
||||
table.addRow(branch.toString(), //
|
||||
ticket.map(t -> t.getTicketStatus().getLabel()).orElse(""), //
|
||||
ticket.map(t -> t.getSummary()).orElse(""));
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public class GitProject {
|
||||
|
||||
private static final String PROJECT_PREFIX = "spring-data";
|
||||
|
||||
private final Project project;
|
||||
private final GitServer server;
|
||||
|
||||
public static GitProject of(Project project) {
|
||||
return new GitProject(project, GitServer.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the repository the project is using.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getRepositoryName() {
|
||||
return String.format("%s-%s", PROJECT_PREFIX,
|
||||
project == Projects.JDBC ? "relational" : project.getName().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the {@link Project}'s repository.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getProjectUri() {
|
||||
return server.getUri() + getRepositoryName();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.data.release.model.Password;
|
||||
import org.springframework.data.release.utils.HttpBasicCredentials;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configurable properties for Git.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "git")
|
||||
public class GitProperties {
|
||||
|
||||
private @Getter(AccessLevel.PRIVATE) Password password;
|
||||
private String username, author, email;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
|
||||
Assert.hasText(username, "No GitHub username (git.username) configured!");
|
||||
Assert.notNull(password, "No GitHub password (git.password) configured!");
|
||||
Assert.hasText(author, "No Git author (git.author) configured!");
|
||||
Assert.hasText(email, "No Git email (git.email) configured!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the jGit {@link CredentialsProvider} to be used.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CredentialsProvider getCredentials() {
|
||||
return new UsernamePasswordCredentialsProvider(username, password.toString());
|
||||
}
|
||||
|
||||
public HttpBasicCredentials getHttpCredentials() {
|
||||
return new HttpBasicCredentials(username, password);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class GitServer {
|
||||
|
||||
public static final GitServer INSTANCE = new GitServer();
|
||||
|
||||
private static final String SERVER_URI = "https://github.com/spring-projects/";
|
||||
|
||||
public String getUri() {
|
||||
return SERVER_URI;
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.release.issues.TicketReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Value object representing a parsed commit message. The {@link #parse(String)} method inspects a commit message to
|
||||
* extract a {@link TicketReference}, related tickets, a pull request reference and summary/body from the commit. Commit
|
||||
* messages may used {@code <ticket> - summary} syntax for Jira and GitHub tickets (gh- and # notation). This
|
||||
* parser also supports {@code Original pull request}, {@code Related ticket} and GitHub close keywords.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
class ParsedCommitMessage {
|
||||
|
||||
private static final Pattern JIRA_TICKET = Pattern.compile("(?>\\[)?([A-Z]+[ ]?-[ ]?\\d+)(?>\\])?");
|
||||
private static final Pattern GITHUB_TICKET = Pattern.compile("((?>#|gh-)\\d+)");
|
||||
|
||||
private static final Pattern GITHUB_CLOSE_SYNTAX = Pattern.compile(
|
||||
"(?>closes|closed|close|fixes|fixed|fix|resolves|resolved|resolve|see|related to)[\\s:]*((?>#|gh-)\\d+)",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
|
||||
|
||||
private static final Pattern GITHUB_PREFIX_SYNTAX = Pattern.compile("^(#\\d+)");
|
||||
|
||||
private static final Pattern A_TICKET = Pattern
|
||||
.compile(String.format("(%s|%s)", JIRA_TICKET.pattern(), GITHUB_TICKET.pattern()));
|
||||
|
||||
private static final Pattern ORIGINAL_PULL_REQUEST = Pattern
|
||||
.compile("Original (?>pull request|PR|pullrequest)[:]*(?>\\s+)?" + A_TICKET.pattern(), Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static final Pattern RELATED_TICKET = Pattern.compile(
|
||||
"Related (?>tickets|ticket)[:]*(?>\\s+)?((" + A_TICKET.pattern() + "(?>[\\s,]*))+)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final String summary;
|
||||
private final @Nullable String body;
|
||||
|
||||
private final TicketReference ticketReference;
|
||||
private final TicketReference pullRequestReference;
|
||||
private final List<TicketReference> relatedTickets;
|
||||
|
||||
private ParsedCommitMessage(String summary, @Nullable String body) {
|
||||
|
||||
this.summary = summary;
|
||||
this.body = body;
|
||||
|
||||
TicketReference ticketReference = null;
|
||||
TicketReference pullRequestReference = null;
|
||||
|
||||
// DATACASS-nnn - syntax
|
||||
Optional<TicketReference> jiraTicket = tryParseJiraTicketReference(summary);
|
||||
|
||||
if (jiraTicket.isPresent()) {
|
||||
ticketReference = jiraTicket.get();
|
||||
}
|
||||
|
||||
// Closes (gh-nnn|#nnn) syntax
|
||||
Matcher gitHubMatcher = GITHUB_CLOSE_SYNTAX.matcher(summary + "\n" + body);
|
||||
|
||||
// #nnn syntax
|
||||
Optional<TicketReference> gitHubTicket = tryParseGitHubTicketReference(summary);
|
||||
|
||||
if (gitHubTicket.isPresent()) {
|
||||
ticketReference = gitHubTicket.get();
|
||||
} else {
|
||||
if (gitHubMatcher.find()) {
|
||||
ticketReference = new TicketReference(gitHubMatcher.group(1), summary, TicketReference.Style.GitHub);
|
||||
}
|
||||
}
|
||||
|
||||
List<TicketReference> relatedTickets = parseRelatedTickets(body, gitHubMatcher);
|
||||
Optional<TicketReference> optionalOriginalPr = parsePullRequestReference(body);
|
||||
|
||||
if (optionalOriginalPr.isPresent()) {
|
||||
|
||||
pullRequestReference = optionalOriginalPr.get();
|
||||
|
||||
if (ticketReference == null) {
|
||||
ticketReference = pullRequestReference;
|
||||
pullRequestReference = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.ticketReference = ticketReference;
|
||||
this.pullRequestReference = pullRequestReference;
|
||||
this.relatedTickets = relatedTickets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a commit message into {@link ParsedCommitMessage}.
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public static ParsedCommitMessage parse(String message) {
|
||||
|
||||
int lineBreak = message.indexOf('\n');
|
||||
|
||||
String summary;
|
||||
String body;
|
||||
|
||||
if (lineBreak > -1) {
|
||||
summary = message.substring(0, lineBreak).trim();
|
||||
body = message.substring(lineBreak + 1).trim();
|
||||
} else {
|
||||
summary = message.trim();
|
||||
body = null;
|
||||
}
|
||||
|
||||
return new ParsedCommitMessage(summary, body);
|
||||
}
|
||||
|
||||
protected static Optional<TicketReference> tryParseGitHubTicketReference(String summary) {
|
||||
|
||||
Matcher gitHubPrefixMatcher = GITHUB_PREFIX_SYNTAX.matcher(summary);
|
||||
|
||||
if (gitHubPrefixMatcher.find()) {
|
||||
|
||||
MatchResult mr = gitHubPrefixMatcher.toMatchResult();
|
||||
if (mr.start(1) == 0) {
|
||||
int summaryStart = findSummaryIndex(summary, mr.end(1));
|
||||
|
||||
return Optional.of(new TicketReference(gitHubPrefixMatcher.group(1).toUpperCase(Locale.ROOT),
|
||||
summaryStart > -1 ? summary.substring(summaryStart) : summary, TicketReference.Style.GitHub));
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
protected static Optional<TicketReference> tryParseJiraTicketReference(String summary) {
|
||||
|
||||
Matcher jiraMatcher = JIRA_TICKET.matcher(summary);
|
||||
|
||||
if (jiraMatcher.find()) {
|
||||
|
||||
MatchResult mr = jiraMatcher.toMatchResult();
|
||||
|
||||
// allow […] syntax and start of message syntax
|
||||
if (mr.start(1) < 2) {
|
||||
int summaryStart = findSummaryIndex(summary, mr.end(1));
|
||||
|
||||
return Optional.of(new TicketReference(jiraMatcher.group(1).toUpperCase(Locale.ROOT),
|
||||
summaryStart > -1 ? summary.substring(summaryStart) : summary, TicketReference.Style.Jira));
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
protected static Optional<TicketReference> parsePullRequestReference(String body) {
|
||||
|
||||
if (body != null) {
|
||||
|
||||
Matcher prMatcher = ORIGINAL_PULL_REQUEST.matcher(body);
|
||||
|
||||
if (prMatcher.find()) {
|
||||
return extractTicket(prMatcher.group(1));
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
protected static List<TicketReference> parseRelatedTickets(String body, Matcher gitHubMatcher) {
|
||||
|
||||
List<TicketReference> relatedTickets = new ArrayList<>();
|
||||
if (body != null) {
|
||||
Matcher relatedTicketsMatcher = RELATED_TICKET.matcher(body);
|
||||
|
||||
if (relatedTicketsMatcher.find()) {
|
||||
|
||||
String ticketIds[] = relatedTicketsMatcher.group(1).split(",");
|
||||
|
||||
for (String ticketId : ticketIds) {
|
||||
extractTicket(ticketId).ifPresent(relatedTickets::add);
|
||||
}
|
||||
}
|
||||
|
||||
while (gitHubMatcher.find()) {
|
||||
extractTicket(gitHubMatcher.group(1)).ifPresent(relatedTickets::add);
|
||||
}
|
||||
}
|
||||
|
||||
return relatedTickets;
|
||||
}
|
||||
|
||||
protected static Optional<TicketReference> extractTicket(String ticketId) {
|
||||
|
||||
if (JIRA_TICKET.matcher(ticketId.trim()).matches()) {
|
||||
return Optional.of(new TicketReference(ticketId.trim(), null, TicketReference.Style.Jira));
|
||||
}
|
||||
|
||||
if (GITHUB_TICKET.matcher(ticketId.trim()).matches()) {
|
||||
return Optional.of(new TicketReference(ticketId.trim(), null, TicketReference.Style.GitHub));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static int findSummaryIndex(String summary, int startAt) {
|
||||
|
||||
int dash = summary.indexOf("- ", startAt);
|
||||
|
||||
if (dash > -1) {
|
||||
return dash + 2;
|
||||
}
|
||||
|
||||
int space = summary.indexOf(" ", startAt);
|
||||
|
||||
if (space > -1) {
|
||||
return space + 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
|
||||
/**
|
||||
* Value object to represent an SCM tag.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Getter
|
||||
public class Tag implements Comparable<Tag> {
|
||||
|
||||
private final String name;
|
||||
private final LocalDateTime creationDate;
|
||||
|
||||
public static Tag of(String source) {
|
||||
return of(source, LocalDateTime.now());
|
||||
}
|
||||
|
||||
public static Tag of(String source, LocalDateTime creationDate) {
|
||||
|
||||
int slashIndex = source.lastIndexOf('/');
|
||||
|
||||
return new Tag(source.substring(slashIndex == -1 ? 0 : slashIndex + 1), creationDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part of the name of the tag that is suitable to derive a version from the tag. Will transparently strip
|
||||
* a {@code v} prefix from the name.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getVersionSource() {
|
||||
return name.startsWith("v") ? name.substring(1) : name;
|
||||
}
|
||||
|
||||
public boolean isVersionTag() {
|
||||
return toArtifactVersion().isPresent();
|
||||
}
|
||||
|
||||
public Optional<ArtifactVersion> toArtifactVersion() {
|
||||
|
||||
try {
|
||||
return Optional.of(getRequiredArtifactVersion());
|
||||
} catch (IllegalArgumentException o_O) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public ArtifactVersion getRequiredArtifactVersion() {
|
||||
return ArtifactVersion.of(getVersionSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Tag} for the given {@link ArtifactVersion} based on the format of the current one.
|
||||
*
|
||||
* @param version
|
||||
* @return
|
||||
*/
|
||||
public Tag createNew(ArtifactVersion version) {
|
||||
return new Tag(name.startsWith("v") ? "v".concat(version.toString()) : version.toString(), LocalDateTime.now());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Tag that) {
|
||||
|
||||
// Prefer artifact versions but fall back to name comparison
|
||||
|
||||
return toArtifactVersion().map(left -> that.toArtifactVersion().map(right -> left.compareTo(right)).//
|
||||
orElse(name.compareTo(that.name))).orElse(name.compareTo(that.name));
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent a collection of {@link Branch}es with assigned tickets.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(staticName = "from")
|
||||
public class TicketBranches implements Streamable<Branch> {
|
||||
|
||||
private final @NonNull Map<Branch, Ticket> ticketBranches;
|
||||
|
||||
/**
|
||||
* Returns whether there's a ticket available for the given {@link Branch}. If {@code requireResolved} is set to
|
||||
* {@literal true} the answer will only be true if the available ticket is marked resolved.
|
||||
*
|
||||
* @param branch must not be {@literal null}.
|
||||
* @param requireResolved whether the {@link Ticket} we look for is required to be resolved.
|
||||
* @return
|
||||
*/
|
||||
public boolean hasTicketFor(Branch branch, boolean requireResolved) {
|
||||
|
||||
Assert.notNull(branch, "Branch must not be null!");
|
||||
|
||||
return findTicket(branch).//
|
||||
map(ticket -> requireResolved ? ticket.getTicketStatus().isResolved() : true).//
|
||||
orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TicketBranches} containing only the branches for which resolved {@link Ticket}s are found.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public TicketBranches getResolvedTickets() {
|
||||
|
||||
return new TicketBranches(ticketBranches.entrySet().stream().//
|
||||
filter(entry -> entry.getValue().getTicketStatus().isResolved()).//
|
||||
collect(Collectors.toMap(Entry::getKey, Entry::getValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ticket for the given {@link Branch}.
|
||||
*
|
||||
* @param branch must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Optional<Ticket> findTicket(Branch branch) {
|
||||
|
||||
Assert.notNull(branch, "Branch must not be null!");
|
||||
return Optional.ofNullable(ticketBranches.get(branch));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Branch> iterator() {
|
||||
return ticketBranches.keySet().iterator();
|
||||
}
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.git;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.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.data.util.Pair;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent a collection of {@link Tag}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class VersionTags implements Streamable<Tag> {
|
||||
|
||||
private final Project project;
|
||||
private final List<Tag> tags;
|
||||
|
||||
/**
|
||||
* Creates a new {@link VersionTags} instance for the given {@link Project} and {@link List} of {@link Tag}s.
|
||||
*
|
||||
* @param project must not be {@literal null}.
|
||||
* @param source must not be {@literal null}.
|
||||
*/
|
||||
VersionTags(Project project, Collection<Tag> source) {
|
||||
|
||||
Assert.notNull(project, "Project must not be null!");
|
||||
Assert.notNull(source, "Tags must not be null!");
|
||||
|
||||
this.project = project;
|
||||
this.tags = source.stream().//
|
||||
filter(Tag::isVersionTag).//
|
||||
sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty {@link VersionTags} object for {@link Project}.
|
||||
*
|
||||
* @param project must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static VersionTags empty(Project project) {
|
||||
return new VersionTags(project, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest {@link Tag}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Tag getLatest() {
|
||||
return tags.isEmpty() ? null : tags.get(tags.size() - 1);
|
||||
}
|
||||
|
||||
public Tag createTag(ModuleIteration iteration) {
|
||||
|
||||
if (iteration.getProject().equals(Projects.BOM)) {
|
||||
return Tag.of(iteration.getTrainIteration().getReleaseTrainNameAndVersion());
|
||||
}
|
||||
|
||||
Tag latest = getLatest();
|
||||
ArtifactVersion version = ArtifactVersion.of(iteration);
|
||||
|
||||
if (latest != null) {
|
||||
return latest.createNew(version);
|
||||
}
|
||||
|
||||
return Tag.of(version.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link Tag}s as {@link List}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Tag> asList() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Tag> iterator() {
|
||||
return tags.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link VersionTags} that contains only tags from the given {@link Train}.
|
||||
*
|
||||
* @param train
|
||||
* @return
|
||||
*/
|
||||
public VersionTags filter(Train train) {
|
||||
return filter((tag, ti) -> ti.getTrain().equals(train));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link VersionTags} from a filtered subset of this {@link VersionTags}.
|
||||
*
|
||||
* @param predicate
|
||||
* @return
|
||||
*/
|
||||
public VersionTags filter(BiPredicate<Tag, TrainIteration> predicate) {
|
||||
|
||||
Map<Tag, TrainIteration> iterations = newMap();
|
||||
|
||||
withIterations().forEach((tag, trainIteration) -> {
|
||||
|
||||
if (predicate.test(tag, trainIteration)) {
|
||||
iterations.put(tag, trainIteration);
|
||||
}
|
||||
});
|
||||
|
||||
return new VersionTags(project, iterations.keySet());
|
||||
}
|
||||
|
||||
Map<Tag, TrainIteration> withIterations() {
|
||||
|
||||
Map<Tag, TrainIteration> iterations = newMap();
|
||||
|
||||
for (Tag tag : tags) {
|
||||
|
||||
Optional<ArtifactVersion> artifactVersion = tag.toArtifactVersion();
|
||||
if (!artifactVersion.isPresent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArtifactVersion version = artifactVersion.get();
|
||||
Optional<Iteration> iteration = toIteration(version);
|
||||
|
||||
if (!iteration.isPresent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Optional<TrainIteration> ti = getTrainIteration(version, project, iteration.get());
|
||||
|
||||
ti.ifPresent(it -> iterations.put(tag, it));
|
||||
}
|
||||
|
||||
return iterations;
|
||||
}
|
||||
|
||||
public Optional<TrainIteration> findMostRecentTrainIterationBefore(Iteration iterationToFind) {
|
||||
return find((tag, iteration) -> iteration.getIteration().compareTo(iterationToFind) < 0, Pair::getSecond);
|
||||
}
|
||||
|
||||
public Optional<Tag> findTag(Iteration iterationToFind) {
|
||||
return find((tag, iteration) -> iteration.getIteration().compareTo(iterationToFind) == 0, Pair::getFirst);
|
||||
}
|
||||
|
||||
public <T> Optional<T> find(BiPredicate<Tag, TrainIteration> filter,
|
||||
Function<Pair<Tag, TrainIteration>, T> resultExtractor) {
|
||||
|
||||
for (Map.Entry<Tag, TrainIteration> entry : withIterations().entrySet()) {
|
||||
|
||||
if (filter.test(entry.getKey(), entry.getValue())) {
|
||||
return Optional.of(resultExtractor.apply(Pair.of(entry.getKey(), entry.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static <K, V> TreeMap<K, V> newMap() {
|
||||
return new TreeMap(Comparator.reverseOrder());
|
||||
}
|
||||
|
||||
private static Optional<TrainIteration> getTrainIteration(ArtifactVersion version, Project project,
|
||||
Iteration iteration) {
|
||||
|
||||
// consider major version bump during ReleaseTrain
|
||||
boolean minorIncrementedButTargetsNextTrain = false;
|
||||
|
||||
for (Train train : ReleaseTrains.trains()) {
|
||||
|
||||
TrainIteration ti = new TrainIteration(train, iteration);
|
||||
|
||||
if (!ti.contains(project)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (minorIncrementedButTargetsNextTrain) {
|
||||
return Optional.of(ti);
|
||||
}
|
||||
|
||||
ModuleIteration mi = ti.getModule(project);
|
||||
|
||||
ArtifactVersion artifactVersion = ArtifactVersion.of(mi);
|
||||
|
||||
if (artifactVersion.equals(version)) {
|
||||
return Optional.of(ti);
|
||||
}
|
||||
|
||||
if (artifactVersion.getNextMinorVersion().equals(version)) {
|
||||
minorIncrementedButTargetsNextTrain = true;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<Iteration> toIteration(ArtifactVersion version) {
|
||||
|
||||
if (version.isMilestoneVersion()) {
|
||||
return Optional.of(Iteration.valueOf("M" + version.getLevel()));
|
||||
}
|
||||
|
||||
if (version.isReleaseCandidateVersion()) {
|
||||
return Optional.of(Iteration.valueOf("RC" + version.getLevel()));
|
||||
}
|
||||
|
||||
if (version.isBugFixVersion()) {
|
||||
return Optional.of(Iteration.valueOf("SR" + version.getLevel()));
|
||||
}
|
||||
|
||||
if (version.isReleaseVersion()) {
|
||||
return Optional.of(Iteration.GA);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class Dependencies {
|
||||
|
||||
static final List<Dependency> dependencies = new ArrayList<>();
|
||||
|
||||
public static final Dependency APT = Dependency.of("APT", "com.mysema.maven:apt-maven-plugin");
|
||||
|
||||
public static final Dependency ASPECTJ = Dependency.of("AspectJ", "org.aspectj:aspectjrt");
|
||||
|
||||
public static final Dependency ASSERTJ = Dependency.of("AssertJ", "org.assertj:assertj-core");
|
||||
|
||||
public static final Dependency JACKSON = Dependency.of("Jackson", "com.fasterxml.jackson:jackson-bom");
|
||||
|
||||
public static final Dependency JACOCO = Dependency.of("Jacoco", "org.jacoco:jacoco");
|
||||
|
||||
public static final Dependency JODA_TIME = Dependency.of("Joda Time", "joda-time:joda-time");
|
||||
|
||||
public static final Dependency JUNIT5 = Dependency.of("JUnit", "org.junit:junit-bom");
|
||||
|
||||
public static final Dependency JUNIT4 = Dependency.of("JUnit", "junit:junit");
|
||||
|
||||
public static final Dependency KOTLIN = Dependency.of("Kotlin", "org.jetbrains.kotlin:kotlin-bom");
|
||||
|
||||
public static final Dependency KOTLIN_COROUTINES = Dependency.of("Kotlin Coroutines",
|
||||
"org.jetbrains.kotlinx:kotlinx-coroutines-bom");
|
||||
|
||||
public static final Dependency MICROMETER = Dependency.of("Micrometer", "io.micrometer:micrometer-bom");
|
||||
|
||||
public static final Dependency MICROMETER_TRACING = Dependency.of("Micrometer Tracing",
|
||||
"io.micrometer:micrometer-tracing-bom");
|
||||
|
||||
public static final Dependency MOCKITO = Dependency.of("Mockito", "org.mockito:mockito-core");
|
||||
|
||||
public static final Dependency MOCKK = Dependency.of("Mockk", "io.mockk:mockk");
|
||||
|
||||
public static final Dependency QUERYDSL = Dependency.of("Querydsl", "com.querydsl:querydsl-bom");
|
||||
|
||||
public static final Dependency RXJAVA1 = Dependency.of("RxJava", "io.reactivex:rxjava");
|
||||
|
||||
public static final Dependency RXJAVA2 = Dependency.of("RxJava", "io.reactivex.rxjava2:rxjava");
|
||||
|
||||
public static final Dependency RXJAVA3 = Dependency.of("RxJava", "io.reactivex.rxjava3:rxjava");
|
||||
|
||||
public static final Dependency RXJAVA_RS = Dependency.of("RxJava Reactive Streams",
|
||||
"io.reactivex:rxjava-reactive-streams");
|
||||
|
||||
public static final Dependency SPRING_HATEOAS = Dependency.of("Spring Hateoas",
|
||||
"org.springframework.hateoas:spring-hateoas");
|
||||
|
||||
public static final Dependency SPRING_PLUGIN = Dependency.of("Spring Plugin",
|
||||
"org.springframework.plugin:spring-plugin");
|
||||
|
||||
public static final Dependency TESTCONTAINERS = Dependency.of("Testcontainers", "org.testcontainers:testcontainers");
|
||||
|
||||
public static final Dependency THREE_TEN_BP = Dependency.of("ThreeTenBp", "org.threeten:threetenbp");
|
||||
|
||||
public static final Dependency OPEN_WEB_BEANS = Dependency.of("OpenWebBeans", "org.apache.openwebbeans:openwebbeans");
|
||||
|
||||
public static final Dependency SMALLRYE_MUTINY = Dependency.of("Smallrye Mutiny", "io.smallrye.reactive:mutiny");
|
||||
|
||||
public static final Dependency VAVR = Dependency.of("Vavr", "io.vavr:vavr").excludeVersionStartingWith("1.0.0-alpha");
|
||||
|
||||
public static final Dependency XML_BEAM = Dependency.of("XMLBeam", "org.xmlbeam:xmlprojector");
|
||||
|
||||
public static final Dependency MONGODB_CORE = Dependency.of("MongoDB", "org.mongodb:mongodb-driver-core");
|
||||
|
||||
public static final Dependency MONGODB_LEGACY = Dependency.of("MongoDB", "org.mongodb:mongo-java-driver");
|
||||
|
||||
public static final Dependency MONGODB_SYNC = Dependency.of("MongoDB", "org.mongodb:mongodb-driver-sync");
|
||||
|
||||
public static final Dependency MONGODB_ASYNC = Dependency.of("MongoDB", "org.mongodb:mongodb-driver-async");
|
||||
|
||||
public static final Dependency MONGODB_RS = Dependency.of("MongoDB Reactive Streams",
|
||||
"org.mongodb:mongodb-driver-reactivestreams");
|
||||
|
||||
public static final Dependency LETTUCE = Dependency.of("Lettuce", "io.lettuce:lettuce-core");
|
||||
|
||||
public static final Dependency JEDIS = Dependency.of("Jedis", "redis.clients:jedis");
|
||||
|
||||
public static final Dependency JMOLECULES = Dependency.of("JMolecules", "org.jmolecules:jmolecules");
|
||||
|
||||
public static final Dependency JMOLECULES_INTEGRATION = Dependency.of("JMolecules",
|
||||
"org.jmolecules.integrations:jmolecules-spring");
|
||||
|
||||
public static final Dependency CASSANDRA_DRIVER3 = Dependency.of("Cassandra Driver",
|
||||
"com.datastax.cassandra:cassandra-driver-core");
|
||||
|
||||
public static final Dependency CASSANDRA_DRIVER4 = Dependency.of("Cassandra Driver",
|
||||
"com.datastax.oss:java-driver-bom");
|
||||
|
||||
public static final Dependency NEO4J_OGM = Dependency.of("Neo4j OGM", "org.neo4j:neo4j-ogm-api");
|
||||
|
||||
public static final Dependency NEO4J_DRIVER = Dependency.of("Neo4j Driver", "org.neo4j.driver:neo4j-java-driver");
|
||||
|
||||
public static final Dependency COUCHBASE = Dependency.of("Couchbase Client", "com.couchbase.client:java-client");
|
||||
|
||||
public static final Dependency ELASTICSEARCH_RHLC = Dependency.of("Elasticsearch",
|
||||
"org.elasticsearch.client:elasticsearch-rest-high-level-client");
|
||||
|
||||
public static final Dependency ELASTICSEARCH_REST_CLIENT = Dependency.of("Elasticsearch REST Client",
|
||||
"org.elasticsearch.client:elasticsearch-rest-client");
|
||||
|
||||
public static final Dependency SPRING_LDAP = Dependency.of("Spring LDAP",
|
||||
"org.springframework.ldap:spring-ldap-core");
|
||||
|
||||
public static final Dependency MAVEN = Dependency.of("Maven Wrapper", "org.apache.maven:apache-maven");
|
||||
|
||||
static {
|
||||
|
||||
ReflectionUtils.doWithFields(Dependencies.class, field -> {
|
||||
|
||||
// ignore dependencies constant
|
||||
if (field.getName().toLowerCase().equals(field.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
dependencies.add((Dependency) ReflectionUtils.getField(field, null));
|
||||
});
|
||||
}
|
||||
|
||||
public static Dependency getRequiredByName(String name) {
|
||||
|
||||
return dependencies.stream().filter(it -> it.getName().equals(name)).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("No such dependency: %s", name)));
|
||||
}
|
||||
|
||||
public static Dependency getRequiredDepependency(String groupId, String artifactId) {
|
||||
|
||||
return dependencies.stream().filter(it -> it.getGroupId().equals(groupId) && it.getArtifactId().equals(artifactId))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("No such dependency: %s", artifactId)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
public class Dependency implements Comparable<Dependency> {
|
||||
|
||||
String name;
|
||||
String groupId, artifactId;
|
||||
Predicate<String> exclusions;
|
||||
|
||||
public static Dependency of(String name, String ga) {
|
||||
|
||||
Assert.hasText(name, "Name must not be empty");
|
||||
Assert.hasText(ga, "GroupId/ArtifactId must not be empty");
|
||||
Assert.isTrue(ga.indexOf(':') != -1, "GroupId/ArtifactId must be in the format of org.group:artifact-id");
|
||||
|
||||
String[] parts = ga.split(":");
|
||||
|
||||
return new Dependency(name, parts[0], parts[1], it -> false);
|
||||
}
|
||||
|
||||
public Dependency excludeVersionStartingWith(String identifier) {
|
||||
return new Dependency(name, groupId, artifactId, it -> it.startsWith(identifier) || exclusions.test(it));
|
||||
}
|
||||
|
||||
public boolean shouldInclude(String identifier) {
|
||||
return !exclusions.test(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Dependency o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getGroupId() + ":" + getArtifactId();
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.issues.Tickets;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.shell.support.table.Table;
|
||||
|
||||
/**
|
||||
* Shell commands for dependency management.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class DependencyCommands extends TimedCommand {
|
||||
|
||||
public static final String BUILD_PROPERTIES = "dependency-upgrade-build.properties";
|
||||
|
||||
DependencyOperations operations;
|
||||
GitOperations git;
|
||||
Logger logger;
|
||||
|
||||
@CliCommand(value = "dependency check")
|
||||
public void check(@CliOption(key = "", mandatory = true) TrainIteration iteration,
|
||||
@CliOption(key = "all", mandatory = false) Boolean reportAll) throws IOException {
|
||||
|
||||
git.prepare(iteration);
|
||||
|
||||
checkBuildDependencies(iteration, reportAll != null ? reportAll : false);
|
||||
checkModuleDependencies(iteration, reportAll != null ? reportAll : false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a dependency report for all store modules to be used typically in Spring Boot upgrade tickets.
|
||||
*
|
||||
* @param iteration
|
||||
* @return
|
||||
*/
|
||||
@CliCommand(value = "dependency report")
|
||||
public String report(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
git.prepare(iteration);
|
||||
|
||||
List<Project> projects = Projects.all().stream()
|
||||
.filter(it -> it != Projects.BOM && it != Projects.BUILD && it != Projects.COMMONS)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<Dependency, DependencyVersion> dependencies = new TreeMap<>();
|
||||
|
||||
for (Project project : projects) {
|
||||
operations.getCurrentDependencies(project).forEach(dependencies::put);
|
||||
}
|
||||
|
||||
StringBuilder report = new StringBuilder();
|
||||
|
||||
report.append(System.lineSeparator()).append("Project Dependencies Spring Data ")
|
||||
.append(iteration.getReleaseTrainNameAndVersion()).append(System.lineSeparator())
|
||||
.append(System.lineSeparator());
|
||||
|
||||
dependencies.forEach((dependency, dependencyVersion) -> {
|
||||
|
||||
report.append(String.format("* %s (%s:%s): %s", dependency.getName(), dependency.getGroupId(),
|
||||
dependency.getArtifactId(), dependencyVersion.getIdentifier())).append(System.lineSeparator());
|
||||
});
|
||||
|
||||
return report.toString();
|
||||
}
|
||||
|
||||
@CliCommand(value = "dependency upgrade")
|
||||
public void upgrade(@CliOption(key = "", mandatory = true) TrainIteration iteration)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
logger.log(iteration, "Applying dependency upgrades to Spring Data Build");
|
||||
|
||||
ModuleIteration module = iteration.getModule(Projects.BUILD);
|
||||
DependencyVersions dependencyVersions = loadDependencyUpgrades(module);
|
||||
|
||||
DependencyVersions upgradesToApply = operations.getDependencyUpgradesToApply(module.getProject(),
|
||||
dependencyVersions);
|
||||
|
||||
if (upgradesToApply.isEmpty()) {
|
||||
logger.log(module, "No dependency upgrades to apply");
|
||||
return;
|
||||
}
|
||||
|
||||
Tickets tickets = operations.getOrCreateUpgradeTickets(module, upgradesToApply);
|
||||
operations.upgradeDependencies(tickets, module, dependencyVersions);
|
||||
|
||||
git.push(module);
|
||||
|
||||
// Allow GitHub to catch up with ticket notifications.
|
||||
Thread.sleep(1500);
|
||||
|
||||
operations.closeUpgradeTickets(module, tickets);
|
||||
}
|
||||
|
||||
private DependencyVersions loadDependencyUpgrades(ModuleIteration iteration)
|
||||
throws IOException {
|
||||
|
||||
if (!Files.exists(Paths.get(BUILD_PROPERTIES))) {
|
||||
logger.log(iteration, "Cannot upgrade dependencies: " + BUILD_PROPERTIES + " does not exist.");
|
||||
}
|
||||
|
||||
Properties properties = new Properties();
|
||||
try (FileInputStream fis = new FileInputStream(BUILD_PROPERTIES)) {
|
||||
properties.load(fis);
|
||||
}
|
||||
|
||||
return DependencyUpgradeProposals.fromProperties(iteration.getTrainIteration(), properties);
|
||||
}
|
||||
|
||||
private void checkModuleDependencies(TrainIteration iteration, boolean reportAll) throws IOException {
|
||||
|
||||
String propertiesFile = "dependency-upgrade-modules.properties";
|
||||
|
||||
List<Project> projects = Projects.all().stream().filter(it -> it != Projects.BOM && it != Projects.BUILD)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyUpgradeProposals proposals = DependencyUpgradeProposals.empty();
|
||||
|
||||
for (Project project : projects) {
|
||||
proposals = proposals.mergeWith(operations.getDependencyUpgradeProposals(project, iteration.getIteration()));
|
||||
}
|
||||
|
||||
Files.write(Paths.get(propertiesFile), proposals.asProperties(iteration).getBytes());
|
||||
|
||||
Table summary = proposals.toTable(reportAll);
|
||||
|
||||
logger.log(iteration, "Upgrade summary:" + System.lineSeparator() + System.lineSeparator() + summary);
|
||||
logger.log(iteration, "Upgrade proposals written to " + propertiesFile);
|
||||
}
|
||||
|
||||
private void checkBuildDependencies(TrainIteration iteration, boolean reportAll) throws IOException {
|
||||
|
||||
String propertiesFile = BUILD_PROPERTIES;
|
||||
|
||||
DependencyUpgradeProposals proposals = operations.getDependencyUpgradeProposals(Projects.BUILD,
|
||||
iteration.getIteration());
|
||||
|
||||
Files.write(Paths.get(propertiesFile), proposals.asProperties(iteration).getBytes());
|
||||
|
||||
Table summary = proposals.toTable(reportAll);
|
||||
|
||||
logger.log(Projects.BUILD, "Upgrade summary:" + System.lineSeparator() + System.lineSeparator() + summary);
|
||||
logger.log(iteration, "Upgrade proposals written to " + propertiesFile);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,597 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.release.build.Pom;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.io.Workspace;
|
||||
import org.springframework.data.release.issues.IssueTracker;
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
import org.springframework.data.release.issues.TicketOperations;
|
||||
import org.springframework.data.release.issues.Tickets;
|
||||
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.Projects;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.ExecutionUtils;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import org.xmlbeam.ProjectionFactory;
|
||||
import org.xmlbeam.annotation.XBRead;
|
||||
import org.xmlbeam.io.XBFileIO;
|
||||
import org.xmlbeam.io.XBStreamInput;
|
||||
|
||||
/**
|
||||
* Operations for dependency management.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class DependencyOperations {
|
||||
|
||||
public static final Pattern REPO_MAVEN_ORG_DIR_LISTING = Pattern
|
||||
.compile("<a (?>[^>]+)>([^\\/]+)\\/<\\/a>(?>\\s*)(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})(?>\\s*)(?>-)");
|
||||
|
||||
public static final DateTimeFormatter DIR_LISTING_TIME_FORMAT = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm");
|
||||
|
||||
private final ProjectionFactory projectionFactory;
|
||||
private final GitOperations gitOperations;
|
||||
private final Workspace workspace;
|
||||
private final TicketOperations tickets;
|
||||
private final ExecutorService executor;
|
||||
private final RestOperations restOperations;
|
||||
private final Logger logger;
|
||||
|
||||
/**
|
||||
* Obtain dependency upgrade proposals for {@link Project} and {@link Iteration}. Considers dependency upgrade rules
|
||||
* according to minor/bugfix release increments. Also, SemVer with modifier are only allowed in milestone versions.
|
||||
*
|
||||
* @param project
|
||||
* @param iteration
|
||||
* @return
|
||||
*/
|
||||
public DependencyUpgradeProposals getDependencyUpgradeProposals(Project project, Iteration iteration) {
|
||||
|
||||
DependencyVersions currentDependencies = getCurrentDependencies(project);
|
||||
Map<Dependency, DependencyUpgradeProposal> proposals = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||
|
||||
ExecutionUtils.run(executor, Streamable.of(currentDependencies.getDependencies()), dependency -> {
|
||||
|
||||
DependencyVersion currentVersion = currentDependencies.get(dependency);
|
||||
List<DependencyVersion> versions = getAvailableVersions(dependency);
|
||||
DependencyUpgradeProposal proposal = getDependencyUpgradeProposal(DependencyUpgradePolicy.from(iteration),
|
||||
currentVersion, versions);
|
||||
|
||||
proposals.put(dependency, proposal);
|
||||
});
|
||||
|
||||
return new DependencyUpgradeProposals(proposals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain dependency upgrade proposals for Maven Wrapper.
|
||||
*
|
||||
* @param project
|
||||
* @param iteration
|
||||
* @return
|
||||
*/
|
||||
public DependencyUpgradeProposals getMavenWrapperDependencyUpgradeProposals(TrainIteration iteration) {
|
||||
|
||||
for (ModuleIteration moduleIteration : iteration) {
|
||||
|
||||
// ensure we have Maven Wrapper for each project.
|
||||
getMavenWrapperVersion(moduleIteration.getProject());
|
||||
}
|
||||
|
||||
return getDependencyUpgradeProposals(Projects.BUILD, DependencyUpgradePolicy.LATEST_STABLE, Dependencies.MAVEN,
|
||||
this::getMavenWrapperVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the upgrade and creates a commit.
|
||||
*
|
||||
* @param tickets
|
||||
* @param module
|
||||
* @param dependencyVersions
|
||||
*/
|
||||
public Tickets upgradeMavenWrapperVersion(Tickets tickets, ModuleIteration module,
|
||||
DependencyVersions dependencyVersions) {
|
||||
|
||||
if (dependencyVersions.isEmpty()) {
|
||||
logger.log(module, "No dependency upgrades to apply");
|
||||
}
|
||||
|
||||
return doWithDependencyVersionsAndCommit(tickets, module, dependencyVersions, (dependency, version) -> {
|
||||
upgradeMavenWrapperVersion(module.getProject(), version);
|
||||
});
|
||||
}
|
||||
|
||||
public List<Project> getProjectsToUpgradeMavenWrapper(DependencyVersion targetVersion, TrainIteration iteration) {
|
||||
|
||||
List<Project> projectsToUpgrade = new ArrayList<>();
|
||||
|
||||
for (ModuleIteration moduleIteration : iteration) {
|
||||
|
||||
DependencyVersion currentVersion = getMavenWrapperVersion(moduleIteration.getProject());
|
||||
|
||||
if (targetVersion.isNewer(currentVersion)) {
|
||||
projectsToUpgrade.add(moduleIteration.getProject());
|
||||
}
|
||||
}
|
||||
|
||||
return projectsToUpgrade;
|
||||
}
|
||||
|
||||
private DependencyVersion getMavenWrapperVersion(Project project) {
|
||||
|
||||
try {
|
||||
|
||||
File file = getMavenWrapperProperties(project);
|
||||
String distributionUrl;
|
||||
|
||||
try (FileInputStream fileInputStream = new FileInputStream(file)) {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.load(fileInputStream);
|
||||
|
||||
distributionUrl = properties.getProperty("distributionUrl");
|
||||
|
||||
}
|
||||
|
||||
Pattern versionPattern = Pattern.compile(".*/maven2/org/apache/maven/apache-maven/([\\d\\.]+)/.*");
|
||||
|
||||
Matcher matcher = versionPattern.matcher(distributionUrl);
|
||||
if (!matcher.find()) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Invalid distribution URL in %s: %s", project.getName(), distributionUrl));
|
||||
}
|
||||
|
||||
return DependencyVersion.of(matcher.group(1));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeMavenWrapperVersion(Project project, DependencyVersion dependencyVersion) {
|
||||
|
||||
String distributionUrlTemplate = "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/%s/apache-maven-%s-bin.zip";
|
||||
|
||||
try {
|
||||
|
||||
File file = getMavenWrapperProperties(project);
|
||||
Properties properties = new Properties();
|
||||
|
||||
try (FileInputStream is = new FileInputStream(file)) {
|
||||
properties.load(is);
|
||||
}
|
||||
|
||||
properties.setProperty("distributionUrl",
|
||||
String.format(distributionUrlTemplate, dependencyVersion.getIdentifier(), dependencyVersion.getIdentifier()));
|
||||
|
||||
try (FileOutputStream os = new FileOutputStream(file)) {
|
||||
properties.store(os, null);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private File getMavenWrapperProperties(Project project) throws FileNotFoundException {
|
||||
File file = workspace.getFile(".mvn/wrapper/maven-wrapper.properties", project);
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException(file.toString());
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public DependencyUpgradeProposals getDependencyUpgradeProposals(Project project, DependencyUpgradePolicy policy,
|
||||
Dependency dependency, Function<Project, DependencyVersion> currentVersionExtractor) {
|
||||
|
||||
DependencyVersions currentDependencies = new DependencyVersions(
|
||||
Collections.singletonMap(dependency, currentVersionExtractor.apply(project)));
|
||||
Map<Dependency, DependencyUpgradeProposal> proposals = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||
|
||||
DependencyVersion currentVersion = currentDependencies.get(dependency);
|
||||
List<DependencyVersion> versions = getAvailableVersions(dependency);
|
||||
DependencyUpgradeProposal proposal = getDependencyUpgradeProposal(policy, currentVersion, versions);
|
||||
|
||||
proposals.put(dependency, proposal);
|
||||
|
||||
return new DependencyUpgradeProposals(proposals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures there's a upgrade ticket for each dependency to upgrade.
|
||||
*
|
||||
* @param module
|
||||
* @param dependencyVersions
|
||||
* @return
|
||||
*/
|
||||
public Tickets getOrCreateUpgradeTickets(ModuleIteration module, DependencyVersions dependencyVersions) {
|
||||
|
||||
List<String> summaries = new ArrayList<>();
|
||||
dependencyVersions.forEach((dependency, dependencyVersion) -> {
|
||||
summaries.add(getUpgradeTicketSummary(dependency, dependencyVersion));
|
||||
});
|
||||
|
||||
return tickets.getOrCreateTicketsWithSummary(module, IssueTracker.TicketType.DependencyUpgrade, summaries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the upgrade, creates a commit.
|
||||
*
|
||||
* @param tickets
|
||||
* @param module
|
||||
* @param dependencyVersions
|
||||
*/
|
||||
public Tickets upgradeDependencies(Tickets tickets, ModuleIteration module, DependencyVersions dependencyVersions) {
|
||||
|
||||
Project project = module.getProject();
|
||||
ProjectDependencies dependencies = ProjectDependencies.get(project);
|
||||
|
||||
if (dependencyVersions.isEmpty()) {
|
||||
logger.log(module, "No dependency upgrades to apply");
|
||||
}
|
||||
|
||||
return doWithDependencyVersionsAndCommit(tickets, module, dependencyVersions, (dependency, version) -> {
|
||||
|
||||
String versionProperty = dependencies.getVersionPropertyFor(dependency);
|
||||
File pom = getPomFile(project);
|
||||
update(pom, Pom.class, it -> {
|
||||
it.setProperty(versionProperty, version.getIdentifier());
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies dependencies to upgrade, applies the upgrade, creates a commit.
|
||||
*
|
||||
* @param tickets
|
||||
* @param module
|
||||
* @param dependencyVersions
|
||||
*/
|
||||
private Tickets doWithDependencyVersionsAndCommit(Tickets tickets, ModuleIteration module,
|
||||
DependencyVersions dependencyVersions, BiConsumer<Dependency, DependencyVersion> action) {
|
||||
|
||||
if (dependencyVersions.isEmpty()) {
|
||||
logger.log(module, "No dependency upgrades to apply");
|
||||
}
|
||||
|
||||
List<Ticket> ticketsToClose = new ArrayList<>();
|
||||
|
||||
dependencyVersions.forEach((dependency, dependencyVersion) -> {
|
||||
|
||||
String upgradeTicketSummary = getUpgradeTicketSummary(dependency, dependencyVersion);
|
||||
Ticket upgradeTicket = getDependencyUpgradeTicket(tickets, upgradeTicketSummary).get();
|
||||
|
||||
action.accept(dependency, dependencyVersion);
|
||||
|
||||
gitOperations.commit(module, upgradeTicket, upgradeTicketSummary, Optional.empty(), true);
|
||||
|
||||
ticketsToClose.add(upgradeTicket);
|
||||
});
|
||||
|
||||
return new Tickets(ticketsToClose);
|
||||
}
|
||||
|
||||
public void closeUpgradeTickets(ModuleIteration module, Tickets tickets) {
|
||||
this.tickets.closeTickets(module, tickets);
|
||||
}
|
||||
|
||||
public DependencyVersions getDependencyUpgradesToApply(Project project, DependencyVersions dependencyVersions) {
|
||||
|
||||
DependencyVersions currentDependencies = getCurrentDependencies(project);
|
||||
Map<Dependency, DependencyVersion> upgrades = new LinkedHashMap<>();
|
||||
|
||||
currentDependencies.forEach((dependency, dependencyVersion) -> {
|
||||
|
||||
if (!dependencyVersions.hasDependency(dependency)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DependencyVersion upgradeVersion = dependencyVersions.get(dependency);
|
||||
|
||||
if (upgradeVersion.equals(dependencyVersion)) {
|
||||
logger.log(project, "Skipping upgrade of %s (%s)", dependency.getName(), dependencyVersion.getIdentifier());
|
||||
return;
|
||||
}
|
||||
|
||||
upgrades.put(dependency, upgradeVersion);
|
||||
});
|
||||
|
||||
return new DependencyVersions(upgrades);
|
||||
}
|
||||
|
||||
private Optional<Ticket> getDependencyUpgradeTicket(Tickets tickets, String upgradeTicketSummary) {
|
||||
|
||||
List<Ticket> upgradeTickets = tickets.filter(it -> it.getSummary().equals(upgradeTicketSummary)).toList();
|
||||
|
||||
if (upgradeTickets.size() > 1) {
|
||||
throw new IllegalStateException("Multiple upgrade tickets found: " + upgradeTickets);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(upgradeTickets.isEmpty() ? null : upgradeTickets.get(0));
|
||||
}
|
||||
|
||||
protected static DependencyUpgradeProposal getDependencyUpgradeProposal(DependencyUpgradePolicy policy,
|
||||
DependencyVersion currentVersion, List<DependencyVersion> allVersions) {
|
||||
|
||||
Optional<DependencyVersion> latestMinor = findLatestMinor(policy, currentVersion, allVersions);
|
||||
Optional<DependencyVersion> latest = findLatest(policy, allVersions);
|
||||
List<DependencyVersion> newerVersions = allVersions.stream() //
|
||||
.sorted() //
|
||||
.filter(it -> it.compareTo(currentVersion) > 0) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyVersion latestToUse = latest.filter(it -> it.isNewer(currentVersion)).orElse(currentVersion);
|
||||
|
||||
DependencyVersion latestMinorFallback = latest
|
||||
.filter(it -> isUpgradeable(policy, it, currentVersion) && it.isNewer(currentVersion)).orElse(currentVersion);
|
||||
|
||||
return DependencyUpgradeProposal.of(policy, currentVersion, latestMinor.orElse(latestMinorFallback), latestToUse,
|
||||
newerVersions);
|
||||
}
|
||||
|
||||
private static boolean isUpgradeable(DependencyUpgradePolicy policy, DependencyVersion proposal,
|
||||
DependencyVersion currentVersion) {
|
||||
|
||||
if (policy.restrictToMinorVersion()) {
|
||||
|
||||
if (proposal.getTrainName() != null && currentVersion.getTrainName() != null) {
|
||||
return proposal.getTrainName().equals(currentVersion.getTrainName());
|
||||
}
|
||||
|
||||
if (proposal.getVersion().getMajor() == currentVersion.getVersion().getMajor()
|
||||
&& proposal.getVersion().getMinor() == currentVersion.getVersion().getMinor()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(proposal.getModifier())) {
|
||||
return policy.milestoneAllowed();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Optional<DependencyVersion> findLatest(DependencyUpgradePolicy policy,
|
||||
List<DependencyVersion> availableVersions) {
|
||||
|
||||
return availableVersions.stream().filter(it -> {
|
||||
|
||||
if (StringUtils.hasText(it.getModifier())) {
|
||||
return policy.milestoneAllowed();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}).max(DependencyVersion::compareTo);
|
||||
}
|
||||
|
||||
private static Optional<DependencyVersion> findLatestMinor(DependencyUpgradePolicy policy,
|
||||
DependencyVersion currentVersion, List<DependencyVersion> availableVersions) {
|
||||
|
||||
return availableVersions.stream().filter(it -> {
|
||||
|
||||
if (StringUtils.hasText(it.getModifier())) {
|
||||
return policy.milestoneAllowed();
|
||||
}
|
||||
|
||||
if (it.getVersion() == null || currentVersion.getVersion() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (it.getTrainName() != null && currentVersion.getTrainName() != null) {
|
||||
return it.getTrainName().equals(currentVersion.getTrainName());
|
||||
}
|
||||
|
||||
if (it.getVersion().getMajor() == currentVersion.getVersion().getMajor()
|
||||
&& it.getVersion().getMinor() == currentVersion.getVersion().getMinor()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}) //
|
||||
.max(DependencyVersion::compareTo);
|
||||
}
|
||||
|
||||
DependencyVersions getCurrentDependencies(Project project) {
|
||||
|
||||
if (!ProjectDependencies.containsProject(project)) {
|
||||
return DependencyVersions.empty();
|
||||
}
|
||||
|
||||
File pom = getPomFile(project);
|
||||
ProjectDependencies dependencies = ProjectDependencies.get(project);
|
||||
|
||||
return doWithPom(pom, Pom.class, it -> {
|
||||
|
||||
Map<Dependency, DependencyVersion> versions = new LinkedHashMap<>();
|
||||
|
||||
for (ProjectDependencies.ProjectDependency projectDependency : dependencies) {
|
||||
|
||||
Dependency dependency = projectDependency.getDependency();
|
||||
|
||||
if (!((project == Projects.MONGO_DB && projectDependency.getProperty().equals("mongo.reactivestreams"))
|
||||
|| project == Projects.NEO4J || project == Projects.BUILD)) {
|
||||
|
||||
if (it.getDependencyVersion(dependency.getArtifactId()) == null
|
||||
&& it.getManagedDependency(dependency.getArtifactId()) == null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String value = it.getProperty(projectDependency.getProperty());
|
||||
|
||||
if (value != null && !value.contains("${")) {
|
||||
versions.put(dependency, DependencyVersion.of(value));
|
||||
}
|
||||
}
|
||||
|
||||
return new DependencyVersions(versions);
|
||||
});
|
||||
}
|
||||
|
||||
private File getPomFile(Project project) {
|
||||
return workspace.getFile(project == Projects.BUILD ? "parent/pom.xml" : "pom.xml", project);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
List<DependencyVersion> getAvailableVersions(Dependency dependency) {
|
||||
|
||||
String baseUrl = String.format("https://repo1.maven.org/maven2/%s/%s/", dependency.getGroupId().replace('.', '/'),
|
||||
dependency.getArtifactId());
|
||||
|
||||
try {
|
||||
ResponseEntity<byte[]> mavenMetadata = restOperations.getForEntity(baseUrl + "/maven-metadata.xml", byte[].class);
|
||||
ResponseEntity<String> directoryListing = restOperations.getForEntity(baseUrl, String.class);
|
||||
|
||||
Map<String, LocalDateTime> creationDates = parseCreationDates(directoryListing.getBody());
|
||||
|
||||
XBStreamInput io = projectionFactory.io().stream(new ByteArrayInputStream(mavenMetadata.getBody()));
|
||||
|
||||
MavenMetadata metadata = io.read(MavenMetadata.class);
|
||||
|
||||
return metadata.getVersions().stream().filter(dependency::shouldInclude).flatMap(s -> {
|
||||
|
||||
try {
|
||||
return Stream.of(DependencyVersion.of(s));
|
||||
} catch (Exception e) {
|
||||
logger.log(dependency.toString(), "Cannot parse dependency version " + s);
|
||||
return Stream.empty();
|
||||
}
|
||||
}).map(it -> {
|
||||
|
||||
if (creationDates.containsKey(it.getIdentifier())) {
|
||||
return it.withCreatedAt(creationDates.get(it.getIdentifier()));
|
||||
}
|
||||
|
||||
return it;
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
} catch (Exception o_O) {
|
||||
|
||||
if (o_O instanceof HttpClientErrorException) {
|
||||
if (((HttpClientErrorException) o_O).getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException(String.format("Cannot determine available versions for %s", dependency), o_O);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, LocalDateTime> parseCreationDates(String body) {
|
||||
|
||||
Map<String, LocalDateTime> creationDates = new LinkedHashMap<>();
|
||||
Matcher matcher = REPO_MAVEN_ORG_DIR_LISTING.matcher(body);
|
||||
|
||||
while (matcher.find()) {
|
||||
|
||||
String version = matcher.group(1);
|
||||
LocalDateTime creationDate = LocalDateTime.from(DIR_LISTING_TIME_FORMAT.parse(matcher.group(2)));
|
||||
creationDates.put(version, creationDate);
|
||||
}
|
||||
|
||||
return creationDates;
|
||||
}
|
||||
|
||||
private <T extends Pom, R> R doWithPom(File file, Class<T> type, Function<T, R> callback) {
|
||||
|
||||
XBFileIO io = projectionFactory.io().file(file);
|
||||
|
||||
try {
|
||||
|
||||
T pom = (T) io.read(type);
|
||||
return callback.apply(pom);
|
||||
|
||||
} catch (Exception o_O) {
|
||||
throw new RuntimeException(o_O);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Pom> void update(File file, Class<T> type, Consumer<T> callback) {
|
||||
|
||||
XBFileIO io = projectionFactory.io().file(file);
|
||||
|
||||
try {
|
||||
|
||||
T pom = (T) io.read(type);
|
||||
callback.accept(pom);
|
||||
io.write(pom);
|
||||
|
||||
} catch (Exception o_O) {
|
||||
throw new RuntimeException(o_O);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getUpgradeTicketSummary(Dependency dependency, DependencyVersion dependencyVersion) {
|
||||
return String.format("Upgrade to %s %s", dependency.getName(), dependencyVersion.getIdentifier());
|
||||
}
|
||||
|
||||
public interface MavenMetadata {
|
||||
|
||||
@XBRead("/metadata/versioning/versions/version/text()")
|
||||
List<String> getVersions();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
|
||||
/**
|
||||
* Upgrade policy expressing rules how dependency upgrades should happen.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
interface DependencyUpgradePolicy {
|
||||
|
||||
DependencyUpgradePolicy LATEST_STABLE = new DependencyUpgradePolicy() {
|
||||
|
||||
@Override
|
||||
public boolean milestoneAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restrictToMinorVersion() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {@code true} if the use of pre-release dependency versions is allowed.
|
||||
*/
|
||||
boolean milestoneAllowed();
|
||||
|
||||
/**
|
||||
* @return {@code true} if upgrades only within the minor version (e.g. bugfixes/patch releases) are allowed.
|
||||
*/
|
||||
boolean restrictToMinorVersion();
|
||||
|
||||
/**
|
||||
* Create a upgrade policy from a {@link Iteration}.
|
||||
*
|
||||
* @param iteration
|
||||
* @return
|
||||
*/
|
||||
static DependencyUpgradePolicy from(Iteration iteration) {
|
||||
|
||||
return new DependencyUpgradePolicy() {
|
||||
|
||||
@Override
|
||||
public boolean milestoneAllowed() {
|
||||
return iteration.isMilestone() || iteration.isReleaseCandidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restrictToMinorVersion() {
|
||||
return iteration.isPublic();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@RequiredArgsConstructor
|
||||
class DependencyUpgradeProposal {
|
||||
|
||||
DependencyVersion current, latest, latestMinor, proposal;
|
||||
List<DependencyVersion> newerVersions;
|
||||
|
||||
public static DependencyUpgradeProposal of(DependencyUpgradePolicy policy, DependencyVersion currentVersion,
|
||||
DependencyVersion latestMinor, DependencyVersion latest, List<DependencyVersion> newerVersions) {
|
||||
|
||||
if (policy.restrictToMinorVersion()) {
|
||||
return new DependencyUpgradeProposal(currentVersion, latest, latestMinor, latestMinor, newerVersions);
|
||||
}
|
||||
|
||||
return new DependencyUpgradeProposal(currentVersion, latest, latestMinor, latest, newerVersions);
|
||||
}
|
||||
|
||||
public String getNewVersions(boolean includeAll, boolean includeDate) {
|
||||
|
||||
if (includeAll) {
|
||||
return getNewerVersions().stream().map(dependencyVersion -> {
|
||||
|
||||
if (includeDate && dependencyVersion.getCreatedAt() != null) {
|
||||
return String.format("%s (%s)", dependencyVersion.getIdentifier(),
|
||||
dependencyVersion.getCreatedAt().toLocalDate());
|
||||
}
|
||||
|
||||
return dependencyVersion.getIdentifier();
|
||||
}).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
if (latestMinor.toString().equals(latest.toString())) {
|
||||
return latest.toString();
|
||||
}
|
||||
|
||||
return String.format("%s, %s", latestMinor, latest);
|
||||
}
|
||||
|
||||
public boolean isUpgradeAvailable() {
|
||||
return !getCurrent().getIdentifier().equals(getProposal().getIdentifier());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getProposal().getIdentifier();
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.fusesource.jansi.Ansi;
|
||||
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.shell.support.table.Table;
|
||||
import org.springframework.shell.support.table.TableHeader;
|
||||
|
||||
/**
|
||||
* Value object capturing upgrade proposals for each {@link Dependency}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class DependencyUpgradeProposals {
|
||||
|
||||
private final Map<Dependency, DependencyUpgradeProposal> proposals;
|
||||
|
||||
public DependencyUpgradeProposals(Map<Dependency, DependencyUpgradeProposal> proposals) {
|
||||
this.proposals = proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty {@link DependencyUpgradeProposal} object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static DependencyUpgradeProposals empty() {
|
||||
return new DependencyUpgradeProposals(Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DependencyUpgradeProposal} by merging this and {@code other}.
|
||||
*
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public DependencyUpgradeProposals mergeWith(DependencyUpgradeProposals other) {
|
||||
|
||||
Map<Dependency, DependencyUpgradeProposal> proposals = new TreeMap<>(this.proposals);
|
||||
proposals.putAll(other.proposals);
|
||||
|
||||
return new DependencyUpgradeProposals(proposals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tabular summary including {@link Ansi} escapes.
|
||||
*
|
||||
* @param includeAll
|
||||
* @return
|
||||
*/
|
||||
public Table toTable(boolean includeAll) {
|
||||
|
||||
Table table = new Table();
|
||||
table.addHeader(1, new TableHeader("Dependency"));
|
||||
table.addHeader(2, new TableHeader("Current"));
|
||||
table.addHeader(3, new TableHeader("Available"));
|
||||
table.addHeader(4, new TableHeader("Proposed"));
|
||||
|
||||
proposals.forEach((dependency, proposal) -> {
|
||||
|
||||
boolean updateAvailable = proposal.isUpgradeAvailable();
|
||||
|
||||
String s = updateAvailable
|
||||
? Ansi.ansi().fg(Ansi.Color.MAGENTA).a(proposal.getProposal()).fg(Ansi.Color.GREEN).toString()
|
||||
: proposal.getProposal().toString();
|
||||
|
||||
if (includeAll || updateAvailable) {
|
||||
table.addRow(dependency.getName(), proposal.getCurrent().toString(), proposal.getNewVersions(includeAll, false),
|
||||
s);
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the upgrade proposal as {@link java.util.Properties} representation.
|
||||
*
|
||||
* @param iteration
|
||||
* @return
|
||||
*/
|
||||
public String asProperties(TrainIteration iteration) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("dependency.train=").append(iteration.getTrain().getName()).append(System.lineSeparator());
|
||||
builder.append("dependency.iteration=").append(iteration.getIteration().getName()).append(System.lineSeparator());
|
||||
builder.append(System.lineSeparator());
|
||||
|
||||
builder.append("# Specify the number of desired dependency upgrades as sanity check")
|
||||
.append(System.lineSeparator());
|
||||
builder.append("dependency.upgrade.count=").append(System.lineSeparator());
|
||||
|
||||
proposals.forEach((dependency, proposal) -> {
|
||||
|
||||
boolean updateAvailable = proposal.isUpgradeAvailable();
|
||||
|
||||
if (updateAvailable) {
|
||||
builder.append(System.lineSeparator());
|
||||
builder.append(String.format("# %s - Available versions: ", dependency.getName()))
|
||||
.append(proposal.getNewVersions(true, true)).append(System.lineSeparator());
|
||||
|
||||
builder.append(
|
||||
String.format("dependency[%s\\:%s]=%s", dependency.getGroupId(), dependency.getArtifactId(), proposal));
|
||||
builder.append(System.lineSeparator());
|
||||
}
|
||||
});
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dependency upgrade map by parsing {@link Properties}.
|
||||
*
|
||||
* @param iteration
|
||||
* @param properties
|
||||
* @return
|
||||
*/
|
||||
public static DependencyVersions fromProperties(TrainIteration iteration, Properties properties) {
|
||||
|
||||
Pattern keyPattern = Pattern.compile("dependency\\[([a-zA-Z0-9\\-\\.]+):([a-zA-Z0-9\\-\\.]+)\\]");
|
||||
|
||||
String verificationTrain = properties.getProperty("dependency.train", "");
|
||||
String verificationIteration = properties.getProperty("dependency.iteration", "");
|
||||
int expectedUpgradeCount;
|
||||
try {
|
||||
expectedUpgradeCount = Integer.parseInt(properties.getProperty("dependency.upgrade.count", "0"));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Please specify a valid dependency.upgrade.count");
|
||||
}
|
||||
|
||||
if (!verificationTrain.equals(iteration.getTrain().getName())
|
||||
|| !verificationIteration.equals(iteration.getIteration().getName())) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Verification failed: Dependency upgrade descriptor reports %s %s", verificationTrain,
|
||||
verificationIteration));
|
||||
}
|
||||
|
||||
Map<Dependency, DependencyVersion> result = new LinkedHashMap<>();
|
||||
|
||||
properties.forEach((k, v) -> {
|
||||
|
||||
if ("dependency.train".equals(k) || "dependency.iteration".equals(k) || "dependency.upgrade.count".equals(k)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Matcher matcher = keyPattern.matcher(k.toString());
|
||||
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException(String.format("Unexpected key: %s", k));
|
||||
}
|
||||
|
||||
String groupId = matcher.group(1);
|
||||
String artifactId = matcher.group(2);
|
||||
Dependency dependency = Dependencies.getRequiredDepependency(groupId, artifactId);
|
||||
|
||||
result.put(dependency, DependencyVersion.of(v.toString()));
|
||||
});
|
||||
|
||||
DependencyVersions dependencyVersions = new DependencyVersions(result);
|
||||
|
||||
if (expectedUpgradeCount != result.size()) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"The number of expected upgrades (dependency.upgrade.count=%s) does not match the number of actual upgrades (%s): %n%n%s",
|
||||
expectedUpgradeCount, result.size(), dependencyVersions.toString(1)));
|
||||
}
|
||||
|
||||
return dependencyVersions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.release.model.Version;
|
||||
|
||||
/**
|
||||
* Value object representing a dependency version. The primary identifier is {@link #identifier} that corresponds with
|
||||
* the actual version identifier. Version identifiers are attempted to be parsed into either a version following Spring
|
||||
* Version or SemVer rules ({@code 1.2.3.RELEASE}, {@code 1.2.3-rc1}) or train name with counter rules
|
||||
* ({@code Foo-RELEASE}, {@code Foo-SR1}).
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
class DependencyVersion implements Comparable<DependencyVersion> {
|
||||
|
||||
private static Pattern VERSION = Pattern.compile("((?>(?>\\d+)[\\.]?)+)((?>-)?[a-zA-Z]+)?(\\d+)?");
|
||||
private static Pattern NAME_VERSION = Pattern.compile("([A-Za-z]+)-(RELEASE|SR(\\d+)|SNAPSHOT|BUILD-SNAPSHOT)");
|
||||
|
||||
private static Comparator<DependencyVersion> VERSION_COMPARATOR = Comparator.comparing(DependencyVersion::getVersion)
|
||||
.thenComparing((o1, o2) -> {
|
||||
|
||||
// no modifier means release so it's higher order
|
||||
if (o1.getModifier().isEmpty() && !o2.getModifier().isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!o1.getModifier().isEmpty() && o2.getModifier().isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}).thenComparing(DependencyVersion::getModifier).thenComparing(DependencyVersion::getCounter)
|
||||
.thenComparing(DependencyVersion::getIdentifier);
|
||||
|
||||
private static Comparator<DependencyVersion> TRAIN_VERSION_COMPARATOR = Comparator
|
||||
.comparing(DependencyVersion::getTrainName).thenComparing(DependencyVersion::getVersion);
|
||||
|
||||
String identifier;
|
||||
String trainName;
|
||||
Version version;
|
||||
String modifier;
|
||||
int counter;
|
||||
@With LocalDateTime createdAt;
|
||||
|
||||
public static DependencyVersion of(String identifier) {
|
||||
|
||||
Matcher bomMatcher = NAME_VERSION.matcher(identifier);
|
||||
|
||||
if (bomMatcher.find()) {
|
||||
|
||||
Version version = Version.of(0);
|
||||
String modifier = "";
|
||||
if (identifier.contains("-SR")) {
|
||||
version = Version.of(Integer.parseInt(bomMatcher.group(3)));
|
||||
}
|
||||
|
||||
if (identifier.endsWith("-SNAPSHOT")) {
|
||||
modifier = "SNAPSHOT";
|
||||
}
|
||||
|
||||
return new DependencyVersion(identifier, bomMatcher.group(1), version, modifier, 0, null);
|
||||
}
|
||||
|
||||
Matcher versionMatcher = VERSION.matcher(identifier);
|
||||
|
||||
if (versionMatcher.find()) {
|
||||
Version version = null;
|
||||
String modifier;
|
||||
String counter;
|
||||
String versionString = versionMatcher.group(1);
|
||||
|
||||
versionString = versionString.endsWith(".") ? versionString.substring(0, versionString.length() - 1)
|
||||
: versionString;
|
||||
try {
|
||||
version = Version.parse(versionString);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalArgumentException(String.format("Cannot parse version number %s", versionString), e);
|
||||
}
|
||||
|
||||
modifier = versionMatcher.group(2);
|
||||
counter = versionMatcher.group(3);
|
||||
|
||||
return new DependencyVersion(identifier, null, version, modifier == null ? "" : modifier.toUpperCase(Locale.ROOT),
|
||||
counter != null ? Integer.parseInt(counter) : 0, null);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot parse version identifier %s", identifier));
|
||||
}
|
||||
|
||||
public boolean isNewer(DependencyVersion other) {
|
||||
return this.compareTo(other) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DependencyVersion o) {
|
||||
|
||||
if (trainName != null && o.trainName != null) {
|
||||
return TRAIN_VERSION_COMPARATOR.compare(this, o);
|
||||
}
|
||||
|
||||
if (trainName != null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (o.trainName != null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (version != null && o.version != null) {
|
||||
return VERSION_COMPARATOR.compare(this, o);
|
||||
}
|
||||
|
||||
return identifier.compareTo(o.identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.maven.shared.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* Value upgrade capturing {@link DependencyVersion} for a {@link Dependency}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
class DependencyVersions {
|
||||
|
||||
@Getter Map<Dependency, DependencyVersion> versions;
|
||||
|
||||
public static DependencyVersions empty() {
|
||||
return new DependencyVersions(Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there's a version specified for {@link Dependency}.
|
||||
*
|
||||
* @param dependency
|
||||
* @return
|
||||
*/
|
||||
public boolean hasDependency(Dependency dependency) {
|
||||
return versions.containsKey(dependency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DependencyVersion} for {@link Dependency} or throws {@link IllegalArgumentException} if no
|
||||
* version was specified. It's recommended to check whether a version has been specified using
|
||||
* {@link #hasDependency(Dependency)}.
|
||||
*
|
||||
* @param dependency
|
||||
* @return
|
||||
* @see #hasDependency(Dependency)
|
||||
*/
|
||||
public DependencyVersion get(Dependency dependency) {
|
||||
|
||||
if (!hasDependency(dependency)) {
|
||||
throw new IllegalArgumentException(String.format("No such dependency: %s", dependency));
|
||||
}
|
||||
|
||||
return versions.get(dependency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the {@code action} to all dependeny/dependency version pairs.
|
||||
*
|
||||
* @param action
|
||||
*/
|
||||
public void forEach(BiConsumer<Dependency, DependencyVersion> action) {
|
||||
versions.forEach(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether dependency versions are configured.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return versions.isEmpty();
|
||||
}
|
||||
|
||||
public Collection<Dependency> getDependencies() {
|
||||
return versions.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(0);
|
||||
}
|
||||
|
||||
public String toString(int indentation) {
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (Map.Entry<Dependency, DependencyVersion> entry : versions.entrySet()) {
|
||||
|
||||
result.append(StringUtils.repeat("\t", indentation)).append(StringUtils.rightPad(entry.getKey().toString(), 40))
|
||||
.append(" = ").append(entry.getValue()).append(System.lineSeparator());
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.util.Strings;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.io.JavaRuntimes;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.shell.support.table.Table;
|
||||
import org.springframework.shell.support.table.TableHeader;
|
||||
|
||||
/**
|
||||
* Shell commands for dependency management.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class InfrastructureCommands extends TimedCommand {
|
||||
|
||||
DependencyOperations operations;
|
||||
GitOperations git;
|
||||
InfrastructureOperations infra;
|
||||
Logger logger;
|
||||
|
||||
@CliCommand(value = "infra jdk list")
|
||||
public Table listJdkVersions() {
|
||||
|
||||
List<JavaRuntimes.JdkInstallation> jdks = JavaRuntimes.getJdks();
|
||||
StringBuilder builder = new StringBuilder("Available Java versions" + Strings.lineSeparator());
|
||||
|
||||
Table table = new Table();
|
||||
table.addHeader(1, new TableHeader("Version", 15));
|
||||
table.addHeader(2, new TableHeader("Vendor", 20));
|
||||
table.addHeader(3, new TableHeader("Home"));
|
||||
|
||||
for (JavaRuntimes.JdkInstallation jdk : jdks) {
|
||||
table.addRow(jdk.getVersion().toString(), jdk.getImplementor(), jdk.getHome().toString());
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
@CliCommand(value = "infra maven check")
|
||||
public void check(@CliOption(key = "", mandatory = true) TrainIteration iteration,
|
||||
@CliOption(key = "all", mandatory = false) Boolean reportAll) throws IOException {
|
||||
|
||||
git.checkout(iteration.getTrain());
|
||||
|
||||
DependencyUpgradeProposals proposals = operations.getMavenWrapperDependencyUpgradeProposals(iteration);
|
||||
|
||||
Files.write(Paths.get(InfrastructureOperations.MAVEN_PROPERTIES), proposals.asProperties(iteration).getBytes());
|
||||
|
||||
Table summary = proposals.toTable(reportAll == null ? false : reportAll);
|
||||
|
||||
logger.log(Projects.BUILD, "Upgrade summary:" + System.lineSeparator() + System.lineSeparator() + summary);
|
||||
logger.log(iteration, "Upgrade proposals written to " + InfrastructureOperations.MAVEN_PROPERTIES);
|
||||
}
|
||||
|
||||
@CliCommand(value = "infra maven upgrade")
|
||||
public void upgradeMavenVersion(@CliOption(key = "", mandatory = true) TrainIteration iteration)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
logger.log(iteration, "Applying Maven wrapper upgrades to Spring Data…");
|
||||
|
||||
infra.upgradeMavenVersion(iteration);
|
||||
}
|
||||
|
||||
@CliCommand(value = "infra distribute ci-properties")
|
||||
public void distributeCiProperties(@CliOption(key = "", mandatory = true) TrainIteration iteration)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
logger.log(iteration, "Distributing CI properties for Spring Data…");
|
||||
|
||||
git.checkout(iteration.getTrain(), true);
|
||||
|
||||
infra.distributeCiProperties(iteration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.git.Branch;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.io.Workspace;
|
||||
import org.springframework.data.release.issues.Tickets;
|
||||
import org.springframework.data.release.model.Module;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
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.data.util.Streamable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class InfrastructureOperations extends TimedCommand {
|
||||
|
||||
public static final String CI_PROPERTIES = "ci/pipeline.properties";
|
||||
|
||||
public static final String MAVEN_PROPERTIES = "dependency-upgrade-maven.properties";
|
||||
|
||||
DependencyOperations dependencies;
|
||||
Workspace workspace;
|
||||
GitOperations git;
|
||||
ExecutorService executor;
|
||||
Logger logger;
|
||||
|
||||
/**
|
||||
* Distribute {@link #CI_PROPERTIES} from {@link Projects#BUILD} to all modules within {@link TrainIteration}.
|
||||
*
|
||||
* @param iteration
|
||||
*/
|
||||
void distributeCiProperties(TrainIteration iteration) {
|
||||
|
||||
File master = workspace.getFile(CI_PROPERTIES, Projects.BUILD);
|
||||
|
||||
if (!master.exists()) {
|
||||
throw new IllegalStateException(String.format("CI Properties file %s does not exist", master));
|
||||
}
|
||||
|
||||
ExecutionUtils.run(executor, iteration, module -> {
|
||||
|
||||
Project project = module.getProject();
|
||||
Branch branch = Branch.from(module);
|
||||
|
||||
git.update(project);
|
||||
git.checkout(project, branch);
|
||||
});
|
||||
|
||||
verifyExistingPropertyFiles(iteration.getTrain(), master);
|
||||
|
||||
ExecutionUtils.run(executor, Streamable.of(iteration.getModulesExcept(Projects.BUILD)), module -> {
|
||||
|
||||
File target = workspace.getFile(CI_PROPERTIES, module.getProject());
|
||||
target.delete();
|
||||
|
||||
FileUtils.copyFile(master, target);
|
||||
|
||||
git.add(module.getProject(), CI_PROPERTIES);
|
||||
git.commit(module, "Update CI properties.", Optional.empty(), false);
|
||||
git.push(module);
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyExistingPropertyFiles(Train train, File master) {
|
||||
|
||||
for (Module module : train) {
|
||||
File target = workspace.getFile(CI_PROPERTIES, module.getProject());
|
||||
|
||||
if (!target.exists()) {
|
||||
throw new IllegalStateException(String.format("CI Properties file %s does not exist", master));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void upgradeMavenVersion(TrainIteration iteration) {
|
||||
|
||||
DependencyVersions dependencyVersions = loadDependencyUpgrades(iteration);
|
||||
|
||||
if (dependencyVersions.isEmpty()) {
|
||||
throw new IllegalStateException("No version to upgrade found!");
|
||||
}
|
||||
|
||||
git.checkout(iteration.getTrain(), false);
|
||||
|
||||
List<Project> projectsToUpgrade = dependencies
|
||||
.getProjectsToUpgradeMavenWrapper(dependencyVersions.get(Dependencies.MAVEN), iteration);
|
||||
|
||||
ExecutionUtils.run(executor, Streamable.of(projectsToUpgrade), project -> {
|
||||
|
||||
ModuleIteration module = iteration.getModule(project);
|
||||
Tickets tickets = dependencies.getOrCreateUpgradeTickets(module, dependencyVersions);
|
||||
dependencies.upgradeMavenWrapperVersion(tickets, module, dependencyVersions);
|
||||
git.push(module);
|
||||
|
||||
// Allow GitHub to catch up with ticket notifications.
|
||||
Thread.sleep(1500);
|
||||
|
||||
dependencies.closeUpgradeTickets(module, tickets);
|
||||
});
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private DependencyVersions loadDependencyUpgrades(TrainIteration iteration) {
|
||||
|
||||
if (!Files.exists(Paths.get(MAVEN_PROPERTIES))) {
|
||||
logger.log(iteration, "Cannot upgrade dependencies: " + MAVEN_PROPERTIES + " does not exist.");
|
||||
}
|
||||
|
||||
Properties properties = new Properties();
|
||||
try (FileInputStream fis = new FileInputStream(MAVEN_PROPERTIES)) {
|
||||
properties.load(fis);
|
||||
}
|
||||
|
||||
return DependencyUpgradeProposals.fromProperties(iteration, properties);
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.AbstractFileFilter;
|
||||
import org.apache.commons.io.filefilter.IOFileFilter;
|
||||
import org.apache.commons.io.filefilter.NameFileFilter;
|
||||
import org.apache.commons.io.filefilter.NotFileFilter;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.io.Workspace;
|
||||
import org.springframework.data.release.issues.IssueTracker;
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
import org.springframework.data.release.issues.TicketOperations;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.ExecutionUtils;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class LicenseHeaderCommands extends TimedCommand {
|
||||
|
||||
GitOperations git;
|
||||
|
||||
Workspace workspace;
|
||||
|
||||
Executor executor;
|
||||
|
||||
Logger logger;
|
||||
|
||||
TicketOperations tickets;
|
||||
|
||||
List<String> filePatterns = Arrays.asList("pom.xml", "**/*.java", "**/*.kt", "**/*.adoc");
|
||||
|
||||
/**
|
||||
* Process all files matching {@link #filePatterns} and update the Apache license header year range, extending to
|
||||
* {@code year}. Rewrites single-year and year-range formats.
|
||||
*
|
||||
* @param iteration
|
||||
* @param year
|
||||
*/
|
||||
@CliCommand(value = "update license-headers")
|
||||
public void updateLicenseHeaders(@CliOption(key = "", mandatory = true) TrainIteration iteration,
|
||||
@CliOption(key = "year", mandatory = true) int year,
|
||||
@CliOption(key = "project", mandatory = false) String projectName) {
|
||||
|
||||
git.prepare(iteration);
|
||||
|
||||
Streamable<ModuleIteration> modules = iteration;
|
||||
|
||||
if (projectName != null) {
|
||||
Project project = Projects.requiredByName(projectName);
|
||||
modules = modules.filter(it -> it.getProject().equals(project));
|
||||
}
|
||||
|
||||
ExecutionUtils.run(executor, modules, module -> {
|
||||
|
||||
String summary = String.format("Extend license header copyright years to %d", year);
|
||||
|
||||
int updated = replaceInFiles(module.getProject(), content -> {
|
||||
|
||||
String contentToUse = content;
|
||||
|
||||
contentToUse = contentToUse.replaceAll("(C) ([\\d]{4})-([\\d]{4})", "(C) $1-" + year);
|
||||
|
||||
contentToUse = contentToUse.replaceAll("Copyright ([\\d]{4}) the original author or authors",
|
||||
"Copyright $1-" + year + " the original author or authors");
|
||||
|
||||
contentToUse = contentToUse.replaceAll("Copyright ([\\d]{4})-([\\d]{4}) the original author or authors",
|
||||
"Copyright $1-" + year + " the original author or authors");
|
||||
|
||||
return contentToUse;
|
||||
});
|
||||
|
||||
if (updated > 0) {
|
||||
commitAndPushWithTicket(module, summary);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void commitAndPushWithTicket(ModuleIteration module, String ticketSummary) throws InterruptedException {
|
||||
|
||||
Ticket ticket = tickets.getOrCreateTicketsWithSummary(module, IssueTracker.TicketType.Task, ticketSummary);
|
||||
git.commit(module, ticket, ticketSummary, Optional.empty(), true);
|
||||
|
||||
try {
|
||||
git.push(module);
|
||||
} catch (Exception e) {
|
||||
logger.warn(module, e);
|
||||
}
|
||||
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
|
||||
tickets.closeTicket(module, ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace content in files by applying {@link Function contentFunction} and return the number of updated files.
|
||||
*
|
||||
* @param project
|
||||
* @param contentFunction
|
||||
* @return
|
||||
*/
|
||||
private int replaceInFiles(Project project, Function<String, String> contentFunction) {
|
||||
|
||||
File projectDirectory = workspace.getProjectDirectory(project);
|
||||
IOFileFilter fileFilter = new AntPathFileFilter(projectDirectory, filePatterns);
|
||||
|
||||
int files = 0;
|
||||
int modified = 0;
|
||||
Iterator<File> fileIterator = FileUtils.iterateFiles(projectDirectory, fileFilter,
|
||||
new NotFileFilter(new NameFileFilter(".git")));
|
||||
|
||||
while (fileIterator.hasNext()) {
|
||||
|
||||
File file = fileIterator.next();
|
||||
files++;
|
||||
|
||||
try {
|
||||
if (doReplace(file, contentFunction)) {
|
||||
modified++;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(String.format("Cannot modify contents of %s", file), e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(project, "Found %s files, updated %s files", files, modified);
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
private boolean doReplace(File file, Function<String, String> modifyFunction) throws IOException {
|
||||
|
||||
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
|
||||
String modified = modifyFunction.apply(content);
|
||||
|
||||
if (!content.equals(modified)) {
|
||||
|
||||
FileUtils.write(file, modified, StandardCharsets.UTF_8);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class AntPathFileFilter extends AbstractFileFilter {
|
||||
|
||||
private final URI projectDirectory;
|
||||
private final List<String> filePatterns;
|
||||
|
||||
public AntPathFileFilter(File basePath, List<String> filePatterns) {
|
||||
this.projectDirectory = basePath.toURI();
|
||||
this.filePatterns = filePatterns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
|
||||
String relativePath = projectDirectory.relativize(file.toURI()).getPath();
|
||||
|
||||
AntPathMatcher matcher = new AntPathMatcher();
|
||||
for (String pattern : filePatterns) {
|
||||
|
||||
if (matcher.match(pattern, relativePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.infra;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Configuration of dependencies for a specific project.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ProjectDependencies implements Streamable<ProjectDependencies.ProjectDependency> {
|
||||
|
||||
private static final MultiValueMap<Project, ProjectDependency> config = new LinkedMultiValueMap<>();
|
||||
|
||||
static {
|
||||
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("apt", Dependencies.APT));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("aspectj", Dependencies.ASPECTJ));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("assertj", Dependencies.ASSERTJ));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("jackson", Dependencies.JACKSON));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("jacoco", Dependencies.JACOCO));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("jodatime", Dependencies.JODA_TIME));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("junit5", Dependencies.JUNIT5));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("jmolecules", Dependencies.JMOLECULES));
|
||||
config.add(Projects.BUILD,
|
||||
ProjectDependency.ofProperty("jmolecules-integration", Dependencies.JMOLECULES_INTEGRATION));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("junit", Dependencies.JUNIT4));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("kotlin", Dependencies.KOTLIN));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("kotlin-coroutines", Dependencies.KOTLIN_COROUTINES));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("micrometer", Dependencies.MICROMETER));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("micrometer-tracing", Dependencies.MICROMETER_TRACING));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("mockito", Dependencies.MOCKITO));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("mockk", Dependencies.MOCKK));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("querydsl", Dependencies.QUERYDSL));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("rxjava", Dependencies.RXJAVA1));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("rxjava2", Dependencies.RXJAVA2));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("rxjava3", Dependencies.RXJAVA3));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("rxjava-reactive-streams", Dependencies.RXJAVA_RS));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("smallrye-mutiny", Dependencies.SMALLRYE_MUTINY));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("spring-hateoas", Dependencies.SPRING_HATEOAS));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("spring-plugin", Dependencies.SPRING_PLUGIN));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("testcontainers", Dependencies.TESTCONTAINERS));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("threetenbp", Dependencies.THREE_TEN_BP));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("webbeans", Dependencies.OPEN_WEB_BEANS));
|
||||
|
||||
config.add(Projects.COMMONS, ProjectDependency.ofProperty("vavr", Dependencies.VAVR));
|
||||
config.add(Projects.COMMONS, ProjectDependency.ofProperty("xmlbeam", Dependencies.XML_BEAM));
|
||||
|
||||
config.add(Projects.MONGO_DB, ProjectDependency.ofProperty("mongo.reactivestreams", Dependencies.MONGODB_RS));
|
||||
config.add(Projects.MONGO_DB, ProjectDependency.ofProperty("mongo", Dependencies.MONGODB_LEGACY));
|
||||
config.add(Projects.MONGO_DB, ProjectDependency.ofProperty("mongo", Dependencies.MONGODB_CORE));
|
||||
config.add(Projects.MONGO_DB, ProjectDependency.ofProperty("mongo", Dependencies.MONGODB_SYNC));
|
||||
config.add(Projects.MONGO_DB, ProjectDependency.ofProperty("mongo", Dependencies.MONGODB_ASYNC));
|
||||
|
||||
config.add(Projects.REDIS, ProjectDependency.ofProperty("lettuce", Dependencies.LETTUCE));
|
||||
config.add(Projects.REDIS, ProjectDependency.ofProperty("jedis", Dependencies.JEDIS));
|
||||
|
||||
config.add(Projects.CASSANDRA,
|
||||
ProjectDependency.ofProperty("cassandra-driver.version", Dependencies.CASSANDRA_DRIVER3));
|
||||
config.add(Projects.CASSANDRA,
|
||||
ProjectDependency.ofProperty("cassandra-driver.version", Dependencies.CASSANDRA_DRIVER4));
|
||||
|
||||
config.add(Projects.NEO4J, ProjectDependency.ofProperty("neo4j.ogm.version", Dependencies.NEO4J_OGM));
|
||||
config.add(Projects.NEO4J, ProjectDependency.ofProperty("neo4j-java-driver.version", Dependencies.NEO4J_DRIVER));
|
||||
|
||||
config.add(Projects.COUCHBASE, ProjectDependency.ofProperty("couchbase", Dependencies.COUCHBASE));
|
||||
|
||||
config.add(Projects.ELASTICSEARCH, ProjectDependency.ofProperty("elasticsearch", Dependencies.ELASTICSEARCH_RHLC));
|
||||
config.add(Projects.ELASTICSEARCH,
|
||||
ProjectDependency.ofProperty("elasticsearch-rhlc", Dependencies.ELASTICSEARCH_RHLC));
|
||||
config.add(Projects.ELASTICSEARCH,
|
||||
ProjectDependency.ofProperty("elasticsearch-java", Dependencies.ELASTICSEARCH_REST_CLIENT));
|
||||
|
||||
config.add(Projects.LDAP, ProjectDependency.ofProperty("spring-ldap", Dependencies.SPRING_LDAP));
|
||||
}
|
||||
|
||||
private final List<ProjectDependency> dependencies;
|
||||
|
||||
private ProjectDependencies(List<ProjectDependency> dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve upgradable dependencies for a {@link Project}.
|
||||
*
|
||||
* @param project
|
||||
* @return
|
||||
* @throws IllegalArgumentException if the project has no upgradable dependencies.
|
||||
*/
|
||||
public static ProjectDependencies get(Project project) {
|
||||
|
||||
if (!containsProject(project)) {
|
||||
throw new IllegalArgumentException(String.format("No dependency configuration for %s!", project));
|
||||
}
|
||||
|
||||
return new ProjectDependencies(config.get(project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the {@link Project} has upgradable dependencies.
|
||||
*
|
||||
* @param project
|
||||
* @return
|
||||
*/
|
||||
public static boolean containsProject(Project project) {
|
||||
return config.containsKey(project);
|
||||
}
|
||||
|
||||
public String getVersionPropertyFor(Dependency dependency) {
|
||||
|
||||
for (ProjectDependency projectDependency : dependencies) {
|
||||
|
||||
if (projectDependency.getDependency().equals(dependency)) {
|
||||
return projectDependency.getProperty();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Dependency " + dependency + " is not a dependency of this project!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ProjectDependency> iterator() {
|
||||
return dependencies.iterator();
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class ProjectDependency {
|
||||
|
||||
String property;
|
||||
|
||||
Dependency dependency;
|
||||
|
||||
public static ProjectDependency ofProperty(String pomProperty, Dependency dependency) {
|
||||
return new ProjectDependency(pomProperty, dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.io;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Value
|
||||
public class CommandResult {
|
||||
|
||||
private final int status;
|
||||
private final String output;
|
||||
private final Exception exception;
|
||||
|
||||
public boolean hasError() {
|
||||
return status != 0;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.io;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "io")
|
||||
class IoProperties {
|
||||
|
||||
private File workDir, logs;
|
||||
|
||||
public void setWorkDir(String workDir) {
|
||||
|
||||
log.info(String.format("🔧 Using %s as working directory!", workDir));
|
||||
this.workDir = new File(workDir.replace("~", FileUtils.getUserDirectoryPath()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.io;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.RegexFileFilter;
|
||||
|
||||
import org.springframework.boot.system.SystemProperties;
|
||||
import org.springframework.data.release.model.JavaVersion;
|
||||
import org.springframework.data.release.model.Version;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import com.dd.plist.NSArray;
|
||||
import com.dd.plist.NSDictionary;
|
||||
import com.dd.plist.XMLPropertyListParser;
|
||||
|
||||
/**
|
||||
* Utility to detect a Java runtime version.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class JavaRuntimes {
|
||||
|
||||
private static final List<JdkDetector> DETECTORS = Arrays.asList(new SDKmanJdkDetector(), new MacNativeJdkDetector(),
|
||||
new JavaHomeJdkDetector());
|
||||
private static final Lazy<List<JdkInstallation>> JDKS = Lazy.of(() -> {
|
||||
|
||||
List<JdkInstallation> jdks = DETECTORS.stream() //
|
||||
.filter(JdkDetector::isAvailable) //
|
||||
.flatMap(it -> it.detect().stream()) //
|
||||
.sorted() //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Collections.reverse(jdks);
|
||||
|
||||
return Collections.unmodifiableList(jdks);
|
||||
});
|
||||
|
||||
/**
|
||||
* Lookup a {@link JdkInstallation} by detecting installed JDKs and applying the {@link Predicate filter}. Returns the
|
||||
* first matching one or throws {@link NoSuchElementException}.
|
||||
*
|
||||
* @param filter
|
||||
* @return
|
||||
*/
|
||||
public static JdkInstallation getJdk(Predicate<JdkInstallation> filter) {
|
||||
return getJdk(filter, () -> "Cannot obtain required JDK");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a {@link JdkInstallation} by detecting installed JDKs and applying the {@link Predicate filter}. Returns the
|
||||
* first matching one or throws {@link NoSuchElementException}.
|
||||
*
|
||||
* @param filter
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public static JdkInstallation getJdk(Predicate<JdkInstallation> filter, Supplier<String> message) {
|
||||
|
||||
List<JdkInstallation> jdks = JDKS.get();
|
||||
|
||||
return jdks.stream().filter(filter).findFirst()
|
||||
.orElseThrow(() -> new NoSuchElementException(String.format("%s%nAvailable JDK: %s", message.get(), jdks)));
|
||||
}
|
||||
|
||||
public static List<JdkInstallation> getJdks() {
|
||||
return JDKS.get();
|
||||
}
|
||||
|
||||
static boolean isDirectory(File file) {
|
||||
return file.exists() && file.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* JDK detection strategy.
|
||||
*/
|
||||
interface JdkDetector {
|
||||
|
||||
/**
|
||||
* @return {@code true} if the detector strategy is available.
|
||||
*/
|
||||
boolean isAvailable();
|
||||
|
||||
/**
|
||||
* @return a list of JDK installations.
|
||||
*/
|
||||
List<JdkInstallation> detect();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector to determine a {@link JdkInstallation}.
|
||||
*/
|
||||
public static class Selector {
|
||||
|
||||
private String notFoundMessage;
|
||||
private Predicate<JdkInstallation> predicate;
|
||||
|
||||
private Selector() {
|
||||
|
||||
}
|
||||
|
||||
public static Selector builder() {
|
||||
return new Selector();
|
||||
}
|
||||
|
||||
public static Selector from(JavaVersion javaVersion) {
|
||||
|
||||
return builder()
|
||||
.and(it -> javaVersion.getVersionDetector().test(it.getVersion())
|
||||
&& javaVersion.getImplementor().test(it.getImplementor()))
|
||||
.message("Cannot find Java " + javaVersion.getName());
|
||||
}
|
||||
|
||||
public Selector and(Predicate<JdkInstallation> predicate) {
|
||||
this.predicate = this.predicate == null ? predicate : this.predicate.and(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Selector notGraalVM() {
|
||||
this.notFoundMessage += " (Not GraalVM)";
|
||||
return and(it -> !it.getName().contains("GraalVM"));
|
||||
}
|
||||
|
||||
public Selector message(String notFoundMessage) {
|
||||
this.notFoundMessage = notFoundMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JdkInstallation getRequiredJdkInstallation() {
|
||||
return JavaRuntimes.getJdk(predicate, () -> notFoundMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Detector using the SDKman utility storing Java installations in {@code ~/.sdkman/candidates/java}.
|
||||
*/
|
||||
static class SDKmanJdkDetector implements JdkDetector {
|
||||
|
||||
private static final File sdkManJavaHome = new File(FileUtils.getUserDirectoryPath(), ".sdkman/candidates/java");
|
||||
|
||||
private static final Pattern CANDIDATE = Pattern.compile("(\\d+[\\.\\d+]+)[.-][-a-zA-Z]*");
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return isDirectory(sdkManJavaHome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JdkInstallation> detect() {
|
||||
|
||||
File[] files = sdkManJavaHome.listFiles((FileFilter) new RegexFileFilter(CANDIDATE));
|
||||
|
||||
return Arrays.stream(files).map(it -> {
|
||||
|
||||
Matcher matcher = CANDIDATE.matcher(it.getName());
|
||||
if (!matcher.find()) {
|
||||
throw new IllegalArgumentException("Cannot determine JVM version number from SDKman candidate name "
|
||||
+ it.getName() + ". This should not happen in an ideal world, check the CANDIDATE regex.");
|
||||
}
|
||||
|
||||
String candidateVersion = matcher.group(1);
|
||||
Version version = Version.parse(candidateVersion);
|
||||
if (version.getMajor() <= 8) {
|
||||
candidateVersion = "1." + candidateVersion;
|
||||
version = Version.parse(candidateVersion);
|
||||
}
|
||||
|
||||
String implementor = normalizeImplementor(parseImplementor(it));
|
||||
|
||||
return new JdkInstallation(version, toDisplayName(implementor, candidateVersion), implementor, it);
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String parseImplementor(File candidateHome) {
|
||||
|
||||
List<String> release = FileUtils.readLines(new File(candidateHome, "release"));
|
||||
|
||||
for (String line : release) {
|
||||
|
||||
if (line.startsWith("IMPLEMENTOR=")) {
|
||||
String substring = line.substring(line.indexOf("=\""));
|
||||
substring = substring.substring(2, substring.length() - 1);
|
||||
return substring;
|
||||
}
|
||||
}
|
||||
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detector using the {@code java.home} system property.
|
||||
*/
|
||||
static class JavaHomeJdkDetector implements JdkDetector {
|
||||
|
||||
private static final File javaHome = new File(System.getProperty("java.home"));
|
||||
private static final String javaVersion = System.getProperty("java.version");
|
||||
private static final String javaVendor = System.getProperty("java.vendor");
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return isDirectory(javaHome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JdkInstallation> detect() {
|
||||
|
||||
return Collections.singletonList(new JdkInstallation(JavaVersion.parse(javaVersion),
|
||||
toDisplayName(javaVendor, javaVersion), normalizeImplementor(javaVendor), javaHome));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detector using the {@code /usr/libexec/java_home} utility storing Java installations in {@code /Libraries/Java} on
|
||||
* the Mac.
|
||||
*/
|
||||
static class MacNativeJdkDetector implements JdkDetector {
|
||||
|
||||
private static final File javaHomeBinary = new File("/usr/libexec/java_home");
|
||||
|
||||
private static final File nativeInstallationDirectory = new File("/Library/Java/JavaVirtualMachines");
|
||||
|
||||
private static final Pattern VERSION = Pattern.compile("((:?\\d+(:?\\.\\d+)*)(:?_+\\d+)?)");
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return isDirectory(nativeInstallationDirectory) && !ObjectUtils.isEmpty(nativeInstallationDirectory.listFiles())
|
||||
&& javaHomeBinary.exists() && SystemProperties.get("os.name").contains("Mac");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public List<JdkInstallation> detect() {
|
||||
|
||||
Process process = new ProcessBuilder(javaHomeBinary.toString(), "-X").redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||
.start();
|
||||
|
||||
process.waitFor(5, TimeUnit.SECONDS);
|
||||
byte[] out = StreamUtils.copyToByteArray(process.getInputStream());
|
||||
|
||||
if (process.exitValue() != 0) {
|
||||
|
||||
throw new IllegalStateException(javaHomeBinary + " failed with: " + System.lineSeparator() + new String(out)
|
||||
+ new String(StreamUtils.copyToByteArray(process.getErrorStream())));
|
||||
}
|
||||
|
||||
NSArray array = (NSArray) XMLPropertyListParser.parse(new ByteArrayInputStream(out));
|
||||
|
||||
return Arrays.stream(array.getArray()).map(it -> {
|
||||
|
||||
NSDictionary dict = (NSDictionary) it;
|
||||
|
||||
String jvmHomePath = dict.get("JVMHomePath").toJavaObject(String.class);
|
||||
String name = dict.get("JVMName").toJavaObject(String.class);
|
||||
String version = dict.get("JVMVersion").toJavaObject(String.class);
|
||||
String vendor = dict.get("JVMVendor").toJavaObject(String.class);
|
||||
|
||||
Matcher matcher = VERSION.matcher(version);
|
||||
if (!matcher.find()) {
|
||||
throw new IllegalArgumentException("Cannot determine JVM version number from JVMVersion " + version
|
||||
+ ". This should not happen in an ideal world, check the VERSION regex.");
|
||||
}
|
||||
|
||||
String implementor = normalizeImplementor(vendor);
|
||||
return new JdkInstallation(JavaVersion.parse(matcher.group(1)), toDisplayName(implementor, version),
|
||||
implementor, new File(jvmHomePath));
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class JdkInstallation implements Comparable<JdkInstallation> {
|
||||
|
||||
Version version;
|
||||
String name;
|
||||
String implementor;
|
||||
File home;
|
||||
|
||||
@Override
|
||||
public int compareTo(JdkInstallation o) {
|
||||
return this.version.compareTo(o.version);
|
||||
}
|
||||
}
|
||||
|
||||
static String normalizeImplementor(String implementor) {
|
||||
|
||||
if (implementor.equals("Eclipse Adoptium")) {
|
||||
return "Eclipse Temurin";
|
||||
}
|
||||
|
||||
if (implementor.equals("Eclipse Foundation")) {
|
||||
return "Eclipse Temurin";
|
||||
}
|
||||
|
||||
return implementor;
|
||||
}
|
||||
|
||||
static String toDisplayName(String implementor, String version) {
|
||||
|
||||
if (implementor.startsWith("Oracle")) {
|
||||
implementor = "Oracle Java";
|
||||
}
|
||||
|
||||
return implementor + " " + version;
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.io;
|
||||
|
||||
import static org.springframework.data.release.utils.StreamUtils.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Scanner;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstraction of the workspace that is used to work with the {@link Project}'s repositories, execute builds, etc.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class Workspace {
|
||||
|
||||
private static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||
|
||||
@NonNull IoProperties ioProperties;
|
||||
@NonNull ResourcePatternResolver resolver;
|
||||
@NonNull Logger logger;
|
||||
|
||||
/**
|
||||
* Returns the current working directory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public File getWorkingDirectory() {
|
||||
return ioProperties.getWorkDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current logs directory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public File getLogsDirectory() {
|
||||
return ioProperties.getLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the working directory by removing all files and folders in it.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void cleanup() throws IOException {
|
||||
|
||||
delete(getWorkingDirectory().toPath(), "workspace");
|
||||
delete(getLogsDirectory().toPath(), "logs");
|
||||
}
|
||||
|
||||
private void delete(Path path, String type) throws IOException {
|
||||
|
||||
logger.log("Workspace", "Cleaning up %s directory at %s.", type, path.toAbsolutePath());
|
||||
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
|
||||
if (!path.equals(dir)) {
|
||||
Files.delete(dir);
|
||||
}
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void purge(Path path, Predicate<Path> filter) throws IOException {
|
||||
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
|
||||
if (filter.test(file)) {
|
||||
Files.delete(file);
|
||||
}
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
|
||||
if (filter.test(dir)) {
|
||||
Files.delete(dir);
|
||||
}
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory for the given {@link Project}.
|
||||
*
|
||||
* @param project must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public File getProjectDirectory(Project project) {
|
||||
|
||||
Assert.notNull(project, "Project must not be null!");
|
||||
return new File(getWorkingDirectory(), project.getFolderName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the project directory for the given project already exists.
|
||||
*
|
||||
* @param project must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean hasProjectDirectory(Project project) {
|
||||
|
||||
Assert.notNull(project, "Project must not be null!");
|
||||
return getProjectDirectory(project).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file with the given name relative to the working directory for the given {@link Project}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param project must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public File getFile(String name, Project project) {
|
||||
|
||||
Assert.hasText(name, "Filename must not be null or empty!");
|
||||
Assert.notNull(project, "Project must not be null!");
|
||||
|
||||
return new File(getProjectDirectory(project), name);
|
||||
}
|
||||
|
||||
public Stream<File> getFiles(String pattern, Project project) {
|
||||
|
||||
File projectDirectory = getProjectDirectory(project);
|
||||
String patternToLookup = String.format("file:%s/%s", projectDirectory.getAbsolutePath(), pattern);
|
||||
|
||||
try {
|
||||
return Arrays.stream(resolver.getResources(patternToLookup)).map(wrap(Resource::getFile));
|
||||
} catch (IOException o_O) {
|
||||
throw new RuntimeException(o_O);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean processFile(String filename, Project project, LineCallback callback) {
|
||||
|
||||
File file = getFile(filename, project);
|
||||
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
try (Scanner scanner = new Scanner(file)) {
|
||||
|
||||
long number = 0;
|
||||
|
||||
while (scanner.hasNextLine()) {
|
||||
callback.doWith(scanner.nextLine(), number++).ifPresent(it -> builder.append(it).append("\n"));
|
||||
}
|
||||
|
||||
writeContentToFile(filename, project, builder.toString());
|
||||
|
||||
} catch (Exception o_O) {
|
||||
throw new RuntimeException(o_O);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writeContentToFile(String name, Project project, String content) throws IOException {
|
||||
|
||||
File file = getFile(name, project);
|
||||
Files.write(file.toPath(), Collections.singleton(content), UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the working directory and creates the folders if necessary.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@PostConstruct
|
||||
public void setUp() throws IOException {
|
||||
|
||||
Path path = getWorkingDirectory().toPath();
|
||||
|
||||
if (!java.nio.file.Files.exists(path)) {
|
||||
java.nio.file.Files.createDirectories(path);
|
||||
}
|
||||
}
|
||||
|
||||
public interface LineCallback {
|
||||
Optional<String> doWith(String line, long number);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.io;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class WorkspaceCommands extends TimedCommand {
|
||||
|
||||
@NonNull Workspace workspace;
|
||||
@NonNull Logger logger;
|
||||
|
||||
@CliCommand("workspace cleanup")
|
||||
public void cleanup() throws IOException {
|
||||
workspace.cleanup();
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.format.datetime.DateFormatter;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
@EqualsAndHashCode
|
||||
public class Changelog {
|
||||
|
||||
@Getter private final ModuleIteration module;
|
||||
private final Tickets tickets;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(true, "");
|
||||
|
||||
}
|
||||
|
||||
public String toString(boolean header, String indentation) {
|
||||
ArtifactVersion version = ArtifactVersion.of(module);
|
||||
|
||||
String headline = String.format("Changes in version %s (%s)", version,
|
||||
new DateFormatter("YYYY-MM-dd").print(new Date(), Locale.US));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (header) {
|
||||
|
||||
builder.append(indentation).append(headline).append(IOUtils.LINE_SEPARATOR).append(indentation);
|
||||
|
||||
for (int i = 0; i < headline.length(); i++) {
|
||||
builder.append("-");
|
||||
}
|
||||
|
||||
builder.append(IOUtils.LINE_SEPARATOR);
|
||||
}
|
||||
|
||||
for (Ticket ticket : tickets) {
|
||||
|
||||
String summary = ticket.getSummary();
|
||||
|
||||
builder.append(indentation).append("* ").append(ticket.getId()).append(" - ")
|
||||
.append(summary != null ? summary.trim() : "");
|
||||
|
||||
if (!summary.endsWith(".")) {
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
builder.append(IOUtils.LINE_SEPARATOR);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.Train;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.plugin.core.Plugin;
|
||||
|
||||
/**
|
||||
* Interface for issue tracker operations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface IssueTracker extends Plugin<Project> {
|
||||
|
||||
/**
|
||||
* Reset internal state (cache, etc).
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Returns all {@link Tickets} for the given {@link TrainIteration}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Tickets getTicketsFor(TrainIteration iteration);
|
||||
|
||||
/**
|
||||
* Returns all {@link Tickets} for the given {@link ModuleIteration}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Tickets getTicketsFor(ModuleIteration iteration);
|
||||
|
||||
/**
|
||||
* Returns all {@link Tickets} for the given {@link Train} and {@link Iteration}.
|
||||
*
|
||||
* @param iteration must not be {@literal null}.
|
||||
* @param forCurrentUser
|
||||
* @return
|
||||
*/
|
||||
Tickets getTicketsFor(TrainIteration iteration, boolean forCurrentUser);
|
||||
|
||||
/**
|
||||
* Returns the {@link Ticket} that tracks modifications in the context of a release.
|
||||
*
|
||||
* @param module the module to lookup the {@link Ticket} for, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Ticket getReleaseTicketFor(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Query the issue tracker for multiple {@link Ticket#id ticket Ids}. Tickets that are not found are not returned
|
||||
* within the result.
|
||||
*
|
||||
* @param project must not be {@literal null}.
|
||||
* @param ticketIds collection of {@link Ticket#id ticket Ids}, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Collection<Ticket> findTickets(Project project, Collection<String> ticketIds);
|
||||
|
||||
/**
|
||||
* Query the issue tracker for multiple {@link Ticket#id ticket Ids}. Tickets that are not found are not returned. The
|
||||
* implementation ensures to resolve only references that match the issue tracker scheme this issue tracker is
|
||||
* responsible for.
|
||||
*
|
||||
* @param moduleIteration must not be {@literal null}.
|
||||
* @param ticketReferences must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Tickets findTickets(ModuleIteration moduleIteration, Collection<String> ticketIds);
|
||||
|
||||
/**
|
||||
* Creates a release version if release version is missing.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
void createReleaseVersion(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Retire the release version from the active versions for a {@link ModuleIteration}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
void archiveReleaseVersion(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Create release ticket if release ticket is missing.
|
||||
* <p>
|
||||
* TODO: Return created ticket
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
void createReleaseTicket(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Creates a ticket for the given {@link ModuleIteration} and summary {@code text}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param text the text to use.
|
||||
* @param ticketType the ticket type.
|
||||
* @param assignToCurrentUser
|
||||
* @return the created ticket.
|
||||
*/
|
||||
Ticket createTicket(ModuleIteration module, String text, TicketType ticketType, boolean assignToCurrentUser);
|
||||
|
||||
/**
|
||||
* Assigns the ticket to the current user.
|
||||
*
|
||||
* @param project must not be {@literal null}.
|
||||
* @param ticket must not be {@literal null}.
|
||||
*/
|
||||
Ticket assignTicketToMe(Project project, Ticket ticket);
|
||||
|
||||
/**
|
||||
* Assigns the release ticket for the given {@link ModuleIteration} to the current user.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Ticket assignReleaseTicketToMe(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Start progress on release tickets.
|
||||
*
|
||||
* @param module
|
||||
* @return
|
||||
*/
|
||||
Ticket startReleaseTicketProgress(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Returns the {@link Changelog} for the given {@link ModuleIteration}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Changelog getChangelogFor(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Returns the {@link Changelog} for the given {@link ModuleIteration} using {@link TicketReference}s.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
default Changelog getChangelogFor(ModuleIteration module, List<TicketReference> ticketReferences) {
|
||||
|
||||
Tickets tickets = findTickets(module,
|
||||
ticketReferences.stream().map(TicketReference::getId).collect(Collectors.toList()));
|
||||
return Changelog.of(module, tickets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the given {@link ModuleIteration}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
void closeIteration(ModuleIteration module);
|
||||
|
||||
/**
|
||||
* Resolve a {@link Ticket}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param ticket must not be {@literal null}.
|
||||
*/
|
||||
void closeTicket(ModuleIteration module, Ticket ticket);
|
||||
|
||||
enum TicketType {
|
||||
Task, DependencyUpgrade;
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import static org.springframework.data.release.utils.ExecutionUtils.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.ExecutionUtils;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class IssueTrackerCommands extends TimedCommand {
|
||||
|
||||
@NonNull PluginRegistry<IssueTracker, Project> tracker;
|
||||
@NonNull Executor executor;
|
||||
|
||||
@CliCommand("tracker evict")
|
||||
public void evict() {
|
||||
StreamSupport.stream(tracker.spliterator(), false).forEach(IssueTracker::reset);
|
||||
}
|
||||
|
||||
@CliCommand(value = "tracker tickets")
|
||||
public String jira(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
|
||||
@CliOption(key = "for-current-user", specifiedDefaultValue = "true",
|
||||
unspecifiedDefaultValue = "false") boolean forCurrentUser) {
|
||||
|
||||
return tracker.getPlugins().stream().//
|
||||
flatMap(it -> it.getTicketsFor(iteration, forCurrentUser).stream()).//
|
||||
collect(Tickets.toTicketsCollector()).toString();
|
||||
}
|
||||
|
||||
@CliCommand(value = "tracker releasetickets")
|
||||
public String releaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
return runAndReturn(executor, iteration, module -> getTrackerFor(module).getReleaseTicketFor(module),
|
||||
Tickets.toTicketsCollector()).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare this release by self-assigning release tickets and setting them to in-progress.
|
||||
*
|
||||
* @param iteration
|
||||
* @return
|
||||
*/
|
||||
@CliCommand(value = "tracker prepare")
|
||||
public String trackerPrepare(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
jiraSelfAssignReleaseTickets(iteration);
|
||||
|
||||
return jiraStartProgress(iteration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a new, upcoming release by creating release versions and release tickets.
|
||||
*
|
||||
* @param iteration
|
||||
* @return
|
||||
*/
|
||||
@CliCommand(value = "tracker setup-next")
|
||||
public String trackerSetupNext(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
jiraCreateReleaseVersions(iteration);
|
||||
|
||||
return createReleaseTickets(iteration);
|
||||
}
|
||||
|
||||
@CliCommand(value = "tracker self-assign releasetickets")
|
||||
public String jiraSelfAssignReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
return runAndReturn(executor, iteration, module -> getTrackerFor(module).assignReleaseTicketToMe(module),
|
||||
Tickets.toTicketsCollector()).toString();
|
||||
}
|
||||
|
||||
public String jiraStartProgress(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
return runAndReturn(executor, iteration, module -> getTrackerFor(module).startReleaseTicketProgress(module),
|
||||
Tickets.toTicketsCollector()).toString();
|
||||
}
|
||||
|
||||
@CliCommand(value = "tracker create releaseversions")
|
||||
public void jiraCreateReleaseVersions(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
run(executor, iteration, module -> getTrackerFor(module).createReleaseVersion(module));
|
||||
}
|
||||
|
||||
@CliCommand(value = "tracker create releasetickets")
|
||||
public String createReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
run(executor, iteration, module -> getTrackerFor(module).createReleaseTicket(module));
|
||||
evict();
|
||||
|
||||
return releaseTickets(iteration);
|
||||
}
|
||||
|
||||
@CliCommand(value = "tracker create tickets")
|
||||
public String createTickets(@CliOption(key = "iteration", mandatory = true) TrainIteration iteration,
|
||||
@CliOption(key = "text", mandatory = true) String text) {
|
||||
|
||||
return iteration.stream().//
|
||||
map(module -> getTrackerFor(module).createTicket(module, text, IssueTracker.TicketType.Task, false))
|
||||
.collect(Tickets.toTicketsCollector())
|
||||
.toString();
|
||||
}
|
||||
|
||||
@CliCommand("tracker open-tickets")
|
||||
public String openTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
|
||||
@CliOption(key = "module") String moduleName,
|
||||
@CliOption(key = "filter-release-tickets") Boolean filterReleaseTickets) {
|
||||
|
||||
Predicate<Ticket> notResolved = it -> !it.isResolved();
|
||||
|
||||
return getTickets(iteration, moduleName,
|
||||
notResolved.and(getFilterPredicate(filterReleaseTickets == null || filterReleaseTickets)));
|
||||
}
|
||||
|
||||
@CliCommand("tracker all-tickets")
|
||||
public String allTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
|
||||
@CliOption(key = "module") String moduleName,
|
||||
@CliOption(key = "filter-release-tickets") Boolean filterReleaseTickets) {
|
||||
|
||||
return getTickets(iteration, moduleName, getFilterPredicate(filterReleaseTickets == null || filterReleaseTickets));
|
||||
}
|
||||
|
||||
private static Predicate<Ticket> getFilterPredicate(boolean filterReleaseTickets) {
|
||||
return it -> filterReleaseTickets == !it.isReleaseTicket();
|
||||
}
|
||||
|
||||
private String getTickets(TrainIteration iteration, String moduleName, Predicate<Ticket> ticketPredicate) {
|
||||
|
||||
if (StringUtils.hasText(moduleName)) {
|
||||
return getTicketsForProject(iteration, Projects.requiredByName(moduleName), ticketPredicate);
|
||||
}
|
||||
|
||||
return ExecutionUtils.runAndReturn(executor, iteration,
|
||||
moduleIteration -> {
|
||||
return getTicketsForProject(iteration, moduleIteration.getModule().getProject(), ticketPredicate);
|
||||
})
|
||||
.stream() //
|
||||
.filter(StringUtils::hasText) //
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private String getTicketsForProject(TrainIteration iteration, Project project, Predicate<Ticket> ticketPredicate) {
|
||||
|
||||
ModuleIteration module = iteration.getModule(project);
|
||||
|
||||
return getTrackerFor(module).getTicketsFor(module) //
|
||||
.stream() //
|
||||
.filter(ticketPredicate) //
|
||||
.collect(Tickets.toTicketsCollector()) //
|
||||
.toString(false);
|
||||
}
|
||||
|
||||
@CliCommand("tracker close")
|
||||
public void closeIteration(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
run(executor, iteration, module -> getTrackerFor(module).closeIteration(module));
|
||||
}
|
||||
|
||||
@CliCommand("tracker archive")
|
||||
public void archiveIteration(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
run(executor, iteration, module -> getTrackerFor(module).archiveReleaseVersion(module));
|
||||
}
|
||||
|
||||
private IssueTracker getTrackerFor(ModuleIteration moduleIteration) {
|
||||
|
||||
return tracker.getRequiredPluginFor(moduleIteration.getProject(),
|
||||
() -> String.format("No issue tracker found for module %s!", moduleIteration));
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.AuthCache;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.impl.auth.BasicScheme;
|
||||
import org.apache.http.impl.client.BasicAuthCache;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cache.CacheManager;
|
||||
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.data.release.issues.github.GitHubProperties;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.utils.HttpBasicCredentials;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.plugin.core.OrderAwarePluginRegistry;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
|
||||
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableCaching(proxyTargetClass = true)
|
||||
class IssueTrackerConfiguration {
|
||||
|
||||
@Bean
|
||||
CacheManager cacheManager() {
|
||||
return new ConcurrentMapCacheManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObjectMapper jacksonObjectMapper() {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
mapper.setSerializationInclusion(Include.NON_NULL);
|
||||
mapper.registerModule(new ParameterNamesModule(Mode.PROPERTIES));
|
||||
mapper.registerModule(new SyntheticLambdaFactoryMethodIgnoringModule());
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory(GitHubProperties gitHubProperties) {
|
||||
|
||||
// Preemptive auth
|
||||
CredentialsProvider credsProvider = new BasicCredentialsProvider();
|
||||
AuthCache authCache = new BasicAuthCache();
|
||||
|
||||
addPreemptiveAuth(credsProvider, authCache, gitHubProperties.getApiUrl(), gitHubProperties.getHttpCredentials());
|
||||
|
||||
Lazy<CloseableHttpClient> lazy = Lazy
|
||||
.of(() -> HttpClientBuilder.create().setDefaultCredentialsProvider(credsProvider).build());
|
||||
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory() {
|
||||
@Override
|
||||
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
|
||||
setHttpClient(lazy.get());
|
||||
return super.createRequest(uri, httpMethod);
|
||||
}
|
||||
};
|
||||
|
||||
factory.setHttpContextFactory((httpMethod, uri) -> {
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setAuthCache(authCache);
|
||||
return context;
|
||||
});
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("tracker")
|
||||
RestTemplateBuilder restTemplate(ClientHttpRequestFactory clientHttpRequestFactory,
|
||||
ObjectMapper jacksonObjectMapper) {
|
||||
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
converter.setObjectMapper(jacksonObjectMapper);
|
||||
|
||||
return new RestTemplateBuilder().messageConverters(converter).requestFactory(() -> clientHttpRequestFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PluginRegistry<IssueTracker, Project> issueTrackers(List<? extends IssueTracker> plugins) {
|
||||
return OrderAwarePluginRegistry.of(plugins);
|
||||
}
|
||||
|
||||
private static void addPreemptiveAuth(CredentialsProvider credsProvider, AuthCache authCache, String requestUrl,
|
||||
HttpBasicCredentials credentials) {
|
||||
HttpHost jiraHost = HttpHost.create(requestUrl);
|
||||
|
||||
credsProvider.setCredentials(new AuthScope(jiraHost),
|
||||
new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword().toString()));
|
||||
|
||||
authCache.put(jiraHost, new BasicScheme());
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class SyntheticLambdaFactoryMethodIgnoringModule extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = -3075786389813846512L;
|
||||
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
|
||||
context.insertAnnotationIntrospector(new NopAnnotationIntrospector() {
|
||||
|
||||
private static final long serialVersionUID = 479313244908256455L;
|
||||
|
||||
@Override
|
||||
public boolean hasIgnoreMarker(AnnotatedMember m) {
|
||||
|
||||
if (!(m instanceof AnnotatedMethod)) {
|
||||
return super.hasIgnoreMarker(m);
|
||||
}
|
||||
|
||||
AnnotatedMethod method = (AnnotatedMethod) m;
|
||||
|
||||
return method.getName().startsWith("lambda$") ? true : super.hasIgnoreMarker(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Tracker;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent a {@link Ticket}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
public class Ticket {
|
||||
|
||||
String id, summary;
|
||||
String url;
|
||||
String assignee;
|
||||
TicketStatus ticketStatus;
|
||||
|
||||
public Ticket(String id, String summary, String url, String assignee, TicketStatus ticketStatus) {
|
||||
this.id = id;
|
||||
this.summary = summary;
|
||||
this.url = url;
|
||||
this.assignee = assignee;
|
||||
this.ticketStatus = ticketStatus;
|
||||
}
|
||||
|
||||
public Ticket(String id, String summary, TicketStatus ticketStatus) {
|
||||
this.id = id;
|
||||
this.summary = summary;
|
||||
this.assignee = null;
|
||||
this.url = null;
|
||||
this.ticketStatus = ticketStatus;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%14s - %s (%s)", id, summary, url);
|
||||
}
|
||||
|
||||
public boolean isResolved() {
|
||||
return ticketStatus.isResolved();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current {@link Ticket} is the release ticket for the given {@link ModuleIteration}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean isReleaseTicketFor(ModuleIteration module) {
|
||||
|
||||
Assert.notNull(module, "Module must not be null!");
|
||||
return summary.startsWith(Tracker.releaseTicketSummary(module));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current {@link Ticket} is the release ticket by checking the summary prefix.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isReleaseTicket() {
|
||||
return summary.startsWith(Tracker.RELEASE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current {@link Ticket} is a release ticket for the given {@link TrainIteration}.
|
||||
*
|
||||
* @param train must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean isReleaseTicketFor(TrainIteration train) {
|
||||
return train.stream().anyMatch(this::isReleaseTicketFor);
|
||||
}
|
||||
|
||||
public boolean isAssignedTo(String username) {
|
||||
return username.equals(assignee);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.utils.Logger;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class TicketOperations {
|
||||
|
||||
Logger logger;
|
||||
|
||||
PluginRegistry<IssueTracker, Project> tracker;
|
||||
|
||||
/**
|
||||
* Create or look up ticket with a particular summary.
|
||||
*
|
||||
* @param module
|
||||
* @param summary
|
||||
* @return
|
||||
*/
|
||||
public Ticket getOrCreateTicketsWithSummary(ModuleIteration module, IssueTracker.TicketType ticketType,
|
||||
String summary) {
|
||||
return getOrCreateTicketsWithSummary(module, ticketType, Collections.singletonList(summary)).getTickets().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or look up tickets with a particular summary.
|
||||
*
|
||||
* @param module
|
||||
* @param ticketType
|
||||
* @param summaries
|
||||
* @return
|
||||
*/
|
||||
public Tickets getOrCreateTicketsWithSummary(ModuleIteration module, IssueTracker.TicketType ticketType,
|
||||
List<String> summaries) {
|
||||
|
||||
Project project = module.getProject();
|
||||
|
||||
IssueTracker tracker = this.tracker.getRequiredPluginFor(project);
|
||||
Tickets tickets = tracker.getTicketsFor(module);
|
||||
List<Ticket> results = new ArrayList<>();
|
||||
|
||||
for (String summary : summaries) {
|
||||
|
||||
Optional<Ticket> upgradeTicket = findBySummary(tickets, summary);
|
||||
|
||||
if (upgradeTicket.isPresent()) {
|
||||
logger.log(project, "Found ticket %s", upgradeTicket.get());
|
||||
upgradeTicket.ifPresent(it -> {
|
||||
tracker.assignTicketToMe(project, it);
|
||||
results.add(it);
|
||||
});
|
||||
} else {
|
||||
|
||||
logger.log(module, "Creating ticket for %s", summary);
|
||||
Ticket ticket = tracker.createTicket(module, summary, ticketType, true);
|
||||
results.add(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
return new Tickets(results);
|
||||
}
|
||||
|
||||
private Optional<Ticket> findBySummary(Tickets tickets, String summary) {
|
||||
|
||||
List<Ticket> result = tickets.filter(it -> it.getSummary().equals(summary)).toList();
|
||||
|
||||
if (result.size() > 1) {
|
||||
throw new IllegalStateException("Multiple tickets found: " + result);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(result.isEmpty() ? null : result.get(0));
|
||||
}
|
||||
|
||||
public void closeTicket(ModuleIteration module, Ticket ticket) {
|
||||
closeTickets(module, new Tickets(Collections.singletonList(ticket)));
|
||||
}
|
||||
|
||||
public void closeTickets(ModuleIteration module, Tickets tickets) {
|
||||
|
||||
IssueTracker tracker = this.tracker.getRequiredPluginFor(module.getProject());
|
||||
|
||||
for (Ticket ticket : tickets) {
|
||||
tracker.closeTicket(module, ticket);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
public class TicketReference implements Comparable<TicketReference> {
|
||||
|
||||
String id;
|
||||
String message;
|
||||
Style style;
|
||||
|
||||
public TicketReference(String id, String message, Style style) {
|
||||
this.id = normalize(id);
|
||||
this.message = message;
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
private static String normalize(String id) {
|
||||
|
||||
if (id.toLowerCase().startsWith("gh-")) {
|
||||
return "#" + id.substring(3);
|
||||
}
|
||||
|
||||
return id.toUpperCase().replaceAll(" ", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TicketReference o) {
|
||||
|
||||
if (id.startsWith("#") && o.id.startsWith("#")) {
|
||||
return Integer.compare(Integer.parseInt(id.substring(1)), Integer.parseInt(o.id.substring(1)));
|
||||
}
|
||||
|
||||
return id.compareToIgnoreCase(o.id);
|
||||
}
|
||||
|
||||
public enum Style {
|
||||
GitHub, Jira;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface TicketStatus {
|
||||
|
||||
/**
|
||||
* Returns
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Returns whether the ticket is marked as resolved.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isResolved();
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
import org.springframework.data.release.model.Tracker;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.ListWrapperCollector;
|
||||
import org.springframework.data.util.Streamable;
|
||||
|
||||
/**
|
||||
* Value object to represent a list of {@link Ticket}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@RequiredArgsConstructor
|
||||
public class Tickets implements Streamable<Ticket> {
|
||||
|
||||
List<Ticket> tickets;
|
||||
int overallTotal;
|
||||
|
||||
public Tickets(List<Ticket> tickets) {
|
||||
this(Collections.unmodifiableList(tickets), tickets.size());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Ticket> iterator() {
|
||||
return tickets.iterator();
|
||||
}
|
||||
|
||||
public boolean hasReleaseTicket(ModuleIteration moduleIteration) {
|
||||
return findReleaseTicket(moduleIteration).isPresent();
|
||||
}
|
||||
|
||||
public Ticket getReleaseTicket(ModuleIteration moduleIteration) {
|
||||
|
||||
return findReleaseTicket(moduleIteration).orElseThrow(
|
||||
() -> new IllegalArgumentException(String.format("Did not find a release ticket for %s containing %s!",
|
||||
moduleIteration, Tracker.releaseTicketSummary(moduleIteration))));
|
||||
}
|
||||
|
||||
public Tickets getIssueTickets(ModuleIteration moduleIteration) {
|
||||
return tickets.stream(). //
|
||||
filter(ticket -> !ticket.isReleaseTicketFor(moduleIteration)).//
|
||||
collect(toTicketsCollector());
|
||||
}
|
||||
|
||||
public Tickets getReleaseTickets(TrainIteration iteration) {
|
||||
|
||||
return stream().//
|
||||
filter(ticket -> ticket.isReleaseTicketFor(iteration)).//
|
||||
distinct().//
|
||||
collect(toTicketsCollector());
|
||||
}
|
||||
|
||||
private Optional<Ticket> findReleaseTicket(ModuleIteration moduleIteration) {
|
||||
|
||||
return stream().//
|
||||
filter(ticket -> ticket.isReleaseTicketFor(moduleIteration)).//
|
||||
findFirst();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(true);
|
||||
}
|
||||
|
||||
public String toString(boolean header) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (header) {
|
||||
builder.append(String.format("Train only tickets: %s of %s", tickets.size(), overallTotal));
|
||||
builder.append(IOUtils.LINE_SEPARATOR);
|
||||
}
|
||||
builder.append(tickets.stream().map(it -> "\t" + it).collect(Collectors.joining(IOUtils.LINE_SEPARATOR)));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new collector to toTicketsCollector {@link Ticket} as {@link Tickets} using the {@link Stream} API.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Collector<? super Ticket, ?, Tickets> toTicketsCollector() {
|
||||
return ListWrapperCollector.collectInto(Tickets::new);
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Generates a changelog markdown file which includes bug fixes, enhancements and contributors for a given milestone.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Component
|
||||
public class ChangelogGenerator {
|
||||
|
||||
private static final Pattern ghUserMentionPattern = Pattern.compile("(^|[^\\w`])(@[\\w-]+)");
|
||||
|
||||
@Getter
|
||||
private final Set<String> excludeLabels;
|
||||
|
||||
@Getter
|
||||
private final Set<String> excludeContributors;
|
||||
|
||||
@Getter
|
||||
private final String contributorsTitle;
|
||||
|
||||
private final ChangelogSections sections;
|
||||
|
||||
public ChangelogGenerator() {
|
||||
this.excludeLabels = new HashSet<>(Arrays.asList("type: task"));
|
||||
this.excludeContributors = new LinkedHashSet<>();
|
||||
this.contributorsTitle = null;
|
||||
this.sections = new ChangelogSections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a file at the given path which includes bug fixes, enhancements and contributors for the given milestone.
|
||||
*
|
||||
* @param issues the issues to generate the changelog for
|
||||
* @param sectionContentPostProcessor the postprocessor for a changelog section
|
||||
*/
|
||||
public String generate(List<GitHubReadIssue> issues,
|
||||
BiFunction<ChangelogSection, String, String> sectionContentPostProcessor) {
|
||||
return generateContent(issues, sectionContentPostProcessor);
|
||||
}
|
||||
|
||||
private boolean isExcluded(GitHubReadIssue issue) {
|
||||
return issue.getLabels().stream().anyMatch(this::isExcluded);
|
||||
}
|
||||
|
||||
private boolean isExcluded(Label label) {
|
||||
return this.excludeLabels.contains(label.getName());
|
||||
}
|
||||
|
||||
private String generateContent(List<GitHubReadIssue> issues,
|
||||
BiFunction<ChangelogSection, String, String> sectionContentPostProcessor) {
|
||||
StringBuilder content = new StringBuilder();
|
||||
addSectionContent(content, this.sections.collate(issues), sectionContentPostProcessor);
|
||||
Set<GitHubUser> contributors = getContributors(issues);
|
||||
if (!contributors.isEmpty()) {
|
||||
addContributorsContent(content, contributors);
|
||||
}
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
private void addSectionContent(StringBuilder result, Map<ChangelogSection, List<GitHubReadIssue>> sectionIssues,
|
||||
BiFunction<ChangelogSection, String, String> sectionContentPostProcessor) {
|
||||
|
||||
sectionIssues.forEach((section, issues) -> {
|
||||
|
||||
issues.sort(Comparator.reverseOrder());
|
||||
|
||||
StringBuilder content = new StringBuilder();
|
||||
|
||||
content.append((content.length() != 0) ? String.format("%n") : "");
|
||||
content.append("## ").append(section).append(String.format("%n%n"));
|
||||
issues.stream().map(this::getFormattedIssue).forEach(content::append);
|
||||
|
||||
result.append(sectionContentPostProcessor.apply(section, content.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
private String getFormattedIssue(GitHubReadIssue issue) {
|
||||
String title = issue.getTitle();
|
||||
title = ghUserMentionPattern.matcher(title).replaceAll("$1`$2`");
|
||||
return String.format("- %s %s%n", title, getLinkToIssue(issue));
|
||||
}
|
||||
|
||||
private String getLinkToIssue(GitHubIssue issue) {
|
||||
return "[" + issue.getId() + "]" + "(" + issue.getUrl() + ")";
|
||||
}
|
||||
|
||||
private Set<GitHubUser> getContributors(List<GitHubReadIssue> issues) {
|
||||
if (this.excludeContributors.contains("*")) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return issues.stream().filter((issue) -> issue.getPullRequest() != null).map(GitHubReadIssue::getUser)
|
||||
.filter(this::isIncludedContributor).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isIncludedContributor(GitHubUser user) {
|
||||
return !this.excludeContributors.contains(user.getName());
|
||||
}
|
||||
|
||||
private void addContributorsContent(StringBuilder content, Set<GitHubUser> contributors) {
|
||||
content.append(String.format("%n## "));
|
||||
content.append((this.contributorsTitle != null) ? this.contributorsTitle : ":heart: Contributors");
|
||||
content.append(String.format("%n%nWe'd like to thank all the contributors who worked on this release!%n%n"));
|
||||
contributors.stream().map(this::formatContributors).forEach(content::append);
|
||||
}
|
||||
|
||||
private String formatContributors(GitHubUser c) {
|
||||
return String.format("- [@%s](%s)%n", c.getName(), c.getUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A single section of a changelog report.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ChangelogSection {
|
||||
|
||||
private final String title;
|
||||
|
||||
private final String group;
|
||||
|
||||
private final Set<String> labels;
|
||||
|
||||
ChangelogSection(String title, String group, String... labels) {
|
||||
this(title, group, new LinkedHashSet<>(Arrays.asList(labels)));
|
||||
}
|
||||
|
||||
ChangelogSection(String title, String group, Set<String> labels) {
|
||||
Assert.hasText(title, "Title must not be empty");
|
||||
Assert.isTrue(!CollectionUtils.isEmpty(labels), "Labels must not be empty");
|
||||
this.title = title;
|
||||
this.group = group;
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
String getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
boolean isMatchFor(GitHubReadIssue issue) {
|
||||
for (String candidate : this.labels) {
|
||||
for (Label label : issue.getLabels()) {
|
||||
if (label.getName().contains(candidate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasLabel(String label) {
|
||||
return this.labels.contains(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Manages sections of the changelog report.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ChangelogSections {
|
||||
|
||||
private static final List<ChangelogSection> DEFAULT_SECTIONS;
|
||||
static {
|
||||
List<ChangelogSection> sections = new ArrayList<>();
|
||||
add(sections, ":star: New Features", "enhancement");
|
||||
add(sections, ":lady_beetle: Bug Fixes", "bug", "regression");
|
||||
add(sections, ":notebook_with_decorative_cover: Documentation", "documentation");
|
||||
add(sections, ":hammer: Dependency Upgrades", "dependency-upgrade");
|
||||
DEFAULT_SECTIONS = Collections.unmodifiableList(sections);
|
||||
}
|
||||
|
||||
private static void add(List<ChangelogSection> sections, String title, String... labels) {
|
||||
sections.add(new ChangelogSection(title, null, labels));
|
||||
}
|
||||
|
||||
private final List<ChangelogSection> sections;
|
||||
|
||||
ChangelogSections() {
|
||||
this.sections = DEFAULT_SECTIONS;
|
||||
}
|
||||
|
||||
Map<ChangelogSection, List<GitHubReadIssue>> collate(List<GitHubReadIssue> issues) {
|
||||
|
||||
SortedMap<ChangelogSection, List<GitHubReadIssue>> collated = new TreeMap<>(
|
||||
Comparator.comparing(this.sections::indexOf));
|
||||
|
||||
for (GitHubReadIssue issue : issues) {
|
||||
List<ChangelogSection> sections = getSections(issue);
|
||||
for (ChangelogSection section : sections) {
|
||||
collated.computeIfAbsent(section, (key) -> new ArrayList<>());
|
||||
collated.get(section).add(issue);
|
||||
}
|
||||
}
|
||||
return collated;
|
||||
}
|
||||
|
||||
private List<ChangelogSection> getSections(GitHubReadIssue issue) {
|
||||
List<ChangelogSection> result = new ArrayList<>();
|
||||
Set<String> groupClaimes = new HashSet<>();
|
||||
for (ChangelogSection section : this.sections) {
|
||||
if (section.isMatchFor(issue) && groupClaimes.add(section.getGroup())) {
|
||||
result.add(section);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,733 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
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.issues.Changelog;
|
||||
import org.springframework.data.release.issues.IssueTracker;
|
||||
import org.springframework.data.release.issues.Ticket;
|
||||
import org.springframework.data.release.issues.Tickets;
|
||||
import org.springframework.data.release.model.ArtifactVersion;
|
||||
import org.springframework.data.release.model.DocumentationMetadata;
|
||||
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.Projects;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
public class GitHub extends GitHubSupport implements IssueTracker {
|
||||
|
||||
private static final String MILESTONE_URI = "/repos/spring-projects/{repoName}/milestones?state={state}";
|
||||
private static final String ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE = "/repos/spring-projects/{repoName}/issues?milestone={id}&state=all&assignee={assignee}";
|
||||
private static final String ISSUES_BY_MILESTONE_URI_TEMPLATE = "/repos/spring-projects/{repoName}/issues?milestone={id}&state=all";
|
||||
private static final String MILESTONES_URI_TEMPLATE = "/repos/spring-projects/{repoName}/milestones";
|
||||
private static final String MILESTONE_BY_ID_URI_TEMPLATE = "/repos/spring-projects/{repoName}/milestones/{id}";
|
||||
private static final String ISSUE_BY_ID_URI_TEMPLATE = "/repos/spring-projects/{repoName}/issues/{id}";
|
||||
private static final String ISSUES_URI_TEMPLATE = "/repos/spring-projects/{repoName}/issues";
|
||||
private static final String RELEASE_BY_TAG_URI_TEMPLATE = "/repos/spring-projects/{repoName}/releases/tags/{tag}";
|
||||
private static final String RELEASE_URI_TEMPLATE = "/repos/spring-projects/{repoName}/releases";
|
||||
private static final String RELEASE_BY_ID_URI_TEMPLATE = "/repos/spring-projects/{repoName}/releases/{id}";
|
||||
|
||||
private static final ParameterizedTypeReference<List<Milestone>> MILESTONES_TYPE = new ParameterizedTypeReference<List<Milestone>>() {};
|
||||
private static final ParameterizedTypeReference<List<GitHubReadIssue>> ISSUES_TYPE = new ParameterizedTypeReference<List<GitHubReadIssue>>() {};
|
||||
private static final ParameterizedTypeReference<GitHubReadIssue> ISSUE_TYPE = new ParameterizedTypeReference<GitHubReadIssue>() {};
|
||||
|
||||
private static final Map<TicketType, Label> TICKET_LABELS = new HashMap<>();
|
||||
|
||||
static {
|
||||
|
||||
TICKET_LABELS.put(TicketType.Task, LabelConfiguration.TYPE_TASK);
|
||||
TICKET_LABELS.put(TicketType.DependencyUpgrade, LabelConfiguration.TYPE_DEPENDENCY_UPGRADE);
|
||||
}
|
||||
|
||||
private final Logger logger;
|
||||
private final GitHubProperties properties;
|
||||
|
||||
|
||||
public GitHub(@Qualifier("tracker") RestTemplateBuilder templateBuilder, Logger logger, GitHubProperties properties) {
|
||||
|
||||
super(createOperations(templateBuilder, properties));
|
||||
this.logger = logger;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.JiraConnector#flushTickets()
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(value = { "tickets", "release-tickets", "milestone" }, allEntries = true)
|
||||
public void reset() {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.IssueTracker#getReleaseTicketFor(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
@Cacheable("release-tickets")
|
||||
public Ticket getReleaseTicketFor(ModuleIteration module) {
|
||||
return getTicketsFor(module).getReleaseTicket(module);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see IssueTracker#findTickets(Project, Collection)
|
||||
*/
|
||||
@Override
|
||||
@Cacheable("tickets")
|
||||
public Collection<Ticket> findTickets(Project project, Collection<String> ticketIds) {
|
||||
|
||||
String repositoryName = GitProject.of(project).getRepositoryName();
|
||||
List<Ticket> tickets = new ArrayList<>();
|
||||
|
||||
ticketIds.forEach(ticketId -> {
|
||||
|
||||
GitHubReadIssue ticket = findTicket(repositoryName, ticketId);
|
||||
if (ticket != null) {
|
||||
tickets.add(toTicket(ticket));
|
||||
}
|
||||
});
|
||||
|
||||
return tickets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tickets findTickets(ModuleIteration moduleIteration, Collection<String> ticketIds) {
|
||||
|
||||
return findGitHubIssues(moduleIteration, ticketIds).stream().map(GitHub::toTicket)
|
||||
.collect(Tickets.toTicketsCollector());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.IssueTracker#getChangelogFor(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
@Cacheable("changelogs")
|
||||
public Changelog getChangelogFor(ModuleIteration moduleIteration) {
|
||||
|
||||
Tickets tickets = getIssuesFor(moduleIteration, false, false).//
|
||||
map(issue -> toTicket(issue)).//
|
||||
collect(Tickets.toTicketsCollector());
|
||||
|
||||
logger.log(moduleIteration, "Created changelog with %s entries.", tickets.getOverallTotal());
|
||||
|
||||
return Changelog.of(moduleIteration, tickets);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Project project) {
|
||||
return project.uses(Tracker.GITHUB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tickets getTicketsFor(TrainIteration iteration) {
|
||||
return getTicketsFor(iteration, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.GitHubIssueConnector#getTicketsFor(org.springframework.data.release.model.TrainIteration, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Tickets getTicketsFor(TrainIteration trainIteration, boolean forCurrentUser) {
|
||||
|
||||
if (forCurrentUser) {
|
||||
logger.log(trainIteration, "Retrieving tickets (for user %s)…", properties.getUsername());
|
||||
} else {
|
||||
logger.log(trainIteration, "Retrieving tickets…");
|
||||
}
|
||||
|
||||
Tickets tickets = trainIteration.stream(). //
|
||||
filter(moduleIteration -> supports(moduleIteration.getProject())). //
|
||||
flatMap(moduleIteration -> getTicketsFor(moduleIteration, forCurrentUser).stream()). //
|
||||
collect(Tickets.toTicketsCollector());
|
||||
|
||||
return tickets;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.tracker.IssueTracker#createReleaseVersion(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
public void createReleaseVersion(ModuleIteration moduleIteration) {
|
||||
|
||||
Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
|
||||
|
||||
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
|
||||
Optional<Milestone> milestone = findMilestone(moduleIteration, repositoryName);
|
||||
|
||||
if (milestone.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GithubMilestone githubMilestone = new GithubMilestone(moduleIteration);
|
||||
logger.log(moduleIteration, "Creating GitHub milestone %s", githubMilestone);
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
|
||||
operations.exchange(MILESTONES_URI_TEMPLATE, HttpMethod.POST,
|
||||
new HttpEntity<Object>(githubMilestone.toMilestone(), httpHeaders), Milestone.class, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.tracker.IssueTracker#retireReleaseVersion(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
public void archiveReleaseVersion(ModuleIteration module) {
|
||||
logger.log(module, "Skipping milestone archival");
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.tracker.IssueTracker#createReleaseTicket(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
public void createReleaseTicket(ModuleIteration moduleIteration) {
|
||||
|
||||
Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
|
||||
|
||||
Tickets tickets = getTicketsFor(moduleIteration);
|
||||
if (tickets.hasReleaseTicket(moduleIteration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(moduleIteration, "Creating release ticket…");
|
||||
|
||||
doCreateTicket(moduleIteration, Tracker.releaseTicketSummary(moduleIteration), TicketType.Task, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ticket createTicket(ModuleIteration moduleIteration, String text, TicketType ticketType,
|
||||
boolean assignToCurrentUser) {
|
||||
|
||||
logger.log(moduleIteration, "Creating ticket…");
|
||||
|
||||
return doCreateTicket(moduleIteration, text, ticketType, assignToCurrentUser);
|
||||
}
|
||||
|
||||
private Ticket doCreateTicket(ModuleIteration moduleIteration, String text, TicketType ticketType,
|
||||
boolean assignToCurrentUser) {
|
||||
|
||||
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
|
||||
Milestone milestone = getMilestone(moduleIteration, repositoryName);
|
||||
|
||||
Label label = TICKET_LABELS.get(ticketType);
|
||||
|
||||
GitHubWriteIssue gitHubIssue = GitHubWriteIssue.of(text, milestone).withLabel(label.getName());
|
||||
|
||||
if (assignToCurrentUser) {
|
||||
gitHubIssue = gitHubIssue.withAssignees(Collections.singletonList(properties.getUsername()));
|
||||
}
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
|
||||
GitHubReadIssue body = operations.exchange(ISSUES_URI_TEMPLATE, HttpMethod.POST,
|
||||
new HttpEntity<Object>(gitHubIssue), GitHubReadIssue.class, parameters).getBody();
|
||||
|
||||
return toTicket(body);
|
||||
}
|
||||
|
||||
@Cacheable("tickets")
|
||||
public Tickets getTicketsFor(ModuleIteration iteration) {
|
||||
return getTicketsFor(iteration, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.IssueTracker#assignTicketToMe(org.springframework.data.release.jira.Ticket)
|
||||
*/
|
||||
@Override
|
||||
public Ticket assignTicketToMe(Project project, Ticket ticket) {
|
||||
|
||||
Assert.notNull(ticket, "Ticket must not be null.");
|
||||
|
||||
if (ticket.isAssignedTo(properties.getUsername())) {
|
||||
logger.log("Ticket", "Skipping self-assignment of %s", ticket);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
String repositoryName = GitProject.of(project).getRepositoryName();
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
parameters.put("id", stripHash(ticket));
|
||||
|
||||
GitHubWriteIssue edit = GitHubWriteIssue.assignedTo(properties.getUsername());
|
||||
|
||||
GitHubReadIssue response = operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH,
|
||||
new HttpEntity<>(edit, new HttpHeaders()), ISSUE_TYPE, parameters).getBody();
|
||||
|
||||
return toTicket(response);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.IssueTracker#assignReleaseTicketToMe(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
public Ticket assignReleaseTicketToMe(ModuleIteration module) {
|
||||
|
||||
Assert.notNull(module, "ModuleIteration must not be null.");
|
||||
|
||||
return assignTicketToMe(module.getProject(), getReleaseTicketFor(module));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.IssueTracker#startReleaseTicketProgress(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
public Ticket startReleaseTicketProgress(ModuleIteration module) {
|
||||
return getReleaseTicketFor(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the release ticket.
|
||||
*
|
||||
* @param module
|
||||
* @return
|
||||
*/
|
||||
public Ticket closeReleaseTicket(ModuleIteration module) {
|
||||
|
||||
Assert.notNull(module, "ModuleIteration must not be null.");
|
||||
|
||||
Ticket releaseTicketFor = getReleaseTicketFor(module);
|
||||
GitHubReadIssue response = close(module, releaseTicketFor);
|
||||
|
||||
return toTicket(response);
|
||||
}
|
||||
|
||||
private GitHubReadIssue close(ModuleIteration module, Ticket ticket) {
|
||||
|
||||
String repositoryName = GitProject.of(module.getProject()).getRepositoryName();
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
parameters.put("id", stripHash(ticket));
|
||||
|
||||
GitHubWriteIssue edit = GitHubWriteIssue.assignedTo(properties.getUsername()).close();
|
||||
|
||||
return operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH,
|
||||
new HttpEntity<>(edit, new HttpHeaders()), ISSUE_TYPE, parameters).getBody();
|
||||
}
|
||||
|
||||
private String stripHash(Ticket ticket) {
|
||||
return ticket.getId().startsWith("#") ? ticket.getId().substring(1) : ticket.getId();
|
||||
}
|
||||
|
||||
private Map<String, Object> newUrlTemplateVariables() {
|
||||
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private Optional<Milestone> findMilestone(ModuleIteration moduleIteration, String repositoryName) {
|
||||
return doFindMilestone(moduleIteration, repositoryName, m -> m.matches(moduleIteration));
|
||||
}
|
||||
|
||||
private Optional<Milestone> doFindMilestone(ModuleIteration moduleIteration, String repositoryName,
|
||||
Predicate<Milestone> milestonePredicate) {
|
||||
|
||||
AtomicReference<Milestone> milestoneRef = new AtomicReference<>();
|
||||
|
||||
for (String state : Arrays.asList("open", "closed")) {
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
parameters.put("state", state);
|
||||
|
||||
logger.log(moduleIteration, "Looking up milestone…");
|
||||
|
||||
doWithPaging(MILESTONE_URI, HttpMethod.GET, parameters, new HttpEntity<>(new HttpHeaders()),
|
||||
MILESTONES_TYPE, milestones -> {
|
||||
|
||||
Optional<Milestone> milestone = milestones.stream(). //
|
||||
filter(milestonePredicate). //
|
||||
findFirst(). //
|
||||
map(m -> {
|
||||
logger.log(moduleIteration, "Found milestone %s.", m);
|
||||
return m;
|
||||
});
|
||||
|
||||
if (milestone.isPresent()) {
|
||||
milestoneRef.set(milestone.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (milestoneRef.get() != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.ofNullable(milestoneRef.get());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.issues.IssueTracker#closeIteration(org.springframework.data.release.model.ModuleIteration)
|
||||
*/
|
||||
@Override
|
||||
public void closeIteration(ModuleIteration module) {
|
||||
|
||||
// for each module
|
||||
|
||||
// - close all tickets
|
||||
// -- make sure only one ticket is open
|
||||
// -- resolve open ticket
|
||||
// -- close tickets
|
||||
|
||||
// - mark version as released
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
|
||||
GitProject project = GitProject.of(module.getProject());
|
||||
|
||||
findMilestone(module, project.getRepositoryName()) //
|
||||
.filter(Milestone::isOpen) //
|
||||
.map(Milestone::markReleased) //
|
||||
.ifPresent(milestone -> {
|
||||
|
||||
logger.log(module, "Marking milestone %s as released.", milestone);
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", project.getRepositoryName());
|
||||
parameters.put("id", milestone.getNumber());
|
||||
|
||||
operations.exchange(MILESTONE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH,
|
||||
new HttpEntity<Object>(milestone, httpHeaders), Map.class, parameters);
|
||||
});
|
||||
|
||||
// - if no next version exists, create
|
||||
|
||||
closeReleaseTicket(module);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeTicket(ModuleIteration module, Ticket ticket) {
|
||||
close(module, ticket);
|
||||
}
|
||||
|
||||
List<GitHubReadIssue> findGitHubIssues(ModuleIteration moduleIteration, Collection<String> ticketIds) {
|
||||
|
||||
logger.log(moduleIteration, "Looking up GitHub issues from milestone …");
|
||||
|
||||
Map<String, GitHubReadIssue> issues = getIssuesFor(moduleIteration, false, true)
|
||||
.collect(Collectors.toMap(GitHubIssue::getId, Function.identity()));
|
||||
|
||||
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
|
||||
|
||||
logger.log(moduleIteration, "Looking up GitHub issues …");
|
||||
Collection<GitHubReadIssue> foundIssues = ticketIds.stream().filter(it -> it.startsWith("#")).flatMap(it -> {
|
||||
|
||||
GitHubReadIssue ticket = getTicket(issues, repositoryName, it);
|
||||
|
||||
if (ticket != null) {
|
||||
return Stream.of(ticket);
|
||||
}
|
||||
|
||||
return Stream.empty();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
List<GitHubReadIssue> gitHubIssues = foundIssues.stream().filter(it -> {
|
||||
Ticket ticket = toTicket(it);
|
||||
return !ticket.isReleaseTicketFor(moduleIteration) && !ticket.isReleaseTicket();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
logger.log(moduleIteration, "Found %s tickets.", gitHubIssues.size());
|
||||
|
||||
return gitHubIssues;
|
||||
}
|
||||
|
||||
private GitHubReadIssue getTicket(Map<String, GitHubReadIssue> cache, String repositoryName,
|
||||
String ticketId) {
|
||||
|
||||
if (cache.containsKey(ticketId)) {
|
||||
return cache.get(ticketId);
|
||||
}
|
||||
|
||||
return findTicket(repositoryName, ticketId);
|
||||
}
|
||||
|
||||
private Tickets getTicketsFor(ModuleIteration moduleIteration, boolean forCurrentUser) {
|
||||
|
||||
return getIssuesFor(moduleIteration, forCurrentUser, false).//
|
||||
map(GitHub::toTicket).//
|
||||
collect(Tickets.toTicketsCollector());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param repositoryName
|
||||
* @param ticketId
|
||||
* @return
|
||||
*/
|
||||
private GitHubReadIssue findTicket(String repositoryName, String ticketId) {
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
parameters.put("id", ticketId.startsWith("#") ? ticketId.substring(1) : ticketId);
|
||||
|
||||
try {
|
||||
|
||||
return operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.GET,
|
||||
new HttpEntity<>(new HttpHeaders()), ISSUE_TYPE, parameters).getBody();
|
||||
} catch (HttpStatusCodeException e) {
|
||||
|
||||
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param module
|
||||
* @param ticketReferences
|
||||
*/
|
||||
public void createOrUpdateRelease(ModuleIteration module, List<String> ticketIds) {
|
||||
|
||||
logger.log(module, "Preparing GitHub Release …");
|
||||
|
||||
List<GitHubReadIssue> gitHubIssues = findGitHubIssues(module, ticketIds);
|
||||
|
||||
ArtifactVersion version = ArtifactVersion.of(module);
|
||||
DocumentationMetadata documentation = DocumentationMetadata.of(module.getProject(), version, false);
|
||||
|
||||
ChangelogGenerator generator = new ChangelogGenerator();
|
||||
generator.getExcludeContributors().addAll(properties.getTeam());
|
||||
|
||||
String releaseBody = generator.generate(gitHubIssues, (changelogSection, s) -> s);
|
||||
String documentationLinks = getDocumentationLinks(module, documentation);
|
||||
|
||||
if (module.getProject() == Projects.BOM || module.getProject() == Projects.BUILD) {
|
||||
// We don't ship Javadoc/reference doc for build and BOM
|
||||
createOrUpdateRelease(module, String.format("%s%n", documentationLinks, releaseBody));
|
||||
} else {
|
||||
createOrUpdateRelease(module, String.format("## :green_book: Links%n%s%n%s%n", documentationLinks, releaseBody));
|
||||
}
|
||||
|
||||
logger.log(module, "GitHub Release up to date");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify GitHub authentication.
|
||||
*/
|
||||
public void verifyAuthentication() {
|
||||
|
||||
logger.log("GitHub", "Verifying GitHub Authentication…");
|
||||
|
||||
String repositoryName = GitProject.of(Projects.BUILD).getRepositoryName();
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
|
||||
// /user requires authentication
|
||||
ResponseEntity<Object> entity = operations.getForEntity("/user", Object.class);
|
||||
|
||||
if (!entity.getStatusCode().is2xxSuccessful()) {
|
||||
throw new IllegalStateException(String.format("Cannot obtain /user. Status: %s", entity.getStatusCode()));
|
||||
}
|
||||
|
||||
logger.log("GitHub", "Authentication verified!");
|
||||
}
|
||||
|
||||
private String getDocumentationLinks(ModuleIteration module, DocumentationMetadata documentation) {
|
||||
|
||||
if (module.getProject() == Projects.BUILD || module.getProject() == Projects.BOM) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String referenceDocUrl = documentation.getReferenceDocUrl();
|
||||
String apiDocUrl = documentation.getApiDocUrl();
|
||||
|
||||
String reference = String.format("* [%s %s Reference documentation](%s)", module.getProject().getFullName(),
|
||||
module.getVersion().toString(), referenceDocUrl);
|
||||
|
||||
String apidoc = String.format("* [%s %s Javadoc](%s)", module.getProject().getFullName(),
|
||||
module.getVersion().toString(), apiDocUrl);
|
||||
|
||||
return String.format("%s%n%s%n", reference, apidoc);
|
||||
}
|
||||
|
||||
private void createOrUpdateRelease(ModuleIteration module, String body) {
|
||||
|
||||
String repositoryName = GitProject.of(module.getProject()).getRepositoryName();
|
||||
Tag tag = VersionTags.empty(module.getProject()).createTag(module);
|
||||
logger.log(module, "Looking up GitHub Release …");
|
||||
|
||||
Iteration iteration = module.getTrainIteration().getIteration();
|
||||
boolean prerelase = iteration.isPreview();
|
||||
GitHubRelease release = findRelease(repositoryName, tag.getName());
|
||||
|
||||
if (release == null) {
|
||||
release = new GitHubRelease(null, tag.getName(), tag.getName(), body, false, prerelase);
|
||||
logger.log(module, "Creating new Release …");
|
||||
createRelease(repositoryName, release);
|
||||
} else {
|
||||
release = release.withPrerelease(prerelase).withBody(body);
|
||||
logger.log(module, "Updating new Release …");
|
||||
updateRelease(repositoryName, release);
|
||||
}
|
||||
}
|
||||
|
||||
private GitHubRelease findRelease(String repositoryName, String tagName) {
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
parameters.put("tag", tagName);
|
||||
|
||||
try {
|
||||
return operations.exchange(RELEASE_BY_TAG_URI_TEMPLATE, HttpMethod.GET, new HttpEntity<>(new HttpHeaders()),
|
||||
GitHubRelease.class, parameters).getBody();
|
||||
} catch (HttpStatusCodeException e) {
|
||||
|
||||
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void createRelease(String repositoryName, GitHubRelease release) {
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
|
||||
operations.exchange(RELEASE_URI_TEMPLATE, HttpMethod.POST, new HttpEntity<>(release), GitHubRelease.class,
|
||||
parameters);
|
||||
}
|
||||
|
||||
private void updateRelease(String repositoryName, GitHubRelease release) {
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
parameters.put("id", release.getId());
|
||||
|
||||
operations.exchange(RELEASE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH, new HttpEntity<>(release), GitHubRelease.class,
|
||||
parameters);
|
||||
}
|
||||
|
||||
private Stream<GitHubReadIssue> getIssuesFor(ModuleIteration moduleIteration, boolean forCurrentUser,
|
||||
boolean ignoreMissingMilestone) {
|
||||
|
||||
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
|
||||
|
||||
Optional<Milestone> optionalMilestone = findMilestone(moduleIteration, repositoryName);
|
||||
|
||||
if (ignoreMissingMilestone && !optionalMilestone.isPresent()) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
Milestone milestone = optionalMilestone.orElseThrow(() -> noSuchMilestone(moduleIteration));
|
||||
|
||||
Map<String, Object> parameters = newUrlTemplateVariables();
|
||||
parameters.put("repoName", repositoryName);
|
||||
parameters.put("id", milestone.getNumber());
|
||||
|
||||
if (forCurrentUser) {
|
||||
parameters.put("assignee", properties.getUsername());
|
||||
|
||||
return getForIssues(ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE, parameters);
|
||||
}
|
||||
|
||||
return getForIssues(ISSUES_BY_MILESTONE_URI_TEMPLATE, parameters);
|
||||
}
|
||||
|
||||
private Stream<GitHubReadIssue> getForIssues(String template, Map<String, Object> parameters) {
|
||||
|
||||
List<GitHubReadIssue> issues = new ArrayList<>();
|
||||
doWithPaging(template, HttpMethod.GET, parameters, new HttpEntity<>(new HttpHeaders()), ISSUES_TYPE,
|
||||
tickets -> {
|
||||
issues.addAll(tickets);
|
||||
return true;
|
||||
});
|
||||
|
||||
return issues.stream();
|
||||
}
|
||||
|
||||
private Milestone getMilestone(ModuleIteration moduleIteration, String repositoryName) {
|
||||
|
||||
Optional<Milestone> milestone = findMilestone(moduleIteration, repositoryName);
|
||||
|
||||
return milestone
|
||||
.orElseThrow(() -> noSuchMilestone(moduleIteration));
|
||||
}
|
||||
|
||||
private IllegalStateException noSuchMilestone(ModuleIteration moduleIteration) {
|
||||
return new IllegalStateException(String.format("No milestone for %s found containing %s!", //
|
||||
moduleIteration.getProject().getFullName(), //
|
||||
new GithubMilestone(moduleIteration)));
|
||||
}
|
||||
|
||||
private static Ticket toTicket(GitHubIssue issue) {
|
||||
return new Ticket(issue.getId(), issue.getTitle(), issue.getUrl(),
|
||||
issue.getAssignees().isEmpty() ? null : issue.getAssignees().get(0), new GithubTicketStatus(issue.getState()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.CliComponent;
|
||||
import org.springframework.data.release.TimedCommand;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.issues.IssueTracker;
|
||||
import org.springframework.data.release.issues.TicketReference;
|
||||
import org.springframework.data.release.model.Project;
|
||||
import org.springframework.data.release.model.Tracker;
|
||||
import org.springframework.data.release.model.TrainIteration;
|
||||
import org.springframework.data.release.utils.ExecutionUtils;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
|
||||
/**
|
||||
* Component to execute GitHub related operations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class GitHubCommands extends TimedCommand {
|
||||
|
||||
@NonNull PluginRegistry<IssueTracker, Project> tracker;
|
||||
@NonNull GitHub gitHub;
|
||||
@NonNull GitOperations git;
|
||||
@NonNull GitHubLabels gitHubLabels;
|
||||
@NonNull Executor executor;
|
||||
|
||||
@CliCommand(value = "github update labels")
|
||||
public void createOrUpdateLabels(@CliOption(key = "", mandatory = true) Project project) {
|
||||
gitHubLabels.createOrUpdateLabels(project);
|
||||
}
|
||||
|
||||
@CliCommand(value = "github push")
|
||||
public void push(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
git.push(iteration);
|
||||
git.pushTags(iteration.getTrain());
|
||||
|
||||
createOrUpdateRelease(iteration);
|
||||
}
|
||||
|
||||
@CliCommand(value = "github create release")
|
||||
public void createOrUpdateRelease(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
|
||||
|
||||
TrainIteration previousIteration = git.getPreviousIteration(iteration);
|
||||
|
||||
ExecutionUtils.run(executor, iteration, it -> {
|
||||
|
||||
if (it.getProject().getTracker() == Tracker.GITHUB) {
|
||||
|
||||
List<String> ticketReferences = git.getTicketReferencesBetween(it.getProject(), previousIteration, iteration)
|
||||
.stream().map(TicketReference::getId).collect(Collectors.toList());
|
||||
gitHub.createOrUpdateRelease(it, ticketReferences);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface GitHubIssue {
|
||||
|
||||
String getNumber();
|
||||
|
||||
default String getId() {
|
||||
return getNumber() == null ? null : "#".concat(getNumber());
|
||||
}
|
||||
|
||||
String getTitle();
|
||||
|
||||
String getState();
|
||||
|
||||
List<String> getAssignees();
|
||||
|
||||
/**
|
||||
* @return HTML URL.
|
||||
*/
|
||||
String getUrl();
|
||||
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.data.release.git.GitProject;
|
||||
import org.springframework.data.release.model.Project;
|
||||
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 com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* Methods to interact with GitHub labels.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Component
|
||||
public class GitHubLabels extends GitHubSupport {
|
||||
|
||||
private static final String LABELS_URI = "/repos/spring-projects/{repoName}/labels";
|
||||
private static final String LABEL_URI = "/repos/spring-projects/{repoName}/labels/{label}";
|
||||
|
||||
private static final ParameterizedTypeReference<List<Label>> LABELS_TYPE = new ParameterizedTypeReference<List<Label>>() {};
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public GitHubLabels(@Qualifier("tracker") RestTemplateBuilder templateBuilder, Logger logger,
|
||||
GitHubProperties properties) {
|
||||
|
||||
super(createOperations(templateBuilder, properties));
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates GitHub labels for a {@link Project}. The actual {@link LabelConfiguration} is obtained from
|
||||
* {@link ProjectLabelConfiguration}.
|
||||
*
|
||||
* @param project the project to process.
|
||||
*/
|
||||
public void createOrUpdateLabels(Project project) {
|
||||
|
||||
logger.log(project, "Obtaining labels…");
|
||||
Map<String, Object> parameters = Collections.singletonMap("repoName", GitProject.of(project).getRepositoryName());
|
||||
|
||||
List<Label> existsOnGitHub = getLabelsFromGitHub(parameters);
|
||||
LabelConfiguration configuration = ProjectLabelConfiguration.forProject(project);
|
||||
List<Label> newLabels = configuration.getNewLabels(existsOnGitHub);
|
||||
List<Label> existingLabels = configuration.getExistingLabels(existsOnGitHub);
|
||||
List<Label> additionalLabels = configuration.getAdditionalLabels(existsOnGitHub);
|
||||
|
||||
if (!newLabels.isEmpty()) {
|
||||
|
||||
logger.log(project, "Creating new labels (%d)…", newLabels.size());
|
||||
newLabels.forEach(it -> {
|
||||
operations.postForObject(LABELS_URI, it, JsonNode.class, parameters);
|
||||
});
|
||||
}
|
||||
|
||||
newLabels.forEach(it -> {
|
||||
operations.postForObject(LABELS_URI, it, String.class, parameters);
|
||||
});
|
||||
|
||||
existingLabels.stream().filter(it -> {
|
||||
|
||||
Label existing = existsOnGitHub.get(existsOnGitHub.indexOf(it));
|
||||
|
||||
return existing.requiresUpdate(it);
|
||||
}).forEach(it -> {
|
||||
|
||||
logger.log(project, "Updating label %s…", it.getName());
|
||||
Map<String, Object> updateLabel = new HashMap<>(parameters);
|
||||
updateLabel.put("label", it.getName());
|
||||
|
||||
operations.postForObject(LABEL_URI, it, JsonNode.class, updateLabel);
|
||||
});
|
||||
|
||||
if (!additionalLabels.isEmpty()) {
|
||||
logger.log(project, "Found additional labels: %s",
|
||||
additionalLabels.stream().map(Label::getName).collect(Collectors.joining(", ")));
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Label> getLabelsFromGitHub(Map<String, Object> parameters) {
|
||||
|
||||
List<Label> existsOnGitHub = new ArrayList<>();
|
||||
|
||||
doWithPaging(LABELS_URI, HttpMethod.GET, parameters, new HttpEntity<>(new HttpHeaders()), LABELS_TYPE, labels -> {
|
||||
existsOnGitHub.addAll(labels);
|
||||
return true;
|
||||
});
|
||||
|
||||
return existsOnGitHub;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.data.release.model.ModuleIteration;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
class GithubMilestone {
|
||||
|
||||
ModuleIteration module;
|
||||
|
||||
public String getDescription() {
|
||||
return module.getReleaseVersionString();
|
||||
}
|
||||
|
||||
public Milestone toMilestone() {
|
||||
return Milestone.of(toString(), getDescription());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return module.getMediumVersionString();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.data.release.git.GitProperties;
|
||||
import org.springframework.data.release.utils.HttpBasicCredentials;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "github")
|
||||
@RequiredArgsConstructor
|
||||
public class GitHubProperties {
|
||||
|
||||
private final @Getter(AccessLevel.NONE) GitProperties gitProperties;
|
||||
|
||||
private String apiUrl;
|
||||
|
||||
/**
|
||||
* Usernames of project team members.
|
||||
*/
|
||||
private List<String> team;
|
||||
|
||||
public String getUsername() {
|
||||
return gitProperties.getUsername();
|
||||
}
|
||||
|
||||
public HttpBasicCredentials getHttpCredentials() {
|
||||
return gitProperties.getHttpCredentials();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Assert.hasText(apiUrl, "No GitHub API base url configured!");
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Read-representation of a GitHub issue.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
class GitHubReadIssue implements Comparable<GitHubReadIssue>, GitHubIssue {
|
||||
|
||||
String number, title, state, url;
|
||||
GitHubUser user;
|
||||
List<GitHubUser> assignees;
|
||||
Milestone milestone;
|
||||
PullRequest pullRequest;
|
||||
List<Label> labels;
|
||||
|
||||
public GitHubReadIssue(String number, String title, String state, @JsonProperty("html_url") String url,
|
||||
GitHubUser user, List<GitHubUser> assignees, Milestone milestone,
|
||||
@JsonProperty("pull_request") PullRequest pullRequest, List<Label> labels) {
|
||||
this.number = number;
|
||||
this.title = title;
|
||||
this.state = state;
|
||||
this.url = url;
|
||||
this.user = user;
|
||||
this.assignees = assignees;
|
||||
this.milestone = milestone;
|
||||
this.pullRequest = pullRequest;
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAssignees() {
|
||||
return assignees == null ? Collections.emptyList()
|
||||
: assignees.stream().map(GitHubUser::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(GitHubReadIssue o) {
|
||||
|
||||
int number = this.number != null ? Integer.parseInt(this.number) : -1;
|
||||
int other = o.number != null ? Integer.parseInt(o.number) : -1;
|
||||
|
||||
return Integer.compare(number, other);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Details of a Github release.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@Value
|
||||
class GitHubRelease {
|
||||
|
||||
Integer id;
|
||||
@JsonProperty("tag_name") String tagName;
|
||||
String name;
|
||||
@With String body;
|
||||
@With boolean draft;
|
||||
@With boolean prerelease;
|
||||
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
||||
/**
|
||||
* Support class for GitHub operations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class GitHubSupport {
|
||||
|
||||
final RestOperations operations;
|
||||
|
||||
static RestOperations createOperations(RestTemplateBuilder templateBuilder, GitHubProperties properties) {
|
||||
return templateBuilder.uriTemplateHandler(new DefaultUriBuilderFactory(properties.getApiUrl())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link Predicate callback} with GitHub paging starting at {@code endpointUri}. The given
|
||||
* {@link Predicate#test(Object)} outcome controls whether paging continues by returning {@literal true} or stops.
|
||||
*
|
||||
* @param endpointUri
|
||||
* @param method
|
||||
* @param parameters
|
||||
* @param entity
|
||||
* @param type
|
||||
* @param callbackContinue
|
||||
* @param <T>
|
||||
*/
|
||||
<T> void doWithPaging(String endpointUri, HttpMethod method, Map<String, Object> parameters, HttpEntity<?> entity,
|
||||
ParameterizedTypeReference<T> type, Predicate<T> callbackContinue) {
|
||||
|
||||
ResponseEntity<T> exchange = operations.exchange(endpointUri, method, entity, type, parameters);
|
||||
|
||||
Pattern pattern = Pattern.compile("<([^ ]*)>; rel=\"(\\w+)\"");
|
||||
|
||||
while (true) {
|
||||
|
||||
if (!callbackContinue.test(exchange.getBody())) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpHeaders responseHeaders = exchange.getHeaders();
|
||||
List<String> links = responseHeaders.getValuesAsList("Link");
|
||||
|
||||
if (links.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String nextLink = null;
|
||||
for (String link : links) {
|
||||
|
||||
Matcher matcher = pattern.matcher(link);
|
||||
if (matcher.find()) {
|
||||
if (matcher.group(2).equals("next")) {
|
||||
nextLink = matcher.group(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextLink == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
exchange = operations.exchange(nextLink, method, entity, type, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Details of a Github user.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Value
|
||||
class GitHubUser {
|
||||
|
||||
String name;
|
||||
|
||||
String url;
|
||||
|
||||
public GitHubUser(@JsonProperty("login") String name, @JsonProperty("html_url") String url) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@With
|
||||
class GitHubWriteIssue implements GitHubIssue {
|
||||
|
||||
String number, title, state;
|
||||
List<Object> assignees;
|
||||
Long milestone;
|
||||
List<String> labels;
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static GitHubWriteIssue of(String title, Milestone milestone) {
|
||||
return new GitHubWriteIssue(null, title, null, null, milestone.getNumber(), null);
|
||||
}
|
||||
|
||||
public static GitHubWriteIssue assignedTo(String username) {
|
||||
|
||||
Assert.hasText(username, "Username must not be null or empty!");
|
||||
return new GitHubWriteIssue(null, null, null, Collections.singletonList(username), null, null);
|
||||
}
|
||||
|
||||
public GitHubWriteIssue close() {
|
||||
return new GitHubWriteIssue(this.number, this.title, "closed", this.assignees, null, null);
|
||||
}
|
||||
|
||||
public GitHubWriteIssue withLabel(String labelName) {
|
||||
|
||||
List<String> labels = new ArrayList<>(this.labels == null ? Collections.emptyList() : this.labels);
|
||||
labels.add(labelName);
|
||||
|
||||
return withLabels(labels);
|
||||
}
|
||||
|
||||
public List<String> getAssignees() {
|
||||
return assignees == null ? Collections.emptyList()
|
||||
: assignees.stream().map(Objects::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.release.issues.github;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import org.springframework.data.release.issues.TicketStatus;
|
||||
|
||||
/**
|
||||
* Value object for a GitHub ticket status.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
class GithubTicketStatus implements TicketStatus {
|
||||
|
||||
private final String status;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.TicketStatus#getLabel()
|
||||
*/
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.TicketStatus#isResolved()
|
||||
*/
|
||||
@Override
|
||||
public boolean isResolved() {
|
||||
return "closed".equalsIgnoreCase(status);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user