Jelajahi Sumber

ASM: Guard exclusion of MSVC C/CXX compiler with a policy

Since commit 6baf65ec46 (ASM: Do not consider MSVC C/CXX compiler for
generic ASM, 2025-04-08) we no longer mistake `cl` for an assembler.
However, some projects unconditionally enable ``ASM``, which worked
on Windows only due to that bug.  Restore compatibility with such
projects by guarding the change behind a new policy ``CMP0194``.

Fixes: #26907
Issue: #26617
Brad King 5 bulan lalu
induk
melakukan
14212494bb

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -98,6 +98,7 @@ Policies Introduced by CMake 4.1
 .. toctree::
    :maxdepth: 1
 
+   CMP0194: MSVC is not an assembler for language ASM. </policy/CMP0194>
    CMP0193: GNUInstallDirs caches CMAKE_INSTALL_* with leading 'usr/' for install prefix '/'. </policy/CMP0193>
    CMP0192: GNUInstallDirs uses absolute SYSCONFDIR, LOCALSTATEDIR, and RUNSTATEDIR in special prefixes. </policy/CMP0192>
    CMP0191: The FindCABLE module is removed. </policy/CMP0191>

+ 27 - 0
Help/policy/CMP0194.rst

@@ -0,0 +1,27 @@
+CMP0194
+-------
+
+.. versionadded:: 4.1
+
+MSVC is not an assembler for language ASM.
+
+When enabling the ``ASM`` language, CMake considers C compiler drivers
+as assembler candidates.  CMake 4.0 and below accidentally selected
+MSVC's ``cl`` compiler as the ``CMAKE_ASM_COMPILER``, allowing the ``ASM``
+language to be enabled on Windows even though ``cl`` does not support
+assembler sources.  CMake 4.1 and above prefer to reject ``cl`` as an
+assembler candidate, but some existing projects unconditionally enable
+``ASM`` on Windows even though they add no assembler sources.  This
+policy provides compatibility for such projects to allow them to
+configure as before.
+
+The ``OLD`` behavior for this policy is to successfully enable ``ASM``
+even if ``cl`` is the only available candidate.  The ``NEW`` behavior
+for this policy is to not consider ``cl`` as a candidate assembler
+for the ``ASM`` language.
+
+.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.1
+.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
+.. include:: include/STANDARD_ADVICE.rst
+
+.. include:: include/DEPRECATED.rst

+ 5 - 0
Help/release/dev/asm-no-msvc.rst

@@ -0,0 +1,5 @@
+asm-no-msvc
+-----------
+
+* Enabling ``ASM`` no longer accidentally succeeds using ``MSVC``'s ``cl``
+  C compiler as an assembler.  See policy :policy:`CMP0194`.

+ 1 - 1
Modules/CMakeASMInformation.cmake

@@ -22,7 +22,7 @@ if(CMAKE_ASM${ASM_DIALECT}_COMPILER_ID)
   include(Compiler/${CMAKE_ASM${ASM_DIALECT}_COMPILER_ID}-ASM${ASM_DIALECT} OPTIONAL  RESULT_VARIABLE _INCLUDED_FILE)
 endif()
 if(NOT _INCLUDED_FILE)
-  if("ASM${ASM_DIALECT}" STREQUAL "ASM")
+  if("ASM${ASM_DIALECT}" STREQUAL "ASM" AND CMAKE_ASM${ASM_DIALECT}_COMPILER_ID)
     message(STATUS "Warning: Did not find file Compiler/${CMAKE_ASM${ASM_DIALECT}_COMPILER_ID}-ASM${ASM_DIALECT}")
   endif()
   include(Platform/${CMAKE_BASE_NAME} OPTIONAL)

+ 29 - 5
Modules/CMakeDetermineASMCompiler.cmake

@@ -6,6 +6,8 @@
 
 include(${CMAKE_ROOT}/Modules/CMakeDetermineCompiler.cmake)
 
+cmake_policy(GET CMP0194 _CMAKE_ASM_CMP0194)
+
 if(NOT CMAKE_ASM${ASM_DIALECT}_COMPILER)
   # prefer the environment variable ASM
   if(NOT $ENV{ASM${ASM_DIALECT}} STREQUAL "")
@@ -21,19 +23,32 @@ if(NOT CMAKE_ASM${ASM_DIALECT}_COMPILER)
   # finally list compilers to try
   if("ASM${ASM_DIALECT}" STREQUAL "ASM") # the generic assembler support
     if(NOT CMAKE_ASM_COMPILER_INIT)
