Finding Unused Gradle Dependencies – 寻找未使用的Gradle依赖项

最后修改: 2020年 6月 28日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Sometimes during development, we might end up adding more dependencies than we use.

有时在开发过程中,我们可能最终会添加比我们使用的更多的依赖。

In this quick tutorial, we’re going to see how to use the Gradle Nebula Lint plugin to identify and fix problems like these.

在这个快速教程中,我们将看到如何使用Gradle Nebula Lint插件来识别和修复这样的问题。

2. Setup and Configuration

2.设置和配置

We’re using a multi-module Gradle 5 setup in our examples.

在我们的例子中,我们使用了一个多模块的Gradle 5设置。

This plugin only works for Groovy-based build files.

该插件仅适用于基于Groovy的构建文件。

Let’s configure it in the root project build file:

让我们在根项目构建文件中配置它。

plugins {
    id "nebula.lint" version "16.9.0"
}

description = "Gradle 5 root project"

allprojects {
    apply plugin :"java"
    apply plugin :"nebula.lint"
    gradleLint {
        rules=['unused-dependency']
    }
    group = "com.baeldung"
    version = "0.0.1"
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"

    repositories {
        jcenter()
    }
}

We can only configure it this way for multi-project builds for the time being. This means we can’t apply it separately in each module.

我们暂时只能对多项目构建进行这样的配置。这意味着我们不能在每个模块中单独应用它。

Next, let’s configure our module dependencies:

接下来,让我们来配置我们的模块依赖性。

description = "Gradle Unused Dependencies example"

dependencies {
    implementation('com.google.guava:guava:29.0-jre')
    testImplementation('junit:junit:4.12')
}

Now let’s add a simple main class in our module sources:

现在让我们在我们的模块源中添加一个简单的主类。

