Ant: Comment exécuter une commande pour chaque fichier dans le répertoire?

97

Je veux exécuter une commande à partir d'un buildfile Ant, pour chaque fichier dans un répertoire.
Je recherche une solution indépendante de la plateforme.

Comment puis-je faire cela?

Bien sûr, je pourrais écrire un script dans un langage de script, mais cela ajouterait des dépendances supplémentaires au projet.

ivan_ivanovich_ivanoff
la source

Réponses:

61

Réponse courte

À utiliser <foreach>avec un<FileSet>

Foreach nécessite ant-contrib .

Exemple mis à jour pour ant-contrib récent:

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

Cela appellera la "barre" cible avec le $ {theFile} résultant dans le fichier courant.

blak3r
la source
Alors, je dois inclure quelque chose? Ou ai-je besoin d'une ant lib externe? J'obtiens "Problème: impossible de créer la tâche ou de taper foreach" . Si je comprends bien, cela signifie que foreach est un mot-clé inconnu.
ivan_ivanovich_ivanoff
4
Ohhh boiteux ... c'est une tâche Ant-Contrib. Donc, vous devez installer quelque chose. Voir ici: ant-contrib.sourceforge.net
blak3r
3
Cet exemple provient d'un foreach avant d'être inclus dans ant-contrib. Il y a un bon exemple à ant.1045680.n5.nabble.com/Using-foreach-td1354624.html Je vais mettre à jour l'exemple pour qu'il fonctionne.
Sean
2
L'élément de l'ensemble de fichiers imbriqué est obsolète, utilisez plutôt un chemin imbriqué
mec
@dude Use <foreach> <path> <fileset> <include name = "*. jar" /> </fileset> </path> </foreach>
Sharat
88

Utilisez la tâche <apply> .

Il exécute une commande une fois pour chaque fichier. Spécifiez les fichiers au moyen d'ensembles de fichiers ou de toute autre ressource. <apply> est intégré; aucune dépendance supplémentaire nécessaire; aucune implémentation de tâche personnalisée nécessaire.

Il est également possible d'exécuter la commande une seule fois, en ajoutant tous les fichiers en tant qu'arguments en une seule fois. Utilisez l'attribut parallel pour changer de comportement.

Désolé d'être en retard d'un an.

Alex
la source
4
Eh bien, je viens de trouver cela utile en 2011, alors merci pour cela quand même!
Michael Della Bitta
7
Le problème avec <apply> est qu'il n'exécute que des commandes système externes. Cela ne fera rien directement. On ne sait pas si c'est ce sur quoi portait la question initiale ou non. En gros, vous devez utiliser <apply> ou <foreach> pour les deux situations différentes ... ce qui est totalement idiot.
Archie
27

