如何在 Docker 图像中填充 Gradle 缓存?

How to populate Gradle cache in a Docker image?

我正在尝试构建 Docker 图像以开发 Android 应用程序。我有一个安装 JDK、Android SDK + NDK 的映像。

当我运行图像和运行gradle包装器(gradlew)时,包装器下载最新的Gradle,然后所有我的应用程序的依赖项,所以我看到如下消息:

Download https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.1.4/gradle-3.1.4.pom
Download https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/3.1.4/builder-3.1.4.pom
...
Download https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-stdlib-jre8/1.2.0/kotlin-stdlib-jre8-1.2.0.pom
Download https://jcenter.bintray.com/com/squareup/javawriter/2.5.0/javawriter-2.5.0.pom
...
Download https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.1.4/gradle-3.1.4.jar
Download https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-core/3.1.4/gradle-core-3.1.4.jar

等等

由于 Docker 容器的性质,每次我 运行 一个新容器时,它都必须重新下载所有这些依赖项,就像第一次一样。 (我可以重复使用一个容器,但我希望每个使用此图像的人都不必下载这些文件;并且出于其他原因每次都需要使用一个新容器。)所以我想得到它们 "baked into" Docker 图片。最好的方法是什么?

一个想法是向 Docker 文件添加指令以复制我项目的源代码并进行构建(然后从映像中删除项目文件)。然而,这似乎很棘手(我们应该忽略未被 .git 跟踪的文件)并且很浪费(为什么要进行整个构建;我只想下载这些文件并将其放入适当的位置)。

另一种选择是进行构建,从构建输出中获取所有这些 URL,弄清楚它们应该放在磁盘上的什么位置(不知道该怎么做),然后在 Docker 文件以手动将它们复制到位。但这似乎需要很多工作,并且在我的应用程序依赖项发生变化时需要手动更新。

似乎应该有一种方法可以复制 gradle 包装器和一组最小的 gradle 构建文件并将其请求给 "just restore dependencies",但我没有能够找到这样的目标。有办法吗?

您可以使用 Artifactory.运行 您自己的 Maven 工件本地缓存。

我通过创建 build.gradle 脚本的精简版、破解版来缓存下载内容,并使用它来触发我的 Dockerfile 中的下载。所以我的 Dockerfile 目录还包含 gradle 包装器(gradlew 脚本和关联的 gradle 目录,里面有 .jar),我的最小 build.gradle 文件,还有满足 android 插件所需的最小虚拟 AndroidManifest.xml 文件。

(这在我看来并不理想,所以我仍然很想听听是否有人有更好的方法。)

从我的 Dockerfile 中,我像这样触发下载:

# The following steps use some dummy build files to trigger gradle to download depencies,
# so that they will be available in the image.
COPY . /tmp/triggerGradleDownloads/

RUN     cd /tmp/triggerGradleDownloads
    && ./gradlew --no-daemon --refresh-dependencies androidDependencies lint
    && rm -rf /tmp/triggerGradleDownloads

./src/main/AndroidManifest.xml的内容:

<?xml version="1.0" encoding="utf-8"?>
<!-- we just need a package name in this file to satisfy the android plugin -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.dummy">
</manifest>

我的 build.gradle 文件的内容:

// This is a cut-down and hacked-together build script used only to trigger download of dependencies.
// From your Dockerfile, run "./gradlew --no-daemon --refresh-dependencies androidDependencies lint".

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 28
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:28.0.0-rc02'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}