Skip to content

Commit fbe9194

Browse files
committed
Add configurable eager BOM resolution for external plugin integration
1 parent e0fadc6 commit fbe9194

File tree

5 files changed

+432
-49
lines changed

5 files changed

+432
-49
lines changed

.claude/settings.local.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(git add:*)"
5+
],
6+
"deny": []
7+
}
8+
}

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

Lines changed: 73 additions & 46 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,70 +79,36 @@ private void applyRecommendationsDirectly(final Project project, final Configura
7479
project.afterEvaluate(new Action<Project>() {
7580
@Override
7681
public void execute(Project p) {
77-
// Eagerly resolve and cache all BOMs after project evaluation
78-
eagerlyResolveBoms(p, recommendationProviderContainer);
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+
}
7986

8087
p.getConfigurations().all(new ExtendRecommenderConfigurationAction(bomConfiguration, p, recommendationProviderContainer));
8188
p.subprojects(new Action<Project>() {
8289
@Override
8390
public void execute(Project sub) {
84-
// Also eagerly resolve BOMs for subprojects
85-
eagerlyResolveBoms(sub, recommendationProviderContainer);
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+
}
8695
sub.getConfigurations().all(new ExtendRecommenderConfigurationAction(bomConfiguration, sub, recommendationProviderContainer));
8796
}
8897
});
8998
}
9099
});
91100
}
92101

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-
}
133102

134103
private void applyRecommendations(final Project project) {
135104
// Add eager BOM resolution for regular (non-core) BOM support
136105
project.afterEvaluate(new Action<Project>() {
137106
@Override
138107
public void execute(Project p) {
139-
// Eagerly resolve and cache all BOMs after project evaluation
140-
eagerlyResolveBoms(p, recommendationProviderContainer);
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+
}
141112
}
142113
});
143114

@@ -300,4 +271,60 @@ public Set<String> getReasonsRecursive(Project project) {
300271
return getReasonsRecursive(project.getParent());
301272
return Collections.emptySet();
302273
}
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+
}
303330
}

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

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@
2323
import org.gradle.api.artifacts.Configuration;
2424
import org.gradle.api.artifacts.Dependency;
2525
import org.gradle.api.internal.ConfigureByMapAction;
26-
import org.gradle.api.internal.DefaultNamedDomainObjectList;
2726
import org.gradle.api.model.ObjectFactory;
28-
import org.gradle.util.GradleVersion;
27+
import org.gradle.util.ConfigureUtil;
2928

3029
import java.io.File;
3130
import java.lang.reflect.InvocationTargetException;
@@ -44,6 +43,7 @@ public class RecommendationProviderContainer {
4443
private Set<String> excludedConfigurations = new HashSet<>();
4544
private Set<String> excludedConfigurationPrefixes = new HashSet<>();
4645
private Set<String> reasons = new HashSet<>();
46+
private Boolean eagerlyResolve = true;
4747

4848
// Make strategies available without import
4949
public static final RecommendationStrategies OverrideTransitives = RecommendationStrategies.OverrideTransitives;
@@ -235,7 +235,65 @@ public Boolean getStrictMode() {
235235
public void setStrictMode(Boolean strict) {
236236
strictMode = strict;
237237
}
238-
238+
239+
/**
240+
* Sets whether BOM configurations should be resolved eagerly during the configuration phase.
241+
*
242+
* <p>When set to {@code true} (default), BOM configurations will be resolved automatically
243+
* during the {@code afterEvaluate} phase to prevent configuration resolution lock conflicts
244+
* in parallel builds with Gradle 9+.</p>
245+
*
246+
* <p>When set to {@code false}, external plugins can take control of BOM resolution timing
247+
* by calling {@link netflix.nebula.dependency.recommender.util.BomResolutionUtil#eagerlyResolveBoms}
248+
* manually after modifying BOM configurations.</p>
249+
*
250+
* <p><strong>Usage by External Plugins:</strong></p>
251+
* <pre>{@code
252+
* // Disable automatic eager resolution
253+
* dependencyRecommendations {
254+
* setEagerlyResolve(false)
255+
*
256+
* // Add initial BOMs
257+
* mavenBom module: 'com.example:base-bom:1.0.0'
258+
* }
259+
*
260+
* project.afterEvaluate { p ->
261+
* def container = p.extensions.getByType(RecommendationProviderContainer)
262+
*
263+
* // Add additional BOMs dynamically
264+
* container.mavenBom(module: 'com.example:dynamic-bom:2.0.0')
265+
*
266+
* // Manually trigger resolution
267+
* BomResolutionUtil.eagerlyResolveBoms(p, container, 'nebulaRecommenderBom')
268+
* }
269+
* }</pre>
270+
*
271+
* @param eagerlyResolve {@code true} to enable automatic eager resolution,
272+
* {@code false} to disable it and allow manual control
273+
* @since 12.7.0
274+
* @see netflix.nebula.dependency.recommender.util.BomResolutionUtil#eagerlyResolveBoms
275+
*/
276+
public void setEagerlyResolve(Boolean eagerlyResolve) {
277+
this.eagerlyResolve = eagerlyResolve;
278+
}
279+
280+
/**
281+
* Returns whether BOM configurations should be resolved eagerly during the configuration phase.
282+
*
283+
* <p>This setting controls whether the dependency recommender plugin automatically resolves
284+
* BOM configurations during {@code afterEvaluate}, or whether external plugins should
285+
* handle resolution timing manually.</p>
286+
*
287+
* @return {@code true} if BOMs should be resolved eagerly (default),
288+
* {@code false} if resolution should be handled manually
289+
* @since 12.7.0
290+
* @see #setEagerlyResolve(Boolean)
291+
* @see netflix.nebula.dependency.recommender.util.BomResolutionUtil#shouldEagerlyResolveBoms
292+
*/
293+
public Boolean shouldEagerlyResolve() {
294+
return eagerlyResolve;
295+
}
296+
239297
public void excludeConfigurations(String ... names) {
240298
excludedConfigurations.addAll(Arrays.asList(names));
241299
}

0 commit comments

Comments
 (0)