Ant: Jak wykonać polecenie dla każdego pliku w katalogu?

97

Chcę wykonać polecenie z pliku kompilacji Ant, dla każdego pliku w katalogu.
Szukam rozwiązania niezależnego od platformy.

Jak mam to zrobic?

Jasne, mógłbym napisać skrypt w jakimś języku skryptowym, ale dodałoby to dalsze zależności do projektu.

ivan_ivanovich_ivanoff
źródło

Odpowiedzi:

61

Krótka odpowiedź

Użyj <foreach>z zagnieżdżonym plikiem<FileSet>

Foreach wymaga ant-contrib .

Zaktualizowany przykład dla niedawnego 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>

Spowoduje to wywołanie docelowego "baru" z $ {theFile} skutkującym plikiem bieżącym.

blak3r
źródło
Więc muszę coś dodać? Czy potrzebuję zewnętrznej biblioteki Ant? Otrzymuję komunikat „Problem: nie udało się utworzyć zadania lub wpisać foreach” . Jeśli dobrze rozumiem, oznacza to, że foreach jest nieznanym słowem kluczowym.
ivan_ivanovich_ivanoff
4
Ohhh lame ... to zadanie Ant-Contrib. Więc musisz coś zainstalować. Zobacz tutaj: ant-contrib.sourceforge.net
blak3r
3
Ten przykład pochodzi z foreach, zanim został uwzględniony w ant-contrib. Dobry przykład jest pod adresem ant.1045680.n5.nabble.com/Using-foreach-td1354624.html Zaktualizuję przykład, aby działał.
Sean
2
Zagnieżdżony element zestawu plików został usunięty, zamiast tego użyj zagnieżdżonej ścieżki
stary,
@dude Use <foreach> <path> <fileset> <include name = "*. jar" /> </fileset> </path> </foreach>
Sharat,
88

Użyj zadania <apply> .

Wykonuje polecenie raz dla każdego pliku. Określ pliki za pomocą zestawów plików lub dowolnego innego zasobu. <apply> jest wbudowane; nie jest potrzebna żadna dodatkowa zależność; nie ma potrzeby wykonywania niestandardowych zadań.

Możliwe jest również uruchomienie polecenia tylko raz, dołączając wszystkie pliki jako argumenty za jednym razem. Użyj atrybutu parallel, aby zmienić zachowanie.

Przepraszam za spóźnienie o rok.

Alex
źródło
4
Właśnie znalazłem to przydatne w 2011 roku, więc i tak dzięki za to!
Michael Della Bitta,
7
Problem z <apply> polega na tym, że wykonuje tylko zewnętrzne polecenia systemowe. Nie zrobi nic bezpośrednio. Nie jest jasne, czy o to chodziło w pierwotnym pytaniu, czy nie. Zasadniczo musisz użyć <apply> lub <foreach> w dwóch różnych sytuacjach ... co jest całkowicie głupie.
Archie
27

Podejście bez wkładu mrówek jest sugerowane przez Tassilo Horn ( oryginalny cel jest tutaj )

Zasadniczo, ponieważ nie ma rozszerzenia <java> (jeszcze?) W taki sam sposób, w jaki <apply> rozszerza <exec> , sugeruje użycie <apply> (które oczywiście może również uruchomić program java w wierszu poleceń)

Oto kilka przykładów:

  <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
źródło
2
To nie jest bardzo niezależne od platformy, chociaż było to wyraźnie wymagane w pierwotnym pytaniu ...
Chucky
18

Oto sposób na zrobienie tego za pomocą javascript i zadania ant scriptdef. Nie potrzebujesz ant-contrib, aby ten kod działał, ponieważ scriptdef jest podstawowym zadaniem ant.

<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
źródło
projectw powyższym przykładzie występuje odniesienie do zmiennej , ale bez udostępnionej wcześniejszej definicji. Byłoby dobrze, gdyby to zostało przedstawione lub wyjaśnione. EDYCJA: n / m, stwierdzono, że projectjest to predefiniowana zmienna dostępu do bieżącego projektu. Podejrzewałem to, ale nie byłem pewien.
Jon L.
2
Dla tych, którzy próbują tego w JDK8 lub nowszym, pamiętajcie, że "importClass" działa, jeśli ScriptEngine załadowany przez JRE to rhino (co było prawdą dla większości JDK 6 i 7), podczas gdy w Nashorn (od 8) możesz użyć kompatybilny wstecz "File = java.io.File" lub nowszy, ale nie kompatybilny wstecz Java.type. Wydaje się, że Ant po cichu zawodzi, gdy mam problemy z uruchomieniem scripttdef, czego doświadczyłem dzisiaj.
Matteo Steccolini
16

