diff --git a/src/main/groovy/nebula/plugin/publishing/ivy/AbstractResolvedDependenciesPlugin.groovy b/src/main/groovy/nebula/plugin/publishing/ivy/AbstractResolvedDependenciesPlugin.groovy new file mode 100644 index 00000000..6815f34c --- /dev/null +++ b/src/main/groovy/nebula/plugin/publishing/ivy/AbstractResolvedDependenciesPlugin.groovy @@ -0,0 +1,65 @@ +package nebula.plugin.publishing.ivy + +import groovy.transform.Memoized +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.artifacts.component.ModuleComponentSelector +import org.gradle.api.artifacts.result.ResolvedDependencyResult + +abstract class AbstractResolvedDependenciesPlugin implements Plugin { + ModuleVersionIdentifier selectedModuleVersion(Project project, String scope, String group, String name) { + def exclude + try { + exclude = project.configurations.getByName(scope).excludeRules.find { + it.group == group && it.module == name + } + } catch (e) { + // leave exclude null in case of unknown configuration + } + if (exclude) { + throw new GradleException("Direct dependency \"${group}:${name}\" is excluded, delete direct dependency or stop excluding it") + } + + ResolvedDependencyResult result = lookupDependency(project, scope, group, name) + + Map scoping = [compile: 'runtime', provided: 'compileOnly'] + + if (!result && scoping[scope]) { + result = lookupDependency(project, scoping[scope], group, name) + } + + result?.selected?.moduleVersion + } + + private ResolvedDependencyResult lookupDependency(Project project, String scope, String group, String name) { + def resolvedDependencies = resolvedDependencyByConfiguration(project)[scope] + def result = resolvedDependencies.find { r -> + def requested = r.requested + (requested instanceof ModuleComponentSelector) && + (requested.group == group) && + (requested.module == name) + } + result + } + + @Memoized + Map> resolvedDependencyByConfiguration(Project project) { + ConfigurationContainer configurations = project.configurations + Map> dependencyMap = [:] + dependencyMap['runtime'] = resolvedDependencies(configurations.runtimeClasspath) + dependencyMap['compile'] = resolvedDependencies(configurations.compileClasspath) + dependencyMap['compileOnly'] = resolvedDependencies(configurations.compileOnly) + dependencyMap['test'] = resolvedDependencies(configurations.testRuntimeClasspath) + return dependencyMap + } + + Set resolvedDependencies(Configuration configuration) { + configuration.incoming.resolutionResult.allDependencies.findAll { + it instanceof ResolvedDependencyResult + } as Set + } +} diff --git a/src/main/groovy/nebula/plugin/publishing/ivy/IvyResolvedDependenciesPlugin.groovy b/src/main/groovy/nebula/plugin/publishing/ivy/IvyResolvedDependenciesPlugin.groovy index 77e43dde..1e0cddd8 100644 --- a/src/main/groovy/nebula/plugin/publishing/ivy/IvyResolvedDependenciesPlugin.groovy +++ b/src/main/groovy/nebula/plugin/publishing/ivy/IvyResolvedDependenciesPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 Netflix, Inc. + * Copyright 2015-2017 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,67 @@ */ package nebula.plugin.publishing.ivy -import nebula.plugin.publishing.verification.DirectDependenciesVerifier -import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.XmlProvider +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionComparator +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionSelectorScheme +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ExactVersionSelector +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelector +import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.publish.ivy.IvyPublication /** * Replaces first order dependencies with the selected versions when publishing. */ -class IvyResolvedDependenciesPlugin implements Plugin { +class IvyResolvedDependenciesPlugin extends AbstractResolvedDependenciesPlugin { @Override void apply(Project project) { project.plugins.apply IvyBasePublishPlugin project.afterEvaluate { - DirectDependenciesVerifier.verify(project) project.publishing { publications { withType(IvyPublication) { - versionMapping { - allVariants { - fromResolutionResult() + descriptor.withXml { XmlProvider xml -> + project.plugins.withType(JavaBasePlugin) { + def dependencies = xml.asNode()?.dependencies?.dependency + dependencies?.each { Node dep -> + String scope = dep.@conf + String group = dep.@org + String name = dep.@name + + if (scope == 'compile->default') { + scope = 'compile' + } + + if (scope == 'provided->default' || scope == 'runtime->default') { + scope = 'runtime' + } + + if (scope == 'test->default') { + scope = 'test' + } + + if (scope.contains('->')) { + scope = scope.split('->')[0] + } + + def mvid = selectedModuleVersion(project, scope, group, name) + if (!mvid) { + return // continue loop if a dependency is not found in dependencyMap + } + + if (dep.@rev) { + def version = dep.@rev as String + VersionSelector selector = parseSelector(version) + setVersionConstraint(selector, version, dep, mvid) + } else { + //no requested version we use selected + dep.@rev = mvid.version + } + updateReplacedModules(mvid, group, name, dep) + } } } } @@ -43,4 +83,26 @@ class IvyResolvedDependenciesPlugin implements Plugin { } } } + + private VersionSelector parseSelector(String version) { + def scheme = new DefaultVersionSelectorScheme(new DefaultVersionComparator()) + def selector = scheme.parseSelector(version) + selector + } + + private void setVersionConstraint(VersionSelector selector, String version, Node dep, ModuleVersionIdentifier selected) { + if (!(selector instanceof ExactVersionSelector)) { + //requested dynamic version will be replaced by specific selected + dep.@revConstraint = version + dep.@rev = selected.version + } + } + + private void updateReplacedModules(ModuleVersionIdentifier mvid, String group, String name, Node dep) { + if (mvid.group != group || mvid.name != name) { + dep.@org = mvid.group + dep.@name = mvid.name + dep.@rev = mvid.version + } + } } diff --git a/src/test/groovy/nebula/plugin/publishing/ivy/interaction/IvyPublishRecommenderInteractionSpec.groovy b/src/test/groovy/nebula/plugin/publishing/ivy/interaction/IvyPublishRecommenderInteractionSpec.groovy deleted file mode 100644 index 0931e316..00000000 --- a/src/test/groovy/nebula/plugin/publishing/ivy/interaction/IvyPublishRecommenderInteractionSpec.groovy +++ /dev/null @@ -1,118 +0,0 @@ -package nebula.plugin.publishing.ivy.interaction - -import nebula.test.IntegrationTestKitSpec -import nebula.test.dependencies.DependencyGraphBuilder -import nebula.test.dependencies.GradleDependencyGenerator -import spock.lang.Unroll - -class IvyPublishRecommenderInteractionSpec extends IntegrationTestKitSpec { - @Unroll - def 'dependencies from recommendation plugin in #scope scope should be in ivy file - java-library plugin'() { - def graph = new DependencyGraphBuilder().addModule('test:foo:1.0.0').build() - def generator = new GradleDependencyGenerator(graph, "$projectDir/repo") - generator.generateTestMavenRepo() - - settingsFile.text = """ - rootProject.name='mytest' - """.stripIndent() - - buildFile << """\ - plugins { - id 'java-library' - id 'nebula.ivy-publish' - id 'nebula.dependency-recommender' version '5.0.0' - } - - group = 'test.nebula' - version = '0.1.0' - - repositories { - ${generator.mavenRepositoryBlock} - } - - dependencyRecommendations { - map recommendations: ['test:foo': '1.0.0'] - } - - dependencies { - $scope 'test:foo' - } - - publishing { - repositories { - ivy { - name 'testlocal' - url 'build/testlocal' - } - } - } - """.stripIndent() - - when: - def results = runTasks('publishNebulaIvyPublicationToTestlocalRepository') - - then: - def ivy = new XmlSlurper().parse(new File(projectDir, 'build/testlocal/test.nebula/mytest/0.1.0/ivy-0.1.0.xml')) - ivy.dependencies.dependency.first().@rev == '1.0.0' - - where: - scope << ['compile', 'implementation', 'api', 'runtimeOnly'] - - } - - @Unroll - def 'dependencies from recommendation plugin in #scope scope should be in ivy file - java plugin'() { - def graph = new DependencyGraphBuilder().addModule('test:foo:1.0.0').build() - def generator = new GradleDependencyGenerator(graph, "$projectDir/repo") - generator.generateTestMavenRepo() - - settingsFile.text = """ - rootProject.name='mytest' - """.stripIndent() - - buildFile << """\ - plugins { - id 'java' - id 'nebula.ivy-publish' - id 'nebula.dependency-recommender' version '5.0.0' - } - - group = 'test.nebula' - version = '0.1.0' - - repositories { - ${generator.mavenRepositoryBlock} - } - - dependencyRecommendations { - map recommendations: ['test:foo': '1.0.0'] - } - - dependencies { - $scope 'test:foo' - } - - publishing { - repositories { - ivy { - name 'testlocal' - url 'build/testlocal' - } - } - } - """.stripIndent() - - when: - def results = runTasks('publishNebulaIvyPublicationToTestlocalRepository') - - then: - def ivy = new XmlSlurper().parse(new File(projectDir, 'build/testlocal/test.nebula/mytest/0.1.0/ivy-0.1.0.xml')) - ivy.dependencies.dependency.first().@rev == '1.0.0' - - where: - scope << ['compile', 'implementation', 'runtimeOnly'] - - } - - -}