Une approche sans ant-contrib est suggérée par Tassilo Horn (la cible d'origine est ici )

Fondamentalement, comme il n'y a pas d'extension de <java> (encore?) De la même manière que <apply> étend <exec> , il suggère d'utiliser <apply> (qui peut bien sûr aussi exécuter un programme java en ligne de commande)

Voici quelques exemples:

  <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
la source
2
Ce n'est pas très indépendant de la plate-forme bien que cela soit clairement requis dans la question initiale ...
Chucky
18

Voici un moyen de le faire en utilisant javascript et la tâche ant scriptdef, vous n'avez pas besoin d'ant-contrib pour que ce code fonctionne car scriptdef est une tâche fourmi principale.

<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
la source
une variable projectest référencée dans l'exemple ci-dessus, mais sans définition préalable disponible. Ce serait bien d'avoir cela représenté ou clarifié. EDIT: n / m, trouvé qu'il projects'agit d'une var prédéfinie pour accéder au projet en cours. J'avais soupçonné cela, mais je n'en étais pas sûr.
Jon L.
1
Pour ceux qui essaient cela dans JDK8 ou version ultérieure, gardez à l'esprit que "importClass" fonctionne si le ScriptEngine chargé par le JRE est rhino (ce qui était vrai pour la plupart des JDK 6 et 7), tandis que dans Nashorn (à partir de 8), vous pouvez utiliser le "File = java.io.File" rétrocompatible ou Java.type plus récent mais pas rétrocompatible. Ant semble échouer silencieusement en rencontrant des problèmes lors de l'exécution de scriptdef, comme je l'ai expérimenté aujourd'hui.
Matteo Steccolini
15

ant-contrib est le mal; écrivez une tâche de fourmi personnalisée.

ant-contrib est mauvais parce qu'il essaie de convertir fourmi d'un style déclaratif à un style impératif. Mais xml fait un langage de programmation de merde.

En revanche, une tâche ant personnalisée vous permet d'écrire dans un vrai langage (Java), avec un véritable IDE, où vous pouvez écrire des tests unitaires pour vous assurer que vous avez le comportement souhaité, puis faire une déclaration propre dans votre script de construction à propos de le comportement que vous souhaitez.

Cette diatribe n'a d'importance que si vous vous souciez d'écrire des scripts ant maintenables. Si vous ne vous souciez pas de la maintenabilité, faites ce qui fonctionne. :)

Jtf

Jeffrey Fredrick
la source
2
Vous avez raison, je devrais juste écrire une tâche de fourmi personnalisée en Java;)
ivan_ivanovich_ivanoff
4
ant-contrib est vraiment mauvais. En ce moment, je suis au milieu d'un grand projet de construction de fourmis qui utilise intensément if / then / else et antcalls et ça lit vraiment horrible. Le tout ressemble à un script batch / shell converti et toutes les dépendances que Ant fait est complètement désactivée par l'utilisation intensive de ant-contrib. Si vous souhaitez garder votre configuration propre, créez votre propre tâche. : - /
grincer des dents
2
@cringe, je ne suis pas d'accord. Comme pour tout, vous devez savoir quand il est dans votre intérêt d'utiliser ant-contrib. Éloignez-vous de choses comme if et var et utilisez ant-contrib pour éviter d'avoir à réinventer la roue.
Neil
1
Et cela aurait dû être un langage de script, si impératif et non déclaratif, pour commencer. Alors c'est et qui est le mal IMO
MVMN
Peut-être que ant-contrib est a priori mauvais, mais l'utilisation par black3r semble raisonnable et ant-ish, pour ainsi dire.
ssimm
7

Je sais que cet article est vraiment vieux, mais maintenant que certaines versions de temps et de fourmis sont passées, il existe un moyen de le faire avec les fonctionnalités de base des fourmis et j'ai pensé que je devrais le partager.

Cela se fait via un macrodef récursif qui appelle des tâches imbriquées (même d'autres macros peuvent être appelées). La seule convention est d'utiliser un nom de variable fixe (élément ici).

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

Les principales fonctionnalités nécessaires là où:

Je n'ai pas réussi à faire le variabel délimiteur, mais ce n'est peut-être pas un inconvénient majeur.

dag
la source
Malheureusement, cela manque de mémoire et explose assez rapidement.
David St Denis
Oui, cela peut faire exploser votre pile, en fonction du nombre de fichiers que vous traitez. Je l'ai fait avec beaucoup de succès avec environ 66 fichiers (sans options de mémoire accrues). Cependant, il n est possible qu une option si vous n avez pas accès à ant-contrib qui offre plus de fonctionnalités (ex: fonctionnement en parallèle).
dag
Cela a été très utile.
DiamondDrake
0

Vous pouvez utiliser la tâche ant-contrib "for" pour parcourir la liste des fichiers séparés par n'importe quel délimiteur, le délimiteur par défaut est ",".

Voici l'exemple de fichier qui montre ceci:

<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
la source
0

Faites ce que blak3r a suggéré et définissez le chemin de classe de vos cibles comme ceci

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

où lib est l'endroit où vous stockez votre pot

élastique
la source