«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.baeldung"
    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

Посмотрим, что получилось. У нас есть неиспользуемая зависимость (guava) в нашей конфигурации compileClasspath.

Если мы запустим задачу 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)

Что случилось? Мы использовали HttpClientBuilder из нашей зависимости (httpclient), но все равно получали ошибки.

Если мы используем библиотеку с отражением, плагин не определяет ее использование.

В итоге мы видим те же две ошибки.

В общем случае такие зависимости следует настраивать как 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.

Это, к сожалению, непоследовательное поведение плагина. В результате теперь мы получаем три ошибки.

4. Заключение

В этом уроке мы увидели, как найти неиспользуемые зависимости в сборках Gradle.

Сначала мы объяснили общую настройку. После этого мы изучили сообщения об ошибках с различными зависимостями и их использование.

Наконец, мы увидели, как генерировать текстовые отчеты.

Как обычно, полные примеры кода можно найти на GitHub.