<?xml version="1.0" encoding="UTF-8"?>

<!--

Basisdefinitionen für das Erstellen von Releases und Branches.

Grundlagen:
1. Die Entwicklung ist auf Projekte mit gegenseitigen Abhängigkeiten verteilt. Die
Eclipse IDE regelt das vollkommen und in diesen Mechanismus wird nicht eingegriffen.
2. Aus diesen Projekten werden Projektartefakte generiert (sprich: jar-Dateien) 
3. Ein Produkt ist eine lauffähige Sammlung von Projektartefakten (sprich: Anwendung),
gewöhnlich gibt es für den Hauptstrang und für jeden Branch ein Produkt
4. Ein Release ist ein Produkt aus Projektartefakten definierter Revision. Per
Produkt wird es eine im Laufe der Zeit wachsende Zahl von Releases geben.
5. Ein Release ist die Grundlage für die Erzeugung eines Branches.
6. Ein Branch repräsentiert die Parallelentwicklung eines Produkts und somit
ein neues Produkt (Branches sind strategisch, Branches leben wie lange?)

Beziehungen:
1. Produkt = ein Satz von versionieren Eclipse Projekten mit gegenseitigen 
Abhängigkeiten = eine releaseXXX.xml = Projektset
2. Produktrelease = eine Target in der releaseXXX.xml = ein Satz von gemeinsam
erzeugten JARs oder ähnlichen Projektartefakten
3. Branch = ein weiteres Produkt als Parallelentwicklung zu einem bestehenden
4. Tag = Revisionsnummer eines Projektstandes = Eintrag im Target der releaseXXX.xml

Das KM erzeugt in einem geordneten Prozess solche Relaeses. Dafür wird per Produkt
ein Ant Buildfile angelegt und versioniert, welches dieses File importiert

	<import file="build.xml">
	</import>

und einen Produktnamen festlegt

	<property name="product.name" value="dolphin">
	</property>

Für jedes Release wird nun ein neues Target angelegt, für Testzwecke zunächst mal
ein Target für den HEAD der Entwicklung, entscheidend ist hier die Auflistung der
Projekte des Produkts in _build_project projectdir. Im laufe der Zeit wird
sich diese Liste verändern und die Schichtarchitektur des Produkts widerspiegeln

	<target name="buildHEAD">
		<property name="product.release" value="HEAD">
		</property>
		<delete failonerror="false">
			<fileset dir="build/${product.name}/${product.release}" includes="**">
			</fileset>
		</delete>
		<_build_project projectdir="../base" />
		<_build_project projectdir="../sub" />
		..
		<copy file="run.cmd.template" tofile="build/${product.name}/${product.release}/run.cmd">
			<filterset>
				<filter token="RELEASEPLACE" value="${basedir}\build\${product.name}\${product.release}" />
			</filterset>
		</copy>
	</target>

Für das Build können verschiedene Ausprägungen erfolgen, ein einfaches JAR 
ist der Standard (siehe _buildCommonJar). Die Ausprägung ist als target zu 
definieren und kann mit dem Attribut "buildTarget" angesprochen werden.
 
Stabile Produktreleases lassen sich aber nicht auf der Grundlage der HEAD Revision
erstellen. Im nächsten Schritt wird ein Release auf der Grundlage von Revisionen
definiert - in einer Kopie des aktuellen buildHEAD-Targets gibt man dabei
implizit die Revisionen des Projekts an:

	<target name="build01.01">
		<property name="product.release" value="01.01">
		</property>
		<delete failonerror="false">
			<fileset dir="build/${product.name}/${product.release}" includes="**">
			</fileset>
		</delete>
		<_build_project projectdir="../base" projectrevision="60" />
		<_build_project projectdir="../sub" projectrevision="45" />
		..
		<copy file="run.cmd.template" tofile="build/${product.name}/${product.release}/run.cmd">
			<filterset>
				<filter token="RELEASEPLACE" value="${basedir}\build\${product.name}\${product.release}" />
			</filterset>
		</copy>
	</target>
	
Diese Revisionen kann man mit Hand ermitteln oder man nutzt das Macro 
_show_head_revision, es ermittelt für ein Projekt die letzte Revision:

	<target name="showHeadRevisions">
		<_show_head_revision projectdir="../base" />
		<_show_head_revision projectdir="../sub" />
	</target>

