Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Gradle include transitive dependencies in compile / runtime classpath?

I'm learning how Gradle works, and I can't understand how it resolves a project transitive dependencies.

For now, I have two projects :

  • projectA : which has a couple of dependencies on external libraries
  • projectB : which has only one dependency on projectA

No matter how I try, when I build projectB, gradle doesn't include any projectA dependencies (X and Y) in projectB's compile or runtime classpath. I've only managed to make it work by including projectA's dependencies in projectB's build script, which, in my opinion does not make any sense. These dependencies should be automatically attached to projectB. I'm pretty sure I'm missing something but I can't figure out what.

I've read about "lib dependencies", but it seems to apply only to local projects like described here, not on external dependencies.

Here is the build.gradle I use in the root project (the one that contains both projectA and projectB) :

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.3'
    }
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'idea'

    group = 'com.company'

    repositories {
        mavenCentral()
        add(new org.apache.ivy.plugins.resolver.SshResolver()) {
            name = 'customRepo'
            addIvyPattern "ssh://.../repository/[organization]/[module]/[revision]/[module].xml"
            addArtifactPattern "ssh://.../[organization]/[module]/[revision]/[module](-[classifier]).[ext]"
        }
    }

    sourceSets {
        main {
            java {
                srcDir 'src/'
            }
        }
    }

    idea.module { downloadSources = true }

    // task that create sources jar
    task sourceJar(type: Jar) {
        from sourceSets.main.java
        classifier 'sources'
    }

    // Publishing configuration
    uploadArchives {
        repositories {
            add project.repositories.customRepo
        }
    }

    artifacts {
        archives(sourceJar) {
            name "$name-sources"
            type 'source'
            builtBy sourceJar
        }
    }
}

This one concerns projectA only :

version = '1.0'
dependencies {
    compile 'com.company:X:1.0'
    compile 'com.company:B:1.0'
}

And this is the one used by projectB :

version = '1.0'
dependencies {
    compile ('com.company:projectA:1.0') {
        transitive = true
    }
}

Thank you in advance for any help, and please, apologize me for my bad English.

like image 230
Francis Toth Avatar asked Jun 23 '13 22:06

Francis Toth


People also ask

Does Gradle handle transitive dependencies?

Transitive dependencyBy default, Gradle resolves transitive dependencies automatically. The version selection for transitive dependencies can be influenced by declaring dependency constraints.

What is compile classpath in Gradle?

A configuration is simply a named set of dependencies. The compile configuration is created by the Java plugin. The classpath configuration is commonly seen in the buildSrc {} block where one needs to declare dependencies for the build. gradle, itself (for plugins, perhaps).

How do you avoid transitive dependencies in Gradle?

When you specify a dependency in your build script, you can provide an exclude rule at the same time telling Gradle not to pull in the specified transitive dependency. For example, say we have a Gradle project that depends on Google's Guava library, or more specifically com.


1 Answers

I know that this specific version of the question has already been solved, but my searching brought me here and I hope I can save some people the hassle of figuring this out.

Bad foo/build.gradle

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

Good foo/build.gradle

dependencies {
    api 'com.example:widget:1.0.0'
}

bar/build.gradle

dependencies {
    implementation project(path: ':foo')
}

implementation hides the widget dependency.

api makes the widget dependency transitive.


From https://stackoverflow.com/a/44493379/68086:

From the Gradle documentation:

dependencies {
    api 'commons-httpclient:commons-httpclient:3.1'
    implementation 'org.apache.commons:commons-lang3:3.5'
}

Dependencies appearing in the api configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers.

Dependencies found in the implementation configuration will, on the other hand, not be exposed to consumers, and therefore not leak into the consumers' compile classpath. This comes with several benefits:

  • dependencies do not leak into the compile classpath of consumers anymore, so you will never accidentally depend on a transitive dependency
  • faster compilation thanks to reduced classpath size
  • less recompilations when implementation dependencies change: consumers would not need to be recompiled
  • cleaner publishing: when used in conjunction with the new maven-publish plugin, Java libraries produce POM files that distinguish exactly between what is required to compile against the library and what is required to use the library at runtime (in other words, don't mix what is needed to compile the library itself and what is needed to compile against the library).

The compile configuration still exists, but should not be used as it will not offer the guarantees that the api and implementation configurations provide.

like image 182
Andrew Keeton Avatar answered Oct 14 '22 10:10

Andrew Keeton