From 1c63aa4d43f101a0d58b5b88d3119c72cadf5517 Mon Sep 17 00:00:00 2001
From: Petr Orlov <zfmeze@gmail.com>
Date: Wed, 31 Aug 2016 18:05:15 +0300
Subject: CPack: Add option to generate a checksum file next to each package
 file

Add variable CPACK_PACKAGE_CHECKSUM to activate it.
---
 Help/release/dev/cpack.hash_computing.rst          |  5 ++++
 Modules/CPack.cmake                                |  9 ++++++
 Source/CPack/cmCPackGenerator.cxx                  | 33 +++++++++++++++++++++-
 Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake        |  4 +++
 Tests/RunCMake/CPack/RunCMakeTest.cmake            |  1 +
 .../CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake |  9 ++++++
 .../CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake  | 14 +++++++++
 .../CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt  |  2 ++
 8 files changed, 76 insertions(+), 1 deletion(-)
 create mode 100644 Help/release/dev/cpack.hash_computing.rst
 create mode 100644 Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake
 create mode 100644 Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake
 create mode 100644 Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake
 create mode 100644 Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt

diff --git a/Help/release/dev/cpack.hash_computing.rst b/Help/release/dev/cpack.hash_computing.rst
new file mode 100644
index 0000000..9780bb2
--- /dev/null
+++ b/Help/release/dev/cpack.hash_computing.rst
@@ -0,0 +1,5 @@
+cpack.hash_computing
+--------------------
+
+* CPack gained a new :variable:`CPACK_PACKAGE_CHECKSUM` variable to
+  enable generation of a checksum file for each package file.
diff --git a/Modules/CPack.cmake b/Modules/CPack.cmake
index 675b38b..70ae7f7 100644
--- a/Modules/CPack.cmake
+++ b/Modules/CPack.cmake
@@ -116,6 +116,15 @@
 #  A branding image that will be displayed inside the installer (used by GUI
 #  installers).
 #
+# .. variable:: CPACK_PACKAGE_CHECKSUM
+#
+#  An algorithm that will be used to generate additional file with checksum
+#  of the package. Output file name will be::
+#
+#     ${CPACK_PACKAGE_FILE_NAME}.${CPACK_PACKAGE_CHECKSUM}
+#
+#  Current supported alogorithms: MD5|SHA1|SHA224|SHA256|SHA384|SHA512.
+#
 # .. variable:: CPACK_PROJECT_CONFIG_FILE
 #
 #  CPack-time project CPack configuration file. This file included at cpack
diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx
index d6b58f2..e6aba89 100644
--- a/Source/CPack/cmCPackGenerator.cxx
+++ b/Source/CPack/cmCPackGenerator.cxx
@@ -14,6 +14,7 @@
 
 #include "cmCPackComponentGroup.h"
 #include "cmCPackLog.h"
+#include "cmCryptoHash.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
 #include "cmMakefile.h"
@@ -163,6 +164,14 @@ int cmCPackGenerator::PrepareNames()
         << std::endl);
     return 0;
   }
+  const char* algoSignature = this->GetOption("CPACK_PACKAGE_CHECKSUM");
+  if (algoSignature) {
+    if (cmCryptoHash::New(algoSignature).get() == CM_NULLPTR) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot recognize algorithm: "
+                      << algoSignature << std::endl);
+      return 0;
+    }
+  }
 
   this->SetOptionIfNotSet("CPACK_REMOVE_TOPLEVEL_DIRECTORY", "1");
 
@@ -980,6 +989,10 @@ int cmCPackGenerator::DoPackage()
     return 0;
   }
 
+  /* Prepare checksum algorithm*/
+  const char* algo = this->GetOption("CPACK_PACKAGE_CHECKSUM");
+  CM_AUTO_PTR<cmCryptoHash> crypto = cmCryptoHash::New(algo ? algo : "");
+
   /*
    * Copy the generated packages to final destination
    *  - there may be several of them
@@ -992,8 +1005,9 @@ int cmCPackGenerator::DoPackage()
   /* now copy package one by one */
   for (it = packageFileNames.begin(); it != packageFileNames.end(); ++it) {
     std::string tmpPF(this->GetOption("CPACK_OUTPUT_FILE_PREFIX"));
+    std::string filename(cmSystemTools::GetFilenameName(*it));
     tempPackageFileName = it->c_str();
-    tmpPF += "/" + cmSystemTools::GetFilenameName(*it);
+    tmpPF += "/" + filename;
     const char* packageFileName = tmpPF.c_str();
     cmCPackLogger(cmCPackLog::LOG_DEBUG, "Copy final package(s): "
                     << (tempPackageFileName ? tempPackageFileName : "(NULL)")
@@ -1009,6 +1023,23 @@ int cmCPackGenerator::DoPackage()
     }
     cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- package: "
                     << packageFileName << " generated." << std::endl);
