Kolumne

Hitchhiker’s Guide to Docs as Code: Build-Magie

Gernot Starke, Ralf D. Müller

©S&S Media

„Jede hinreichend fortschrittliche Technologie ist von Magie nicht zu unterscheiden.“ Dieses Zitat von Arthur C. Clarke trifft auf vieles zu, was ein modernes Build-Skript stellenweise leistet. In dieser Folge unserer Kolumne lüften wir das Geheimnis und erklären einige der nützlichen Gradle-Features, die Sie für Ihre Dokumentation verwenden können. Sollte das Ihr erstes Date mit Gradle sein, empfehlen wir zuerst die Lektüre einer entsprechenden Einführung [1].

Anatomie eines Build-Skripts

Listing 1 zeigt ein typisches Build-Skript für eine Spring-Boot-Anwendung, erstellt über den Spring Initializr [2]. Auffällig sind die doppelten Abschnitte repositories und dependencies. Der erste der beiden – innerhalb des buildscript {}-Abschnitts – ist für die Abhängigkeiten während des Builds zuständig und somit für Sie interessant. Der zweite Abschnitt definiert die Abhängigkeiten des Codes, der gebaut wird. Da Sie Dokumentation bauen, werden Sie diesen Abschnitt nicht benötigen.

buildscript {
  ext {
    springBootVersion = '1.5.10.RELEASE'
  }
  repositories { mavenCentral() }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
  }
}

apply plugin: 'groovy'
apply plugin: 'org.springframework.boot'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories { mavenCentral() }

dependencies {
  compile('org.springframework.boot:spring-boot-starter-web')
  compile('org.codehaus.groovy:groovy')
  testCompile('org.springframework.boot:spring-boot-starter-test')
}

 

 

Auch der apply-plugin-Abschnitt wird für den Docs-as-Code-Ansatz benötigt. Listing 2 zeigt einen Ausschnitt aus dem docToolchain-build.gradle-File [3], in dem das AsciiDoc-Plug-in definiert und über die Abhängigkeiten um das Diagramm- und PDF-Plug-in ergänzt wird (ja, auch htmlSanityCheck wird als Plug-in referenziert).

plugins {
  id "org.asciidoctor.convert" version "1.5.3"
  id "org.aim42.htmlSanityCheck" version "0.9.7"
}

dependencies {
  asciidoctor 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.15'
  asciidoctor 'org.asciidoctor:asciidoctorj-diagram:1.5.4.1'
}

Für die AsciiDoc-Plug-ins ist dabei die Definition mit asciidoctor wichtig, damit alles funktioniert. Sie wird anstelle der Ausdrücke genutzt, die man von Java her kennt, wie compile oder runtime.

Gradle bringt viele Tasks für die gängigsten Aufgaben eines Build-Systems mit. Wir wollen Ihnen hier kurz zeigen, wie Sie zu ganz individuellen Build-Schritten Ihres Docs-as-Code-Projekts kommen.

Eigene Tasks

Um ein Gradle-Skript zu erweitern, können Sie einen eigenen Gradle-Task schreiben. Listing 3 zeigt dafür ein Grundgerüst. Solche Tasks schreiben Sie über eine Groovy-basierte DSL (Domain Specific Language).

task helloWorld (
  description: 'just a greeting',
  group: 'demo',
  dependsOn: [],
) doLast {
  10.times { i ->
    println "Hello World"
  }
}

Die Attribute description und group dienen der Dokumentation, mit dem Befehl gradle tasks erhalten Sie eine Liste aller Tasks mit Namen und description nach group geordnet. Möchten Sie einen Task als internen Task verstecken, dann setzen Sie das Attribut group auf null – das ist oft hilfreich, wenn Sie vor mehreren ähnlichen Build-Schritten den gleichen vorbereitenden Schritt ausführen möchten. So generiert die docToolchain das Zwischenformat .docBook für .epub und .docx, was wir ansonsten nicht benötigen.

