Lessons learned: komplexe Multi‐Projekt‐Builds von Maven nach Gradle migrieren

AS IdeAS Engineering GmbH

Stoyan Stoyanov

10 November 2014

Zur Person

  • Java, Ruby, Groovy
  • Chef
  • Release Management
  • Twitter: @s_stoyanoff

Technologie-Stack

  • Escenic CMS
  • Java, Apache, Varnish, Tomcat, MySQL, Solr ...
  • Nexus, Jenkins, (Bash)

Ziel von dem Talk

  • Lohnt es sich von Maven auf Gradle zu migrieren?
  • Erfahrung teilen
  • Kein Maven Bashing

Build-Prozess ist wichtig

  • Testen ist der wichtigste Part von CD
    • Wird aber oft unterschätzt
    • Integration aller Testarten im Build soll einfach sein
    • Schnelles Feedback
  • Reproduzierbarkeit
  • Release/Build-Engineer ist ein Anti-Pattern

Like any other software system, when build scripts are poorly designed and maintained, the effort to keep them working grows in what seems like an exponential manner.

(Continous Delivery, The Commit Stage)

Motivation zur Migration

  • Maven 3
  • 30 Subprojekte (am Anfang waren 60)
    • 7000 LOC nur für den Build
    • Davon viel Ant
  •  15 min fürs Bauen
  •  50 min fürs Release
  • Spaßfaktor=0

Maven Release Plugin

Migration

  • gradlew init
    • Erstellt auch settings.gradle für uns
  • Scopes in Maven sind fast identish zu den Konfigurationen in Gradle
  • Provided nur in dem War-Plugin
  • Übernehmen von allen Ant-Skripten im Schritt 1
configurations {
    provided
    testProvided.extendsFrom provided
}

sourceSets {
    main {
        compileClasspath += configurations.provided
    }

    test {
        compileClasspath += configurations.testProvided
    }
}

Vergleichen von Artefakten

  • Bauen zuerst mit Maven
  • Bauen mit Gradle
  • Vergleichen von den Artefakten mit zipcmp (libzip)
  • Fühlt sich wie TDD an
  • Sehr einfach in Gradle zu automatisieren
  • Hilft sehr für besseres Verständnis vom Maven-Build

Tipps beim Vergleichen

  • maven-{jar,war}-plugin anpassen
    • <addMavenDescriptor>false</addMavenDescriptor>
  •  includeEmptyDirs=false in Gradle
  • Manifeste leicht anpassen während des Vergleichens
apply plugin: 'war'

// some dependencies and build logic here ..

war {
    includeEmptyDirs = false
}

war << {
    exec {
        ignoreExitValue = false
        executable 'zipcmp'
        args "$projectDir/target/$archiveName", archivePath
    }
}

Umgang mit Abhängigkeiten in Gradle

  • Build bei Konflikten brechen
    • failOnVersionConflict()
    • ./gradlew dependencyInsight --dependency someDep
  • Maven dependencyManagement mit Properties in Gradle definieren
// versions.gradle
project.ext {
    escenicEngineVersion = '5.5.4.143753'
}

// dependencyDeclarations.gradle
apply from: "$rootDir/gradle/dependencies/versions.gradle"

project.ext.escenicNativeLibraries = [
    engineCore: "com.escenic.engine:engine-core:$escenicEngineVersion"
]

// in build.gradle
apply from: "$rootDir/gradle/dependencies/dependencyDeclarations.gradle"

// dependency can be used in any subproject
compile escenicNativeLibraries.engineCore




Grunt in Gradle

  • Ant-Targets von dem Legacy-Build
    • Gradle -> Ant -> yuicompressor, less
    • Ant-Skript sehr komplex, langsam, unwartbar
    • JavaScript-Devs beherrschen kein Ant, Maven oder Gradle
  • Grunt ist JavaScript Task Runner (less, uglify, etc..)
    • Plain JavaScript
    • Tasks On-The-Fly erstellen
    • Dependency Management für JS durch bower
    • Grunt ist sehr einfach in Gradle zu integrieren
// in buildSrc

class Grunt extends DefaultTask {
    @Input
    List<String> commands

    @TaskAction
    void callGrunt() {
        project.exec {
            executable 'grunt'
            args commands
	}
    }
}

// in build.gradle

task installGruntDependencies(type: Exec) {
    inputs.file 'package.json'
    outputs.file 'node_modules'

    executable 'npm'
    args 'install'
}

task grunt(type: Grunt, dependsOn: installGruntDependencies) {
    commands = [project.name]
}

Tests in Gradle

  • Gradle hat andere Regeln für Ausführen von Unit-Tests als Maven
  • Konventionen für Integrationstest einführen
    • src/integTest/java/../*IntegTest.java

Plugins

  • java
  • war
  • maven-publish
  • gradle-jrebel-plugin
  • idea
  • (sonar-runner)
  • (gradle-tomcat-plugin)

früher

  • Maven 3
  • 30 Subprojekte
  • 7000 LOC in XML
    • Davon viel Ant
  • mvn clean install -Pminify  ~ 15 min
  • Kein inkrementeller Build
  • Release dauert 50 min

jetzt

  • Gradle 2.1
  • 30 Subprojekte
  • 1000 LOC in Groovy
    • 300 LOC JS (Grunt)
  • ./gradlew clean grunt build ~ 5 min
  • Inkrementeller Build ~ 31s
  • Release dauert 6 min (wegen Jenkins)

Lessons Learned

  • Bei einer Migration werden sehr viele Probleme entdeckt
  • Zuerst Migrieren, dann optimieren
    • Maven und Gradle gleichzeitig ist schwierig
    • Maven Know-How ist gewünscht
  • Alle Ant Tasks von Maven im Schritt 1 übernehmen
    • Im Schritt 2 kann man Ant komplett ersetzen
  • Viele Plugins sind nicht sinvoll

Wohin wir wollen

  • Infrastruktur gehört auch zum Build
    • vagrant, chef, docker
    • funktionelle Tests im Build integrieren
    • funktionelle Tests nicht mehr gegen DEV/UAT - Systeme
  • SonarCube
  • Know-How im Teams verteilen

Leseempfehlungen

WeltN24 is hiring :)

Made with Slides.com