在使用Gradle打包成一个可运行的Jar包的时候,需要把编译时依赖的库也打包进去,因此要搞清楚打包时如何才能将库文件打包进去。

对于implementation引入的库,则需要如下的语句:

from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }

对于testImplementation引入的库,则需要如下的语句:

from {
        configurations.testRuntimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }

我们只需要打包运行时的依赖包即可,则完整build.gradle如下:

plugins {
    id 'java'
    id 'org.jetbrains.kotlin.jvm' version '1.3.21'
}

group 'com.zhuyanbin'
version '1.0.0'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

jar {
    manifest {
        attributes 'Implementation-Title' : 'DropBox-Backup-Service'
        attributes 'Manifest-Version': '1.0.0'
        attributes 'Main-Class': 'com.zhuyanbin.dropbox.AppKt'
    }

    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation 'com.dropbox.core:dropbox-core-sdk:3.0.10'
    implementation 'commons-configuration:commons-configuration:1.9'
    testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version:'1.4.0'
    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version:'5.4.0'
    testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version:'5.4.0'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

The following graph describes the main configurations setup when the Java Library plugin is in use.

  • The configurations in green are the ones a user should use to declare dependencies
  • The configurations in pink are the ones used when a component compiles, or runs against the library
  • The configurations in blue are internal to the component, for its own use
  • The configurations in white are configurations inherited from the Java plugin

And the next graph describes the test configurations setup:

The compiletestCompileruntime and testRuntime configurations inherited from the Java plugin are still available but are deprecated. You should avoid using them, as they are only kept for backwards compatibility.

The role of each configuration is described in the following tables:

Table 1. Java Library plugin – configurations used to declare dependencies

Configuration nameRoleConsumable?Resolvable?Description
apiDeclaring API dependenciesnonoThis is where you should declare dependencies which are transitively exported to consumers, for compile.
implementationDeclaring implementation dependenciesnonoThis is where you should declare dependencies which are purely internal and not meant to be exposed to consumers.
compileOnlyDeclaring compile only dependenciesyesyesThis is where you should declare dependencies which are only required at compile time, but should not leak into the runtime. This typically includes dependencies which are shaded when found at runtime.
runtimeOnlyDeclaring runtime dependenciesnonoThis is where you should declare dependencies which are only required at runtime, and not at compile time.
testImplementationTest dependenciesnonoThis is where you should declare dependencies which are used to compile tests.
testCompileOnlyDeclaring test compile only dependenciesyesyesThis is where you should declare dependencies which are only required at test compile time, but should not leak into the runtime. This typically includes dependencies which are shaded when found at runtime.
testRuntimeOnlyDeclaring test runtime dependenciesnonoThis is where you should declare dependencies which are only required at test runtime, and not at test compile time.

Table 2. Java Library plugin — configurations used by consumers

Configuration nameRoleConsumable?Resolvable?Description
apiElementsFor compiling against this libraryyesnoThis configuration is meant to be used by consumers, to retrieve all the elements necessary to compile against this library. Unlike the default configuration, this doesn’t leak implementation or runtime dependencies.
runtimeElementsFor executing this libraryyesnoThis configuration is meant to be used by consumers, to retrieve all the elements necessary to run against this library.

Table 3. Java Library plugin – configurations used by the library itself

Configuration nameRoleConsumable?Resolvable?Description
compileClasspathFor compiling this librarynoyesThis configuration contains the compile classpath of this library, and is therefore used when invoking the java compiler to compile it.
runtimeClasspathFor executing this librarynoyesThis configuration contains the runtime classpath of this library
testCompileClasspathFor compiling the tests of this librarynoyesThis configuration contains the test compile classpath of this library.
testRuntimeClasspathFor executing tests of this librarynoyesThis configuration contains the test runtime classpath of this library

Link: https://docs.gradle.org/current/userguide/java_library_plugin.html

The key difference between the standard Java plugin and the Java Library plugin is that the latter introduces the concept of an APIexposed to consumers. A library is a Java component meant to be consumed by other components. It’s a very common use case in multi-project builds, but also as soon as you have external dependencies.

The plugin exposes two configurations that can be used to declare dependencies: api and implementation. The apiconfiguration should be used to declare dependencies which are exported by the library API, whereas the implementationconfiguration should be used to declare dependencies which are internal to the component.

Example 2. Declaring API and implementation dependencies

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).

If your build consumes a published module with POM metadata, the Java and Java Library plugins both honor api and implementation separation through the scopes used in the pom. Meaning that the compile classpath only includes compilescoped dependencies, while the runtime classpath adds the runtime scoped dependencies as well.

This often does not have an effect on modules published with Maven, where the POM that defines the project is directly published as metadata. There, the compile scope includes both dependencies that were required to compile the project (i.e. implementation dependencies) and dependencies required to compile against the published library (i.e. API dependencies). For most published libraries, this means that all dependencies belong to the compile scope. However, as mentioned above, if the library is published with Gradle, the produced POM file only puts api dependencies into the compile scope and the remaining implementation dependencies into the runtime scope.

link: https://docs.gradle.org/current/userguide/java_library_plugin.html

使用Docker编译Kotlin时,遇到了如下报错:

FAILURE: Build failed with an exception.

* What went wrong:
Could not create service of type ScriptPluginFactory using BuildScopeServices.createScriptPluginFactory().
> Could not create service of type CrossBuildFileHashCache using BuildSessionScopeServices.createCrossBuildFileHashCache().

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 2s

我的Dockerfile如下:

FROM gradle:5.2.1-jdk8 as builder
COPY ./ /data/dropbox/
WORKDIR /data/dropbox
RUN gradle build --no-daemon

经过排查,发现是权限问题,无法启动gradle进行编译,因此加入如下一句:

USER root

即可成功通过。完整Dockerfile如下:

FROM gradle:5.2.1-jdk8 as builder
COPY ./ /data/dropbox/
USER root
WORKDIR /data/dropbox
RUN gradle build --no-daemon