время чтения : 15 minutes

Вы уже знали этот момент, когда простой`./gradlew build`который вы запускаете сто раз в день, вдруг начинает падать из-за ранее невиданной ошибки? Умножьте это одиннадцать раз подряд, добавьте хорошую дозу несовместимости Docker Engine 29, посыпьте Gradle 9, который убивает API, которых вы использовали десять лет. Вот полный журнал.


Диаграмма 1 — Миграция Gradle 9 (цепочка ошибок)

Цель: показать ряд несовместимостей.

👉 Четкий, ориентированный на Gradle.
@startuml
left to right direction
title Миграция на Gradle 9
rectangle "1\nreportsDir" as A
rectangle "2\nmainClass" as B
rectangle "3\nsourceCompatibility" as C
rectangle "4\nGroovy assert" as D

A --> B
B --> C
C --> D

@enduml

Диаграмма 2 — Несовместимые плагины JHipster

Цель: изолировать сломанные плагины.

👉 Чистый вид зависимостей.
@startuml
left to right direction
title Плагины, требующие исправления
skinparam shadowing false
skinparam roundcorner 18

rectangle "1\nopenApiGenerate" as A #FFF3E0
rectangle "2\ngitProperties\n2.5.7" as B #FFF3E0
rectangle "3\nVersions\nисправлены" as C #E3F2FD
rectangle "4\nСборка ОК" as D #E8F5E9

A --> B
B --> C
C --> D

@enduml

Диаграмма 3 — Docker 29 / Testcontainers

Цель: проблема выполнения.

👉 Чёткая история рантайма.
@startuml
left to right direction
title Docker 29 + Testcontainers

skinparam shadowing false
skinparam roundcorner 18
skinparam defaultFontSize 14

rectangle "1\nСборка OK" as A #E8F5E9
rectangle "2\nТесты Cucumber FAIL" as B #FFEBEE
rectangle "3\nTestcontainers 1.32\nотклонено" as C #FFF3E0
rectangle "4\nОбновление\nверсий" as D #E3F2FD
rectangle "5\nФинальная сборка OK" as E #E8F5E9

A --> B
B --> C
C --> D
D --> E

@enduml

другой способ представить его
@startuml

!option handwritten true

skinparam backgroundColor #FEFEFE
skinparam shadowing false
skinparam defaultFontName Inter
skinparam defaultFontSize 14
skinparam ArrowThickness 2
skinparam RoundCorner 18

title Дорожная карта стабилизации — от Gradle 9 до финальной сборки
left to right direction

rectangle "⚙️ Миграция Gradle 9" as A #E3F2FD
rectangle "🧩 Плагины JHipster" as B #FFF3E0
rectangle "🐳 Docker 29\nTestcontainers" as C #FCE4EC
rectangle "✅ Сборка стабильна" as D #E8F5E9

note bottom of A
reportsDir
mainClass
sourceCompatibility
Groovy assert
end note

note bottom of B
openApiGenerate
gitProperties
выровненные версии
end note

note bottom of C
тесты Cucumber KO
образ Docker отклонен
обновление библиотек
end note

