Browse Source

file: Add CONFIGURE subcommand

Extend the `file()` command with a new `CONFIGURE` subcommand that
behaves the same as `string(CONFIGURE)` except that it writes the
resulting output immediately to a file.

Fixes: #20388
Leander Beernaert 5 years ago
parent
commit
a6fee09484
25 changed files with 253 additions and 0 deletions
  1. 40 0
      Help/command/file.rst
  2. 6 0
      Help/release/dev/file_configure.rst
  3. 118 0
      Source/cmFileCommand.cxx
  4. 1 0
      Tests/RunCMake/CMakeLists.txt
  5. 1 0
      Tests/RunCMake/File_Configure/BadArg-result.txt
  6. 4 0
      Tests/RunCMake/File_Configure/BadArg-stderr.txt
  7. 1 0
      Tests/RunCMake/File_Configure/BadArg.cmake
  8. 1 0
      Tests/RunCMake/File_Configure/BadArgGeneratorExpressionContent-result.txt
  9. 5 0
      Tests/RunCMake/File_Configure/BadArgGeneratorExpressionContent-stderr.txt
  10. 4 0
      Tests/RunCMake/File_Configure/BadArgGeneratorExpressionContent.cmake
  11. 1 0
      Tests/RunCMake/File_Configure/BadArgGeneratorExpressionOutput-result.txt
  12. 5 0
      Tests/RunCMake/File_Configure/BadArgGeneratorExpressionOutput-stderr.txt
  13. 4 0
      Tests/RunCMake/File_Configure/BadArgGeneratorExpressionOutput.cmake
  14. 3 0
      Tests/RunCMake/File_Configure/CMakeLists.txt
  15. 1 0
      Tests/RunCMake/File_Configure/DirOutput-stderr.txt
  16. 4 0
      Tests/RunCMake/File_Configure/DirOutput.cmake
  17. 1 0
      Tests/RunCMake/File_Configure/DirOutput.txt
  18. 1 0
      Tests/RunCMake/File_Configure/NewLineStyle-NoArg-result.txt
  19. 5 0
      Tests/RunCMake/File_Configure/NewLineStyle-NoArg-stderr.txt
  20. 6 0
      Tests/RunCMake/File_Configure/NewLineStyle-NoArg.cmake
  21. 20 0
      Tests/RunCMake/File_Configure/NewLineStyle-ValidArg.cmake
  22. 1 0
      Tests/RunCMake/File_Configure/NewLineStyle-WrongArg-result.txt
  23. 5 0
      Tests/RunCMake/File_Configure/NewLineStyle-WrongArg-stderr.txt
  24. 6 0
      Tests/RunCMake/File_Configure/NewLineStyle-WrongArg.cmake
  25. 9 0
      Tests/RunCMake/File_Configure/RunCMakeTest.cmake

+ 40 - 0
Help/command/file.rst

@@ -19,6 +19,7 @@ Synopsis
     file({`WRITE`_ | `APPEND`_} <filename> <content>...)
     file({`TOUCH`_ | `TOUCH_NOCREATE`_} [<file>...])
     file(`GENERATE`_ OUTPUT <output-file> [...])
+    file(`CONFIGURE`_ OUTPUT <output-file> CONTENT <content> [...])
 
   `Filesystem`_
     file({`GLOB`_ | `GLOB_RECURSE`_} <out-var> [...] [<globbing-expr>...])
@@ -484,6 +485,45 @@ generation phase. The output file will not yet have been written when the
 ``file(GENERATE)`` command returns, it is written only after processing all
 of a project's ``CMakeLists.txt`` files.
 
