Skip to content

Commit e0b7720

Browse files
committed
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
1 parent 484d662 commit e0b7720

9 files changed

+502
-223
lines changed
Lines changed: 17 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,32 +16,16 @@
1616

1717
package org.springframework.boot.build.bom.bomr;
1818

19-
import java.util.ArrayList;
2019
import java.util.Collection;
21-
import java.util.Collections;
2220
import java.util.HashMap;
23-
import java.util.HashSet;
24-
import java.util.LinkedHashMap;
2521
import java.util.List;
2622
import java.util.Map;
2723
import java.util.Objects;
28-
import java.util.Set;
29-
import java.util.SortedSet;
3024
import java.util.stream.Collectors;
3125

32-
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
33-
import org.gradle.api.InvalidUserDataException;
3426
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
3527

3628
import org.springframework.boot.build.bom.Library;
37-
import org.springframework.boot.build.bom.Library.DependencyVersions;
38-
import org.springframework.boot.build.bom.Library.Group;
39-
import org.springframework.boot.build.bom.Library.Module;
40-
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
41-
import org.springframework.boot.build.bom.Library.VersionAlignment;
42-
import org.springframework.boot.build.bom.UpgradePolicy;
43-
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
44-
import org.springframework.util.StringUtils;
4529

4630
/**
4731
* Interactive {@link UpgradeResolver} that uses command line input to choose the upgrades
@@ -51,17 +35,13 @@
5135
*/
5236
public final class InteractiveUpgradeResolver implements UpgradeResolver {
5337

54-
private final VersionResolver versionResolver;
55-
56-
private final UpgradePolicy upgradePolicy;
57-
5838
private final UserInputHandler userInputHandler;
5939

60-
InteractiveUpgradeResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy,
61-
UserInputHandler userInputHandler) {
62-
this.versionResolver = versionResolver;
63-
this.upgradePolicy = upgradePolicy;
40+
private final LibraryUpdateResolver libraryUpdateResolver;
41+
42+
InteractiveUpgradeResolver(UserInputHandler userInputHandler, LibraryUpdateResolver libraryUpdateResolver) {
6443
this.userInputHandler = userInputHandler;
44+
this.libraryUpdateResolver = libraryUpdateResolver;
6545
}
6646

6747
@Override
@@ -70,196 +50,22 @@ public List<Upgrade> resolveUpgrades(Collection<Library> librariesToUpgrade, Col
7050
for (Library library : libraries) {
7151
librariesByName.put(library.getName(), library);
7252
}
73-
return librariesToUpgrade.stream().filter((library) -> !library.getName().equals("Spring Boot"))
74-
.map((library) -> resolveUpgrade(library, librariesByName)).filter(Objects::nonNull)
75-
.collect(Collectors.toList());
53+
List<LibraryWithVersionOptions> libraryUpdates = this.libraryUpdateResolver
54+
.findLibraryUpdates(librariesToUpgrade, librariesByName);
55+
return libraryUpdates.stream().map(this::resolveUpgrade).filter(Objects::nonNull).collect(Collectors.toList());
7656
}
7757

78-
private Upgrade resolveUpgrade(Library library, Map<String, Library> libraries) {
79-
List<VersionOption> versionOptions = getVersionOptions(library, libraries);
80-
if (versionOptions.isEmpty()) {
58+
private Upgrade resolveUpgrade(LibraryWithVersionOptions libraryWithVersionOptions) {
59+
if (libraryWithVersionOptions.getVersionOptions().isEmpty()) {
8160
return null;
8261
}
83-
VersionOption current = new VersionOption(library.getVersion().getVersion());
84-
VersionOption selected = this.userInputHandler
85-
.selectOption(library.getName() + " " + library.getVersion().getVersion(), versionOptions, current);
86-
return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
87-
}
88-
89-
private List<VersionOption> getVersionOptions(Library library, Map<String, Library> libraries) {
90-
if (library.getVersion().getVersionAlignment() != null) {
91-
return determineAlignedVersionOption(library, libraries);
92-
}
93-
return determineResolvedVersionOptions(library);
94-
}
95-
96-
private List<VersionOption> determineResolvedVersionOptions(Library library) {
97-
Map<String, SortedSet<DependencyVersion>> moduleVersions = new LinkedHashMap<>();
98-
DependencyVersion libraryVersion = library.getVersion().getVersion();
99-
for (Group group : library.getGroups()) {
100-
for (Module module : group.getModules()) {
101-
moduleVersions.put(group.getId() + ":" + module.getName(),
102-
getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion));
103-
}
104-
for (String bom : group.getBoms()) {
105-
moduleVersions.put(group.getId() + ":" + bom,
106-
getLaterVersionsForModule(group.getId(), bom, libraryVersion));
107-
}
108-
for (String plugin : group.getPlugins()) {
109-
moduleVersions.put(group.getId() + ":" + plugin,
110-
getLaterVersionsForModule(group.getId(), plugin, libraryVersion));
111-
}
112-
}
113-
List<DependencyVersion> allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
114-
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
115-
.collect(Collectors.toList());
116-
if (allVersions.isEmpty()) {
117-
return Collections.emptyList();
118-
}
119-
return allVersions.stream()
120-
.map((version) -> new ResolvedVersionOption(version, getMissingModules(moduleVersions, version)))
121-
.collect(Collectors.toList());
122-
}
123-
124-
private List<VersionOption> determineAlignedVersionOption(Library library, Map<String, Library> libraries) {
125-
VersionOption alignedVersionOption = alignedVersionOption(library, libraries);
126-
if (alignedVersionOption == null) {
127-
return Collections.emptyList();
128-
}
129-
if (!isPermitted(alignedVersionOption.version, library.getProhibitedVersions())) {
130-
throw new InvalidUserDataException("Version alignment failed. Version " + alignedVersionOption.version
131-
+ " from " + library.getName() + " is prohibited");
132-
}
133-
return Collections.singletonList(alignedVersionOption);
134-
}
135-
136-
private VersionOption alignedVersionOption(Library library, Map<String, Library> libraries) {
137-
VersionAlignment versionAlignment = library.getVersion().getVersionAlignment();
138-
Library alignmentLibrary = libraries.get(versionAlignment.getLibraryName());
139-
DependencyVersions dependencyVersions = alignmentLibrary.getDependencyVersions();
140-
if (dependencyVersions == null) {
141-
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
142-
+ "' as it does not define any dependency versions");
143-
}
144-
if (!dependencyVersions.available()) {
145-
return null;
146-
}
147-
Set<String> versions = new HashSet<>();
148-
for (Group group : library.getGroups()) {
149-
for (Module module : group.getModules()) {
150-
String version = dependencyVersions.getVersion(group.getId(), module.getName());
151-
if (version != null) {
152-
versions.add(version);
153-
}
154-
}
155-
}
156-
if (versions.isEmpty()) {
157-
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
158-
+ "' as its dependency versions do not include any of this library's modules");
159-
}
160-
if (versions.size() > 1) {
161-
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
162-
+ "' as it uses multiple different versions of this library's modules");
163-
}
164-
DependencyVersion version = DependencyVersion.parse(versions.iterator().next());
165-
return library.getVersion().getVersion().equals(version) ? null
166-
: new AlignedVersionOption(version, alignmentLibrary);
167-
}
168-
169-
private boolean isPermitted(DependencyVersion dependencyVersion, List<ProhibitedVersion> prohibitedVersions) {
170-
for (ProhibitedVersion prohibitedVersion : prohibitedVersions) {
171-
String dependencyVersionToString = dependencyVersion.toString();
172-
if (prohibitedVersion.getRange() != null && prohibitedVersion.getRange()
173-
.containsVersion(new DefaultArtifactVersion(dependencyVersionToString))) {
174-
return false;
175-
}
176-
for (String startsWith : prohibitedVersion.getStartsWith()) {
177-
if (dependencyVersionToString.startsWith(startsWith)) {
178-
return false;
179-
}
180-
}
181-
for (String endsWith : prohibitedVersion.getEndsWith()) {
182-
if (dependencyVersionToString.endsWith(endsWith)) {
183-
return false;
184-
}
185-
}
186-
for (String contains : prohibitedVersion.getContains()) {
187-
if (dependencyVersionToString.contains(contains)) {
188-
return false;
189-
}
190-
}
191-
}
192-
return true;
193-
}
194-
195-
private List<String> getMissingModules(Map<String, SortedSet<DependencyVersion>> moduleVersions,
196-
DependencyVersion version) {
197-
List<String> missingModules = new ArrayList<>();
198-
moduleVersions.forEach((name, versions) -> {
199-
if (!versions.contains(version)) {
200-
missingModules.add(name);
201-
}
202-
});
203-
return missingModules;
204-
}
205-
206-
private SortedSet<DependencyVersion> getLaterVersionsForModule(String groupId, String artifactId,
207-
DependencyVersion currentVersion) {
208-
SortedSet<DependencyVersion> versions = this.versionResolver.resolveVersions(groupId, artifactId);
209-
versions.removeIf((candidate) -> !this.upgradePolicy.test(candidate, currentVersion));
210-
return versions;
211-
}
212-
213-
private static class VersionOption {
214-
215-
private final DependencyVersion version;
216-
217-
protected VersionOption(DependencyVersion version) {
218-
this.version = version;
219-
}
220-
221-
@Override
222-
public String toString() {
223-
return this.version.toString();
224-
}
225-
226-
}
227-
228-
private static final class AlignedVersionOption extends VersionOption {
229-
230-
private final Library alignedWith;
231-
232-
private AlignedVersionOption(DependencyVersion version, Library alignedWith) {
233-
super(version);
234-
this.alignedWith = alignedWith;
235-
}
236-
237-
@Override
238-
public String toString() {
239-
return super.toString() + " (aligned with " + this.alignedWith.getName() + " "
240-
+ this.alignedWith.getVersion().getVersion() + ")";
241-
}
242-
243-
}
244-
245-
private static final class ResolvedVersionOption extends VersionOption {
246-
247-
private final List<String> missingModules;
248-
249-
private ResolvedVersionOption(DependencyVersion version, List<String> missingModules) {
250-
super(version);
251-
this.missingModules = missingModules;
252-
}
253-
254-
@Override
255-
public String toString() {
256-
if (this.missingModules.isEmpty()) {
257-
return super.toString();
258-
}
259-
return super.toString() + " (some modules are missing: "
260-
+ StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")";
261-
}
262-
62+
VersionOption current = new VersionOption(libraryWithVersionOptions.getLibrary().getVersion().getVersion());
63+
VersionOption selected = this.userInputHandler.selectOption(
64+
libraryWithVersionOptions.getLibrary().getName() + " "
65+
+ libraryWithVersionOptions.getLibrary().getVersion().getVersion(),
66+
libraryWithVersionOptions.getVersionOptions(), current);
67+
return (selected.equals(current)) ? null
68+
: new Upgrade(libraryWithVersionOptions.getLibrary(), selected.getVersion());
26369
}
26470

