Search code examples
javakotlingradledependencieskotlin-exposed

Kotlin Gradle API dependencies unavailable in consumers


I am building a Kotlin library and several web services which are all consumers of the library. The library defines database models, which the web services can then all commonly access and manipulate.

Here is the build.gradle file for my library:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.4.10'
}

group 'org.bioround'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib"

    api "org.jetbrains.exposed:exposed-core:$exposed_version"
    api "org.jetbrains.exposed:exposed-dao:$exposed_version"
    api "org.jetbrains.exposed:exposed-jdbc:$exposed_version"

    implementation "mysql:mysql-connector-java:$mysqlconnector_version"
    implementation "com.zaxxer:HikariCP:$hikaricp_version"
}

Note how the org.jetbrains.exposed dependencies are declared as api dependencies, since I want to access the methods these dependencies provide in my web services.

Here is the build.gradle file for one of my consumers, where I declare mylibrary as a local dependency:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.4.10'
}

group 'org.bioround'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib"

    implementation files("../mylibrary/build/libs/mylibrary-1.0-SNAPSHOT.jar")
}

However, I am unable to access the methods from org.jetbrains.exposed in my consumer service. I expected the methods to be available since I declared them as api dependencies in my library.

What am I doing wrong here?


Solution

  • File dependencies are not transitive, because .jar files do not store any dependency information. Your consuming project simply cannot know the dependencies of the consumed project.

    There are several approaches that will fix your problem:

    If your projects are strongly coupled (e.g. if they are in the same SCM repository), you can use a multi-project build. This means, all your projects are registered in a central settings.gradle file using include(...). Projects may then depend on other projects using project(...) inside the dependencies block:

    dependencies {
        implementation project(':mylibrary')
    }
    

    If your projects are not directly related, but you still want to develop both of them simultaneously, you can use a composite build. This way your builds for the projects can be designed without the other projects in mind, but still include them to load any changes immediately using includeBuild(...) in your settings.gradle file. The project dependencies can be expressed as module dependencies (defining group, artifact and version), but will be replaced by the included builds automatically.

    dependencies {
        implementation 'com.example:mylibrary:1.0.0'
    }
    

    The last possibility is to actually include the consumed project as an external module. For this to work, the consumed project must be published to a Maven repository on each change, e.g. the local Maven repository. Just apply the Maven Publish Plugin to your consumed project plugins { id 'maven-publish' } and call the task publishToMavenLocal. It can then be consumed using the same dependency notation shown above, as long as the local Maven repository is registered:

    repositories {
        mavenLocal()
    }
    

    The disadvantage of this approach is that the consumed project must be published for each change, whereas using a multi-project or composite build will rebuild your consumed project automatically.