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/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(); 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; } } 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() 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()