Skip to content

Commit e0fadc6

Browse files
committed
Fix configuration resolution lock conflicts in parallel builds for Gradle 9
1 parent 9013cb8 commit e0fadc6

File tree

6 files changed

+850
-121
lines changed

6 files changed

+850
-121
lines changed

src/main/groovy/netflix/nebula/dependency/recommender/DependencyRecommendationsPlugin.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,73 @@ private void applyRecommendationsDirectly(final Project project, final Configura
7474
project.afterEvaluate(new Action<Project>() {
7575
@Override
7676
public void execute(Project p) {
77+
// Eagerly resolve and cache all BOMs after project evaluation
78+
eagerlyResolveBoms(p, recommendationProviderContainer);
79+
7780
p.getConfigurations().all(new ExtendRecommenderConfigurationAction(bomConfiguration, p, recommendationProviderContainer));
7881
p.subprojects(new Action<Project>() {
7982
@Override
8083
public void execute(Project sub) {
84+
// Also eagerly resolve BOMs for subprojects
85+
eagerlyResolveBoms(sub, recommendationProviderContainer);
8186
sub.getConfigurations().all(new ExtendRecommenderConfigurationAction(bomConfiguration, sub, recommendationProviderContainer));
8287
}
8388
});
8489
}
8590
});
8691
}
92+
93+
/**
94+
* Eagerly resolves BOM configurations during the configuration phase to prevent
95+
* configuration resolution lock conflicts in parallel builds.
96+
*
97+
* <p>This method is called during {@code afterEvaluate} when exclusive locks are
98+
* available. It instructs the {@link BomResolverService} to resolve all BOM
99+
* configurations and cache the results for later use during dependency resolution.</p>
100+
*
101+
* <p>The eager resolution prevents the need to resolve configurations during the
102+
* dependency resolution phase, which would cause {@code IllegalResolutionException}
103+
* in parallel builds with Gradle 9+.</p>
104+
*
105+
* @param project the Gradle project whose BOM configurations should be resolved
106+
* @param container the recommendation provider container to check for additional BOM providers
107+
*/
108+
private void eagerlyResolveBoms(Project project, RecommendationProviderContainer container) {
109+
try {
110+
// Get the build service
111+
org.gradle.api.provider.Provider<netflix.nebula.dependency.recommender.service.BomResolverService> bomResolverService =
112+
project.getGradle().getSharedServices().registerIfAbsent(
113+
"bomResolver", netflix.nebula.dependency.recommender.service.BomResolverService.class, spec -> {}
114+
);
115+
116+
// Resolve BOMs from the nebulaRecommenderBom configuration
117+
bomResolverService.get().eagerlyResolveAndCacheBoms(project, NEBULA_RECOMMENDER_BOM);
118+
119+
// Also trigger resolution for maven BOM provider if it exists
120+
// This handles mavenBom providers configured in the extension
121+
netflix.nebula.dependency.recommender.provider.MavenBomRecommendationProvider mavenBomProvider = container.getMavenBomProvider();
122+
if (mavenBomProvider != null) {
123+
try {
124+
mavenBomProvider.getVersion("dummy", "dummy"); // Trigger lazy initialization
125+
} catch (Exception e) {
126+
// Expected - just needed to trigger BOM resolution
127+
}
128+
}
129+
} catch (Exception e) {
130+
logger.warn("Failed to eagerly resolve BOMs for project " + project.getPath(), e);
131+
}
132+
}
87133

88134
private void applyRecommendations(final Project project) {
135+
// Add eager BOM resolution for regular (non-core) BOM support
136+
project.afterEvaluate(new Action<Project>() {
137+
@Override
138+
public void execute(Project p) {
139+
// Eagerly resolve and cache all BOMs after project evaluation
140+
eagerlyResolveBoms(p, recommendationProviderContainer);
141+
}
142+
});
143+
89144
project.getConfigurations().all(new Action<Configuration>() {
90145
@Override
91146
public void execute(final Configuration conf) {

src/main/groovy/netflix/nebula/dependency/recommender/provider/ClasspathBasedRecommendationProvider.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,44 @@
1515
*/
1616
package netflix.nebula.dependency.recommender.provider;
1717

18+
import netflix.nebula.dependency.recommender.service.BomResolverService;
1819
import org.gradle.api.Project;
1920
import org.gradle.api.artifacts.Configuration;
21+
import org.gradle.api.provider.Provider;
2022

2123
import java.io.File;
24+
import java.util.Map;
2225
import java.util.Set;
2326

2427
public abstract class ClasspathBasedRecommendationProvider extends AbstractRecommendationProvider {
2528
protected Project project;
2629
protected Configuration configuration;
30+
protected String configName;
31+
protected Provider<BomResolverService> bomResolverService;
2732

2833
ClasspathBasedRecommendationProvider(Project project, String configName) {
2934
this.project = project;
35+
this.configName = configName;
3036
this.configuration = project.getConfigurations().getByName(configName);
37+
this.bomResolverService = project.getGradle().getSharedServices().registerIfAbsent(
38+
"bomResolver", BomResolverService.class, spec -> {}
39+
);
3140
}
3241

33-
Set<File> getFilesOnConfiguration() {
34-
return configuration.resolve();
42+
/**
43+
* Retrieves BOM recommendations using the shared {@link BomResolverService}.
44+
*
45+
* <p>This method delegates to the build service to get cached BOM recommendations,
46+
* avoiding direct configuration resolution that could cause lock conflicts in
47+
* parallel builds. The build service ensures that all BOM resolution happens
48+
* during the configuration phase when exclusive locks are available.</p>
49+
*
50+
* @param reasons a mutable set that will be populated with reasons explaining
51+
* why specific recommendations were applied
52+
* @return a map of dependency coordinates (groupId:artifactId) to recommended versions
53+
* @throws RuntimeException if the build service is not available or BOM resolution fails
54+
*/
55+
protected Map<String, String> getBomRecommendations(Set<String> reasons) {
56+
return bomResolverService.get().getRecommendations(project, configName, reasons);
3557
}
3658
}

0 commit comments

Comments
 (0)