All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gradle.integtests.resolve.ResolveConfigurationDependenciesBuildOperationIntegrationTest.groovy Maven / Gradle / Ivy

There is a newer version: 8.6
Show newest version
/*
 * Copyright 2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.integtests.resolve

import org.gradle.api.internal.artifacts.configurations.ResolveConfigurationDependenciesBuildOperationType
import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest
import org.gradle.integtests.fixtures.BuildOperationNotificationsFixture
import org.gradle.integtests.fixtures.BuildOperationsFixture
import org.gradle.test.fixtures.maven.MavenFileRepository
import org.gradle.test.fixtures.server.http.AuthScheme
import org.gradle.test.fixtures.server.http.MavenHttpModule
import org.gradle.test.fixtures.server.http.MavenHttpRepository
import spock.lang.Ignore
import spock.lang.Unroll

class ResolveConfigurationDependenciesBuildOperationIntegrationTest extends AbstractHttpDependencyResolutionTest {

    def operations = new BuildOperationsFixture(executer, temporaryFolder)

    @SuppressWarnings("GroovyUnusedDeclaration")
    def operationNotificationsFixture = new BuildOperationNotificationsFixture(executer, temporaryFolder)

    def "resolved configurations are exposed via build operation"() {
        setup:
        buildFile << """                
            allprojects {
                apply plugin: "java"
                repositories {
                    maven { url '${mavenHttpRepo.uri}' }
                }
            }
            dependencies {
                compile 'org.foo:hiphop:1.0'
                compile 'org.foo:unknown:1.0' //does not exist
                compile project(":child")
                compile 'org.foo:rock:1.0' //contains unresolved transitive dependency
            }

            task resolve(type: Copy) {
                from configurations.compile
                into "build/resolved"
            }
        """
        settingsFile << "include 'child'"
        def m1 = mavenHttpRepo.module('org.foo', 'hiphop').publish()
        def m2 = mavenHttpRepo.module('org.foo', 'unknown')
        def m3 = mavenHttpRepo.module('org.foo', 'broken')
        def m4 = mavenHttpRepo.module('org.foo', 'rock').dependsOn(m3).publish()

        m1.allowAll()
        m2.allowAll()
        m3.pom.expectGetBroken()
        m4.allowAll()

        when:
        fails "resolve"

        then:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        op.details.configurationName == "compile"
        op.details.projectPath == ":"
        op.details.buildPath == ":"
        op.details.scriptConfiguration == false
        op.details.configurationDescription ==~ /Dependencies for source set 'main'.*/
        op.details.configurationVisible == false
        op.details.configurationTransitive == true

        op.result.resolvedDependenciesCount == 4
    }

    def "resolved detached configurations are exposed"() {
        setup:
        buildFile << """                
        repositories {
            maven { url '${mavenHttpRepo.uri}' }
        }
        
        task resolve {
            doLast {
                project.configurations.detachedConfiguration(dependencies.create('org.foo:dep:1.0')).files
            }
        }
        """
        def m1 = mavenHttpRepo.module('org.foo', 'dep').publish()


        m1.allowAll()

        when:
        run "resolve"

        then:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        op.details.configurationName == "detachedConfiguration1"
        op.details.projectPath == ":"
        op.details.scriptConfiguration == false
        op.details.buildPath == ":"
        op.details.configurationDescription == null
        op.details.configurationVisible == true
        op.details.configurationTransitive == true

        op.result.resolvedDependenciesCount == 1
    }

    def "resolved configurations in composite builds are exposed via build operation"() {
        setup:
        def m1 = mavenHttpRepo.module('org.foo', 'app-dep').publish()
        def m2 = mavenHttpRepo.module('org.foo', 'root-dep').publish()

        setupComposite()
        buildFile << """                
            allprojects {
                apply plugin: "java"
                repositories {
                    maven { url '${mavenHttpRepo.uri}' }
                }
            }
            dependencies {
                compile 'org.foo:root-dep:1.0'
                compile 'org.foo:my-composite-app:1.0'
            }

            task resolve(type: Copy) {
                from configurations.compile
                into "build/resolved"
            }
        """


        m1.allowAll()
        m2.allowAll()

        when:
        run "resolve"

        then: "configuration of composite are exposed"
        def resolveOperations = operations.all(ResolveConfigurationDependenciesBuildOperationType)
        resolveOperations.size() == 2
        resolveOperations[0].details.configurationName == "compile"
        resolveOperations[0].details.projectPath == ":"
        resolveOperations[0].details.buildPath == ":"
        resolveOperations[0].details.scriptConfiguration == false
        resolveOperations[0].details.configurationDescription ==~ /Dependencies for source set 'main'.*/
        resolveOperations[0].details.configurationVisible == false
        resolveOperations[0].details.configurationTransitive == true
        resolveOperations[0].result.resolvedDependenciesCount == 2

        and: "classpath configuration is exposed"
        resolveOperations[1].details.configurationName == "compileClasspath"
        resolveOperations[1].details.projectPath == ":"
        resolveOperations[1].details.buildPath == ":my-composite-app"
        resolveOperations[1].details.scriptConfiguration == false
        resolveOperations[1].details.configurationDescription == "Compile classpath for source set 'main'."
        resolveOperations[1].details.configurationVisible == false
        resolveOperations[1].details.configurationTransitive == true
        resolveOperations[1].result.resolvedDependenciesCount == 1
    }

    def "resolved configurations of composite builds as build dependencies are exposed"() {
        setup:
        def m1 = mavenHttpRepo.module('org.foo', 'root-dep').publish()
        setupComposite()
        buildFile << """                
            buildscript {
                repositories {
                    maven { url '${mavenHttpRepo.uri}' }
                }
                dependencies {
                    classpath 'org.foo:root-dep:1.0'
                    classpath 'org.foo:my-composite-app:1.0'
                }
            }
            
            apply plugin: "java"
        """


        m1.allowAll()

        when:
        run "buildEnvironment"

        then:
        def resolveOperations = operations.all(ResolveConfigurationDependenciesBuildOperationType)
        resolveOperations.size() == 2
        resolveOperations[0].details.configurationName == "classpath"
        resolveOperations[0].details.projectPath == null
        resolveOperations[0].details.buildPath == ":"
        resolveOperations[0].details.scriptConfiguration == true
        resolveOperations[0].details.configurationDescription == null
        resolveOperations[0].details.configurationVisible == true
        resolveOperations[0].details.configurationTransitive == true
        resolveOperations[0].result.resolvedDependenciesCount == 2

        resolveOperations[1].details.configurationName == "compileClasspath"
        resolveOperations[1].details.projectPath == ":"
        resolveOperations[1].details.buildPath == ":my-composite-app"
        resolveOperations[1].details.scriptConfiguration == false
        resolveOperations[1].details.configurationDescription == "Compile classpath for source set 'main'."
        resolveOperations[1].details.configurationVisible == false
        resolveOperations[1].details.configurationTransitive == true
        resolveOperations[1].result.resolvedDependenciesCount == 1
    }

    @Unroll
    def "#scriptType script classpath configurations are exposed"() {
        setup:
        def m1 = mavenHttpRepo.module('org.foo', 'root-dep').publish()

        def initScript = file('init.gradle')
        initScript << ''
        executer.usingInitScript(initScript)

        file('scriptPlugin.gradle') << '''
        task foo
        '''

        buildFile << '''
        apply from: 'scriptPlugin.gradle'
        '''

        file(scriptFileName) << """                
            $scriptBlock {
                repositories {
                    maven { url '${mavenHttpRepo.uri}' }
                }
                dependencies {
                    classpath 'org.foo:root-dep:1.0'
                }
            }

        """

        m1.allowAll()
        when:
        run "foo"

        then:
        def resolveOperations = operations.all(ResolveConfigurationDependenciesBuildOperationType)
        resolveOperations.size() == 1
        resolveOperations[0].details.buildPath == ":"
        resolveOperations[0].details.configurationName == "classpath"
        resolveOperations[0].details.projectPath == null
        resolveOperations[0].details.scriptConfiguration == true
        resolveOperations[0].details.configurationDescription == null
        resolveOperations[0].details.configurationVisible == true
        resolveOperations[0].details.configurationTransitive == true
        resolveOperations[0].result.resolvedDependenciesCount == 1

        where:
        scriptType      | scriptBlock   | scriptFileName
        "project build" | 'buildscript' | getDefaultBuildFileName()
        "script plugin" | 'buildscript' | "scriptPlugin.gradle"
        "settings"      | 'buildscript' | 'settings.gradle'
        "init"          | 'initscript'  | 'init.gradle'
    }

    def "included build classpath configuration resolution result is exposed"() {
        setup:
        def m1 = mavenHttpRepo.module('org.foo', 'some-dep').publish()

        file("projectB/settings.gradle") << """
        rootProject.name = 'project-b'
        include "sub1"
        """

        file("projectB/build.gradle") << """
                buildscript {
                    repositories {
                        maven { url '${mavenHttpRepo.uri}' }
                    }
                    dependencies {
                        classpath "org.foo:some-dep:1.0"
                    }
                }
                allprojects {
                    apply plugin: 'java'
                    group "org.sample"
                    version "1.0"
                }
                
        """

        settingsFile << """
            includeBuild 'projectB'
        """

        buildFile << """
            buildscript {
                dependencies {

                    classpath 'org.sample:sub1:1.0'
                }
            }
            task foo
        """

        m1.allowAll()
        executer.requireIsolatedDaemons()
        when:
        run "foo"

        then:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType) {
            it.details.configurationName == 'classpath' && it.details.buildPath == ':project-b'
        }
        op.result.resolvedDependenciesCount == 1
    }

    private void setupComposite() {
        file("my-composite-app/src/main/java/App.java") << "public class App {}"
        file("my-composite-app/build.gradle") << """
            group = "org.foo"
            version = '1.0'

            apply plugin: "java"
            repositories {
                maven { url '${mavenHttpRepo.uri}' }
            }
            
            dependencies {
                compile 'org.foo:app-dep:1.0'
            }
            
            tasks.withType(JavaCompile) {
                options.annotationProcessorPath = files()
            }
        """
        file("my-composite-app/settings.gradle") << "rootProject.name = 'my-composite-app'"

        settingsFile << """
        rootProject.name='root'
        includeBuild 'my-composite-app'
        """
        mavenHttpRepo.module('org.foo', 'app-dep').publish().allowAll()
    }

    def "failed resolved configurations are exposed via build operation"() {
        given:
        MavenHttpModule a
        MavenHttpModule b
        MavenHttpModule leaf1
        MavenHttpModule leaf2
        mavenHttpRepo.with {
            a = module('org', 'a', '1.0').dependsOn('org', 'leaf', '1.0').publish()
            b = module('org', 'b', '1.0').dependsOn('org', 'leaf', '2.0').publish()
            leaf1 = module('org', 'leaf', '1.0').publish()
            leaf2 = module('org', 'leaf', '2.0').publish()
        }

        when:
        buildFile << """    
            repositories {
                maven { url = '${mavenHttpRepo.uri}' }
            }
            
            configurations {
                compile {
                    resolutionStrategy.failOnVersionConflict()
                }
            }
                        
            dependencies {
               compile 'org:a:1.0'
               compile 'org:b:1.0'
            }
            
            task resolve {
              doLast {
                  println(configurations.compile.files.name)
              }
            }
"""
        a.pom.expectGet()
        b.pom.expectGet()
        leaf1.pom.expectGet()
        leaf2.pom.expectGet()

        then:
        fails "resolve"

        and:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        op.details.configurationName == "compile"
        op.failure == "org.gradle.api.artifacts.ResolveException: Could not resolve all dependencies for configuration ':compile'."
        failure.assertHasCause("""Conflict(s) found for the following module(s):
  - org:leaf between versions 2.0 and 1.0""")
        op.result != null
        op.result.resolvedDependenciesCount == 2
    }

    // This documents the current behavior, not necessarily the smartest one.
    // FTR This behaves the same in 4.7, 4.8 and 4.9
    def "non fatal errors incur no resolution failure"() {
        def mod = mavenHttpRepo.module('org', 'a', '1.0')
        mod.pomFile << "corrupt"

        when:
        buildFile << """    
            repositories {
                maven { url = '${mavenHttpRepo.uri}' }
            }
            
            configurations {
                compile
            }
                        
            dependencies {
               compile 'org:a:1.0'
            }
            
            task resolve {
              doLast {
                  println(configurations.compile.files.name)
              }
            }
"""
        then:
        mod.allowAll()
        fails "resolve"

        and:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        op.details.configurationName == "compile"
        op.failure == null
        op.result.resolvedDependenciesCount == 1
    }

    @Ignore("There is no longer a call to project.getServices so this test no longer breaks the engine and could not find an alternative for now")
    def "a fatal failure is captured in resolution build operation result"() {
        def mod = mavenHttpRepo.module('org', 'a', '1.0').publish()

        when:
        buildFile << """    
            repositories {
                maven { url = '${mavenHttpRepo.uri}' }
            }
            
            configurations {
                compile
            }
                        
            dependencies {
               compile 'org:a:1.0'
            }
            
            task resolve {
              doLast {
                  // this will shutdown the project scope services, which is going to trigger a
                  // fatal dependency resolution error
                  services.close()
                  println(configurations.compile.files.name)
              }
            }
            
"""
        then:
        mod.allowAll()
        fails "resolve"

        and:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        op.details.configurationName == "compile"
        op.failure == "org.gradle.api.artifacts.ResolveException: Could not resolve all dependencies for configuration ':compile'."
        op.result == null
    }

    def "resolved components contain their source repository name, even when taken from the cache"() {
        setup:
        def secondMavenHttpRepo = new MavenHttpRepository(server, '/repo-2', new MavenFileRepository(file('maven-repo-2')))

        // 'direct1' 'transitive1' and 'child-transitive1' are found in 'maven1'
        mavenHttpRepo.module('org.foo', 'transitive1').publish().allowAll()
        mavenHttpRepo.module('org.foo', 'direct1').publish().allowAll()
        mavenHttpRepo.module('org.foo', 'child-transitive1').publish().allowAll()

        // 'direct2' 'transitive2', and 'child-transitive2' are found in 'maven2' (unpublished in 'maven1')
        mavenHttpRepo.module('org.foo', 'direct2').allowAll()
        secondMavenHttpRepo.module('org.foo', 'direct2')
            .dependsOn('org.foo', 'transitive1', '1.0')
            .dependsOn('org.foo', 'transitive2', '1.0')
            .publish().allowAll()
        mavenHttpRepo.module('org.foo', 'transitive2').allowAll()
        secondMavenHttpRepo.module('org.foo', 'transitive2').publish().allowAll()
        mavenHttpRepo.module('org.foo', 'child-transitive2').allowAll()
        secondMavenHttpRepo.module('org.foo', 'child-transitive2').publish().allowAll()

        buildFile << """                
            apply plugin: "java"
            repositories {
                maven { 
                    name 'maven1'
                    url '${mavenHttpRepo.uri}'
                }
                maven { 
                    name 'maven2'
                    url '${secondMavenHttpRepo.uri}'
                }
            }
            dependencies {
                compile 'org.foo:direct1:1.0'
                compile 'org.foo:direct2:1.0'
                compile project(':child')
            }

            task resolve { doLast { configurations.compile.resolve() } }
            
            project(':child') {
                apply plugin: "java"
                dependencies {
                    compile 'org.foo:child-transitive1:1.0'
                    compile 'org.foo:child-transitive2:1.0'
                }
            }
        """
        settingsFile << "include 'child'"

        def verifyExpectedOperation = {
            def ops = operations.all(ResolveConfigurationDependenciesBuildOperationType)
            assert ops.size() == 1
            def op = ops[0]
            assert op.result.resolvedDependenciesCount == 3
            def resolvedComponents = op.result.components
            assert resolvedComponents.size() == 8
            assert resolvedComponents.'project :'.repoName == null
            assert resolvedComponents.'org.foo:direct1:1.0'.repoName == 'maven1'
            assert resolvedComponents.'org.foo:direct2:1.0'.repoName == 'maven2'
            assert resolvedComponents.'org.foo:transitive1:1.0'.repoName == 'maven1'
            assert resolvedComponents.'org.foo:transitive2:1.0'.repoName == 'maven2'
            assert resolvedComponents.'project :child'.repoName == null
            assert resolvedComponents.'org.foo:child-transitive1:1.0'.repoName == 'maven1'
            assert resolvedComponents.'org.foo:child-transitive2:1.0'.repoName == 'maven2'
            return true
        }

        when:
        succeeds 'resolve'

        then:
        verifyExpectedOperation()

        when:
        server.resetExpectations()
        succeeds 'resolve'

        then:
        verifyExpectedOperation()
    }

    def "resolved components contain their source repository name when resolution fails"() {
        setup:
        mavenHttpRepo.module('org.foo', 'transitive1').publish().allowAll()
        mavenHttpRepo.module('org.foo', 'direct1')
            .dependsOn('org.foo', 'transitive1', '1.0')
            .publish().allowAll()

        buildFile << """                
            apply plugin: "java"
            repositories {
                maven { 
                    name 'maven1'
                    url '${mavenHttpRepo.uri}'
                }
            }
            dependencies {
                compile 'org.foo:direct1:1.0'
                compile 'org.foo:missing-direct:1.0' // does not exist
                compile project(':child')
            }

            task resolve { doLast { configurations.compile.resolve() } }
            
            project(':child') {
                apply plugin: "java"
                dependencies {
                    compile 'org.foo:broken-transitive:1.0' // throws exception trying to resolve
                }
            }
        """
        settingsFile << "include 'child'"

        when:
        mavenHttpRepo.module('org.foo', 'missing-direct').allowAll()
        mavenHttpRepo.module('org.foo', 'broken-transitive').pom.expectGetBroken()

        and:
        fails 'resolve'

        then:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        def resolvedComponents = op.result.components
        resolvedComponents.size() == 4
        resolvedComponents.'project :'.repoName == null
        resolvedComponents.'project :child'.repoName == null
        resolvedComponents.'org.foo:direct1:1.0'.repoName == 'maven1'
        resolvedComponents.'org.foo:transitive1:1.0'.repoName == 'maven1'
    }

    def "resolved components contain their source repository id, even when they are structurally identical"() {
        setup:
        buildFile << """                
            apply plugin: "java"
            repositories {
                maven { 
                    name 'withoutCreds'
                    url '${mavenHttpRepo.uri}'
                }
                maven { 
                    name 'withCreds'
                    url '${mavenHttpRepo.uri}'
                    credentials {
                        username = 'foo'
                        password = 'bar'
                    }
                }
            }
            dependencies {
                compile 'org.foo:good:1.0'
            }

            task resolve { doLast { configurations.compile.resolve() } }
        """
        def module = mavenHttpRepo.module('org.foo', 'good').publish()
        server.authenticationScheme = AuthScheme.BASIC
        server.allowGetOrHead('/repo/org/foo/good/1.0/good-1.0.pom', 'foo', 'bar', module.pomFile)
        server.allowGetOrHead('/repo/org/foo/good/1.0/good-1.0.jar', 'foo', 'bar', module.artifactFile)

        when:
        succeeds 'resolve'

        then:
        def op = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        def resolvedComponents = op.result.components
        resolvedComponents.size() == 2
        resolvedComponents.'org.foo:good:1.0'.repoName == 'withCreds'

        when:
        server.resetExpectations()
        succeeds 'resolve'

        then:
        // This demonstrates a bug in Gradle, where we ignore the requirement for credentials when retrieving from the cache
        def op2 = operations.first(ResolveConfigurationDependenciesBuildOperationType)
        def resolvedComponents2 = op2.result.components
        resolvedComponents2.size() == 2
        resolvedComponents2.'org.foo:good:1.0'.repoName == 'withoutCreds'
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy