From 042f7965c3a5db7420363fdb76f9ebaa8e93efdc Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Tue, 15 Nov 2011 20:39:36 -0500
Subject: Add file(MD5) command to compute cryptographic hash

Provide a CMake-language binding to the md5sum function previously
available only by "cmake -E md5sum".
---
 Source/cmFileCommand.cxx                | 36 +++++++++++++++++++++++++++++++++
 Source/cmFileCommand.h                  |  4 ++++
 Tests/CMakeTests/CheckCMakeTest.cmake   |  2 +-
 Tests/CMakeTests/File-MD5-BadArg1.cmake |  1 +
 Tests/CMakeTests/File-MD5-BadArg2.cmake |  1 +
 Tests/CMakeTests/File-MD5-BadArg4.cmake |  1 +
 Tests/CMakeTests/File-MD5-NoFile.cmake  |  1 +
 Tests/CMakeTests/File-MD5-Works.cmake   |  2 ++
 Tests/CMakeTests/FileTest.cmake.in      | 15 ++++++++++++++
 9 files changed, 62 insertions(+), 1 deletion(-)
 create mode 100644 Tests/CMakeTests/File-MD5-BadArg1.cmake
 create mode 100644 Tests/CMakeTests/File-MD5-BadArg2.cmake
 create mode 100644 Tests/CMakeTests/File-MD5-BadArg4.cmake
 create mode 100644 Tests/CMakeTests/File-MD5-NoFile.cmake
 create mode 100644 Tests/CMakeTests/File-MD5-Works.cmake

diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index f933666..32454f5 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -13,6 +13,7 @@
 #include "cmake.h"
 #include "cmHexFileConverter.h"
 #include "cmFileTimeComparison.h"
+#include "cmCryptoHash.h"
 
 #if defined(CMAKE_BUILD_WITH_CMAKE)
 #include "cm_curl.h"
@@ -22,6 +23,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
+#include <cmsys/auto_ptr.hxx>
 #include <cmsys/Directory.hxx>
 #include <cmsys/Glob.hxx>
 #include <cmsys/RegularExpression.hxx>
@@ -83,6 +85,10 @@ bool cmFileCommand
     {
     return this->HandleReadCommand(args);
     }
+  else if ( subCommand == "MD5" )
+    {
+    return this->HandleHashCommand(args);
+    }
   else if ( subCommand == "STRINGS" )
     {
     return this->HandleStringsCommand(args);
@@ -339,6 +345,36 @@ bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args)
 }
 
 //----------------------------------------------------------------------------
+bool cmFileCommand::HandleHashCommand(std::vector<std::string> const& args)
+{
+  if(args.size() != 3)
+    {
+    cmOStringStream e;
+    e << args[0] << " requires a file name and output variable";
+    this->SetError(e.str().c_str());
+    return false;
+    }
+
+  cmsys::auto_ptr<cmCryptoHash> hash;
+  if(args[0] == "MD5")
+    { hash.reset(new cmCryptoHashMD5); }
+  if(hash.get())
+    {
+    std::string out = hash->HashFile(args[1].c_str());
+    if(!out.empty())
+      {
+      this->Makefile->AddDefinition(args[2].c_str(), out.c_str());
+      return true;
+      }
+    cmOStringStream e;
+    e << args[0] << " failed to read file \"" << args[1] << "\": "
+      << cmSystemTools::GetLastSystemError();
+    this->SetError(e.str().c_str());
+    }
+  return false;
+}
+
+//----------------------------------------------------------------------------
 bool cmFileCommand::HandleStringsCommand(std::vector<std::string> const& args)
 {
   if(args.size() < 3)
diff --git a/Source/cmFileCommand.h b/Source/cmFileCommand.h
index 162890a..dce6478 100644
--- a/Source/cmFileCommand.h
+++ b/Source/cmFileCommand.h
@@ -65,6 +65,7 @@ public:
       "  file(WRITE filename \"message to write\"... )\n"
       "  file(APPEND filename \"message to write\"... )\n"
       "  file(READ filename variable [LIMIT numBytes] [OFFSET offset] [HEX])\n"
+      "  file(MD5 filename variable)\n"
       "  file(STRINGS filename variable [LIMIT_COUNT num]\n"
       "       [LIMIT_INPUT numBytes] [LIMIT_OUTPUT numBytes]\n"
       "       [LENGTH_MINIMUM numBytes] [LENGTH_MAXIMUM numBytes]\n"
@@ -94,6 +95,8 @@ public:
       "variable. It will start at the given offset and read up to numBytes. "
       "If the argument HEX is given, the binary data will be converted to "
       "hexadecimal representation and this will be stored in the variable.\n"
+      "MD5 "
+      "will compute a cryptographic hash of the content of a file.\n"
       "STRINGS will parse a list of ASCII strings from a file and "
       "store it in a variable. Binary data in the file are ignored. Carriage "
       "return (CR) characters are ignored. It works also for Intel Hex and "
@@ -227,6 +230,7 @@ protected:
   bool HandleRemove(std::vector<std::string> const& args, bool recurse);
   bool HandleWriteCommand(std::vector<std::string> const& args, bool append);
   bool HandleReadCommand(std::vector<std::string> const& args);
+  bool HandleHashCommand(std::vector<std::string> const& args);
   bool HandleStringsCommand(std::vector<std::string> const& args);
   bool HandleGlobCommand(std::vector<std::string> const& args, bool recurse);
   bool HandleMakeDirectoryCommand(std::vector<std::string> const& args);
diff --git a/Tests/CMakeTests/CheckCMakeTest.cmake b/Tests/CMakeTests/CheckCMakeTest.cmake
index 2e4fedd..db92905 100644
--- a/Tests/CMakeTests/CheckCMakeTest.cmake
+++ b/Tests/CMakeTests/CheckCMakeTest.cmake
@@ -12,7 +12,7 @@ function(check_cmake_test prefix)
       )
     string(REGEX REPLACE "\n" "\n out> " out " out> ${stdout}")
     string(REGEX REPLACE "\n" "\n err> " err " err> ${stderr}")
