diff options
18 files changed, 210 insertions, 13 deletions
diff --git a/Help/command/file.rst b/Help/command/file.rst index c64b13f4b7..3db605d6a8 100644 --- a/Help/command/file.rst +++ b/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 diff --git a/Help/release/dev/file-generate-new-line-style.rst b/Help/release/dev/file-generate-new-line-style.rst new file mode 100644 index 0000000000..b629b461db --- /dev/null +++ b/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. diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx index 9377baab5e..5eff78913d 100644 --- a/Source/cmFileCommand.cxx +++ b/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; } diff --git a/Source/cmGeneratorExpressionEvaluationFile.cxx b/Source/cmGeneratorExpressionEvaluationFile.cxx index ec44df3041..9fae15ae8e 100644 --- a/Source/cmGeneratorExpressionEvaluationFile.cxx +++ b/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); } diff --git a/Source/cmGeneratorExpressionEvaluationFile.h b/Source/cmGeneratorExpressionEvaluationFile.h index 9a870ac0ae..0eb78acdc3 100644 --- a/Source/cmGeneratorExpressionEvaluationFile.h +++ b/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; }; diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index f479af1054..e4859864c6 100644 --- a/Source/cmMakefile.cxx +++ b/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))); } diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 60b66a2fad..6341abcb86 100644 --- a/Source/cmMakefile.h +++ b/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; diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-Default.cmake b/Tests/RunCMake/File_Generate/NewLineStyle-Default.cmake new file mode 100644 index 0000000000..9df8ffe3a9 --- /dev/null +++ b/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") diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg-result.txt b/Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg-stderr.txt b/Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg-stderr.txt new file mode 100644 index 0000000000..44e32d0280 --- /dev/null +++ b/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\) diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg.cmake b/Tests/RunCMake/File_Generate/NewLineStyle-InvalidArg.cmake new file mode 100644 index 0000000000..578cf21629 --- /dev/null +++ b/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 + ) diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-result.txt b/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt b/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt new file mode 100644 index 0000000000..bc71f2f599 --- /dev/null +++ b/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\) diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-NoArg.cmake b/Tests/RunCMake/File_Generate/NewLineStyle-NoArg.cmake new file mode 100644 index 0000000000..9bd2ffa44e --- /dev/null +++ b/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 + ) diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-Unix.cmake b/Tests/RunCMake/File_Generate/NewLineStyle-Unix.cmake new file mode 100644 index 0000000000..7c26217ca1 --- /dev/null +++ b/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") diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-Win32.cmake b/Tests/RunCMake/File_Generate/NewLineStyle-Win32.cmake new file mode 100644 index 0000000000..394ef7585a --- /dev/null +++ b/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") diff --git a/Tests/RunCMake/File_Generate/RunCMakeTest.cmake b/Tests/RunCMake/File_Generate/RunCMakeTest.cmake index 51491af5f0..be3bf04dd9 100644 --- a/Tests/RunCMake/File_Generate/RunCMakeTest.cmake +++ b/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) diff --git a/Tests/RunCMake/File_Generate/VerifyContent.cmake b/Tests/RunCMake/File_Generate/VerifyContent.cmake new file mode 100644 index 0000000000..8563708fcd --- /dev/null +++ b/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() |