+
+    /* Generate checksum file */
+    if (crypto.get() != CM_NULLPTR) {
+      std::string hashFile(this->GetOption("CPACK_OUTPUT_FILE_PREFIX"));
+      hashFile +=
+        "/" + filename.substr(0, filename.rfind(this->GetOutputExtension()));
+      hashFile += "." + cmSystemTools::LowerCase(algo);
+      cmsys::ofstream outF(hashFile.c_str());
+      if (!outF) {
+        cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot create checksum file: "
+                        << hashFile << std::endl);
+        return 0;
+      }
+      outF << crypto->HashFile(packageFileName) << "  " << filename << "\n";
+      cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- checksum file: "
+                      << hashFile << " generated." << std::endl);
+    }
   }
 
   return 1;
diff --git a/Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake b/Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake
new file mode 100644
index 0000000..5ca288c
--- /dev/null
+++ b/Tests/RunCMake/CPack/PACKAGE_CHECKSUM.cmake
@@ -0,0 +1,4 @@
+install(FILES CMakeLists.txt DESTINATION foo)
+
+set(CPACK_PACKAGE_NAME "package_checksum")
+set(CPACK_PACKAGE_CHECKSUM ${RunCMake_SUBTEST_SUFFIX})
diff --git a/Tests/RunCMake/CPack/RunCMakeTest.cmake b/Tests/RunCMake/CPack/RunCMakeTest.cmake
index a3029cf..abad58b 100644
--- a/Tests/RunCMake/CPack/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CPack/RunCMakeTest.cmake
@@ -18,3 +18,4 @@ run_cpack_test(DEB_GENERATE_SHLIBS "DEB" true)
 run_cpack_test(DEB_GENERATE_SHLIBS_LDCONFIG "DEB" true)
 run_cpack_test(DEBUGINFO "RPM" true)
 run_cpack_test(LONG_FILENAMES "DEB" false)
+run_cpack_test_subtests(PACKAGE_CHECKSUM "invalid;MD5;SHA1;SHA224;SHA256;SHA384;SHA512" "TGZ" false)
diff --git a/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake
new file mode 100644
index 0000000..205dcd8
--- /dev/null
+++ b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-ExpectedFiles.cmake
@@ -0,0 +1,9 @@
+set(whitespaces_ "[\t\n\r ]*")
+
+set(EXPECTED_FILES_COUNT "0")
+
+if (NOT ${RunCMake_SUBTEST_SUFFIX} MATCHES "invalid")
+  set(EXPECTED_FILES_COUNT "1")
+  set(EXPECTED_FILE_1 "package_checksum*.tar.gz")
+  set(EXPECTED_FILE_CONTENT_1 "^[^\n]*package_checksum*-[^\n]*/foo/\n[^\n]*package_checksum-[^\n]*/foo/CMakeLists.txt$")
+endif()
diff --git a/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake
new file mode 100644
index 0000000..e9e65d6
--- /dev/null
+++ b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-VerifyResult.cmake
@@ -0,0 +1,14 @@
+if(NOT ${RunCMake_SUBTEST_SUFFIX} MATCHES "invalid")
+  string(TOLOWER ${RunCMake_SUBTEST_SUFFIX} EXTENSION)
+  file(GLOB PACKAGE RELATIVE ${bin_dir} "*.tar.gz")
+  file(GLOB CSUMFILE RELATIVE ${bin_dir} "*.${EXTENSION}")
+  file(STRINGS ${CSUMFILE} CHSUM_VALUE)
+  file(${RunCMake_SUBTEST_SUFFIX} ${PACKAGE} expected_value )
+  set(expected_value "${expected_value}  ${PACKAGE}")
+
+  if(NOT expected_value STREQUAL CHSUM_VALUE)
+    message(FATAL_ERROR "Generated checksum is not valid! Expected [${expected_value}] Got [${CHSUM_VALUE}]")
+  endif()
+else()
+  message(${error})
+endif()
diff --git a/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt
new file mode 100644
index 0000000..abf6d8c
--- /dev/null
+++ b/Tests/RunCMake/CPack/TGZ/PACKAGE_CHECKSUM-invalid-stderr.txt
@@ -0,0 +1,2 @@
+^CPack Error: Cannot recognize algorithm: invalid
+CPack Error: Error when generating package: package_checksum$
-- 
cgit v0.12