Време за читање : 15 minutes

Već ste doživili ovaj trenutak kad je jednostavno`./gradlew build`Шта то покрећеш сто пута на дан, неожињено почиње да пада на грешку коју није раније видела? Помножи то на једанаест последоватих грешака, додај добру дозу несусадности Docker Engine 29, порошљи Gradle 9 који брише API-je које smo koristili deset года. Ово је повни журнал.


Дијаграм 1 — Миграција Gradle 9 (niz грешака)

Cilj: pokazati seriju nekompatibilnosti.

Jasno, usmeren na Gradle.

@startuml
left to right direction
title Migracija Gradle 9
rectangle "1
reportsDir" 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 додаци

Cilj: izolirati pokvareni plugins.

👉 Čista vidovina zavisnosti.
@startuml
left to right direction
title Плагинови који треба исправити
skinparam shadowing false
skinparam roundcorner 18

rectangle "1
openApiGenerate" as A #FFF3E0
rectangle "2
gitProperties
2.5.7" as B #FFF3E0
rectangle "3
Verzije
ispravljene" as C #E3F2FD
rectangle "4\nSklati OK" as D #E8F5E9

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

@enduml

Dijagram 3 — Docker 29 / Testcontainers

Cilj : problem runtime

👉 Jasna istorija runtimea.
@startuml
left to right direction
title Docker 29 + Testcontainers

skinparam shadowing false
skinparam roundcorner 18
skinparam defaultFontSize 14

rectangle "1
Sastavi OK" as A #E8F5E9
rectangle "2
Testovi Cucumber NEUSPEŠNO" as B #FFEBEE
rectangle "3
Testcontainers 1.32
odbacen" as C #FFF3E0
rectangle "4
Upgrade
verzije" as D #E3F2FD
rectangle "5
Izgradi konačno OK" as E #E8F5E9

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

@enduml

drugi način da ga představite
@startuml

!option handwritten true

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

title Roadmap stabilizacije — Gradle 9 ka finalnom buildu
left to right direction

rectangle "⚙️ Migracija Gradle 9" as A #E3F2FD
rectangle "Додатци JHipster" as B #FFF3E0
rectangle "🐳 Docker 29
Testcontainers" as C #FCE4EC
rectangle "✅ stabilna izgradnja" as D #E8F5E9

note bottom of A
reportsDir
mainClass
sourceCompatibility
Groovy assert
end note

note bottom of B
openApiGenerate
gitProperties
versions alignées
end note

note bottom of C
tests Cucumber KO
image Docker rejetée
upgrade libs
end note

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

@enduml

Scena: JHipster projekat, build koji više ne učitava

Projekat`edster`Ovo je JHipster aplikacija generisana 2024. godine. Glavni skript za gradnju`build.gradle`bio je ostao stabilan tokom meseci. Zatim odlučujemo da nadogradimo verziju :

komponenta

Ciljna verzija

Gradle

9.4.1

Јава

21.0.11-tem (Eclipse Temurin)

JHipster

8.x (okvir`tech.jhipster:jhipster-framework:8.11.0`)

Spring Boot

3.4.5

Kotlin

2.3.0

Docker Engine

29.4.1 (API 1.54)

Promena verzije Jave se vrši putem SDKMAN-a:

sdk use java 21.0.11-tem

Prvi pokretanje:

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

Nikad čak i nismo pokrenuli učitavanje skripte za gradnju. Problem je u`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.

Greška 1 : fantomska promenljiva `reportsDir

Скрипта`jhipster.cucumber-conventions.gradle`sadrži :

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"
    // ...
}

Definisana promenljiva je`cucumberReportsDir`. Korišćena je`reportsDir`— која није дефинисана нигде.

Корекција : outputs.dir(cucumberReportsDir)

Greška 2 : main = "…​" više ne postoji u Gradle 9

Hitno ponovno pokretanje :

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

Gradle 9 uklanja svojstvo`main`u korist`mainClass`о задацима`JavaExec`.

Корекција:`mainClass = "org.junit.platform.console.ConsoleLauncher"`

Greška 3 : sourceCompatibility više nije svojstvo projekta

Nova greška:

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

Коренијски скрипт је imao :

sourceCompatibility=17
targetCompatibility=17

Gradle 9 uklanja ove svojstva na korenom nivou. Oni moraju da budu unutar bloka.java.

Корекција:

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

Greška 4: Groovy tvrdenje sa tri operanda

Stari skript je sadržavao:

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

У Грови,"21" et "24"`су truthy низове. Izraz se rešava u(…​ == "17") || true || true`, što je uvek tačno — ali sintaksa nije validna za zahtevanu strogu tvrdnju.

Ispravka :

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

