Skip to content

Commit 1861d2a

Browse files
Merge pull request #293 from nebula-plugins/modernization
Modernization, Phase 5: Configuration cache compatibility for core lock tasks
2 parents 80e3a2a + 9a9ac45 commit 1861d2a

26 files changed

+1074
-189
lines changed

src/main/groovy/nebula/plugin/dependencylock/DependencyLockTaskConfigurer.groovy

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -173,22 +173,16 @@ class DependencyLockTaskConfigurer {
173173
TaskProvider<SaveLockTask> saveLockTask = project.tasks.register(SAVE_LOCK_TASK_NAME, SaveLockTask)
174174

175175
saveLockTask.configure { saveTask ->
176-
saveTask.doFirst {
177-
// Skip global lock check if global locks are disabled
178-
if (isGlobalLockDisabled()) {
179-
return
180-
}
181-
//TODO: address Invocation of Task.project at execution time has been deprecated.
182-
DeprecationLogger.whileDisabled {
183-
SaveLockTask globalSave = project.rootProject.tasks.findByName(SAVE_GLOBAL_LOCK_TASK_NAME) as SaveLockTask
184-
if (globalSave && globalSave.outputLock.isPresent() && globalSave.outputLock.get().asFile.exists()) {
185-
throw new GradleException('Cannot save individual locks when global lock is in place, run deleteGlobalLock task')
186-
}
187-
}
188-
}
189176
// Set input and output files using Property API
190177
saveTask.generatedLock.set(project.layout.buildDirectory.file(lockFilename ?: extension.lockFile.get()))
191178
saveTask.outputLock.set(project.layout.projectDirectory.file(lockFilename ?: extension.lockFile.get()))
179+
180+
// Configuration cache compatible: Capture global lock file path to check at execution time
181+
if (!isGlobalLockDisabled()) {
182+
saveTask.globalLockFile.set(
183+
project.rootProject.layout.projectDirectory.file(extension.globalLockFile)
184+
)
185+
}
192186
}
193187
configureCommonSaveTask(saveLockTask, lockTask, updateTask)
194188

@@ -198,7 +192,7 @@ class DependencyLockTaskConfigurer {
198192
private static void configureCommonSaveTask(TaskProvider<SaveLockTask> saveLockTask, TaskProvider<GenerateLockTask> lockTask,
199193
TaskProvider<UpdateLockTask> updateTask) {
200194
saveLockTask.configure { saveTask ->
201-
saveTask.notCompatibleWithConfigurationCache("Dependency locking plugin tasks require project access. Please consider using Gradle's dependency locking mechanism")
195+
// Configuration cache compatible: SaveLockTask uses only Property API
202196
saveTask.mustRunAfter lockTask, updateTask
203197
saveTask.outputs.upToDateWhen {
204198
def generated = saveTask.generatedLock.get().asFile
@@ -255,8 +249,6 @@ class DependencyLockTaskConfigurer {
255249

256250
private void setupLockProperties(TaskProvider<GenerateLockTask> task, DependencyLockExtension extension, Map overrideMap) {
257251
task.configure { generateTask ->
258-
generateTask.notCompatibleWithConfigurationCache("Dependency locking plugin tasks require project access. Please consider using Gradle's dependency locking mechanism")
259-
260252
// Set skipped dependencies
261253
generateTask.skippedDependencies.set(extension.skippedDependencies)
262254

@@ -272,12 +264,39 @@ class DependencyLockTaskConfigurer {
272264

273265
// Set overrides
274266
generateTask.overrides.set(overrideMap)
267+
268+
// Wire properties for configuration cache compatibility
269+
generateTask.projectDirectory.set(project.layout.projectDirectory)
270+
generateTask.globalLockFileName.set(extension.globalLockFile)
271+
generateTask.dependencyLockIgnored.set(project.provider { shouldIgnoreDependencyLock(project) })
272+
273+
// Wire Resolution API properties (Approach 1 - Official Gradle APIs) for regular locks
274+
// Use zip() to ensure both properties are evaluated together at execution time
275+
generateTask.resolutionResults.set(
276+
generateTask.configurationNames.zip(generateTask.skippedConfigurationNames) { configNames, skippedNames ->
277+
def lockableConfs = GenerateLockTask.lockableConfigurations(project, project, configNames, skippedNames)
278+
279+
lockableConfs.collectEntries { conf ->
280+
[(conf.name): conf.incoming.resolutionResult.rootComponent]
281+
}
282+
}
283+
)
284+
285+
generateTask.peerProjectCoordinates.set(project.provider {
286+
project.rootProject.allprojects.collect { p ->
287+
String group = p.group?.toString() ?: ''
288+
String name = p.name
289+
"${group}:${name}".toString()
290+
}
291+
})
275292
}
276293
}
277294

278295
private TaskProvider<GenerateLockTask> configureGlobalLockTask(TaskProvider<GenerateLockTask> globalLockTask, String lockFilename,
279296
DependencyLockExtension extension, Map overrides) {
280-
setupLockProperties(globalLockTask, extension, overrides)
297+
// Global lock uses the OLD API (not configuration cache compatible)
298+
// It relies on conventionMapping to set configurations dynamically at execution time
299+
// Do NOT call setupLockProperties for global lock - it interferes with conventionMapping
281300
globalLockTask.configure { globalGenerateTask ->
282301
globalGenerateTask.notCompatibleWithConfigurationCache("Dependency locking plugin tasks require project access. Please consider using Gradle's dependency locking mechanism")
283302
globalGenerateTask.doFirst {
@@ -290,17 +309,36 @@ class DependencyLockTaskConfigurer {
290309
// Set output file
291310
globalGenerateTask.dependenciesLock.set(project.layout.buildDirectory.file(lockFilename ?: extension.globalLockFile.get()))
292311

312+
// Wire minimal properties needed for basic checks in lock() method
313+
globalGenerateTask.projectDirectory.set(project.layout.projectDirectory)
314+
globalGenerateTask.globalLockFileName.set(lockFilename ?: extension.globalLockFile.get())
315+
globalGenerateTask.dependencyLockIgnored.set(project.provider { shouldIgnoreDependencyLock(project) })
316+
globalGenerateTask.skippedDependencies.set(extension.skippedDependencies)
317+
globalGenerateTask.overrides.set(overrides)
318+
globalGenerateTask.filter = extension.dependencyFilter
319+
320+
// Capture peer coordinates to avoid accessing project at execution time
321+
globalGenerateTask.peerProjectCoordinates.set(project.provider {
322+
project.rootProject.allprojects.collect { p ->
323+
String group = p.group?.toString() ?: ''
324+
String name = p.name
325+
"${group}:${name}".toString()
326+
}
327+
})
328+
293329
// TODO: Refactor this to not use conventionMapping. The global lock's configuration logic is complex
294330
// because it creates aggregate configurations at execution time. This needs a proper Property-based solution.
295331
// For now, keeping conventionMapping for this specific case to maintain functionality.
296332
globalGenerateTask.conventionMapping.with {
333+
includeTransitives = { extension.includeTransitives.get() }
297334
configurations = {
298335
def subprojects = project.subprojects.collect { subproject ->
299336
def ext = subproject.getExtensions().findByType(DependencyLockExtension)
300337
if (ext != null) {
301338
Collection<Configuration> lockableConfigurations = lockableConfigurations(project, subproject, ext.configurationNames.get(), extension.skippedConfigurationNamesPrefixes.get())
302339
Collection<Configuration> configurations = filterNonLockableConfigurationsAndProvideWarningsForGlobalLockSubproject(subproject, ext.configurationNames.get(), lockableConfigurations)
303-
Configuration aggregate = subproject.configurations.create("aggregateConfiguration")
340+
// Use unique name to avoid conflicts if evaluated multiple times
341+
Configuration aggregate = subproject.configurations.create("aggregateConfiguration_${System.currentTimeMillis()}_${subproject.path.replace(':', '_')}")
304342
aggregate.setCanBeConsumed(true)
305343
aggregate.setCanBeResolved(true)
306344
configurations
@@ -367,7 +405,6 @@ class DependencyLockTaskConfigurer {
367405
TaskProvider<Delete> deleteLockTask = project.tasks.register('deleteLock', Delete)
368406

369407
deleteLockTask.configure { it ->
370-
it.notCompatibleWithConfigurationCache("Dependency locking plugin tasks require project access. Please consider using Gradle's dependency locking mechanism")
371408
it.delete saveLock.map { it.outputLock }
372409
}
373410

@@ -377,7 +414,6 @@ class DependencyLockTaskConfigurer {
377414
TaskProvider<Delete> deleteGlobalLockTask = project.tasks.register('deleteGlobalLock', Delete)
378415

379416
deleteGlobalLockTask.configure { it ->
380-
it.notCompatibleWithConfigurationCache("Dependency locking plugin tasks require project access. Please consider using Gradle's dependency locking mechanism")
381417
it.delete saveGlobalLock.map { it.outputLock }
382418
}
383419
}
@@ -399,7 +435,6 @@ class DependencyLockTaskConfigurer {
399435
TaskProvider<DiffLockTask> diffLockTask = project.tasks.register(DIFF_LOCK_TASK_NAME, DiffLockTask)
400436

401437
diffLockTask.configure { diffTask ->
402-
diffTask.notCompatibleWithConfigurationCache("Dependency locking plugin tasks require project access. Please consider using Gradle's dependency locking mechanism")
403438
diffTask.mustRunAfter(project.tasks.named(GENERATE_LOCK_TASK_NAME), project.tasks.named(UPDATE_LOCK_TASK_NAME))
404439

405440
// Set file properties
@@ -413,6 +448,17 @@ class DependencyLockTaskConfigurer {
413448
// Set output properties
414449
diffTask.outputDir.set(project.layout.buildDirectory.dir("dependency-lock"))
415450
diffTask.diffFile.set(diffTask.outputDir.file("lockdiff.${diffTask.diffFileExtension()}"))
451+
452+
// Wire resolution results for path-aware diff (configuration cache compatible!)
453+
diffTask.resolutionResults.set(
454+
extension.configurationNames.zip(extension.skippedConfigurationNamesPrefixes) { configNames, skippedNames ->
455+
def lockableConfs = GenerateLockTask.lockableConfigurations(project, project, configNames, skippedNames)
456+
457+
lockableConfs.collectEntries { conf ->
458+
[(conf.name): conf.incoming.resolutionResult.rootComponent]
459+
}
460+
}
461+
)
416462
}
417463

418464
project.tasks.named(SAVE_LOCK_TASK_NAME).configure { save ->
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
package nebula.plugin.dependencylock.diff
22

3-
import org.gradle.api.Project
43
import nebula.dependencies.comparison.DependencyDiff
4+
import org.gradle.api.artifacts.result.ResolvedComponentResult
5+
import org.gradle.api.provider.Provider
56

67
interface DiffReportGenerator {
7-
List<Map<String, Object>> generateDiffReport(Project project, Map<String, List<DependencyDiff>> diffsByConfiguration)
8+
/**
9+
* Generate a diff report from dependency differences.
10+
*
11+
* @param resolutionResults Map of configuration name to its resolved component result (for path-aware diff)
12+
* @param diffsByConfiguration Map of configuration name to list of dependency diffs
13+
* @return List of diff report entries
14+
*/
15+
List<Map<String, Object>> generateDiffReport(
16+
Map<String, Provider<ResolvedComponentResult>> resolutionResults,
17+
Map<String, List<DependencyDiff>> diffsByConfiguration
18+
)
819
}

src/main/groovy/nebula/plugin/dependencylock/tasks/DiffLockTask.groovy

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import groovy.json.JsonSlurper
55
import nebula.dependencies.comparison.*
66
import nebula.plugin.dependencylock.diff.DiffReportGenerator
77
import nebula.plugin.dependencylock.utils.DependencyLockingFeatureFlags
8+
import org.gradle.api.artifacts.result.ResolvedComponentResult
89
import org.gradle.api.file.DirectoryProperty
910
import org.gradle.api.file.RegularFileProperty
11+
import org.gradle.api.provider.MapProperty
12+
import org.gradle.api.provider.Provider
1013
import org.gradle.api.tasks.InputFile
1114
import org.gradle.api.tasks.Internal
1215
import org.gradle.api.tasks.Optional
@@ -38,33 +41,40 @@ abstract class DiffLockTask extends AbstractLockTask {
3841

3942
@OutputFile
4043
abstract RegularFileProperty getDiffFile()
44+
45+
/**
46+
* Resolution results for path-aware diff (configuration cache compatible).
47+
* Map of configuration name to its resolved component result.
48+
*/
49+
@Internal
50+
abstract MapProperty<String, Provider<ResolvedComponentResult>> getResolutionResults()
4151

4252
String diffFileExtension() {
4353
DependencyLockingFeatureFlags.isPathAwareDependencyDiffEnabled() ? "json" : "txt"
4454
}
4555

4656
@TaskAction
4757
def diffLocks() {
48-
//TODO: address Invocation of Task.project at execution time has been deprecated.
49-
DeprecationLogger.whileDisabled {
50-
ConfigurationsSet existingLock = readLocks(existingLockFile.asFile.getOrNull())
51-
ConfigurationsSet newLock = readLocks(updatedLockFile.asFile.get())
52-
if (DependencyLockingFeatureFlags.isPathAwareDependencyDiffEnabled()) {
53-
Map<String, List<DependencyDiff>> diffByConfiguration = new DependenciesComparison().performDiffByConfiguration(existingLock, newLock)
54-
DiffReportGenerator generator = Class.forName("nebula.plugin.dependencylock.diff.PathAwareDiffReportGenerator").newInstance() as DiffReportGenerator
55-
def lockDiff = generator.generateDiffReport(project, diffByConfiguration)
58+
ConfigurationsSet existingLock = readLocks(existingLockFile.asFile.getOrNull())
59+
ConfigurationsSet newLock = readLocks(updatedLockFile.asFile.get())
60+
61+
if (DependencyLockingFeatureFlags.isPathAwareDependencyDiffEnabled()) {
62+
Map<String, List<DependencyDiff>> diffByConfiguration = new DependenciesComparison().performDiffByConfiguration(existingLock, newLock)
63+
DiffReportGenerator generator = Class.forName("nebula.plugin.dependencylock.diff.PathAwareDiffReportGenerator").newInstance() as DiffReportGenerator
64+
65+
// Use resolution results instead of project - configuration cache compatible!
66+
def lockDiff = generator.generateDiffReport(resolutionResults.get(), diffByConfiguration)
67+
outputDir.asFile.get().mkdirs()
68+
diffFile.asFile.get().text = JsonOutput.prettyPrint(JsonOutput.toJson(lockDiff))
69+
} else {
70+
if (newLock.isEmpty()) {
5671
outputDir.asFile.get().mkdirs()
57-
diffFile.asFile.get().text = JsonOutput.prettyPrint(JsonOutput.toJson(lockDiff))
58-
} else {
59-
if (newLock.isEmpty()) {
60-
outputDir.asFile.get().mkdirs()
61-
diffFile.asFile.get().withPrintWriter(StandardCharsets.UTF_8.displayName()) { writer ->
62-
writer.println('--no updated locks to diff--')
63-
}
64-
} else {
65-
List<DependencyDiff> diff = new DependenciesComparison().performDiff(existingLock, newLock)
66-
writeDiff(diff)
72+
diffFile.asFile.get().withPrintWriter(StandardCharsets.UTF_8.displayName()) { writer ->
73+
writer.println('--no updated locks to diff--')
6774
}
75+
} else {
76+
List<DependencyDiff> diff = new DependenciesComparison().performDiff(existingLock, newLock)
77+
writeDiff(diff)
6878
}
6979
}
7080
}

0 commit comments

Comments
 (0)