Split von Filesets in Apache ANT

vorhergehende Artikel in: Java
14.02.2025

Ich musste neulich darüber nachdenken, eine Parallelisierung für einen meiner ANT-Tasks in meinem Static Site Generator einzubauen.

Zunächst einmal: Ja, ich weiß, dass es Maven gibt. Aber ich habe seit ich meine erste Berührung mit Maven hatte immer Bedenken gegen Maven und andere Tools, die dem Gedanken "Configuration by Convention" folgen. Das würde jedoch hier zu weit führen - also zurück zum Thema.

Ich erzeuge Sites indem in einer ANT Datei verschiedene Tasks hintereinander ausgeführt werden. Optional besteht eine Möglichkeit, die resultierenden Dateien zu minimieren. Das übernimmt für mich der Google htmlcompressor.

Dieser kann auf verschiedene Weisen in eigene Projekte integriert werden: Zum Beispiel ist es möglich, die API in eigene Java-Anwendungen zu integrieren oder auch einfach das JAR als standalone Anwendung zu starten. Ich habe mich für die zweite Variante entschieden, was in folgender Definition in meinem build.xml resultierte:

<apply executable="java" parallel="false" force="true"  dest="${generated.dir}">
	<fileset dir="${generated.dir}" includes="*.html"/>
	<arg value="-jar"/>
	<arg path="${jars.dir}/ivy/jar/htmlcompressor.jar"/>
	<srcfile/>
	<arg value="-o"/>
	<arg path="${generated.dir}"/>
	<mapper type="identity"/>
	<targetfile/>
</apply>

Das funktioniert gut - allerdings wird nur ein Prozessorkern benutzt. Ich wollte diesen Schritt der Erzeugung einer Site parallelisieren und dachte darüber nach, wie ich das erreichen könnte. Ich könnte - wie oben bereits angemerkt - natürlich ein entsprechendes Java-Programm schreiben und eventuell gleich eine ANT-Erweiterung daraus machen. Das wollte ich aber nicht - ich steckte meinen Ehrgeiz in die Umsetzung mit Bordmitteln von ANT.

Es besteht die Möglichkeit, Tasks in ANT parallel auszuführen. Um davon zu profitieren, müsste ich lediglich schaffen, das Fileset, das die Input-Dateien auflistet in so viele Teile aufzuteilen, wie es Prozessorkerne gibt und dann entsprechend viele Tasks parallel starten wie den den ich oben gezeigt habe.

Hier stellte sich eine überraschend große Aufgabe: Es ist nicht ganz einfach, ein FileSet so zu splitten: ich fand eine Idee hier, allerdings müsste ich die Mächtigkeit jedes einzelnen der resultierenden Filesets vorher wissen und das ginge nur, wenn ich die Gesamtzahl aller Dateien im Fileset auf einfache Art und Weise durch n teilen könnte. Dazu würde ich aber mit ant-contrib eine weitere Abhängigkeit zum Projekt hinzufühen - das wollte ich nicht.

Bei weiterem Suchen stellte sich aber heraus, dass es möglich ist, Filesets nicht nur mit Pattern zu spezifizieren, sondern über Selectors auch mittels regulärer Ausdrücke.

Dadurch kam die Idee auf, einfach Compartments in den Dateien zu bilden abhängig vom ersten Buchstaben des Dateinamens. Dafür muss man natürlich die Gesamtzahl aller Dateien wissen und die Anzahl der Dateien gruppiert nach dem Anfangsbuchstaben ihres Namens. Die erste Anforderung (unter Linux und Bash) kann wie folgt umgesetzt werden:

ls ${some_directory}|wc -l

Das Beispiel ergibt das Resultat 1554. Die zweite Anforderung beispielsweise so:

ls ${some_directory}|cut -c1-1|sort|uniq -c

In meinem Beispiel ergibt das ein Resultat von

      4 _
      2 1
      3 2
      9 3
      1 7
      1 8
     82 a
     68 b
     39 c
     75 d
     66 e
     29 f
     69 g
     12 h
     69 i
     35 j
     23 k
     79 l
    102 m
     51 n
     31 o
     53 p
      1 q
     49 r
    119 s
    361 t
     12 u
     40 v
     46 w
     16 x
      2 y
      5 z

Nun kann man basierend auf den Zahlen entsprechende Compartments definieren und käme für - zum Beispiel - 6 Compartments auf folgendes ANT-Fragment