-      if(CMAKE_C_COMPILER_LOADED AND NOT CMAKE_C_COMPILER_ID MATCHES "^(MSVC)$")
+      if(_CMAKE_ASM_CMP0194 STREQUAL "NEW")
+        set(_CMAKE_ASM_REGEX_MSVC "^(MSVC)$")
+        set(_CMAKE_ASM_REGEX_CL "(^|/)[Cc][Ll](\\.|$)")
+        set(_CMAKE_ASM_MAYBE_CL "")
+      else()
+        set(_CMAKE_ASM_REGEX_MSVC "CMP0194_OLD_MSVC_NOT_EXCLUDED")
+        set(_CMAKE_ASM_REGEX_CL "CMP0194_OLD_MSVC_NOT_EXCLUDED")
+        set(_CMAKE_ASM_MAYBE_CL "cl")
+      endif()
+      if(CMAKE_C_COMPILER_LOADED AND NOT CMAKE_C_COMPILER_ID MATCHES "${_CMAKE_ASM_REGEX_MSVC}")
         set(CMAKE_ASM_COMPILER_LIST ${CMAKE_C_COMPILER})
-      elseif(NOT CMAKE_C_COMPILER_LOADED AND CMAKE_C_COMPILER AND NOT CMAKE_C_COMPILER MATCHES "(^|/)[Cc][Ll](\\.|$)")
+      elseif(NOT CMAKE_C_COMPILER_LOADED AND CMAKE_C_COMPILER AND NOT CMAKE_C_COMPILER MATCHES "${_CMAKE_ASM_REGEX_CL}")
         set(CMAKE_ASM_COMPILER_LIST ${CMAKE_C_COMPILER})
-      elseif(CMAKE_CXX_COMPILER_LOADED AND NOT CMAKE_CXX_COMPILER_ID MATCHES "^(MSVC)$")
+      elseif(CMAKE_CXX_COMPILER_LOADED AND NOT CMAKE_CXX_COMPILER_ID MATCHES "${_CMAKE_ASM_REGEX_MSVC}")
         set(CMAKE_ASM_COMPILER_LIST ${CMAKE_CXX_COMPILER})
-      elseif(NOT CMAKE_CXX_COMPILER_LOADED AND CMAKE_CXX_COMPILER AND NOT CMAKE_CXX_COMPILER MATCHES "(^|/)[Cc][Ll](\\.|$)")
+      elseif(NOT CMAKE_CXX_COMPILER_LOADED AND CMAKE_CXX_COMPILER AND NOT CMAKE_CXX_COMPILER MATCHES "${_CMAKE_ASM_REGEX_CL}")
         set(CMAKE_ASM_COMPILER_LIST ${CMAKE_CXX_COMPILER})
       else()
         # List all default C and CXX compilers
         set(CMAKE_ASM_COMPILER_LIST
              ${_CMAKE_TOOLCHAIN_PREFIX}cc  ${_CMAKE_TOOLCHAIN_PREFIX}gcc xlc
+             ${_CMAKE_ASM_MAYBE_CL}
           CC ${_CMAKE_TOOLCHAIN_PREFIX}c++ ${_CMAKE_TOOLCHAIN_PREFIX}g++ xlC)
+        unset(_CMAKE_ASM_MAYBE_CL)
+        unset(_CMAKE_ASM_REGEX_CL)
+        unset(_CMAKE_ASM_REGEX_MSVC)
       endif()
     endif()
   else() # some specific assembler "dialect"
@@ -100,7 +115,11 @@ if(NOT CMAKE_ASM${ASM_DIALECT}_COMPILER_ID)
 
   list(APPEND CMAKE_ASM${ASM_DIALECT}_COMPILER_ID_VENDORS MSVC )
   set(CMAKE_ASM${ASM_DIALECT}_COMPILER_ID_VENDOR_FLAGS_MSVC "-?")
-  set(CMAKE_ASM${ASM_DIALECT}_COMPILER_ID_VENDOR_REGEX_MSVC "Microsoft.*Macro Assembler")
+  if(_CMAKE_ASM_CMP0194 STREQUAL "NEW")
+    set(CMAKE_ASM${ASM_DIALECT}_COMPILER_ID_VENDOR_REGEX_MSVC "Microsoft.*Macro Assembler")
+  else()
+    set(CMAKE_ASM${ASM_DIALECT}_COMPILER_ID_VENDOR_REGEX_MSVC "Microsoft")
+  endif()
 
   list(APPEND CMAKE_ASM${ASM_DIALECT}_COMPILER_ID_VENDORS TI )
   set(CMAKE_ASM${ASM_DIALECT}_COMPILER_ID_VENDOR_FLAGS_TI "-h")
@@ -200,6 +219,11 @@ else()
   message(STATUS "The ASM${ASM_DIALECT} compiler identification is unknown")
 endif()
 