-    if(NOT "${result}" STREQUAL ${${test}-RESULT})
+    if(NOT "${result}" STREQUAL "${${test}-RESULT}")
       message(FATAL_ERROR
         "Test ${test} result is [${result}], not [${${test}-RESULT}].\n"
         "Test ${test} output:\n"
diff --git a/Tests/CMakeTests/File-MD5-BadArg1.cmake b/Tests/CMakeTests/File-MD5-BadArg1.cmake
new file mode 100644
index 0000000..ac5f67a
--- /dev/null
+++ b/Tests/CMakeTests/File-MD5-BadArg1.cmake
@@ -0,0 +1 @@
+file(MD5)
diff --git a/Tests/CMakeTests/File-MD5-BadArg2.cmake b/Tests/CMakeTests/File-MD5-BadArg2.cmake
new file mode 100644
index 0000000..68a172f
--- /dev/null
+++ b/Tests/CMakeTests/File-MD5-BadArg2.cmake
@@ -0,0 +1 @@
+file(MD5 ${CMAKE_CURRENT_LIST_DIR}/File-Copy-NoDest.cmake)
diff --git a/Tests/CMakeTests/File-MD5-BadArg4.cmake b/Tests/CMakeTests/File-MD5-BadArg4.cmake
new file mode 100644
index 0000000..a11efcb
--- /dev/null
+++ b/Tests/CMakeTests/File-MD5-BadArg4.cmake
@@ -0,0 +1 @@
+file(MD5 ${CMAKE_CURRENT_LIST_DIR}/File-Copy-NoDest.cmake md5 extra_arg)
diff --git a/Tests/CMakeTests/File-MD5-NoFile.cmake b/Tests/CMakeTests/File-MD5-NoFile.cmake
new file mode 100644
index 0000000..1b91bc8
--- /dev/null
+++ b/Tests/CMakeTests/File-MD5-NoFile.cmake
@@ -0,0 +1 @@
+file(MD5 ${CMAKE_CURRENT_LIST_DIR}/DoesNotExist.cmake md5)
diff --git a/Tests/CMakeTests/File-MD5-Works.cmake b/Tests/CMakeTests/File-MD5-Works.cmake
new file mode 100644
index 0000000..2989e98
--- /dev/null
+++ b/Tests/CMakeTests/File-MD5-Works.cmake
@@ -0,0 +1,2 @@
+file(MD5 ${CMAKE_CURRENT_LIST_DIR}/File-Copy-NoDest.cmake md5)
+message("${md5}")
diff --git a/Tests/CMakeTests/FileTest.cmake.in b/Tests/CMakeTests/FileTest.cmake.in
index b6dcaa6..3aa88a7 100644
--- a/Tests/CMakeTests/FileTest.cmake.in
+++ b/Tests/CMakeTests/FileTest.cmake.in
@@ -12,6 +12,16 @@ set(Copy-NoDest-RESULT 1)
 set(Copy-NoDest-STDERR "given no DESTINATION")
 set(Copy-NoFile-RESULT 1)
 set(Copy-NoFile-STDERR "COPY cannot find.*/does_not_exist\\.txt")
+set(MD5-NoFile-RESULT 1)
+set(MD5-NoFile-STDERR "file MD5 failed to read file")
+set(MD5-BadArg1-RESULT 1)
+set(MD5-BadArg1-STDERR "file must be called with at least two arguments")
+set(MD5-BadArg2-RESULT 1)
+set(MD5-BadArg2-STDERR "file MD5 requires a file name and output variable")
+set(MD5-BadArg4-RESULT 1)
+set(MD5-BadArg4-STDERR "file MD5 requires a file name and output variable")
+set(MD5-Works-RESULT 0)
+set(MD5-Works-STDERR "a28a44fb5b58a6acf0cbec46557bc775")
 
 include("@CMAKE_CURRENT_SOURCE_DIR@/CheckCMakeTest.cmake")
 check_cmake_test(File
@@ -22,6 +32,11 @@ check_cmake_test(File
   Copy-LateArg
   Copy-NoDest
   Copy-NoFile
+  MD5-NoFile
+  MD5-BadArg1
+  MD5-BadArg2
+  MD5-BadArg4
+  MD5-Works
   )
 
 # Also execute each test listed in FileTestScript.cmake:
-- 
cgit v0.12