From 12b471e828e4462cce4354c87bb4fe18aeb600e3 Mon Sep 17 00:00:00 2001
From: Wouter Klouwen <wouter.klouwen@youview.com>
Date: Tue, 20 Nov 2018 10:17:33 +0000
Subject: file: add SIZE option

This commit adds the SIZE option to file(). It returns the file size of
the given path if it exists and produces an error if not.
---
 Help/command/file.rst                              | 11 ++++++++
 Help/release/dev/file-size.rst                     |  5 ++++
 Source/cmFileCommand.cxx                           | 32 ++++++++++++++++++++++
 Source/cmFileCommand.h                             |  1 +
 Tests/RunCMake/file/RunCMakeTest.cmake             |  2 ++
 .../file/SIZE-error-does-not-exist-result.txt      |  1 +
 .../file/SIZE-error-does-not-exist-stderr.txt      |  5 ++++
 .../RunCMake/file/SIZE-error-does-not-exist.cmake  |  3 ++
 Tests/RunCMake/file/SIZE.cmake                     |  9 ++++++
 9 files changed, 69 insertions(+)
 create mode 100644 Help/release/dev/file-size.rst
 create mode 100644 Tests/RunCMake/file/SIZE-error-does-not-exist-result.txt
 create mode 100644 Tests/RunCMake/file/SIZE-error-does-not-exist-stderr.txt
 create mode 100644 Tests/RunCMake/file/SIZE-error-does-not-exist.cmake
 create mode 100644 Tests/RunCMake/file/SIZE.cmake

diff --git a/Help/command/file.rst b/Help/command/file.rst
index f5279c0..26a9ae2 100644
--- a/Help/command/file.rst
+++ b/Help/command/file.rst
@@ -25,6 +25,7 @@ Synopsis
     file({`REMOVE`_ | `REMOVE_RECURSE`_ } [<files>...])
     file(`MAKE_DIRECTORY`_ [<dir>...])
     file({`COPY`_ | `INSTALL`_} <file>... DESTINATION <dir> [...])
+    file(`SIZE`_ <filename> <out-var>)
 
   `Path Conversion`_
     file(`RELATIVE_PATH`_ <out-var> <directory> <file>)
@@ -333,6 +334,16 @@ and ``NO_SOURCE_PERMISSIONS`` is default.
 Installation scripts generated by the :command:`install` command
 use this signature (with some undocumented options for internal use).
 
+.. _SIZE:
+
+.. code-block:: cmake
+
+  file(SIZE <filename> <variable>)
+
+Determine the file size of the ``<filename>`` and put the result in
+``<variable>`` variable. Requires that ``<filename>`` is a valid path
+pointing to a file and is readable.
+
 Path Conversion
 ^^^^^^^^^^^^^^^
 
diff --git a/Help/release/dev/file-size.rst b/Help/release/dev/file-size.rst
new file mode 100644
index 0000000..4f0e196
--- /dev/null
+++ b/Help/release/dev/file-size.rst
@@ -0,0 +1,5 @@
+file-size
+---------
+
+* The :command:`file` command gained a ``SIZE`` mode to get the size
+  of a file on disk.
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index f86e5e2..34c88ab 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -176,6 +176,9 @@ bool cmFileCommand::InitialPass(std::vector<std::string> const& args,
   if (subCommand == "LOCK") {
     return this->HandleLockCommand(args);
   }
+  if (subCommand == "SIZE") {
+    return this->HandleSizeCommand(args);
+  }
 
   std::string e = "does not recognize sub-command " + subCommand;
   this->SetError(e);
@@ -3605,3 +3608,32 @@ bool cmFileCommand::HandleTimestampCommand(
 
   return true;
 }
+
+bool cmFileCommand::HandleSizeCommand(std::vector<std::string> const& args)
+{
+  if (args.size() != 3) {
+    std::ostringstream e;
+    e << args[0] << " requires a file name and output variable";
+    this->SetError(e.str());
+    return false;
+  }
+
+  unsigned int argsIndex = 1;
+
+  const std::string& filename = args[argsIndex++];
+
+  const std::string& outputVariable = args[argsIndex++];
+
+  if (!cmSystemTools::FileExists(filename, true)) {
+    std::ostringstream e;
+    e << "SIZE requested of path that is not readable " << filename;
+    this->SetError(e.str());
+    return false;
+  }
+
+  this->Makefile->AddDefinition(
+    outputVariable,
+    std::to_string(cmSystemTools::FileLength(filename)).c_str());
+
+  return true;
+}
diff --git a/Source/cmFileCommand.h b/Source/cmFileCommand.h
index 719dca2..01e007d 100644
--- a/Source/cmFileCommand.h
+++ b/Source/cmFileCommand.h
@@ -59,6 +59,7 @@ protected:
   bool HandleTimestampCommand(std::vector<std::string> const& args);
   bool HandleGenerateCommand(std::vector<std::string> const& args);
   bool HandleLockCommand(std::vector<std::string> const& args);
+  bool HandleSizeCommand(std::vector<std::string> const& args);
 
 private:
   void AddEvaluationFile(const std::string& inputName,
diff --git a/Tests/RunCMake/file/RunCMakeTest.cmake b/Tests/RunCMake/file/RunCMakeTest.cmake
index b383230..b9d76bf 100644
--- a/Tests/RunCMake/file/RunCMakeTest.cmake
+++ b/Tests/RunCMake/file/RunCMakeTest.cmake
@@ -36,6 +36,8 @@ run_cmake(READ_ELF)
 run_cmake(GLOB)
 run_cmake(GLOB_RECURSE)
 run_cmake(GLOB_RECURSE-noexp-FOLLOW_SYMLINKS)
+run_cmake(SIZE)
+run_cmake(SIZE-error-does-not-exist)
 
 # tests are valid both for GLOB and GLOB_RECURSE
 run_cmake(GLOB-sort-dedup)
diff --git a/Tests/RunCMake/file/SIZE-error-does-not-exist-result.txt b/Tests/RunCMake/file/SIZE-error-does-not-exist-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/file/SIZE-error-does-not-exist-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/file/SIZE-error-does-not-exist-stderr.txt b/Tests/RunCMake/file/SIZE-error-does-not-exist-stderr.txt
new file mode 100644
index 0000000..b797a41
--- /dev/null
+++ b/Tests/RunCMake/file/SIZE-error-does-not-exist-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Error at SIZE-error-does-not-exist.cmake:[0-9]+ \(file\):
+  file SIZE requested of path that is not readable
+  /a/file/that/does-not-exist
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file/SIZE-error-does-not-exist.cmake b/Tests/RunCMake/file/SIZE-error-does-not-exist.cmake
new file mode 100644
index 0000000..edcc222
--- /dev/null
+++ b/Tests/RunCMake/file/SIZE-error-does-not-exist.cmake
@@ -0,0 +1,3 @@
+set(file "/a/file/that/does-not-exist")
+
+file(SIZE "${file}" CALCULATED_SIZE)
diff --git a/Tests/RunCMake/file/SIZE.cmake b/Tests/RunCMake/file/SIZE.cmake
new file mode 100644
index 0000000..4d9dbd2
--- /dev/null
+++ b/Tests/RunCMake/file/SIZE.cmake
@@ -0,0 +1,9 @@
+set(file "${CMAKE_CURRENT_BINARY_DIR}/a-test-file")
+
+file(WRITE "${file}" "test")
+
+file(SIZE "${file}" CALCULATED_SIZE)
+
+if (NOT CALCULATED_SIZE EQUAL 4)
+  message(FATAL_ERROR "Unexpected file size")
+endif()
-- 
cgit v0.12