I just installed OpenIndiana with a ZFS Raid z1 on an old 1HE Rack Server. I was also able to make VirtualBox running :-). It looks like there will be some fun in the nearer future!
Category: Hacking
Jenkins/Hudson Password Hash Format
Nice to know:
The Build-In Security Realm of Jenkins/Hudson is based on acegisecurity. The Hash is Sha256 based. For a Salt
foo
and a password
bar
, you have to Hash
bar{foo}
, that’s then
77ce9123f864f6749a2b2c99b988089c21d33e39247f7b1276dfad01a112f038
(via hashgenerator.de)
You find the Hashes in <Jenkins-Dir>/users/<username>/config.xml
it is then storred as
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <user> <fullName>user</fullName> <properties> <jenkins.security.ApiTokenProperty> <apiToken>…</apiToken> </jenkins.security.ApiTokenProperty> <hudson.model.MyViewsProperty> <views> <hudson.model.AllView> <owner reference="../../.."/> <name>Alle</name> <filterExecutors>false</filterExecutors> <filterQueue>false</filterQueue> <properties/> </hudson.model.AllView> </views> </hudson.model.MyViewsProperty> <hudson.security.HudsonPrivateSecurityRealm_-Details> <passwordHash>foo:77ce9123f864f6749a2b2c99b988089c21d33e39247f7b1276dfad01a112f038</passwordHash> </hudson.security.HudsonPrivateSecurityRealm_-Details> <hudson.tasks.Mailer_-UserProperty> <emailAddress>mail@example.com</emailAddress> </hudson.tasks.Mailer_-UserProperty> </properties> </user>
maven is not ant
Neben vielen berechtigten Kritikpunkten die manch einer dem Build-Tool Maven vorwerfen kann, ist oftmals der falsche Einsatz einer der Hauptgründe für die schlechte Perfomance (d.h. lange Build-Zeiten) die fehlerhafte Nutzung von Maven Features.
Oft sieht man, dass Module untereinander durch relative Pfadangaben verknüpft sind. Dies mag bei Ant-Builds ein gängiges Mittel sein, aber wenn man sich vor Augen hält, dass jeder Modul eines Projektes für sich genommen ausgecheckt und gebaut werden können soll, so ist klar, dass man eine abstractere Art und Weise benötigt, einzelne Module miteinander zu verbinden.
Und auch hierfür eignet sich das Mitte der Dependencies. Zu einem vollständigen (und nützlichem) Maven-Build-System gehört zwangsläufig auch ein (eigenes) funktionierendes Maven-Repository (sei es nun Archiva oder Nexus). So ein Repository dient nicht nur zum Lagern von Artefakten, sondern auch als Dreh- und Angelpunkt der einzelnen Module untereinander.
Nehmen wir an, ein Projekt besitzt folgende Struktur:
Root |-Modul A |-Modul B |-Modul C
Nehmen wir weiterhin an, dass Modul A von C abhängt, weil C zum Beispiel XSD Scripte enthält, die separat von einem Fachler aktualisiert werden, aber für die Generierung von Java-Klassen in A benötigt werden.
Würde es nach Ant-Manier gehen, so wäre folgendes denkbar:
<project> ... <build> <plugins> ... <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>xmlbeans-maven-plugin</artifactId> <version>2.3.3</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>xmlbeans</goal> </goals> </execution> </executions> <inherited>true</inherited> <configuration> ... <schemaDirectory>../B/C/xsd</schemaDirectory> <classGenerationDirectory>${project.build.directory}/classes</classGenerationDirectory> </configuration> </plugin> ... </plugins> </build> ... </project>
Dies dürfte aber spätestens beim Release Build scheitern, weil hier jedes Modul für das Releas separat innerhalb des eigenen Target-Dirs ausgecheckt wird (spätestens hier kommt es dann mit den relativen Pfaden nicht mehr hin).
Was also tun?
Abhilfe schaft das maven-dependency-plugin, welches dem aktuellen Modul erlaub direkt auf die Daten eines einzelnen Artefacts zugreifen zu können. Damit das Artefact durch das Plugin verarbeitet werden kann, sollte es (logischerweise) auch als Dependency im eingetragen sein. So ist sogar sicher gestellt, dass immer der aktuellste Codestand verwendet wird, denn sollt ein Artefact im Repository neuere sein als das, welches lokal vorgehalten wird, so wird dieses heran gezogen.
Die obige Konfiguration würde sich also wie folgt ändern:
<project> ... <dependencies> ... <dependency> <groupId>com.example</groupId> <artifactId>C</artifactId> <version>${project.version}</version> </dependency> ... </dependencies> <build> <plugins> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.3</version> <executions> <execution> <id>src-dependencies</id> <phase>generate-sources</phase> <goals> <!-- use copy-dependencies instead if you don't want to explode the sources --> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>com.example</groupId> <artifactId>C</artifactId> <version>${project.version}</version> <classifier>resources</classifier> <type>zip</type> <includes>**/*.xsd</includes> <overWrite>true</overWrite> <outputDirectory>${project.build.directory}/C</outputDirectory> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>xmlbeans-maven-plugin</artifactId> <version>2.3.3</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>xmlbeans</goal> </goals> </execution> </executions> <inherited>true</inherited> <configuration> ... <schemaDirectory>${project.build.directory}/C/xsd</schemaDirectory> <classGenerationDirectory>${project.build.directory}/classes</classGenerationDirectory> </configuration> </plugin> ... </plugins> </build> ... </project>
Zu beachten ist hier, dass die Reihenfolge der beiden Plugins wichtig ist, weil ansonsten die Code-Generierung nicht auf die entpackten Dateien der Dependency zugreifen kann. Ich gebe zu, es handelt sich hier um ein einfaches Beispiel, aber zumindest der Drang, sich durch relative Pfade innerhalb von Maven-Files zu helfen ist meiner Beobachtung nach recht weit verbreitet :-/.
Upload von Third-Party Artefakten in ein Maven Repository per SCP
Manchmal gibt es den Fall, dass es notwendig ist, eine externe Bibliothek in einem Maven Projekt einzubinden. Um nun im Build Zyklus des Maven-Projektes keinen Bruch zu erhalten bietet es sich an, dass Artefakt manuell als Maven Artefakt im Repository zur Verfügung zu stellen.
Wir benutzen bei consolving.de u.a. ein öffentliches Maven Repository ( maven.javastream.de ) in dem wir einige OSS Artefakte den Entwicklern unter unseren Kunden zur Verfügung stellen. Hierbei handelt es sich nicht um ein vollständiges Repository System wie Archiva oder Nexus, sondern um ein lokales Repositry, welche statisch per Web-Server zur Verfügung stellt wird. Das ganze wird befüllt durch einen Jenkins-CI Server, der im Hintergrund alle Projekte periodisch neu dorthin deployed.
Es sind vier Schritte notwendig damit ein Deployment über SCP möglich ist:
- Das Verzeichnis des Repositories muss per scp erreichbar sein – hierzu bietet es sich an, den SSH-Key auf dem Server zu hinterlegen.
- Einrichten der notwendigen Credentials unter der ~/.m2/settings.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <settings> <servers> <server> <id>maven.javastream.de</id> <username>##user##</username> <privateKey>##user##</privateKey> </server> </servers> </settings>
- Vorbereiten des Third-Parts Projektes
Ein vollständiges Maven-Artefakt besteht aus drei Teilen: 1. das Binary, 2. die Sourcen und 3. die JavaDoc Dateien. Alle drei Teile sollten als JAR gepackt werden.
Nachfolgend ein Script, welches die drei notwendigen Teile in Archive packt:#!/bin/bash # Direkt aus dem Directory zu packen scheint die einzige Möglichkeit zu sein, dass die einzelnen Dateien direkt im Root des JARs liegen. cd src jar -c . > ../src.jar cd .. cd doc jar -c . > ../doc.jar cd . cd lib jar -c . > ../lib.jar cd .
- Zuletzt werden nun die drei JARs per Maven-Command in das Repository geladen:
#!/bin/bash #Binary mvn deploy:deploy-file -DgroupId=de.javastream \ -DartifactId=example \ -Dversion=1.0 \ -Dpackaging=jar \ -Dfile=lib.jar \ -DrepositoryId=maven.javastream.de \ -Durl=scp://maven.javastream.de/home/javastream.de/maven # Javadoc mvn deploy:deploy-file -DgroupId=de.javastream \ -DartifactId=example \ -Dversion=1.0 \ -Dclassifier=javadoc \ -Dpackaging=jar \ -Dfile=doc.jar \ -DrepositoryId=maven.javastream.de \ -Durl=scp://maven.javastream.de/home/javastream.de/maven # Sourcen mvn deploy:deploy-file -DgroupId=de.javastream \ -DartifactId=example \ -Dversion=1.0 \ -Dclassifier=sources \ -Dpackaging=jar \ -Dfile=src.jar \ -DrepositoryId=maven.javastream.de \ -Durl=scp://maven.javastream.de/home/javastream.de/maven
Play! Framework Model, View & Controller
Im letzten Post wurde eine kurze Beschreibung in den Aufbau einer Play! Anwendung gegeben.
Nachfolgend soll eine einfache kleine Anwendung erstellt werden.
Die Anwendung soll die Links von verschiedenen Webseiten als Bookmarks speichern.
Hierzu benötigen wir ein Model für ein Bookmark:
package models; import java.util.Date; import javax.persistence.Entity; import javax.persistence.Temporal; import javax.persistence.TemporalType; import play.data.validation.Required; import play.db.jpa.Model; @Entity public class Bookmark extends Model { @Temporal(TemporalType.DATE) public Date createdAt; @Required public String title; @Required public String url; public Bookmark(){ createdAt = new Date(); } }
Wie der geneigte Java-Entwickler auf den ersten Blick erkennen mag, handelt es sich um ein normales JPA Model. Einzig der public Accessor für die einzelnen Attribute erscheint befremdlich.
Zur Beruhigung sei gesagt, dass hier eine der Vereinfuchungen von Play! in Aktion tritt. Zur Laufzeit werden hier die notwendigen Getter/Setter erstellt (diese können aber auch selbst definiert werden), sowie der direkte Zugriff auf die Attribut verhindert. (vgl. http://www.playframework.org/documentation/1.2.3/model#properties ).
Weiterhin unbekannt ist hier die Anotation @Required. Play! unterstützt hier eine on-Model-Validation. Das Attribut wird als Required gekennzeichnet, es ist also notwendig, dass diese Attribut zu Persistierung gesetzt ist. Wie die Validierung im Controller verwendet wird, dazu später mehr.
Startup mit dem Play Framework
Wie einige vielleicht ja schon wissen beschäftige ich mich jetzt schon seit einige Zeit mit dem recht neuen, aber mittlerweile immer bekannterem, Java-basiertem Web-Framework Play!. Anders als bei vielen neueren Frameworks wird nicht durch die Mittel einer dynamischen Sprache, sondern durch die Vereinfachung der bestehenden Sprachmittel von java versucht die Entwicklung einer Web-Anwendung zu beschleunigen und zu vereinfachen. Das bedeuten unter anderem das konsequente Weglassen der bisherigen Package-Struktur. Auf der anderen Seite werden altbewährte Bibliotheken aus der Java-Welt verwendet (z.B. JPA, Hibernate, Lucene, usw.). Da es eine ganze Menge an englischsprachigen Tutorials gibt und ich mir schon länger vorgenommen habe in einem kleinen Tutorial die grundlegenden Züge einer Play!-Anwendung zu beschreiben, wird dieser Text mal wieder in Deutsch sein :-).
Play verwendet – wie fast alle aktuellen Web-Frameworks ein MVC-Pattern,
So sollte es einem Entwickler (egal ob Ruby, Java oder Python), der mit der aktuellen Techniken vertraut ist, relativ leicht fallen, sich mit dem typischen Aufbaue einer Play-Anwendung zurecht zu finden.
Die Installation des Frameworks ist auf der Seite des Projektes gut erklärt. Neben einer aktuellen JVM sind keine weiteren Vorausetzungen zu erfüllen. Wichtig ist nur, dass das “play” Script im aktuellen Pfad erreichbar ist.
Nachfolgend möchte ich eine kleine Bookmark-Verwaltung erstellen.
Mit einem
# play new bookmarks
Wird die Grundstruktur einer Play-Anwendung erstellt.
Das oberste Verzeichnis enthält alle Modelle, Controller und Views der Anwendung (dazu später mehr). Unter “conf” befinden sich folgende Dateien:
– application.conf: Konfiguration der Anwendung – Art der DB, Caching, Konfiguration der Module
– dependencies.yml: Play unterstützt Module, die verschiedene Funktionen kapseln können. Seit Version 1.2 werden die Abhängigkeiten einer Anwendung durch Einträge in dieser Datei gesetzt.
– messages: Diese Datei wird für die Internationalisierung einer Anwendung benötigt (amerikanische Strings). Um eine neue Sprache hinzu zu fügen legt man eine neue Datei mit dem entsprechenden Ländercode an – z.B. messages.de für deutsch.
– routes: Play ermöglicht es, durch Einträge in dieser Datei, das Routing der Anwendung zu beeinflussen. Es erfolgt ein Mapping von URL auf Controller-Actions (dazu später mehr).
Das lib Verzeichnis kann verwendet werden um Java Libs zu verwenden, die nicht als Modul für Play! zur Verfügung stehen. Alle Jars im libs-Verzeichnis werden automatisch in den Classpath der Anwendung aufgenommen.
Das test Verzeichnis enthält die die Testdateien (Junit-/Selenium-Tests sowie ew. Fixures mit Testdaten).
Nach dem ersten Start der Anwendung werden noch zwei weitere Verzeichnisse erstellt:
Ein tmp-Verzeichnis (welches die kompilierten Klassen, sowie Caching Dateien enthält) und ein
db-Verzeichnis, welches bei der Wahl einer h2 Datenbank, die entsprechende Datenkbankdatei enthält (auch dazu später mehr).
Dies soll für die ersten 5 Minuten genügen :-).
Philipps 5 mins: Web-Development mit dem Play Framework Part 1
Wie einige vielleicht ja schon wissen beschäftige ich mich jetzt schon seit einige Zeit mit dem recht neuen, aber mittlerweile immer bekannterem, Java-basiertem Web-Framework Play!. Anders als bei vielen neueren Frameworks wird nicht durch die Mittel einer dynamischen Sprache, sondern durch die Vereinfachung der bestehenden Sprachmittel von java versucht die Entwicklung einer Web-Anwendung zu beschleunigen und zu vereinfachen. Das bedeuten unter anderem das konsequente Weglassen der bisherigen Package-Struktur. Auf der anderen Seite werden altbewährte Bibliotheken aus der Java-Welt verwendet (z.B. JPA, Hibernate, Lucene, usw.). Da es eine ganze Menge an englischsprachigen Tutorials gibt und ich mir schon länger vorgenommen habe in einem kleinen Tutorial die grundlegenden Züge einer Play!-Anwendung zu beschreiben, wird dieser Text mal wieder in Deutsch sein :-).
Play verwendet – wie fast alle aktuellen Web-Frameworks ein MVC-Pattern. So sollte es einem Entwickler (egal ob Ruby, Java oder Python), der mit der aktuellen Techniken vertraut ist, relativ leicht fallen, sich mit dem typischen Aufbaue einer Play-Anwendung zurecht zu finden.
Die Installtion des Frameworks ist auf der Seite des Projektes gut erklärt. Neben einer aktuellen JVM sind keine weiteren Vorrausetzungen zu erfüllen. Wichtig ist nur, dass das “play” Script im aktuellen Pfad erreichbar ist.
Nachfolgend möchte ich eine kleine Bookmark-Verwaltung erstellen.
Mit einem
# play new bookmarks
Wird die Grundstruktur einer Play-Anwendung erstellt.
- app:
Das oberste Verzeichnis enthält alle Modelle, Controller und Views der Anwendung (dazu später mehr). - Unter “conf” befinden sich folgende Dateien:
- application.conf:
Konfiguration der Anwendung – Art der DB, Caching, Konfiguration der Module - dependencies.yml:
Play unterstützt Module, die verschiedene Funktionen kapseln können. Seit Version 1.2 werden die Abhängigkeiten einer Anwendung durch Einträge in dieser Datei gesetzt. - messages:
Diese Datei wird für die Internationalisierung einer Anwendung benötigt (amerikanische Strings). Um eine neue Sprache hinzu zu fügen legt man eine neue Datei mit dem entsprechenden Ländercode an – z.B. messages.de für deutsch. - routes:
Play ermöglicht es, durch Einträge in dieser Datei, das Routing der Anwendung zu beeinflussen. Es erfolgt ein Mapping von URL auf Controller-Actions (dazu später mehr).
- application.conf:
- Das lib Verzeichnis kann verwendet werden um Java Libs dort zu speichern, die nicht als Modul für Play! zur Verfügung stehen. Alle Jars im libs-Verzeichnis werden automatisch in den Classpath der Anwendung aufgenommen.
- Das test Verzeichnis enthält die die Testdateien (Junit-/Selenium-Tests sowie ew. Fixures mit Testdaten).
Nach dem ersten Start der Anwendung werden noch zwei weitere Verzeichnisse erstellt:
- Ein tmp-Verzeichnis (welches die kompilierten Klassen, sowie Caching Dateien ernhält) und ein
- db-Verzeichnis, welches bei der Wahl einer h2 Datenbank, die entsprechende Datenkbankdatei enthält (auch dazu später mehr).
Dies soll für die ersten 5 Minuten genügen :-).
Mass Conversion of different video files with handbreak-CLI
I am currently converting all my videos to a fixed format using handbrake. If you have a lot of videos to convert, the UI version of handbrake is not always the best solution. Handbrake offers a CLI, sometimes you it needs to be installed separately.
It has a lot of options ( https://trac.handbrake.fr/wiki/CLIGuide ). So the best is to go with your preferred preset.
I want to encode all video files in a given folder to the preset “universal”.
So i wrote a short bash script, to to the work:
#!/bin/bash # Folder Setup OUT_FOLDER=/home/media/out IN_FOLDER=/home/media/in DONE_FOLDER=$OUT_FOLDER/videos_done if [ ! -f $DONE_FOLDER ] ; then mkdir -p $DONE_FOLDER; fi if [ ! -f $OUT_FOLDER ] ; then mkdir -p $OUT_FOLDER; fi #All Extensions of the input files EXTS=( mp4 flv ) #Find Handbrake CLI HB=`which HandBrakeCLI` for ext in ${EXTS[@]}; do echo "for $ext" for FILENAME in `ls $IN_FOLDER/*.$ext`; do #echo $FILENAME $HB -i $FILENAME -o $OUT_FOLDER/`basename "$FILENAME" .$ext`.mp4 --preset="Universal" mv $FILENAME $DONE_FOLDER/`basename "$FILENAME"` done done
Another use-case might the conversion of some videos to flv for a web playback (or the other way round flv->mp4 for playback on iOS Devices)
setup your public SSH key to another UNIX Host
Normally you would prefer to use your public ssh key for login into a remote linux machine.
I created a script to perform the basic steps for inserting your public key into the hosts authorized_keys files.
The script looks like this:
#!/bin/bash HOST=$1; echo ">> setup your ssh keys for $HOST" echo "" echo ">> creating ssh keys on $HOST if necessary" echo "(you need to enter your password)" echo "" ssh $HOST 'if [ ! -d ~/.ssh ] ; then ssh-keygen -t rsa; fi' echo "" PUBKEY=`cat ~/.ssh/id_dsa.pub` echo "==========================================================" echo "your id_dsa.pub:" echo "$PUBKEY" echo "==========================================================" echo "" echo ">> transfering your public ssh key" scp ~/.ssh/authorized_keys $HOST:~/.ssh/authorized_keys ssh $HOST 'chmod 600 ~/.ssh/authorized_keys' echo "" echo ">> login with your public key" echo "(should work without a password)" ssh $HOST
A typical run might look like this:
imotep:~ philipp$ setupssh philipp@192.168.178.55 >> setup your ssh keys for philipp@192.168.178.55 >> creating ssh keys on philipp@192.168.178.55 if necessary (you need to enter your password) The authenticity of host '192.168.178.55 (192.168.178.55)' can't be established. RSA key fingerprint is ... Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '192.168.178.55' (RSA) to the list of known hosts. philipp@192.168.178.55's password: Enter file in which to save the key (/home/philipp/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Generating public/private rsa key pair. Created directory '/home/philipp/.ssh'. Your identification has been saved in /home/philipp/.ssh/id_rsa. Your public key has been saved in /home/philipp/.ssh/id_rsa.pub. The key fingerprint is: ... philipp@debian The key's randomart image is: +--[ RSA 2048]----+ |... | +-----------------+ ========================================================== your id_dsa.pub: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx......xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ========================================================== >> transfering your public ssh key philipp@192.168.178.55's password: authorized_keys 100% 610 0.6KB/s 00:00 >> login with your public key (should work without a password) Linux debian 2.6.26-2-amd64 #1 SMP Thu Nov 25 04:30:55 UTC 2010 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun Jan 23 17:31:16 2011 from imotep.fritz.box philipp@debian:~$
Show Build-Information in your iOS App About Panel
Sometimes it might be useful to have an exact piece of information about what version of an app you have currently running. Especially if you have a decent Testing-Group, it is important to track the versions in which a bug appears. The goal of this post is to achieve a info panel like this in your application.
You get the Application version (from the Application Bundle), the Repository Revision and the Date of the last Commit.
Picture 1: Example Application About Dialog
We are using here the build-in functions of subversion to update given keywords with the repository information. More about this topic here. There is also a way to use this method with git, but i did not test it yet. You may find out more about this here
The first step is to create a File-Template you can import in your code, with which you can access all the necessary details:
#define APP_VERSION [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] #define APP_EXECUTABLE [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"] #define APP_NAME [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"] #define APP_BUILD_REVISION @"$Rev$" #define APP_BUILD_DATE @"$Date$" #define APP_LAST_AUTHOR @"$Author$"
Code 1: version.h template
The next step is to tell Subversion to replace the placeholder with the subversion values.
You can do this with setting the subversion keyword for that file.
After that, with every commit of the file “version.h” the values will be updated.
svn propset svn:keywords 'Revision Author Date' version.h
Code 2: version.h template
The very last step is to make sure, that “version.h” will be updated each time you make a change to your application. Assuming you build your app every time you made a change, you can use the functions, build into Xcode to force an update on “version.h”. We use the trick, that every change on the propsets of “version.h” is equal to a file modification itself.
So we create a small bash script, setting the propset “build” to a new value. After that, “version.h” needs to be commited as a new version.
#!/bin/sh DATE=`date` HOST=`hostname` svn propset build "$HOST $DATE" Version.h
Code 3: buildUpdate.sh
Now we need to add the run of “buildUpdate.sh” to our Build-Cycle. (Picture 2 & Picture 3).
Picture 2: Project Target Settings
After a successful commit, the file “version.h” will look something like this:
#define APP_VERSION [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] #define APP_EXECUTABLE [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"] #define APP_NAME [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"] #define APP_BUILD_REVISION @"$Rev: 1047 $" #define APP_BUILD_DATE @"$Date: 2011-01-21 18:53:38 +0100 (Fri, 21 Jan 2011) $" #define APP_LAST_AUTHOR @"$Author: phaus $"
Code 4: updated version.h
You might modify the output (e.g. filter out the $s or reformat the date) to get a more stylish output.