A -[#42A5F5]-> B : corrections build.gradle
B -[#FB8C00]-> C : assemble OK
C -[#E53935]-> D : tests réparés

@enduml

Сцена: проект JHipster, сборка, которая больше не загружается

Проект`edster`это приложение JHipster, сгенерированное в 2024 году. Корневой скрипт сборки`build.gradle`Он оставался стабильным в течение месяцев. Затем решили обновить до версии:

Компонент Целевая версия

Gradle

9.4.1

Джава

21.0.11-tem (Eclipse Temurin)

JHipster

8.x (фреймворк`tech.jhipster:jhipster-framework:8.11.0`)

Spring Boot

3.4.5

Котлин

2.3.0

Движок Docker

29.4.1 (API 1.54)

Изменение версии Java выполняется через SDKMAN :

sdk use java 21.0.11-tem

Первый запуск :

$ ./gradlew build --no-daemon

FAILURE: Build failed with an exception.

* Where:
Build file '/home/.../edster/build.gradle' line: 18

* What went wrong:
An exception occurred applying plugin request [id: 'jhipster.cucumber-conventions']
> Failed to apply plugin 'jhipster.cucumber-conventions'.
   > Could not create task ':cucumberTest'.
      > Could not create task ':consoleLauncherTest'.
         > Could not set unknown property 'reportsDir' for task ':consoleLauncherTest'

Мы даже не начинаем загрузку скрипта сборки. Проблема в`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.

Ошибка 1 : переменная-призрак `reportsDir

Скрипт`jhipster.cucumber-conventions.gradle`содержит:

tasks.register('consoleLauncherTest', JavaExec) {
    dependsOn(testClasses)
    String cucumberReportsDir = file("$buildDir/reports/tests")
    outputs.dir(reportsDir)           // <-- reportsDir n'existe PAS
    classpath = sourceSets["test"].runtimeClasspath
    main = "org.junit.platform.console.ConsoleLauncher"
    // ...
}

Определённая переменная — это`cucumberReportsDir`. Это используется`reportsDir`-- которая не определена нигде.

Исправление:`outputs.dir(cucumberReportsDir)`

Ошибка 2: main = "…​" больше не существует в Gradle 9

Срочный перезапуск:

Could not set unknown property 'main' for task ':consoleLauncherTest' of type org.gradle.api.tasks.JavaExec.

Gradle 9 удаляет свойство`main`в пользу`mainClass`о задачах`JavaExec`.

Исправление : mainClass = "org.junit.platform.console.ConsoleLauncher"

Ошибка 3: sourceCompatibility больше не является свойством проекта

Новая ошибка:

Could not set unknown property 'sourceCompatibility' for root project 'edster' of type org.gradle.api.Project.

Корневой скрипт имел:

sourceCompatibility=17
targetCompatibility=17

Gradle 9 удаляет эти свойства на уровне корня. Они должны находиться в блоке`java`.

Коррекция:

java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}

Ошибка 4: трехоперандное утверждение Groovy

В старом скрипте содержалось:

assert System.properties["java.specification.version"] == "17" || "21" || "24"

В Groovy,"21" et "24"`являются strings truthy. Выражение разрешается в(…​ == "17") || true || true`, что всегда истинно — но синтаксис неверен для желаемого строгого утверждения.

Исправление:

assert System.properties["java.specification.version"] in ["17", "21", "23", "24"]

Ошибка 5: неявная зависимость compileKotlin → `openApiGenerate

Сборка продолжается. Компиляция Kotlin прошла успешно. Затем :

Task ':compileKotlin' uses this output of task ':openApiGenerate'
without declaring an explicit or implicit dependency.

Gradle 9 отклоняет неявные зависимости между задачами. Поскольку`compileKotlin`читает сгенерированные источники`openApiGenerate`, нужно объявить ссылку.

Коррекцияв`build.gradle` :

afterEvaluate {
    tasks.named("compileKotlin").configure {
        dependsOn(tasks.named("openApiGenerate"))
    }
}

Le `afterEvaluate`необходимо потому что`openApiGenerate`Это расширение (плагин OpenAPI Generator), а не задача, непосредственно доступная во время фазы конфигурации.

Ошибка 6 : generateGitProperties ломается при `FilterOutputStream.write()

Компиляция Java проходит. Ресурсы обработаны. Затем:

> Task :generateGitProperties FAILED

No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]

Плагин`gradle-git-properties`версия 2.5.0 несовместима с Java 21 / Gradle 9. Внутренняя ошибка пытается вызвать`write(int)`через Groovy на потоке, который он закрыл.

Исправлениев`gradle.properties`:

gitPropertiesPluginVersion=2.5.7

Наконец: Testcontainers вступает в игру

До этого это была чистая отладка скриптов сборки. Каждая ошибка была несовместимостью Gradle 9 или Java 21 в скриптах сборки. После шести исправлений, указанных выше, это`./gradlew assemble`проходит успешно.

Но`./gradlew build`Он также запускает тесты, а тесты Cucumber используют Testcontainers для запуска временного PostgreSQL.

Could not find a valid Docker environment.

EnvironmentAndSystemPropertyClientProviderStrategy: failed with exception BadRequestException
(Status 400: {"message":"client version 1.32 is too old. Minimum supported API version is 1.40"}
UnixSocketClientProviderStrategy: failed with exception BadRequestException
(Status 400: {"message":"client version 1.32 is too old. Minimum supported API version is 1.40"}

Testcontainers не может взаимодействовать с Docker. Две разные стратегии (переменные окружения + сокет Unix) терпят неудачу из-за одной и той же ошибки.

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

actor "Testcontainers\n1.20.6" as TC
participant "docker-java\n3.4.1" as DJ
participant "Docker Engine\n29.4.1" as DE

TC -> DJ : new DockerClient()
DJ -> DE : GET /_ping\nUser-Agent: docker-java/3.4.1\nAPI-Version: 1.32
DE --> DJ : HTTP 400 Bad Request\nclient version 1.32 is too old.\nMinimum supported API version is 1.40
DJ --> TC : BadRequestException
TC --> TC : Could not find a valid\nDocker environment

note right of DE
  Docker Engine 29.x impose
  API minimum = 1.40
  (défaut ancien : 1.24)
end note

note left of DJ
  docker-java 3.4.1 envoie
  hardcoded API version 1.32
  dans l'en-tête HTTP
end note

@enduml

Этап 1: путь docker-java

Первый рефлекс: клиент Docker Java, используемый Testcontainers, отправляет версию API`1.32`в его HTTP-handshake, но Docker Engine 29.4.1 требует минимум`1.40`. Проблема находится на уровне клиента, а не демона.

Проверка версии docker-java, решённая Testcontainers 1.20.6 :

$ ./gradlew dependencies --configuration testRuntimeClasspath | grep docker-java

+--- com.github.docker-java:docker-java-api:3.4.1
+--- com.github.docker-java:docker-java-transport:3.4.1

docker-java 3.4.1, датированный Testcontainers 1.20.6. Последняя публичная версия docker-java — 3.5.1. Попытаемся принудительно повысить версию через`resolutionStrategy`в блоке`configurations` de build.gradle :

configurations {
    all {
        resolutionStrategy.eachDependency { details ->
            if (details.requested.group == "com.github.docker-java"
                && details.requested.name.startsWith("docker-java")) {
                details.useVersion("3.5.1")
            }
        }
    }
}

Перезапуск сборки. Та же ошибка:`client version 1.32 is too old`.

Проверка кэша Gradle: JAR‑файлы успешно перезагружены, но ошибка сохраняется. docker-java 3.5.1 по-прежнему отправляет`1.32`. Эта версия поэтому НЕ достаточна для Docker Engine 29.4.1.

действие очищения: очистить полностью кэш Gradle, просто чтобы быть уверенным :

rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle

Полный перезапуск после очистки. Та же ошибка.

Фаза 2: веб-поиск и хлебные крошки GitHub

docker-java 3.5.1 недостаточен. Осталось только поискать, есть ли кто-то, кто столкнулся с этой проблемой.

Целевой поиск по проблемам testcontainers-java и docker-java :

site:github.com/testcontainers "client version 1.32 is too old"
site:github.com/docker-java "client version 1.32" Docker Engine 29

Первые немедленные результаты :

Нить Ариадны ясна: Docker Engine 29.x повысил свою минимальную версию API до`1.24`(старый дефект) к`1.40`. Testcontainers 1.20.x использует docker-java 3.4.x, который согласовывает версию API, отправляя`1.32`. Демон вежливо, но твердо отказывает.

В проблеме #11491 maintainer отвечает:

_ Привет, у меня запущен проект с версией 2.0.3 на GH runner с движком 29.1, и он работает. Пожалуйста, проверьте, нет ли конфликта версий? Пожалуйста, запомните, что все модули были префиксированы с`testcontainers-. Итак, начиная с версии 2.x мы перешли от`postgresql to testcontainers-postgresql _

Этот ответ содержитдве критические сведения :

  1. Testcontainers 2.0.3+ решает проблему

  2. Модули былипереименованные с префиксом `testcontainers-

Фаза 3: ловушка переименованных артефактов

Переименование жестоко. В Testcontainers 2.x ни одно из старых имён не работает под этим именем:

Старое название (1.x) Новое имя (2.x)

org.testcontainers:postgresql

org.testcontainers:testcontainers-postgresql

org.testcontainers:jdbc

org.testcontainers:testcontainers-jdbc

org.testcontainers:junit-jupiter

org.testcontainers:testcontainers-junit-jupiter

org.testcontainers:testcontainers

неизменный

Первая попытка: применить BOM Testcontainers 2.0.5 и новые названия артефактов в`build.gradle`.

dependencies {
    testImplementation platform("org.testcontainers:testcontainers-bom:2.0.5")
    testImplementation "org.testcontainers:testcontainers-postgresql"
    testImplementation "org.testcontainers:testcontainers-jdbc"
    testImplementation "org.testcontainers:testcontainers-junit-jupiter"
    testImplementation "org.testcontainers:testcontainers"
}

./gradlew build

FAILURE: Could not find org.testcontainers:jdbc:2.0.5.
Could not find org.testcontainers:junit-jupiter:2.0.5.

BOM testcontainers-bom недостаточно.Почему? Потому что Spring Boot 3.4.5 предоставляет свой собственный BOM управления зависимостями (spring-boot-dependencies) которыйвыиграйВ транзитивном разрешении BOM Testcontainers. Spring Boot устанавливает`testcontainers` à 1.20.6, и поэтому все артефакты, которые не указаны явно в BOM Spring Boot (как новые`testcontainers-*) резолвят к старым именам`1.20.6-- которые больше не существуют в этой версии.

При осмотре`dependencies --configuration testRuntimeClasspath`, имеется:

+--- org.testcontainers:testcontainers -> 1.20.6 (*)
|    \--- org.testcontainers:testcontainers:2.0.5 -> 1.20.6

Le →`указывает на замену: Testcontainers 2.0.5 требуется, но Spring Boot принуждает`1.20.6.

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component "build.gradle\n(consommateur)" as BG #E3F2FD {
    rectangle "demande\n`testcontainers-bom:2.0.5`" as REQ
    rectangle "demande\n`testcontainers-postgresql`" as REQ2
}

component "Maven Central" as MC #FFF3E0

component "Déclarations" as DEC #E8F5E9 {
    rectangle "`testcontainers-bom:2.0.5`\nfixe 2.0.5" as BOM2
    rectangle "`spring-boot-dependencies:3.4.5`\nfixe 1.20.6" as BOM1
}

component "Résolution Gradle" as RES #FFEBEE {
    rectangle "Version gagnante\n`testcontainers:1.20.6`" as WIN #EF9A9A
}

BG --> DEC : "analyse les BOMs"
BOM2 --> RES : "propose 2.0.5"
BOM1 --> RES : "propose 1.20.6"
WIN -> MC : "télécharge\ntestcontainers:1.20.6"

note bottom of WIN
  Spring Boot BOM prime car
  le plugin spring-boot applique
  ses dépendances de gestion
  après la résolution du consommateur
end note

@enduml

Этап 4 : принудительно разрешить

Стратегия становится двойной:

  1. Принудительно установить docker-java в версию 3.7.1 (протестирована как совместимая с Docker Engine 29)

  2. Принудительно установить Testcontainers core версии 2.0.5, обходя BOM Spring Boot

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component "build.gradle\n(avec resolutionStrategy)" as BG #E8F5E9 {
    rectangle "BOM Testcontainers\n`2.0.5`" as BOM2
    rectangle "`resolutionStrategy\n.eachDependency`" as RS #66BB6A
}

component "Conflit résolu" as RESOLVED #E3F2FD {
    rectangle "docker-java\n`3.7.1` (forcé)" as DJ
    rectangle "testcontainers\n`2.0.5` (forcé)" as TC #81C784
    rectangle "testcontainers-postgresql\n`2.0.5`" as TPG
    rectangle "testcontainers-jdbc\n`2.0.5`" as TJ
    rectangle "testcontainers-junit-jupiter\n`2.0.5`" as TJJ
}

component "BOM Spring Boot\n(implicite, outrepassé)" as BOM1 #FFEBEE

component "Maven Central" as MC #FFF3E0

BG --> RESOLVED : "BOM Testcontainers 2.0.5\npasse directement"
RS -> DJ : "force 3.7.1"
RS -> TC : "force 2.0.5"
RESOLVED --> MC : "télécharge les JARs\ncompatibles Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "ignoré sur testcontainers\net docker-java"

note bottom of RS
  Le `eachDependency` intercepte
  la résolution Gradle AVANT
  que le BOM Spring Boot ne gagne.
  C'est une intervention chirurgicale.
end note

@enduml

Финальная правкав`build.gradle` :

configurations {
    all {
        resolutionStrategy.eachDependency { details ->
            if (details.requested.group == "com.github.docker-java"
                && details.requested.name.startsWith("docker-java")) {
                details.useVersion("3.7.1")
            }
            if (details.requested.group == "org.testcontainers"
                && details.requested.name == "testcontainers") {
                details.useVersion("2.0.5")
            }
        }
    }
}

dependencies {
    testImplementation platform("org.testcontainers:testcontainers-bom:2.0.5")
    testImplementation "org.testcontainers:testcontainers-jdbc"
    testImplementation "org.testcontainers:testcontainers-junit-jupiter"
    testImplementation "org.testcontainers:testcontainers-postgresql"
    testImplementation "org.testcontainers:testcontainers"
    // ... autres dépendances Spring Boot
}

Le `resolutionStrategy.eachDependency`является единственным способом обойти BOM`spring-boot-dependencies`управляемый плагином Spring Boot. Тест равенства на`details.requested.name == "testcontainers"`является намеренно ограничивающим: мы заставляем только основной модуль. Модули`testcontainers-*`следуют BOM Testcontainers 2.0.5, который мы явно объявили.

Итоговый результат

$ ./gradlew build --no-daemon

> Task :consoleLauncherTest
[2 containers found]
[2 containers started]
[2 containers successful]

BUILD SUCCESSFUL in 1m 16s

Testcontainers 2.0.5 + docker-java 3.7.1 наконец смогли создать свои контейнеры PostgreSQL через Docker Engine 29.4.1. Последняя бизнес‑ошибка (HTTP 500 в тесте Cucumber) — это полностью независимая от скрипта сборки проблема приложения.

Сводная таблица исправлений

# Файл проблема Исправление

1

buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle

Переменная`reportsDir`несуществующая

outputs.dir(cucumberReportsDir)

2

buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle

`main`устаревший Gradle 9

mainClass

3

build.gradle

`sourceCompatibility`top-level запрещено Gradle 9

Блок`java { sourceCompatibility = JavaVersion.VERSION_21 }`

4

build.gradle

Неправильное утверждение Groovy

assert …​ in ["17", "21", "23", "24"]

5

build.gradle

Неявная зависимость`compileKotlin`→openApiGenerate

afterEvaluate { tasks.named("compileKotlin").dependsOn("openApiGenerate") }

6

gradle.properties

`gitPropertiesPluginVersion`Несовместимый Java 21

2.5.7

7

Кеш Gradle

docker-java 3.5.1 протестировано, решено, но недостаточно

Очистка + проход docker-java 3.7.1

8

build.gradle

Testcontainers 1.20.6 не совместим с Docker Engine 29

Принуждение Testcontainers 2.0.5 + префикс`testcontainers-*`+ resolutionStrategy против Spring Boot BOM

Вывод

Если ваша сборка JHipster/Gradle падает после обновления до Gradle 9 + Docker Engine 29, ошибки делятся на две категории :

Ошибки скрипта сборки(первые 6) : Gradle 9 удаляет свойства и синтаксисы, которые работали годами (sourceCompatibility=, main =, reportsDir). Это механические миграции, когда мы знаем новую API.

Ошибки времени выполнения Docker(последние 2) : Docker Engine 29.4.1 исключает клиенты API < 1.40. Testcontainers 1.20.6 (навязанный Spring Boot 3.4.5) использует docker-java 3.4.1, который отправляет 1.32. Только Testcontainers 2.0.5 + docker-java 3.7.1 решает проблему, с переименованием артефактов и принудительным указанием версии via`resolutionStrategy.eachDependency`как единственный способ обойти неявный BOM Spring Boot.

Скрипт сборки Gradle в корне проекта теперь чистый.`./gradlew build`переходит на Java 21, Gradle 9.4.1, Spring Boot 3.4.5 и Docker Engine 29.4.1.

Похожие статьи