I've been pulling my hair out trying to figure out why this code is failing, when the echo debug output clearly shows that the checksums do, in fact, match:
<macrodef name="compare">
<attribute name="getChecksumFor"/>
<attribute name="getChecksumFrom"/>
<sequential>
<checksum file="@{getChecksumFor}" property="md5.for"/>
<loadfile srcfile="@{getChecksumFrom}" property="md5.from"/>
<fail message="WARNING: Checksums do not match:
installed FILE=@{getChecksumFor}
installed FILE=${md5.for}
required FILE=${md5.from}">
<condition>
<not>
<equals arg1="${md5.from}" arg2="${md5.for}"/>
</not>
</condition>
</fail>
</sequential>
</macrodef>
What's the deal ? Why is the comparison between arg1 & arg2 failing when they match ?
And what is the "standard way" in Apache Ant to do checksum comparisons ?
The problem here is that when you check the output file of the checksum task, you'll notice that there's a newline character at the end, which is why the comparison is always failing, even when the hashes are the same.
To fix this issue, you'll need to use a filterchain with <striplinebreaks/>
when you loadfile:
<macrodef name="compare">
<attribute name="getChecksumFor"/>
<attribute name="getChecksumFrom"/>
<sequential>
<checksum file="@{getChecksumFor}" property="md5.for"/>
<loadfile srcfile="@{getChecksumFrom}" property="md5.from">
<filterchain>
<striplinebreaks/>
</filterchain>
</loadfile>
<fail message="WARNING: Checksums do not match:
installed FILE=@{getChecksumFor}
installed FILE=${md5.for}
required FILE=${md5.from}">
<condition>
<not>
<equals arg1="${md5.from}" arg2="${md5.for}"/>
</not>
</condition>
</fail>
</sequential>
</macrodef>
The glaring issue with this approach is that this macrodef <compare>
can only be used once, because the properties md5.for
& md5.from
can only be set once. Run your file with the ant -v
option for verbose mode, and you'll see the warning:
Override ignored for property "md5.for"
One possible fix is to use ant-contrib to unset any properties right before you set them. Or, if you're using Ant version 1.8.0+, then you can define local properties.
Here's another approach:
<macrodef name="compare">
<attribute name="getChecksumFor"/>
<sequential>
<fail message="WARNING: Incorrect checksum for @{getChecksumFor}">
<condition>
<not>
<checksum>
<fileset file="@{getChecksumFor}"/>
</checksum>
</not>
</condition>
</fail>
</sequential>
</macrodef>
This will calculate the checksum for "file1.ext" (for example), and compare it against the corresponding "file1.ext.MD5" file: This works because the Checksum class implements the Condition interface, which will return a boolean value. Tested & working!!!
So, how do you perform the comparison if the file & MD5 file are stored in different directories ? I'm not sure what the correct "ant way" is, but a quick & dirty solution would be to copy them both to some temporary location, pass them into the macro, then delete them, like this:
<target name="init">
<mkdir dir="${temp}"/>
</target>
<target name="check1" depends="init">
<copy file="${dir1}/${file1.ext}" todir="${temp}" overwrite="true"/>
<copy file="${dir2}/${file1.ext}.MD5" todir="${temp}" overwrite="true"/>
<compare getChecksumFor="${temp}/${file1.ext}"/>
<delete file="${temp}/${file1.ext}"/>
<delete file="${temp}/${file1.ext}.MD5"/>
<delete dir="${temp}"/>
</target>
<target name="target1" depends="check1">
<!-- check1 passed: Safe to proceed -->
</target>
The problem here, is that I have designed this to fail fast, which means that if it does fail, the deletions on the temporary files will never be performed: But luckily for me, this ${temp}
directory is already part of my cleanup task.
<target name="clean">
<delete dir="${temp}"/>
</target>
Here's an alternate solution where the macro will assume the job of copying the files to the same directory:
<macrodef name="compare">
<attribute name="getChecksumFor"/>
<attribute name="getChecksumFrom"/>
<sequential>
<delete dir="${temp}/checksums"/>
<mkdir dir="${temp}/checksums"/>
<copy file="@{getChecksumFor}" todir="${temp}/checksums" overwrite="true"/>
<copy file="@{getChecksumFrom}" todir="${temp}/checksums" overwrite="true"/>
<fail message="WARNING: Incorrect checksum for @{getChecksumFor}">
<condition>
<not>
<checksum>
<fileset dir="${temp}/checksums">
<exclude name="*.MD5"/>
</fileset>
</checksum>
</not>
</condition>
</fail>
<delete dir="${temp}/checksums"/>
</sequential>
</macrodef>