Перейти к основному содержимому

Поиск неиспользуемых зависимостей Gradle

· 5 мин. чтения

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.foreach"
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 .