Ant: ¿Cómo ejecutar un comando para cada archivo en el directorio?

97

Quiero ejecutar un comando desde un archivo de compilación Ant, para cada archivo en un directorio.
Busco una solución independiente de la plataforma.

¿Cómo hago esto?

Claro, podría escribir un script en algún lenguaje de script, pero esto agregaría más dependencias al proyecto.

ivan_ivanovich_ivanoff
fuente

Respuestas:

61

Respuesta corta

Usar <foreach>con un anidado<FileSet>

Foreach requiere ant-contrib .

Ejemplo actualizado para ant-contrib reciente:

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

Esto anulará la "barra" de destino con el $ {theFile} resultando en el archivo actual.

blak3r
fuente
Entonces, ¿tengo que incluir algo? ¿O necesito alguna biblioteca de hormigas externa? Recibo "Problema: no se pudo crear la tarea o escribir foreach" . Si lo entiendo correctamente, esto significa que foreach es una palabra clave desconocida.
ivan_ivanovich_ivanoff
4
Ohhh cojo ... es una tarea Ant-Contrib. Entonces, tienes que instalar algo. Ver aquí: ant-contrib.sourceforge.net
blak3r
3
Ese ejemplo es de un foreach antes de que se incluyera en ant-contrib. Hay un buen ejemplo en ant.1045680.n5.nabble.com/Using-foreach-td1354624.html Voy a actualizar el ejemplo para que funcione.
Sean
2
El elemento de conjunto de archivos anidado está obsoleto, use una ruta anidada en su lugar
amigo
@dude Use <foreach> <path> <fileset> <include name = "*. jar" /> </fileset> </path> </foreach>
Sharat
88

Utilice la tarea <apply> .

Ejecuta un comando una vez para cada archivo. Especifique los archivos mediante catálogos de archivos o cualquier otro recurso. <apply> está integrado; no se necesita dependencia adicional; no se necesita implementación de tareas personalizadas.

También es posible ejecutar el comando solo una vez, agregando todos los archivos como argumentos de una sola vez. Utilice el atributo paralelo para cambiar el comportamiento.

Perdón por llegar tarde un año.

Alex
fuente
4
Bueno, encontré esto útil en 2011, ¡así que gracias por eso de todos modos!
Michael Della Bitta
7
El problema con <apply> es que solo ejecuta comandos externos del sistema. No hará nada directamente. No está claro si de eso se trataba la pregunta original o no. Básicamente tienes que usar <apply> o <foreach> para las dos situaciones diferentes ... lo cual es totalmente tonto.
Archie
27

Tassilo Horn sugiere un enfoque sin ant-contrib (el objetivo original está aquí )

Básicamente, como no hay una extensión de <java> (¿todavía?) De la misma manera que <apply> extiende <exec> , sugiere usar <apply> (que, por supuesto, también puede ejecutar un programa java en una línea de comando)

Aquí algunos ejemplos:

  <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
fuente
2
Esto no es muy independiente de la plataforma, aunque claramente se requería en la pregunta original ...
Chucky
18

Aquí hay una forma de hacer esto usando javascript y la tarea ant scriptdef, no necesita ant-contrib para que este código funcione ya que scriptdef es una tarea central de 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
fuente
projectse hace referencia a una variable en el ejemplo anterior, pero sin una definición previa disponible. Sería bueno tener eso representado o aclarado. EDITAR: n / m, se encontró que projectes una var predefinida para acceder al proyecto actual. Lo sospechaba, pero no estaba seguro.
Jon L.
1
Para aquellos que intentan esto en JDK8 o posterior, tengan en cuenta que "importClass" funciona si el ScriptEngine cargado por JRE es rhino (que era cierto para la mayoría de JDK 6 y 7), mientras que en Nashorn (a partir de 8) puede usar el "File = java.io.File" compatible con versiones anteriores o el tipo Java.type más nuevo pero no compatible con versiones anteriores. Ant parece fallar silenciosamente cuando tiene problemas para ejecutar scriptdef, como pude experimentar hoy.
Matteo Steccolini
15

ant-contrib es maligno; escribir una tarea de hormiga personalizada.

ant-contrib es maligno porque intenta convertir ant de un estilo declarativo a un estilo imperativo. Pero xml es un lenguaje de programación basura.

Por el contrario, una tarea de hormiga personalizada le permite escribir en un lenguaje real (Java), con un IDE real, donde puede escribir pruebas unitarias para asegurarse de tener el comportamiento que desea, y luego hacer una declaración limpia en su script de compilación sobre el comportamiento que desea.

Esta perorata solo importa si le interesa escribir scripts de hormigas que se puedan mantener. Si no le importa la mantenibilidad, haga lo que sea que funcione. :)

Jtf

Jeffrey Fredrick
fuente
2
Tienes razón, debería escribir una tarea de hormigas personalizada en Java;)
ivan_ivanovich_ivanoff
4
ant-contrib realmente es malvado. Ahora mismo estoy en medio de un gran proyecto de construcción de hormigas que hace un uso intenso de if / then / else y antcalls y realmente se lee horrible. Todo parece un script por lotes / shell convertido y todas las cosas de dependencia que hace ant están completamente desactivadas por el uso intensivo de ant-contrib. Si desea mantener limpia su configuración, cree su propia tarea. : - /
cringe
2
@cringe, no estoy de acuerdo. Como todo, debe saber cuándo le conviene usar ant-contrib. Manténgase alejado de cosas como if y var y use ant-contrib para evitar tener que reinventar la rueda.
Neil
1
Y debería haber sido un lenguaje de secuencias de comandos, tan imperativo y no declarativo, para empezar. Entonces es Y quién es el mal en mi opinión
mvmn
Tal vez ant-contrib sea a priori malvado, pero el uso que hace black3r parece razonable y ant-ish, por así decirlo.
ssimm
7

Sé que esta publicación es muy antigua, pero ahora que pasaron algunas versiones de ant, hay una manera de hacer esto con características básicas de ant y pensé que debería compartirla.

Se hace a través de una macrodef recursiva que llama a tareas anidadas (incluso se pueden llamar otras macros). La única convención es usar un nombre de variable fijo (elemento aquí).

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

Las características clave necesarias donde:

No logré hacer el delimitador variable, pero esto puede no ser un inconveniente importante.

trozo de cuero
fuente
Desafortunadamente, esto se queda sin memoria y explota bastante rápido.
David St Denis
Sí, puede hacer explotar su pila, dependiendo de la cantidad de archivos que procese. Lo hice con bastante éxito con unos 66 archivos (sin mayores opciones de memoria). Sin embargo, es sólo una opción si no tiene acceso a ant-contrib, que ofrece más funciones (por ejemplo, en paralelo).
dag
Esto fue muy útil.
DiamondDrake
0

Puede usar la tarea ant-contrib "for" para iterar en la lista de archivos separados por cualquier delimitador, el delimitador predeterminado es ",".

A continuación se muestra el archivo de muestra que muestra esto:

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

Haga lo que blak3r sugirió y defina su classpath de objetivos así

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

donde lib es donde almacena su jar

bungee
fuente