+if("ASM${ASM_DIALECT}" STREQUAL "ASM" AND CMAKE_ASM_COMPILER_ID STREQUAL "MSVC" AND _CMAKE_ASM_CMP0194 STREQUAL "")
+  cmake_policy(GET_WARNING CMP0194 _CMAKE_ASM_CMP0194_WARNING)
+  message(AUTHOR_WARNING "${_CMAKE_ASM_CMP0194_WARNING}")
+endif()
+
 # If we have a gas/as cross compiler, they have usually some prefix, like
 # e.g. powerpc-linux-gas, arm-elf-gas or i586-mingw32msvc-gas , optionally
 # with a 3-component version number at the end

+ 3 - 1
Source/cmPolicies.h

@@ -578,7 +578,9 @@ class cmMakefile;
   SELECT(POLICY, CMP0193,                                                     \
          "GNUInstallDirs caches CMAKE_INSTALL_* with leading 'usr/' for "     \
          "install prefix '/'.",                                               \
-         4, 1, 0, WARN)
+         4, 1, 0, WARN)                                                       \
+  SELECT(POLICY, CMP194, "MSVC is not an assembler for language ASM.", 4, 1,  \
+         0, WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \

+ 1 - 0
Tests/RunCMake/CMP0194/CMP0194-NEW-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
Tests/RunCMake/CMP0194/CMP0194-NEW-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error at CMP0194-common\.cmake:[0-9]+ \(enable_language\):
+  No CMAKE_ASM_COMPILER could be found\.
+(
+  Tell CMake where to find the compiler by setting either the environment
+  variable "ASM" or the CMake cache entry CMAKE_ASM_COMPILER to the full path
+  to the compiler, or to the compiler name if it is in the PATH\.)?
+Call Stack \(most recent call first\):
+  CMP0194-NEW\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)$

+ 2 - 0
Tests/RunCMake/CMP0194/CMP0194-NEW-stdout.txt

@@ -0,0 +1,2 @@
+-- The ASM compiler identification is unknown
+-- Didn't find assembler

+ 2 - 0
Tests/RunCMake/CMP0194/CMP0194-NEW.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0194 NEW)
+include(CMP0194-common.cmake)

+ 3 - 0
Tests/RunCMake/CMP0194/CMP0194-OLD-stdout.txt

@@ -0,0 +1,3 @@
+-- The ASM compiler identification is MSVC
+-- Found assembler: [^
+]*/cl\.exe

+ 2 - 0
Tests/RunCMake/CMP0194/CMP0194-OLD.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0194 OLD)
+include(CMP0194-common.cmake)

+ 10 - 0
Tests/RunCMake/CMP0194/CMP0194-WARN-stderr.txt

@@ -0,0 +1,10 @@
+^CMake Warning \(dev\) at [^
+]*/Modules/CMakeDetermineASMCompiler.cmake:[0-9]+ \(message\):
+  Policy CMP194 is not set: MSVC is not an assembler for language ASM\.  Run
+  "cmake --help-policy CMP194" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0194-common\.cmake:[0-9]+ \(enable_language\)
+  CMP0194-WARN\.cmake:[0-9]+ \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$

+ 3 - 0
Tests/RunCMake/CMP0194/CMP0194-WARN-stdout.txt

@@ -0,0 +1,3 @@
+-- The ASM compiler identification is MSVC
+-- Found assembler: [^
+]*/cl\.exe

+ 2 - 0
Tests/RunCMake/CMP0194/CMP0194-WARN.cmake

@@ -0,0 +1,2 @@
+# CMP0194 is unset
+include(CMP0194-common.cmake)

+ 3 - 0
Tests/RunCMake/CMP0194/CMP0194-common.cmake

@@ -0,0 +1,3 @@
+enable_language(C)
+set(ENV{PATH} "")
+enable_language(ASM)

+ 3 - 0
Tests/RunCMake/CMP0194/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 4.0)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 10 - 0
Tests/RunCMake/CMP0194/RunCMakeTest.cmake

@@ -0,0 +1,10 @@
+include(RunCMake)
+
+# The test cases empty the PATH before enabling ASM to avoid finding
+# another assembler in the caller's environment.  However, old
+# versions of MSVC do not support running `cl` without the PATH set.
+if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 16)
+  run_cmake(CMP0194-WARN)
+  run_cmake(CMP0194-OLD)
+endif()
+run_cmake(CMP0194-NEW)

+ 4 - 0
Tests/RunCMake/CMakeLists.txt

@@ -181,6 +181,10 @@ add_RunCMake_test(CMP0171)
 add_RunCMake_test(CMP0173)
 add_RunCMake_test(CMP0187)
 
+if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
+  add_RunCMake_test(CMP0194 -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
+endif()
+
 # The test for Policy 65 requires the use of the
 # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode
 # generators ignore.  The policy will have no effect on those generators.