Greška 5: implicitna zavisnost compileKotlin → `openApiGenerate

Build napreduje. Kompajliranje Kotlin-a je uspelo. Zatim:

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

Gradle 9 odbija implicitne zavisnosti između zadataka. Jer`compileKotlin`чита генерисане изворове од`openApiGenerate`, Треба јавити везу.

Исправкаu`build.gradle`:

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

Le `afterEvaluate`jer je potrebno`openApiGenerate`је додатак (OpenAPI Generator plugin) и није задатак који је директно доступан током фазе конфигурације.

Грешка 6 : generateGitProperties се срушава на `FilterOutputStream.write()

Kompajliranje Jave prolazi. Resursi se obrađuju. Zatim :

> Task :generateGitProperties FAILED

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

Плагин`gradle-git-properties`version 2.5.0 nije kompatibilna sa Java 21 / Gradle 9. Unutrašnji greška pokušava da pozove`write(int)`preko Groovy na toku koji ga je zatvorio

Ispravkau`gradle.properties`:

gitPropertiesPluginVersion=2.5.7

Konačno: Testcontainers ulazi na scenu

Do sada, ovo je bilo čisto 'build script debugging'. Svaka greška je bila nekompatibilnost Gradle 9 ili Java 21 u skriptovima za gradnju. Posle šest ispravki iznad, le`./gradlew assemble`успешно прође

Ali`./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 ne može da komunicira sa Docker-om. Dve različite strategije (promenljive okoline + Unix soket) ne uspevaju sa istom greškom.

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

actor "Testcontainers
1.20.6" as TC
participant "docker-java
3.4.1" as DJ
participant "Docker Engine
29.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

Faza 1 : docker-java staza

Prvi refleks : Docker Java klijent korišćen od Testcontainers šalje verziju API`1.32`u njegovom HTTP handshake-u, ali Docker Engine 29.4.1 zahteva najmanje`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, datira iz Testcontainers 1.20.6. Najnovija javna verzija docker-jave je 3.5.1. Pokušamo da prisilimo na nadogradnju verzije kroz`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")
            }
        }
    }
}

Ponovno pokretanje izgradnje. Ista greška:`client version 1.32 is too old`.

Провера Gradle кеша: JAR-ови су успешно поново преузети, али грешка остаје. docker-java 3.5.1 увек шаље`1.32`. Ova verzija nije dovoljna za Docker Engine 29.4.1.

Акција чишћењаOčisti potpuno keš Gradle, radi slučaja

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

Ponovni pokret nakon čišćenja. Ista greška.

Faza 2: web pretraga i GitHub krumplin

docker-java 3.5.1 je nedovoljan. Ostaje samo da se proveri da li je neko susrešao ovaj tačan problem.

Ciljana pretraga na problemima testcontainers-java i 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

Prvi neposredni rezultati :

Нита Ариадне је јасна: Docker Engine 29.x је повео свој минималан API на`1.24`(стари дефект) у`1.40`. Testcontainers 1.20.x koristi docker-java 3.4.x koji pregovara verziju API-ja slanjem`1.32`. Daemon ljubazno ali tvrdohladno odbija.

У проблему #11491, одржавач одговара:

_ Zdravo, imam projekat koji radi na verziji 2.0.3 na GH runneru sa engine-om 29.1 i radi. Молим вас, проверите да ли нема конфликта верzije? Molimo, sećajte se da su svi moduli prefiksirani sa`testcontainers-. Zato, počevši od verzije 2.x, prešli smo od`postgresql to testcontainers-postgresql _

Ovaj odgovor sadržidve kritične informacije:

  1. Testcontainers 2.0.3+ rešava problem

  2. Moduli su bilipreimenovani sa prefiksom `testcontainers-

Faza 3 : zamka preimenovanih artefakata

Preimenuvanje je brutalno. U Testcontainers 2.x, ni jedno od starih imena ne radi pod tim imenom:

Старо име (1.x)

Novo ime (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

непромењен

Prvi pokušaj : primeniti BOM Testcontainers 2.0.5 i nova imena artefakata u`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 nije dovoljan.Зашто? Пошто Spring Boot 3.4.5 излага свој BOM за управљање зависиснца (spring-boot-dependencies) копобедина BOM-u Testcontainers у transitivnoj rezoluciji. Spring Boot исправља.testcontainers à 1.20.6, i zato svi artefakti koji nisu eksplicitno u BOM-u Spring Boot ( kao noviji`testcontainers-*) razrešavaju se ka starim imenima`1.20.6-- koji više ne postoje u ovoj verziji.

Prilikom inspekce`dependencies --configuration testRuntimeClasspath`, nalazi se :

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

Le →`označuje zamenu: Testcontainers 2.0.5 je zahtevan, ali Spring Boot prisiljuje`1.20.6.

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

left to right direction

component "build.gradle\n(potrošač)" as BG #E3F2FD {
    rectangle "zahtev\n`testcontainers-bom:2.0.5" as REQ
    rectangle "захтев
