Skip to content

Commit e1e2204

Browse files
authored
Merge pull request #144 from nebula-plugins/plugin-enhancements
Modernize plugin: remove technical debt and adopt Provider API
2 parents 79fb39a + d2d6200 commit e1e2204

File tree

5 files changed

+212
-90
lines changed

5 files changed

+212
-90
lines changed

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

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import org.gradle.api.provider.Provider;
4444
import org.gradle.util.GradleVersion;
4545

46-
import java.lang.reflect.Method;
4746
import java.util.*;
4847
import java.util.concurrent.atomic.AtomicInteger;
4948

@@ -129,9 +128,6 @@ public Unit invoke(ResolvableDependencies resolvableDependencies) {
129128

130129
for (Dependency dependency : resolvableDependencies.getDependencies()) {
131130
applyRecommendationToDependency(rsFactory, dependency, new ArrayList<ProjectDependency>(), project);
132-
133-
// if project dependency, pull all first orders and apply recommendations if missing dependency versions
134-
// dependency.getProjectConfiguration().allDependencies iterate and inspect them as well
135131
}
136132

137133
conf.getResolutionStrategy().eachDependency(new Action<DependencyResolveDetails>() {
@@ -156,7 +152,7 @@ public void execute(DependencyResolveDetails details) {
156152
details.because("Recommending version " + version + " for dependency " + coordinate + " via " + strategyText + "\n" +
157153
"\twith reasons: " + StringUtils.join(getReasonsRecursive(project), ", "));
158154
} else {
159-
if (recommendationProviderContainer.getStrictMode()) {
155+
if (recommendationProviderContainer.getStrictMode().get()) {
160156
String errorMessage = "Dependency " + details.getRequested().getGroup() + ":" + details.getRequested().getName() + " omitted version with no recommended version. General causes include a dependency being removed from the recommendation source or not applying a recommendation source to a project that depends on another project using a recommender.";
161157
project.getLogger().error(errorMessage);
162158
throw new GradleException(errorMessage);
@@ -174,11 +170,11 @@ public void execute(DependencyResolveDetails details) {
174170
}
175171

176172
private boolean isExcludedConfiguration(String confName) {
177-
if (recommendationProviderContainer.getExcludedConfigurations().contains(confName)) {
173+
if (recommendationProviderContainer.getExcludedConfigurations().get().contains(confName)) {
178174
return true;
179175
}
180176

181-
for (String prefix : recommendationProviderContainer.getExcludedConfigurationPrefixes()) {
177+
for (String prefix : recommendationProviderContainer.getExcludedConfigurationPrefixes().get()) {
182178
if (confName.startsWith(prefix)) {
183179
return true;
184180
}
@@ -194,25 +190,11 @@ private void applyRecommendationToDependency(final RecommendationStrategyFactory
194190
ProjectDependency projectDependency = (ProjectDependency) dependency;
195191
if (!visited.contains(projectDependency)) {
196192
visited.add(projectDependency);
197-
final Configuration[] configuration = new Configuration[1];
198-
try {
199-
ProjectDependency.class.getMethod("getTargetConfiguration");
200-
String targetConfiguration = projectDependency.getTargetConfiguration() == null ? Dependency.DEFAULT_CONFIGURATION : projectDependency.getTargetConfiguration();
201-
Project dependencyProject = rootProject.findProject(projectDependency.getPath());
202-
if (dependencyProject != null) {
203-
configuration[0] = dependencyProject.getConfigurations().getByName(targetConfiguration);
204-
}
205-
206-
} catch (NoSuchMethodException ignore) {
207-
try {
208-
Method method = ProjectDependency.class.getMethod("getProjectConfiguration");
209-
configuration[0] = (Configuration) method.invoke(dependency);
210-
} catch (Exception e) {
211-
throw new RuntimeException("Unable to retrieve configuration for project dependency", e);
212-
}
213-
}
214-
if (configuration[0] != null) {
215-
DependencySet dependencies = configuration[0].getAllDependencies();
193+
String targetConfiguration = projectDependency.getTargetConfiguration() == null ? Dependency.DEFAULT_CONFIGURATION : projectDependency.getTargetConfiguration();
194+
Project dependencyProject = rootProject.findProject(projectDependency.getPath());
195+
if (dependencyProject != null) {
196+
Configuration configuration = dependencyProject.getConfigurations().getByName(targetConfiguration);
197+
DependencySet dependencies = configuration.getAllDependencies();
216198
for (Dependency dep : dependencies) {
217199
applyRecommendationToDependency(factory, dep, visited, rootProject);
218200
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public ExtendRecommenderConfigurationAction(Configuration bom, Project project,
4040

4141
@Override
4242
public void execute(Configuration configuration) {
43-
if (!isClasspathConfiguration(configuration) || container.getExcludedConfigurations().contains(configuration.getName()) || isCopyOfBomConfiguration(configuration)) {
43+
if (!isClasspathConfiguration(configuration) || container.getExcludedConfigurations().get().contains(configuration.getName()) || isCopyOfBomConfiguration(configuration)) {
4444
return;
4545
}
4646

@@ -98,8 +98,14 @@ private DefaultConfiguration createCopy(Set<Dependency> dependencies, Set<Depend
9898

9999
private String getNameWithCopySuffix() {
100100
int count = this.copyCount.incrementAndGet();
101-
String copyName = bom.getName() + "Copy";
102-
return count == 1 ? copyName : copyName + count;
101+
// Include project path to ensure uniqueness across composite builds and subprojects
102+
// Replace colons with underscores to avoid configuration naming issues
103+
String projectPathSuffix = project.getPath().replace(":", "_");
104+
if (projectPathSuffix.isEmpty() || projectPathSuffix.equals("_")) {
105+
projectPathSuffix = "root";
106+
}
107+
String copyName = bom.getName() + "Copy_" + projectPathSuffix;
108+
return count == 1 ? copyName : copyName + "_" + count;
103109
}
104110

105111
//we want to apply recommendation only into final resolvable configurations like `compileClasspath` or `runtimeClasspath` across all source sets.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public RecommendationStrategy getRecommendationStrategy() {
2121
if(recommendationStrategy == null) {
2222
try {
2323
RecommendationProviderContainer recommendationProviderContainer = project.getExtensions().findByType(RecommendationProviderContainer.class);
24-
recommendationStrategy = Objects.requireNonNull(recommendationProviderContainer).getStrategy().getStrategyClass().newInstance();
24+
recommendationStrategy = Objects.requireNonNull(recommendationProviderContainer).getStrategy().get().getStrategyClass().newInstance();
2525
} catch (Exception e) {
2626
throw new IllegalStateException(e);
2727
}

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

Lines changed: 64 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.gradle.api.artifacts.Dependency;
2525
import org.gradle.api.internal.ConfigureByMapAction;
2626
import org.gradle.api.model.ObjectFactory;
27+
import org.gradle.api.provider.Property;
28+
import org.gradle.api.provider.SetProperty;
2729

2830
import java.io.File;
2931
import java.lang.reflect.InvocationTargetException;
@@ -36,13 +38,13 @@ public class RecommendationProviderContainer {
3638

3739
private Project project;
3840
private NamedDomainObjectList<RecommendationProvider> providers;
39-
private RecommendationStrategies strategy = RecommendationStrategies.ConflictResolved;
41+
private final Property<RecommendationStrategies> strategy;
4042
private MavenBomRecommendationProvider mavenBomProvider;
41-
private Boolean strictMode = false;
42-
private Set<String> excludedConfigurations = new HashSet<>();
43-
private Set<String> excludedConfigurationPrefixes = new HashSet<>();
44-
private Set<String> reasons = new HashSet<>();
45-
private Boolean eagerlyResolve = true;
43+
private final Property<Boolean> strictMode;
44+
private final SetProperty<String> excludedConfigurations;
45+
private final SetProperty<String> excludedConfigurationPrefixes;
46+
private Set<String> reasons = new HashSet<>(); // Keep as regular Set - it's an output/result collection
47+
private final Property<Boolean> eagerlyResolve;
4648

4749
// Make strategies available without import
4850
public static final RecommendationStrategies OverrideTransitives = RecommendationStrategies.OverrideTransitives;
@@ -51,6 +53,20 @@ public class RecommendationProviderContainer {
5153
public RecommendationProviderContainer(Project project) {
5254
createList(project);
5355
this.project = project;
56+
57+
// Initialize properties using ObjectFactory for proper Gradle integration
58+
ObjectFactory objects = project.getObjects();
59+
this.strategy = objects.property(RecommendationStrategies.class)
60+
.convention(RecommendationStrategies.ConflictResolved);
61+
this.strictMode = objects.property(Boolean.class)
62+
.convention(false);
63+
this.excludedConfigurations = objects.setProperty(String.class)
64+
.convention(new HashSet<>());
65+
this.excludedConfigurationPrefixes = objects.setProperty(String.class)
66+
.convention(new HashSet<>());
67+
this.eagerlyResolve = objects.property(Boolean.class)
68+
.convention(true);
69+
5470
this.mavenBomProvider = getMavenBomRecommendationProvider();
5571
providers.add(this.mavenBomProvider);
5672
}
@@ -214,83 +230,71 @@ public String getRecommendedVersion(String group, String name) {
214230
return null;
215231
}
216232

217-
public RecommendationStrategies getStrategy() {
233+
public Property<RecommendationStrategies> getStrategy() {
218234
return strategy;
219235
}
220236

221-
public void setStrategy(RecommendationStrategies strategy) {
222-
this.strategy = strategy;
223-
}
224-
237+
/**
238+
* @deprecated Use {@link #getStrategy()}.set() instead
239+
*/
225240
@Deprecated
226-
public Boolean isStrictMode() {
227-
return getStrictMode();
241+
public void setStrategy(RecommendationStrategies value) {
242+
strategy.set(value);
228243
}
229244

230-
public Boolean getStrictMode() {
245+
public Property<Boolean> getStrictMode() {
231246
return strictMode;
232247
}
233248

234-
public void setStrictMode(Boolean strict) {
235-
strictMode = strict;
249+
/**
250+
* @deprecated Use {@link #getStrictMode()}.set() instead
251+
*/
252+
@Deprecated
253+
public void setStrictMode(Boolean value) {
254+
strictMode.set(value);
236255
}
237256

238257
/**
239-
* Sets whether BOM configurations should be resolved eagerly during the configuration phase.
240-
*
241-
* <p>When set to {@code true} (default), BOM configurations will be resolved automatically
242-
* during the {@code afterEvaluate} phase to prevent configuration resolution lock conflicts
258+
* Returns the Property controlling whether BOM configurations should be resolved eagerly.
259+
*
260+
* <p>When set to {@code true} (default), BOM configurations will be resolved automatically
261+
* during the {@code afterEvaluate} phase to prevent configuration resolution lock conflicts
243262
* in parallel builds with Gradle 9+.</p>
244-
*
263+
*
245264
* <p>When set to {@code false}, external plugins can take control of BOM resolution timing
246-
* by calling {@link netflix.nebula.dependency.recommender.util.BomResolutionUtil#eagerlyResolveBoms}
265+
* by calling {@link netflix.nebula.dependency.recommender.util.BomResolutionUtil#eagerlyResolveBoms}
247266
* manually after modifying BOM configurations.</p>
248-
*
249-
* <p><strong>Usage by External Plugins:</strong></p>
250-
* <pre>{@code
251-
* // Disable automatic eager resolution
252-
* dependencyRecommendations {
253-
* setEagerlyResolve(false)
254-
*
255-
* // Add initial BOMs
256-
* mavenBom module: 'com.example:base-bom:1.0.0'
257-
* }
258-
*
259-
* project.afterEvaluate { p ->
260-
* def container = p.extensions.getByType(RecommendationProviderContainer)
261-
*
262-
* // Add additional BOMs dynamically
263-
* container.mavenBom(module: 'com.example:dynamic-bom:2.0.0')
264-
*
265-
* // Manually trigger resolution
266-
* BomResolutionUtil.eagerlyResolveBoms(p, container, 'nebulaRecommenderBom')
267-
* }
268-
* }</pre>
269-
*
270-
* @param eagerlyResolve {@code true} to enable automatic eager resolution,
271-
* {@code false} to disable it and allow manual control
267+
*
268+
* @return Property containing the eagerly resolve flag
272269
* @since 12.7.0
273270
* @see netflix.nebula.dependency.recommender.util.BomResolutionUtil#eagerlyResolveBoms
274271
*/
275-
public void setEagerlyResolve(Boolean eagerlyResolve) {
276-
this.eagerlyResolve = eagerlyResolve;
272+
public Property<Boolean> getEagerlyResolve() {
273+
return eagerlyResolve;
277274
}
278275

279276
/**
280-
* Returns whether BOM configurations should be resolved eagerly during the configuration phase.
281-
*
282-
* <p>This setting controls whether the dependency recommender plugin automatically resolves
283-
* BOM configurations during {@code afterEvaluate}, or whether external plugins should
284-
* handle resolution timing manually.</p>
285-
*
286-
* @return {@code true} if BOMs should be resolved eagerly (default),
277+
* Convenience method to check if BOMs should be resolved eagerly.
278+
*
279+
* @return {@code true} if BOMs should be resolved eagerly (default),
287280
* {@code false} if resolution should be handled manually
288281
* @since 12.7.0
289-
* @see #setEagerlyResolve(Boolean)
290-
* @see netflix.nebula.dependency.recommender.util.BomResolutionUtil#shouldEagerlyResolveBoms
291282
*/
292283
public Boolean shouldEagerlyResolve() {
293-
return eagerlyResolve;
284+
return eagerlyResolve.get();
285+
}
286+
287+
/**
288+
* Convenience method to set whether BOMs should be resolved eagerly.
289+
*
290+
* @param value {@code true} to enable automatic eager resolution,
291+
* {@code false} to disable it and allow manual control
292+
* @since 12.7.0
293+
* @deprecated Use {@link #getEagerlyResolve()}.set() instead
294+
*/
295+
@Deprecated
296+
public void setEagerlyResolve(Boolean value) {
297+
eagerlyResolve.set(value);
294298
}
295299

296300
public void excludeConfigurations(String ... names) {
@@ -301,11 +305,11 @@ public void excludeConfigurationPrefixes(String ... names) {
301305
excludedConfigurationPrefixes.addAll(Arrays.asList(names));
302306
}
303307

304-
public Set<String> getExcludedConfigurations() {
308+
public SetProperty<String> getExcludedConfigurations() {
305309
return excludedConfigurations;
306310
}
307311

308-
public Set<String> getExcludedConfigurationPrefixes() {
312+
public SetProperty<String> getExcludedConfigurationPrefixes() {
309313
return excludedConfigurationPrefixes;
310314
}
311315

0 commit comments

Comments
 (0)