Nachdem ein Produktrelease so definiert und gebuildet wurde werden
die Integrationstests durchgeführt.
Gewöhnlich werden solche Tests Fehler aufdecken, die beseitigt werden müssen.
Dafür kann unterschiedlich stark eskaliert werden.
A) Ist das Release sehr jung, kann möglicherweise die aktuellste Revision des
fehlerhaften Projekts das Problem beheben. In diesem Fall wird nur die Revision
des betreffenden Projekts angepasst und der Integrationstest für
das Release beginnt von vorne.
B) Wenn der HEAD des fehlerhaften Projekts zu weit fortgeschritten ist, zum
Beispiel weil das Projekt vor langer Zeit ausgeliefert wurde: auf der 
Grundlage der fehlerhaften Revision wird ein Branch des fehlerhaften Projekts 
angelegt und ein Release definiert, das auf diesem Branch verweist.

Hinweis: Das Erstellen von hochwertigen Releases ist ein kreativer Akt!
Es müssen nicht automatisierbare Entscheidungen getroffen werden!

Anlegen eines Produkt-Branches

-= Vorbereitung =-
A) Auswahl eines geeigneten, stabilen Releases. In vielen Fällen ist das
jeweils aktuellste Release _kein_ geeigneter Kandidat.
B) Kopie der releaseXXX.xml, diese Kopie wird nun das neue Produkt
repräsentieren
C) in der releaseXXX.xml anpassen:
1. Name des Produkts
2. Im Target buildHEAD und showHeadRevisions werden die Revisionsnummern des Originals 
übertragen, alle anderen buildXXX Targets können gelöscht werden.
3. Die neue releaseXXX.xml wird versioniert 
D) Test von buildHEAD des Produktbranches 
E) Anlegen eines Projektsets für das neue Produkt