mrówka jest złem; napisz własne zadanie mrówki.

ant-contrib jest zła, ponieważ próbuje zmienić styl mrówki ze stylu deklaratywnego na imperatywny. Ale xml tworzy gówniany język programowania.

W przeciwieństwie do tego, niestandardowe zadanie mrówki umożliwia pisanie w prawdziwym języku (Java), w prawdziwym środowisku IDE, w którym można pisać testy jednostkowe, aby upewnić się, że zachowuje się pożądane, a następnie sporządzić czystą deklarację w skrypcie kompilacji o pożądane zachowanie.

Ta racja ma znaczenie tylko wtedy, gdy zależy ci na pisaniu skryptów mrówek, które można utrzymać. Jeśli nie zależy Ci na łatwości utrzymania, zrób wszystko, co działa. :)

Jtf

Jeffrey Fredrick
źródło
2
Masz rację, powinienem po prostu napisać niestandardowe zadanie mrówek w Javie;)
ivan_ivanovich_ivanoff
4
mrówka naprawdę jest zła. W tej chwili jestem w trakcie dużego projektu kompilacji mrówek, który intensywnie wykorzystuje if / then / else i antcalls, i naprawdę brzmi okropnie. Całość wygląda jak przekonwertowany skrypt wsadowy / powłoki, a wszystkie zależności, które robi ant, są całkowicie wyłączone przez intensywne użycie ant-contrib. Jeśli chcesz zachować czystość konfiguracji, utwórz własne zadanie. : - /
cringe
2
@cringe, nie zgadzam się. Jak wszystko, musisz wiedzieć, kiedy korzystanie z ant-contrib leży w twoim najlepszym interesie. Trzymaj się z dala od rzeczy takich jak if i var i używaj ant-contrib, aby uniknąć konieczności ponownego wynajdywania koła.
Neil
1
I powinien był to być język skryptowy, tak konieczny, a nie deklaratywny, na początek. Więc to jest A kto jest zły IMO
mvmn
Być może ant-contrib jest złem a priori, ale użycie go przez black3r wydaje się rozsądne i mrówcze, że tak powiem.
ssimm
7

Wiem, że ten post jest naprawdę stary, ale teraz, gdy minęło trochę czasu i wersji Ant, jest sposób na zrobienie tego z podstawowymi funkcjami Ant i pomyślałem, że powinienem się nim podzielić.

Odbywa się to za pomocą rekurencyjnej makrodefy, która wywołuje zagnieżdżone zadania (można wywołać nawet inne makra). Jedyną konwencją jest użycie stałej nazwy zmiennej (tutaj elementu).

<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>

Najważniejsze funkcje potrzebne, gdy:

Nie udało mi się ustawić separatora zmiennego, ale może to nie być poważny minus.

dag
źródło
Niestety zabrakło pamięci i dość szybko wybucha.
David St Denis
Tak, może to spowodować wysadzenie stosu, w zależności od liczby przetwarzanych plików. Zrobiłem to całkiem pomyślnie z około 66 plikami (bez zwiększonych opcji pamięci). Jednak jest to opcja tylko wtedy, gdy nie masz dostępu do programu Ant-contrib, który oferuje większą funkcjonalność (np. Działa równolegle).
dag
To było bardzo pomocne.
DiamondDrake
0

Możesz użyć zadania ant-contrib „for”, aby iterować na liście plików oddzielonych dowolnym separatorem, domyślnym separatorem jest „,”.

Poniżej znajduje się przykładowy plik, który to pokazuje:

<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
źródło
0

Zrób to, co sugerował blak3r i zdefiniuj ścieżkę klas swoich celów

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

gdzie lib to miejsce, w którym przechowujesz swoje słoiki

bungee
źródło