Ant: Como executar um comando para cada arquivo no diretório?

97

Eu quero executar um comando de um buildfile Ant, para cada arquivo em um diretório.
Estou procurando uma solução independente de plataforma.

Como eu faço isso?

Claro, eu poderia escrever um script em alguma linguagem de script, mas isso adicionaria mais dependências ao projeto.

ivan_ivanovich_ivanoff
fonte

Respostas:

61

Resposta curta

Use <foreach>com um aninhado<FileSet>

Foreach requer ant-contrib .

Exemplo atualizado para ant-contrib recente:

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

Isso irá chamar a "barra" de destino com o $ {theFile} resultando no arquivo atual.

blak3r
fonte
Então, eu tenho que incluir algo? Ou eu preciso de alguma biblioteca externa de formigas? Estou recebendo "Problema: falha ao criar tarefa ou digitar foreach" . Se bem entendi, isso significa que foreach é uma palavra-chave desconhecida.
ivan_ivanovich_ivanoff
4
Ohhh coxo ... é uma tarefa Ant-Contrib. Então, você tem que instalar algo. Veja aqui: ant-contrib.sourceforge.net
blak3r
3
Esse exemplo é de um foreach antes de ser incluído no ant-contrib. Há um bom exemplo em ant.1045680.n5.nabble.com/Using-foreach-td1354624.html Vou atualizar o exemplo para funcionar.
Sean
2
O elemento do conjunto de arquivos aninhado está obsoleto, use um caminho aninhado em vez disso
cara,
@dude Use <foreach> <path> <fileset> <include name = "*. jar" /> </fileset> </path> </foreach>
Sharat
88

Use a tarefa <apply> .

Ele executa um comando uma vez para cada arquivo. Especifique os arquivos por meio de conjuntos de arquivos ou qualquer outro recurso. <apply> está embutido; nenhuma dependência adicional necessária; nenhuma implementação de tarefa personalizada necessária.

Também é possível executar o comando apenas uma vez, anexando todos os arquivos como argumentos de uma vez. Use o atributo parallel para mudar o comportamento.

Desculpe pelo atraso de um ano.

Alex
fonte
4
Bem, eu achei isso útil em 2011, então obrigado por isso de qualquer maneira!
Michael Della Bitta
7
O problema com <apply> é que ele executa apenas comandos externos do sistema. Ele não fará nenhuma outra coisa diretamente. Não está claro se essa é a questão original ou não. Basicamente, você tem que usar <apply> ou <foreach> para as duas situações diferentes ... o que é totalmente estúpido.
Archie
27

Uma abordagem sem ant-contrib é sugerida por Tassilo Horn (o alvo original está aqui )

Basicamente, como não há extensão de <java> (ainda?) Da mesma forma que <apply> estende <exec> , ele sugere o uso de <apply> (que pode, é claro, também executar um programa java em uma linha de comando)

Aqui estão alguns exemplos:

  <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
fonte
2
Não é muito independente de plataforma, embora fosse claramente exigido na pergunta original ...
Chucky
18

Esta é uma maneira de fazer isso usando javascript e a tarefa ant scriptdef, você não precisa do ant-contrib para que esse código funcione, pois o scriptdef é uma tarefa ant central.

<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
fonte
uma variável projecté referenciada no exemplo acima, mas sem nenhuma definição prévia disponível. Seria bom ter isso representado ou esclarecido. EDIT: n / m, descobri que projecté uma var predefinida para acessar o projeto atual. Eu suspeitava disso, mas não tinha certeza.
Jon L.
1
Para aqueles que estão tentando fazer isso no JDK8 ou posterior, tenha em mente que "importClass" funciona se o ScriptEngine carregado pelo JRE for rhino (o que era verdade para a maioria dos JDK 6 e 7), enquanto em Nashorn (do 8 em diante) você pode usar o compatível com versões anteriores "File = java.io.File" ou o mais recente, mas não compatível com versões anteriores, Java.type. O Ant parece falhar silenciosamente quando tem problemas ao executar o scriptdef, como pude experimentar hoje.
Matteo Steccolini
15

ant-contrib é mau; escrever uma tarefa de formiga personalizada.

ant-contrib é mau porque tenta converter formiga de um estilo declarativo para um estilo imperativo. Mas o xml é uma linguagem de programação de merda.

Por outro lado, uma tarefa de formiga personalizada permite que você escreva em uma linguagem real (Java), com um IDE real, onde você pode escrever testes de unidade para se certificar de que tem o comportamento desejado e, em seguida, fazer uma declaração limpa em seu script de construção sobre o comportamento que você deseja.

Este discurso só importa se você se preocupa em escrever scripts de formigas sustentáveis. Se você não se preocupa com a sustentabilidade por todos os meios, faça o que funcionar. :)

Jtf

Jeffrey Fredrick
fonte
2
Você está certo, eu deveria apenas escrever uma tarefa personalizada de formigas em Java;)
ivan_ivanovich_ivanoff
4
ant-contrib realmente é mau. No momento, estou no meio de um grande projeto de construção de formigas que faz uso intenso de if / then / else e antcalls e é realmente horrível. A coisa toda se parece com um script batch / shell convertido e todas as dependências que o ant faz são completamente desativadas pelo uso pesado do ant-contrib. Se você quiser manter sua configuração limpa, crie sua própria tarefa. : - /
estremecer de
2
@cringe, eu discordo. Como qualquer coisa, você precisa saber quando é do seu interesse usar o ant-contrib. Fique longe de coisas como if e var e use ant-contrib para evitar ter que reinventar a roda.
Neil
1
E deveria ter sido uma linguagem de script, tão imperativa e não declarativa, para começar. Então é E quem é o mal IMO
mvmn
Talvez ant-contrib seja um mal a priori, mas o uso que o black3r faz dele parece razoável e antipático, por assim dizer.
ssimm de
7

Eu sei que este post é muito antigo, mas agora que algum tempo e as versões do Ant se passaram, existe uma maneira de fazer isso com os recursos básicos do Ant e achei que deveria compartilhá-lo.

Isso é feito por meio de um macrodef recursivo que chama tarefas aninhadas (até mesmo outras macros podem ser chamadas). A única convenção é usar um nome de variável fixo (elemento aqui).

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

Os principais recursos necessários são:

Não consegui fazer a variável delimitadora, mas isso pode não ser uma grande desvantagem.

dag
fonte
Infelizmente, isso fica sem memória e explode rapidamente.
David St Denis
Sim, pode explodir sua pilha, dependendo do número de arquivos que você processa. Fiz isso com bastante sucesso com cerca de 66 arquivos (sem opções de memória aumentadas). No entanto, é apenas uma opção se você não tiver acesso ao ant-contrib, que oferece mais funcionalidades (por exemplo, execução em paralelo).
dag
Isso foi muito útil.
DiamondDrake
0

Você pode usar a tarefa ant-contrib "for" para iterar na lista de arquivos separados por qualquer delimitador, o delimitador padrão é ",".

A seguir está o arquivo de amostra que mostra isso:

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

Faça o que o blak3r sugeriu e defina seu classpath de destino assim

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

onde lib é onde você guarda o seu jar

elástico
fonte