Skip to content

Commit 1163b3e

Browse files
authored
Merge pull request #139 from nebula-plugins/more-gradle-9-work
Fix configuration resolution lock conflicts in parallel builds for Gradle 9
2 parents 9013cb8 + 1766d1a commit 1163b3e

File tree

8 files changed

+1227
-124
lines changed

8 files changed

+1227
-124
lines changed

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import netflix.nebula.dependency.recommender.provider.RecommendationProviderContainer;
2222
import netflix.nebula.dependency.recommender.provider.RecommendationResolver;
2323
import netflix.nebula.dependency.recommender.publisher.MavenBomXmlGenerator;
24+
import netflix.nebula.dependency.recommender.service.BomResolverService;
25+
import netflix.nebula.dependency.recommender.util.BomResolutionUtil;
2426
import org.apache.commons.lang3.StringUtils;
2527
import org.codehaus.groovy.runtime.MethodClosure;
2628
import org.gradle.api.Action;
@@ -38,13 +40,16 @@
3840
import org.gradle.api.logging.Logger;
3941
import org.gradle.api.logging.Logging;
4042
import org.gradle.api.plugins.ExtraPropertiesExtension;
43+
import org.gradle.api.provider.Provider;
44+
import org.gradle.util.GradleVersion;
4145

4246
import java.lang.reflect.Method;
4347
import java.util.*;
4448

4549
public class DependencyRecommendationsPlugin implements Plugin<Project> {
4650
public static final String NEBULA_RECOMMENDER_BOM = "nebulaRecommenderBom";
4751
public static final boolean CORE_BOM_SUPPORT_ENABLED = Boolean.getBoolean("nebula.features.coreBomSupport");
52+
private static final GradleVersion GRADLE_9_0 = GradleVersion.version("9.0");
4853
private Logger logger = Logging.getLogger(DependencyRecommendationsPlugin.class);
4954
private RecommendationProviderContainer recommendationProviderContainer;
5055
//TODO: remove this exclusion once https://github.com/gradle/gradle/issues/6750 is resolved
@@ -74,18 +79,39 @@ private void applyRecommendationsDirectly(final Project project, final Configura
7479
project.afterEvaluate(new Action<Project>() {
7580
@Override
7681
public void execute(Project p) {
82+
// Eagerly resolve and cache all BOMs if using build service approach
83+
if (shouldUseBuildService(p) && BomResolutionUtil.shouldEagerlyResolveBoms(p, recommendationProviderContainer)) {
84+
BomResolutionUtil.eagerlyResolveBoms(p, recommendationProviderContainer, NEBULA_RECOMMENDER_BOM);
85+
}
86+
7787
p.getConfigurations().all(new ExtendRecommenderConfigurationAction(bomConfiguration, p, recommendationProviderContainer));
7888
p.subprojects(new Action<Project>() {
7989
@Override
8090
public void execute(Project sub) {
91+
// Also eagerly resolve BOMs for subprojects if using build service
92+
if (shouldUseBuildService(sub) && BomResolutionUtil.shouldEagerlyResolveBoms(sub, recommendationProviderContainer)) {
93+
BomResolutionUtil.eagerlyResolveBoms(sub, recommendationProviderContainer, NEBULA_RECOMMENDER_BOM);
94+
}
8195
sub.getConfigurations().all(new ExtendRecommenderConfigurationAction(bomConfiguration, sub, recommendationProviderContainer));
8296
}
8397
});
8498
}
8599
});
86100
}
101+
87102

88103
private void applyRecommendations(final Project project) {
104+
// Add eager BOM resolution for regular (non-core) BOM support
105+
project.afterEvaluate(new Action<Project>() {
106+
@Override
107+
public void execute(Project p) {
108+
if (shouldUseBuildService(p) && BomResolutionUtil.shouldEagerlyResolveBoms(p, recommendationProviderContainer)) {
109+
// Eagerly resolve and cache all BOMs after project evaluation
110+
BomResolutionUtil.eagerlyResolveBoms(p, recommendationProviderContainer, NEBULA_RECOMMENDER_BOM);
111+
}
112+
}
113+
});
114+
89115
project.getConfigurations().all(new Action<Configuration>() {
90116
@Override
91117
public void execute(final Configuration conf) {
@@ -245,4 +271,60 @@ public Set<String> getReasonsRecursive(Project project) {
245271
return getReasonsRecursive(project.getParent());
246272
return Collections.emptySet();
247273
}
274+
275+
/**
276+
* Determines whether to use the BomResolverService (build service) approach.
277+
*
278+
* <p>The build service is used when:</p>
279+
* <ul>
280+
* <li>Gradle version is 9.0 or higher, OR</li>
281+
* <li>The gradle property 'nebula.dependency-recommender.useBuildService' is set to true</li>
282+
* </ul>
283+
*
284+
* @param project the Gradle project to check
285+
* @return true if build service should be used, false otherwise
286+
*/
287+
private boolean shouldUseBuildService(Project project) {
288+
// Check if explicitly enabled via gradle property
289+
if (project.hasProperty("nebula.dependency-recommender.useBuildService")) {
290+
Object property = project.property("nebula.dependency-recommender.useBuildService");
291+
if (Boolean.parseBoolean(property.toString())) {
292+
return true;
293+
}
294+
}
295+
296+
// Default behavior: use build service for Gradle 9+
297+
GradleVersion currentVersion = GradleVersion.current();
298+
return currentVersion.compareTo(GRADLE_9_0) >= 0;
299+
}
300+
301+
/**
302+
* Eagerly resolves BOM configurations during the configuration phase to prevent
303+
* configuration resolution lock conflicts in parallel builds.
304+
*
305+
* <p>This method delegates to {@link BomResolutionUtil#eagerlyResolveBoms} and is
306+
* provided for backward compatibility and convenience for external plugins.</p>
307+
*
308+
* <p><strong>External Plugin Usage:</strong></p>
309+
* <pre>{@code
310+
* // Get the plugin instance and container
311+
* DependencyRecommendationsPlugin plugin = project.plugins.getPlugin(DependencyRecommendationsPlugin)
312+
* RecommendationProviderContainer container = project.extensions.getByType(RecommendationProviderContainer)
313+
*
314+
* // Disable automatic resolution and add BOMs
315+
* container.setEagerlyResolve(false)
316+
* container.mavenBom(module: 'com.example:custom-bom:1.0.0')
317+
*
318+
* // Manually trigger resolution
319+
* plugin.eagerlyResolveBoms(project, container)
320+
* }</pre>
321+
*
322+
* @param project the Gradle project whose BOM configurations should be resolved
323+
* @param container the recommendation provider container to check for additional BOM providers
324+
* @since 12.7.0
325+
* @see BomResolutionUtil#eagerlyResolveBoms(Project, RecommendationProviderContainer, String)
326+
*/
327+
public void eagerlyResolveBoms(Project project, RecommendationProviderContainer container) {
328+
BomResolutionUtil.eagerlyResolveBoms(project, container, NEBULA_RECOMMENDER_BOM);
329+
}
248330
}

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)