Browse Source

file(GENERATE): Support new line style

Fixes: #19198
Asit Dhal 5 years ago
parent
commit
255df8622b

+ 9 - 1
Help/command/file.rst

@@ -481,7 +481,8 @@ modified.
        <INPUT input-file|CONTENT content>
        <INPUT input-file|CONTENT content>
        [CONDITION expression] [TARGET target]
        [CONDITION expression] [TARGET target]
        [FILE_PERMISSIONS <permissions>...]
        [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
 Generate an output file for each build configuration supported by the current
 :manual:`CMake Generator <cmake-generators(7)>`.  Evaluate
 :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.
   Transfer the file permissions of the original file to the generated file.
   This option expects INPUT option.
   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
 Exactly one ``CONTENT`` or ``INPUT`` option must be given.  A specific
 ``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
 ``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
 Generated files are modified and their timestamp updated on subsequent cmake
 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& targetName,
                        const std::string& outputExpr,
                        const std::string& outputExpr,
                        const std::string& condition, bool inputIsContent,
                        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();
   cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
 
 
@@ -2304,7 +2305,7 @@ void AddEvaluationFile(const std::string& inputName,
 
 
   status.GetMakefile().AddEvaluationFile(
   status.GetMakefile().AddEvaluationFile(
     inputName, targetName, std::move(outputCge), std::move(conditionCge),
     inputName, targetName, std::move(outputCge), std::move(conditionCge),
-    permissions, inputIsContent);
+    newLineCharacter, permissions, inputIsContent);
 }
 }
 
 
 bool HandleGenerateCommand(std::vector<std::string> const& args,
 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 Content;
     std::string Condition;
     std::string Condition;
     std::string Target;
     std::string Target;
+    std::string NewLineStyle;
     bool NoSourcePermissions = false;
     bool NoSourcePermissions = false;
     bool UseSourcePermissions = false;
     bool UseSourcePermissions = false;
     std::vector<std::string> FilePermissions;
     std::vector<std::string> FilePermissions;
@@ -2336,7 +2338,8 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
       .Bind("TARGET"_s, &Arguments::Target)
       .Bind("TARGET"_s, &Arguments::Target)
       .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
       .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
       .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
       .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> unparsedArguments;
   std::vector<std::string> keywordsMissingValues;
   std::vector<std::string> keywordsMissingValues;
@@ -2400,6 +2403,18 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
     status.SetError("Unknown argument to GENERATE subcommand.");
     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;
   std::string input = arguments.Input;
   if (inputIsContent) {
   if (inputIsContent) {
     input = arguments.Content;
     input = arguments.Content;
@@ -2463,7 +2478,8 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
   }
   }
 
 
   AddEvaluationFile(input, arguments.Target, arguments.Output,
   AddEvaluationFile(input, arguments.Target, arguments.Output,
-                    arguments.Condition, inputIsContent, permisiions, status);
+                    arguments.Condition, inputIsContent,
+                    newLineStyle.GetCharacters(), permisiions, status);
   return true;
   return true;
 }
 }
 
 

+ 28 - 3
Source/cmGeneratorExpressionEvaluationFile.cxx

@@ -21,13 +21,14 @@ cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
   std::string input, std::string target,
   std::string input, std::string target,
   std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
   std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
   std::unique_ptr<cmCompiledGeneratorExpression> condition,
   std::unique_ptr<cmCompiledGeneratorExpression> condition,
-  bool inputIsContent, mode_t permissions,
+  bool inputIsContent, std::string newLineCharacter, mode_t permissions,
   cmPolicies::PolicyStatus policyStatusCMP0070)
   cmPolicies::PolicyStatus policyStatusCMP0070)
   : Input(std::move(input))
   : Input(std::move(input))
   , Target(std::move(target))
   , Target(std::move(target))
   , OutputFileExpr(std::move(outputFileExpr))
   , OutputFileExpr(std::move(outputFileExpr))
   , Condition(std::move(condition))
   , Condition(std::move(condition))
   , InputIsContent(inputIsContent)
   , InputIsContent(inputIsContent)
+  , NewLineCharacter(std::move(newLineCharacter))
   , PolicyStatusCMP0070(policyStatusCMP0070)
   , PolicyStatusCMP0070(policyStatusCMP0070)
   , Permissions(permissions)
   , Permissions(permissions)
 {
 {
@@ -82,9 +83,33 @@ void cmGeneratorExpressionEvaluationFile::Generate(
   this->Files.push_back(outputFileName);
   this->Files.push_back(outputFileName);
   outputFiles[outputFileName] = outputContent;
   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.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) {
   if (fout.Close() && perm) {
     cmSystemTools::SetPermissions(outputFileName.c_str(), perm);
     cmSystemTools::SetPermissions(outputFileName.c_str(), perm);
   }
   }

+ 2 - 1
Source/cmGeneratorExpressionEvaluationFile.h

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

+ 3 - 3
Source/cmMakefile.cxx

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

+ 2 - 1
Source/cmMakefile.h

@@ -899,7 +899,8 @@ public:
     const std::string& inputFile, const std::string& targetName,
     const std::string& inputFile, const std::string& targetName,
     std::unique_ptr<cmCompiledGeneratorExpression> outputName,
     std::unique_ptr<cmCompiledGeneratorExpression> outputName,
     std::unique_ptr<cmCompiledGeneratorExpression> condition,
     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>>&
   const std::vector<std::unique_ptr<cmGeneratorExpressionEvaluationFile>>&
   GetEvaluationFiles() const;
   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(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
   file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
   file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
   set(RunCMake_TEST_NO_CLEAN 1)
   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(${case})
   run_cmake_command("${case}-build" ${CMAKE_COMMAND} --build .)
   run_cmake_command("${case}-build" ${CMAKE_COMMAND} --build .)
   unset(RunCMake_TEST_NO_CLEAN)
   unset(RunCMake_TEST_NO_CLEAN)
@@ -144,3 +149,9 @@ endfunction()
 run_cmake_and_verify_after_build(NoSourcePermissions)
 run_cmake_and_verify_after_build(NoSourcePermissions)
 run_cmake_and_verify_after_build(UseSourcePermissions)
 run_cmake_and_verify_after_build(UseSourcePermissions)
 run_cmake_and_verify_after_build(CustomFilePermissions)
 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()