我可以将Ant'replace'任务输出发送到新文件吗?

时间:2010-05-07 21:28:49

标签: ant replace

Ant replace任务在不创建新文件的情况下执行就地替换。

以下代码段使用'my.properties'文件中的相应值替换任何'* .xml'文件中的标记。

<replace dir="${projects.prj.dir}/config"
         replacefilterfile="${projects.prj.dir}/my.properties"
         includes="*.xml" summary="true" />

我希望那些替换了其标记的文件以模式(例如)'* .xml.filtered'命名,并保留原始文件。

这可以在Ant中实现一些智能组合吗?

2 个答案:

答案 0 :(得分:7)

有两种方法可以接近您想要的内容而无需复制到临时目录并进行复制。

Filtersets

如果可以更改源文件,以便可以使用开始和结束标记分隔要替换的部分,如@date@@是默认标记,但可以更改)然后,您可以将copy taskglobmapperfilterset

一起使用
<copy todir="config">
  <fileset dir="config" includes="*.xml" />
  <globmapper from="*.xml" to="*.xml.filtered" />
  <filterset filtersfile="replace.properties" />
</copy>

如果replace.properties包含FOO = bar,那么源xml文件文件中出现的任何@FOO@都会被目标​​中的bar替换。

请注意,源目录和目标目录相同,globmapper表示目标文件,并以后缀.filtered命名。将文件复制到不同的目标目录是可能的(也是更常见的)

Filterchains

如果无法更改源文件以添加开始和结束标记,则可能的替代方法是使用filterchain一个或多个replacestring filters而不是filterset

<copy todir="config">
  <fileset dir="config" includes="*.xml" />
  <globmapper from="*.xml" to="*.xml.filtered" />
  <filterchain>
    <tokenfilter>
      <replacestring from="foo" to="bar" />
      <!-- extra replacestring elements here as required -->
    </tokenfilter>
  </filterchain>
</copy>

这将在文件中的任何位置替换foobar的任何匹配项,这更像是replace任务的行为。不幸的是,这种方式意味着您需要在构建文件本身中包含所有替换项,您不能将它们包含在单独的属性文件中。

在这两种情况下,copy任务只会复制比目标文件更新的源文件,因此不会进行不必要的工作。

复制然后替换

第三种可能性(在编写其他两种情况时刚刚发生的那种情况)是首先执行复制到重命名的文件,然后运行指定重命名文件的replace任务:

<copy todir="config">
  <fileset dir="config" includes="*.xml" />
  <globmapper from="*.xml" to="*.xml.filtered" />
</copy>
<replace dir="config" replacefilterfile="replace.properties" summary="true" 
  includes="*.xml.filtered" />

这可能是最原始要求的最接近的解决方案。缺点是每次重命名的文件都会运行replace任务。对于某些替换模式,这可能是一个问题(诚然,它们会像foo=foofoo那样奇怪,但前两种方法都可以。)当依赖关系不变时,你将做不必要的工作。 / p>

答案 1 :(得分:1)

replace任务没有观察到依赖关系,而是通过为每个输入文件写一个临时文件来执行替换。如果临时文件与输入文件相同,则将其丢弃。将重命名与输入文件不同的临时文件以替换该输入。这意味着所有文件都被处理,即使它们都不需要 - 因此它可能效率低下。

此问题的原始解决方案是执行复制替换副本。但是不需要第二个副本,因为可以在第一个副本中使用映射器。在副本中,依赖关系可用于将处理限制为仅更改的文件 - 通过显式文件集中的depend selector

<copy todir="${projects.prj.dir}">
  <fileset dir="${projects.prj.dir}">
    <include name="*.xml" />
    <depend targetdir="${projects.prj.dir}">
      <mapper type="glob" from="*.xml" to="*.xml.filtered" />
    </depend>
  </fileset>
  <mapper type="glob" from="*.xml" to="*.xml.filtered" />
</copy>

这会将复制文件集限制为那些已更改的文件。映射器的另一种语法是:

<globmapper from="*.xml" to="*.xml.filtered" />

最简单的替换是:

<replace dir="${projects.prj.dir}"
         replacefilterfile="my.properties"
         includes="*.xml.filtered" />

即使这些文件都不需要进行替换,它仍会处理所有文件。 replace任务具有隐式文件集,可以对显式文件集进行操作,但与类似任务不同,隐式文件集不是可选的,因此为了利用显式文件集中的选择器,必须使隐式文件集“无所事事” - 因此.dummy文件在这里:

<replace dir="${projects.prj.dir}"
         replacefilterfile="my.properties">
         includes=".dummy" />
  <fileset dir="${projects.prj.dir}" includes="*.xml.filtered">
    <not>
      <different targetdir="${projects.prj.dir}">
        <globmapper from="*.xml.filtered" to="*.xml" />
      </different>
    </not>
  </fileset>
</replace>

这将阻止replace任务不必要地处理先前已经过替换的文件。但是,它不会阻止处理未更改且不需要替换的文件。

除此之外,我不确定是否有办法'编码高尔夫'这个问题,以减少步骤的数量。 没有多个字符串替换过滤器可以在copy任务中使用以实现与replace相同的效果,这是一种耻辱,因为感觉它是正确的解决方案。

另一种方法是为一系列replace string过滤器生成xml,然后让Ant执行该过滤器。但是这将比现有解决方案更复杂,并且容易出现替换字符串的问题,如果粘贴到xml片段中将导致无法解析的内容。

另一种方法是编写自定义任务或script任务来完成工作。如果有许多文件并且判断复制替换解决方案太慢,那么这可能是要走的路。但同样,这种方法不如现有解决方案简单。

如果要求最小化处理中的工作,而不是提出最短的Ant解决方案,那么这种方法可能会这样做。

  • 创建一个包含已更改输入列表的文件集。
  • 从该文件集中创建一个逗号分隔的相应过滤文件列表。
  • 在文件集上执行副本。
  • 在逗号分隔列表中执行替换。

这里的一个问题是,如果没有文件发生变化,替换任务中的隐式文件集将回退到处理所有内容。为了解决这个问题,我们插入一个虚拟文件名。

<fileset id="changed" dir="${projects.prj.dir}" includes="*.xml">
  <depend targetdir="${projects.prj.dir}">
    <globmapper from="*.xml" to="*.xml.filtered" />
  </depend>
</fileset>

<pathconvert property="replace.includes" refid="changed">
  <map from=".xml" to=".xml.filtered" />
</pathconvert>

<copy todir="${projects.prj.dir}" preservelastmodified="true">
  <fileset refid="changed" />
 <globmapper from="*.xml" to="*.xml.filtered" />
</copy>

<replace dir="${projects.prj.dir}"
         replacefilterfile="my.properties"
         includes=".dummy,${replace.includes}" summary="true" />