Contents

查找未使用的 Gradle 依赖

1. 概述

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

在本快速教程中,我们将了解如何使用 Gradle Nebula Lint 插件来识别和修复此类问题。

2. 设置和配置

我们在示例中使用多模块 Gradle 5 设置。

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

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

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.blogdemo"
    version = "0.0.1"
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"
    repositories {
        jcenter()
    }
}

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

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

description = "Gradle Unused Dependencies example"
dependencies {
    implementation('com.google.guava:guava:29.0-jre')
    testImplementation('junit:junit:4.12')
}

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

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

稍后我们将以此为基础,看看插件是如何工作的。

3. 检测场景及报告

该插件搜索输出 jar 以检测是否使用了依赖项。

然而,根据不同的条件 ,它可以给我们不同的结果

我们将在下一节中探索更有趣的案例。

3.1. 未使用的依赖项

现在我们已经设置好了,让我们看看基本用例。我们对未使用的依赖项感兴趣。

让我们运行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

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

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

但是,让我们使用一些虚拟逻辑来代替我们的依赖:

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(" ")));
}

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

$ ./gradlew lintGradle
BUILD SUCCESSFUL in 559ms
3 actionable tasks: 1 executed, 2 up-to-date

3.2. 使用传递依赖

现在让我们包含另一个依赖项:

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

这一次,让我们使用来自传递依赖的东西:

public static void main(String[] args) {
    System.out.println("Hello world");
    useGuava();
    useHttpCore();
}
// other methods
private static void useHttpCore() {
    SSLContextBuilder.create();
}

让我们看看发生了什么:

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

我们得到两个错误。第一个错误粗略地说我们应该直接引用httpcore

我们示例中的 SSLContextBuilder实际上是其中的一部分。

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

如果我们使用传递依赖,插件会告诉我们将其设为直接依赖

让我们看一下我们的依赖树:

$ ./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

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

3.3. 通过反射使用依赖关系

当我们使用反射时呢?

让我们稍微增强一下示例:

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();
    }
}

现在让我们重新运行 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)

发生了什么?我们使用了依赖项*(httpclient)中的HttpClientBuilder*,但仍然出现错误。

如果我们使用带有反射的库,插件不会检测到它的使用

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

一般来说,我们应该配置runtimeOnly这样的依赖。

3.4. 生成报告

对于大型项目,终端中返回的错误数量变得难以处理。

让我们配置插件来给我们一个报告:

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

让我们运行generateGradleLintReport任务并检查我们的构建输出:

$ ./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]

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

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