From 1c1258ad9583c06b8f96bb894d631d2a58c1b10b Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Fri, 18 Sep 2026 11:34:49 +0200 Subject: [PATCH 1/4] cmWorkerPool: Allow capturing process stderr separately JobT::RunProcess always merged the process stderr into ProcessResultT::StdOut. That is fine for jobs that only log the output, but not for jobs that consume stdout as data. Add a mergedOutput parameter that defaults to the previous behavior. Issue: #28093 --- Source/cmWorkerPool.cxx | 13 ++++++++----- Source/cmWorkerPool.h | 5 ++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/cmWorkerPool.cxx b/Source/cmWorkerPool.cxx index cae37a76c1..6a9442c027 100644 --- a/Source/cmWorkerPool.cxx +++ b/Source/cmWorkerPool.cxx @@ -401,7 +401,7 @@ public: */ bool RunProcess(cmWorkerPool::ProcessResultT& result, std::vector command, - std::string const& workingDirectory); + std::string const& workingDirectory, bool mergedOutput); private: // -- Libuv callbacks @@ -434,7 +434,8 @@ cmWorkerPoolWorker::~cmWorkerPoolWorker() bool cmWorkerPoolWorker::RunProcess(cmWorkerPool::ProcessResultT& result, std::vector command, - std::string const& workingDirectory) + std::string const& workingDirectory, + bool mergedOutput) { if (command.empty()) { return false; @@ -443,7 +444,7 @@ bool cmWorkerPoolWorker::RunProcess(cmWorkerPool::ProcessResultT& result, { std::lock_guard lock(this->Proc_.Mutex); this->Proc_.ROP = cm::make_unique(); - this->Proc_.ROP->setup(&result, true, std::move(command), + this->Proc_.ROP->setup(&result, mergedOutput, std::move(command), workingDirectory); } // Send asynchronous process start request to libuv loop @@ -737,11 +738,13 @@ cmWorkerPool::JobT::~JobT() = default; bool cmWorkerPool::JobT::RunProcess(ProcessResultT& result, std::vector command, - std::string const& workingDirectory) + std::string const& workingDirectory, + bool mergedOutput) { // Get worker by index auto* worker = this->Pool_->Int_->Workers.at(this->WorkerIndex_).get(); - return worker->RunProcess(result, std::move(command), workingDirectory); + return worker->RunProcess(result, std::move(command), workingDirectory, + mergedOutput); } cmWorkerPool::cmWorkerPool() diff --git a/Source/cmWorkerPool.h b/Source/cmWorkerPool.h index e8614a4707..0049c1d9bf 100644 --- a/Source/cmWorkerPool.h +++ b/Source/cmWorkerPool.h @@ -101,9 +101,12 @@ public: /** * Run an external read only process. * Use only during JobT::Process() call! + * @arg mergedOutput Append the process stderr to ProcessResultT::StdOut + * instead of ProcessResultT::StdErr. */ bool RunProcess(ProcessResultT& result, std::vector command, - std::string const& workingDirectory); + std::string const& workingDirectory, + bool mergedOutput = true); private: //! Needs access to Work() From 4a4a654814fbeb73aa11d75a6a29df0b8880c00f Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Fri, 18 Sep 2026 11:34:49 +0200 Subject: [PATCH 2/4] Autogen: Keep stderr out of the generated moc_predefs.h The stdout of CMAKE_CXX_COMPILER_PREDEFINES_COMMAND is written to moc_predefs.h verbatim, so whatever the command prints on stderr must not be mixed in. MSVC-like compilers echo the name of the source file they preprocess. Run the job with separate streams and report both streams when the command fails. Issue: #28093 --- Source/cmQtAutoMocUic.cxx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx index bd3701dffa..6721edf2c3 100644 --- a/Source/cmQtAutoMocUic.cxx +++ b/Source/cmQtAutoMocUic.cxx @@ -359,7 +359,8 @@ public: /** @brief Run an external process. Use only during Process() call! */ bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result, std::vector const& command, - std::string* infoMessage = nullptr); + std::string* infoMessage = nullptr, + bool mergedOutput = true); }; /** Fence job utility class. */ @@ -863,7 +864,8 @@ void cmQtAutoMocUicT::JobT::MaybeWriteResponseFile( bool cmQtAutoMocUicT::JobT::RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result, std::vector const& command, - std::string* infoMessage) + std::string* infoMessage, + bool mergedOutput) { // Log command if (this->Log().Verbose()) { @@ -878,7 +880,7 @@ bool cmQtAutoMocUicT::JobT::RunProcess(GenT genType, } // Run command return this->cmWorkerPool::JobT::RunProcess( - result, command, this->BaseConst().AutogenBuildDir); + result, command, this->BaseConst().AutogenBuildDir, mergedOutput); } void cmQtAutoMocUicT::JobMocPredefsT::Process() @@ -904,13 +906,15 @@ void cmQtAutoMocUicT::JobMocPredefsT::Process() // Check if response file is necessary MaybeWriteResponseFile(this->MocConst().PredefsFileAbs, cmd); - // Execute command - if (!this->RunProcess(GenT::MOC, result, cmd, reason.get())) { + // Execute command. Keep stderr out of the captured stdout, which is + // written to the predefs file verbatim: MSVC-like compilers echo the + // name of the source file they preprocess. + if (!this->RunProcess(GenT::MOC, result, cmd, reason.get(), false)) { this->LogCommandError(GenT::MOC, cmStrCat("The content generation command for ", this->MessagePath(predefsFileAbs), " failed.\n", result.ErrorMessage), - cmd, result.StdOut); + cmd, result.StdOut + result.StdErr); return; } } From 0fd711348c70eba309308b278124e20584b09d61 Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Fri, 18 Sep 2026 11:34:49 +0200 Subject: [PATCH 3/4] Autogen: Pass WIN32 to moc when targeting the MSVC ABI WIN32 was added to the moc definitions only when no moc_predefs.h was generated, as of commit 5f0f84c7e0 (Autogen: Don't add a WIN32 moc definition if we have a moc_predefs.h file, 2018-11-27, v3.14.0-rc1~298^2). For the MSVC ABI that is not enough: WIN32 comes from our default Windows flags rather than from the compiler, so a generated moc_predefs.h does not carry it either. Keep the existing fallback for the other toolchains. GCC predefines WIN32 on Windows, but only with the GNU dialects, so injecting it unconditionally would make moc disagree with a strict ISO compile. Issue: #28093 --- Source/cmQtAutoGenInitializer.cxx | 17 +++++++++++++---- Source/cmQtAutoGenInitializer.h | 3 +++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx index afd68328a2..435fc13286 100644 --- a/Source/cmQtAutoGenInitializer.cxx +++ b/Source/cmQtAutoGenInitializer.cxx @@ -713,6 +713,13 @@ bool cmQtAutoGenInitializer::InitCustomTargets() return true; } +bool cmQtAutoGenInitializer::IsMsvcAbi() const +{ + return this->Makefile->GetSafeDefinition("CMAKE_CXX_COMPILER_ID") == + "MSVC" || + this->Makefile->GetSafeDefinition("CMAKE_CXX_SIMULATE_ID") == "MSVC"; +} + bool cmQtAutoGenInitializer::InitMoc() { // Mocs compilation file @@ -814,10 +821,12 @@ bool cmQtAutoGenInitializer::InitMoc() auto getDefs = [this](std::string const& cfg) -> std::set { std::set defines; this->LocalGen->GetTargetDefines(this->GenTarget, cfg, "CXX", defines); - if (this->Moc.PredefsCmd.empty() && - this->Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") == - "Windows") { - // Add WIN32 definition if we don't have a moc_predefs.h + if (this->Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") == + "Windows" && + (this->Moc.PredefsCmd.empty() || this->IsMsvcAbi())) { + // Add WIN32 definition if moc_predefs.h cannot supply it. Targeting + // the MSVC ABI it comes from our default flags rather than from the + // compiler, so it never appears in moc_predefs.h. defines.insert("WIN32"); } return defines; diff --git a/Source/cmQtAutoGenInitializer.h b/Source/cmQtAutoGenInitializer.h index 879d98d4d4..f4e444edb6 100644 --- a/Source/cmQtAutoGenInitializer.h +++ b/Source/cmQtAutoGenInitializer.h @@ -111,6 +111,9 @@ private: return (this->Moc.Enabled || this->Uic.Enabled); } + /** Whether the CXX compiler targets the MSVC ABI. */ + bool IsMsvcAbi() const; + bool InitMoc(); bool InitUic(); bool InitRcc(); From 7c3ca57b9102b475daa890a90d8f161add68ad4a Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Fri, 18 Sep 2026 11:35:02 +0200 Subject: [PATCH 4/4] MSVC,Clang: Set CMAKE_CXX_COMPILER_PREDEFINES_COMMAND for the MSVC ABI The variable was only set by the GNU-like compiler modules, so AUTOMOC never generated a moc_predefs.h for compilers targeting the MSVC ABI and moc parsed headers without the compiler's predefined macros. MSVC dumps its predefined macros with '/PD', which needs the conforming preprocessor. Require VS 2019 16.8, because 16.7 and older ignore the flag with warning D9002 and emit no macros at all. clang-cl takes '-dM' through '-Xclang'. Both also echo the preprocessed source, so feed them an empty translation unit. clang with the GNU driver is excluded from 'Compiler/GNU' as well when it simulates MSVC, so give it the usual '-dM -E' command there. Fixes: #28093 --- Modules/CMakeCXXCompilerPredefines.cpp | 3 +++ Modules/Compiler/Clang-CXX.cmake | 25 +++++++++++++++++++++++ Modules/Compiler/MSVC.cmake | 17 +++++++++++++++ Tests/RunCMake/Autogen_1/MocPredefs.cmake | 8 ++++++++ 4 files changed, 53 insertions(+) create mode 100644 Modules/CMakeCXXCompilerPredefines.cpp diff --git a/Modules/CMakeCXXCompilerPredefines.cpp b/Modules/CMakeCXXCompilerPredefines.cpp new file mode 100644 index 0000000000..418a8d1e32 --- /dev/null +++ b/Modules/CMakeCXXCompilerPredefines.cpp @@ -0,0 +1,3 @@ +// An empty translation unit for CMAKE__COMPILER_PREDEFINES_COMMAND. +// MSVC-like compilers echo the preprocessed source along with the macro +// definitions, so the input must contribute no output of its own. diff --git a/Modules/Compiler/Clang-CXX.cmake b/Modules/Compiler/Clang-CXX.cmake index 5a8bc3fda3..d0c3228c79 100644 --- a/Modules/Compiler/Clang-CXX.cmake +++ b/Modules/Compiler/Clang-CXX.cmake @@ -20,6 +20,31 @@ if("x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC") set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE_DRIVER_MODE "cl") endif() +# 'Compiler/GNU' is not included when clang simulates MSVC, so the predefines +# command is not set up there. +if(NOT DEFINED CMAKE_CXX_COMPILER_PREDEFINES_COMMAND) + set(CMAKE_CXX_COMPILER_PREDEFINES_COMMAND "${CMAKE_CXX_COMPILER}") + if(CMAKE_CXX_COMPILER_ARG1) + separate_arguments(_COMPILER_ARGS NATIVE_COMMAND "${CMAKE_CXX_COMPILER_ARG1}") + list(APPEND CMAKE_CXX_COMPILER_PREDEFINES_COMMAND ${_COMPILER_ARGS}) + unset(_COMPILER_ARGS) + endif() + if("x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC") + # '-dM' is a cc1 option in this driver mode, and '-EP' also echoes the + # preprocessed source, hence the empty translation unit. + list(APPEND CMAKE_CXX_COMPILER_PREDEFINES_COMMAND + "-nologo" "-w" "-Xclang" "-dM" "-EP" + "${CMAKE_ROOT}/Modules/CMakeCXXCompilerPredefines.cpp") + else() + list(APPEND CMAKE_CXX_COMPILER_PREDEFINES_COMMAND + "-w" "-dM" "-E" "${CMAKE_ROOT}/Modules/CMakeCXXCompilerABI.cpp") + endif() + if(CMAKE_CXX_COMPILER_TARGET) + list(APPEND CMAKE_CXX_COMPILER_PREDEFINES_COMMAND + "--target=${CMAKE_CXX_COMPILER_TARGET}") + endif() +endif() + if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.1 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")) if (CMAKE_CXX_COMPILER_CLANG_RESOURCE_DIR) diff --git a/Modules/Compiler/MSVC.cmake b/Modules/Compiler/MSVC.cmake index 40517b93e2..35c67e4170 100644 --- a/Modules/Compiler/MSVC.cmake +++ b/Modules/Compiler/MSVC.cmake @@ -23,5 +23,22 @@ macro(__compiler_msvc lang) set(CMAKE_INCLUDE_SYSTEM_FLAG_${lang}_WARNING "-external:W0 ") endif () + # '/PD' dumps the predefined macros, but it needs the conforming + # preprocessor and one of '/E', '/EP' or '/P'. VS 2019 16.7 and older + # ignore it with warning D9002, so require 16.8. '/EP' also echoes the + # preprocessed source, hence the empty translation unit. + if("${lang}" STREQUAL "CXX" AND + CMAKE_${lang}_COMPILER_VERSION VERSION_GREATER_EQUAL 19.28) + set(CMAKE_${lang}_COMPILER_PREDEFINES_COMMAND "${CMAKE_${lang}_COMPILER}") + if(CMAKE_${lang}_COMPILER_ARG1) + separate_arguments(_COMPILER_ARGS NATIVE_COMMAND "${CMAKE_${lang}_COMPILER_ARG1}") + list(APPEND CMAKE_${lang}_COMPILER_PREDEFINES_COMMAND ${_COMPILER_ARGS}) + unset(_COMPILER_ARGS) + endif() + list(APPEND CMAKE_${lang}_COMPILER_PREDEFINES_COMMAND + "-nologo" "-w" "-Zc:preprocessor" "-PD" "-EP" + "${CMAKE_ROOT}/Modules/CMakeCXXCompilerPredefines.cpp") + endif() + set(CMAKE_${lang}_LINK_MODE LINKER) endmacro() diff --git a/Tests/RunCMake/Autogen_1/MocPredefs.cmake b/Tests/RunCMake/Autogen_1/MocPredefs.cmake index 6ea192a273..cc71119f8e 100644 --- a/Tests/RunCMake/Autogen_1/MocPredefs.cmake +++ b/Tests/RunCMake/Autogen_1/MocPredefs.cmake @@ -11,6 +11,14 @@ set(CMAKE_CXX_EXTENSIONS OFF) add_library(MocPredefs MocPredefs.cxx) if(NOT DEFINED CMAKE_CXX_COMPILER_PREDEFINES_COMMAND) + # Do not silently skip on toolchains that are expected to provide it. + if((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER_EQUAL 1928) + OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" + AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) + message(FATAL_ERROR + "CMAKE_CXX_COMPILER_PREDEFINES_COMMAND is not defined for " + "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}.") + endif() return() endif()