#159 - Dependency upgrade automation.
Add commands to check for dependency upgrades and to perform an upgrade for Spring Data Build.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,4 +7,5 @@ target/
|
||||
application-local.properties
|
||||
spring-shell.log
|
||||
.idea/
|
||||
*.iml
|
||||
*.iml
|
||||
dependency-upgrade-*.properties
|
||||
|
||||
@@ -83,3 +83,16 @@ $ tracker archive $trainIteration.previous
|
||||
$ 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`
|
||||
|
||||
@@ -48,6 +48,9 @@ public interface Pom {
|
||||
@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);
|
||||
|
||||
@@ -56,13 +59,16 @@ public interface Pom {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
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 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-jpa");
|
||||
|
||||
public static final Dependency PROJECT_REACTOR = Dependency.of("Project Reactor", "io.projectreactor:reactor-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_FRAMEWORK = Dependency.of("Spring Framework",
|
||||
"org.springframework:spring-core");
|
||||
|
||||
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 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 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-core");
|
||||
|
||||
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 = Dependency.of("Elasticsearch",
|
||||
"org.elasticsearch.client:elasticsearch-rest-high-level-client");
|
||||
|
||||
public static final Dependency SPRING_LDAP = Dependency.of("Spring LDAP",
|
||||
"org.springframework.ldap:spring-ldap-core");
|
||||
|
||||
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 getRequiredByArtifactId(String artifactId) {
|
||||
|
||||
return dependencies.stream().filter(it -> it.getArtifactId().equals(artifactId)).findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("No such dependency: %s", artifactId)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
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.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.checkout(iteration.getTrain());
|
||||
|
||||
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.checkout(iteration.getTrain());
|
||||
|
||||
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) {
|
||||
dependencies.putAll(operations.getCurrentDependencies(project));
|
||||
}
|
||||
|
||||
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 check(@CliOption(key = "", mandatory = true) TrainIteration iteration)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
git.checkout(iteration.getTrain());
|
||||
logger.log(iteration, "Applying dependency upgrades to Spring Data Build");
|
||||
|
||||
Properties properties = loadDependencyUpgrades(iteration);
|
||||
|
||||
Map<Dependency, DependencyVersion> dependencyUpgrades = DependencyUpgradeProposals.fromProperties(iteration,
|
||||
properties);
|
||||
|
||||
operations.createUpgradeTickets(iteration, Projects.BUILD, dependencyUpgrades);
|
||||
operations.upgradeDependencies(iteration, Projects.BUILD, dependencyUpgrades);
|
||||
}
|
||||
|
||||
protected Properties loadDependencyUpgrades(@CliOption(key = "", mandatory = true) TrainIteration 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 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
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.concurrent.ExecutorService;
|
||||
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 org.springframework.data.release.CliComponent;
|
||||
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.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.ResponseEntity;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.StringUtils;
|
||||
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
|
||||
*/
|
||||
@CliComponent
|
||||
@RequiredArgsConstructor
|
||||
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 PluginRegistry<IssueTracker, Project> tracker;
|
||||
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) {
|
||||
|
||||
Map<Dependency, DependencyVersion> currentDependencies = getCurrentDependencies(project);
|
||||
Map<Dependency, DependencyUpgradeProposal> proposals = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||
|
||||
ExecutionUtils.run(executor, Streamable.of(currentDependencies.keySet()), dependency -> {
|
||||
|
||||
DependencyVersion currentVersion = currentDependencies.get(dependency);
|
||||
List<DependencyVersion> versions = getAvailableVersions(dependency);
|
||||
DependencyUpgradeProposal proposal = getDependencyUpgradeProposal(iteration, currentVersion, versions);
|
||||
|
||||
proposals.put(dependency, proposal);
|
||||
});
|
||||
|
||||
return new DependencyUpgradeProposals(proposals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures there's a upgrade ticket for each dependency to upgrade.
|
||||
*
|
||||
* @param iteration
|
||||
* @param project
|
||||
* @param dependencyVersions
|
||||
*/
|
||||
public void createUpgradeTickets(TrainIteration iteration, Project project,
|
||||
Map<Dependency, DependencyVersion> dependencyVersions) {
|
||||
|
||||
Map<Dependency, DependencyVersion> upgrades = getDependencyUpgradesToApply(project, dependencyVersions);
|
||||
|
||||
IssueTracker tracker = this.tracker.getRequiredPluginFor(project);
|
||||
Tickets tickets = tracker.getTicketsFor(iteration);
|
||||
|
||||
upgrades.forEach((dependency, dependencyVersion) -> {
|
||||
|
||||
String upgradeTicketSummary = getUpgradeTicketSummary(dependency, dependencyVersion);
|
||||
Optional<Ticket> upgradeTicket = getDependencyUpgradeTicket(tickets, upgradeTicketSummary);
|
||||
|
||||
if (upgradeTicket.isPresent()) {
|
||||
logger.log(project, "Found upgrade ticket %s", upgradeTicket.get());
|
||||
} else {
|
||||
|
||||
ModuleIteration module = iteration.getModule(project);
|
||||
|
||||
logger.log(module, "Creating upgrade ticket for %s", upgradeTicketSummary);
|
||||
tracker.createTicket(module, upgradeTicketSummary);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies dependencies to upgrade, applies the upgrade, creates a commit, pushes the repository and resolves the
|
||||
* upgrade ticket.
|
||||
*
|
||||
* @param iteration
|
||||
* @param project
|
||||
* @param dependencyVersions
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void upgradeDependencies(TrainIteration iteration, Project project,
|
||||
Map<Dependency, DependencyVersion> dependencyVersions) throws InterruptedException {
|
||||
|
||||
Map<Dependency, DependencyVersion> upgrades = getDependencyUpgradesToApply(project, dependencyVersions);
|
||||
ProjectDependencies dependencies = ProjectDependencies.get(project);
|
||||
ModuleIteration module = iteration.getModule(project);
|
||||
|
||||
if (upgrades.isEmpty()) {
|
||||
logger.log(module, "No dependency upgrades to apply");
|
||||
}
|
||||
|
||||
IssueTracker tracker = this.tracker.getRequiredPluginFor(project);
|
||||
Tickets tickets = tracker.getTicketsFor(iteration);
|
||||
List<Ticket> ticketsToClose = new ArrayList<>();
|
||||
|
||||
upgrades.forEach((dependency, dependencyVersion) -> {
|
||||
|
||||
String upgradeTicketSummary = getUpgradeTicketSummary(dependency, dependencyVersion);
|
||||
Ticket upgradeTicket = getDependencyUpgradeTicket(tickets, upgradeTicketSummary).get();
|
||||
String versionProperty = dependencies.getVersionPropertyFor(dependency);
|
||||
|
||||
File pom = getPomFile(project);
|
||||
update(pom, Pom.class, it -> {
|
||||
it.setProperty(versionProperty, dependencyVersion.getIdentifier());
|
||||
});
|
||||
|
||||
gitOperations.commit(module, upgradeTicket, upgradeTicketSummary, Optional.empty(), pom);
|
||||
|
||||
ticketsToClose.add(upgradeTicket);
|
||||
});
|
||||
|
||||
gitOperations.push(module);
|
||||
|
||||
// Allow GitHub to catch up with ticket notifications.
|
||||
Thread.sleep(1500);
|
||||
|
||||
for (Ticket ticket : ticketsToClose) {
|
||||
tracker.closeTicket(module, ticket);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Dependency, DependencyVersion> getDependencyUpgradesToApply(Project project,
|
||||
Map<Dependency, DependencyVersion> dependencyVersions) {
|
||||
|
||||
Map<Dependency, DependencyVersion> currentDependencies = getCurrentDependencies(project);
|
||||
Map<Dependency, DependencyVersion> upgrades = new LinkedHashMap<>();
|
||||
|
||||
currentDependencies.forEach((dependency, dependencyVersion) -> {
|
||||
|
||||
DependencyVersion upgradeVersion = dependencyVersions.get(dependency);
|
||||
|
||||
if (upgradeVersion == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (upgradeVersion.equals(dependencyVersion)) {
|
||||
logger.log(project, "Skipping upgrade of %s (%s)", dependency.getName(), dependencyVersion.getIdentifier());
|
||||
return;
|
||||
}
|
||||
|
||||
upgrades.put(dependency, upgradeVersion);
|
||||
});
|
||||
|
||||
return 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(Iteration iteration,
|
||||
DependencyVersion currentVersion, List<DependencyVersion> allVersions) {
|
||||
|
||||
DependencyVersion latestMinor = findLatestMinor(iteration, currentVersion, allVersions);
|
||||
DependencyVersion latest = findLatest(iteration, allVersions);
|
||||
List<DependencyVersion> newerVersions = allVersions.stream() //
|
||||
.sorted() //
|
||||
.filter(it -> it.compareTo(currentVersion) > 0) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return DependencyUpgradeProposal.of(iteration, currentVersion, latestMinor, latest, newerVersions);
|
||||
}
|
||||
|
||||
private static DependencyVersion findLatest(Iteration iteration, List<DependencyVersion> availableVersions) {
|
||||
|
||||
return availableVersions.stream().filter(it -> {
|
||||
|
||||
if (!iteration.isMilestone() && StringUtils.hasText(it.getModifier())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}).max(DependencyVersion::compareTo).orElseThrow(
|
||||
() -> new IllegalArgumentException("Cannot determine new latest version from " + availableVersions));
|
||||
}
|
||||
|
||||
private static DependencyVersion findLatestMinor(Iteration iteration, DependencyVersion currentVersion,
|
||||
List<DependencyVersion> availableVersions) {
|
||||
|
||||
return availableVersions.stream().filter(it -> {
|
||||
|
||||
if (!iteration.isMilestone() && StringUtils.hasText(it.getModifier())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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) //
|
||||
.orElseThrow(
|
||||
() -> new IllegalArgumentException("Cannot determine new minor version from " + availableVersions));
|
||||
}
|
||||
|
||||
Map<Dependency, DependencyVersion> getCurrentDependencies(Project project) {
|
||||
|
||||
if (!ProjectDependencies.containsProject(project)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
||||
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 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());
|
||||
|
||||
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()));
|
||||
|
||||
try {
|
||||
|
||||
MavenMetadata metadata = io.read(MavenMetadata.class);
|
||||
|
||||
return metadata.getVersions().stream().filter(dependency::shouldInclude).map(DependencyVersion::of).map(it -> {
|
||||
|
||||
if (creationDates.containsKey(it.getIdentifier())) {
|
||||
return it.withCreatedAt(creationDates.get(it.getIdentifier()));
|
||||
}
|
||||
|
||||
return it;
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
} catch (Exception o_O) {
|
||||
throw new RuntimeException(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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
@RequiredArgsConstructor
|
||||
class DependencyUpgradeProposal {
|
||||
|
||||
DependencyVersion current, latest, latestMinor, proposal;
|
||||
List<DependencyVersion> newerVersions;
|
||||
|
||||
public static DependencyUpgradeProposal of(Iteration iteration, DependencyVersion currentVersion,
|
||||
DependencyVersion latestMinor, DependencyVersion latest, List<DependencyVersion> newerVersions) {
|
||||
|
||||
if (iteration.isServiceIteration()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
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());
|
||||
|
||||
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 Map<Dependency, DependencyVersion> 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", "");
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Matcher matcher = keyPattern.matcher(k.toString());
|
||||
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException(String.format("Unexpected key: %s", k));
|
||||
}
|
||||
|
||||
String artifactId = matcher.group(2);
|
||||
Dependency dependency = Dependencies.getRequiredByArtifactId(artifactId);
|
||||
|
||||
result.put(dependency, DependencyVersion.of(v.toString()));
|
||||
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @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+))");
|
||||
|
||||
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);
|
||||
if (identifier.contains("-SR")) {
|
||||
version = Version.of(Integer.parseInt(bomMatcher.group(3)));
|
||||
}
|
||||
|
||||
return new DependencyVersion(identifier, bomMatcher.group(1), version, "", 0, null);
|
||||
}
|
||||
|
||||
Matcher versionMatcher = VERSION.matcher(identifier);
|
||||
|
||||
if (versionMatcher.find()) {
|
||||
Version version = null;
|
||||
String modifier;
|
||||
String counter;
|
||||
try {
|
||||
version = Version.parse(versionMatcher.group(1));
|
||||
} catch (NumberFormatException 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));
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
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("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("mockito", Dependencies.MOCKITO));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("querydsl", Dependencies.QUERYDSL));
|
||||
config.add(Projects.BUILD, ProjectDependency.ofProperty("reactor", Dependencies.PROJECT_REACTOR));
|
||||
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("spring", Dependencies.SPRING_FRAMEWORK));
|
||||
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));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,28 +233,29 @@ public class GitOperations {
|
||||
}
|
||||
|
||||
public void push(TrainIteration iteration) {
|
||||
ExecutionUtils.run(executor, iteration, this::push);
|
||||
}
|
||||
|
||||
ExecutionUtils.run(executor, iteration, module -> {
|
||||
public void push(ModuleIteration module) {
|
||||
|
||||
Branch branch = Branch.from(module);
|
||||
logger.log(module, "git push origin %s", branch);
|
||||
Branch branch = Branch.from(module);
|
||||
logger.log(module, "git push origin %s", branch);
|
||||
|
||||
if (!branchExists(module.getProject(), branch)) {
|
||||
if (!branchExists(module.getProject(), branch)) {
|
||||
|
||||
logger.log(module, "No branch %s in %s, skip push", branch, module.getProject().getName());
|
||||
return;
|
||||
}
|
||||
logger.log(module, "No branch %s in %s, skip push", branch, module.getProject().getName());
|
||||
return;
|
||||
}
|
||||
|
||||
doWithGit(module.getProject(), git -> {
|
||||
doWithGit(module.getProject(), git -> {
|
||||
|
||||
Ref ref = git.getRepository().findRef(branch.toString());
|
||||
Ref ref = git.getRepository().findRef(branch.toString());
|
||||
|
||||
git.push()//
|
||||
.setRemote("origin")//
|
||||
.setRefSpecs(new RefSpec(ref.getName()))//
|
||||
.setCredentialsProvider(gitProperties.getCredentials())//
|
||||
.call();
|
||||
});
|
||||
git.push()//
|
||||
.setRemote("origin")//
|
||||
.setRefSpecs(new RefSpec(ref.getName()))//
|
||||
.setCredentialsProvider(gitProperties.getCredentials())//
|
||||
.call();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -621,6 +622,26 @@ public class GitOperations {
|
||||
() -> String.format("No issue tracker found for project %s!", project));
|
||||
Ticket ticket = tracker.getReleaseTicketFor(module);
|
||||
|
||||
commit(module, ticket, summary, details, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the given files for the given {@link ModuleIteration} using the given summary and details for the commit
|
||||
* message. If no files are given, all pending changes are committed.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param summary must not be {@literal null} or empty.
|
||||
* @param details can be {@literal null} or empty.
|
||||
* @param files can be empty.
|
||||
* @throws Exception
|
||||
*/
|
||||
public void commit(ModuleIteration module, Ticket ticket, String summary, Optional<String> details, File... files) {
|
||||
|
||||
Assert.notNull(module, "Module iteration must not be null!");
|
||||
Assert.hasText(summary, "Summary must not be null or empty!");
|
||||
|
||||
Project project = module.getProject();
|
||||
|
||||
Commit commit = new Commit(ticket, summary, details);
|
||||
String author = gitProperties.getAuthor();
|
||||
String email = gitProperties.getEmail();
|
||||
|
||||
@@ -144,7 +144,7 @@ public interface IssueTracker extends Plugin<Project> {
|
||||
*/
|
||||
default Changelog getChangelogFor(ModuleIteration module, List<TicketReference> ticketReferences) {
|
||||
|
||||
Tickets tickets = resolve(module, ticketReferences);
|
||||
Tickets tickets = findTickets(module, ticketReferences);
|
||||
return Changelog.of(module, tickets);
|
||||
}
|
||||
|
||||
@@ -155,6 +155,14 @@ public interface IssueTracker extends Plugin<Project> {
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* Resolve a {@link List} of {@link TicketReference}s to {@link Tickets} for a given {@link ModuleIteration}. The
|
||||
* implementation ensures to resolve only references that match the issue tracker scheme this issue tracker is
|
||||
@@ -164,5 +172,5 @@ public interface IssueTracker extends Plugin<Project> {
|
||||
* @param ticketReferences must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Tickets resolve(ModuleIteration moduleIteration, List<TicketReference> ticketReferences);
|
||||
Tickets findTickets(ModuleIteration moduleIteration, List<TicketReference> ticketReferences);
|
||||
}
|
||||
|
||||
@@ -320,18 +320,23 @@ class GitHub extends GitHubSupport implements IssueTracker {
|
||||
Assert.notNull(module, "ModuleIteration must not be null.");
|
||||
|
||||
Ticket releaseTicketFor = getReleaseTicketFor(module);
|
||||
GitHubIssue response = close(module, releaseTicketFor);
|
||||
|
||||
return toTicket(response);
|
||||
}
|
||||
|
||||
private GitHubIssue 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(releaseTicketFor));
|
||||
parameters.put("id", stripHash(ticket));
|
||||
|
||||
GitHubIssue edit = GitHubIssue.assignedTo(properties.getUsername()).close();
|
||||
|
||||
GitHubIssue response = operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH,
|
||||
return operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH,
|
||||
new HttpEntity<>(edit, new HttpHeaders()), ISSUE_TYPE, parameters).getBody();
|
||||
|
||||
return toTicket(response);
|
||||
}
|
||||
|
||||
private String stripHash(Ticket ticket) {
|
||||
@@ -424,7 +429,12 @@ class GitHub extends GitHubSupport implements IssueTracker {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tickets resolve(ModuleIteration moduleIteration, List<TicketReference> ticketReferences) {
|
||||
public void closeTicket(ModuleIteration module, Ticket ticket) {
|
||||
close(module, ticket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tickets findTickets(ModuleIteration moduleIteration, List<TicketReference> ticketReferences) {
|
||||
|
||||
logger.log(moduleIteration, "Looking up GitHub issues from milestone …");
|
||||
|
||||
|
||||
@@ -543,6 +543,11 @@ class Jira implements JiraConnector {
|
||||
// - if no next version exists, create
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeTicket(ModuleIteration module, Ticket ticket) {
|
||||
resolve(ticket);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.release.jira.JiraConnector#getChangelogFor(org.springframework.data.release.model.Module, org.springframework.data.release.model.Iteration)
|
||||
@@ -575,7 +580,7 @@ class Jira implements JiraConnector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tickets resolve(ModuleIteration moduleIteration, List<TicketReference> ticketReferences) {
|
||||
public Tickets findTickets(ModuleIteration moduleIteration, List<TicketReference> ticketReferences) {
|
||||
|
||||
List<String> ids = ticketReferences.stream()
|
||||
.filter(it -> it.getId().startsWith(moduleIteration.getProjectKey().getKey())).map(TicketReference::getId)
|
||||
|
||||
@@ -131,7 +131,7 @@ public class ReleaseOperations {
|
||||
List<Ticket> tickets = new ArrayList<>();
|
||||
|
||||
for (IssueTracker tracker : trackers) {
|
||||
tickets.addAll(tracker.resolve(module, ticketReferences).getTickets());
|
||||
tickets.addAll(tracker.findTickets(module, ticketReferences).getTickets());
|
||||
}
|
||||
|
||||
changelog = Changelog.of(module, new Tickets(tickets));
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.release.AbstractIntegrationTests;
|
||||
import org.springframework.data.release.git.GitOperations;
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
import org.springframework.data.release.model.Projects;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DependencyOperations}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Disabled
|
||||
class DependencyOperationsIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
@Autowired GitOperations git;
|
||||
@Autowired DependencyOperations operations;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
try {
|
||||
URL url = new URL("https://repo1.maven.org");
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
urlConnection.connect();
|
||||
urlConnection.getInputStream().close();
|
||||
} catch (IOException e) {
|
||||
assumeTrue("Test requires connectivity to Maven: " + e.toString(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDiscoverDependencyVersions() {
|
||||
assertThat(operations.getAvailableVersions(Dependencies.PROJECT_REACTOR)).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportExistingDependencyVersions() {
|
||||
assertThat(operations.getCurrentDependencies(Projects.BUILD)).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportExistingOptionalDependencies() {
|
||||
|
||||
// git.checkout(ReleaseTrains.MOORE);
|
||||
|
||||
assertThat(operations.getCurrentDependencies(Projects.CASSANDRA)).hasSize(1);
|
||||
assertThat(operations.getCurrentDependencies(Projects.MONGO_DB)).hasSize(1);
|
||||
assertThat(operations.getCurrentDependencies(Projects.NEO4J)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUpgradeProposals() {
|
||||
System.out.println(operations.getDependencyUpgradeProposals(Projects.BUILD, Iteration.M1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DependencyOperations}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class DependencyOperationsUnitTests {
|
||||
|
||||
@Test
|
||||
void shouldRetainCurrentVersion() {
|
||||
|
||||
List<DependencyVersion> availableVersions = Stream.of("5.7.0", "5.7.0-M1") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyUpgradeProposal proposal = DependencyOperations.getDependencyUpgradeProposal(Iteration.SR1,
|
||||
DependencyVersion.of("5.7.0"), availableVersions);
|
||||
|
||||
assertThat(proposal.getCurrent()).isEqualTo(DependencyVersion.of("5.7.0"));
|
||||
assertThat(proposal.getLatestMinor()).isEqualTo(DependencyVersion.of("5.7.0"));
|
||||
assertThat(proposal.getProposal()).isEqualTo(DependencyVersion.of("5.7.0"));
|
||||
assertThat(proposal.getLatest()).isEqualTo(DependencyVersion.of("5.7.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSelectNextMinorVersion() {
|
||||
|
||||
List<DependencyVersion> availableVersions = Stream.of("5.7.0", "5.7.1", "5.8.0") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyUpgradeProposal proposal = DependencyOperations.getDependencyUpgradeProposal(Iteration.SR1,
|
||||
DependencyVersion.of("5.7.0"), availableVersions);
|
||||
|
||||
assertThat(proposal.getCurrent()).isEqualTo(DependencyVersion.of("5.7.0"));
|
||||
assertThat(proposal.getLatestMinor()).isEqualTo(DependencyVersion.of("5.7.1"));
|
||||
assertThat(proposal.getProposal()).isEqualTo(DependencyVersion.of("5.7.1"));
|
||||
assertThat(proposal.getLatest()).isEqualTo(DependencyVersion.of("5.8.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSelectLatestVersion() {
|
||||
|
||||
List<DependencyVersion> availableVersions = Stream.of("5.7.0", "5.7.1", "5.8.0") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyUpgradeProposal proposal = DependencyOperations.getDependencyUpgradeProposal(Iteration.M1,
|
||||
DependencyVersion.of("5.7.0"), availableVersions);
|
||||
|
||||
assertThat(proposal.getCurrent()).isEqualTo(DependencyVersion.of("5.7.0"));
|
||||
assertThat(proposal.getLatestMinor()).isEqualTo(DependencyVersion.of("5.7.1"));
|
||||
assertThat(proposal.getProposal()).isEqualTo(DependencyVersion.of("5.8.0"));
|
||||
assertThat(proposal.getLatest()).isEqualTo(DependencyVersion.of("5.8.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportNewerVersions() {
|
||||
|
||||
List<DependencyVersion> availableVersions = Stream.of("5.7.0", "5.7.1", "5.7.2-M2", "5.7.2", "5.8.0") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyUpgradeProposal proposal = DependencyOperations.getDependencyUpgradeProposal(Iteration.SR1,
|
||||
DependencyVersion.of("5.7.1"), availableVersions);
|
||||
|
||||
assertThat(proposal.getNewerVersions()).extracting(DependencyVersion::getIdentifier).containsExactly("5.7.2-M2",
|
||||
"5.7.2", "5.8.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReportMilestoneVersionForMilestoneIteration() {
|
||||
|
||||
List<DependencyVersion> availableVersions = Stream.of("5.7.0", "5.7.1", "5.7.2-M2") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyUpgradeProposal proposal = DependencyOperations.getDependencyUpgradeProposal(Iteration.M1,
|
||||
DependencyVersion.of("5.7.1"), availableVersions);
|
||||
|
||||
assertThat(proposal.getLatest()).extracting(DependencyVersion::getIdentifier).isEqualTo("5.7.2-M2");
|
||||
assertThat(proposal.getProposal()).extracting(DependencyVersion::getIdentifier).isEqualTo("5.7.2-M2");
|
||||
assertThat(proposal.getNewerVersions()).extracting(DependencyVersion::getIdentifier).containsExactly("5.7.2-M2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipMilestoneVersionForNonMilestoneIteration() {
|
||||
|
||||
List<DependencyVersion> availableVersions = Stream.of("5.7.0", "5.7.1", "5.7.2-M2") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DependencyUpgradeProposal proposal = DependencyOperations.getDependencyUpgradeProposal(Iteration.RC1,
|
||||
DependencyVersion.of("5.7.1"), availableVersions);
|
||||
|
||||
assertThat(proposal.getLatest()).extracting(DependencyVersion::getIdentifier).isEqualTo("5.7.1");
|
||||
assertThat(proposal.getProposal()).extracting(DependencyVersion::getIdentifier).isEqualTo("5.7.1");
|
||||
assertThat(proposal.getNewerVersions()).extracting(DependencyVersion::getIdentifier).containsExactly("5.7.2-M2");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.release.model.Iteration;
|
||||
import org.springframework.data.release.model.ReleaseTrains;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DependencyUpgradeProposals}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class DependencyUpgradeProposalsUnitTests {
|
||||
|
||||
@Test
|
||||
void shouldParseDependencies() {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("dependency.train", "Pascal");
|
||||
properties.put("dependency.iteration", "M1");
|
||||
properties.put("dependency[org.assertj:assertj-core]", "3.18.1");
|
||||
|
||||
Map<Dependency, DependencyVersion> dependencies = DependencyUpgradeProposals
|
||||
.fromProperties(ReleaseTrains.PASCAL.getIteration(Iteration.M1), properties);
|
||||
|
||||
assertThat(dependencies).hasSize(1).containsEntry(Dependencies.ASSERTJ, DependencyVersion.of("3.18.1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.release.dependency;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DependencyVersion}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class DependencyVersionUnitTests {
|
||||
|
||||
@Test
|
||||
void shouldConsiderSemVerSortOrder() {
|
||||
|
||||
List<String> sorted = Stream.of("1.0.0", "1.0.0-m1", "1.0.0-rc1", "1.0.0-m2") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.map(DependencyVersion::getIdentifier) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertThat(sorted).containsExactly("1.0.0-m1", "1.0.0-m2", "1.0.0-rc1", "1.0.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConsiderReleaseTrainSortOrder() {
|
||||
|
||||
List<String> sorted = Stream.of("Bismuth-SR1", "Aluminium-SR1", "Aluminium-RELEASE", "Aluminium-SR2") //
|
||||
.map(DependencyVersion::of) //
|
||||
.sorted() //
|
||||
.map(DependencyVersion::getIdentifier) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertThat(sorted).containsExactly("Aluminium-RELEASE", "Aluminium-SR1", "Aluminium-SR2", "Bismuth-SR1");
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class ReleaseOperationsIntegrationTests extends AbstractIntegrationTests {
|
||||
List<TicketReference> ticketReferences = gitOperations.getTicketReferencesBetween(Projects.MONGO_DB, from, to);
|
||||
IssueTracker tracker = trackers.getRequiredPluginFor(Projects.MONGO_DB);
|
||||
|
||||
Tickets tickets = tracker.resolve(to.getModule(Projects.MONGO_DB), ticketReferences);
|
||||
Tickets tickets = tracker.findTickets(to.getModule(Projects.MONGO_DB), ticketReferences);
|
||||
|
||||
assertThat(tickets).hasSize(15);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class ReleaseOperationsIntegrationTests extends AbstractIntegrationTests {
|
||||
List<TicketReference> ticketReferences = gitOperations.getTicketReferencesBetween(Projects.R2DBC, from, to);
|
||||
IssueTracker tracker = trackers.getRequiredPluginFor(Projects.R2DBC);
|
||||
|
||||
Tickets tickets = tracker.resolve(to.getModule(Projects.R2DBC), ticketReferences);
|
||||
Tickets tickets = tracker.findTickets(to.getModule(Projects.R2DBC), ticketReferences);
|
||||
|
||||
assertThat(tickets).hasSize(22);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user