Ant: Wie führe ich einen Befehl für jede Datei im Verzeichnis aus?

97

Ich möchte für jede Datei in einem Verzeichnis einen Befehl aus einer Ant-Builddatei ausführen.
Ich suche eine plattformunabhängige Lösung.

Wie mache ich das?

Sicher, ich könnte ein Skript in einer Skriptsprache schreiben, aber dies würde dem Projekt weitere Abhängigkeiten hinzufügen.

ivan_ivanovich_ivanoff
quelle

Antworten:

61

Kurze Antwort

Verwenden Sie <foreach>mit einem verschachtelten<FileSet>

Foreach erfordert Ant-Contrib .

Aktualisiertes Beispiel für den letzten Ant-Contrib:

<target name="foo">
  <foreach target="bar" param="theFile">
    <fileset dir="${server.src}" casesensitive="yes">
      <include name="**/*.java"/>
      <exclude name="**/*Test*"/>
    </fileset>
  </foreach>
</target>

<target name="bar">
  <echo message="${theFile}"/>
</target>

Dadurch wird die Zielleiste mit dem $ {theFile} aufgerufen, was zur aktuellen Datei führt.

blak3r
quelle
Also muss ich etwas hinzufügen? Oder brauche ich eine externe Ameisenbibliothek? Ich erhalte die Meldung "Problem: Aufgabe konnte nicht erstellt oder foreach eingegeben werden" . Wenn ich das richtig verstehe, bedeutet dies, dass foreach ein unbekanntes Schlüsselwort ist.
ivan_ivanovich_ivanoff
4
Ohhh lahm ... es ist eine Ant-Contrib-Aufgabe. Sie müssen also etwas installieren. Siehe hier: ant-contrib.sourceforge.net
blak3r
3
Dieses Beispiel stammt aus einem Foreach, bevor es in Ant-Contrib aufgenommen wurde. Es gibt ein gutes Beispiel unter ant.1045680.n5.nabble.com/Using-foreach-td1354624.html Ich werde das Beispiel aktualisieren, damit es funktioniert.
Sean
2
Das verschachtelte Dateigruppenelement ist korrigiert. Verwenden Sie stattdessen einen verschachtelten Pfad
Alter
@dude Verwenden Sie <foreach> <path> <fileset> <include name = "*. jar" /> </ fileset> </ path> </ foreach>
Sharat
88

Verwenden Sie die Aufgabe <apply> .

Es führt einmal für jede Datei einen Befehl aus. Geben Sie die Dateien mithilfe von Dateigruppen oder anderen Ressourcen an. <apply> ist eingebaut; keine zusätzliche Abhängigkeit erforderlich; Keine Implementierung einer benutzerdefinierten Aufgabe erforderlich.

Es ist auch möglich, den Befehl nur einmal auszuführen und alle Dateien auf einmal als Argumente anzuhängen. Verwenden Sie das Attribut parallel, um das Verhalten zu ändern.

Tut mir leid, dass ich zu spät im Jahr bin.

Alex
quelle
4
Nun, ich fand das erst 2011 nützlich, also danke trotzdem dafür!
Michael Della Bitta
7
Das Problem mit <apply> ist, dass nur externe Systembefehle ausgeführt werden. Es wird nichts direktes tun. Nicht klar, ob es darum ging oder nicht. Grundsätzlich müssen Sie <apply> oder <foreach> für die zwei verschiedenen Situationen verwenden ... was völlig dumm ist.
Archie
27

Ein Ansatz ohne Ant-Contrib wird von Tassilo Horn vorgeschlagen (das ursprüngliche Ziel ist hier ).

Grundsätzlich, da es keine Erweiterung von <java> (noch?) In der gleichen Art und Weise , dass <anwenden> erweitert <exec> , er Gebrauch schlägt <anwenden> (die natürlich auch ein Java - Programm in einer Befehlszeile ausführen)

Hier einige Beispiele:

  <apply executable="java"> 
    <arg value="-cp"/> 
    <arg pathref="classpath"/> 
    <arg value="-f"/> 
    <srcfile/> 
    <arg line="-o ${output.dir}"/> 

    <fileset dir="${input.dir}" includes="*.txt"/> 
  </apply> 
Jmini
quelle
2
Dies ist nicht sehr plattformunabhängig, obwohl es in der ursprünglichen Frage eindeutig erforderlich war ...
Chucky
18

