From e0b7720b9e218ea0e85e67a9f255f0694c40a0d3 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 13 Jan 2023 10:42:44 +0100 Subject: [PATCH] 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 --- .../bom/bomr/InteractiveUpgradeResolver.java | 228 ++---------------- .../build/bom/bomr/LibraryUpdateResolver.java | 41 ++++ .../bom/bomr/LibraryWithVersionOptions.java | 42 ++++ .../MultithreadedLibraryUpdateResolver.java | 87 +++++++ .../bomr/StandardLibraryUpdateResolver.java | 208 ++++++++++++++++ .../boot/build/bom/bomr/UpgradeBom.java | 22 +- .../boot/build/bom/bomr/VersionOption.java | 84 +++++++ .../bom/bomr/github/GitHubRepository.java | 5 +- .../bomr/github/StandardGitHubRepository.java | 8 +- 9 files changed, 502 insertions(+), 223 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryUpdateResolver.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryWithVersionOptions.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MultithreadedLibraryUpdateResolver.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionOption.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java index 27f3aa9c6f0..7d430b93910 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java @@ -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 libraryUpdates = this.libraryUpdateResolver + .findLibraryUpdates(librariesToUpgrade, librariesByName); + return libraryUpdates.stream().map(this::resolveUpgrade).filter(Objects::nonNull).collect(Collectors.toList()); } - private Upgrade resolveUpgrade(Library library, Map libraries) { - List 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 getVersionOptions(Library library, Map libraries) { - if (library.getVersion().getVersionAlignment() != null) { - return determineAlignedVersionOption(library, libraries); - } - return determineResolvedVersionOptions(library); - } - - private List determineResolvedVersionOptions(Library library) { - Map> 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 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 determineAlignedVersionOption(Library library, Map 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 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 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 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 getMissingModules(Map> moduleVersions, - DependencyVersion version) { - List missingModules = new ArrayList<>(); - moduleVersions.forEach((name, versions) -> { - if (!versions.contains(version)) { - missingModules.add(name); - } - }); - return missingModules; - } - - private SortedSet getLaterVersionsForModule(String groupId, String artifactId, - DependencyVersion currentVersion) { - SortedSet 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 missingModules; - - private ResolvedVersionOption(DependencyVersion version, List 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()); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryUpdateResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryUpdateResolver.java new file mode 100644 index 00000000000..13c32c0033a --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryUpdateResolver.java @@ -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 findLibraryUpdates(Collection librariesToUpgrade, + Map librariesByName); + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryWithVersionOptions.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryWithVersionOptions.java new file mode 100644 index 00000000000..4dbb097bea6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/LibraryWithVersionOptions.java @@ -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 versionOptions; + + LibraryWithVersionOptions(Library library, List versionOptions) { + this.library = library; + this.versionOptions = versionOptions; + } + + Library getLibrary() { + return this.library; + } + + List getVersionOptions() { + return this.versionOptions; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MultithreadedLibraryUpdateResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MultithreadedLibraryUpdateResolver.java new file mode 100644 index 00000000000..69239b006ee --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MultithreadedLibraryUpdateResolver.java @@ -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 findLibraryUpdates(Collection librariesToUpgrade, + Map librariesByName) { + LOGGER.info("Looking for updates using {} threads", this.threads); + ExecutorService executorService = Executors.newFixedThreadPool(this.threads); + try { + List> 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 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 result = new ArrayList<>(); + for (Future job : jobs) { + try { + result.add(job.get()); + } + catch (InterruptedException | ExecutionException ex) { + throw new RuntimeException(ex); + } + } + return result; + } + finally { + executorService.shutdownNow(); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java new file mode 100644 index 00000000000..c7c1f45bb89 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/StandardLibraryUpdateResolver.java @@ -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 findLibraryUpdates(Collection librariesToUpgrade, + Map librariesByName) { + List result = new ArrayList<>(); + for (Library library : librariesToUpgrade) { + if (isLibraryExcluded(library)) { + continue; + } + LOGGER.info("Looking for updates for {}", library.getName()); + long start = System.nanoTime(); + List 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 getVersionOptions(Library library, Map libraries) { + if (library.getVersion().getVersionAlignment() != null) { + return determineAlignedVersionOption(library, libraries); + } + return determineResolvedVersionOptions(library); + } + + private List determineResolvedVersionOptions(Library library) { + Map> 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 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 determineAlignedVersionOption(Library library, Map 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 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 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 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 getMissingModules(Map> moduleVersions, + DependencyVersion version) { + List missingModules = new ArrayList<>(); + moduleVersions.forEach((name, versions) -> { + if (!versions.contains(version)) { + missingModules.add(name); + } + }); + return missingModules; + } + + private SortedSet getLaterVersionsForModule(String groupId, String artifactId, + DependencyVersion currentVersion) { + SortedSet versions = this.versionResolver.resolveVersions(groupId, artifactId); + versions.removeIf((candidate) -> !this.upgradePolicy.test(candidate, currentVersion)); + return versions; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java index 8a38fce9bed..eb09f0e01bc 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java @@ -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 repositoryUrls; + private final Set 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 availableLabels = repository.getLabels(); + Set availableLabels = repository.getLabels(); List issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels(); if (!availableLabels.containsAll(issueLabels)) { List unknownLabels = new ArrayList<>(issueLabels); @@ -115,9 +122,10 @@ public class UpgradeBom extends DefaultTask { } Milestone milestone = determineMilestone(repository); List existingUpgradeIssues = repository.findIssues(issueLabels, milestone); - List upgrades = new InteractiveUpgradeResolver(new MavenMetadataVersionResolver(this.repositoryUrls), - this.bom.getUpgrade().getPolicy(), getServices().get(UserInputHandler.class)) - .resolveUpgrades(matchingLibraries(this.libraries), this.bom.getLibraries()); + List 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); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionOption.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionOption.java new file mode 100644 index 00000000000..def90fe4506 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionOption.java @@ -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 missingModules; + + ResolvedVersionOption(DependencyVersion version, List 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, ", ") + ")"; + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java index 98b47a414eb..d1460a9506f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java @@ -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 getLabels(); + Set getLabels(); /** * Returns the milestones in the repository. diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java index 2c9059f3ec4..e7d0b6a6817 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java @@ -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 getLabels() { - return get("labels?per_page=100", (label) -> (String) label.get("name")); + public Set getLabels() { + return new HashSet<>(get("labels?per_page=100", (label) -> (String) label.get("name"))); } @Override