From f508a3446b2138a0639ffdc840d1f9fecb2a6d10 Mon Sep 17 00:00:00 2001 From: Danny Thomas Date: Mon, 2 May 2016 13:36:06 -0700 Subject: [PATCH] Switch from forces to eachDependency when applying locks. Fixes #76, fixes #86 eachDependency.useTarget is the resolutionStrategy that currently wins. Includes test to ensure that remains to be the case --- .../DependencyLockPlugin.groovy | 77 +++++++++++-------- .../DependencyLockLauncherSpec.groovy | 75 ++++++++++++++++++ 2 files changed, 119 insertions(+), 33 deletions(-) diff --git a/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy b/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy index 0c4367c6..96aa9c29 100644 --- a/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy +++ b/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy @@ -26,6 +26,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ResolutionStrategy import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging import org.gradle.api.tasks.Delete @@ -75,7 +76,7 @@ class DependencyLockPlugin implements Plugin { createDeleteLock(saveTask) // configure global lock only on rootProject - SaveLockTask globalSave + SaveLockTask globalSave = null String globalLockFileName = project.hasProperty('dependencyLock.globalLockFile') ? project['dependencyLock.globalLockFile'] : null GenerateLockTask globalLockTask if (project == project.rootProject) { @@ -92,10 +93,12 @@ class DependencyLockPlugin implements Plugin { configureCommitTask(clLockFileName, globalLockFileName, saveTask, extension, commitExtension, globalSave) - def lockAfterEvaluating = project.hasProperty('dependencyLock.lockAfterEvaluating') ? Boolean.parseBoolean(project['dependencyLock.lockAfterEvaluating']) : extension.lockAfterEvaluating + def lockAfterEvaluating = project.hasProperty('dependencyLock.lockAfterEvaluating') ? Boolean.parseBoolean(project['dependencyLock.lockAfterEvaluating'] as String) : extension.lockAfterEvaluating if (lockAfterEvaluating) { logger.info('Applying dependency lock in afterEvaluate block') - project.afterEvaluate { applyLockToResolutionStrategy(extension, overrides, globalLockFileName, clLockFileName) } + project.afterEvaluate { + applyLockToResolutionStrategy(extension, overrides, globalLockFileName, clLockFileName) + } } else { logger.info('Applying dependency lock as is (outside afterEvaluate block)') applyLockToResolutionStrategy(extension, overrides, globalLockFileName, clLockFileName) @@ -104,12 +107,12 @@ class DependencyLockPlugin implements Plugin { project.gradle.taskGraph.whenReady { taskGraph -> def hasLockingTask = taskGraph.hasTask(genLockTask) || taskGraph.hasTask(updateLockTask) || ((project == project.rootProject) && (taskGraph.hasTask(globalLockTask) || taskGraph.hasTask(globalUpdateLock))) if (hasLockingTask) { - project.configurations.all { + project.configurations.all({ resolutionStrategy { cacheDynamicVersionsFor 0, 'seconds' cacheChangingModulesFor 0, 'seconds' } - } + }) if (!shouldIgnoreDependencyLock()) { applyOverrides(overrides) } @@ -119,7 +122,7 @@ class DependencyLockPlugin implements Plugin { private void applyLockToResolutionStrategy(DependencyLockExtension extension, Map overrides, String globalLockFileName, String clLockFileName) { if (extension.configurationNames.empty) { - extension.configurationNames = project.configurations.collect { it.name } + extension.configurationNames = project.configurations.toSet().collect { it.name } } File dependenciesLock @@ -135,7 +138,7 @@ class DependencyLockPlugin implements Plugin { if (dependenciesLock.exists() && !shouldIgnoreDependencyLock() && !hasGenerationTask(taskNames)) { applyLock(dependenciesLock, overrides) } else if (dependenciesLock.exists() && !shouldIgnoreDependencyLock() && hasUpdateTask(taskNames)) { - def updates = project.hasProperty('dependencyLock.updateDependencies') ? parseUpdates(project.property('dependencyLock.updateDependencies')) : extension.updateDependencies + def updates = project.hasProperty('dependencyLock.updateDependencies') ? parseUpdates(project.property('dependencyLock.updateDependencies') as String) : extension.updateDependencies applyLock(dependenciesLock, overrides, updates) } else if (!shouldIgnoreDependencyLock()) { applyOverrides(overrides) @@ -178,7 +181,7 @@ class DependencyLockPlugin implements Plugin { } } - private Set parseUpdates(String updates) { + private static Set parseUpdates(String updates) { updates.tokenize(',') as Set } @@ -198,7 +201,7 @@ class DependencyLockPlugin implements Plugin { project['commitDependencyLock.message'] : commitExtension.message } patternsToCommit = { - def lockFiles = [] + List lockFiles = [] def rootLock = new File(project.rootProject.projectDir, clLockFileName ?: lockExtension.lockFile) if (rootLock.exists()) { lockFiles << rootLock @@ -234,7 +237,7 @@ class DependencyLockPlugin implements Plugin { private SaveLockTask configureSaveTask(String lockFileName, GenerateLockTask lockTask, UpdateLockTask updateTask, DependencyLockExtension extension) { SaveLockTask saveTask = project.tasks.create('saveLock', SaveLockTask) saveTask.doFirst { - SaveLockTask globalSave = project.rootProject.tasks.findByName('saveGlobalLock') + SaveLockTask globalSave = project.rootProject.tasks.findByName('saveGlobalLock') as SaveLockTask if (globalSave?.outputLock?.exists()) { throw new GradleException('Cannot save individual locks when global lock is in place, run deleteGlobalLock task') } @@ -248,7 +251,8 @@ class DependencyLockPlugin implements Plugin { saveTask } - private void configureCommonSaveTask(SaveLockTask saveTask, GenerateLockTask lockTask, UpdateLockTask updateTask) { + private + static void configureCommonSaveTask(SaveLockTask saveTask, GenerateLockTask lockTask, UpdateLockTask updateTask) { saveTask.mustRunAfter lockTask, updateTask saveTask.outputs.upToDateWhen { if (saveTask.generatedLock.exists() && saveTask.outputLock.exists()) { @@ -263,7 +267,7 @@ class DependencyLockPlugin implements Plugin { SaveLockTask globalSaveTask = project.tasks.create('saveGlobalLock', SaveLockTask) globalSaveTask.doFirst { project.subprojects.each { Project sub -> - SaveLockTask save = sub.tasks.findByName('saveLock') + SaveLockTask save = sub.tasks.findByName('saveLock') as SaveLockTask if (save && save.outputLock?.exists()) { throw new GradleException('Cannot save global lock, one or more individual locks are in place, run deleteLock task') } @@ -294,7 +298,7 @@ class DependencyLockPlugin implements Plugin { task.conventionMapping.with { skippedDependencies = { extension.skippedDependencies } includeTransitives = { - project.hasProperty('dependencyLock.includeTransitives') ? Boolean.parseBoolean(project['dependencyLock.includeTransitives']) : extension.includeTransitives + project.hasProperty('dependencyLock.includeTransitives') ? Boolean.parseBoolean(project['dependencyLock.includeTransitives'] as String) : extension.includeTransitives } filter = { extension.dependencyFilter } overrides = { overrideMap } @@ -361,14 +365,12 @@ class DependencyLockPlugin implements Plugin { logger.info("Using command line overrides ${project['dependencyLock.override']}") } - def overrideForces = overrides.collect { "${it.key}:${it.value}" } - logger.debug(overrideForces.toString()) + def overrideDeps = overrides.collect { "${it.key}:${it.value}" } + logger.debug(overrideDeps.toString()) - project.configurations.all { - resolutionStrategy { - overrideForces.each { dep -> force dep } - } - } + project.configurations.all({ Configuration conf -> + configureResolutionStrategy(conf.resolutionStrategy, overrideDeps) + }) } void applyLock(File dependenciesLock, Map overrides, Collection updates = []) { @@ -376,9 +378,10 @@ class DependencyLockPlugin implements Plugin { def locks = loadLock(dependenciesLock) if (updates) { - locks = locks.collectEntries { configurationName, deps -> [(configurationName): deps.findAll { coord, info -> (info.transitive == null) && !updates.contains(coord) }]} + locks = locks.collectEntries { configurationName, deps -> [(configurationName): deps.findAll { coord, info -> (info.transitive == null) && !updates.contains(coord) }] } } + // in the old format, all first level props were groupId:artifactId def isDeprecatedFormat = !locks.isEmpty() && locks.every { it.key ==~ /[^:]+:.+/ } // in the old format, all first level props were groupId:artifactId if (isDeprecatedFormat) { logger.warn("${dependenciesLock.name} is using a deprecated lock format. Support for this format may be removed in future versions.") @@ -394,7 +397,7 @@ class DependencyLockPlugin implements Plugin { def nonProjectLocks = deps.findAll { it.value?.locked } // Override locks from the file with any of the user specified manual overrides. - def lockForces = nonProjectLocks.collect { + def locked = nonProjectLocks.collect { overrides.containsKey(it.key) ? "${it.key}:${overrides[it.key]}" : "${it.key}:${it.value.locked}" } @@ -403,19 +406,26 @@ class DependencyLockPlugin implements Plugin { "${it.key}:${it.value}" } - lockForces.addAll(unusedOverrides) - logger.debug('lockForces: {}', lockForces) + locked.addAll(unusedOverrides) + logger.debug('locked: {}', locked) - // Create the dependencies explicitly to avoid doing that implicitly for every configuration - lockForces = lockForces.collect { dep -> project.dependencies.create(dep) } - resolutionStrategy { - force lockForces.toArray() - } + configureResolutionStrategy(conf.resolutionStrategy, locked) } }) } - boolean shouldIgnoreDependencyLock() { + private void configureResolutionStrategy(ResolutionStrategy resolutionStrategy, List dependencyNotations) { + def dependencies = dependencyNotations.collect { project.dependencies.create(it) } + resolutionStrategy.eachDependency { details -> + dependencies.each { dep -> + if (details.requested.group == dep.group && details.requested.name == dep.name) { + details.useTarget group: details.requested.group, name: details.requested.name, version: dep.version + } + } + } + } + + private boolean shouldIgnoreDependencyLock() { if (project.hasProperty('dependencyLock.ignore')) { def prop = project.property('dependencyLock.ignore') @@ -434,9 +444,10 @@ class DependencyLockPlugin implements Plugin { // Load overrides from a file if the user has specified one via a property. if (project.hasProperty('dependencyLock.overrideFile')) { - File dependenciesLock = new File(project.rootDir, project['dependencyLock.overrideFile']) + File dependenciesLock = new File(project.rootDir, project['dependencyLock.overrideFile'] as String) def lockOverride = loadLock(dependenciesLock) - def isDeprecatedFormat = lockOverride.any { it.value.getClass() != String && it.value.locked } // the old lock override files specified the version to override under the "locked" property + def isDeprecatedFormat = lockOverride.any { it.value.getClass() != String && it.value.locked } + // the old lock override files specified the version to override under the "locked" property if (isDeprecatedFormat) { logger.warn("The override file ${dependenciesLock.name} is using a deprecated format. Support for this format may be removed in future versions.") } @@ -456,7 +467,7 @@ class DependencyLockPlugin implements Plugin { return overrides } - private loadLock(File lock) { + private static loadLock(File lock) { try { return new JsonSlurper().parseText(lock.text) } catch (ex) { diff --git a/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy b/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy index 283c2caf..7ecf8591 100644 --- a/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy +++ b/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy @@ -979,6 +979,81 @@ class DependencyLockLauncherSpec extends IntegrationSpec { new File(projectDir, 'build/dependencies.lock').text == updatedLock } + def 'eachDependency wins over force'() { + buildFile << """\ + apply plugin: 'java' + + repositories { maven { url '${Fixture.repo}' } } + + dependencies { + compile 'test.example:foo:latest.release' + } + + configurations.all { + resolutionStrategy { + eachDependency { details -> + if (details.requested.group == 'test.example' && details.requested.name == 'foo') { + details.useTarget group: details.requested.group, name: details.requested.name, version: '1.0.1' + } + } + force 'test.example:foo:1.0.0' + } + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies') + + then: + result.standardOutput.contains('\\--- test.example:foo:latest.release -> 1.0.1\n') + } + + @Issue("https://github.com/nebula-plugins/gradle-dependency-lock-plugin/issues/86") + def 'locks win over Spring dependency management'() { + buildFile << """\ + buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE' + } + } + + apply plugin: 'java' + apply plugin: 'nebula.dependency-lock' + apply plugin: 'spring-boot' + + repositories { + mavenCentral() + } + + dependencies { + compile ('com.hazelcast:hazelcast:3.6-RC1') + compile ('com.hazelcast:hazelcast-spring:3.6-RC1') + compile ('org.mariadb.jdbc:mariadb-java-client:1.1.7') + compile ('org.flywaydb:flyway-core') + + testCompile ('org.springframework.boot:spring-boot-starter-test') + } + """.stripIndent() + + when: + runTasksSuccessfully('generateLock', 'saveLock') + + then: + noExceptionThrown() + def buildFileText = buildFile.text + buildFile.delete() + buildFile << buildFileText.replace('com.hazelcast:hazelcast:3.6-RC1', 'com.hazelcast:hazelcast:3.6-EA2') + + when: + def result = runTasksSuccessfully('dependencies') + + then: + result.standardOutput.contains('\\--- com.hazelcast:hazelcast:3.6-RC1\n') + } + def 'deprecated lock format message is not output for an empty file'() { def dependenciesLock = new File(projectDir, 'dependencies.lock') dependenciesLock << """{}"""