<target name="te">
	<fileset id="Fileset" dir="${generated.dir}" includes="*"/>
	<fileset id="Filesetatod" dir="${generated.dir}" includes="*">
		<filename regex="^[a-d].*"/>
	</fileset>
	<fileset id="Filesetetoi" dir="${generated.dir}" includes="*">
		<filename regex="^[e-i].*"/>
	</fileset>
	<fileset id="Filesetjtom" dir="${generated.dir}" includes="*">
		<filename regex="^[j-m].*"/>
	</fileset>
	<fileset id="Filesetntor" dir="${generated.dir}" includes="*">
		<filename regex="^[n-r].*"/>
	</fileset>
	<fileset id="Filesett" dir="${generated.dir}" includes="*">
		<filename regex="^[t].*"/>
	</fileset>
	<difference id="Filesetrest">
		<resources refid="Fileset" />
		<resources refid="Filesetatod" />
		<resources refid="Filesetetoi" />
		<resources refid="Filesetjtom" />
		<resources refid="Filesetntor" />
		<resources refid="Filesett" />
	</difference>
	<resourcecount property="count.all" refid="Fileset">
	</resourcecount>
	<resourcecount property="count.atod" refid="Filesetatod">
	</resourcecount>
	<resourcecount property="count.etoi" refid="Filesetetoi">
	</resourcecount>
	<resourcecount property="count.jtom" refid="Filesetjtom">
	</resourcecount>
	<resourcecount property="count.ntor" refid="Filesetntor">
	</resourcecount>
	<resourcecount property="count.t" refid="Filesett">
	</resourcecount>
	<resourcecount property="count.rest" refid="Filesetrest">
	</resourcecount>
	<echo message="Total: ${count.all}, a-d: ${count.atod}, e-i: ${count.etoi}, j-m: ${count.jtom}, n-r: ${count.ntor}, t: ${count.t}, rest: ${count.rest}"/>
</target>

Die Tasks sind hier nur zur Illustration eingefügt... Das ergibt eine (sehr) annäherungsweise Aufteilung in sechs Teile:

Total: 1545, a-d: 261, e-i: 244, j-m: 237, n-r: 183, t: 361, rest: 259

Die Aufteilung muss auch nicht genauer sein - die Komplexität der Konvertierung kann zwischen zwei Dateien sehr unterschiedlich sein - selbst wenn alle Teile exakt gleichmächtig wären - das Vorhandensein einer besonders großen Datei in einem der Teilmengen würde dafür sorgen, dass die parallelen Tasks nicht gleichzeitig beendet würden...

Interessant ist noch die Möglichkeit zu erwähnen, dass man - anders als oben erwähnt - natürlich keine Erweiterung schreiben müsste, die als Java-Artefakt zu ANT hinzugefügt würde - ANT kann durch Skripte erweitert werden, die es erlauben, neue Tasks in einem Skript zu definieren - diese neuen Tasks können dann in dem entsprechenden build.xml genutzt werden.

Alle Artikel rss Wochenübersicht Monatsübersicht Codeberg Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • OAuth und OTP

    16.02.2020

    Wie bereits beschrieben will ich mich demnächst näher mit OAuth befassen...

    Weiterlesen...

Neueste Artikel

  • Ein Doclet zur Erzeugung von DocBook aus Javadoc

    Ich habe mich mit der Idee zu diesem Projekt Monate abgequält - hätte ich gewusst, was die eigentliche Implementierung für Qualen verursachen würde, hätte ich sie wahrscheinlich eingestampft.

    Weiterlesen
  • Motion JPEG Erzeugung aus Java heraus

    Da ich mich in den letzten Wochen wieder einmal mit Javas Sicherheitsmechanismen und dem Erzeugen von Animationen beschäftigt habe, habe ich den Entschluss gefasst, die bisher mittels JMF AVIs in dWb+ zu erstetzen - nur wodurch?

    Weiterlesen
  • Animationen mit Multi-Scale Truchet Patterns

    Ich hatte neulich hier einen Link zu Multi-Scale Truchet Patterns und habe seitdem den Algorithmus mit java umgesetzt und ihn als Teil meines Projekts zur Testdatengenerierung veröffentlicht.

    Weiterlesen

Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.

Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.

Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...

PS: Meine öffentlichen Codeberg-Repositories findet man hier.