Hier ist eine Möglichkeit, dies mit Javascript und der Ant-Scriptdef-Task zu tun. Sie benötigen Ant-Contrib, damit dieser Code funktioniert, da Scriptdef eine zentrale Ant-Task ist.

<scriptdef name="bzip2-files" language="javascript">
<element name="fileset" type="fileset"/>
<![CDATA[
  importClass(java.io.File);
  filesets = elements.get("fileset");

  for (i = 0; i < filesets.size(); ++i) {
    fileset = filesets.get(i);
    scanner = fileset.getDirectoryScanner(project);
    scanner.scan();
    files = scanner.getIncludedFiles();
    for( j=0; j < files.length; j++) {

        var basedir  = fileset.getDir(project);
        var filename = files[j];
        var src = new File(basedir, filename);
        var dest= new File(basedir, filename + ".bz2");

        bzip2 = self.project.createTask("bzip2");        
        bzip2.setSrc( src);
        bzip2.setDestfile(dest ); 
        bzip2.execute();
    }
  }
]]>
</scriptdef>

<bzip2-files>
    <fileset id="test" dir="upstream/classpath/jars/development">
            <include name="**/*.jar" />
    </fileset>
</bzip2-files>
ams
quelle
Im projectobigen Beispiel wird auf eine Variable verwiesen, jedoch ohne vorherige Definition. Wäre gut, wenn das dargestellt oder geklärt würde. BEARBEITEN: n / m, festgestellt, dass dies projecteine vordefinierte Variable für den Zugriff auf das aktuelle Projekt ist. Ich hatte das vermutet, war mir aber nicht sicher.
Jon L.
1
Wenn Sie dies in JDK8 oder höher versuchen, denken Sie daran, dass "importClass" funktioniert, wenn die von der JRE geladene ScriptEngine Nashorn ist (was für die meisten JDK 6 und 7 zutraf), während Sie in Nashorn (ab 8) die verwenden können abwärtskompatibel "File = java.io.File" oder der neuere, aber nicht abwärtskompatible Java.type. Ant scheint lautlos zu scheitern, wenn Probleme beim Ausführen von scriptdef auftreten, wie ich heute erfahren habe.
Matteo Steccolini
15

Ameisenbeitrag ist böse; schreibe eine benutzerdefinierte Ameisenaufgabe.

Ant-Contrib ist böse, weil es versucht, Ant von einem deklarativen Stil in einen imperativen Stil umzuwandeln. Aber XML macht eine beschissene Programmiersprache.

Im Gegensatz dazu können Sie mit einer benutzerdefinierten Ameisenaufgabe in einer echten Sprache (Java) mit einer echten IDE schreiben, in der Sie Komponententests schreiben können, um sicherzustellen, dass Sie das gewünschte Verhalten haben, und dann in Ihrem Build-Skript eine saubere Deklaration über erstellen das Verhalten, das Sie wollen.

Diese Beschimpfung ist nur wichtig, wenn Sie wartbare Ameisenskripte schreiben möchten. Wenn Sie sich nicht unbedingt um die Wartbarkeit kümmern, tun Sie alles, was funktioniert. :) :)

Jtf

Jeffrey Fredrick
quelle
2
Du hast recht, ich sollte einfach eine benutzerdefinierte Ameisenaufgabe in Java schreiben;)
ivan_ivanovich_ivanoff
4
Ameisenbeitrag ist wirklich böse. Im Moment bin ich mitten in einem großen Ameisenbauprojekt, das intensiv von if / then / else und antcalls Gebrauch macht und wirklich schrecklich liest. Das Ganze sieht aus wie ein konvertiertes Batch- / Shell-Skript, und all das Abhängigkeitsmaterial, das Ant ausführt, wird durch den starken Einsatz von Ant-Contrib vollständig deaktiviert. Wenn Sie Ihr Setup sauber halten möchten, erstellen Sie Ihre eigene Aufgabe. : - /
cringe
2
@cringe, ich bin anderer Meinung. Wie bei allem müssen Sie wissen, wann es in Ihrem besten Interesse ist, Ant-Contrib zu verwenden. Halten Sie sich von Dingen wie if und var fern und verwenden Sie Ant-Contrib, um zu vermeiden, dass Sie das Rad neu erfinden müssen.
Neil
1
Und hätte eine Skriptsprache sein sollen, die von Anfang an so zwingend und nicht deklarativ war. Also ist es Und wer ist böse IMO
mvmn
Vielleicht ist Ant-Contrib a priori böse, aber die Verwendung von black3r scheint sozusagen vernünftig und ant-ish zu sein.
SSimm
7

