[X] Build-Systeme Teil 2: Apache Ant



  • @Side
    Sorry, dass ich erst jetzt damit komme, aber was hältst du von nem Abschnitt/Verweis zu "Installation von Ant"?



  • Kann ich mich ab Mittwoch drum kümmern.

    MfG SideWinder



  • Build-Systeme Teil 2: Apache Ant

    Inhalt:
    
    Voraussetzungen
    Einführung
    Basiswissen
    Ein Beispielprojekt
    	Property-Dateien
    	Implementierung der Build-Datei
        Implementierung der Sub-Targets
    Ein paar Dinge mehr
    What comes next...
    Anhang
    Quellen
    

    Voraussetzungen:
    - Kenntnisse über Build-Systeme im Allgemeinen (Erhältlich im 1. Teil dieser Serie)
    - Basis-Kenntnisse im Bereich XML (Erhältlich auf http://www.w3schools.com)
    - Um die Beispiele sofort ausprobieren zu können ist Ant zu installieren (Siehe http://ant.apache.org/manual/install.html#installing)

    Einführung:
    Herzlich Willkommen beim 2. Teil unserer Artikel-Serie über Build-Systeme. Nachdem GPC die theoretischen Grundlagen für mich erledigt hat, werde ich euch das Build-Tool Apache Ant näher bringen. Das von der Apache Software Foundation frei zur Verfügung gestellte Build-System ist reich an Funktionalität und einfach einzusetzen. Ant bezeichnet sich selbst als "Make[1] without wrinkles", was das Build-System meiner Meinung nach sehr treffend beschreibt.

    Einige Vorteile von Ant sind:
    -> Eine sehr mächtige BuiltIn-Bibliothek mit oft benötigten Funktionen
    -> Ant ist in reinem Java geschrieben und daher auf nahezu allen modernen Plattformen einsetzbar
    -> Dank XML sind die Build-Dateien gut strukturiert und wartbar

    Andere Build-Systeme gehen meist den Weg eingespeiste Befehle auf der Shell des Systems auszuführen, das hat den Vorteil, dass sie für jede Programmiersprache gleich gut aber auch gleich schlecht eingesetzt werden können. Ant geht einen anderen Weg: Befehle werden über eine BuiltIn-Bibliothek angeboten, das vereinfacht die Anwendung ungemein und ermöglicht mächtige Features mit wenigen Zeilen einzubinden. Jede Medaille hat aber auch ihre Kehrseite: Die BuiltIn-Bibliothek ist nahezu ausschließlich auf Java ausgelegt, was Ant auf diese Sprache einschränkt.

    Hinweis: Die BuiltIn-Bibliothek ist mit eigenen Modulen erweiterbar. Ant wäre also durchaus in der Lage auch mit anderen Programmiersprachen als Java eingesetzt zu werden. Der Aufwand ist allerdings nicht gerechtfertigt, ein anderes Build-Tool liegt da näher.

    Was diese BuiltIn-Bibliothek alles leistet wird spätestens bei folgendem Beispiel deutlich:

    Auftraggeber schrieb:

    Sämtliche Source-Dateien liegen auf einem CVS-Server[2], die neueste Version ist natürlich vor dem Build von dort zu beziehen. Danach soll der eigentliche Build-Prozess von statten gehen. Wird dieser korrekt beendet sollen automatisierte Tests am Ergebnis durchgeführt werden (Kurzfassung der Ergebnisse per Mail an den Projektleiter). Zu guter letzt soll die computergenerierte Entwickler-Dokumentation erneuert werden und der fertige Build mit der richtigen Build-Nummer auf einen Web-Server geladen werden.

    Mit einem auf Shellbefehlen basierendem Build-System würde man hier gehörig ins Schwitzen kommen, mit Ant reichte es in diesem Fall einen Ferialpraktikanten einzustellen.

    Um auch anderen zu zeigen wie einfach Ant in ein bestehendes System zu integrieren ist, möchte ich mein Wissen - learning by doing - an einem Beispielprojekt weitergeben.

    Basiswissen
    Nach den theoretischen Grundlagen kommen wir nun zur praktischen Anwendung. Im Gegensatz zu Make werden Ant die auszuführenden Targets im XML-Schema zugeführt. Der grundlegende Aufbau gleicht dem der meisten Build-Systeme:

    - Projekt (= Projekt)
    - Target (= Ziel)
      Typisches Beispiel: build, create_docs
    - Task (= Aufgabe)
      Typisches Beispiel: javac
    

    Während die Targets von uns definiert werden, verwenden wir Tasks aus der BuiltIn-Bibliothek.

    Kommen wir also zur Build-Datei. Die Datei heißt gewöhnlich build.xml und liegt im Projekt-Root-Verzeichnis. Durch Übersetzen des oben gezeigten Schemas in XML erhalten wir folgenden Grundaufbau:

    <?xml version="1.0"?>
    <project name="MeinProjekt">
    
        <target name="Ziel">
    
            <task param="value" />
            <task param="value" />
            ...
    
        </target>
        ...
    
    </project>
    

    Hinweis: Kommentare können im XML-Format gemacht werden:

    <!-- Kommentar -->
    <!-- Auch
         mehrzeilig
         möglich.
    -->
    

    Ein Beispielprojekt:
    Damit wir den genauen Aufbau der Build-Datei nicht am Trockendock der Theorie erlernen müssen, muss ein fiktiver Taschenrechner als Beispielprojekt herhalten. Die Source-Dateien liegen relativ zur Build-Datei im Verzeichnis source. Mehr ist noch nicht vorhanden. In Zukunft möchten wir im Verzeichnis /docs stets die aktuelle JavaDoc für unser Projekt haben und im Verzeichnis release jeweils die aktuelle Jar-Datei für unseren Taschenrechner (mit aktueller Build-Nummer im Namen).

    Property-Dateien:
    Bevor wir uns jetzt allerdings an die Implementierung machen können muss ich noch ein kurzes Wort über Property-Dateien verlieren. Sehr oft hat man es in Build-Dateien mit Konstanten zu tun (Pfade, Dateien, Bezeichner, etc.), damit diese nicht hardcoded in der Build-Datei - sicherlich auch noch redundant und unauffindbar für Änderungen - ihr Dasein fristen, werden sie in Property-Dateien zusammengefasst. Diese tragen die Endung ".properties" und man verwendet für die Standard-Property-Datei analog zur "build.xml" den Namen "build.properties".

    Ein Property selbst ist nicht mehr als ein Key-Value-Paar bestehend aus Konstanten-Name (Key) und -Wert (Value) getrennt durch ein Gleich-Zeichen. Alle Konstanten aus unserem Beispielprojekt ergeben folgende Property-Datei:

    project.name=Taschenrechner
    project.version=1.2
    
    path.source=source
    path.dest=classes
    path.docs=docs
    path.release=release
    

    Implementierung der Build-Datei:
    Nun denn, wollen wir unserem Taschenrechner ein ordentliches Build-System verpassen. Erweitern wir den Rahmen aus dem letzten Kapitel und wir erhalten folgende Build-Datei:

    <?xml version="1.0"?>
    <!-- Unser Projekt
         name ... Projektname
         basedir ... Basis-Verzeichnis für alle relativen Pfadangaben
         default ... Standard-Target, das ausgeführt wird, wenn Ant ohne Parameter gestartet wird
    -->
    <project name="Taschenrechner" basedir="." default="full">
    
        <!-- Kleine Beschreibung wozu die vorliegende Build-Datei gut ist -->
        <description>
        Automatisierung des Build-Prozesses für Taschenrechner 1.2
        </description>
    
        <!-- Laden unserer Property-Datei für Zugriff auf Konstanten (Details folgen unten) -->
        <property file="build.properties" />    
    
        <!-- Legt die richtig hochgezähle Build-Nummer in eine Konstante (Details folgen unten) -->
        <buildnumber />
    
        <!-- Unser Standard-Target
             name ... Target-Name
             depends ... Abhängigkeiten von anderen Targets, diese müssen ausgeführt worden sein *bevor* full ausgeführt wird
        -->
        <target name="full" depends="compile,javadoc,jar">
        </target>
    
        <!-- Sub-Targets -->
        <target name="compile">
            ...
        </target>
    
        <target name="javadoc">
        <!-- Nicht abhängig von compile - JavaDoc wird aus den Source-Dateien erzeugt und nicht aus den compilierten Class-Dateien! -->
            ...
        </target>
    
        <target name="jar" depends="compile">
            ...
        </target>
    
    </project>
    

    Es hat sich einiges getan an unserer Basis-Datei. Wir haben das Gesamt-Target in 3 Sub-Targets aufgeteilt. Zudem haben wir einige globale Tasks (= Tasks ohne Zugehörigkeit zu einem Target) angelegt, diese werden unabhängig vom gewählten Target immer ausgeführt. Gehen wir die Tasks also Schritt für Schritt durch:

    [b]Name:       description[/b]
    Attribute:  -
    Inhalt:     Beschreibung des Projekts
    

    Dieser Task wird von Ant ignoriert, er dient lediglich der Beschreibung, damit ein Entwickler weiß, um welche Datei es sich handelt.

    [b]Name:       property[/b]
    Attribute:  file ... Datei-Name
    Inhalt:     -
    

    Dieser Task lädt unsere Property-Datei die wir im vorangegangenen Kapitel angelegt haben. Ab jetzt können wir mit ${key} auf die Konstanten zugreifen.

    [b]Name:       buildnumber[/b]
    Attribute:  -
    Inhalt:     -
    

    Dieser Task legt im Verzeichnis eine zusätzliche Datei "build.number" an und speichert dort die aktuelle Build-Nummer. Wird die Build-Datei von Ant abermals ausgeführt findet der Task die Datei und erhöht die Build-Nummer. Zudem legt der Task die aktuelle Build-Nummer automatisch in das Property "build.number", auf das wir dann mit Hilfe von ${build.number} zugreifen können.

    Implementierung der Sub-Targets:
    Eigentlich hat man jetzt schon genügend Basiswissen um mit Ant vernünftig zu arbeiten. Die gesamte BuiltIn-Bibliothek ist sehr detailliert im Manual[->1] beschrieben. Dennoch möchte ich noch einige Feinheiten zeigen, daher erstmal die fehlenden Teile unserer Build-Datei:

    <target name="compile">
    
            <echo>Compiling (Beispiel-Konsolen-Ausgabe)</echo>
            <javac
                srcdir="${path.source}"
                destdir="${path.dest}"
                failonerror="true"
            />
    
        </target>
    
        <target name="javadoc">
    
            <javadoc
                sourcepath="${path.source}"
                destdir="${path.docs}"
                packagenames="*"
                access="private"
                source="1.5"
                use="true"
                windowtitle="${project.name}"
            />
    
        </target>
    
        <target name="jar" depends="compile">
    
            <jar
                basedir="${path.dest}"
                destfile="${path.release}/${project.name}-${project.version}.${build.number}.jar"
            >
                <manifest />
            </jar>
    
        </target>
    

    Endlich greifen wir ordentlich auf die BuiltIn-Bibliothek von Ant zurück. Zuerst wieder eine kleine Übersicht über die benützten Tags:

    [b]Name:       echo[/b]
    Attribute:  -
    Inhalt:     Nachricht
    

    Gibt eine Nachricht auf der Konsole aus.

    [b]Name:       javac[/b]
    Attribute:  srcdir ... Pfad in dem unsere Java-Dateien liegen
                destdir ... Pfad in den die Class-Dateien kommen sollen
    Inhalt:     -
    

    Dieser Task ruft den Java-Compiler auf.

    [b]Name:       javadoc[/b]
    Attribute:  sourcepath ... Pfad in dem unsere Java-Dateien liegen
                destdir ... Pfad in dem die fertige JavaDoc landen soll
                packagenames ... Packages die dokumentiert werden sollen (* = alle Packages)
                access ... Bis zu welchem Grad soll dokumentiert werden (private = Alle Methoden, public = Nur öffentliche Methoden, etc.)
                source ... Java-Version des Source
                use ... Soll ein Use-File erstellt werden?
                windowtitle ... Kommt in den <title>-Tag der HTML-Dokumente
    Inhalt:     -
    

    Dieser Task ruft den JavaDoc-Builder auf, er lässt sich sehr detailliert konfigurieren über Ant. Alle Attribute findet man im Ant-Manual.

    [b]Name:       jar[/b]
    Attribute:  basedir ... Pfad in dem die Class-Dateien liegen
                destfile ... Angabe des Pfad- und Dateinamens an dem die fertige Jar-Datei gespeichert werden soll
    Inhalt:     -
    

    Ruft den Jar-Packer auf.

    [b]Name:       manifest[/b]
    Attribute:  -
    Inhalt:     -
    

    Weist den Jar-Packer darauf hin, auch eine Manifest-Datei zu erzeugen.

    Damit haben wir erst einmal unsere Build-Datei fertiggestellt, mit einem Aufruf von:

    ant
    

    im Verzeichnis der build.xml führt Ant das Default-Target für uns aus, ein bestimmtes Target kann natürlich ebenfalls mit angegeben werden:

    ant javadoc
    

    Ein paar Dinge mehr:
    -> Manchen Tasks kann ein bestimmtes Attribut gesetzt werden, in dem man etwas in den Body des Tags schreibt. So muss man z. B. nicht <echo message="Blubb" /> schreiben sondern kann bequem <echo>Blubb</echo> verwenden
    -> Viele Tasks besitzen ein Attribut failonerror, das - wenn auf "true" gesetzt - den gesamten Ant-Build-Prozess beendet wenn ein Fehler auftritt (so kann man z. B. verhindern, dass trotz Fehler im Compile-Vorgang ein Release auf einen Webserver geladen wird)
    -> Einige Tasks unterstützen Sub-Tasks, so verhilft uns der Task <manifest> im <jar>-Task zu einer Manifest-Datei (siehe oben), diese Sub-Tags fallen unter den Begriff "Nested Tags"

    Den Start von Ant auch noch automatisieren...
    ...ist ebenfalls kein Problem. Alle gängigen Betriebssysteme bietet heutzutage Möglichkeiten dazu: Unix - cronjob, Windows - Geplante Tasks.

    Erweiterung von Ant:
    Ant ist über Java-Klassen erweiterbar. So gibt es externe 3rd-Party-Module zum fertigen Download aber man ist auch in der Lage eigene Module zu entwickeln. Das würde allerdings den Rahmen des Artikels sprengen (Bei Interesse zeige ich es vielleicht in einem zukünftigen Artikel).

    What comes next...
    Im nächsten Artikel unserer Serie wird Talla uns MSBuild näher bringen 🙂

    Anhang:
    [1] Make ist ebenfalls ein Build-Tool, mehr Informationen zu Make findet man auf http://de.wikipedia.org/wiki/Make .
    [2] CVS ist ein sehr verbreitetes Version-Control-System. Version-Control-Systeme ermöglichen eine nachvollziehbare und merge-konfliktfreie gleichzeitige Entwicklung an Software von mehreren Entwicklern, mehr Informationen zu CVS findet man auf https://www.cvshome.org .
    [3] Ant erlaubt es Tasks mittels Java selbst zu schreiben, mehr Informationen findet man im Ant-Manual auf http://ant.apache.org/manual/develop.html#writingowntask .

    Quellen:
    [1] http://ant.apache.org/manual/index.html



  • Hab alle Punkte jetzt mal in eine Art "vorläufig endgültige" Version gepackt.

    GPC schrieb:

    @Side
    Sorry, dass ich erst jetzt damit komme, aber was hältst du von nem Abschnitt/Verweis zu "Installation von Ant"?

    Sicher das man da was schreiben soll? Man entpackt Ant und fügt das Ant\Bin dem Pfad hinzu... 😉 Für ganz penible kann man auch noch ANT_HOME setzen. Aber eventuell ein paar Zeilen. Werd ich morgen dann machen 🙂

    BTW: Da wir offenbar niemanden mehr haben der am Forum frickelt, wird das mit den XML-Kommentaren eine dumme Sache. Ich werd nochmal Druck machen.

    MfG SideWinder



  • Hallo,

    SideWinder schrieb:

    GPC schrieb:

    @Side
    Sorry, dass ich erst jetzt damit komme, aber was hältst du von nem Abschnitt/Verweis zu "Installation von Ant"?

    Sicher das man da was schreiben soll? Man entpackt Ant und fügt das Ant\Bin dem Pfad hinzu... 😉 Für ganz penible kann man auch noch ANT_HOME setzen. Aber eventuell ein paar Zeilen. Werd ich morgen dann machen 🙂

    Hm, vllt. zwei Sätze und ein Link auf die Installations-Anleitung von der Apache Foundation.

    BTW: Da wir offenbar niemanden mehr haben der am Forum frickelt, wird das mit den XML-Kommentaren eine dumme Sache. Ich werd nochmal Druck machen.

    Mach das, momentan sieht man die Kommentare ein bisserl schlechte 🙂

    Btw. Kennst du maven? Ist ebenfalls von der Apache Foundation, ziemlich heißes Teil. Noch mächtiger als Ant. Vllt. werd ich noch was dazu schreiben. Mal sehen...



  • Maven ist noch eine Ebene höher als Ant, da musst du nicht einmal mehr wissen wie du compilierst. Du sagst bloß nur noch "compile" und der weiß aus der Projekt-Konfiguration wo die einzelnen Verzeichnisse sind und sucht sich das alles selbst zusammen. Hab aber noch nie damit gearbeitet. Aber ein Artikel drüber wäre in der Tat nett 🙂

    Hm, vllt. zwei Sätze und ein Link auf die Installations-Anleitung von der Apache Foundation.

    sth like that, werd am Abend noch einen Absatz einbauen :xmas1:

    MfG SideWinder



  • SideWinder schrieb:

    sth like that, werd am Abend noch einen Absatz einbauen :xmas1:

    wenn ich korrigieren soll, dann solltest du den absatz langsam mal einbauen. 😉

    Mr. B



  • Hab den Absatz wieder gelöscht, und stattdessen einen Punkt unter Voraussetzungen hinzugefügt. Ist das soweit okay? Wenn GPC das reicht kannst du dann die RK durchführen MrB :xmas1:

    MfG SideWinder



  • wäre das dann die letzte version, die hier gepostet wurde?

    Mr. B



  • Mr. B schrieb:

    wäre das dann die letzte version, die hier gepostet wurde?

    Mr. B

    Ja, die Version die hier auf Seite 4 des Threads steht.

    MfG SideWinder



  • okay... was soll ich machen? soll ich anfangen oder noch aufn kommentar von GPC warten??? 🙂

    Mr. B



  • Mr. B schrieb:

    okay... was soll ich machen? soll ich anfangen oder noch aufn kommentar von GPC warten??? 🙂

    Mr. B

    Fang an, im Notfall musst du später doch noch einen zusätzlichen Absatz korregieren. Der Rest bleibt aber auf jeden Fall wie er ist 🙂

    MfG SideWinder



  • Ist inhaltlich, soweit ich es sehe, alles in Ordnung. So hab ich mir das gedacht mit dem Install-Verweis 🙂



  • Build-Systeme Teil 2: Apache Ant

    Inhalt:
    
    Voraussetzungen
    Einführung
    Basiswissen
    Ein Beispielprojekt
    	Property-Dateien
    	Implementierung der Build-Datei
        Implementierung der Sub-Targets
    Ein paar Dinge mehr
    What comes next...
    Anhang
    Quellen
    

    Voraussetzungen:
    - Kenntnisse über Build-Systeme im Allgemeinen (Erhältlich im 1. Teil dieser Serie)
    - Basis-Kenntnisse im Bereich XML (Erhältlich auf http://www.w3schools.com)
    - Um die Beispiele sofort ausprobieren zu können, ist Ant zu installieren (Siehe http://ant.apache.org/manual/install.html#installing)

    Einführung:
    Herzlich Willkommen beim 2. Teil unserer Artikelserie über Build-Systeme. Nachdem GPC die theoretischen Grundlagen für mich erledigt hat, werde ich euch das Build-Tool Apache Ant näher bringen. Das von der Apache Software Foundation frei zur Verfügung gestellte Build-System ist reich an Funktionalität und einfach einzusetzen. Ant bezeichnet sich selbst als "Make[1] without wrinkles", was das Build-System meiner Meinung nach sehr treffend beschreibt.

    Einige Vorteile von Ant sind:
    -> Eine sehr mächtige BuiltIn-Bibliothek mit oft benötigten Funktionen
    -> Ant ist in reinem Java geschrieben und daher auf nahezu allen modernen Plattformen einsetzbar
    -> Dank XML sind die Build-Dateien gut strukturiert und gut zu warten

    Andere Build-Systeme gehen meist den Weg, eingespeiste Befehle auf der Shell des Systems auszuführen, was den Vorteil hat, dass sie für jede Programmiersprache gleich gut, aber auch gleich schlecht eingesetzt werden können. Ant geht einen anderen Weg: Befehle werden über eine BuiltIn-Bibliothek angeboten; das vereinfacht die Anwendung ungemein und ermöglicht mächtige Features mit wenigen Zeilen einzubinden. Jede Medaille hat aber auch ihre Kehrseite: Die BuiltIn-Bibliothek ist nahezu ausschließlich auf Java ausgelegt, was Ant auf diese Sprache einschränkt.

    Hinweis: Die BuiltIn-Bibliothek kann man mit eigenen Modulen erweitern. Ant wäre also durchaus in der Lage auch mit anderen Programmiersprachen als Java eingesetzt zu werden. Der Aufwand ist allerdings nicht gerechtfertigt, ein anderes Build-Tool liegt da näher.

    Was diese BuiltIn-Bibliothek alles leistet, wird spätestens bei folgendem Beispiel deutlich:

    Auftraggeber schrieb:

    Sämtliche Source-Dateien liegen auf einem CVS-Server[2]. Die neueste Version ist natürlich vor dem Build von dort zu beziehen. Danach soll der eigentliche Build-Prozess vonstatten gehen. Wird dieser korrekt beendet, sollen automatisierte Tests am Ergebnis durchgeführt werden (Kurzfassung der Ergebnisse per Mail an den Projektleiter). Zu guter Letzt soll die computergenerierte Entwicklerdokumentation erneuert werden und der fertige Build mit der richtigen Build-Nummer auf einen Webserver geladen werden.

    Mit einem auf Shellbefehlen basierenden Build-System würde man hier gehörig ins Schwitzen kommen, mit Ant reichte es in diesem Fall***,*** einen Ferialpraktikanten einzustellen.

    Um auch anderen zu zeigen, wie einfach Ant in ein bestehendes System zu integrieren ist, möchte ich mein Wissen - learning by doing - an einem Beispielprojekt weitergeben.

    Basiswissen
    Nach den theoretischen Grundlagen kommen wir nun zur praktischen Anwendung. Im Gegensatz zu Make werden bei Ant die auszuführenden Targets im XML-Schema zugeführt. Der grundlegende Aufbau gleicht dem der meisten Build-Systeme:

    - Projekt (= Projekt)
    - Target (= Ziel)
      Typisches Beispiel: build, create_docs
    - Task (= Aufgabe)
      Typisches Beispiel: javac
    

    Während die Targets von uns definiert werden, verwenden wir Tasks aus der BuiltIn-Bibliothek.

    Kommen wir also zur Build-Datei. Die Datei heißt gewöhnlich build.xml und liegt im Projekt-Root-Verzeichnis. Durch Übersetzen des oben gezeigten Schemas in XML erhalten wir folgenden Grundaufbau:

    <?xml version="1.0"?>
    <project name="MeinProjekt">
    
        <target name="Ziel">
    
            <task param="value" />
            <task param="value" />
            ...
    
        </target>
        ...
    
    </project>
    

    Hinweis: Kommentare können im XML-Format gemacht werden:

    <!-- Kommentar -->
    <!-- Auch
         mehrzeilig
         möglich.
    -->
    

    Ein Beispielprojekt:
    Damit wir den genauen Aufbau der Build-Datei nicht am Trockendock der Theorie erlernen müssen, muss ein fiktiver Taschenrechner als Beispielprojekt herhalten. Die Source-Dateien liegen relativ zur Build-Datei im Verzeichnis source ~muss nach dem "relativ" nicht ein wörtchen folgen?~. Mehr ist noch nicht vorhanden. In Zukunft möchten wir im Verzeichnis /docs stets die aktuelle JavaDoc für unser Projekt haben und im Verzeichnis release jeweils die aktuelle Jar-Datei für unseren Taschenrechner (mit aktueller Build-Nummer im Namen).

    Property-Dateien:
    Bevor wir uns jetzt allerdings an die Implementierung machen können, muss ich noch ein kurzes Wort über Property-Dateien verlieren. Sehr oft hat man es in Build-Dateien mit Konstanten zu tun (Pfade, Dateien, Bezeichner, etc.), damit diese nicht hardcoded in der Build-Datei - sicherlich auch noch redundant und unauffindbar für Änderungen - ihr Dasein fristen, werden sie in Property-Dateien zusammengefasst. Diese tragen die Endung ".properties" und man verwendet für die Standard-Property-Datei analog zur "build.xml" den Namen "build.properties".

    Ein Property selbst ist nicht mehr als ein Key-Value-Paar bestehend aus Konstanten-Name (Key) und -Wert (Value) getrennt durch ein Gleichzeichen. Alle Konstanten aus unserem Beispielprojekt ergeben folgende Property-Datei:

    project.name=Taschenrechner
    project.version=1.2
    
    path.source=source
    path.dest=classes
    path.docs=docs
    path.release=release
    

    Implementierung der Build-Datei:
    Nun denn, wollen wir unserem Taschenrechner ein ordentliches Build-System verpassen. Erweitern wir den Rahmen aus dem letzten Kapitel, so erhalten wir folgende Build-Datei:

    <?xml version="1.0"?>
    <!-- Unser Projekt
         name ... Projektname
         basedir ... Basis-Verzeichnis für alle relativen Pfadangaben
         default ... Standard-Target, das ausgeführt wird, wenn Ant ohne Parameter gestartet wird
    -->
    <project name="Taschenrechner" basedir="." default="full">
    
        <!-- Kleine Beschreibung, wozu die vorliegende Build-Datei gut ist -->
        <description>
        Automatisierung des Build-Prozesses für Taschenrechner 1.2
        </description>
    
        <!-- Laden unserer Property-Datei für Zugriff auf Konstanten (Details folgen unten) -->
        <property file="build.properties" />    
    
        <!-- Legt die richtig hochgezähle Build-Nummer in eine Konstante (Details folgen unten) -->
        <buildnumber />
    
        <!-- Unser Standard-Target
             name ... Target-Name
             depends ... Abhängigkeiten von anderen Targets, diese müssen ausgeführt worden sein *bevor* full ausgeführt wird
        -->
        <target name="full" depends="compile,javadoc,jar">
        </target>
    
        <!-- Sub-Targets -->
        <target name="compile">
            ...
        </target>
    
        <target name="javadoc">
        <!-- Nicht abhängig von compile - JavaDoc wird aus den Source-Dateien erzeugt und nicht aus den kompilierten Class-Dateien! -->
            ...
        </target>
    
        <target name="jar" depends="compile">
            ...
        </target>
    
    </project>
    

    Es hat sich einiges getan an unserer Basisdatei. Wir haben das Gesamt-Target in 3 Sub-Targets aufgeteilt. Zudem haben wir einige globale Tasks (= Tasks ohne Zugehörigkeit zu einem Target) angelegt. Diese werden unabhängig vom gewählten Target immer ausgeführt. Gehen wir die Tasks also Schritt für Schritt durch:

    [b]Name:       description[/b]
    Attribute:  -
    Inhalt:     Beschreibung des Projekts
    

    Dieser Task wird von Ant ignoriert. Er dient lediglich der Beschreibung, damit ein Entwickler weiß, um welche Datei es sich handelt.

    [b]Name:       property[/b]
    Attribute:  file ... Datei-Name
    Inhalt:     -
    

    Dieser Task lädt unsere Property-Datei, die wir im vorangegangenen Kapitel angelegt haben. Ab jetzt können wir mit ${key} auf die Konstanten zugreifen.

    [b]Name:       buildnumber[/b]
    Attribute:  -
    Inhalt:     -
    

    Dieser Task legt im Verzeichnis eine zusätzliche Datei "build.number" an und speichert dort die aktuelle Build-Nummer. Wird die Build-Datei von Ant abermals ausgeführt findet der Task die Datei und erhöht die Build-Nummer. Zudem legt der Task die aktuelle Build-Nummer automatisch in das Property "build.number", auf das wir dann mit Hilfe von ${build.number} zugreifen können.

    Implementierung der Sub-Targets:
    Eigentlich hat man jetzt schon genügend Basiswissen, um mit Ant vernünftig zu arbeiten. Die gesamte BuiltIn-Bibliothek ist sehr detailliert im Manual[->1] beschrieben. Dennoch möchte ich noch einige Feinheiten zeigen, daher erstmal die fehlenden Teile unserer Build-Datei:

    <target name="compile">
    
            <echo>Compiling (Beispiel-Konsolen-Ausgabe)</echo>
            <javac
                srcdir="${path.source}"
                destdir="${path.dest}"
                failonerror="true"
            />
    
        </target>
    
        <target name="javadoc">
    
            <javadoc
                sourcepath="${path.source}"
                destdir="${path.docs}"
                packagenames="*"
                access="private"
                source="1.5"
                use="true"
                windowtitle="${project.name}"
            />
    
        </target>
    
        <target name="jar" depends="compile">
    
            <jar
                basedir="${path.dest}"
                destfile="${path.release}/${project.name}-${project.version}.${build.number}.jar"
            >
                <manifest />
            </jar>
    
        </target>
    

    Endlich greifen wir ordentlich auf die BuiltIn-Bibliothek von Ant zurück. Zuerst wieder eine kleine Übersicht über die benutzten Tags:

    [b]Name:       echo[/b]
    Attribute:  -
    Inhalt:     Nachricht
    

    Gibt eine Nachricht auf der Konsole aus.

    [b]Name:       javac[/b]
    Attribute:  srcdir ... Pfad, in dem unsere Java-Dateien liegen
                destdir ... Pfad, in den die Class-Dateien kommen sollen
    Inhalt:     -
    

    Dieser Task ruft den Java-Compiler auf.

    [b]Name:       javadoc[/b]
    Attribute:  sourcepath ... Pfad, in dem unsere Java-Dateien liegen
                destdir ... Pfad, in dem die fertige JavaDoc landen soll
                packagenames ... Packages, die dokumentiert werden sollen (* = alle Packages)
                access ... Bis zu welchem Grad soll dokumentiert werden (private = Alle Methoden, public = Nur öffentliche Methoden, etc.)
                source ... Java-Version des Source
                use ... Soll ein Use-File erstellt werden?
                windowtitle ... Kommt in den <title>-Tag der HTML-Dokumente
    Inhalt:     -
    

    Dieser Task ruft den JavaDoc-Builder auf. Er lässt sich sehr detailliert konfigurieren über Ant. Alle Attribute findet man im Ant-Manual.

    [b]Name:       jar[/b]
    Attribute:  basedir ... Pfad, in dem die Class-Dateien liegen
                destfile ... Angabe des Pfad- und Dateinamens, an dem die fertige Jar-Datei gespeichert werden soll
    Inhalt:     -
    

    Ruft den Jar-Packer auf.

    [b]Name:       manifest[/b]
    Attribute:  -
    Inhalt:     -
    

    Weist den Jar-Packer darauf hin, auch eine Manifest-Datei zu erzeugen.

    Damit haben wir erst einmal unsere Build-Datei fertiggestellt mit einem Aufruf von:

    ant
    

    Im Verzeichnis der build.xml führt Ant das Default-Target für uns aus. Ein bestimmtes Target kann natürlich ebenfalls mit angegeben werden:

    ant javadoc
    

    Ein paar Dinge mehr:
    -> Manchen Tasks kann ein bestimmtes Attribut gesetzt werden, indem man etwas in den Body des Tags schreibt. So muss man z. B. nicht <echo message="Blubb" /> schreiben, sondern kann bequem <echo>Blubb</echo> verwenden.
    -> Viele Tasks besitzen ein Attribut failonerror, das - wenn auf "true" gesetzt - den gesamten Ant-Build-Prozess beendet, wenn ein Fehler auftritt (so kann man z. B. verhindern, dass trotz Fehler im Compile-Vorgang ein Release auf einen Webserver geladen wird)
    -> Einige Tasks unterstützen Sub-Tasks. So verhilft uns der Task <manifest> im <jar>-Task zu einer Manifest-Datei (siehe oben). Diese Sub-Tags fallen unter den Begriff "Nested Tags".

    Den Start von Ant auch noch automatisieren...
    ...ist ebenfalls kein Problem. Alle gängigen Betriebssysteme bieten heutzutage Möglichkeiten dazu: Unix - cronjob, Windows - Geplante Tasks.

    Erweiterung von Ant:
    Ant ist über Java-Klassen erweiterbar. So gibt es externe 3rd-Party-Module zum fertigen Download, aber man ist auch in der Lage eigene Module zu entwickeln. Das würde allerdings den Rahmen des Artikels sprengen (bei Interesse zeige ich es vielleicht in einem zukünftigen Artikel).

    What comes next...
    Im nächsten Artikel unserer Serie wird Talla uns MSBuild näher bringen 🙂

    Anhang:
    [1] Make ist ebenfalls ein Build-Tool, mehr Informationen zu Make findet man auf http://de.wikipedia.org/wiki/Make .
    [2] CVS ist ein sehr verbreitetes Version-Control-System. Version-Control-Systeme ermöglichen eine nachvollziehbare und merge-konfliktfreie, gleichzeitige Entwicklung an Software von mehreren Entwicklern. Mehr Informationen zu CVS findet man auf https://www.cvshome.org .
    [3] Ant erlaubt es, Tasks mittels Java selbst zu schreiben, mehr Informationen findet man im Ant-Manual auf http://ant.apache.org/manual/develop.html#writingowntask .

    Quellen:
    [1] http://ant.apache.org/manual/index.html



  • Insgesamt recht fehlerfrei. Eine Anmerkung ist jedoch im Text enthalten. Such mal nach kleinem Text, dann musst du dir nich alles durchlesen.

    Zwei Dinge seien jedoch gesagt:

    Sch**** auf den Bindestrich und schreib mal Wörter einfach so zusammen. Ich habe das jetzt mal bei allen englischen Wörtern durchgehen lassen, aber so was wie "Webserver" oder "Subtask" kann man eigentlich zusammenschreiben. Da bindet der Bindestrich nicht mehr, sondern da trennt er wohl eher.

    Außerdem ist mir oft aufgefallen, dass du manchmal einfach zwei Hauptsätze mit nem Komma verbindest. Das ist so nicht erlaubt. Entweder du setzt ein "und" dazwischen, was auf Dauer sehr grässlich klingt, oder du setzt ab und an einfach mal nen Punkt! 😉

    Mr. B



  • Build-Systeme Teil 2: Apache Ant

    Inhalt:
    
    Voraussetzungen
    Einführung
    Basiswissen
    Ein Beispielprojekt
    	Property-Dateien
    	Implementierung der Build-Datei
        Implementierung der Sub-Targets
    Ein paar Dinge mehr
    What comes next...
    Anhang
    Quellen
    

    Voraussetzungen:
    - Kenntnisse über Build-Systeme im Allgemeinen (Erhältlich im 1. Teil dieser Serie)
    - Basis-Kenntnisse im Bereich XML (Erhältlich auf http://www.w3schools.com)
    - Um die Beispiele sofort ausprobieren zu können, ist Ant zu installieren (Siehe http://ant.apache.org/manual/install.html#installing)

    Einführung:
    Herzlich Willkommen beim 2. Teil unserer Artikelserie über Build-Systeme. Nachdem GPC die theoretischen Grundlagen für mich erledigt hat, werde ich euch das Build-Tool Apache Ant näher bringen. Das von der Apache Software Foundation frei zur Verfügung gestellte Build-System ist reich an Funktionalität und einfach einzusetzen. Ant bezeichnet sich selbst als "Make[1] without wrinkles", was das Build-System meiner Meinung nach sehr treffend beschreibt.

    Einige Vorteile von Ant sind:
    -> Eine sehr mächtige BuiltIn-Bibliothek mit oft benötigten Funktionen
    -> Ant ist in reinem Java geschrieben und daher auf nahezu allen modernen Plattformen einsetzbar
    -> Dank XML sind die Build-Dateien gut strukturiert und gut zu warten

    Andere Build-Systeme gehen meist den Weg, eingespeiste Befehle auf der Shell des Systems auszuführen, was den Vorteil hat, dass sie für jede Programmiersprache gleich gut, aber auch gleich schlecht eingesetzt werden können. Ant geht einen anderen Weg: Befehle werden über eine BuiltIn-Bibliothek angeboten; das vereinfacht die Anwendung ungemein und ermöglicht mächtige Features mit wenigen Zeilen einzubinden. Jede Medaille hat aber auch ihre Kehrseite: Die BuiltIn-Bibliothek ist nahezu ausschließlich auf Java ausgelegt, was Ant auf diese Sprache einschränkt.

    Hinweis: Die BuiltIn-Bibliothek kann man mit eigenen Modulen erweitern. Ant wäre also durchaus in der Lage auch mit anderen Programmiersprachen als Java eingesetzt zu werden. Der Aufwand ist allerdings nicht gerechtfertigt, ein anderes Build-Tool liegt da näher.

    Was diese BuiltIn-Bibliothek alles leistet, wird spätestens bei folgendem Beispiel deutlich:

    Auftraggeber schrieb:

    Sämtliche Source-Dateien liegen auf einem CVS-Server[2]. Die neueste Version ist natürlich vor dem Build von dort zu beziehen. Danach soll der eigentliche Build-Prozess vonstatten gehen. Wird dieser korrekt beendet, sollen automatisierte Tests am Ergebnis durchgeführt werden (Kurzfassung der Ergebnisse per Mail an den Projektleiter). Zu guter Letzt soll die computergenerierte Entwicklerdokumentation erneuert werden und der fertige Build mit der richtigen Build-Nummer auf einen Webserver geladen werden.

    Mit einem auf Shellbefehlen basierenden Build-System würde man hier gehörig ins Schwitzen kommen, mit Ant reichte es in diesem Fall, einen Ferialpraktikanten einzustellen.

    Um auch anderen zu zeigen, wie einfach Ant in ein bestehendes System zu integrieren ist, möchte ich mein Wissen - learning by doing - an einem Beispielprojekt weitergeben.

    Basiswissen
    Nach den theoretischen Grundlagen kommen wir nun zur praktischen Anwendung. Im Gegensatz zu Make werden bei Ant die auszuführenden Targets im XML-Schema zugeführt. Der grundlegende Aufbau gleicht dem der meisten Build-Systeme:

    - Projekt (= Projekt)
    - Target (= Ziel)
      Typisches Beispiel: build, create_docs
    - Task (= Aufgabe)
      Typisches Beispiel: javac
    

    Während die Targets von uns definiert werden, verwenden wir Tasks aus der BuiltIn-Bibliothek.

    Kommen wir also zur Build-Datei. Die Datei heißt gewöhnlich build.xml und liegt im Projekt-Root-Verzeichnis. Durch Übersetzen des oben gezeigten Schemas in XML erhalten wir folgenden Grundaufbau:

    <?xml version="1.0"?>
    <project name="MeinProjekt">
    
        <target name="Ziel">
    
            <task param="value" />
            <task param="value" />
            ...
    
        </target>
        ...
    
    </project>
    

    Hinweis: Kommentare können im XML-Format gemacht werden:

    <!-- Kommentar -->
    <!-- Auch
         mehrzeilig
         möglich.
    -->
    

    Ein Beispielprojekt:
    Damit wir den genauen Aufbau der Build-Datei nicht am Trockendock der Theorie erlernen müssen, muss ein fiktiver Taschenrechner als Beispielprojekt herhalten. Die Source-Dateien liegen relativ zur Build-Datei im Verzeichnis source. Mehr ist noch nicht vorhanden. In Zukunft möchten wir im Verzeichnis /docs stets die aktuelle JavaDoc für unser Projekt haben und im Verzeichnis release jeweils die aktuelle Jar-Datei für unseren Taschenrechner (mit aktueller Build-Nummer im Namen).

    Property-Dateien:
    Bevor wir uns jetzt allerdings an die Implementierung machen können, muss ich noch ein kurzes Wort über Property-Dateien verlieren. Sehr oft hat man es in Build-Dateien mit Konstanten zu tun (Pfade, Dateien, Bezeichner, etc.), damit diese nicht hardcoded in der Build-Datei - sicherlich auch noch redundant und unauffindbar für Änderungen - ihr Dasein fristen, werden sie in Property-Dateien zusammengefasst. Diese tragen die Endung ".properties" und man verwendet für die Standard-Property-Datei analog zur "build.xml" den Namen "build.properties".

    Ein Property selbst ist nicht mehr als ein Key-Value-Paar bestehend aus Konstanten-Name (Key) und -Wert (Value) getrennt durch ein Gleichzeichen. Alle Konstanten aus unserem Beispielprojekt ergeben folgende Property-Datei:

    project.name=Taschenrechner
    project.version=1.2
    
    path.source=source
    path.dest=classes
    path.docs=docs
    path.release=release
    

    Implementierung der Build-Datei:
    Nun denn, wollen wir unserem Taschenrechner ein ordentliches Build-System verpassen. Erweitern wir den Rahmen aus dem letzten Kapitel, so erhalten wir folgende Build-Datei:

    <?xml version="1.0"?>
    <!-- Unser Projekt
         name ... Projektname
         basedir ... Basis-Verzeichnis für alle relativen Pfadangaben
         default ... Standard-Target, das ausgeführt wird, wenn Ant ohne Parameter gestartet wird
    -->
    <project name="Taschenrechner" basedir="." default="full">
    
        <!-- Kleine Beschreibung, wozu die vorliegende Build-Datei gut ist -->
        <description>
        Automatisierung des Build-Prozesses für Taschenrechner 1.2
        </description>
    
        <!-- Laden unserer Property-Datei für Zugriff auf Konstanten (Details folgen unten) -->
        <property file="build.properties" />    
    
        <!-- Legt die richtig [kor]hochgezählte[/kor] Build-Nummer in eine Konstante (Details folgen unten) -->
        <buildnumber />
    
        <!-- Unser Standard-Target
             name ... Target-Name
             depends ... Abhängigkeiten von anderen Targets, diese müssen ausgeführt worden sein *bevor* full ausgeführt wird
        -->
        <target name="full" depends="compile,javadoc,jar">
        </target>
    
        <!-- Sub-Targets -->
        <target name="compile">
            ...
        </target>
    
        <target name="javadoc">
        <!-- Nicht abhängig von compile - JavaDoc wird aus den Source-Dateien erzeugt und nicht aus den kompilierten Class-Dateien! -->
            ...
        </target>
    
        <target name="jar" depends="compile">
            ...
        </target>
    
    </project>
    

    Es hat sich einiges getan an unserer Basisdatei. Wir haben das Gesamt-Target in 3 Sub-Targets aufgeteilt. Zudem haben wir einige globale Tasks (= Tasks ohne Zugehörigkeit zu einem Target) angelegt. Diese werden unabhängig vom gewählten Target immer ausgeführt. Gehen wir die Tasks also Schritt für Schritt durch:

    [b]Name:       description[/b]
    Attribute:  -
    Inhalt:     Beschreibung des Projekts
    

    Dieser Task wird von Ant ignoriert. Er dient lediglich der Beschreibung, damit ein Entwickler weiß, um welche Datei es sich handelt.

    [b]Name:       property[/b]
    Attribute:  file ... Datei-Name
    Inhalt:     -
    

    Dieser Task lädt unsere Property-Datei, die wir im vorangegangenen Kapitel angelegt haben. Ab jetzt können wir mit ${key} auf die Konstanten zugreifen.

    [b]Name:       buildnumber[/b]
    Attribute:  -
    Inhalt:     -
    

    Dieser Task legt im Verzeichnis eine zusätzliche Datei "build.number" an und speichert dort die aktuelle Build-Nummer. Wird die Build-Datei von Ant abermals ausgeführt findet der Task die Datei und erhöht die Build-Nummer. Zudem legt der Task die aktuelle Build-Nummer automatisch in das Property "build.number", auf das wir dann mit Hilfe von ${build.number} zugreifen können.

    Implementierung der Sub-Targets:
    Eigentlich hat man jetzt schon genügend Basiswissen, um mit Ant vernünftig zu arbeiten. Die gesamte BuiltIn-Bibliothek ist sehr detailliert im Manual[->1] beschrieben. Dennoch möchte ich noch einige Feinheiten zeigen, daher erstmal die fehlenden Teile unserer Build-Datei:

    <target name="compile">
    
            <echo>Compiling (Beispiel-Konsolen-Ausgabe)</echo>
            <javac
                srcdir="${path.source}"
                destdir="${path.dest}"
                failonerror="true"
            />
    
        </target>
    
        <target name="javadoc">
    
            <javadoc
                sourcepath="${path.source}"
                destdir="${path.docs}"
                packagenames="*"
                access="private"
                source="1.5"
                use="true"
                windowtitle="${project.name}"
            />
    
        </target>
    
        <target name="jar" depends="compile">
    
            <jar
                basedir="${path.dest}"
                destfile="${path.release}/${project.name}-${project.version}.${build.number}.jar"
            >
                <manifest />
            </jar>
    
        </target>
    

    Endlich greifen wir ordentlich auf die BuiltIn-Bibliothek von Ant zurück. Zuerst wieder eine kleine Übersicht über die benutzten Tags:

    [b]Name:       echo[/b]
    Attribute:  -
    Inhalt:     Nachricht
    

    Gibt eine Nachricht auf der Konsole aus.

    [b]Name:       javac[/b]
    Attribute:  srcdir ... Pfad, in dem unsere Java-Dateien liegen
                destdir ... Pfad, in den die Class-Dateien kommen sollen
    Inhalt:     -
    

    Dieser Task ruft den Java-Compiler auf.

    [b]Name:       javadoc[/b]
    Attribute:  sourcepath ... Pfad, in dem unsere Java-Dateien liegen
                destdir ... Pfad, in dem die fertige JavaDoc landen soll
                packagenames ... Packages, die dokumentiert werden sollen (* = alle Packages)
                access ... Bis zu welchem Grad soll dokumentiert werden (private = Alle Methoden, public = Nur öffentliche Methoden, etc.)
                source ... Java-Version des Source
                use ... Soll ein Use-File erstellt werden?
                windowtitle ... Kommt in den <title>-Tag der HTML-Dokumente
    Inhalt:     -
    

    Dieser Task ruft den JavaDoc-Builder auf. Er lässt sich sehr detailliert konfigurieren über Ant. Alle Attribute findet man im Ant-Manual.

    [b]Name:       jar[/b]
    Attribute:  basedir ... Pfad, in dem die Class-Dateien liegen
                destfile ... Angabe des Pfad- und Dateinamens, an dem die fertige Jar-Datei gespeichert werden soll
    Inhalt:     -
    

    Ruft den Jar-Packer auf.

    [b]Name:       manifest[/b]
    Attribute:  -
    Inhalt:     -
    

    Weist den Jar-Packer darauf hin, auch eine Manifest-Datei zu erzeugen.

    Damit haben wir erst einmal unsere Build-Datei fertig gestellt mit einem Aufruf von:

    ant
    

    Im Verzeichnis der build.xml führt Ant das Default-Target für uns aus. Ein bestimmtes Target kann natürlich ebenfalls mit angegeben werden:

    ant javadoc
    

    Ein paar Dinge mehr:
    -> Manchen Tasks kann ein bestimmtes Attribut gesetzt werden, indem man etwas in den Body des Tags schreibt. So muss man z. B. nicht <echo message="Blubb" /> schreiben, sondern kann bequem <echo>Blubb</echo> verwenden.
    -> Viele Tasks besitzen ein Attribut failonerror, das - wenn auf "true" gesetzt - den gesamten Ant-Build-Prozess beendet, wenn ein Fehler auftritt (so kann man z. B. verhindern, dass trotz Fehler im Compile-Vorgang ein Release auf einen Webserver geladen wird)
    -> Einige Tasks unterstützen Sub-Tasks. So verhilft uns der Task <manifest> im <jar>-Task zu einer Manifest-Datei (siehe oben). Diese Sub-Tags fallen unter den Begriff "Nested Tags".

    Den Start von Ant auch noch automatisieren...
    ...ist ebenfalls kein Problem. Alle gängigen Betriebssysteme bieten heutzutage Möglichkeiten dazu: Unix - cronjob, Windows - Geplante Tasks.

    Erweiterung von Ant:
    Ant ist über Java-Klassen erweiterbar. So gibt es externe 3rd-Party-Module zum fertigen Download, aber man ist auch in der Lage eigene Module zu entwickeln. Das würde allerdings den Rahmen des Artikels sprengen (bei Interesse zeige ich es vielleicht in einem zukünftigen Artikel).

    What comes next...
    Im nächsten Artikel unserer Serie wird Talla uns MSBuild näher bringen 🙂

    Anhang:
    [1] Make ist ebenfalls ein Build-Tool, mehr Informationen zu Make findet man auf http://de.wikipedia.org/wiki/Make .
    [2] CVS ist ein sehr verbreitetes Version-Control-System. Version-Control-Systeme ermöglichen eine nachvollziehbare und merge-konfliktfreie, gleichzeitige Entwicklung an Software von mehreren Entwicklern. Mehr Informationen zu CVS findet man auf https://www.cvshome.org .
    [3] Ant erlaubt es, Tasks mittels Java selbst zu schreiben, mehr Informationen findet man im Ant-Manual auf http://ant.apache.org/manual/develop.html#writingowntask .

    Quellen:
    [1] http://ant.apache.org/manual/index.html



  • Ich hab mir mal erlaubt, noch zwei kleine Fehler zu korrigieren.
    @Mr. B:
    Das mit dem "relativ" stimmt schon in diesem Zusammenhang.



  • -predator- schrieb:

    Ich hab mir mal erlaubt, noch zwei kleine Fehler zu korrigieren.
    @Mr. B:
    Das mit dem "relativ" stimmt schon in diesem Zusammenhang.

    geht alles in ordnung... 😉



  • Frage zum weiteren Vorgehen: Soll ich jetzt schon die endgültige Version zusammenstöpseln? Diese bereits in einen eigenen Thread posten? Diese noch in irgendein bestimmtes Format quetschen?

    MfG SideWinder



  • Hast du Gerard mal gefragt, ob er das mit den Kommentaren irgendwie hinbiegen kann?


Anmelden zum Antworten