Implement multithreaded BOMR library resolution

- BOMR now first looks for library updates, collects them all and then
  prompts the user to choose which update to apply
- Refactored code into StandardLibraryUpdateResolver
- Implemented MultithreadedLibraryUpdateResolver on top of the standard
  one
- Uses 8 threads by default, this is configurable
- When run with --info, it logs how long each update search took

Closes gh-33824
This commit is contained in:
Moritz Halbritter 2023-01-13 10:42:44 +01:00
parent 484d662085
commit e0b7720b9e
9 changed files with 502 additions and 223 deletions

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -16,32 +16,16 @@
package org.springframework.boot.build.bom.bomr;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.gradle.api.internal.tasks.userinput.UserInputHandler;
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
@ -51,17 +35,13 @@ import org.springframework.util.StringUtils;
*/
public final class InteractiveUpgradeResolver implements UpgradeResolver {
private final VersionResolver versionResolver;
private final UpgradePolicy upgradePolicy;
private final UserInputHandler userInputHandler;
InteractiveUpgradeResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy,
UserInputHandler userInputHandler) {
this.versionResolver = versionResolver;
this.upgradePolicy = upgradePolicy;
private final LibraryUpdateResolver libraryUpdateResolver;
InteractiveUpgradeResolver(UserInputHandler userInputHandler, LibraryUpdateResolver libraryUpdateResolver) {
this.userInputHandler = userInputHandler;
this.libraryUpdateResolver = libraryUpdateResolver;
}
@Override
@ -70,196 +50,22 @@ public final class InteractiveUpgradeResolver implements UpgradeResolver {
for (Library library : libraries) {
librariesByName.put(library.getName(), library);
}
return librariesToUpgrade.stream().filter((library) -> !library.getName().equals("Spring Boot"))
.map((library) -> resolveUpgrade(library, librariesByName)).filter(Objects::nonNull)
.collect(Collectors.toList());
List<LibraryWithVersionOptions> libraryUpdates = this.libraryUpdateResolver
.findLibraryUpdates(librariesToUpgrade, librariesByName);
return libraryUpdates.stream().map(this::resolveUpgrade).filter(Objects::nonNull).collect(Collectors.toList());
}
private Upgrade resolveUpgrade(Library library, Map<String, Library> libraries) {
List<VersionOption> versionOptions = getVersionOptions(library, libraries);
if (versionOptions.isEmpty()) {
private Upgrade resolveUpgrade(LibraryWithVersionOptions libraryWithVersionOptions) {
if (libraryWithVersionOptions.getVersionOptions().isEmpty()) {
return null;
}
VersionOption current = new VersionOption(library.getVersion().getVersion());
VersionOption selected = this.userInputHandler
.selectOption(library.getName() + " " + library.getVersion().getVersion(), versionOptions, current);
return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
}
private 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 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.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, ", ") + ")";
}
VersionOption current = new VersionOption(libraryWithVersionOptions.getLibrary().getVersion().getVersion());
VersionOption selected = this.userInputHandler.selectOption(
libraryWithVersionOptions.getLibrary().getName() + " "
+ libraryWithVersionOptions.getLibrary().getVersion().getVersion(),
libraryWithVersionOptions.getVersionOptions(), current);
return (selected.equals(current)) ? null
: new Upgrade(libraryWithVersionOptions.getLibrary(), selected.getVersion());
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -56,10 +56,11 @@ import org.springframework.util.StringUtils;
* {@link Task} to upgrade the libraries managed by a bom.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
*/
public class UpgradeBom extends DefaultTask {
private Set<String> repositoryUrls;
private final Set<String> repositoryUrls = new LinkedHashSet<>();
private final BomExtension bom;
@ -67,10 +68,11 @@ public class UpgradeBom extends DefaultTask {
private String libraries;
private int threads = 8;
@Inject
public UpgradeBom(BomExtension bom) {
this.bom = bom;
this.repositoryUrls = new LinkedHashSet<>();
getProject().getRepositories().withType(MavenArtifactRepository.class, (repository) -> {
String repositoryUrl = repository.getUrl().toString();
if (!repositoryUrl.endsWith("snapshot")) {
@ -84,6 +86,11 @@ public class UpgradeBom extends DefaultTask {
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
public String getMilestone() {
return this.milestone;
@ -105,7 +112,7 @@ public class UpgradeBom extends DefaultTask {
void upgradeDependencies() {
GitHubRepository repository = createGitHub().getRepository(this.bom.getUpgrade().getGitHub().getOrganization(),
this.bom.getUpgrade().getGitHub().getRepository());
List<String> availableLabels = repository.getLabels();
Set<String> availableLabels = repository.getLabels();
List<String> issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels();
if (!availableLabels.containsAll(issueLabels)) {
List<String> unknownLabels = new ArrayList<>(issueLabels);
@ -115,9 +122,10 @@ public class UpgradeBom extends DefaultTask {
}
Milestone milestone = determineMilestone(repository);
List<Issue> existingUpgradeIssues = repository.findIssues(issueLabels, milestone);
List<Upgrade> upgrades = new InteractiveUpgradeResolver(new MavenMetadataVersionResolver(this.repositoryUrls),
this.bom.getUpgrade().getPolicy(), getServices().get(UserInputHandler.class))
.resolveUpgrades(matchingLibraries(this.libraries), this.bom.getLibraries());
List<Upgrade> upgrades = new InteractiveUpgradeResolver(getServices().get(UserInputHandler.class),
new MultithreadedLibraryUpdateResolver(new MavenMetadataVersionResolver(this.repositoryUrls),
this.bom.getUpgrade().getPolicy(), this.threads))
.resolveUpgrades(matchingLibraries(this.libraries), this.bom.getLibraries());
Path buildFile = getProject().getBuildFile().toPath();
Path gradleProperties = new File(getProject().getRootProject().getProjectDir(), "gradle.properties").toPath();
UpgradeApplicator upgradeApplicator = new UpgradeApplicator(buildFile, gradleProperties);

View File

@ -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, ", ") + ")";
}
}
}

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.boot.build.bom.bomr.github;
import java.util.List;
import java.util.Set;
/**
* Minimal API for interacting with a GitHub repository.
@ -40,7 +41,7 @@ public interface GitHubRepository {
* Returns the labels in the repository.
* @return the labels
*/
List<String> getLabels();
Set<String> getLabels();
/**
* Returns the milestones in the repository.

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -17,8 +17,10 @@
package org.springframework.boot.build.bom.bomr.github;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -71,8 +73,8 @@ final class StandardGitHubRepository implements GitHubRepository {
}
@Override
public List<String> getLabels() {
return get("labels?per_page=100", (label) -> (String) label.get("name"));
public Set<String> getLabels() {
return new HashSet<>(get("labels?per_page=100", (label) -> (String) label.get("name")));
}
@Override