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