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

org.gradle.java.JavaLibraryFeatureCompilationIntegrationTest.groovy Maven / Gradle / Ivy

There is a newer version: 8.11.1
Show newest version
/*
 * Copyright 2019 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.java


import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import spock.lang.Issue

class JavaLibraryFeatureCompilationIntegrationTest extends AbstractIntegrationSpec {

    private toggleCompileClasspathPackaging(boolean activate) {
        if (activate) {
            propertiesFile << """
                systemProp.org.gradle.java.compile-classpath-packaging=true
            """.trim()
        }
    }

    def "project can declare and compile feature [configuration=#configuration][compileClasspathPackaging=#compileClasspathPackaging]"() {
        toggleCompileClasspathPackaging(compileClasspathPackaging)
        settingsFile << """
            include 'b'
        """
        given:
        buildFile << """
            apply plugin: 'java-library'

            sourceSets {
                myFeature
            }

            java {
                registerFeature("myFeature") {
                    usingSourceSet(sourceSets.myFeature)
                }
            }

            dependencies {
                $configuration project(":b")
            }
        """
        file("b/build.gradle") << """
            apply plugin: 'java-library'
        """
        file("b/src/main/java/com/foo/Foo.java") << """
            package com.foo;
            public class Foo {
                public void foo() {
                }
            }
        """
        file("src/myFeature/java/com/bar/Bar.java") << """
            package com.bar;
            import com.foo.Foo;

            public class Bar {
                public void bar() {
                    Foo foo = new Foo();
                    foo.foo();
                }
            }
        """

        when:
        succeeds ':compileMyFeatureJava'

        then:
        executedAndNotSkipped ':b:compileJava'
        packagingTasks(compileClasspathPackaging, 'b')

        where:
        compileClasspathPackaging | configuration
        false                     | "myFeatureApi"
        true                      | "myFeatureApi"
        false                     | "myFeatureImplementation"
        true                      | "myFeatureImplementation"
    }

    def "Java Library can depend on feature of component [compileClasspathPackaging=#compileClasspathPackaging]"() {
        toggleCompileClasspathPackaging(compileClasspathPackaging)
        settingsFile << """
            include 'b', 'c', 'd', 'e', 'f', 'g'
        """
        given:
        file("b/build.gradle") << """
            apply plugin: 'java-library'

            group = 'org.gradle.test'

            sourceSets {
                myFeature
            }

            java {
                registerFeature("myFeature") {
                    usingSourceSet(sourceSets.myFeature)
                }
            }

            dependencies {
                myFeatureApi project(":c")
                myFeatureImplementation project(":d")
                myFeatureCompileOnlyApi project(":e")
                myFeatureCompileOnly project(":f")
                myFeatureRuntimeOnly project(":g")
            }

        """
        buildFile << """
            apply plugin: 'java-library'

            dependencies {
                implementation(project(":b")) {
                    capabilities {
                        requireCapability("org.gradle.test:b-my-feature")
                    }
                }
            }

            task verifyClasspath {
                dependsOn(configurations.compileClasspath)
                dependsOn(configurations.runtimeClasspath)
                def incomingCompileClasspath = provider {
                    configurations.compileClasspath.incoming.resolutionResult.allDependencies.collect {
                        it.toString()
                    } as Set
                }
                def incomingRuntimeClasspath = provider {
                    configurations.runtimeClasspath.incoming.resolutionResult.allDependencies.collect {
                        it.toString()
                    } as Set
                }
                doLast {
                    assert incomingCompileClasspath.get() == ['project :b', 'project :c', 'project :e'] as Set // only API dependencies
                    assert incomingRuntimeClasspath.get() == ['project :b', 'project :c', 'project :d', 'project :g'] as Set // all dependencies (except compile only)
                }
            }
        """
        ['c', 'd', 'e', 'f', 'g'].each {
            file("$it/build.gradle") << """
            apply plugin: 'java-library'
        """
            file("$it/src/main/java/com/baz/Baz${it}.java") << """
            package com.baz;
            public class Baz${it} {}
        """
        }

        file("b/src/myFeature/java/com/foo/Foo.java") << """
            package com.foo;
            public class Foo {
                public void foo() {
                }
            }
        """
        file("src/main/java/com/bar/Bar.java") << """
            package com.bar;
            import com.foo.Foo;

            public class Bar {
                public void bar() {
                    Foo foo = new Foo();
                    foo.foo();
                }
            }
        """

        when:
        succeeds ':compileJava'

        then:
        executedAndNotSkipped ':b:compileMyFeatureJava', ':c:compileJava', ':d:compileJava', ':e:compileJava', ':f:compileJava'
        packagingTasks(compileClasspathPackaging, 'b', 'myFeature')
        packagingTasks(compileClasspathPackaging, 'c')
        packagingTasks(compileClasspathPackaging, 'd')
        packagingTasks(compileClasspathPackaging, 'e')
        packagingTasks(compileClasspathPackaging, 'f')

        when:
        succeeds 'clean', ':verifyClasspath'

        then:
        executedAndNotSkipped ':b:myFeatureJar', ':c:jar', ':d:jar', ':g:jar' // runtime
        packagingTasks(compileClasspathPackaging, 'e') // compile time only
        packagingTasks(compileClasspathPackaging, 'f') // compile time only

        where:
        compileClasspathPackaging | _
        false                     | _
        true                      | _

    }

    def "main component doesn't expose dependencies from feature [compileClasspathPackaging=#compileClasspathPackaging]"() {
        toggleCompileClasspathPackaging(compileClasspathPackaging)
        settingsFile << """
            include 'b', 'c'
        """
        given:
        file("b/build.gradle") << """
            apply plugin: 'java-library'

            sourceSets {
                myFeature
            }

            java {
                registerFeature("myFeature") {
                    usingSourceSet(sourceSets.myFeature)
                }
            }

            dependencies {
                myFeatureImplementation project(":c")
            }

        """
        buildFile << """
            apply plugin: 'java-library'

            dependencies {
                implementation(project(":b"))
            }

            task resolveRuntime {
                def runtimeClasspath = configurations.runtimeClasspath
                dependsOn(runtimeClasspath)
                doLast {
                    assert runtimeClasspath.files.name as Set == ['b.jar'] as Set
                }
            }
        """
        file("c/build.gradle") << """
            apply plugin: 'java-library'
        """
        file("c/src/main/java/com/baz/Baz.java") << """
            package com.baz;
            public class Baz {}
        """
        file("b/src/main/java/com/foo/Foo.java") << """
            package com.foo;
            public class Foo {
                public void foo() {
                }
            }
        """
        file("src/myFeature/java/com/bar/Bar.java") << """
            package com.bar;
            import com.foo.Foo;

            public class Bar {
                public void bar() {
                    Foo foo = new Foo();
                    foo.foo();
                }
            }
        """

        when:
        succeeds ':compileJava'

        then:
        executedAndNotSkipped ':b:compileJava'
        packagingTasks(compileClasspathPackaging, 'b')

        when:
        succeeds 'clean', ':resolveRuntime'

        then:
        executedAndNotSkipped ':b:jar'

        where:
        compileClasspathPackaging | _
        false                     | _
        true                      | _
    }

    def "can build a feature that uses its own source directory [compileClasspathPackaging=#compileClasspathPackaging]"() {
        toggleCompileClasspathPackaging(compileClasspathPackaging)
        settingsFile << """
            include 'b'
        """
        given:
        buildFile << """
            apply plugin: 'java-library'

            sourceSets {
                myFeature {
                    java {
                        srcDir "src/myFeature/java"
                    }
                }
            }

            java {
                registerFeature("myFeature") {
                    usingSourceSet(sourceSets.myFeature)
                }
            }

            dependencies {
                $configuration project(":b")
            }
        """
        file("b/build.gradle") << """
            apply plugin: 'java-library'
        """
        file("b/src/main/java/com/foo/Foo.java") << """
            package com.foo;
            public class Foo {
                public void foo() {
                }
            }
        """
        file("src/myFeature/java/com/bar/Bar.java") << """
            package com.bar;
            import com.foo.Foo;

            public class Bar {
                public void bar() {
                    Foo foo = new Foo();
                    foo.foo();
                }
            }
        """

        when:
        succeeds ':compileMyFeatureJava'

        then:
        executedAndNotSkipped ':b:compileJava'
        packagingTasks(compileClasspathPackaging, 'b')
        notExecuted ':compileJava'

        where:
        compileClasspathPackaging | configuration
        false                     | "myFeatureApi"
        true                      | "myFeatureApi"
        false                     | "myFeatureImplementation"
        true                      | "myFeatureImplementation"
    }

    def "Java Library can depend on feature of component which uses its own source set [compileClasspathPackaging=#compileClasspathPackaging]"() {
        toggleCompileClasspathPackaging(compileClasspathPackaging)
        settingsFile << """
            include 'b', 'c', 'd'
            rootProject.name = 'test'
        """
        given:
        file("b/build.gradle") << """
            apply plugin: 'java-library'

            group = 'org.gradle.test'

            sourceSets {
                myFeature {
                    java {
                        srcDir "src/myFeature/java"
                    }
                }
            }

            java {
                registerFeature("myFeature") {
                    usingSourceSet(sourceSets.myFeature)
                }
            }

            dependencies {
                myFeatureApi project(":c")
                myFeatureImplementation project(":d")
            }

        """
        buildFile << """
            apply plugin: 'java-library'

            dependencies {
                implementation(project(":b")) {
                    capabilities {
                        requireCapability("org.gradle.test:b-my-feature")
                    }
                }
            }

            task verifyClasspath {
                dependsOn(configurations.compileClasspath)
                dependsOn(configurations.runtimeClasspath)
                def incomingCompileClasspath = provider {
                    configurations.compileClasspath.incoming.resolutionResult.allDependencies.collect {
                        it.toString()
                    } as Set
                }
                def runtimeClasspath = configurations.runtimeClasspath
                def incomingRuntimeClasspath = provider {
                    runtimeClasspath.incoming.resolutionResult.allDependencies.collect {
                        it.toString()
                    } as Set
                }
                doLast {
                    assert incomingCompileClasspath.get() == ['project :b', 'project :c'] as Set // only API dependencies
                    assert incomingRuntimeClasspath.get() == ['project :b', 'project :c', 'project :d'] as Set // all dependencies
                    assert runtimeClasspath.files.name as Set == ['b-my-feature.jar', 'c.jar', 'd.jar'] as Set
                }
            }
        """
        ['c', 'd'].each {
            file("$it/build.gradle") << """
                apply plugin: 'java-library'
            """

            file("$it/src/main/java/com/baz/Baz${it}.java") << """
                package com.baz;
                public class Baz${it} {}
            """
        }

        file("b/src/myFeature/java/com/foo/Foo.java") << """
            package com.foo;
            public class Foo {
                public void foo() {
                }
            }
        """
        file("src/main/java/com/bar/Bar.java") << """
            package com.bar;
            import com.foo.Foo;

            public class Bar {
                public void bar() {
                    Foo foo = new Foo();
                    foo.foo();
                }
            }
        """

        when:
        succeeds ':compileJava'

        then:
        executedAndNotSkipped ':b:compileMyFeatureJava', ':c:compileJava', ':d:compileJava'
        packagingTasks(compileClasspathPackaging, 'b', 'myFeature')
        packagingTasks(compileClasspathPackaging, 'c')
        packagingTasks(compileClasspathPackaging, 'd')

        when:
        succeeds 'clean', ':verifyClasspath'

        then:
        executedAndNotSkipped ':b:myFeatureJar', ':c:jar', ':d:jar'
        notExecuted ':b:jar' // main jar should NOT be built in this case

        where:
        compileClasspathPackaging | _
        false                     | _
        true                      | _
    }

    @Issue("gradle/gradle#10778")
    def "dependencies of a feature that uses the main source set are available on test compile classpath"() {
        buildFile << """
            apply plugin: 'java-library'

            ${mavenCentralRepository()}

            java {
                registerFeature('feat') {
                   usingSourceSet(sourceSets.main)
                }
            }

            dependencies {
                testImplementation "junit:junit:4.13"
                featApi "org.apache.commons:commons-math3:3.6.1"
            }
        """
        file("src/test/java/com/acme/FeatureTest.java") << """package com.acme;
            import org.apache.commons.math3.complex.Complex;
            import org.junit.Test;
            import static org.junit.Assert.*;

            public class FeatureTest {
                @Test
                public void shouldCompileAndRun() {
                    Complex complex = new Complex(2.0, 1);
                    assertEquals(3, complex.pow(2.0).getReal(), 1e-5);
                }
            }
        """

        when:
        executer.expectDocumentedDeprecationWarning("The 'feat' feature was created using the main source set. This behavior has been deprecated. This will fail with an error in Gradle 9.0. The main source set is reserved for production code and should not be used for features. Use another source set instead. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#deprecate_register_feature_main_source_set")
        run 'test'

        then:
        executedAndNotSkipped ':compileTestJava', ':test'
    }

    @Issue("gradle/gradle#10999")
    def "registerFeature can be used when there is no main SourceSet"() {
        given:
        buildFile << """
            apply plugin: 'java-base'

            sourceSets {
               main211 {}
               main212 {}
            }
            java {
               registerFeature('scala211') {
                  usingSourceSet(sourceSets.main211)
               }
               registerFeature('scala212') {
                  usingSourceSet(sourceSets.main212)
               }
            }
        """
        file("src/main211/java/com/foo/Foo.java") << """
            package com.foo;
            public class Foo {
                public void foo() {
                }
            }
        """
        file("src/main212/java/com/bar/Bar.java") << """
            package com.bar;

            public class Bar {
                public void bar() {
                }
            }
        """

        when:
        succeeds ':compileMain211Java', ':compileMain212Java'

        then:
        executedAndNotSkipped ':compileMain211Java', ':compileMain212Java'
    }

    def "creates main feature with main source set when java plugin not applied" () {
        given:
        buildFile << """
            plugins {
                id('java-base')
            }

            sourceSets {
               main
            }

            configurations {
                testCompileClasspath
                testRuntimeClasspath
            }

            java {
               registerFeature('main') {
                  usingSourceSet(sourceSets.main)
               }
            }
        """

        when:
        executer.expectDocumentedDeprecationWarning("The 'main' feature was created using the main source set. This behavior has been deprecated. This will fail with an error in Gradle 9.0. The main source set is reserved for production code and should not be used for features. Use another source set instead. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#deprecate_register_feature_main_source_set")
        succeeds 'dependencies'

        then:
        outputContains("mainRuntimeOnly")
        outputContains("mainCompileOnly")
        outputContains("mainImplementation")
        outputContains("mainApi")
        outputContains("mainCompileOnlyApi")
        outputContains("mainRuntimeElements")
        outputContains("mainApiElements")
    }

    def "creates configurations when using main source set, non-main feature, java-library is not applied" () {
        given:
        buildFile << """
            plugins {
                id('java-base')
            }

            sourceSets {
               main
            }

            configurations {
                testCompileClasspath
                testRuntimeClasspath
            }

            java {
               registerFeature('feature') {
                  usingSourceSet(sourceSets.main)
               }
            }
        """

        when:
        executer.expectDocumentedDeprecationWarning("The 'feature' feature was created using the main source set. This behavior has been deprecated. This will fail with an error in Gradle 9.0. The main source set is reserved for production code and should not be used for features. Use another source set instead. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#deprecate_register_feature_main_source_set")
        succeeds 'dependencies'

        then:
        outputContains("featureRuntimeOnly")
        outputContains("featureCompileOnly")
        outputContains("featureImplementation")
        outputContains("featureApi")
        outputContains("featureCompileOnlyApi")
        outputContains("featureRuntimeElements")
        outputContains("featureApiElements")
    }

    def "creates configurations when using main source set and main feature name"() {
        buildFile << """
            plugins {
                id('java-library')
            }

            java {
                registerFeature('main') {
                   usingSourceSet(sourceSets.main)
                }
            }
        """

        when:
        executer.expectDocumentedDeprecationWarning("The 'main' feature was created using the main source set. This behavior has been deprecated. This will fail with an error in Gradle 9.0. The main source set is reserved for production code and should not be used for features. Use another source set instead. Consult the upgrading guide for further information: https://docs.gradle.org/current/userguide/upgrading_version_8.html#deprecate_register_feature_main_source_set")
        run 'dependencies'

        then:
        outputContains("mainRuntimeOnly")
        outputContains("mainCompileOnly")
        outputContains("mainImplementation")
        outputContains("mainApi")
        outputContains("mainCompileOnlyApi")
        outputContains("mainRuntimeElements")
        outputContains("mainApiElements")
    }

    def "elements configurations have the correct roles"() {
        given:
        buildFile << """
            plugins {
                id 'java-library'
            }

            sourceSets {
                sources
            }

            java {
                registerFeature("example") {
                    usingSourceSet(sourceSets.sources)
                }
            }

            task verifyConfigurations {
                def apiElements = configurations.sourcesApiElements
                def runtimeElements = configurations.sourcesRuntimeElements

                [apiElements, runtimeElements].each {
                    assert it.canBeConsumed == true
                    assert it.canBeResolved == false
                    assert it.canBeDeclared == true

                    assert it.deprecatedForDeclarationAgainst == true
                    assert it.deprecatedForResolution == false
                    assert it.deprecatedForConsumption == false
                }
            }
        """

        expect:
        succeeds("verifyConfigurations")
    }

    private void packagingTasks(boolean expectExecuted, String subproject, String feature = '') {
        def tasks = [":$subproject:process${feature.capitalize()}Resources", ":$subproject:${feature.isEmpty() ? 'classes' : feature + 'Classes'}", ":$subproject:${feature.isEmpty() ? 'jar' : feature + 'Jar'}"]
        if (expectExecuted) {
            executed(*tasks)
        } else {
            notExecuted(*tasks)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy