3939import org .gradle .api .logging .Logging ;
4040import org .gradle .api .plugins .ExtraPropertiesExtension ;
4141import org .gradle .internal .deprecation .DeprecationLogger ;
42+ import org .gradle .util .GradleVersion ;
4243
4344import java .lang .reflect .Method ;
4445import java .util .*;
4546
4647public class DependencyRecommendationsPlugin implements Plugin <Project > {
4748 public static final String NEBULA_RECOMMENDER_BOM = "nebulaRecommenderBom" ;
4849 public static final boolean CORE_BOM_SUPPORT_ENABLED = Boolean .getBoolean ("nebula.features.coreBomSupport" );
50+ private static final GradleVersion GRADLE_9_0 = GradleVersion .version ("9.0" );
4951 private Logger logger = Logging .getLogger (DependencyRecommendationsPlugin .class );
5052 private RecommendationProviderContainer recommendationProviderContainer ;
5153 //TODO: remove this exclusion once https://github.com/gradle/gradle/issues/6750 is resolved
@@ -75,10 +77,19 @@ private void applyRecommendationsDirectly(final Project project, final Configura
7577 project .afterEvaluate (new Action <Project >() {
7678 @ Override
7779 public void execute (Project p ) {
80+ // Eagerly resolve and cache all BOMs if using build service approach
81+ if (shouldUseBuildService (p )) {
82+ eagerlyResolveBoms (p , recommendationProviderContainer );
83+ }
84+
7885 p .getConfigurations ().all (new ExtendRecommenderConfigurationAction (bomConfiguration , p , recommendationProviderContainer ));
7986 p .subprojects (new Action <Project >() {
8087 @ Override
8188 public void execute (Project sub ) {
89+ // Also eagerly resolve BOMs for subprojects if using build service
90+ if (shouldUseBuildService (sub )) {
91+ eagerlyResolveBoms (sub , recommendationProviderContainer );
92+ }
8293 sub .getConfigurations ().all (new ExtendRecommenderConfigurationAction (bomConfiguration , sub , recommendationProviderContainer ));
8394 }
8495 });
@@ -87,6 +98,17 @@ public void execute(Project sub) {
8798 }
8899
89100 private void applyRecommendations (final Project project ) {
101+ // Add eager BOM resolution for regular (non-core) BOM support if using build service
102+ project .afterEvaluate (new Action <Project >() {
103+ @ Override
104+ public void execute (Project p ) {
105+ if (shouldUseBuildService (p )) {
106+ // Eagerly resolve and cache all BOMs after project evaluation
107+ eagerlyResolveBoms (p , recommendationProviderContainer );
108+ }
109+ }
110+ });
111+
90112 project .getConfigurations ().all (new Action <Configuration >() {
91113 @ Override
92114 public void execute (final Configuration conf ) {
@@ -101,7 +123,7 @@ public Unit invoke(ResolvableDependencies resolvableDependencies) {
101123 }
102124
103125 for (Dependency dependency : resolvableDependencies .getDependencies ()) {
104- applyRecommendationToDependency (rsFactory , dependency , new ArrayList <ProjectDependency >());
126+ applyRecommendationToDependency (rsFactory , dependency , new ArrayList <ProjectDependency >(), project );
105127
106128 // if project dependency, pull all first orders and apply recommendations if missing dependency versions
107129 // dependency.getProjectConfiguration().allDependencies iterate and inspect them as well
@@ -160,7 +182,7 @@ private boolean isExcludedConfiguration(String confName) {
160182 return false ;
161183 }
162184
163- private void applyRecommendationToDependency (final RecommendationStrategyFactory factory , Dependency dependency , List <ProjectDependency > visited ) {
185+ private void applyRecommendationToDependency (final RecommendationStrategyFactory factory , Dependency dependency , List <ProjectDependency > visited , Project rootProject ) {
164186 if (dependency instanceof ExternalModuleDependency ) {
165187 factory .getRecommendationStrategy ().inspectDependency (dependency );
166188 } else if (dependency instanceof ProjectDependency ) {
@@ -171,10 +193,10 @@ private void applyRecommendationToDependency(final RecommendationStrategyFactory
171193 try {
172194 ProjectDependency .class .getMethod ("getTargetConfiguration" );
173195 String targetConfiguration = projectDependency .getTargetConfiguration () == null ? Dependency .DEFAULT_CONFIGURATION : projectDependency .getTargetConfiguration ();
174-
175- DeprecationLogger . whileDisabled (() -> {
176- configuration [0 ] = projectDependency . getDependencyProject () .getConfigurations ().getByName (targetConfiguration );
177- });
196+ Project dependencyProject = rootProject . findProject ( projectDependency . getPath ());
197+ if ( dependencyProject != null ) {
198+ configuration [0 ] = dependencyProject .getConfigurations ().getByName (targetConfiguration );
199+ }
178200 } catch (NoSuchMethodException ignore ) {
179201 try {
180202 Method method = ProjectDependency .class .getMethod ("getProjectConfiguration" );
@@ -183,9 +205,11 @@ private void applyRecommendationToDependency(final RecommendationStrategyFactory
183205 throw new RuntimeException ("Unable to retrieve configuration for project dependency" , e );
184206 }
185207 }
186- DependencySet dependencies = configuration [0 ].getAllDependencies ();
187- for (Dependency dep : dependencies ) {
188- applyRecommendationToDependency (factory , dep , visited );
208+ if (configuration [0 ] != null ) {
209+ DependencySet dependencies = configuration [0 ].getAllDependencies ();
210+ for (Dependency dep : dependencies ) {
211+ applyRecommendationToDependency (factory , dep , visited , rootProject );
212+ }
189213 }
190214 }
191215 }
@@ -243,4 +267,71 @@ public Set<String> getReasonsRecursive(Project project) {
243267 return getReasonsRecursive (project .getParent ());
244268 return Collections .emptySet ();
245269 }
270+
271+ /**
272+ * Determines whether to use the BomResolverService (build service) approach.
273+ *
274+ * <p>The build service is used when:</p>
275+ * <ul>
276+ * <li>Gradle version is 9.0 or higher, OR</li>
277+ * <li>The gradle property 'nebula.dependency-recommender.useBuildService' is set to true</li>
278+ * </ul>
279+ *
280+ * @param project the Gradle project to check
281+ * @return true if build service should be used, false otherwise
282+ */
283+ private boolean shouldUseBuildService (Project project ) {
284+ // Check if explicitly enabled via gradle property
285+ if (project .hasProperty ("nebula.dependency-recommender.useBuildService" )) {
286+ Object property = project .property ("nebula.dependency-recommender.useBuildService" );
287+ if (Boolean .parseBoolean (property .toString ())) {
288+ return true ;
289+ }
290+ }
291+
292+ // Default behavior: use build service for Gradle 9+
293+ GradleVersion currentVersion = GradleVersion .current ();
294+ return currentVersion .compareTo (GRADLE_9_0 ) >= 0 ;
295+ }
296+
297+ /**
298+ * Eagerly resolves BOM configurations during the configuration phase to prevent
299+ * configuration resolution lock conflicts in parallel builds.
300+ *
301+ * <p>This method is called during {@code afterEvaluate} when exclusive locks are
302+ * available. It instructs the {@link netflix.nebula.dependency.recommender.service.BomResolverService} to resolve all BOM
303+ * configurations and cache the results for later use during dependency resolution.</p>
304+ *
305+ * <p>The eager resolution prevents the need to resolve configurations during the
306+ * dependency resolution phase, which would cause {@code IllegalResolutionException}
307+ * in parallel builds with Gradle 9+.</p>
308+ *
309+ * @param project the Gradle project whose BOM configurations should be resolved
310+ * @param container the recommendation provider container to check for additional BOM providers
311+ */
312+ private void eagerlyResolveBoms (Project project , RecommendationProviderContainer container ) {
313+ try {
314+ // Get the build service
315+ org .gradle .api .provider .Provider <netflix .nebula .dependency .recommender .service .BomResolverService > bomResolverService =
316+ project .getGradle ().getSharedServices ().registerIfAbsent (
317+ "bomResolver" , netflix .nebula .dependency .recommender .service .BomResolverService .class , spec -> {}
318+ );
319+
320+ // Resolve BOMs from the nebulaRecommenderBom configuration
321+ bomResolverService .get ().eagerlyResolveAndCacheBoms (project , NEBULA_RECOMMENDER_BOM );
322+
323+ // Also trigger resolution for maven BOM provider if it exists
324+ // This handles mavenBom providers configured in the extension
325+ netflix .nebula .dependency .recommender .provider .MavenBomRecommendationProvider mavenBomProvider = container .getMavenBomProvider ();
326+ if (mavenBomProvider != null ) {
327+ try {
328+ mavenBomProvider .getVersion ("dummy" , "dummy" ); // Trigger lazy initialization
329+ } catch (Exception e ) {
330+ // Expected - just needed to trigger BOM resolution
331+ }
332+ }
333+ } catch (Exception e ) {
334+ logger .warn ("Failed to eagerly resolve BOMs for project " + project .getPath (), e );
335+ }
336+ }
246337}
0 commit comments