-= Beginn des Branches =- 
für jedes Projekt an dem Änderungen durchzuführen sind:
A) Kopie des Projekts in SVN, Anpassung des Namens des Projektbranches
in der .project und Anpassung im Projektset
B) Im Target buildHEAD und showHeadRevisions Umstellen des Projekts auf diesen Namen und
Löschen der Revisionsnummern
C) Test 
-->
<project name="Master" default="showProductName" basedir=".">

	<!-- load environment variables -->
	<property environment="env" />

	<!-- get Unix hostname, and set to Windows comparable name -->
	<property name="env.COMPUTERNAME" value="${env.HOSTNAME}" />

	<!-- Load user specific settings (nicht versioniert) -->
	<property file="${user.home}/build.properties" />

	<!-- load the project specific settings (versioniert!) -->
	<property file="build.properties" />

	<!-- set some props explicitly -->
	<property name="svn.tempfile" value="${env.TMP}/svn.tmp.xml">
	</property>

	<!-- Erzeuge einen Zeitstempel -->
	<tstamp>
		<format property="timenow" pattern="yyyy MM dd - HH:mm" />
	</tstamp>

	<!-- task fuer die Auswertung von XPath Ausdruecken -->
	<taskdef name="XPathProcessor" classpath="../master/bin" classname="com.XPathProcessor">
	</taskdef>

	<!-- Zeigt den Namen des Products -->
	<target name="showProductName">
		<echo message="Product name is '${product.name}'">
		</echo>
	</target>

	<!-- 
	 Dieses Macro
	 1. liest den Projektnamen aus der .project
	 2. macht ein SVN Update auf die angegebene Revision
	 3. macht ein Build des Projekts in den Release-Ordner
	-->
	<macrodef name="_build_project">
		<attribute name="projectdir" />
		<attribute name="projectrevision" default="HEAD" />
		<attribute name="buildTarget" default="_buildCommonJar" />
		<sequential>
			<!-- Der Projektname ist hier nur erforderlich, um einen eindeutigen
			Namen für die condition property zu konstruieren -->
			<_read_projectname projectdir="@{projectdir}" propertyname="project.name" />
			<echo message="===================================">
			</echo>
			<echo message="Build project '${project.name}' in '@{projectdir}' with revision '@{projectrevision}'">
			</echo>
			<!-- Jetzt noch ein update auf das Projekt -->
			<_update_project projectdir="@{projectdir}" projectrevision="@{projectrevision}" projectname="${project.name}" />

			<mkdir dir="build/${product.name}/${product.release}" />
			<!-- Das Build kann potenziell für jedes Projekt anders sein -->
			<antcall target="@{buildTarget}">
				<param name="local.projectdir" value="@{projectdir}" />
				<param name="local.projectrevision" value="@{projectrevision}" />
			</antcall>
		</sequential>
	</macrodef>

	<!--
	Dieses Macro
	1. testet, ob es im Projekt lokale Modifikationen gibt, dann erfolgt ein Abbruch
	2. macht ein SVN Update auf die gegebene Revision gemacht  
	-->
	<macrodef name="_update_project">
		<attribute name="projectdir" />
		<attribute name="projectrevision" default="HEAD" />
		<attribute name="projectname" />
		<sequential>
			<exec executable="svn" output="${svn.tempfile}" dir="@{projectdir}">
				<arg value="status" />
				<arg value="--show-updates" />
				<arg value="--xml" />
			</exec>
			<XPathProcessor xPath="count(/status/target/entry/wc-status[@item='modified'])" xmlFile="${svn.tempfile}" valuePropertyName="no.of.mods" />
			<condition property="no.potential.updates.@{projectname}">
				<matches pattern="0" string="${no.of.mods}" />
			</condition>
			<fail message="Problem: Die WC des Projekts '@{projectdir}' besitzt ${no.of.mods} locale Modifizierungen" unless="no.potential.updates.@{projectname}" />
			<exec executable="svn" dir="@{projectdir}">
				<arg value="update" />
				<arg value="-r@{projectrevision}" />
			</exec>
		</sequential>
	</macrodef>

	<!--
	Dieses Macro
	1. testet, ob es im Projekt lokale Modifikationen gibt, dann erfolgt ein Abbruch
	2. macht ein SVN Update auf HEAD oder den angegebenen Revisionen
	3. extrahiert dann die Revision des Projekt-Verzeichnisses  
	-->
	<macrodef name="_show_head_revision">
		<attribute name="projectdir" />
		<attribute name="projectrevision" default="HEAD" />
		<sequential>
			<!-- Der Projektname ist hier nur erforderlich, um einen eindeutigen
			Namen für die condition property zu konstruieren -->
			<_read_projectname projectdir="@{projectdir}" propertyname="project.name" />
			<echo message="===================================">
			</echo>
			<echo message="Read last revision for project '${project.name}' in '@{projectdir}'">
			</echo>
			<!-- Jetzt noch ein update auf das Projekt zu Revision HEAD -->
			<_update_project projectdir="@{projectdir}" projectrevision="@{projectrevision}" projectname="${project.name}" />
			<exec executable="svn" output="${svn.tempfile}" dir="@{projectdir}">
				<arg value="status" />
				<arg value="--verbose" />
				<arg value="--xml" />
			</exec>
			<XPathProcessor xPath="/status/target[@path='.']/entry[@path='.']/wc-status/commit/@revision" xmlFile="${svn.tempfile}" valuePropertyName="project.revision" />
			<echo message="Revision for '${project.name}' is ${project.revision}">
			</echo>
		</sequential>
	</macrodef>

	<!--
	Dieses Macro ermittelt den Namen eines Eclipse-Projekts und legt ihn
	in der gegebenen Property ab oder überschreibt diese.
	-->
	<macrodef name="_read_projectname">
		<attribute name="projectdir" />
		<attribute name="propertyname" />
		<sequential>
			<XPathProcessor xPath="/projectDescription/name" xmlFile="@{projectdir}/.project" valuePropertyName="@{propertyname}" />
		</sequential>
	</macrodef>

	<!--
	Das Target zum Erstellen eines JARs aus Quellen. Das Target wird nicht
	direkt gerufen, sondern parametrisiert als Teil der Buildstrategie
	Properties:
	- product.name = Name des Produkts
	- product.release = 
	- local.projectdir = Pfad zum Verzeichnis des Projekts
	Konventionen:
	- der Quellpfad ist <projektpfad>/src/
	- alle abhängigen Bibliotheken liegen in build/<product.name>/<product.release>
	- der Zielordner ist build/
	-->
	<target name="_buildCommonJar">
		<mkdir dir="build/tmp" />
		<javac srcdir="${local.projectdir}/src" destdir="build/tmp" debug="on" source="1.5">
			<classpath>
				<fileset dir="build/${product.name}/${product.release}">
					<include name="**/*.jar" />
				</fileset>
			</classpath>
		</javac>
		<copy todir="build/tmp" overwrite="true">
			<fileset dir="${local.projectdir}/src" excludes="**/*.java">
			</fileset>
		</copy>
		<jar destfile="build/${product.name}/${product.release}/${project.name}.jar" update="true">
			<fileset dir="build/tmp" />
			<manifest>
				<!-- Who is building this jar when? -->
				<attribute name="built-by" value="${user.name}" />
				<attribute name="built-time" value="${timenow}" />
				<attribute name="product-name" value="${product.name}" />
				<attribute name="product-release" value="${product.release}" />
				<attribute name="project-path" value="${local.projectdir}" />
				<attribute name="project-name" value="${project.name}" />
				<attribute name="project-revision" value="${local.projectrevision}" />
			</manifest>
		</jar>
		<delete includeEmptyDirs="true">
			<fileset dir="build/tmp" />
		</delete>
	</target>

</project>