mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Merge branch '2.7.x'
Closes gh-33825
This commit is contained in:
commit
c1ca05d99d
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2022 the original author or authors.
|
* Copyright 2012-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,32 +16,15 @@
|
|||||||
|
|
||||||
package org.springframework.boot.build.bom.bomr;
|
package org.springframework.boot.build.bom.bomr;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
|
||||||
import org.gradle.api.InvalidUserDataException;
|
|
||||||
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
|
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
|
||||||
|
|
||||||
import org.springframework.boot.build.bom.Library;
|
import org.springframework.boot.build.bom.Library;
|
||||||
import org.springframework.boot.build.bom.Library.DependencyVersions;
|
|
||||||
import org.springframework.boot.build.bom.Library.Group;
|
|
||||||
import org.springframework.boot.build.bom.Library.Module;
|
|
||||||
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
|
|
||||||
import org.springframework.boot.build.bom.Library.VersionAlignment;
|
|
||||||
import org.springframework.boot.build.bom.UpgradePolicy;
|
|
||||||
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interactive {@link UpgradeResolver} that uses command line input to choose the upgrades
|
* Interactive {@link UpgradeResolver} that uses command line input to choose the upgrades
|
||||||
@ -51,17 +34,13 @@ import org.springframework.util.StringUtils;
|
|||||||
*/
|
*/
|
||||||
public final class InteractiveUpgradeResolver implements UpgradeResolver {
|
public final class InteractiveUpgradeResolver implements UpgradeResolver {
|
||||||
|
|
||||||
private final VersionResolver versionResolver;
|
|
||||||
|
|
||||||
private final UpgradePolicy upgradePolicy;
|
|
||||||
|
|
||||||
private final UserInputHandler userInputHandler;
|
private final UserInputHandler userInputHandler;
|
||||||
|
|
||||||
InteractiveUpgradeResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy,
|
private final LibraryUpdateResolver libraryUpdateResolver;
|
||||||
UserInputHandler userInputHandler) {
|
|
||||||
this.versionResolver = versionResolver;
|
InteractiveUpgradeResolver(UserInputHandler userInputHandler, LibraryUpdateResolver libraryUpdateResolver) {
|
||||||
this.upgradePolicy = upgradePolicy;
|
|
||||||
this.userInputHandler = userInputHandler;
|
this.userInputHandler = userInputHandler;
|
||||||
|
this.libraryUpdateResolver = libraryUpdateResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -70,195 +49,22 @@ public final class InteractiveUpgradeResolver implements UpgradeResolver {
|
|||||||
for (Library library : libraries) {
|
for (Library library : libraries) {
|
||||||
librariesByName.put(library.getName(), library);
|
librariesByName.put(library.getName(), library);
|
||||||
}
|
}
|
||||||
return librariesToUpgrade.stream().filter((library) -> !library.getName().equals("Spring Boot"))
|
List<LibraryWithVersionOptions> libraryUpdates = this.libraryUpdateResolver
|
||||||
.map((library) -> resolveUpgrade(library, librariesByName)).filter(Objects::nonNull).toList();
|
.findLibraryUpdates(librariesToUpgrade, librariesByName);
|
||||||
|
return libraryUpdates.stream().map(this::resolveUpgrade).filter(Objects::nonNull).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Upgrade resolveUpgrade(Library library, Map<String, Library> libraries) {
|
private Upgrade resolveUpgrade(LibraryWithVersionOptions libraryWithVersionOptions) {
|
||||||
List<VersionOption> versionOptions = getVersionOptions(library, libraries);
|
if (libraryWithVersionOptions.getVersionOptions().isEmpty()) {
|
||||||
if (versionOptions.isEmpty()) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
VersionOption current = new VersionOption(library.getVersion().getVersion());
|
VersionOption current = new VersionOption(libraryWithVersionOptions.getLibrary().getVersion().getVersion());
|
||||||
VersionOption selected = this.userInputHandler
|
VersionOption selected = this.userInputHandler.selectOption(
|
||||||
.selectOption(library.getName() + " " + library.getVersion().getVersion(), versionOptions, current);
|
libraryWithVersionOptions.getLibrary().getName() + " "
|
||||||
return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
|
+ libraryWithVersionOptions.getLibrary().getVersion().getVersion(),
|
||||||
}
|
libraryWithVersionOptions.getVersionOptions(), current);
|
||||||
|
return (selected.equals(current)) ? null
|
||||||
private List<VersionOption> getVersionOptions(Library library, Map<String, Library> libraries) {
|
: new Upgrade(libraryWithVersionOptions.getLibrary(), selected.getVersion());
|
||||||
if (library.getVersion().getVersionAlignment() != null) {
|
|
||||||
return determineAlignedVersionOption(library, libraries);
|
|
||||||
}
|
|
||||||
return determineResolvedVersionOptions(library);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<VersionOption> determineResolvedVersionOptions(Library library) {
|
|
||||||
Map<String, SortedSet<DependencyVersion>> moduleVersions = new LinkedHashMap<>();
|
|
||||||
DependencyVersion libraryVersion = library.getVersion().getVersion();
|
|
||||||
for (Group group : library.getGroups()) {
|
|
||||||
for (Module module : group.getModules()) {
|
|
||||||
moduleVersions.put(group.getId() + ":" + module.getName(),
|
|
||||||
getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion));
|
|
||||||
}
|
|
||||||
for (String bom : group.getBoms()) {
|
|
||||||
moduleVersions.put(group.getId() + ":" + bom,
|
|
||||||
getLaterVersionsForModule(group.getId(), bom, libraryVersion));
|
|
||||||
}
|
|
||||||
for (String plugin : group.getPlugins()) {
|
|
||||||
moduleVersions.put(group.getId() + ":" + plugin,
|
|
||||||
getLaterVersionsForModule(group.getId(), plugin, libraryVersion));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<DependencyVersion> allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
|
|
||||||
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
|
|
||||||
.toList();
|
|
||||||
if (allVersions.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
Stream<VersionOption> resolvedVersionOptions = allVersions.stream()
|
|
||||||
.map((version) -> new ResolvedVersionOption(version, getMissingModules(moduleVersions, version)));
|
|
||||||
return resolvedVersionOptions.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<VersionOption> determineAlignedVersionOption(Library library, Map<String, Library> libraries) {
|
|
||||||
VersionOption alignedVersionOption = alignedVersionOption(library, libraries);
|
|
||||||
if (alignedVersionOption == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
if (!isPermitted(alignedVersionOption.version, library.getProhibitedVersions())) {
|
|
||||||
throw new InvalidUserDataException("Version alignment failed. Version " + alignedVersionOption.version
|
|
||||||
+ " from " + library.getName() + " is prohibited");
|
|
||||||
}
|
|
||||||
return Collections.singletonList(alignedVersionOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VersionOption alignedVersionOption(Library library, Map<String, Library> libraries) {
|
|
||||||
VersionAlignment versionAlignment = library.getVersion().getVersionAlignment();
|
|
||||||
Library alignmentLibrary = libraries.get(versionAlignment.getLibraryName());
|
|
||||||
DependencyVersions dependencyVersions = alignmentLibrary.getDependencyVersions();
|
|
||||||
if (dependencyVersions == null) {
|
|
||||||
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
|
|
||||||
+ "' as it does not define any dependency versions");
|
|
||||||
}
|
|
||||||
if (!dependencyVersions.available()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Set<String> versions = new HashSet<>();
|
|
||||||
for (Group group : library.getGroups()) {
|
|
||||||
for (Module module : group.getModules()) {
|
|
||||||
String version = dependencyVersions.getVersion(group.getId(), module.getName());
|
|
||||||
if (version != null) {
|
|
||||||
versions.add(version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (versions.isEmpty()) {
|
|
||||||
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
|
|
||||||
+ "' as its dependency versions do not include any of this library's modules");
|
|
||||||
}
|
|
||||||
if (versions.size() > 1) {
|
|
||||||
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
|
|
||||||
+ "' as it uses multiple different versions of this library's modules");
|
|
||||||
}
|
|
||||||
DependencyVersion version = DependencyVersion.parse(versions.iterator().next());
|
|
||||||
return library.getVersion().getVersion().equals(version) ? null
|
|
||||||
: new AlignedVersionOption(version, alignmentLibrary);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPermitted(DependencyVersion dependencyVersion, List<ProhibitedVersion> prohibitedVersions) {
|
|
||||||
for (ProhibitedVersion prohibitedVersion : prohibitedVersions) {
|
|
||||||
String dependencyVersionToString = dependencyVersion.toString();
|
|
||||||
if (prohibitedVersion.getRange() != null && prohibitedVersion.getRange()
|
|
||||||
.containsVersion(new DefaultArtifactVersion(dependencyVersionToString))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (String startsWith : prohibitedVersion.getStartsWith()) {
|
|
||||||
if (dependencyVersionToString.startsWith(startsWith)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String endsWith : prohibitedVersion.getEndsWith()) {
|
|
||||||
if (dependencyVersionToString.endsWith(endsWith)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String contains : prohibitedVersion.getContains()) {
|
|
||||||
if (dependencyVersionToString.contains(contains)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getMissingModules(Map<String, SortedSet<DependencyVersion>> moduleVersions,
|
|
||||||
DependencyVersion version) {
|
|
||||||
List<String> missingModules = new ArrayList<>();
|
|
||||||
moduleVersions.forEach((name, versions) -> {
|
|
||||||
if (!versions.contains(version)) {
|
|
||||||
missingModules.add(name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return missingModules;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SortedSet<DependencyVersion> getLaterVersionsForModule(String groupId, String artifactId,
|
|
||||||
DependencyVersion currentVersion) {
|
|
||||||
SortedSet<DependencyVersion> versions = this.versionResolver.resolveVersions(groupId, artifactId);
|
|
||||||
versions.removeIf((candidate) -> !this.upgradePolicy.test(candidate, currentVersion));
|
|
||||||
return versions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class VersionOption {
|
|
||||||
|
|
||||||
private final DependencyVersion version;
|
|
||||||
|
|
||||||
protected VersionOption(DependencyVersion version) {
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return this.version.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AlignedVersionOption extends VersionOption {
|
|
||||||
|
|
||||||
private final Library alignedWith;
|
|
||||||
|
|
||||||
private AlignedVersionOption(DependencyVersion version, Library alignedWith) {
|
|
||||||
super(version);
|
|
||||||
this.alignedWith = alignedWith;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString() + " (aligned with " + this.alignedWith.getName() + " "
|
|
||||||
+ this.alignedWith.getVersion().getVersion() + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ResolvedVersionOption extends VersionOption {
|
|
||||||
|
|
||||||
private final List<String> missingModules;
|
|
||||||
|
|
||||||
private ResolvedVersionOption(DependencyVersion version, List<String> missingModules) {
|
|
||||||
super(version);
|
|
||||||
this.missingModules = missingModules;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if (this.missingModules.isEmpty()) {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
return super.toString() + " (some modules are missing: "
|
|
||||||
+ StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.boot.build.bom.bomr;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.boot.build.bom.Library;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves library updates.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
public interface LibraryUpdateResolver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds library updates.
|
||||||
|
* @param librariesToUpgrade libraries to update
|
||||||
|
* @param librariesByName libraries indexed by name
|
||||||
|
* @return library which have updates
|
||||||
|
*/
|
||||||
|
List<LibraryWithVersionOptions> findLibraryUpdates(Collection<Library> librariesToUpgrade,
|
||||||
|
Map<String, Library> librariesByName);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.boot.build.bom.bomr;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.boot.build.bom.Library;
|
||||||
|
|
||||||
|
class LibraryWithVersionOptions {
|
||||||
|
|
||||||
|
private final Library library;
|
||||||
|
|
||||||
|
private final List<VersionOption> versionOptions;
|
||||||
|
|
||||||
|
LibraryWithVersionOptions(Library library, List<VersionOption> versionOptions) {
|
||||||
|
this.library = library;
|
||||||
|
this.versionOptions = versionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
Library getLibrary() {
|
||||||
|
return this.library;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<VersionOption> getVersionOptions() {
|
||||||
|
return this.versionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.boot.build.bom.bomr;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import org.springframework.boot.build.bom.Library;
|
||||||
|
import org.springframework.boot.build.bom.UpgradePolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses multiple threads to find library updates.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
class MultithreadedLibraryUpdateResolver extends StandardLibraryUpdateResolver {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(MultithreadedLibraryUpdateResolver.class);
|
||||||
|
|
||||||
|
private final int threads;
|
||||||
|
|
||||||
|
MultithreadedLibraryUpdateResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy, int threads) {
|
||||||
|
super(versionResolver, upgradePolicy);
|
||||||
|
this.threads = threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<LibraryWithVersionOptions> findLibraryUpdates(Collection<Library> librariesToUpgrade,
|
||||||
|
Map<String, Library> librariesByName) {
|
||||||
|
LOGGER.info("Looking for updates using {} threads", this.threads);
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(this.threads);
|
||||||
|
try {
|
||||||
|
List<Future<LibraryWithVersionOptions>> jobs = new ArrayList<>();
|
||||||
|
for (Library library : librariesToUpgrade) {
|
||||||
|
if (isLibraryExcluded(library)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
jobs.add(executorService.submit(() -> {
|
||||||
|
LOGGER.info("Looking for updates for {}", library.getName());
|
||||||
|
long start = System.nanoTime();
|
||||||
|
List<VersionOption> versionOptions = getVersionOptions(library, librariesByName);
|
||||||
|
LOGGER.info("Found {} updates for {}, took {}", versionOptions.size(), library.getName(),
|
||||||
|
Duration.ofNanos(System.nanoTime() - start));
|
||||||
|
return new LibraryWithVersionOptions(library, versionOptions);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
List<LibraryWithVersionOptions> result = new ArrayList<>();
|
||||||
|
for (Future<LibraryWithVersionOptions> job : jobs) {
|
||||||
|
try {
|
||||||
|
result.add(job.get());
|
||||||
|
}
|
||||||
|
catch (InterruptedException | ExecutionException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.boot.build.bom.bomr;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||||
|
import org.gradle.api.InvalidUserDataException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import org.springframework.boot.build.bom.Library;
|
||||||
|
import org.springframework.boot.build.bom.Library.DependencyVersions;
|
||||||
|
import org.springframework.boot.build.bom.Library.Group;
|
||||||
|
import org.springframework.boot.build.bom.Library.Module;
|
||||||
|
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
|
||||||
|
import org.springframework.boot.build.bom.Library.VersionAlignment;
|
||||||
|
import org.springframework.boot.build.bom.UpgradePolicy;
|
||||||
|
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation for {@link LibraryUpdateResolver}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(StandardLibraryUpdateResolver.class);
|
||||||
|
|
||||||
|
private final VersionResolver versionResolver;
|
||||||
|
|
||||||
|
private final UpgradePolicy upgradePolicy;
|
||||||
|
|
||||||
|
StandardLibraryUpdateResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy) {
|
||||||
|
this.versionResolver = versionResolver;
|
||||||
|
this.upgradePolicy = upgradePolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<LibraryWithVersionOptions> findLibraryUpdates(Collection<Library> librariesToUpgrade,
|
||||||
|
Map<String, Library> librariesByName) {
|
||||||
|
List<LibraryWithVersionOptions> result = new ArrayList<>();
|
||||||
|
for (Library library : librariesToUpgrade) {
|
||||||
|
if (isLibraryExcluded(library)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LOGGER.info("Looking for updates for {}", library.getName());
|
||||||
|
long start = System.nanoTime();
|
||||||
|
List<VersionOption> versionOptions = getVersionOptions(library, librariesByName);
|
||||||
|
result.add(new LibraryWithVersionOptions(library, versionOptions));
|
||||||
|
LOGGER.info("Found {} updates for {}, took {}", versionOptions.size(), library.getName(),
|
||||||
|
Duration.ofNanos(System.nanoTime() - start));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isLibraryExcluded(Library library) {
|
||||||
|
return library.getName().equals("Spring Boot");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<VersionOption> getVersionOptions(Library library, Map<String, Library> libraries) {
|
||||||
|
if (library.getVersion().getVersionAlignment() != null) {
|
||||||
|
return determineAlignedVersionOption(library, libraries);
|
||||||
|
}
|
||||||
|
return determineResolvedVersionOptions(library);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<VersionOption> determineResolvedVersionOptions(Library library) {
|
||||||
|
Map<String, SortedSet<DependencyVersion>> moduleVersions = new LinkedHashMap<>();
|
||||||
|
DependencyVersion libraryVersion = library.getVersion().getVersion();
|
||||||
|
for (Group group : library.getGroups()) {
|
||||||
|
for (Module module : group.getModules()) {
|
||||||
|
moduleVersions.put(group.getId() + ":" + module.getName(),
|
||||||
|
getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion));
|
||||||
|
}
|
||||||
|
for (String bom : group.getBoms()) {
|
||||||
|
moduleVersions.put(group.getId() + ":" + bom,
|
||||||
|
getLaterVersionsForModule(group.getId(), bom, libraryVersion));
|
||||||
|
}
|
||||||
|
for (String plugin : group.getPlugins()) {
|
||||||
|
moduleVersions.put(group.getId() + ":" + plugin,
|
||||||
|
getLaterVersionsForModule(group.getId(), plugin, libraryVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<DependencyVersion> allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
|
||||||
|
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (allVersions.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return allVersions.stream().map((version) -> new VersionOption.ResolvedVersionOption(version,
|
||||||
|
getMissingModules(moduleVersions, version))).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<VersionOption> determineAlignedVersionOption(Library library, Map<String, Library> libraries) {
|
||||||
|
VersionOption alignedVersionOption = alignedVersionOption(library, libraries);
|
||||||
|
if (alignedVersionOption == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
if (!isPermitted(alignedVersionOption.getVersion(), library.getProhibitedVersions())) {
|
||||||
|
throw new InvalidUserDataException("Version alignment failed. Version " + alignedVersionOption.getVersion()
|
||||||
|
+ " from " + library.getName() + " is prohibited");
|
||||||
|
}
|
||||||
|
return Collections.singletonList(alignedVersionOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VersionOption alignedVersionOption(Library library, Map<String, Library> libraries) {
|
||||||
|
VersionAlignment versionAlignment = library.getVersion().getVersionAlignment();
|
||||||
|
Library alignmentLibrary = libraries.get(versionAlignment.getLibraryName());
|
||||||
|
DependencyVersions dependencyVersions = alignmentLibrary.getDependencyVersions();
|
||||||
|
if (dependencyVersions == null) {
|
||||||
|
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
|
||||||
|
+ "' as it does not define any dependency versions");
|
||||||
|
}
|
||||||
|
if (!dependencyVersions.available()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Set<String> versions = new HashSet<>();
|
||||||
|
for (Group group : library.getGroups()) {
|
||||||
|
for (Module module : group.getModules()) {
|
||||||
|
String version = dependencyVersions.getVersion(group.getId(), module.getName());
|
||||||
|
if (version != null) {
|
||||||
|
versions.add(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (versions.isEmpty()) {
|
||||||
|
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
|
||||||
|
+ "' as its dependency versions do not include any of this library's modules");
|
||||||
|
}
|
||||||
|
if (versions.size() > 1) {
|
||||||
|
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
|
||||||
|
+ "' as it uses multiple different versions of this library's modules");
|
||||||
|
}
|
||||||
|
DependencyVersion version = DependencyVersion.parse(versions.iterator().next());
|
||||||
|
return library.getVersion().getVersion().equals(version) ? null
|
||||||
|
: new VersionOption.AlignedVersionOption(version, alignmentLibrary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPermitted(DependencyVersion dependencyVersion, List<ProhibitedVersion> prohibitedVersions) {
|
||||||
|
for (ProhibitedVersion prohibitedVersion : prohibitedVersions) {
|
||||||
|
String dependencyVersionToString = dependencyVersion.toString();
|
||||||
|
if (prohibitedVersion.getRange() != null && prohibitedVersion.getRange()
|
||||||
|
.containsVersion(new DefaultArtifactVersion(dependencyVersionToString))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String startsWith : prohibitedVersion.getStartsWith()) {
|
||||||
|
if (dependencyVersionToString.startsWith(startsWith)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String endsWith : prohibitedVersion.getEndsWith()) {
|
||||||
|
if (dependencyVersionToString.endsWith(endsWith)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String contains : prohibitedVersion.getContains()) {
|
||||||
|
if (dependencyVersionToString.contains(contains)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getMissingModules(Map<String, SortedSet<DependencyVersion>> moduleVersions,
|
||||||
|
DependencyVersion version) {
|
||||||
|
List<String> missingModules = new ArrayList<>();
|
||||||
|
moduleVersions.forEach((name, versions) -> {
|
||||||
|
if (!versions.contains(version)) {
|
||||||
|
missingModules.add(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return missingModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SortedSet<DependencyVersion> getLaterVersionsForModule(String groupId, String artifactId,
|
||||||
|
DependencyVersion currentVersion) {
|
||||||
|
SortedSet<DependencyVersion> versions = this.versionResolver.resolveVersions(groupId, artifactId);
|
||||||
|
versions.removeIf((candidate) -> !this.upgradePolicy.test(candidate, currentVersion));
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2022 the original author or authors.
|
* Copyright 2012-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -55,10 +55,11 @@ import org.springframework.util.StringUtils;
|
|||||||
* {@link Task} to upgrade the libraries managed by a bom.
|
* {@link Task} to upgrade the libraries managed by a bom.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Moritz Halbritter
|
||||||
*/
|
*/
|
||||||
public class UpgradeBom extends DefaultTask {
|
public class UpgradeBom extends DefaultTask {
|
||||||
|
|
||||||
private final Set<String> repositoryUrls;
|
private final Set<String> repositoryUrls = new LinkedHashSet<>();
|
||||||
|
|
||||||
private final BomExtension bom;
|
private final BomExtension bom;
|
||||||
|
|
||||||
@ -66,10 +67,11 @@ public class UpgradeBom extends DefaultTask {
|
|||||||
|
|
||||||
private String libraries;
|
private String libraries;
|
||||||
|
|
||||||
|
private int threads = 8;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UpgradeBom(BomExtension bom) {
|
public UpgradeBom(BomExtension bom) {
|
||||||
this.bom = bom;
|
this.bom = bom;
|
||||||
this.repositoryUrls = new LinkedHashSet<>();
|
|
||||||
getProject().getRepositories().withType(MavenArtifactRepository.class, (repository) -> {
|
getProject().getRepositories().withType(MavenArtifactRepository.class, (repository) -> {
|
||||||
String repositoryUrl = repository.getUrl().toString();
|
String repositoryUrl = repository.getUrl().toString();
|
||||||
if (!repositoryUrl.endsWith("snapshot")) {
|
if (!repositoryUrl.endsWith("snapshot")) {
|
||||||
@ -83,6 +85,11 @@ public class UpgradeBom extends DefaultTask {
|
|||||||
this.milestone = milestone;
|
this.milestone = milestone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Option(option = "threads", description = "Number of Threads to use for update resolution")
|
||||||
|
public void setThreads(String threads) {
|
||||||
|
this.threads = Integer.parseInt(threads);
|
||||||
|
}
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
public String getMilestone() {
|
public String getMilestone() {
|
||||||
return this.milestone;
|
return this.milestone;
|
||||||
@ -104,7 +111,7 @@ public class UpgradeBom extends DefaultTask {
|
|||||||
void upgradeDependencies() {
|
void upgradeDependencies() {
|
||||||
GitHubRepository repository = createGitHub().getRepository(this.bom.getUpgrade().getGitHub().getOrganization(),
|
GitHubRepository repository = createGitHub().getRepository(this.bom.getUpgrade().getGitHub().getOrganization(),
|
||||||
this.bom.getUpgrade().getGitHub().getRepository());
|
this.bom.getUpgrade().getGitHub().getRepository());
|
||||||
List<String> availableLabels = repository.getLabels();
|
Set<String> availableLabels = repository.getLabels();
|
||||||
List<String> issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels();
|
List<String> issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels();
|
||||||
if (!availableLabels.containsAll(issueLabels)) {
|
if (!availableLabels.containsAll(issueLabels)) {
|
||||||
List<String> unknownLabels = new ArrayList<>(issueLabels);
|
List<String> unknownLabels = new ArrayList<>(issueLabels);
|
||||||
@ -114,8 +121,9 @@ public class UpgradeBom extends DefaultTask {
|
|||||||
}
|
}
|
||||||
Milestone milestone = determineMilestone(repository);
|
Milestone milestone = determineMilestone(repository);
|
||||||
List<Issue> existingUpgradeIssues = repository.findIssues(issueLabels, milestone);
|
List<Issue> existingUpgradeIssues = repository.findIssues(issueLabels, milestone);
|
||||||
List<Upgrade> upgrades = new InteractiveUpgradeResolver(new MavenMetadataVersionResolver(this.repositoryUrls),
|
List<Upgrade> upgrades = new InteractiveUpgradeResolver(getServices().get(UserInputHandler.class),
|
||||||
this.bom.getUpgrade().getPolicy(), getServices().get(UserInputHandler.class))
|
new MultithreadedLibraryUpdateResolver(new MavenMetadataVersionResolver(this.repositoryUrls),
|
||||||
|
this.bom.getUpgrade().getPolicy(), this.threads))
|
||||||
.resolveUpgrades(matchingLibraries(this.libraries), this.bom.getLibraries());
|
.resolveUpgrades(matchingLibraries(this.libraries), this.bom.getLibraries());
|
||||||
Path buildFile = getProject().getBuildFile().toPath();
|
Path buildFile = getProject().getBuildFile().toPath();
|
||||||
Path gradleProperties = new File(getProject().getRootProject().getProjectDir(), "gradle.properties").toPath();
|
Path gradleProperties = new File(getProject().getRootProject().getProjectDir(), "gradle.properties").toPath();
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.boot.build.bom.bomr;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.boot.build.bom.Library;
|
||||||
|
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An option for a library update.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class VersionOption {
|
||||||
|
|
||||||
|
private final DependencyVersion version;
|
||||||
|
|
||||||
|
VersionOption(DependencyVersion version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
DependencyVersion getVersion() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.version.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class AlignedVersionOption extends VersionOption {
|
||||||
|
|
||||||
|
private final Library alignedWith;
|
||||||
|
|
||||||
|
AlignedVersionOption(DependencyVersion version, Library alignedWith) {
|
||||||
|
super(version);
|
||||||
|
this.alignedWith = alignedWith;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " (aligned with " + this.alignedWith.getName() + " "
|
||||||
|
+ this.alignedWith.getVersion().getVersion() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ResolvedVersionOption extends VersionOption {
|
||||||
|
|
||||||
|
private final List<String> missingModules;
|
||||||
|
|
||||||
|
ResolvedVersionOption(DependencyVersion version, List<String> missingModules) {
|
||||||
|
super(version);
|
||||||
|
this.missingModules = missingModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (this.missingModules.isEmpty()) {
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
|
return super.toString() + " (some modules are missing: "
|
||||||
|
+ StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2021 the original author or authors.
|
* Copyright 2012-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,6 +17,7 @@
|
|||||||
package org.springframework.boot.build.bom.bomr.github;
|
package org.springframework.boot.build.bom.bomr.github;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal API for interacting with a GitHub repository.
|
* Minimal API for interacting with a GitHub repository.
|
||||||
@ -40,7 +41,7 @@ public interface GitHubRepository {
|
|||||||
* Returns the labels in the repository.
|
* Returns the labels in the repository.
|
||||||
* @return the labels
|
* @return the labels
|
||||||
*/
|
*/
|
||||||
List<String> getLabels();
|
Set<String> getLabels();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the milestones in the repository.
|
* Returns the milestones in the repository.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2022 the original author or authors.
|
* Copyright 2012-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,8 +17,10 @@
|
|||||||
package org.springframework.boot.build.bom.bomr.github;
|
package org.springframework.boot.build.bom.bomr.github;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -70,8 +72,8 @@ final class StandardGitHubRepository implements GitHubRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getLabels() {
|
public Set<String> getLabels() {
|
||||||
return get("labels?per_page=100", (label) -> (String) label.get("name"));
|
return new HashSet<>(get("labels?per_page=100", (label) -> (String) label.get("name")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
Reference in New Issue
Block a user