diff --git a/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy b/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy index 2ba73d0b..4eb18eb2 100644 --- a/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy +++ b/src/main/groovy/nebula/plugin/dependencylock/DependencyLockPlugin.groovy @@ -295,8 +295,10 @@ class DependencyLockPlugin implements Plugin { private void maybeApplyLock(Configuration conf, DependencyLockExtension extension, Map overrides, String globalLockFileName, String lockFilename) { File dependenciesLock File globalLock = new File(project.rootProject.projectDir, globalLockFileName ?: extension.globalLockFile) + boolean isGlobal = false if (globalLock.exists()) { dependenciesLock = globalLock + isGlobal = true } else { dependenciesLock = new File(project.projectDir, lockFilename ?: extension.lockFile) } @@ -307,11 +309,11 @@ class DependencyLockPlugin implements Plugin { boolean hasGenerateTask = hasGenerationTask(taskNames) if (dependenciesLock.exists()) { if (!hasGenerateTask) { - applyLock(conf, dependenciesLock, overrides) + applyLock(conf, dependenciesLock, overrides, [], isGlobal) appliedLock = true } else if (hasUpdateTask(taskNames)) { def updates = project.hasProperty(UPDATE_DEPENDENCIES) ? parseUpdates(project.property(UPDATE_DEPENDENCIES) as String) : extension.updateDependencies - applyLock(conf, dependenciesLock, overrides, updates) + applyLock(conf, dependenciesLock, overrides, updates, isGlobal) appliedLock = true } } @@ -361,12 +363,20 @@ class DependencyLockPlugin implements Plugin { updates.tokenize(',') as Set } - void applyLock(Configuration conf, File dependenciesLock, Map overrides, Collection updates = []) { + private static boolean isTopLevel(info, deps, isGlobal) { + // If this is not listed as being depended on at all, it must be a top-level dependency + if (info.transitive == null) return true + + // If a dependency is listed as being depended on by something that we know is a project, then it must be a top-level dependency + return isGlobal && info.transitive.any { deps[it]?.project } + } + + void applyLock(Configuration conf, File dependenciesLock, Map overrides, Collection updates = [], boolean isGlobal) { LOGGER.info("Using ${dependenciesLock.name} to lock dependencies in $conf") 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 -> isTopLevel(info, deps, isGlobal) && !updates.contains(coord) }] } } // in the old format, all first level props were groupId:artifactId diff --git a/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy b/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy index 25ee49e0..ddbeb7cf 100644 --- a/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy +++ b/src/test/groovy/nebula/plugin/dependencylock/DependencyLockLauncherSpec.groovy @@ -1115,6 +1115,95 @@ class DependencyLockLauncherSpec extends IntegrationSpec { !result.standardOutput.contains("is using a deprecated lock format") } + def 'only the update dependency and its transitives are updated when using a global lock'() { + addSubproject('sub1', """\ + dependencies { + compile 'test.example:bar:1.+' + compile 'test.example:qux:latest.release' + } + """.stripIndent()) + buildFile << """\ + allprojects { + ${applyPlugin(DependencyLockPlugin)} + group = 'test' + } + subprojects { + apply plugin: 'java' + repositories { maven { url '${Fixture.repo}' } } + } + dependencyLock { + includeTransitives = true + } + """.stripIndent() + + def lockFile = new File(projectDir, 'global.lock') + def lockText = '''\ + { + "_global_": { + "test.example:bar": { + "locked": "1.0.0", + "requested": "1.+", + "transitive": [ + "test:sub1" + ] + }, + "test.example:qux": { + "locked": "1.0.0", + "requested": "latest.release", + "transitive": [ + "test:sub1" + ] + }, + "test.example:foo": { + "locked": "1.0.0", + "transitive": [ + "test.example:bar", + "test.example:qux" + ] + }, + "test:sub1": { + "project": true + } + } + }'''.stripIndent() + lockFile.text = lockText + + def updatedLock = '''\ + { + "_global_": { + "test.example:bar": { + "locked": "1.1.0", + "transitive": [ + "test:sub1" + ] + }, + "test.example:foo": { + "locked": "1.0.1", + "transitive": [ + "test.example:bar", + "test.example:qux" + ] + }, + "test.example:qux": { + "locked": "1.0.0", + "transitive": [ + "test:sub1" + ] + }, + "test:sub1": { + "project": true + } + } + }'''.stripIndent() + + when: + def results = runTasksSuccessfully('updateGlobalLock', '-PdependencyLock.updateDependencies=test.example:bar') + + then: + println results.standardOutput + new File(projectDir, 'build/global.lock').text == updatedLock + } + @Ignore def 'diff the generated lock with the existing lock '() { def dependenciesLock = new File(projectDir, 'dependencies.lock')