Browse Source

Merge topic 'issue-19198'

255df8622b file(GENERATE): Support new line style

Acked-by: Kitware Robot <[email protected]>
Acked-by: Brad King <[email protected]>
Merge-request: !5649
Brad King 4 years ago
parent
commit
8387aa20f2

+ 9 - 1
Help/command/file.rst

@@ -481,7 +481,8 @@ modified.
        <INPUT input-file|CONTENT content>
        [CONDITION expression] [TARGET target]
        [FILE_PERMISSIONS <permissions>...]
-       [NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS])
+       [NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS]
+       [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
 
 Generate an output file for each build configuration supported by the current
 :manual:`CMake Generator <cmake-generators(7)>`.  Evaluate
@@ -533,6 +534,13 @@ from the input content to produce the output content.  The options are:
   Transfer the file permissions of the original file to the generated file.
   This option expects INPUT option.
 
+``NEWLINE_STYLE <style>``
+  .. versionadded:: 3.20
+
+  Specify the newline style for the generated file.  Specify
+  ``UNIX`` or ``LF`` for ``\n`` newlines, or specify
+  ``DOS``, ``WIN32``, or ``CRLF`` for ``\r\n`` newlines.
+
 Exactly one ``CONTENT`` or ``INPUT`` option must be given.  A specific
 ``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
 Generated files are modified and their timestamp updated on subsequent cmake

+ 5 - 0
Help/release/dev/file-generate-new-line-style.rst

@@ -0,0 +1,5 @@
+file-generate-new-line-style
+----------------------------
+
+* The :command:`file(GENERATE)` command gained ``NEWLINE_STYLE`` option to
+  support newline style of the generated file.

+ 20 - 4
Source/cmFileCommand.cxx

@@ -2290,7 +2290,8 @@ void AddEvaluationFile(const std::string& inputName,
                        const std::string& targetName,
                        const std::string& outputExpr,
                        const std::string& condition, bool inputIsContent,
-                       mode_t permissions, cmExecutionStatus& status)
+                       const std::string& newLineCharacter, mode_t permissions,
+                       cmExecutionStatus& status)
 {
   cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
 
@@ -2304,7 +2305,7 @@ void AddEvaluationFile(const std::string& inputName,
 
   status.GetMakefile().AddEvaluationFile(
     inputName, targetName, std::move(outputCge), std::move(conditionCge),
-    permissions, inputIsContent);
+    newLineCharacter, permissions, inputIsContent);
 }
 
 bool HandleGenerateCommand(std::vector<std::string> const& args,
@@ -2322,6 +2323,7 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
     std::string Content;
     std::string Condition;
     std::string Target;
+    std::string NewLineStyle;
     bool NoSourcePermissions = false;
     bool UseSourcePermissions = false;
     std::vector<std::string> FilePermissions;
@@ -2336,7 +2338,8 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
       .Bind("TARGET"_s, &Arguments::Target)
       .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
       .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
-      .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions);
+      .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
+      .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle);
 
   std::vector<std::string> unparsedArguments;
   std::vector<std::string> keywordsMissingValues;
@@ -2400,6 +2403,18 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
     status.SetError("Unknown argument to GENERATE subcommand.");
   }
 
+  const bool newLineStyleSpecified =
+    std::find(parsedKeywords.begin(), parsedKeywords.end(),
+              "NEWLINE_STYLE"_s) != parsedKeywords.end();
+  cmNewLineStyle newLineStyle;
+  if (newLineStyleSpecified) {
+    std::string errorMessage;
+    if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
+      status.SetError(cmStrCat("GENERATE ", errorMessage));
+      return false;
+    }
+  }
+
   std::string input = arguments.Input;
   if (inputIsContent) {
     input = arguments.Content;
@@ -2463,7 +2478,8 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
   }
 
   AddEvaluationFile(input, arguments.Target, arguments.Output,
-                    arguments.Condition, inputIsContent, permisiions, status);
+                    arguments.Condition, inputIsContent,
+                    newLineStyle.GetCharacters(), permisiions, status);
   return true;
 }
 

+ 28 - 3
Source/cmGeneratorExpressionEvaluationFile.cxx

@@ -21,13 +21,14 @@ cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
   std::string input, std::string target,
   std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
   std::unique_ptr<cmCompiledGeneratorExpression> condition,
-  bool inputIsContent, mode_t permissions,
+  bool inputIsContent, std::string newLineCharacter, mode_t permissions,
   cmPolicies::PolicyStatus policyStatusCMP0070)
   : Input(std::move(input))
   , Target(std::move(target))
   , OutputFileExpr(std::move(outputFileExpr))
   , Condition(std::move(condition))
   , InputIsContent(inputIsContent)
