From 3a60c899fce77f221cba670b62c90fe2d2daffed Mon Sep 17 00:00:00 2001
From: Brad King <brad.king@kitware.com>
Date: Wed, 14 Jan 2015 13:22:29 -0500
Subject: cmake: Teach "-E tar" command a "--mtime=" option

Add an option to set the mtime of entries in a tarball so that one can
create a tarball with a consistent content hash (e.g. MD5) for a given
set of files regardless of their current timestamps on disk.  This will
be useful for submission of tarballs to CDash, which tracks content
hashes to avoid duplication.

Inspired-by: Bill Hoffman <bill.hoffman@kitware.com>
---
 Help/release/dev/cmake-E-tar-mtime.rst             |  6 +++++
 Source/CMakeLists.txt                              |  2 ++
 Source/cmArchiveWrite.cxx                          | 15 ++++++++++++
 Source/cmArchiveWrite.h                            |  2 ++
 Source/cmSystemTools.cxx                           |  3 ++-
 Source/cmSystemTools.h                             |  6 ++---
 Source/cm_get_date.c                               | 16 +++++++++++++
 Source/cm_get_date.h                               | 28 ++++++++++++++++++++++
 Source/cmcmd.cxx                                   | 26 ++++++++++++++++++--
 .../CommandLine/E_tar-bad-mtime1-result.txt        |  1 +
 .../CommandLine/E_tar-bad-mtime1-stderr.txt        |  2 ++
 .../RunCMake/CommandLine/E_tar-bad-opt1-result.txt |  1 +
 .../RunCMake/CommandLine/E_tar-bad-opt1-stderr.txt |  1 +
 .../RunCMake/CommandLine/E_tar-end-opt1-result.txt |  1 +
 .../RunCMake/CommandLine/E_tar-end-opt1-stderr.txt |  2 ++
 Tests/RunCMake/CommandLine/RunCMakeTest.cmake      |  6 +++++
 16 files changed, 112 insertions(+), 6 deletions(-)
 create mode 100644 Help/release/dev/cmake-E-tar-mtime.rst
 create mode 100644 Source/cm_get_date.c
 create mode 100644 Source/cm_get_date.h
 create mode 100644 Tests/RunCMake/CommandLine/E_tar-bad-mtime1-result.txt
 create mode 100644 Tests/RunCMake/CommandLine/E_tar-bad-mtime1-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/E_tar-bad-opt1-result.txt
 create mode 100644 Tests/RunCMake/CommandLine/E_tar-bad-opt1-stderr.txt
 create mode 100644 Tests/RunCMake/CommandLine/E_tar-end-opt1-result.txt
 create mode 100644 Tests/RunCMake/CommandLine/E_tar-end-opt1-stderr.txt

