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
This commit is contained in:
Leander Beernaert
2020-03-06 13:41:07 +01:00
committed by Brad King
parent 4a1baca6f7
commit a6fee09484
25 changed files with 253 additions and 0 deletions

View File

@@ -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
^^^^^^^^^^

View File

@@ -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.

View File

@@ -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);

View File

@@ -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)

View File

@@ -0,0 +1 @@
1

View File

@@ -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\)

View File

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

View File

@@ -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\)

View File

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

View File

@@ -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\)

View File

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

View File

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

View File

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

View File

@@ -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}")

View File

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

View File

@@ -0,0 +1 @@
1

View File

@@ -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\)

View File

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

View File

@@ -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")

View File

@@ -0,0 +1 @@
1

View File

@@ -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\)

View File

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

View File

@@ -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)