+.. _CONFIGURE:
+
+.. code-block:: cmake
+
+  file(CONFIGURE OUTPUT output-file
+       CONTENT content
+       [ESCAPE_QUOTES] [@ONLY]
+       [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
+
+Generate an output file using the input given by ``CONTENT`` and substitute
+variable values referenced as ``@VAR@`` or ``${VAR}`` contained therein. The
+substitution rules behave the same as the :command:`configure_file` command.
+In order to match :command:`configure_file`'s behavior, generator expressions
+are not supported for both ``OUTPUT`` and ``CONTENT``.
+
+The arguments are:
+
+``OUTPUT <output-file>``
+  Specify the output file name to generate. A relative path is treated with
+  respect to the value of :variable:`CMAKE_CURRENT_BINARY_DIR`. See policy
+  :policy:`CMP0070`.
+  ``<output-file>`` does not support generator expressions.
+
+``CONTENT <content>``
+  Use the content given explicitly as input.
+  ``<content>`` does not support generator expressions.
+
+``ESCAPE_QUOTES``
+  Escape any substituted quotes with backslashes (C-style).
+
+``@ONLY``
+  Restrict variable replacement to references of the form ``@VAR@``.
+  This is useful for configuring scripts that use ``${VAR}`` syntax.
+
+``NEWLINE_STYLE <style>``
+  Specify the newline style for the output file.  Specify
+  ``UNIX`` or ``LF`` for ``\n`` newlines, or specify
+  ``DOS``, ``WIN32``, or ``CRLF`` for ``\r\n`` newlines.
+
 Filesystem
 ^^^^^^^^^^
 

+ 6 - 0
Help/release/dev/file_configure.rst

@@ -0,0 +1,6 @@
+file_configure
+--------------
+
+* The :command:`file(CONFIGURE)` subcommand was created in order replicate the
+  :command:`configure_file` functionality without resorting to a pre-existing
+  file on disk as input. The content is instead passed as a string.

+ 118 - 0
Source/cmFileCommand.cxx

@@ -33,12 +33,14 @@
 #include "cmFileInstaller.h"
 #include "cmFileLockPool.h"
 #include "cmFileTimes.h"
+#include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGlobalGenerator.h"
 #include "cmHexFileConverter.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
+#include "cmNewLineStyle.h"
 #include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmRuntimeDependencyArchive.h"
@@ -2776,6 +2778,121 @@ bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
   return true;
 }
 
+bool HandleConfigureCommand(std::vector<std::string> const& args,
+                            cmExecutionStatus& status)
+{
+  if (args.size() < 5) {
+    status.SetError("Incorrect arguments to CONFIGURE subcommand.");
+    return false;
+  }
+  if (args[1] != "OUTPUT") {
+    status.SetError("Incorrect arguments to CONFIGURE subcommand.");
+    return false;
+  }
+  if (args[3] != "CONTENT") {
+    status.SetError("Incorrect arguments to CONFIGURE subcommand.");
+    return false;
+  }
+
+  std::string errorMessage;
+  cmNewLineStyle newLineStyle;
+  if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
+    status.SetError(cmStrCat("CONFIGURE ", errorMessage));
+    return false;
+  }
+
+  bool escapeQuotes = false;
+  bool atOnly = false;
+  for (unsigned int i = 5; i < args.size(); ++i) {
+    if (args[i] == "@ONLY") {
+      atOnly = true;
+    } else if (args[i] == "ESCAPE_QUOTES") {
+      escapeQuotes = true;
+    } else if (args[i] == "NEWLINE_STYLE" || args[i] == "LF" ||
+               args[i] == "UNIX" || args[i] == "CRLF" || args[i] == "WIN32" ||
+               args[i] == "DOS") {
+      /* Options handled by NewLineStyle member above.  */
+    } else {
+      status.SetError(
+        cmStrCat("CONFIGURE Unrecognized argument \"", args[i], "\""));
+      return false;
+    }
+  }
+
+  // Check for generator expressions
+  const std::string input = args[4];
+  std::string outputFile = args[2];
+
+  std::string::size_type pos = input.find_first_of("<>");
+  if (pos != std::string::npos) {
+    status.SetError(cmStrCat("CONFIGURE called with CONTENT containing a \"",
+                             input[pos],
+                             "\".  This character is not allowed."));
+    return false;
+  }
+
+  pos = outputFile.find_first_of("<>");
+  if (pos != std::string::npos) {
+    status.SetError(cmStrCat("CONFIGURE called with OUTPUT containing a \"",
+                             outputFile[pos],
+                             "\".  This character is not allowed."));
+    return false;
+  }
+
+  cmMakefile& makeFile = status.GetMakefile();
+  if (!makeFile.CanIWriteThisFile(outputFile)) {
+    cmSystemTools::Error("Attempt to write file: " + outputFile +
+                         " into a source directory.");
+    return false;
+  }
+
+  cmSystemTools::ConvertToUnixSlashes(outputFile);
+
+  // Re-generate if non-temporary outputs are missing.
+  // when we finalize the configuration we will remove all
+  // output files that now don't exist.
+  makeFile.AddCMakeOutputFile(outputFile);
+
+  // Create output directory
+  const std::string::size_type slashPos = outputFile.rfind('/');
+  if (slashPos != std::string::npos) {
+    const std::string path = outputFile.substr(0, slashPos);
+    cmSystemTools::MakeDirectory(path);
+  }
+
+  std::string newLineCharacters;
+  bool open_with_binary_flag = false;
+  if (newLineStyle.IsValid()) {
+    open_with_binary_flag = true;
+    newLineCharacters = newLineStyle.GetCharacters();
+  }
+
+  cmGeneratedFileStream fout;
+  fout.Open(outputFile, false, open_with_binary_flag);
+  if (!fout) {
+    cmSystemTools::Error("Could not open file for write in copy operation " +
+                         outputFile);
+    cmSystemTools::ReportLastSystemError("");
+    return false;
+  }
+  fout.SetCopyIfDifferent(true);
+
+  // copy intput to output and expand variables from input at the same time
+  std::stringstream sin(input, std::ios::in);
+  std::string inLine;
+  std::string outLine;
+  while (cmSystemTools::GetLineFromStream(sin, inLine)) {
+    outLine.clear();
+    makeFile.ConfigureString(inLine, outLine, atOnly, escapeQuotes);
+    fout << outLine << newLineCharacters;
+  }
+
+  // close file before attempting to copy
+  fout.close();
+
+  return true;
+}
+
 } // namespace
 
 bool cmFileCommand(std::vector<std::string> const& args,
@@ -2829,6 +2946,7 @@ bool cmFileCommand(std::vector<std::string> const& args,
     { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
     { "CREATE_LINK"_s, HandleCreateLinkCommand },
     { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
+    { "CONFIGURE"_s, HandleConfigureCommand },
   };
 
   return subcommand(args[0], args, status);

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -448,6 +448,7 @@ if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang"
   add_RunCMake_test(Framework)
 endif()
 
+add_RunCMake_test(File_Configure)
 add_RunCMake_test(File_Generate)
 add_RunCMake_test(ExportWithoutLanguage)
 add_RunCMake_test(target_link_directories)

+ 1 - 0
Tests/RunCMake/File_Configure/BadArg-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/File_Configure/BadArg-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at BadArg.cmake:[0-9]+ \(file\):
+  file Incorrect arguments to CONFIGURE subcommand.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 1 - 0
Tests/RunCMake/File_Configure/BadArg.cmake

@@ -0,0 +1 @@
+file(CONFIGURE OUTPUT)

+ 1 - 0
Tests/RunCMake/File_Configure/BadArgGeneratorExpressionContent-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/File_Configure/BadArgGeneratorExpressionContent-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at BadArgGeneratorExpressionContent.cmake:[0-9]+ \(file\):
+  file CONFIGURE called with CONTENT containing a "<".  This character is not
+  allowed.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/File_Configure/BadArgGeneratorExpressionContent.cmake

@@ -0,0 +1,4 @@
+file(CONFIGURE
+    OUTPUT "file.txt"
+    CONTENT "foo-$<CONFIG>"
+)

+ 1 - 0
Tests/RunCMake/File_Configure/BadArgGeneratorExpressionOutput-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/File_Configure/BadArgGeneratorExpressionOutput-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at BadArgGeneratorExpressionOutput.cmake:[0-9]+ \(file\):
+  file CONFIGURE called with OUTPUT containing a "<".  This character is not
+  allowed.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 4 - 0
Tests/RunCMake/File_Configure/BadArgGeneratorExpressionOutput.cmake

@@ -0,0 +1,4 @@
+file(CONFIGURE
+    OUTPUT "file-$<CONFIG>.txt"
+    CONTENT "foo"
+)

+ 3 - 0
Tests/RunCMake/File_Configure/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.17)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 1 - 0
Tests/RunCMake/File_Configure/DirOutput-stderr.txt

@@ -0,0 +1 @@
+^DirOutput test file$

+ 4 - 0
Tests/RunCMake/File_Configure/DirOutput.cmake

@@ -0,0 +1,4 @@
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/DirOutput)
+configure_file(DirOutput.txt DirOutput)
+file(READ ${CMAKE_CURRENT_BINARY_DIR}/DirOutput/DirOutput.txt out)
+message("${out}")

+ 1 - 0
Tests/RunCMake/File_Configure/DirOutput.txt

@@ -0,0 +1 @@
+DirOutput test file

+ 1 - 0
Tests/RunCMake/File_Configure/NewLineStyle-NoArg-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/File_Configure/NewLineStyle-NoArg-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at NewLineStyle-NoArg.cmake:[0-9]+ \(file\):
+  file CONFIGURE NEWLINE_STYLE must set a style: LF, CRLF, UNIX, DOS, or
+  WIN32
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 6 - 0
Tests/RunCMake/File_Configure/NewLineStyle-NoArg.cmake

@@ -0,0 +1,6 @@
+set(file_name  ${CMAKE_CURRENT_BINARY_DIR}/NewLineStyle.txt)
+file(CONFIGURE
+    OUTPUT ${file_name}
+    CONTENT "Data\n"
+    NEWLINE_STYLE
+)

+ 20 - 0
Tests/RunCMake/File_Configure/NewLineStyle-ValidArg.cmake

@@ -0,0 +1,20 @@
+set(file_name  ${CMAKE_CURRENT_BINARY_DIR}/NewLineStyle.txt)
+
+function(test_eol style in out)
+    file(CONFIGURE
+        OUTPUT ${file_name}
+        CONTENT "@in@"
+        NEWLINE_STYLE ${style}
+    )
+    file(READ ${file_name} new HEX)
+    if(NOT "${new}" STREQUAL "${out}")
+        message(FATAL_ERROR "No ${style} line endings")
+    endif()
+endfunction()
+
+test_eol(DOS   "a" "610d0a")
+test_eol(WIN32 "b" "620d0a")
+test_eol(CRLF  "c" "630d0a")
+
+test_eol(UNIX  "d" "640a")
+test_eol(LF    "e" "650a")

+ 1 - 0
Tests/RunCMake/File_Configure/NewLineStyle-WrongArg-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/File_Configure/NewLineStyle-WrongArg-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at NewLineStyle-WrongArg.cmake:[0-9]+ \(file\):
+  file CONFIGURE NEWLINE_STYLE sets an unknown style, only LF, CRLF, UNIX,
+  DOS, and WIN32 are supported
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)

+ 6 - 0
Tests/RunCMake/File_Configure/NewLineStyle-WrongArg.cmake

@@ -0,0 +1,6 @@
+set(file_name  ${CMAKE_CURRENT_BINARY_DIR}/NewLineStyle.txt)
+file(CONFIGURE
+    OUTPUT ${file_name}
+    CONTENT "Data\n"
+    NEWLINE_STYLE FOO
+)

+ 9 - 0
Tests/RunCMake/File_Configure/RunCMakeTest.cmake

@@ -0,0 +1,9 @@
+include(RunCMake)
+
+run_cmake(BadArg)
+run_cmake(BadArgGeneratorExpressionContent)
+run_cmake(BadArgGeneratorExpressionOutput)
+run_cmake(DirOutput)
+run_cmake(NewLineStyle-NoArg)
+run_cmake(NewLineStyle-ValidArg)
+run_cmake(NewLineStyle-WrongArg)