Donnerstag, 3. Oktober 2013

Danke Gradle!

Evtl. bin ich nach über 25 Jahren professioneller Softwareentwicklung und Projektgeschäft etwas abgebrüht, desillusioniert und blind gegen die Schönheit des Neues geworden, aber ich finde Maven nicht besser als ANT und ANT nicht grundlegend mächtiger als make. Bisher habe ich noch mit jedem Build-Werkzeug meine Ziele erreichen können und mich dabei immer in dem Sinne verrenken müssen, daß ich nie aufscheiben konnte, was ich eigentlich meine.

"Das war schon immer so", "Da kann ja jeder kommen" und andere Leitsätze

Egal, ob es make, ant oder maven waren: Meist bestimmte das Werkzeug den Build und nicht die Entwickler oder die Art der Software, die es zusammenzubauen galt. Im wesentlichen nutze ich die Werkzeuge daher immer nur deshalb, weil sie Verfügbar im jeweiligen Kontext sind, oder weil es einfach eine breite Masse von Nutzern gibt, sodaß man nicht so alleine ist.
Im Moment nutze ich in jeden professionellen Projekt Maven und weiß daher, welche Kosten es entfesselt, um seinen Nutzen zu bringen. Den Nutzen der vernünftigen Abhängigkeitsumsetzung will ich nicht missen, aber der Rest ist weder gut lesbar noch gut im Ablauf verständlich. Ich finde die Schreibeweisen nach wie vor unleserlich und das Werkzeug unverständlich in seiner Sprache und Komplexität.

Peak Maven?

Nachdem ich also nun viele Jahre lang leidenschaftslos beim Buildwerkzeug war, sehe ich jetzt auf die umfassenden Lösungenmit Maven in großen Projekten (wie dem CoreMedia 7 Blueprint) wie auf die letzten Werke des Maven-Barock. Großartige umfassende Lösungen, die mir netto sehr helfen (weil ich sie nicht selbst schreiben mußte), Maven und das Projekt im Griff zu halten, und die so vieles bedacht haben, das ich nicht einmal bemerke.

Kleine Danksagung zum 1.8er Release

Den Grund, warum sich meine Sicht auf Buildwerkzeuge nun geändert hat, möchte ich anhand einer kleinen Begebenheit illustrieren: Immer wenn ich kleinere Softwareprojekte anderen Menschen bereitstellen will, reicht es nicht, daß ich reprodzierbar aus meiner IDE getestet eine Software purzeln lassen kann. Bevor ich also beschreibe, was ich getan habe und was eventuell bei anderen dann gerade einmal nicht genauso aussieht, schreibe ich lieber gleich die notwendigen Build-Skripte, um den Vorgang zu dokumentieren und dabei gleich zu automatisieren.
Bei tangram habe ich mich noch lange mit der Auswahl des Werkzeugs gequält, weil ich mich nicht auch noch in meiner Freitzeit mit Maven herumschlagen wollte und kein anderes Tool mir spontan unter "das machen alle so" oder "das war einfach da" vorlag. Letztlich hat Gradle über Maven gewonnen und ich habe mich ahnungslos in die Umsetzung gestürzt. Heute verstehen wir uns recht gut.
Als ich vor kurzem wieder ein paar Zeilen Code herausgeben wollte, stand ich mit einem kompletten Fremdprojekt und einem alten ANT-Script da. JFileSync sollte - weit vor der NSA-Affäre, die mir keine neuen Erkenntnisse brachte - um für mich notwendige Features erweitert werden. Das Ergebnis wollte ich wieder gerne reprodzierbar als nutzbares Programm erzeugen können. Und das ging mit Gradle so erschreckend gut und einfach - und mit wenigen Worten, daß ich mich gleich wieder in Goodies für ein schöneres Ergebnis stürzen konnte.

Gradle-Deutsch / Deutsch-Gradle

Wir haben also ein Java-Programm

apply plugin: 'java'

Und wir hätten den Code gerne sauber in aktueller Java-Version, mit UTF-8 und ohne alten Kram.

sourceCompatibility = 1.7
targetCompatibility = 1.7
compileJava.options.encoding = 'UTF-8'
compileJava.options.deprecation = true

Der Source-Code und die Ressourcen finden sich zusammen im Verzeichnis src.

sourceSets {
  main {
    java {
      srcDir 'src'
    }
    resources {
      srcDir 'src'
    }
  }
}

Damit man das ganze kompilieren kann, muß man aus diesen Quellen

repositories {
  maven { url "http://repo1.maven.org/maven2" }
  maven { url "http://sardine.googlecode.com/svn/maven/" }
  maven { url "http://repo2.maven.org/maven2/org/bouncycastle" }
}

diese Dinge besorgen

dependencies {
  compile 'org.slf4j:slf4j-api:1.5.8'
  compile 'org.slf4j:slf4j-log4j12:1.5.8'
  compile 'org.apache.httpcomponents:httpclient:4.2.5'
  compile 'commons-lang:commons-lang:2.6'
  compile 'org.apache.commons:commons-compress:1.3'
  compile 'org.bouncycastle:bcprov-jdk16:1.46'
  compile 'javax.servlet:servlet-api:2.5'
}