+  , NewLineCharacter(std::move(newLineCharacter))
   , PolicyStatusCMP0070(policyStatusCMP0070)
   , Permissions(permissions)
 {
@@ -82,9 +83,33 @@ void cmGeneratorExpressionEvaluationFile::Generate(
   this->Files.push_back(outputFileName);
   outputFiles[outputFileName] = outputContent;
 
-  cmGeneratedFileStream fout(outputFileName);
+  bool openWithBinaryFlag = false;
+  if (!this->NewLineCharacter.empty()) {
+    openWithBinaryFlag = true;
+  }
+  cmGeneratedFileStream fout;
+  fout.Open(outputFileName, false, openWithBinaryFlag);
+  if (!fout) {
+    lg->IssueMessage(MessageType::FATAL_ERROR,
+                     "Could not open file for write in copy operation " +
+                       outputFileName);
+    return;
+  }
   fout.SetCopyIfDifferent(true);
-  fout << outputContent;
+  std::istringstream iss(outputContent);
+  std::string line;
+  bool hasNewLine = false;
+  while (cmSystemTools::GetLineFromStream(iss, line, &hasNewLine)) {
+    fout << line;
+    if (!this->NewLineCharacter.empty()) {
+      fout << this->NewLineCharacter;
+    } else if (hasNewLine) {
+      // if new line character is not specified, the file will be opened in
+      // text mode. So, "\n" will be translated to the correct newline
+      // ending based on the platform.
+      fout << "\n";
+    }
+  }
   if (fout.Close() && perm) {
     cmSystemTools::SetPermissions(outputFileName.c_str(), perm);
   }

+ 2 - 1
Source/cmGeneratorExpressionEvaluationFile.h

@@ -24,7 +24,7 @@ public:
     std::string input, std::string target,
     std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
     std::unique_ptr<cmCompiledGeneratorExpression> condition,
-    bool inputIsContent, mode_t permissions,
+    bool inputIsContent, std::string newLineCharacter, mode_t permissions,
     cmPolicies::PolicyStatus policyStatusCMP0070);
 
   void Generate(cmLocalGenerator* lg);
@@ -58,6 +58,7 @@ private:
   const std::unique_ptr<cmCompiledGeneratorExpression> Condition;
   std::vector<std::string> Files;
   const bool InputIsContent;
+  const std::string NewLineCharacter;
   cmPolicies::PolicyStatus PolicyStatusCMP0070;
   mode_t Permissions;
 };

+ 3 - 3
Source/cmMakefile.cxx

@@ -865,13 +865,13 @@ void cmMakefile::EnforceDirectoryLevelRules() const
 void cmMakefile::AddEvaluationFile(
   const std::string& inputFile, const std::string& targetName,
   std::unique_ptr<cmCompiledGeneratorExpression> outputName,
-  std::unique_ptr<cmCompiledGeneratorExpression> condition, mode_t permissions,
-  bool inputIsContent)
+  std::unique_ptr<cmCompiledGeneratorExpression> condition,
+  const std::string& newLineCharacter, mode_t permissions, bool inputIsContent)
 {
   this->EvaluationFiles.push_back(
     cm::make_unique<cmGeneratorExpressionEvaluationFile>(
       inputFile, targetName, std::move(outputName), std::move(condition),
-      inputIsContent, permissions,
+      inputIsContent, newLineCharacter, permissions,
       this->GetPolicyStatus(cmPolicies::CMP0070)));
 }
 

+ 2 - 1
Source/cmMakefile.h

@@ -899,7 +899,8 @@ public:
     const std::string& inputFile, const std::string& targetName,
     std::unique_ptr<cmCompiledGeneratorExpression> outputName,
     std::unique_ptr<cmCompiledGeneratorExpression> condition,
-    mode_t permissions, bool inputIsContent);
+    const std::string& newLineCharacter, mode_t permissions,
+    bool inputIsContent);
   const std::vector<std::unique_ptr<cmGeneratorExpressionEvaluationFile>>&
   GetEvaluationFiles() const;
 

+ 35 - 0
Tests/RunCMake/File_Generate/NewLineStyle-Default.cmake

@@ -0,0 +1,35 @@
+function(generate_from_file in out)
+  file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/file_ip.txt "${in}")
+  file(GENERATE
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/file_op.txt
+    INPUT ${CMAKE_CURRENT_BINARY_DIR}/file_ip.txt
+    )
+
+  add_custom_target(verifyContentFromFile ALL
+    COMMAND ${CMAKE_COMMAND}
+      -DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/file_op.txt
+      -DexpectedContent=${out}
+      -P "${CMAKE_CURRENT_SOURCE_DIR}/VerifyContent.cmake"
+    )
+endfunction()
+
+function(generate_from_content in out)
+  file(GENERATE
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/content_op.txt
+    CONTENT ${in}
+    )
+
+  add_custom_target(verifyContentFromContent ALL
+    COMMAND ${CMAKE_COMMAND}
+      -DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/content_op.txt
+      -DexpectedContent=${out}
+      -P "${CMAKE_CURRENT_SOURCE_DIR}/VerifyContent.cmake"
+    )
+endfunction()
+
+if (WIN32)
+  generate_from_file("a" "610d0a") # 62->b, 0d0a->\r\n
+elseif(UNIX)
+  generate_from_file("a" "610a") # 62->b, 0a->\n
+endif()
+generate_from_content("a" "61")