Das Attribut dependsOn enthält eine Liste von Tasks, die vor dem hier definierten Task ausgeführt werden müssen. Sollte es sich um nur einen einzelnen Task handeln, könnten Sie das einfach als String definieren – aber verwenden Sie zur besseren Lesbarkeit lieber eine Liste (in Listing 3 ist diese leer).

Der doLast-Codeblock wird in die Liste der auszuführenden Aktionen des Tasks hinten eingereiht. Theoretisch können Sie mehrere solcher Blöcke definieren, und auch das Statement doFirst ist vorhanden. In der Praxis reicht aber meist ein solcher Block, da Sie beliebigen Code hineinpacken können. Mit gradle helloWorld können Sie diesen Task nun ausführen.

Input- und Outputfiles

Da Sie auch im Docs-as-Code-Ansatz, genauso wie beim Bauen Ihrer Anwendung, mit Dateien arbeiten und nur die veränderten Dateien neu bauen wollen, fehlt jetzt noch die Definition der Input- und Outputfiles, damit Gradle überprüfen kann, ob es die entsprechenden Tasks überhaupt ausführen muss. Dies steuern Sie über die Schlüsselwörter inputs und outputs zur Konfigurationsphase des Tasks. Ein Beispiel sehen Sie in Listing 4 (vgl. Kasten: „Linter“). Alle Zeilen außerhalb des doLast-Blocks werden in der Konfigurationsphase ausgeführt. Durch die Angabe des Inputverzeichnisses und des Outputfiles kann Gradle nun entscheiden, bei unveränderten Dateien den Task nicht auszuführen. Probieren Sie es aus, indem Sie das Skript zweimal aufrufen.

ext {
  docsDir = new File('./src/docs')
  reportFile = new File(
    project.buildDir,
    'report.txt'
  )
}
task analyzeText (
  description: 'einfache Textanalyse',
  group: 'docToolchain',
  dependsOn: [],
) {
  inputs.dir docsDir
  outputs.files reportFile
  doLast {
    def srcFolder = docsDir
    reportFile.write("Analyse Ergebnis: \n", "UTF-8")
    srcFolder.eachFile { file ->
      if (file.name.endsWith('.adoc')) {
        println "analysiere " + file.name
        file.text.split("[^a-zA-Zäöü]")
          .each { word ->
          if (word.endsWith("bar")) {
            reportFile.append(
              "> Warning: bitte ersetze " +
              "'${word}' durch " +
              "'es lässt sich " +
              "${word - 'bar' + 'en'}' \n",
              "UTF-8"
            )
          }
        }
      }
    }
  }
}

defaultTasks 'analyzeText'

Linter

In Listing 4 haben wir zum Spaß eine kleine Textanalyse implementiert. Was Sie für Ihren Code als Linter kennen (also im weitesten Sinne eine Analyse des Schreibstils), gibt es auch für Ihre Dokumentation. Chris Ward hat in einem Blogbeitrag eine Liste von Linters für englische Texte zusammengetragen [4].

 

Properties

DRY (Don’t Repeat Yourself) gilt auch bei Docs as Code. Einige Eigenschaften Ihrer Tasks werden Sie an mehreren Stellen wiederholen, wie z. B. das aktuelle Datum der Erstellung Ihres Dokuments oder den Namen des Autors. Sie können Gradle-Build-Skripte auf vielfältige Weise parametrisieren. Die wichtigsten sind aber wohl das gradle.properties-File und die in build.gradle definierten zusätzlichen Werte, genannt Properties.

Das gradle.properties-File erstellen Sie einfach im gleichen Verzeichnis wie das build.gradle-Skript. Die Extra Properties [5] definieren Sie in einem speziellen ext-Block des Build-Skripts, wie in den ersten Zeilen von Listing 4 zu sehen.

helloName = Universe
currentDate = new Date().format('dd.mm.yy')

 

All diese Properties referenzieren Sie nun aus Ihrem Task als wären es lokale Variablen. Ob die Properties im gradle.properties-File oder als Extended Properties definiert wurden, macht dabei keinen Unterschied. Sie sind bei der Definition auch nicht auf Strings beschränkt, wodurch Sie Ihren Build lesbarer gestalten können – auch das sehen Sie im Beispiel oben und in Listing 4.