Ach, und ich würde gerne weiterhin Eclipse nutzen

apply plugin: 'eclipse'

Könnt Ihr Euch bitte auf ein Vereichnis für den Output einigen?

eclipse.classpath.conventionMapping.defaultOutputDir = { 
  new File(project.projectDir, 'build/classes/main')
}
Du solltest aber sauberer Arbeiten als meine IDE!

defaultTasks 'clean', 'build'

Das ganze soll am Ende ein benutzbares Programm werden.

apply plugin: 'application'

Den Einstieg findest Du dabei in dieser Java-Klasse:

mainClassName = 'jfs.JFileSync'

Wow! Du hast ja automatisch Start-Scripts für Unix und Windows erzeugt! Danke! Ich hätte da noch ein paar Verbesserungsvorschläge...

startScripts {
  doLast {
    // Add some more reasonable memory settings to JFileSync3
    unixScript.text = 

      unixScript.text.replace('DEFAULT_JVM_OPTS=""', 
                                     'DEFAULT_JVM_OPTS="-ms1280m -mx1536m -Xms1280m -Xmx1536m"')
    windowsScript.text = 

      windowsScript.text.replace('DEFAULT_JVM_OPTS=', 
                                        'DEFAULT_JVM_OPTS=-ms1280m -mx1536m -Xms1280m -Xmx1536m')
    windowsScript.text = windowsScript.text.replace('java.exe', '%JAVA_CMD%')
    windowsScript.text = 

      windowsScript.text.replace('@rem Find %JAVA_CMD%', 
                                 'set JAVA_PREFIX=start ... java.exe')
    windowsScript.text = 

      windowsScript.text.replace('"%JAVA_EXE%" %DEFAULT_JVM_OPTS%', 
                                 '%JAVA_PREFIX% "%JAVA_EXE%" %DEFAULT_JVM_OPTS%')
  }
}
Und ich habe da gerade diesen netten Launcher für Java-Anwendungen unter Windows gefunden

buildscript {
  repositories {
    maven { url "http://repo.smokejumperit.com" }
    ivy {artifactPattern 'http://gradle-launch4j.googlecode.com/files/[module]-[revision].[ext]'}
  }
  dependencies {
    classpath 'com.smokejumperit:gradle-plugins:0.8.2'
    classpath 'edu.sc.seis:gradle-launch4j:1.0.5'
  }
}

den will ich haben!

apply plugin: 'launch4j'

Schreib doch bitte einfach rein, wer das gemacht hat, danke.

launch4j {
    mainClassName = project.mainClassName
    version = '3.0.0'
    copyright = '(C) 2002-2013, J. Heidrich, M. Goellnitz'
    downloadUrl = 'https://www.dropbox.com/s/3n4snlbw9tyjgec/JFileSync3.zip'
    supportUrl = 'https://github.com/mgoellnitz/JFileSync3'
    icon = "$project.projectDir/win/JFileSync.ico"
    dontWrapJar = true
    xmlFileName = 'JFileSync3-launcher.xml'
    initialHeapSize = 1024
    maxHeapSize = 1536
}
Ach, und wo Du gerade da bist: Pack das doch noch mal etwas anders ein, als sonst

distZip {
  into(project.name) {
    from "$buildDir/launch4j"
    include '*.exe'
    include '*.xml'
  }
  into(project.name) {
    from '.'
    include 'legal/*.*'
    include 'profiles/*.*'
    include 'README.md'
  }
}

Oh, äh. Testen. Machst Du das noch schnell? Ich habe da vier Beispiele abgelegt.

apply plugin:com.smokejumperit.gradle.ExecPlugin

task(encryptionTest) << {
  // Extract distribution
  println "Extracting distribution"
  ant.unzip(src: "$buildDir/distributions/${project.name}.zip", dest: "$buildDir")
  // Extract test data
  println "Extracting test data"
  ant.unzip(src: "test/test-folders.zip", dest: "$buildDir/${project.name}")

  println "Encrypting"

  String cmd = "";
  cmd = "cmd /c bin${File.separator}${project.name} -config 1encrypt.xml -nogui -nohistory -quiet"
  project.exec(cmd, "$buildDir/${project.name}")

  println "Decrypting taking every folders metadata into account"
  cmd = "cmd /c bin${File.separator}${project.name} -config 2decrypt1.xml -nogui -nohistory -quiet"
  project.exec(cmd, "$buildDir/${project.name}")

  println "Decrypting ignoring every folders metadata"
  cmd = "cmd /c bin${File.separator}${project.name} -config 3decrypt2.xml -nogui -nohistory -quiet"
  project.exec(cmd, "$buildDir/${project.name}")

  println "And now please compare by hand"
  cmd = "cmd /c bin${File.separator}${project.name} -config 4compare.xml -nohistory"
  project.exec(cmd, "$buildDir/${project.name}")
}


Macht das bitte nicht nur in der Kürze sondern auch in so kurzer Zeit und so, daß man es immer wieder lesen kann, mit anderen Werkzeugen. Für mich ist Gradle nun weiterhin die erste Wahl in allen Projekten, in denen ich die Wahl habe.

Keine Kommentare:

Kommentar veröffentlichen