Ich weiß, dass dieser Beitrag wirklich alt ist, aber jetzt, da einige Zeit- und Ameisenversionen vergangen sind, gibt es eine Möglichkeit, dies mit grundlegenden Ameisenfunktionen zu tun, und ich dachte, ich sollte ihn teilen.

Dies erfolgt über ein rekursives Makrodef, das verschachtelte Aufgaben aufruft (auch andere Makros können aufgerufen werden). Die einzige Konvention besteht darin, einen festen Variablennamen (Element hier) zu verwenden.

<project name="iteration-test" default="execute" xmlns="antlib:org.apache.tools.ant" xmlns:if="ant:if" xmlns:unless="ant:unless">

    <macrodef name="iterate">
        <attribute name="list" />
        <element name="call" implicit="yes" />
        <sequential>
            <local name="element" />
            <local name="tail" />
            <local name="hasMoreElements" />
            <!-- unless to not get a error on empty lists -->
            <loadresource property="element" unless:blank="@{list}" >
                <concat>@{list}</concat>
                <filterchain>
                    <replaceregex pattern="([^;]*).*" replace="\1" />
                </filterchain>
            </loadresource>
            <!-- call the tasks that handle the element -->
            <call />

            <!-- recursion -->
            <condition property="hasMoreElements">
                <contains string="@{list}" substring=";" />
            </condition>

            <loadresource property="tail" if:true="${hasMoreElements}">
                <concat>@{list}</concat>
                <filterchain>
                    <replaceregex pattern="[^;]*;(.*)" replace="\1" />
                </filterchain>
            </loadresource>

            <iterate list="${tail}" if:true="${hasMoreElements}">
                <call />
            </iterate>
        </sequential>
    </macrodef>

    <target name="execute">
        <fileset id="artifacts.fs" dir="build/lib">
            <include name="*.jar" />
            <include name="*.war" />
        </fileset>

        <pathconvert refid="artifacts.fs" property="artifacts.str" />

        <echo message="$${artifacts.str}: ${artifacts.str}" />
        <!-- unless is required for empty lists to not call the enclosed tasks -->
        <iterate list="${artifacts.str}" unless:blank="${artifacts.str}">
            <echo message="I see:" />
            <echo message="${element}" />
        </iterate>
        <!-- local variable is now empty -->
        <echo message="${element}" />
    </target>
</project>

Die wichtigsten Funktionen, die benötigt werden, wo:

Ich habe es nicht geschafft, das Trennzeichen variabel zu machen, aber dies ist möglicherweise kein großer Nachteil.

dag
quelle
Leider geht der Speicher aus und es explodiert ziemlich schnell.
David St Denis
Ja, es kann Ihren Stapel in die Luft jagen, abhängig von der Anzahl der Dateien, die Sie verarbeiten. Ich habe es mit ungefähr 66 Dateien ziemlich erfolgreich gemacht (ohne erhöhte Speicheroptionen). Dies ist jedoch nur dann eine Option, wenn Sie keinen Zugriff auf Ant-Contrib haben, das mehr Funktionen bietet (z. B. parallel ausgeführt wird).
Tag
Das war sehr hilfreich.
DiamondDrake
0

Sie können die Ant-Contrib-Aufgabe "für" verwenden, um die Liste der Dateien zu durchlaufen, die durch ein beliebiges Delimeter getrennt sind. Das Standard-Delimeter ist ",".

Das Folgende ist die Beispieldatei, die dies zeigt:

<project name="modify-files" default="main" basedir=".">
    <taskdef resource="net/sf/antcontrib/antlib.xml"/>
    <target name="main">
        <for list="FileA,FileB,FileC,FileD,FileE" param="file">
          <sequential>
            <echo>Updating file: @{file}</echo>
            <!-- Do something with file here -->
          </sequential>
        </for>                         
    </target>
</project>
Hemant
quelle
0

Tun Sie, was blak3r vorgeschlagen hat, und definieren Sie den Klassenpfad Ihres Ziels so

<taskdef resource="net/sf/antcontrib/antlib.xml">
    <classpath>
        <fileset dir="lib">
          <include name="**/*.jar"/>
        </fileset>
    </classpath>        
</taskdef>

wo lib ist, wo Sie Ihre Gläser aufbewahren

Bungee
quelle