+ 1 - 0
Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg-result.txt

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

+ 5 - 0
Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg-stderr.txt

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

+ 7 - 0
Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg.cmake

@@ -0,0 +1,7 @@
+file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/main.cpp")
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/main.cpp"
+  CONTENT "int main() { return 0; }\n"
+  NEWLINE_STYLE FOO
+  )

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

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

+ 4 - 0
Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt

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

+ 7 - 0
Tests/RunCMake/File_Generate/NewLineStyle-NoArg.cmake

@@ -0,0 +1,7 @@
+file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/main.cpp")
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/main.cpp"
+  CONTENT "int main() { return 0; }\n"
+  NEWLINE_STYLE
+  )

+ 33 - 0
Tests/RunCMake/File_Generate/NewLineStyle-Unix.cmake

@@ -0,0 +1,33 @@
+function(generate_from_file in out)
+  file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/file_ip.txt "${in}")
+  file(GENERATE
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/file_op.txt
+    INPUT ${CMAKE_CURRENT_BINARY_DIR}/file_ip.txt
+    NEWLINE_STYLE UNIX
+    )
+
+  add_custom_target(verifyContentFromFile ALL
+    COMMAND ${CMAKE_COMMAND}
+      -DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/file_op.txt
+      -DexpectedContent=${out}
+      -P "${CMAKE_CURRENT_SOURCE_DIR}/VerifyContent.cmake"
+    )
+endfunction()
+
+function(generate_from_content in out)
+  file(GENERATE
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/content_op.txt
+    CONTENT ${in}
+    NEWLINE_STYLE UNIX
+    )
+
+  add_custom_target(verifyContentFromContent ALL
+    COMMAND ${CMAKE_COMMAND}
+      -DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/content_op.txt
+      -DexpectedContent=${out}
+      -P "${CMAKE_CURRENT_SOURCE_DIR}/VerifyContent.cmake"
+    )
+endfunction()
+
+generate_from_file("a" "610a") # 62->b, 0a->\n
+generate_from_content("a" "610a")

+ 33 - 0
Tests/RunCMake/File_Generate/NewLineStyle-Win32.cmake

@@ -0,0 +1,33 @@
+function(generate_from_file in out)
+  file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/file_ip.txt "${in}")
+  file(GENERATE
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/file_op.txt
+    INPUT ${CMAKE_CURRENT_BINARY_DIR}/file_ip.txt
+    NEWLINE_STYLE WIN32
+    )
+
+  add_custom_target(verifyContentFromFile ALL
+    COMMAND ${CMAKE_COMMAND}
+      -DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/file_op.txt
+      -DexpectedContent=${out}
+      -P "${CMAKE_CURRENT_SOURCE_DIR}/VerifyContent.cmake"
+    )
+endfunction()
+
+function(generate_from_content in out)
+  file(GENERATE
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/content_op.txt
+    CONTENT ${in}
+    NEWLINE_STYLE WIN32
+    )
+
+  add_custom_target(verifyContentFromContent ALL
+    COMMAND ${CMAKE_COMMAND}
+      -DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/content_op.txt
+      -DexpectedContent=${out}
+      -P "${CMAKE_CURRENT_SOURCE_DIR}/VerifyContent.cmake"
+    )
+endfunction()
+
+generate_from_file("a" "610d0a") # 62->b, 0d0a->\r\n
+generate_from_content("a" "610d0a")

+ 11 - 0
Tests/RunCMake/File_Generate/RunCMakeTest.cmake

@@ -135,6 +135,11 @@ function(run_cmake_and_verify_after_build case)
   file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
   file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
   set(RunCMake_TEST_NO_CLEAN 1)
+  if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(RunCMake_TEST_OPTIONS -DCMAKE_CONFIGURATION_TYPES=Debug)
+  else()
+    set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
+  endif()
   run_cmake(${case})
   run_cmake_command("${case}-build" ${CMAKE_COMMAND} --build .)
   unset(RunCMake_TEST_NO_CLEAN)
@@ -144,3 +149,9 @@ endfunction()
 run_cmake_and_verify_after_build(NoSourcePermissions)
 run_cmake_and_verify_after_build(UseSourcePermissions)
 run_cmake_and_verify_after_build(CustomFilePermissions)
+
+run_cmake(NewLineStyle-NoArg)
+run_cmake(NewLineStyle-InvalidArg)
+run_cmake_and_verify_after_build(NewLineStyle-Default)
+run_cmake_and_verify_after_build(NewLineStyle-Unix)
+run_cmake_and_verify_after_build(NewLineStyle-Win32)

+ 4 - 0
Tests/RunCMake/File_Generate/VerifyContent.cmake

@@ -0,0 +1,4 @@
+file(READ ${generatedFile} actualContent HEX)
+if(NOT "${actualContent}" STREQUAL "${expectedContent}")
+    message(SEND_ERROR "Content mismatch actual: \"${actualContent}\" expected: \"${expectedContent}\"")
+endif()