Hitchhikers Guide to Docs as Code

Mit ihrer Kolumne Hitchhikers Guide to Docs as Code – kurz HHGDC – möchten die beiden Autoren Gernot Starke (innoq) und Ralf D. Müller (freier Softwareentwickler) die Mühen der Dokumentation lindern. Ihr Ansatz ist es Softwaredokumentation wie Code zu behandeln: schreiben, bauen, testen, committen, mergen.

Skript-Plug-ins

Damit Sie auch in umfangreichen Build-Skripten den Überblick behalten, sollten Sie die einzelnen Build-Schritte in sogenannten Skript-Plug-ins organisieren. Solche Schritte sind Taskdefinitionen, die Sie in einzelne Dateien auslagern (sprich: modularisieren). In Ihrer build.gradle-Datei referenzieren Sie diese einzelnen Skript-Plug-ins (= Dateien) über apply: ‚helloWorld.gradle‘. Unserer Ansicht nach genügt diese einfache Modularisierung, um Ihren Build verständlich zu strukturieren.

Livepreview

In der letzten Folge haben wir Ihnen ein paar fortschrittliche Browser und IDEs vorgestellt, die eine Livepreview Ihrer Dokumentation anbieten. Immer, wenn Sie die Quellen Ihrer Dokumentation ändern, wird die Voransicht neu aufgebaut.

Was aber, wenn Ihr Lieblingseditor das nicht unterstützt? Gradle holt in diesem Fall zusammen mit einem modernen Browser ein Ass aus dem Ärmel: Continuous Builds.

Starten Sie in einer Shell die AsciiDoc-Konvertierung mit ./gradlew -t asciidoctor (-t steht für für –continuous). Gradle wird nun Ihre Dokumentation als HTML generieren. Öffnen Sie die generierte Dokumentation mit Chrome, um sich davon zu überzeugen. Gradle beendet den Build aber nicht, sondern wartet nun auf Änderungen an den Quelldateien. Öffnen Sie die Quelldatei in einem Editor und beobachten Sie, was bei einer Änderung passiert. Richtig – nicht viel. Außer dass Gradle die HTML-Ausgabe neu generiert. Wenn Sie jetzt in Chrome das LiveReload-Plug-in installieren (in den Einstellungen das Häkchen bei Allow Access to file URLs setzen!) und in einer weiteren Shell den Befehl ./gradlew livereload absetzen, können Sie Chrome über das Plug-in mit dem von Gradle gestarteten LiveReload-Server verbinden. Eine Änderung an der Quelldatei stößt nun einen Re-Build an, was den Browser zum Neuladen der Seite veranlasst. Gefunden haben wir dieses Beispiel übrigens im Asciidoctor-Examples-Repository [6].

Fazit

Mit den hier vorgestellten Ansätzen können Sie die AsciiDoc-Konvertierung sehr einfach und modular um eigene Build-Schritte erweitern. Der Automatisierung Ihrer Dokumentation steht somit nichts mehr im Weg. Noch mehr Tipps und Tricks zu diesem Thema finden Sie im Blog von Hubert Klein Ikkink [7].

Geschrieben von
Gernot Starke
Gernot Starke
    Informatikstudium an der RWTH Aachen, Dissertation über Software-Engineering an der J. Kepler Universität Linz. Langjährige Tätigkeit bei mehreren Software- und Beratungsunternehmen als Softwareentwickler, -architekt und technischer Projektleiter. 1996 Mitgründer und technischer Direktor des „Object Reality Center“, einer Kooperation mit Sun Microsystems. Dort Entwickler und technischer Leiter des ersten offizielle Java-Projekts von Sun in Deutschland. Seit 2011 Fellow der innoQ GmbH.  
Ralf D. Müller
Ralf D. Müller
Ralf D. Müller arbeitet als Architekt und Entwickler. Er erlebt täglich die Notwendigkeit effektiver Dokumentation. Außerdem ist er erklärter AsciiDoc-Fan und Committer bei arc42 sowie Gründer des docToolchain-Projekts.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: