From 92d612645083678ffac4c04b58f4e926be6f4e21 Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 30 May 2025 18:47:31 -0400 Subject: [PATCH] MSVC: Move link -machine flag out of CMAKE_*_LINKER_FLAGS The `CMAKE_{EXE,SHARED,MODULE,STATIC}_LINKER_FLAGS` variables are not language-specific, so multiple languages' toolchains may disagree about if/how to pass the flag through a compiler driver to the linker. Furthermore, carrying the flag in public-facing variables allows projects or users to change it even though it is required. Add policy CMP0197 to remove the flag from the public-facing variables and generate it automatically instead: * For command-line generators, add the `-machine:` flag to the linker and archiver rule variables. * For Visual Studio generators, we do not need to explicitly add the link `-machine:` flag. MSBuild automatically adds it, and the new behavior actually removes a duplicate we generated previously. Issue: #21934 --- Help/manual/cmake-policies.7.rst | 1 + Help/policy/CMP0197.rst | 45 +++++++++++++++++++ Help/release/dev/msvc-link-machine.rst | 5 +++ Modules/Platform/Windows-IntelLLVM.cmake | 2 +- Modules/Platform/Windows-MSVC.cmake | 23 ++++++++-- Modules/Platform/Windows-NVIDIA-CUDA.cmake | 2 +- Source/cmCoreTryCompile.cxx | 7 +++ Source/cmPolicies.h | 5 ++- .../MSVCDefaultFlags/CMP0197-NEW-config.txt | 2 + .../MSVCDefaultFlags/CMP0197-NEW.cmake | 2 + .../MSVCDefaultFlags/CMP0197-OLD-config.txt | 1 + .../MSVCDefaultFlags/CMP0197-OLD.cmake | 2 + .../MSVCDefaultFlags/CMP0197-WARN-config.txt | 1 + .../MSVCDefaultFlags/CMP0197-WARN.cmake | 2 + .../MSVCDefaultFlags/CMP0197-build-stdout.txt | 1 + .../MSVCDefaultFlags/CMP0197-common.cmake | 20 +++++++++ .../MSVCDefaultFlags/RunCMakeTest.cmake | 13 ++++++ Tests/RunCMake/MSVCDefaultFlags/main.c | 4 ++ 18 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 Help/policy/CMP0197.rst create mode 100644 Help/release/dev/msvc-link-machine.rst create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW-config.txt create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW.cmake create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD-config.txt create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD.cmake create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN-config.txt create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN.cmake create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-build-stdout.txt create mode 100644 Tests/RunCMake/MSVCDefaultFlags/CMP0197-common.cmake create mode 100644 Tests/RunCMake/MSVCDefaultFlags/main.c diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst index 15d5f970eb..45e0ca035c 100644 --- a/Help/manual/cmake-policies.7.rst +++ b/Help/manual/cmake-policies.7.rst @@ -98,6 +98,7 @@ Policies Introduced by CMake 4.1 .. toctree:: :maxdepth: 1 + CMP0197: MSVC link -machine: flag is not in CMAKE_*_LINKER_FLAGS. CMP0196: The CMakeDetermineVSServicePack module is removed. CMP0195: Swift modules in build trees use the Swift module directory structure. CMP0194: MSVC is not an assembler for language ASM. diff --git a/Help/policy/CMP0197.rst b/Help/policy/CMP0197.rst new file mode 100644 index 0000000000..6742f7c750 --- /dev/null +++ b/Help/policy/CMP0197.rst @@ -0,0 +1,45 @@ +CMP0197 +------- + +.. versionadded:: 4.1 + +MSVC link ``-machine:`` flag is not in ``CMAKE_*_LINKER_FLAGS``. + +When using MSVC-like compilers in CMake 4.0 and below, the linker's +``-machine:`` flag is added via language-agnostic linker flag variables: + +* :variable:`CMAKE_EXE_LINKER_FLAGS` +* :variable:`CMAKE_SHARED_LINKER_FLAGS` +* :variable:`CMAKE_MODULE_LINKER_FLAGS` +* :variable:`CMAKE_STATIC_LINKER_FLAGS` + +This is problematic when multiple languages' toolchains disagree about +if/how to pass the flag through a compiler driver to the linker. +Furthermore, carrying the flag in public-facing variables allows projects +or users to change it even though it is required. + +CMake 4.1 and above prefer to leave the ``-machine:`` flag out of +``CMAKE_*_LINKER_FLAGS`` variables, and instead generate the link +flag automatically where needed. + +This policy provides compatibility with projects that have not been updated +to expect the lack of the ``-machine:`` flags. The policy setting takes +effect as of the first :command:`project` or :command:`enable_language` +command that initializes the above-listed ``CMAKE_*_LINKER_FLAGS`` variables. + +.. note:: + + Once the policy has taken effect at the top of a project for a given + language, that choice must be used throughout the tree for that language. + In projects that have nested projects in subdirectories, be sure to + convert everything together. + +The ``OLD`` behavior for this policy is to place the MSVC ``-machine:`` +flag in ``CMAKE_*_LINKER_FLAGS``. The ``NEW`` behavior for this policy +is to *not* place the MSVC ``-machine:`` flag in ``CMAKE_*_LINKER_FLAGS``. + +.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.1 +.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn +.. include:: include/STANDARD_ADVICE.rst + +.. include:: include/DEPRECATED.rst diff --git a/Help/release/dev/msvc-link-machine.rst b/Help/release/dev/msvc-link-machine.rst new file mode 100644 index 0000000000..986fd73812 --- /dev/null +++ b/Help/release/dev/msvc-link-machine.rst @@ -0,0 +1,5 @@ +msvc-link-machine +----------------- + +* The MSVC link ``-machine:`` flag is no longer added to the + ``CMAKE_*_LINKER_FLAGS`` variables. See policy :policy:`CMP0197`. diff --git a/Modules/Platform/Windows-IntelLLVM.cmake b/Modules/Platform/Windows-IntelLLVM.cmake index 953da81f01..58f1d91bed 100644 --- a/Modules/Platform/Windows-IntelLLVM.cmake +++ b/Modules/Platform/Windows-IntelLLVM.cmake @@ -47,7 +47,7 @@ macro(__windows_compiler_intel lang) if (NOT "${lang}" STREQUAL "Fortran" OR CMAKE_${lang}_COMPILER_VERSION VERSION_GREATER_EQUAL 2022.1) # The Fortran driver does not support -fuse-ld=llvm-lib before compiler version 2022.1 set(CMAKE_${lang}_CREATE_STATIC_LIBRARY - " ${CMAKE_CL_NOLOGO} ${CMAKE_START_TEMP_FILE} -fuse-ld=llvm-lib -o ${CMAKE_END_TEMP_FILE}") + " ${CMAKE_CL_NOLOGO}${_PLATFORM_ARCHIVE_FLAGS} ${CMAKE_START_TEMP_FILE} -fuse-ld=llvm-lib -o ${CMAKE_END_TEMP_FILE}") endif() set(CMAKE_DEPFILE_FLAGS_${lang} "-QMD -QMT -QMF ") diff --git a/Modules/Platform/Windows-MSVC.cmake b/Modules/Platform/Windows-MSVC.cmake index eafff7d431..d9f21fd949 100644 --- a/Modules/Platform/Windows-MSVC.cmake +++ b/Modules/Platform/Windows-MSVC.cmake @@ -276,7 +276,12 @@ unset(_GR) set(CMAKE_CXX_STANDARD_LIBRARIES_INIT "${CMAKE_C_STANDARD_LIBRARIES_INIT}") -set(_machine "/machine") +cmake_policy(GET CMP0197 CMAKE_MSVC_CMP0197) +if(CMAKE_MSVC_CMP0197 STREQUAL "NEW") + set(_machine "-machine") +else() + set(_machine "/machine") +endif() # executable linker flags set (CMAKE_LINK_DEF_FILE_FLAG "/DEF:") @@ -321,7 +326,9 @@ if(NOT WINDOWS_PHONE AND NOT WINDOWS_STORE AND NOT CMAKE_SYSTEM_NAME STREQUAL "W endif() foreach(t EXE SHARED MODULE) - string(APPEND CMAKE_${t}_LINKER_FLAGS_INIT " ${_MACHINE_ARCH_FLAG}") + if(NOT CMAKE_MSVC_CMP0197 STREQUAL "NEW") + string(APPEND CMAKE_${t}_LINKER_FLAGS_INIT " ${_MACHINE_ARCH_FLAG}") + endif() if (CMAKE_COMPILER_SUPPORTS_PDBTYPE) string(APPEND CMAKE_${t}_LINKER_FLAGS_DEBUG_INIT " ${_Wl}/debug ${_Wl}/pdbtype:sept ${MSVC_INCREMENTAL_YES_FLAG}") string(APPEND CMAKE_${t}_LINKER_FLAGS_RELWITHDEBINFO_INIT " ${_Wl}/debug ${_Wl}/pdbtype:sept ${MSVC_INCREMENTAL_YES_FLAG}") @@ -340,11 +347,19 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WindowsCE") elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsKernelModeDriver") string(APPEND _PLATFORM_LINK_FLAGS " -subsystem:native -kernel -MANIFEST:NO") endif() +if(CMAKE_MSVC_CMP0197 STREQUAL "NEW") + string(APPEND _PLATFORM_LINK_FLAGS " ${_MACHINE_ARCH_FLAG}") +endif() +set(_PLATFORM_ARCHIVE_FLAGS "") if((_MSVC_C_ARCHITECTURE_FAMILY STREQUAL "ARM64EC") OR (_MSVC_CXX_ARCHITECTURE_FAMILY STREQUAL "ARM64EC")) set(_MACHINE_ARCH_FLAG " ${_Wl}${_machine}:ARM64X") endif() -string(APPEND CMAKE_STATIC_LINKER_FLAGS_INIT " ${_MACHINE_ARCH_FLAG}") +if(CMAKE_MSVC_CMP0197 STREQUAL "NEW") + string(APPEND _PLATFORM_ARCHIVE_FLAGS " ${_MACHINE_ARCH_FLAG}") +else() + string(APPEND CMAKE_STATIC_LINKER_FLAGS_INIT " ${_MACHINE_ARCH_FLAG}") +endif() unset(_MACHINE_ARCH_FLAG) unset(_machine) @@ -388,7 +403,7 @@ macro(__windows_compiler_msvc lang) unset(_DLL_DRIVER) set(CMAKE_${lang}_CREATE_SHARED_MODULE ${CMAKE_${lang}_CREATE_SHARED_LIBRARY}) - set(CMAKE_${lang}_CREATE_STATIC_LIBRARY " ${CMAKE_CL_NOLOGO} /out: ") + set(CMAKE_${lang}_CREATE_STATIC_LIBRARY " ${CMAKE_CL_NOLOGO}${_PLATFORM_ARCHIVE_FLAGS} /out: ") set(CMAKE_${lang}_COMPILE_OBJECT " ${CMAKE_START_TEMP_FILE} ${CMAKE_CL_NOLOGO}${_COMPILE_${lang}} /Fo /Fd${_FS_${lang}} -c ${CMAKE_END_TEMP_FILE}") diff --git a/Modules/Platform/Windows-NVIDIA-CUDA.cmake b/Modules/Platform/Windows-NVIDIA-CUDA.cmake index fcd3e0307e..cc6b6876d3 100644 --- a/Modules/Platform/Windows-NVIDIA-CUDA.cmake +++ b/Modules/Platform/Windows-NVIDIA-CUDA.cmake @@ -17,7 +17,7 @@ set(CMAKE_CUDA_CREATE_SHARED_LIBRARY "${_CMAKE_VS_LINK_DLL} ${CMAKE_CL_NOLOGO} ${CMAKE_START_TEMP_FILE} /out: /implib: /pdb: /dll /version:.${_PLATFORM_LINK_FLAGS} ${__IMPLICIT_LINKS} ${CMAKE_END_TEMP_FILE}") set(CMAKE_CUDA_CREATE_SHARED_MODULE ${CMAKE_CUDA_CREATE_SHARED_LIBRARY}) -set(CMAKE_CUDA_CREATE_STATIC_LIBRARY " ${CMAKE_CL_NOLOGO} /out: ") +set(CMAKE_CUDA_CREATE_STATIC_LIBRARY " ${CMAKE_CL_NOLOGO}${_PLATFORM_ARCHIVE_FLAGS} /out: ") set(CMAKE_CUDA_LINKER_SUPPORTS_PDB ON) set(CMAKE_CUDA_LINK_EXECUTABLE "${_CMAKE_VS_LINK_EXE} ${CMAKE_CL_NOLOGO} ${CMAKE_START_TEMP_FILE} /out: /implib: /pdb: /version:.${_PLATFORM_LINK_FLAGS} ${__IMPLICIT_LINKS} ${CMAKE_END_TEMP_FILE}") diff --git a/Source/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx index d82f2ca19b..eb8f616020 100644 --- a/Source/cmCoreTryCompile.cxx +++ b/Source/cmCoreTryCompile.cxx @@ -91,6 +91,7 @@ std::string const kCMAKE_MSVC_DEBUG_INFORMATION_FORMAT_DEFAULT = "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT_DEFAULT"; std::string const kCMAKE_MSVC_RUNTIME_CHECKS_DEFAULT = "CMAKE_MSVC_RUNTIME_CHECKS_DEFAULT"; +std::string const kCMAKE_MSVC_CMP0197 = "CMAKE_MSVC_CMP0197"; /* GHS Multi platform variables */ std::set const ghs_platform_vars{ @@ -684,6 +685,12 @@ cm::optional cmCoreTryCompile::TryCompileCode( fprintf(fout, "cmake_policy(SET CMP0128 OLD)\n"); } + /* Set MSVC link -machine: policy to match outer project. */ + if (cmValue cmp0197 = this->Makefile->GetDefinition(kCMAKE_MSVC_CMP0197)) { + fprintf(fout, "cmake_policy(SET CMP0197 %s)\n", + *cmp0197 == "NEW"_s ? "NEW" : "OLD"); + } + std::string projectLangs; for (std::string const& li : testLangs) { projectLangs += cmStrCat(' ', li); diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index 49ecaa32a1..b6ef05e102 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -586,7 +586,10 @@ class cmMakefile; "Swift modules in build trees use the Swift module directory structure.", \ 4, 1, 0, WARN) \ SELECT(POLICY, CMP0196, \ - "The CMakeDetermineVSServicePack module is removed.", 4, 1, 0, WARN) + "The CMakeDetermineVSServicePack module is removed.", 4, 1, 0, WARN) \ + SELECT(POLICY, CMP0197, \ + "MSVC link -machine: flag is not in CMAKE_*_LINKER_FLAGS.", 4, 1, 0, \ + WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) #define CM_FOR_EACH_POLICY_ID(POLICY) \ diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW-config.txt b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW-config.txt new file mode 100644 index 0000000000..ca1c340385 --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW-config.txt @@ -0,0 +1,2 @@ +[ ,][/-][Mm][Aa][Cc][Hh][Ii][Nn][Ee]:[A-Za-z0-9_]+[ ]|[Vv][Cc](98|7)\\\\[Bb][Ii][Nn]\\\\[Ll][Ii][Nn][Kk]\.[Ee][Xx][Ee] [^ +]*@[^ ]+\\\\nm diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW.cmake b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW.cmake new file mode 100644 index 0000000000..95633891a2 --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-NEW.cmake @@ -0,0 +1,2 @@ +cmake_policy(SET CMP0197 NEW) +include(CMP0197-common.cmake) diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD-config.txt b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD-config.txt new file mode 100644 index 0000000000..caa568dc34 --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD-config.txt @@ -0,0 +1 @@ +[" ,]/[Mm][Aa][Cc][Hh][Ii][Nn][Ee]:[A-Za-z0-9_]+[ "] diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD.cmake b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD.cmake new file mode 100644 index 0000000000..1b54b5be3b --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-OLD.cmake @@ -0,0 +1,2 @@ +cmake_policy(SET CMP0197 OLD) +include(CMP0197-common.cmake) diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN-config.txt b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN-config.txt new file mode 100644 index 0000000000..caa568dc34 --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN-config.txt @@ -0,0 +1 @@ +[" ,]/[Mm][Aa][Cc][Hh][Ii][Nn][Ee]:[A-Za-z0-9_]+[ "] diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN.cmake b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN.cmake new file mode 100644 index 0000000000..a5d15f3f5b --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-WARN.cmake @@ -0,0 +1,2 @@ + +include(CMP0197-common.cmake) diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-build-stdout.txt b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-build-stdout.txt new file mode 100644 index 0000000000..bdd31e70ad --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-build-stdout.txt @@ -0,0 +1 @@ +[ ,][/-][Mm][Aa][Cc][Hh][Ii][Nn][Ee]:[A-Za-z0-9_]+[ ] diff --git a/Tests/RunCMake/MSVCDefaultFlags/CMP0197-common.cmake b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-common.cmake new file mode 100644 index 0000000000..ad6fed0352 --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/CMP0197-common.cmake @@ -0,0 +1,20 @@ +enable_language(C) + +# Make sure the compile command is not hidden. +string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}") +string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}") + +cmake_policy(GET CMP0197 cmp0197) +foreach(t EXE SHARED MODULE STATIC) + if(cmp0197 STREQUAL "NEW") + if("${CMAKE_${t}_LINKER_FLAGS}" MATCHES "([/-][Mm][Aa][Cc][Hh][Ii][Nn][Ee]:)") + message(SEND_ERROR "CMAKE_${t}_LINKER_FLAGS has '${CMAKE_MATCH_1}' under NEW behavior") + endif() + else() + if(NOT " ${CMAKE_${t}_LINKER_FLAGS} " MATCHES "[ ,]/machine:[A-Za-z0-9_]+ ") + message(SEND_ERROR "CMAKE_${t}_LINKER_FLAGS does not have '/machine:' under OLD behavior") + endif() + endif() +endforeach() + +add_executable(main main.c) diff --git a/Tests/RunCMake/MSVCDefaultFlags/RunCMakeTest.cmake b/Tests/RunCMake/MSVCDefaultFlags/RunCMakeTest.cmake index 7ce448dfff..bfe310469e 100644 --- a/Tests/RunCMake/MSVCDefaultFlags/RunCMakeTest.cmake +++ b/Tests/RunCMake/MSVCDefaultFlags/RunCMakeTest.cmake @@ -3,3 +3,16 @@ include(RunCMake) run_cmake(CMP0092-WARN) run_cmake(CMP0092-OLD) run_cmake(CMP0092-NEW) + +function(run_CMP0197 pol) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0197-${pol}) + run_cmake(CMP0197-${pol}) + set(RunCMake_TEST_NO_CLEAN 1) + set(RunCMake_TEST_OUTPUT_MERGE 1) + set(RunCMake-stdout-file "CMP0197-build-stdout.txt") + run_cmake_command(CMP0197-${pol}-build ${CMAKE_COMMAND} --build . --config Debug --verbose) +endfunction() + +run_CMP0197(WARN) +run_CMP0197(OLD) +run_CMP0197(NEW) diff --git a/Tests/RunCMake/MSVCDefaultFlags/main.c b/Tests/RunCMake/MSVCDefaultFlags/main.c new file mode 100644 index 0000000000..8488f4e58f --- /dev/null +++ b/Tests/RunCMake/MSVCDefaultFlags/main.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +}