1+ /*
2+ * Copyright 2025 Netflix, Inc.
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+ * http://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+ package netflix .nebula .dependency .recommender .util ;
17+
18+ import netflix .nebula .dependency .recommender .provider .RecommendationProviderContainer ;
19+ import netflix .nebula .dependency .recommender .service .BomResolverService ;
20+ import org .gradle .api .Project ;
21+ import org .gradle .api .logging .Logger ;
22+ import org .gradle .api .logging .Logging ;
23+ import org .gradle .api .provider .Provider ;
24+
25+ /**
26+ * Utility class for handling BOM (Bill of Materials) resolution operations.
27+ *
28+ * <p>This utility provides methods for eagerly resolving BOM configurations during the
29+ * configuration phase to prevent configuration resolution lock conflicts in parallel builds
30+ * with Gradle 9+.</p>
31+ *
32+ * <p>The eager resolution approach is particularly important for:</p>
33+ * <ul>
34+ * <li>Parallel builds where configuration resolution locks can cause deadlocks</li>
35+ * <li>Build services that need cached BOM data during dependency resolution</li>
36+ * <li>External plugins that want to control when BOM resolution occurs</li>
37+ * </ul>
38+ *
39+ * @since 12.7.0
40+ */
41+ public final class BomResolutionUtil {
42+ private static final Logger logger = Logging .getLogger (BomResolutionUtil .class );
43+
44+ private BomResolutionUtil () {
45+ // Utility class - prevent instantiation
46+ }
47+
48+ /**
49+ * Eagerly resolves BOM configurations for the given project during the configuration phase.
50+ *
51+ * <p>This method should be called during {@code afterEvaluate} when exclusive locks are
52+ * available. It instructs the {@link BomResolverService} to resolve all BOM
53+ * configurations and cache the results for later use during dependency resolution.</p>
54+ *
55+ * <p>The eager resolution prevents the need to resolve configurations during the
56+ * dependency resolution phase, which would cause {@code IllegalResolutionException}
57+ * in parallel builds with Gradle 9+.</p>
58+ *
59+ * <p><strong>Usage by External Plugins:</strong></p>
60+ * <pre>{@code
61+ * // Disable automatic eager resolution
62+ * container.setEagerlyResolve(false);
63+ *
64+ * // Modify BOMs as needed
65+ * container.mavenBom(module: 'com.example:updated-bom:1.0.0');
66+ *
67+ * // Manually trigger eager resolution
68+ * BomResolutionUtil.eagerlyResolveBoms(project, container, "nebulaRecommenderBom");
69+ * }</pre>
70+ *
71+ * @param project the Gradle project whose BOM configurations should be resolved
72+ * @param container the recommendation provider container to check for additional BOM providers
73+ * @param bomConfigurationName the name of the BOM configuration to resolve
74+ * @throws IllegalArgumentException if any parameter is null
75+ * @since 12.7.0
76+ */
77+ public static void eagerlyResolveBoms (Project project , RecommendationProviderContainer container , String bomConfigurationName ) {
78+ if (project == null ) {
79+ throw new IllegalArgumentException ("Project cannot be null" );
80+ }
81+ if (container == null ) {
82+ throw new IllegalArgumentException ("RecommendationProviderContainer cannot be null" );
83+ }
84+ if (bomConfigurationName == null || bomConfigurationName .trim ().isEmpty ()) {
85+ throw new IllegalArgumentException ("BOM configuration name cannot be null or empty" );
86+ }
87+
88+ try {
89+ // Get the build service
90+ Provider <BomResolverService > bomResolverService =
91+ project .getGradle ().getSharedServices ().registerIfAbsent (
92+ "bomResolver" , BomResolverService .class , spec -> {}
93+ );
94+
95+ // Resolve BOMs from the specified configuration
96+ bomResolverService .get ().eagerlyResolveAndCacheBoms (project , bomConfigurationName );
97+
98+ // Also trigger resolution for maven BOM provider if it exists
99+ // This handles mavenBom providers configured in the extension
100+ netflix .nebula .dependency .recommender .provider .MavenBomRecommendationProvider mavenBomProvider = container .getMavenBomProvider ();
101+ if (mavenBomProvider != null ) {
102+ try {
103+ mavenBomProvider .getVersion ("dummy" , "dummy" ); // Trigger lazy initialization
104+ } catch (Exception e ) {
105+ // Expected - just needed to trigger BOM resolution
106+ logger .debug ("Triggered BOM resolution for maven BOM provider" , e );
107+ }
108+ }
109+
110+ logger .debug ("Successfully resolved BOMs for project {} using configuration {}" ,
111+ project .getPath (), bomConfigurationName );
112+
113+ } catch (Exception e ) {
114+ logger .warn ("Failed to eagerly resolve BOMs for project {} using configuration {}: {}" ,
115+ project .getPath (), bomConfigurationName , e .getMessage ());
116+ if (logger .isDebugEnabled ()) {
117+ logger .debug ("BOM resolution failure details" , e );
118+ }
119+ }
120+ }
121+
122+ /**
123+ * Checks if the given project should use eager BOM resolution.
124+ *
125+ * <p>This method evaluates both the container's eager resolution setting and
126+ * any project-specific overrides to determine if BOMs should be resolved eagerly.</p>
127+ *
128+ * @param project the Gradle project to check
129+ * @param container the recommendation provider container with eager resolution settings
130+ * @return true if BOMs should be resolved eagerly, false otherwise
131+ * @throws IllegalArgumentException if any parameter is null
132+ * @since 12.7.0
133+ */
134+ public static boolean shouldEagerlyResolveBoms (Project project , RecommendationProviderContainer container ) {
135+ if (project == null ) {
136+ throw new IllegalArgumentException ("Project cannot be null" );
137+ }
138+ if (container == null ) {
139+ throw new IllegalArgumentException ("RecommendationProviderContainer cannot be null" );
140+ }
141+
142+ // Check container setting first
143+ if (!container .shouldEagerlyResolve ()) {
144+ logger .debug ("Eager BOM resolution disabled for project {} via container setting" , project .getPath ());
145+ return false ;
146+ }
147+
148+ // Could add additional project-specific checks here in the future
149+ // For example, checking project properties or environment variables
150+
151+ logger .debug ("Eager BOM resolution enabled for project {}" , project .getPath ());
152+ return true ;
153+ }
154+ }
0 commit comments