Intro
In a build process, you have a properties file, the former one, and another one, the latter file. Now you want to replace all properties in the former that exists in the latter. A kind of SED for property files.
This can be done easily in any programming language, but Property files in Java follow a spec and we rather not deal with the complexities. Ant has the PropertyFile Task for this.
Apache Ant provides an optional task for editing property files. This is very useful when wanting to make unattended modifications to configuration files for application servers and applications.
Need to iterate
The problem is that the PropertyFile task requires the explicit listing of each key value to edit (the task can do more then just replace). AFAIK there is no way to use a resource collection. Simple example:
Listing 1 – example of PropertyFile use
<propertyfile file="old.properties"> <entry key="tag.name" value="release_1"/> </propertyfile>
We instead require the creation of the elements while we iterate thru the new file.
Propertiesreplace scriptdef
Groovy is a perfect fit for this. The Builder DSL allows a more ‘natural’ approach. I thought it would be a challenge to code this, but in a few minutes, it was done!
The trick is to create a new AntBuilder in the script. However, this may be a bad or non-idiomatic approach. Perhaps a Groovy expert can comment on it?
Listing 2 – the scriptdef
<!-- Replace properties in oldfile whose key match those in newfile --> <scriptdef name="propertiesreplace" language="groovy" classpathref="libs" uri="http://com.jbetancourt1.ant"> <attribute name="oldfile"/> <attribute name="newfile"/> <![CDATA[ def ant = new AntBuilder(project) ant.propertyfile( file:attributes.get("oldfile") ){ def nps = new Properties() nps.load( new File( attributes.get("newfile") ) .newReader() ) nps.each{ cur -> entry( key:cur.key,value:cur.value ) } } ]]> </scriptdef>
I should have named it replaceproperties. Note that it would be better to put the Groovy script external, such as:
<!-- Replace properties in oldfile whose key match those in newfile --> <scriptdef name="propertiesreplace" language="groovy" classpathref="libs" uri="http://com.jbetancourt1.ant" src="srcReplaceProperties.groovy"> the rest of the declarations from listing 2 ... </scriptdef>
Listing 3 – the old properties file
one=1111 #four=4444 two=2222 five=5555 three=3333
Listing 4 – the new properties file
one=abcd two=efgh three=ijkl four=mnop
Listing 5 – run example Ant script
C:temp\antpropMerge>ant Buildfile: C:temp\antpropMerge\build.xml init: [copy] Copying 1 file to C:temp\antpropMerge merge: [propertyfile] Updating property file: C:\temp\antpropMerge\oldProps.properties list: [exec] one=abcd [exec] #four=4444 [exec] two=efgh [exec] five=5555 [exec] three=ijkl [exec] [exec] four=mnop all: BUILD SUCCESSFUL Total time: 1 second
We actually save these files as templates, so that as shown in the init task of listing 6, we can recreate the test files. Then in target ‘list’, we print the modified old properties file.
Listing 6 – Ant script for dev of solution
<project name="merge" default="all" basedir="."> <!-- File: build.xml, author: J.Betancourt, Date:2011-08-17T20:59-0500 --> <path id="libs"> <fileset dir="lib"> <include name="*.jar" /> </fileset> </path> <!-- Groovy library --> <typedef resource="org/codehaus/groovy/antlib.xml" classpathref="libs" /> <!-- Replace properties in oldfile whose key match those in newfile --> <scriptdef name="propertiesreplace" language="groovy" classpathref="libs" uri="http://com.jbetancourt1.ant"> <attribute name="oldfile"/> <attribute name="newfile"/> <![CDATA[ def ant = new AntBuilder(project) ant.propertyfile( file:attributes.get("oldfile") ){ def nps = new Properties() nps.load(new File( attributes.get("newfile")).newReader()) nps.each{ cur -> entry( key:cur.key,value:cur.value ) } } ]]> </scriptdef> <!-- use the "propertiesreplace" scriptdef --> <target name="merge" depends="init" xmlns:s="http://com.jbetancourt1.ant"> <s:propertiesreplace oldfile="oldProps.properties" newfile="newProps.properties"/> </target> <target name="all" depends="init,merge,list"/> <target name="init"> <copy file="oldProps.properties-template" tofile="oldProps.properties"/> </target> <target name="list"> <exec executable="cmd.exe" > <arg line="/c type oldProps.properties"/> </exec> </target> </project>
Why version control?
I code this up, then I decided to share it for anyone who may appreciate some examples (and for my own dev notes). I edit a file. It stops working. Can’t figure out why quickly enough. My editor’s (notepad++) undo is not helping.
Fixed it, but to avoid this scenario again I do:
git init
git commit -a -m “initial commit”
Careful
Did you see the future human operator error waiting to happen? In the example, the modified properties file has two entries:
#four=4444
four=mnop
Lets say this file is, for example, involved in code controlling a 800 ton crane, and an admin removes comment mark from the first “four”. If the file is processed with Ant, the first ‘four’ entry wins. Control code is deployed and now crane is waiting to drop its crate at bad moment. A Road Runner coyote scenario. Beep beep.
Updates
2011-0819: Got stumped regarding exception invoking cvs task in Ant and losing all existing properties at the BuildFinished handler. Did a search and found that someone already created a merge properties task:
. Merging property files…
. A version with fixes
2012-04-02: It may be possible that the Groovy scriptdef taskdef already includes an AntBuilder.
Further Reading
- Groovy
- Using Ant from Groovy
- Scriptdef
- PropertyFile Task
- Ant Groovy Task
- Ouroboros
- SED
One thought on “Groovy AntBuilder in Ant Scriptdef to Replace Props”