diff --git a/Help/release/dev/cmake-E-tar-mtime.rst b/Help/release/dev/cmake-E-tar-mtime.rst
new file mode 100644
index 0000000..6496577
--- /dev/null
+++ b/Help/release/dev/cmake-E-tar-mtime.rst
@@ -0,0 +1,6 @@
+cmake-E-tar-mtime
+-----------------
+
+* The :manual:`cmake(1)` ``-E tar`` command learned a new
+  ``--mtime=<date>`` option to specify the modification time
+  recorded in tarball entries.
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 96f4709..caabd43 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -335,6 +335,8 @@ set(SRCS
   cmake.cxx
   cmake.h
 
+  cm_get_date.h
+  cm_get_date.c
   cm_sha2.h
   cm_sha2.c
   cm_utf8.h
diff --git a/Source/cmArchiveWrite.cxx b/Source/cmArchiveWrite.cxx
index 16f67f4..c24c68e 100644
--- a/Source/cmArchiveWrite.cxx
+++ b/Source/cmArchiveWrite.cxx
@@ -16,6 +16,7 @@
 #include <cmsys/Directory.hxx>
 #include <cmsys/FStream.hxx>
 #include <cm_libarchive.h>
+#include "cm_get_date.h"
 
 //----------------------------------------------------------------------------
 static std::string cm_archive_error_string(struct archive* a)
@@ -277,6 +278,20 @@ bool cmArchiveWrite::AddFile(const char* file,
     this->Error += cm_archive_error_string(this->Disk);
     return false;
     }
+  if (!this->MTime.empty())
+    {
+    time_t now;
+    time(&now);
+    time_t t = cm_get_date(now, this->MTime.c_str());
+    if (t == -1)
+      {
+      this->Error = "unable to parse mtime '";
+      this->Error += this->MTime;
+      this->Error += "'";
+      return false;
+      }
+    archive_entry_set_mtime(e, t, 0);
+    }
   // Clear acl and xattr fields not useful for distribution.
   archive_entry_acl_clear(e);
   archive_entry_xattr_clear(e);
diff --git a/Source/cmArchiveWrite.h b/Source/cmArchiveWrite.h
index a6dcc0e..17357b4 100644
--- a/Source/cmArchiveWrite.h
+++ b/Source/cmArchiveWrite.h
@@ -74,6 +74,7 @@ public:
   // std::cout.
   void SetVerbose(bool v) { this->Verbose = v; }
 
+  void SetMTime(std::string const& t) { this->MTime = t; }
 private:
   bool Okay() const { return this->Error.empty(); }
   bool AddPath(const char* path, size_t skip, const char* prefix);
@@ -90,6 +91,7 @@ private:
   struct archive* Disk;
   bool Verbose;
   std::string Error;
+  std::string MTime;
 };
 
 #endif
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 7d938c5..e9735ed 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -1469,7 +1469,7 @@ bool cmSystemTools::IsPathToFramework(const char* path)
 bool cmSystemTools::CreateTar(const char* outFileName,
                               const std::vector<std::string>& files,
                               cmTarCompression compressType,
-                              bool verbose)
+                              bool verbose, std::string const& mtime)
 {
 #if defined(CMAKE_BUILD_WITH_CMAKE)
   std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
@@ -1501,6 +1501,7 @@ bool cmSystemTools::CreateTar(const char* outFileName,
     }
   cmArchiveWrite a(fout, compress,
                    cmArchiveWrite::TypeTAR);
+  a.SetMTime(mtime);
   a.SetVerbose(verbose);
   for(std::vector<std::string>::const_iterator i = files.begin();
       i != files.end(); ++i)
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index 09ceea6..361f42e 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -394,9 +394,9 @@ public:
                       bool verbose);
   static bool CreateTar(const char* outFileName,
                         const std::vector<std::string>& files,
-                        cmTarCompression compressType, bool verbose);
-  static bool ExtractTar(const char* inFileName,
-                         bool verbose);
+                        cmTarCompression compressType, bool verbose,
+                        std::string const& mtime = std::string());
+  static bool ExtractTar(const char* inFileName, bool verbose);
   // This should be called first thing in main
   // it will keep child processes from inheriting the
   // stdin and stdout of this process.  This is important
diff --git a/Source/cm_get_date.c b/Source/cm_get_date.c
new file mode 100644
index 0000000..2e0d4bd
--- /dev/null
+++ b/Source/cm_get_date.c
@@ -0,0 +1,16 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2015 Kitware, Inc.
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#include "cm_get_date.h"
+
+#define __archive_get_date cm_get_date
+
+#include "../Utilities/cmlibarchive/libarchive/archive_getdate.c"
diff --git a/Source/cm_get_date.h b/Source/cm_get_date.h
new file mode 100644
index 0000000..d5f6d3e
--- /dev/null
+++ b/Source/cm_get_date.h
@@ -0,0 +1,28 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2015 Kitware, Inc.
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#ifndef cm_get_date_h
+#define cm_get_date_h
+
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Parse a date/time string.  Treat relative times with respect to 'now'. */
+time_t cm_get_date(time_t now, const char *str);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index ecf4650..7ca3eb3 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -729,9 +729,31 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
       std::string flags = args[2];
       std::string outFile = args[3];
       std::vector<std::string> files;
