1

I've got a Gradle multi-project build for a custom Spring Boot Starter. Following Spring Boot Starter convention, my classes are all in an 'autoconfiguration' project, and I have a separate 'starter' project that ONLY brings in the dependencies needed to use the autoconfiguration.

The multi-project build produces 2 non-runnable jars, 1 for the autoconfiguration sub-project, and one for the starter sub-project. My new project that is using this starter pulls in the starter jar, but when I go to use classes that are from transitive dependencies, my project can't find them anywhere on the classpath.

When I dug into the starter jar, I found that all the dependencies it defines are RUNTIME scoped, which would explain the problem. I can 'fix' the problem by setting all the dependencies in my starter as 'compile' instead of 'implementation', but it's my understanding that 'compile' is on its way out, and that 'implementation' dependencies should be compile-scoped anyway. Can someone tell me what additional config may be necessary to define the starter dependencies as 'implementation' without them being scoped as 'runtime' in the resulting jar?

My starter/autoconfigure multi-project root gradle file:

plugins {
    id 'org.springframework.boot' version '2.1.4.RELEASE' apply false
    id 'io.spring.dependency-management' version '1.0.7.RELEASE' apply false
}

wrapper {
    gradleVersion = '5.2.1'
}

repositories {
    mavenLocal()
    // private repo info omitted
    mavenCentral()
    jcenter()
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'maven-publish'
    apply plugin: 'java-library'

    group = 'com.mystarter'

    repositories {
        mavenLocal()
        // private repo info omitted
        mavenCentral()
        jcenter()
    }

    dependencies {
        annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
        annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
    }

    bootJar {
        enabled = false
    }

    jar {
        enabled = true
    }

    javadoc {
        failOnError = false
        options.addStringOption('Xdoclint:none', '-quiet')
    }

    task sourcesJar(type: Jar) {
        from sourceSets.main.allJava
        classifier = 'sources'
    }
    task javadocJar(type: Jar) {
        from javadoc
        classifier = 'javadoc'
    }

    publishing {
        publications {
            myProjStarterArtifacts(MavenPublication) {
                from components.java
                artifact sourcesJar
                artifact javadocJar
            }
        }
        repositories {
            // private repo info omitted
        }
    }

    tasks.build.finalizedBy tasks.publishToMavenLocal
}

My starter sub-project's build file:

dependencies {
    compile project(':myproj-spring-boot-autoconfig')

    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.security:spring-security-cas'
    implementation 'org.springframework.security:spring-security-ldap'
}

If I change the above 'implementation' lines to be 'compile' lines, that's when the resulting pom file stops making these 4 dependencies 'runtime' scope and instead correctly scopes them as 'compile'. As a side note, the 'compile project' line works just fine, it's just the lines that are 'implementation' that don't seem to work the way I'd expect when the project has no classes of its own.

My new project's dependency for my starter:

dependencies {
    implementation('com.myproj:myproj-spring-boot-starter:1.0.0')
    // other dependencies
}
AForsberg
  • 1,074
  • 2
  • 13
  • 25

1 Answers1

4

implementation dependencies defined in a Gradle project are only made transitive for the runtimeClasspath of the consumers of said project, that is by design.

If you have a project without code but only defining dependencies, consider using the java-platform plugin for it, which allows you to specify constraints and optionally dependencies.

Otherwise, if you want the project to expose its dependencies to consumers at compilation time, you should use the api configuration for declaring them instead of compile which is indeed on its way out.

For more details, see documentation.

Louis Jacomet
  • 13,661
  • 2
  • 34
  • 43