public class UnusedDependencies {

    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

We’ll build on this a bit later and see how the plugin works.

我们稍后将在此基础上,看看该插件如何工作。

3. Detection Scenarios and Reports

3.探测情况和报告

The plugin searches the output jars to detect whether a dependency is used or not.

该插件搜索输出的罐子,以检测是否使用了一个依赖关系。

However, depending on several conditions, it can give us different results.

然而,取决于几个条件,它可以给我们不同的结果

We’ll explore the more interesting cases in the next sections.

我们将在接下来的章节中探讨更有趣的案例。

3.1. Unused Dependencies

3.1.未使用的依赖关系

Now that we have our setup, let’s see the basic use-case. We’re interested in unused dependencies.

现在我们有了我们的设置,让我们看看基本的使用情况。我们对未使用的依赖关系感兴趣。

Let’s run the lintGradle task:

让我们运行lintGradle任务。

$ ./gradlew lintGradle

> Task :lintGradle FAILED
# failure output omitted

warning   unused-dependency                  this dependency is unused and can be removed
unused-dependencies/build.gradle:6
implementation('com.google.guava:guava:29.0-jre')

✖ 1 problem (0 errors, 1 warning)

To apply fixes automatically, run fixGradleLint, review, and commit the changes.
# some more failure output

Let’s see what happened. We have an unused dependency (guava) in our compileClasspath configuration.

让我们看看发生了什么。我们的compileClasspath配置中有一个未使用的依赖(guava)。

If we run fixGradleLint task as the plugin suggests, the dependency is automatically removed from our build.gradle.

如果我们按照插件的建议运行fixGradleLint任务该依赖就会自动从我们的build.gradle中删除。

However, let’s use some dummy logic with our dependency instead:

然而,让我们用一些假的逻辑来代替我们的依赖性。

public static void main(String[] args) {
    System.out.println("Hello world");
    useGuava();
}

private static void useGuava() {
    List<String> list = ImmutableList.of("Baledung", "is", "cool");
    System.out.println(list.stream().collect(Collectors.joining(" ")));
}

If we rerun it we get no more errors:

如果我们重新运行它,就不会再出现错误。

$ ./gradlew lintGradle

BUILD SUCCESSFUL in 559ms
3 actionable tasks: 1 executed, 2 up-to-date

3.2. Using Transitive Dependencies

3.2.使用过渡性依赖关系

Let’s now include another dependency:

现在让我们包括另一个依赖关系。

dependencies {
    implementation('com.google.guava:guava:29.0-jre')
    implementation('org.apache.httpcomponents:httpclient:4.5.12')
    testImplementation('junit:junit:4.12')
}

This time, let’s use something from a transitive dependency:

这一次,让我们使用来自过渡性依赖的东西。

public static void main(String[] args) {
    System.out.println("Hello world");
    useGuava();
    useHttpCore();
}

// other methods

private static void useHttpCore() {
    SSLContextBuilder.create();
}

Let’s see what happens:

让我们看看会发生什么。

$ ./gradlew lintGradle

> Task :lintGradle FAILED
# failure output omitted 

warning   unused-dependency                  one or more classes in org.apache.httpcomponents:httpcore:4.4.13 
are required by your code directly (no auto-fix available)
warning   unused-dependency                  this dependency is unused and can be removed 
unused-dependencies/build.gradle:8
implementation('org.apache.httpcomponents:httpclient:4.5.12')

✖ 2 problems (0 errors, 2 warnings)

We get two errors. The first error roughly says we should reference httpcore directly.

我们得到两个错误。第一个错误大概是说我们应该直接引用httpcore

The SSLContextBuilder in our sample is actually part of it.

我们样本中的SSLContextBuilder实际上是它的一部分。

The second error says we’re not using anything from httpclient.

第二个错误说我们没有使用任何来自httpclient.的东西。

If we use a transitive dependency, the plugin tells us to make it a direct one.

如果我们使用一个过渡性的依赖关系,该插件会告诉我们把它变成一个直接依赖关系

Let’s take a peek at our dependency tree:

让我们来看看我们的依赖关系树。

$ ./gradlew unused-dependencies:dependencies --configuration compileClasspath

> Task :unused-dependencies:dependencies

------------------------------------------------------------
Project :unused-dependencies - Gradle Unused Dependencies example
------------------------------------------------------------

compileClasspath - Compile classpath for source set 'main'.
+--- com.google.guava:guava:29.0-jre
|    +--- com.google.guava:failureaccess:1.0.1
|    +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
|    +--- com.google.code.findbugs:jsr305:3.0.2
|    +--- org.checkerframework:checker-qual:2.11.1
|    +--- com.google.errorprone:error_prone_annotations:2.3.4
|    \--- com.google.j2objc:j2objc-annotations:1.3
\--- org.apache.httpcomponents:httpclient:4.5.12
     +--- org.apache.httpcomponents:httpcore:4.4.13
     +--- commons-logging:commons-logging:1.2
     \--- commons-codec:commons-codec:1.11

In this case, we can see that httpcore is brought in by httpclient.

在这种情况下,我们可以看到,httpcore是由httpclient带来的。

3.3. Using Dependencies with Reflection

3.3.使用反思的依赖关系

What about when we use reflection?

当我们使用反思时呢?

Let’s enhance our example a bit:

让我们加强一下我们的例子。

public static void main(String[] args) {
    System.out.println("Hello world");
    useGuava();
    useHttpCore();
    useHttpClientWithReflection();
}

// other methods

private static void useHttpClientWithReflection() {
    try {
        Class<?> httpBuilder = Class.forName("org.apache.http.impl.client.HttpClientBuilder");
        Method create = httpBuilder.getMethod("create", null);
        create.invoke(httpBuilder, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Now let’s rerun the Gradle task:

现在让我们重新运行Gradle任务。

$ ./gradlew lintGradle

> Task :lintGradle FAILED
# failure output omitted

warning   unused-dependency                  one or more classes in org.apache.httpcomponents:httpcore:4.4.13 
are required by your code directly (no auto-fix available)

warning   unused-dependency                  this dependency is unused and can be removed
unused-dependencies/build.gradle:9
implementation('org.apache.httpcomponents:httpclient:4.5.12')

✖ 2 problems (0 errors, 2 warnings)

What happened? We used HttpClientBuilder from our dependency (httpclient) but still got errors.

发生了什么?我们使用了我们的依赖关系(httpclient)中的HttpClientBuilder,但还是出现了错误。

If we use a library with reflection, the plugin does not detect its usage.

如果我们使用一个带反射的库,该插件不会检测到它的使用情况

As a result, we can see the same two errors.

结果,我们可以看到同样的两个错误。

In general, we should configure such dependencies as runtimeOnly.

一般来说,我们应该把这种依赖关系配置为runtimeOnly

3.4. Generating Reports

3.4.生成报告

For big projects, the number of errors returned in a terminal becomes challenging to handle.

对于大项目来说,在一个终端中返回的错误数量变得难以处理。

Let’s configure the plugin to give us a report instead:

让我们配置一下这个插件,让它给我们一份报告。

allprojects {
    apply plugin :"java"
    apply plugin :"nebula.lint"
    gradleLint {
        rules=['unused-dependency']
        reportFormat = 'text'
    }
    // other  details omitted
}

Let’s run the generateGradleLintReport task and check our build output:

让我们运行generateGradleLintReporttask,检查我们的构建输出。

$ ./gradlew generateGradleLintReport
# task output omitted

$ cat unused-dependencies/build/reports/gradleLint/unused-dependencies.txt

CodeNarc Report - Jun 20, 2020, 3:25:28 PM

Summary: TotalFiles=1 FilesWithViolations=1 P1=0 P2=3 P3=0

File: /home/user/tutorials/gradle-5/unused-dependencies/build.gradle
    Violation: Rule=unused-dependency P=2 Line=null Msg=[one or more classes in org.apache.httpcomponents:httpcore:4.4.13 
                                                         are required by your code directly]
    Violation: Rule=unused-dependency P=2 Line=9 Msg=[this dependency is unused and can be removed] 
                                                 Src=[implementation('org.apache.httpcomponents:httpclient:4.5.12')]
    Violation: Rule=unused-dependency P=2 Line=17 Msg=[this dependency is unused and can be removed] 
                                                  Src=[testImplementation('junit:junit:4.12')]

[CodeNarc (http://www.codenarc.org) v0.25.2]

Now it detects unused dependencies on the testCompileClasspath configuration.

现在它检测到了testCompileClasspath配置上未使用的依赖。

This is, unfortunately, an inconsistent behavior of the plugin. As a result, we now get three errors.

不幸的是,这是该插件的一个不一致的行为。因此,我们现在得到三个错误。

4. Conclusion

4.总结

In this tutorial, we saw how to find unused dependencies on Gradle builds.

在本教程中,我们看到了如何在Gradle构建中找到未使用的依赖项。

First, we explained the general setup. After that, we explored the errors reported with different dependencies and their usage.

首先,我们解释了一般的设置。之后,我们探讨了不同的依赖关系和它们的使用所报告的错误。

Finally, we saw how to generate text-based reports.

最后,我们看到如何生成基于文本的报告。

As usual, we can find the complete code samples over on GitHub.

像往常一样,我们可以在GitHub上找到完整的代码样本over