Search code examples
antconditional-statementschecksum

Ant checksum comparison?


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 ?


Solution

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