`testcontainers-postgresql" as REQ2
}

component "Maven Central" as MC #FFF3E0

component "Izjave" as DEC #E8F5E9 {
    rectangle "testcontainers-bom:2.0.5`\nfiksirano 2.0.5" as BOM2
    rectangle "spring-boot-dependencies:3.4.5`\nисправљено 1.20.6" as BOM1
}

component "Решење Gradle" as RES #FFEBEE {
    rectangle "Pobednička verzija
`testcontainers:1.20.6" as WIN #EF9A9A
}

BG --> DEC : "analiziraj BOM-ove"
BOM2 --> RES : "предлаже 2.0.5"
BOM1 --> RES : "предлага 1.20.6"
WIN -> MC : "preuzmi
testcontainers: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

Faza 4: prisiljavanje resolucije

Strategija postaje dvostruka:

  1. Postaviti docker-java na verziju 3.7.1 (testirana kao kompatibilna sa Docker Engine 29)

  2. Prisiliti Testcontainers core na 2.0.5 obilazeći Spring Boot BOM

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

left to right direction

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

component "Конфликт решен" as RESOLVED #E3F2FD {
    rectangle "docker-java
`3.7.1` (prisilno)" as DJ
    rectangle "testcontainers
`2.0.5` (prisiljeno)" as TC #81C784
    rectangle "testcontainers-postgresql
`2.0.5" as TPG
    rectangle "testcontainers-jdbc
`2.0.5" as TJ
    rectangle "testcontainers-junit-jupiter\n`2.0.5" as TJJ
}

component "BOM Spring Boot\n(implicitno, prekrženo)" as BOM1 #FFEBEE

component "Maven Centar" as MC #FFF3E0

BG --> RESOLVED : "BOM Testcontainers 2.0.5
prosto prolazi"
RS -> DJ : "sila 3.7.1"
RS -> TC : "sila 2.0.5"
RESOLVED --> MC : "preuzmi JAR-ove\nkompatibilne sa Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "пропуштено на testcontainers\nи 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`upravljeno pluginom Spring Boot. Test jednakosti na`details.requested.name == "testcontainers"`Namerno je ograničeno: prisiljujemo samo core modul. Module`testcontainers-*`Sledeju BOM Testcontainers 2.0.5 koji smo eksplicitno naveli.

Konačni rezultat

$ ./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 konačno uspevaju da kreiraju svoje PostgreSQL kontejnere putem Docker Engine 29.4.1. Konačna poslovna greška (HTTP 500 na Cucumber testu) je problem u aplikaciji potpuno nezavisno od skripte za grade.

Табела сажетак исправка

# datoteka Проблем Исправка

1

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

promenljiva`reportsDir`nepostojeca

outputs.dir(cucumberReportsDir)

2

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

`main`zastareo Gradle 9

mainClass

3

build.gradle

`sourceCompatibility`top-level zabranjeno Gradle 9

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

4

build.gradle

Sintaktički neispravna Groovy assertion

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

5

build.gradle

Implicitna zavisnost`compileKotlin`</think> (empty)openApiGenerate

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

6

gradle.properties

`gitPropertiesPluginVersion`nekompatibilan Java 21

2.5.7

7

Keš Gradle

docker-java 3.5.1 testiran, rešen ali nedostavan

Обриши + прелазак docker-java 3.7.1

8

build.gradle

Testcontainers 1.20.6 je nekompatibilan sa Docker Engine 29

Prisiljenje Testcontainers 2.0.5 + prefiks`testcontainers-*`+ resolutionStrategy vs Spring Boot BOM

Zaključak

Ако ваш JHipster/Gradle build падне nakon подијања верзије на Gradle 9 + Docker Engine 29, грешке се подељују на две категорије :

Greške u skripti za gradnju(prvih 6) : Gradle 9 uklanja svojstva i sintakse koje su radile veoma godinama (sourceCompatibility=, main =, reportsDir). Ovo su mehaničke migracije jednom kada upoznamo novi API.

Greške u izvršavanju Docker(последње две) : Docker Engine 29.4.1 odbacuje klijente API < 1.40. Testcontainers 1.20.6 (impozovan od strane Spring Boot 3.4.5) koristi docker-java 3.4.1 koji šalje 1.32. Jedino Testcontainers 2.0.5 + docker-java 3.7.1 rešava problem, sa preimenovanjem artefakata i prisilivanjem verzije via`resolutionStrategy.eachDependency`kao jedini način da se obiđe implicitni BOM u Spring Boot-u.

Gradle build skript u korenu projekta je sada čist.`./gradlew build`користи Java 21, Gradle 9.4.1, Spring Boot 3.4.5 и Docker Engine 29.4.1.

Повезани чланци