The replace task does not take into account dependencies; instead, it performs the replacement by writing a temporary file for each input file. If the temporary file matches the input file, it is discarded. A temporary file that is different from the input file is renamed to replace this input. This means that all files are processed, even if none of them are needed - therefore, it can be inefficient.
The original solution to this issue was to execute a copy-replace copy. The second copy is not needed, since in the first case you can use a cartographer. In a copy, dependencies can be used to limit processing to only modified files - using the depend selector in an explicit set:
<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>
This will limit the set of copies to only those files that have been modified. Alternative syntax for mappers:
<globmapper from="*.xml" to="*.xml.filtered" />
The simplest replacement would be the following:
<replace dir="${projects.prj.dir}" replacefilterfile="my.properties" includes="*.xml.filtered" />
This will still process all the files, even if none of them needs to be replaced. The replace task has an implicit file set and can work with an explicit file set, but unlike similar tasks, an implicit file set is not optional, therefore, to use selectors in an explicit file set, you must do an implicit "do nothing" - hence .dummy here:
<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>
This will prevent unnecessary processing of the replace task by processing files that were previously replaced. However, it does not prevent the processing of files that have not been modified and do not need to be substituted.
Other than that, I'm not sure there is a way to “encode golf” with this problem in order to reduce the number of steps to one. There is no multi-line filter that can be used in the copy task to achieve the same effect as replace , which is a shame because it seems like it would be the right solution.
Another approach would be to generate xml for a series of filters replace string , and then execute Ant. But it will be more complicated than the existing solution, and prone to problems with replacing strings, which, if inserted into the xml fragment, will lead to something that cannot be parsed.
Another approach would be to create a custom task or script task to do this work. If there are many files, and the solution to replace the copy is considered too slow, then this may be the way to go. But again, this approach is less simple than the existing solution.
If the requirement is to minimize the work done during processing, instead of coming up with the shortest Ant solution, this approach can do.
- Make a set of files containing a list of inputs that have changed.
- From this set of files, create a comma-separated list of filters.
- Make a copy on the fileset.
- Replace in a comma separated list.
The wrinkle here is that the implicit set of files in the replacement task will return to processing everything if the files have not changed. To overcome this, insert a dummy file name.
<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" />