26571
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.build.bom.bomr;
18+
19+
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.springframework.boot.build.bom.Library;
24+
25+
/**
26+
* Resolves library updates.
27+
*
28+
* @author Moritz Halbritter
29+
*/
30+
public interface LibraryUpdateResolver {
31+
32+
/**
33+
* Finds library updates.
34+
* @param librariesToUpgrade libraries to update
35+
* @param librariesByName libraries indexed by name
36+
* @return library which have updates
37+
*/
38+
List<LibraryWithVersionOptions> findLibraryUpdates(Collection<Library> librariesToUpgrade,
39+
Map<String, Library> librariesByName);
40+
41+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.build.bom.bomr;
18+
19+
import java.util.List;
20+
21+
import org.springframework.boot.build.bom.Library;
22+
23+
class LibraryWithVersionOptions {
24+
25+
private final Library library;
26+
27+
private final List<VersionOption> versionOptions;
28+
29+
LibraryWithVersionOptions(Library library, List<VersionOption> versionOptions) {
30+
this.library = library;
31+
this.versionOptions = versionOptions;
32+
}
33+
34+
Library getLibrary() {
35+
return this.library;
36+
}
37+
38+
List<VersionOption> getVersionOptions() {
39+
return this.versionOptions;
40+
}
41+
42+
}

0 commit comments

Comments
 (0)