+      std::string mtime;
+      bool doing_options = true;
       for (std::string::size_type cc = 4; cc < args.size(); cc ++)
         {
-        files.push_back(args[cc]);
+        std::string const& arg = args[cc];
+        if (doing_options && cmHasLiteralPrefix(arg, "--"))
+          {
+          if (arg == "--")
+            {
+            doing_options = false;
+            }
+          else if (cmHasLiteralPrefix(arg, "--mtime="))
+            {
+            mtime = arg.substr(8);
+            }
+          else
+            {
+            cmSystemTools::Error("Unknown option to -E tar: ", arg.c_str());
+            return 1;
+            }
+          }
+        else
+          {
+          files.push_back(arg);
+          }
         }
       cmSystemTools::cmTarCompression compress =
         cmSystemTools::TarCompressNone;
@@ -774,7 +796,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
       else if ( flags.find_first_of('c') != flags.npos )
         {
         if ( !cmSystemTools::CreateTar(
-               outFile.c_str(), files, compress, verbose) )
+               outFile.c_str(), files, compress, verbose, mtime) )
           {
           cmSystemTools::Error("Problem creating tar: ", outFile.c_str());
           return 1;
diff --git a/Tests/RunCMake/CommandLine/E_tar-bad-mtime1-result.txt b/Tests/RunCMake/CommandLine/E_tar-bad-mtime1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_tar-bad-mtime1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/E_tar-bad-mtime1-stderr.txt b/Tests/RunCMake/CommandLine/E_tar-bad-mtime1-stderr.txt
new file mode 100644
index 0000000..ca925f1
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_tar-bad-mtime1-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: unable to parse mtime 'bad'
+CMake Error: Problem creating tar: bad.tar$
diff --git a/Tests/RunCMake/CommandLine/E_tar-bad-opt1-result.txt b/Tests/RunCMake/CommandLine/E_tar-bad-opt1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_tar-bad-opt1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/E_tar-bad-opt1-stderr.txt b/Tests/RunCMake/CommandLine/E_tar-bad-opt1-stderr.txt
new file mode 100644
index 0000000..35133c8
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_tar-bad-opt1-stderr.txt
@@ -0,0 +1 @@
+^CMake Error: Unknown option to -E tar: --bad$
diff --git a/Tests/RunCMake/CommandLine/E_tar-end-opt1-result.txt b/Tests/RunCMake/CommandLine/E_tar-end-opt1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_tar-end-opt1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/E_tar-end-opt1-stderr.txt b/Tests/RunCMake/CommandLine/E_tar-end-opt1-stderr.txt
new file mode 100644
index 0000000..1fddf6d
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_tar-end-opt1-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: archive_read_disk_entry_from_file '--bad':.*
+CMake Error: Problem creating tar: bad.tar$
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 0c43c20..2994f16 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -1,5 +1,11 @@
 include(RunCMake)
 
+run_cmake_command(E_tar-bad-opt1   ${CMAKE_COMMAND} -E tar cvf bad.tar --bad)
+run_cmake_command(E_tar-bad-mtime1 ${CMAKE_COMMAND} -E tar cvf bad.tar --mtime=bad .)
+run_cmake_command(E_tar-end-opt1   ${CMAKE_COMMAND} -E tar cvf bad.tar -- --bad)
+run_cmake_command(E_tar-end-opt2   ${CMAKE_COMMAND} -E tar cvf bad.tar --)
+run_cmake_command(E_tar-mtime      ${CMAKE_COMMAND} -E tar cvf bad.tar "--mtime=1970-01-01 00:00:00 UTC")
+
 run_cmake_command(build-no-cache
   ${CMAKE_COMMAND} --build ${RunCMake_SOURCE_DIR})
 run_cmake_command(build-no-generator
-- 
cgit v0.12