About Gradle's compile, api, implementation, etc.

A study note about compile, ʻapi, ʻimplementation specified by dependencies in Gradle.

compile is deprecated

Java Library Plugin has been added in Gradle 3.4 (https://docs.gradle.org/3.4/release-notes. html # the-java-library-plugin), so it seems that using compile with dependencies was deprecated (also runtime, testCompile, testRuntime).

In 4.7 Description of Java Plugin, it is written as Deprecated. (4.6 documentation doesn't say Deprecated, but it's definitely deprecated. Recently?)

It is recommended to use ʻimplementation and ʻapi instead.

Difference between compile and implementation

For compile

Implementation

Project structure


|-settings.gradle
|-build.gradle
|
|-foo/
|  |-build.gradle
|  `-src/main/java/foo/
|    `-Foo.java
|
`-bar/
   |-build.gradle
   `-src/main/java/bar/
     `-Bar.java

/settings.gradle


include 'foo', 'bar'

/build.gradle


subprojects {
    apply plugin: 'java'

    sourceCompatibility = 10
    targetCompatibility = 10

    compileJava.options.encoding = 'UTF-8'

    repositories {
        mavenCentral()
    }
}

/bar/build.gradle


dependencies {
    compile 'org.apache.commons:commons-lang3:3.7'
}

Bar.java


package bar;

import org.apache.commons.lang3.RandomStringUtils;

public class Bar {

    public void hello() {
        System.out.println("Bar: " + RandomStringUtils.random(10, "0123456789"));
    }
}

/foo/build.gradle


apply plugin: 'application'

mainClassName = 'foo.Foo'

dependencies {
    compile project(':bar')
}

Foo.java


package foo;

import bar.Bar;
import org.apache.commons.lang3.RandomStringUtils;

public class Foo {

    public static void main(String... args) {
        new Bar().hello();
        System.out.println("Foo: " + RandomStringUtils.random(10, "0123456789"));
    }
}

--Consists of two subprojects, foo and bar --bar depends on commons-lang3 --foo depends on the bar project --Each project uses commons-lang3

Execution result

> gradle :foo:run
Bar: 3803159716
Foo: 6423224304

When using implementation

Implementation

/bar/build.gradle


dependencies {
    implementation 'org.apache.commons:commons-lang3:3.7'
}

/foo/build.gradle


apply plugin: 'application'

mainClassName = 'foo.Foo'

dependencies {
    implementation project(':bar')
}

Execution result

> gradle :foo:run

...\foo\src\main\java\foo\Foo.java:4:error:Package org.apache.commons.lang3 does not exist
import org.apache.commons.lang3.RandomStringUtils;
                               ^
...\foo\src\main\java\foo\Foo.java:10:error:Can't find symbol
        System.out.println("Foo: " + RandomStringUtils.random(10, "0123456789"));
                                     ^
symbol:Variable RandomStringUtils
place:Class Foo
2 errors

...

Description

Dependency relationship


# compile
[foo] --compile--> [bar] --compile--> [commons-lang3]

  [foo] - ok -> [bar] - ok -> [commons-lang3]
    |                               ^
    |                               |
    +------------ ok ---------------+

# implementation
[foo] --implementation--> [bar] --implementation--> [commons-lang3]

  [foo] - ok -> [bar] - ok -> [commons-lang3]
    |                               x
    |                               |
    +------------ ng ---------------+

--The dependency specified by compile is propagated --If you specify commons-lang3 in compile in the bar project and specify the bar project as a dependency in the foo project, the foo project also depends on commons-lang3 Will be --The dependency specified by ʻimplementationis not propagated --If you specifycommons-lang3 with ʻimplementation in the bar project, the foo project depends on commons-lang3 even if you specify the bar project as a dependency in the foo project. No (foo project cannot use commons-lang3)

Propagate dependencies

Implementation

/bar/build.gradle


apply plugin: 'java-library'

dependencies {
    api 'org.apache.commons:commons-lang3:3.7'
}

Execution result

$ gradle :foo:run

Bar: 7783742303
Foo: 6741510207

Description

/bar/build.gradle


apply plugin: 'java-library'

dependencies {
    api 'org.apache.commons:commons-lang3:3.7'
}

--To propagate dependencies like compile, specify the dependencies with ʻapi --ʻApi becomes available by adding Java Library Plugin --To use the Java Library Plugin, add the plugin with java-library

merit

The official documentation explains that using ʻimplementation instead of compile` has the following benefits:

  1. Dependencies are not leaked anywhere on the user side at compile time. Therefore, unintended transitive dependencies do not occur.
  2. The removed classpath makes compilation faster.
  3. Even if the dependency specified in ʻimplementation` is changed, the user does not need to recompile.
  4. When used in combination with the new Maven plugin, it will generate a POM file that clearly separates the libraries required at compile time and the libraries required at runtime, and can be published neatly (honestly, the Maven plugin). I'm not using it so I'm not sure)

In short, using compile had the following problems.

--Since all the dependencies are transitively propagated, the dependencies have expanded unnecessarily. --It has spread to dependencies that you don't want to leak to the outside, such as internal APIs.

By defining this with ʻimplementation, it is possible to prevent unnecessary expansion of dependencies and to transition only the dependencies that are really necessary with ʻapi.

It also has the advantage that the frequency of recompilation can be reduced by preventing ʻimplementation` from propagating dependencies.

Proper use of ʻimplementation and ʻapi

The following is my personal opinion.

--Basically declared with ʻimplementation --Publish to the user side with ʻapi only when it is absolutely necessary to propagate it to the user side --If possible, it would be best if you could eliminate the direct dependence on the library you are using by wrapping it in your own API. --Easy to replace the library --By limiting the usage with your own API, you can reduce unintended usage and incorrect usage. ――However, if wrapping with your own API is difficult and cost-effective, I think it may be possible to publish it with ʻapi`.

Summary

configuration Dependency propagation Defined plugin
compile To do Java Plugin
implementation do not do Java Plugin
api To do Java Library Plugin

reference

Recommended Posts

About Gradle's compile, api, implementation, etc.
Chew about API
About Apache Inference API
About merge processing implementation including sorting function of Stream API
About Apache Jena Ontology API