diff --git a/Source/CTest/cmCTestLaunch.cxx b/Source/CTest/cmCTestLaunch.cxx index b5265dc06e..a4c4dae716 100644 --- a/Source/CTest/cmCTestLaunch.cxx +++ b/Source/CTest/cmCTestLaunch.cxx @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,7 @@ bool cmCTestLaunch::ParseArguments(int argc, char const* const* argv) { DoingNone, DoingOutput, + DoingOutputAsFileName, DoingSource, DoingLanguage, DoingTargetLabels, @@ -87,6 +89,8 @@ bool cmCTestLaunch::ParseArguments(int argc, char const* const* argv) doing = DoingCommandType; } else if (strcmp(arg, "--output") == 0) { doing = DoingOutput; + } else if (strcmp(arg, "--output-as-file-name") == 0) { + doing = DoingOutputAsFileName; } else if (strcmp(arg, "--source") == 0) { doing = DoingSource; } else if (strcmp(arg, "--language") == 0) { @@ -112,6 +116,19 @@ bool cmCTestLaunch::ParseArguments(int argc, char const* const* argv) } else if (doing == DoingOutput) { this->Reporter.OptionOutput = arg; doing = DoingNone; + } else if (doing == DoingOutputAsFileName) { + // Read all data from the file name + cmsys::ifstream file(arg, std::ios::in); + if (!file) { + std::cerr << "failed to open file '" << arg << "' for reading (" + << cmSystemTools::GetLastSystemError() << "):\n"; + return false; + } + + this->Reporter.OptionOutput = + std::string(std::istreambuf_iterator(file), + std::istreambuf_iterator()); + doing = DoingNone; } else if (doing == DoingSource) { this->Reporter.OptionSource = arg; doing = DoingNone; diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index 364f854d85..6bb4b72388 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -11,6 +11,8 @@ #include #include +#include "cmsys/FStream.hxx" + #include "cmCryptoHash.h" #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" @@ -520,3 +522,24 @@ cmCustomCommandGenerator::GetUtilities() const { return this->Utilities; } + +std::string cmCustomCommandGenerator::StoreContentToFile( + std::string const& content) const +{ + cmCryptoHash hash(cmCryptoHash::AlgoSHA256); + + std::string fileDir = + cmStrCat(this->LG->GetBinaryDirectory(), "/CMakeFiles/c"); + if (!cmSystemTools::MakeDirectory(fileDir)) { + return ""; + } + + std::string fileName = cmStrCat(fileDir, "/", hash.HashString(content)); + + cmsys::ofstream file(fileName.c_str(), std::ios::out); + if (!file) { + return ""; + } + file.write(content.data(), content.size()); + return fileName; +} diff --git a/Source/cmCustomCommandGenerator.h b/Source/cmCustomCommandGenerator.h index 1bf12e95fe..b4ca4886a9 100644 --- a/Source/cmCustomCommandGenerator.h +++ b/Source/cmCustomCommandGenerator.h @@ -72,4 +72,6 @@ public: std::string const& GetOutputConfig() const { return this->OutputConfig; } std::string const& GetCommandConfig() const { return this->CommandConfig; } + + std::string StoreContentToFile(std::string const& content) const; }; diff --git a/Source/cmFastbuildTargetGenerator.cxx b/Source/cmFastbuildTargetGenerator.cxx index fc5a850e22..b4c8081c0e 100644 --- a/Source/cmFastbuildTargetGenerator.cxx +++ b/Source/cmFastbuildTargetGenerator.cxx @@ -716,6 +716,10 @@ std::string cmFastbuildTargetGenerator::MakeCustomLauncher( } } vars.Output = output.c_str(); + + std::string filePathWithOutput = ccg.StoreContentToFile(output); + vars.FilePathWithOutput = filePathWithOutput.c_str(); + vars.Role = ccg.GetCC().GetRole().c_str(); vars.CMTargetName = ccg.GetCC().GetTarget().c_str(); vars.Config = ccg.GetOutputConfig().c_str(); diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx index 102ed39930..f482005345 100644 --- a/Source/cmLocalNinjaGenerator.cxx +++ b/Source/cmLocalNinjaGenerator.cxx @@ -913,6 +913,10 @@ std::string cmLocalNinjaGenerator::MakeCustomLauncher( } } vars.Output = output.c_str(); + + std::string filePathWithOutput = ccg.StoreContentToFile(output); + vars.FilePathWithOutput = filePathWithOutput.c_str(); + vars.Role = ccg.GetCC().GetRole().c_str(); vars.CMTargetName = ccg.GetCC().GetTarget().c_str(); vars.Config = ccg.GetOutputConfig().c_str(); diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx index 6363d0bc40..bf91696b24 100644 --- a/Source/cmLocalUnixMakefileGenerator3.cxx +++ b/Source/cmLocalUnixMakefileGenerator3.cxx @@ -1071,6 +1071,10 @@ void cmLocalUnixMakefileGenerator3::AppendCustomCommand( } } vars.Output = output.c_str(); + + std::string filePathWithOutput = ccg.StoreContentToFile(output); + vars.FilePathWithOutput = filePathWithOutput.c_str(); + vars.Role = ccg.GetCC().GetRole().c_str(); vars.CMTargetName = ccg.GetCC().GetTarget().c_str(); vars.Config = ccg.GetOutputConfig().c_str(); diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx index 7f9f0c3aac..70a736b4cb 100644 --- a/Source/cmRulePlaceholderExpander.cxx +++ b/Source/cmRulePlaceholderExpander.cxx @@ -304,6 +304,11 @@ std::string cmRulePlaceholderExpander::ExpandVariable( return this->ReplaceValues->Output; } } + if (this->ReplaceValues->FilePathWithOutput) { + if (variable == "OUTPUT_STORE_TO_FILE") { + return this->ReplaceValues->FilePathWithOutput; + } + } if (variable == "CMAKE_COMMAND") { return this->OutputConverter->ConvertToOutputFormat( cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL); diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h index 60fc8f8ae3..e533f36a83 100644 --- a/Source/cmRulePlaceholderExpander.h +++ b/Source/cmRulePlaceholderExpander.h @@ -56,6 +56,7 @@ public: char const* PreprocessedSource = nullptr; char const* DynDepFile = nullptr; char const* Output = nullptr; + char const* FilePathWithOutput = nullptr; char const* Object = nullptr; char const* TargetSupportDir = nullptr; char const* ObjectDir = nullptr; diff --git a/Source/cmake.cxx b/Source/cmake.cxx index f33f61ab2e..fa569374e6 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -2882,8 +2882,9 @@ int cmake::ActualConfigure() "--output --config --language -- ")); this->State->SetGlobalProperty( "RULE_LAUNCH_CUSTOM", - cmStrCat(launcher, "--command-type custom", common_args, - "--output \"\" --role -- ")); + cmStrCat( + launcher, "--command-type custom", common_args, + "--output-as-file-name \"\" --role -- ")); } #endif diff --git a/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake b/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake index a427224c95..c66c3a6199 100644 --- a/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake +++ b/Tests/RunCMake/Instrumentation/RunCMakeTest.cmake @@ -617,6 +617,16 @@ instrument(cmake-command-trace CHECK_SCRIPT check-trace-removed.cmake ) +# FIXME(#27984): FASTBuild long output test failure +# FIXME(#279846): Watcom WMake unsupported file names +if(NOT RunCMake_GENERATOR MATCHES "FASTBuild|Watcom WMake") + instrument(cmake-command-long-output + BUILD + CONFIGURE_ARGS "-DLONG_CUSTOM_COMMAND_OUTPUT=ON" + CHECK_SCRIPT check-long-output.cmake + ) +endif() + # Test capture output instrument(cmake-command-capture-output BUILD CAPTURE_OUTPUT_QUERY diff --git a/Tests/RunCMake/Instrumentation/check-long-output.cmake b/Tests/RunCMake/Instrumentation/check-long-output.cmake new file mode 100644 index 0000000000..3278afdf0b --- /dev/null +++ b/Tests/RunCMake/Instrumentation/check-long-output.cmake @@ -0,0 +1,53 @@ +include(${CMAKE_CURRENT_LIST_DIR}/json.cmake) + +if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/Destination-Modules/check-done.txt") + string(APPEND RunCMake_TEST_FAILED + "long custom command with instrumentation did not run (Destination-Modules missing)\n") +endif() + +# Reconstruct expected outputs as in project/CMakeLists.txt +file(GLOB_RECURSE input_files + RELATIVE "${CMAKE_ROOT}" + "${CMAKE_ROOT}/Modules/*") +set(expected_outs) +foreach(f IN LISTS input_files) + list(APPEND expected_outs "Destination-${f}") +endforeach() +list(LENGTH expected_outs expected_count) +list(SORT expected_outs) + +# Find the custom snippet for the long-output command and verify its outputs +file(GLOB snippets LIST_DIRECTORIES false "${v1}/data/custom-*") +set(found_long_snippet 0) +foreach(snippet IN LISTS snippets) + read_json("${snippet}" contents) + string(JSON outputs ERROR_VARIABLE noOutputs GET "${contents}" outputs) + if(NOT outputs MATCHES "Destination-") + continue() + endif() + set(found_long_snippet 1) + + string(JSON outputs_len LENGTH "${contents}" outputs) + if(NOT outputs_len EQUAL expected_count) + json_error("${snippet}" + "Expected ${expected_count} outputs, got ${outputs_len}") + continue() + endif() + + set(actual_outs) + math(EXPR last "${outputs_len} - 1") + foreach(i RANGE ${last}) + string(JSON out GET "${contents}" outputs ${i}) + string(REPLACE "\\" "/" out "${out}") + list(APPEND actual_outs "${out}") + endforeach() + list(SORT actual_outs) + if(NOT expected_outs STREQUAL actual_outs) + json_error("${snippet}" + "outputs field does not match expected Destination-* list") + endif() +endforeach() + +if(NOT found_long_snippet) + add_error("No custom snippet with Destination- outputs was found") +endif() diff --git a/Tests/RunCMake/Instrumentation/project/CMakeLists.txt b/Tests/RunCMake/Instrumentation/project/CMakeLists.txt index e2dda8573f..3029c2a86c 100644 --- a/Tests/RunCMake/Instrumentation/project/CMakeLists.txt +++ b/Tests/RunCMake/Instrumentation/project/CMakeLists.txt @@ -62,6 +62,26 @@ install(TARGETS main) set_target_properties(main PROPERTIES LABELS "label1;label2") set_target_properties(lib PROPERTIES LABELS "label3") +if(LONG_CUSTOM_COMMAND_OUTPUT) + file(GLOB_RECURSE LONG_CUSTOM_FILES + RELATIVE "${CMAKE_ROOT}" + "${CMAKE_ROOT}/Modules/*") + set(long_dst "${CMAKE_CURRENT_BINARY_DIR}/Destination-Modules") + set(long_outs) + foreach(f IN LISTS LONG_CUSTOM_FILES) + # Generate outputs without spaces to correct expansion + list(APPEND long_outs "Destination-${f}") + endforeach() + add_custom_command( + OUTPUT ${long_outs} + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_ROOT}/Modules" "${long_dst}" + COMMAND "${CMAKE_COMMAND}" -E touch "${long_dst}/check-done.txt" + COMMENT "Copying Modules to Destination-Modules" + ) + add_custom_target(longCustomCommand ALL DEPENDS ${long_outs}) +endif() + if (FAIL) file(WRITE "${CMAKE_BINARY_DIR}/dummy.c" "#error \"something which will not compile\"\n"