Browse Source

VS: Add [CMAKE_]VS_USE_DEBUG_LIBRARIES options to control UseDebugLibraries

This indicates to MSBuild which configurations are considered debug
configurations.  This is useful for reference both by humans and tools.

Issue: #25327
Brad King 1 year ago
parent
commit
b814641444

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

@@ -445,6 +445,7 @@ Properties on Targets
    /prop_tgt/VS_SDK_REFERENCES
    /prop_tgt/VS_SOLUTION_DEPLOY
    /prop_tgt/VS_SOURCE_SETTINGS_tool
+   /prop_tgt/VS_USE_DEBUG_LIBRARIES
    /prop_tgt/VS_USER_PROPS
    /prop_tgt/VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION
    /prop_tgt/VS_WINRT_COMPONENT

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

@@ -137,6 +137,7 @@ Variables that Provide Information
    /variable/CMAKE_VS_TARGET_FRAMEWORK_IDENTIFIER
    /variable/CMAKE_VS_TARGET_FRAMEWORK_TARGETS_VERSION
    /variable/CMAKE_VS_TARGET_FRAMEWORK_VERSION
+   /variable/CMAKE_VS_USE_DEBUG_LIBRARIES
    /variable/CMAKE_VS_VERSION_BUILD_NUMBER
    /variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION
    /variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION

+ 18 - 0
Help/prop_tgt/VS_USE_DEBUG_LIBRARIES-PURPOSE.txt

@@ -0,0 +1,18 @@
+Indicate to :ref:`Visual Studio Generators` what configurations are considered
+debug configurations.  This controls the ``UseDebugLibraries`` setting in
+each configuration of a ``.vcxproj`` file.
+
+The "Use Debug Libraries" setting in Visual Studio projects, despite its
+specific-sounding name, is a general-purpose indicator of what configurations
+are considered debug configurations.  In standalone projects, this may affect
+MSBuild's default selection of MSVC runtime library, optimization flags,
+runtime checks, and similar settings.  In CMake projects those settings are
+typically generated explicitly based on the project's specification, e.g.,
+the MSVC runtime library is controlled by |MSVC_RUNTIME_LIBRARY|.  However,
+the ``UseDebugLibraries`` indicator is useful for reference by both humans
+and tools, and may also affect the behavior of platform-specific SDKs.
+
+Set |VS_USE_DEBUG_LIBRARIES| to a true or false value to indicate whether
+each configuration is considered a debug configuration.  The value may also
+be the empty string (``""``) in which case no ``UseDebugLibraries`` will be
+added explicitly by CMake, and MSBuild will use its default value, ``false``.

+ 27 - 0
Help/prop_tgt/VS_USE_DEBUG_LIBRARIES.rst

@@ -0,0 +1,27 @@
+VS_USE_DEBUG_LIBRARIES
+----------------------
+
+.. versionadded:: 3.30
+
+.. |VS_USE_DEBUG_LIBRARIES| replace:: ``VS_USE_DEBUG_LIBRARIES``
+.. |MSVC_RUNTIME_LIBRARY| replace:: :prop_tgt:`MSVC_RUNTIME_LIBRARY`
+
+.. include:: VS_USE_DEBUG_LIBRARIES-PURPOSE.txt
+
+Use :manual:`generator expressions <cmake-generator-expressions(7)>`
+for per-configuration specification.  For example, the code:
+
+.. code-block:: cmake
+
+  add_executable(foo foo.c)
+  set_property(TARGET foo PROPERTY
+    VS_USE_DEBUG_LIBRARIES "$<CONFIG:Debug,Custom>")
+
+indicates that target ``foo`` considers its "Debug" and "Custom"
+configurations to be debug configurations, and its other configurations
+to be non-debug configurations.
+
+The property is initialized from the value of the
+:variable:`CMAKE_VS_USE_DEBUG_LIBRARIES` variable, if it is set.
+If the property is not set, then CMake does not generate any
+``UseDebugLibraries`` indicator.

+ 6 - 0
Help/release/dev/vs-UseDebugLibraries.rst

@@ -0,0 +1,6 @@
+vs-UseDebugLibraries
+--------------------
+
+* The :variable:`CMAKE_VS_USE_DEBUG_LIBRARIES` variable and corresponding
+  :prop_tgt:`VS_USE_DEBUG_LIBRARIES` target property were added to explicitly
+  control ``UseDebugLibraries`` indicators in ``.vcxproj`` files.

+ 28 - 0
Help/variable/CMAKE_VS_USE_DEBUG_LIBRARIES.rst

@@ -0,0 +1,28 @@
+CMAKE_VS_USE_DEBUG_LIBRARIES
+----------------------------
+
+.. versionadded:: 3.30
+
+.. |VS_USE_DEBUG_LIBRARIES| replace:: ``CMAKE_VS_USE_DEBUG_LIBRARIES``
+.. |MSVC_RUNTIME_LIBRARY| replace:: :variable:`CMAKE_MSVC_RUNTIME_LIBRARY`
+
+.. include:: ../prop_tgt/VS_USE_DEBUG_LIBRARIES-PURPOSE.txt
+
+Use :manual:`generator expressions <cmake-generator-expressions(7)>`
+for per-configuration specification.  For example, the code:
+
+.. code-block:: cmake
+
+  set(CMAKE_VS_USE_DEBUG_LIBRARIES "$<CONFIG:Debug,Custom>")
+
+indicates that all following targets consider their "Debug" and "Custom"
+configurations to be debug configurations, and their other configurations
+to be non-debug configurations.
+
+This variable is used to initialize the :prop_tgt:`VS_USE_DEBUG_LIBRARIES`
+property on all targets as they are created.  It is also propagated by
+calls to the :command:`try_compile` command into its test project.
+
+If this variable is not set then the :prop_tgt:`VS_USE_DEBUG_LIBRARIES`
+property will not be set automatically.  If that property is not set
+then CMake does not generate any ``UseDebugLibraries`` indicator.

+ 1 - 0
Source/cmCoreTryCompile.cxx

@@ -1151,6 +1151,7 @@ cm::optional<cmTryCompileResult> cmCoreTryCompile::TryCompileCode(
     vars.emplace("CMAKE_WATCOM_RUNTIME_LIBRARY"_s);
     vars.emplace("CMAKE_MSVC_DEBUG_INFORMATION_FORMAT"_s);
     vars.emplace("CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS"_s);
+    vars.emplace("CMAKE_VS_USE_DEBUG_LIBRARIES"_s);
 
     if (cmValue varListStr = this->Makefile->GetDefinition(
           kCMAKE_TRY_COMPILE_PLATFORM_VARIABLES)) {

+ 1 - 0
Source/cmTarget.cxx

@@ -404,6 +404,7 @@ TargetProperty const StaticTargetProperties[] = {
   { "VS_DEBUGGER_COMMAND_ARGUMENTS"_s, IC::ExecutableTarget },
   { "VS_DEBUGGER_ENVIRONMENT"_s, IC::ExecutableTarget },
   { "VS_DEBUGGER_WORKING_DIRECTORY"_s, IC::ExecutableTarget },
+  { "VS_USE_DEBUG_LIBRARIES"_s, IC::NonImportedTarget },
   // ---- OpenWatcom
   { "WATCOM_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
   // -- Language

+ 19 - 0
Source/cmVisualStudio10TargetGenerator.cxx

@@ -1622,6 +1622,25 @@ void cmVisualStudio10TargetGenerator::WriteMSToolConfigurationValuesCommon(
   } else if (const char* toolset = gg->GetPlatformToolset()) {
     e1.Element("PlatformToolset", toolset);
   }
+
+  cm::optional<bool> maybeUseDebugLibraries;
+  if (cmValue useDebugLibrariesProp =
+        this->GeneratorTarget->GetProperty("VS_USE_DEBUG_LIBRARIES")) {
+    // The project explicitly specified a value for this target.
+    // An empty string suppresses generation of the setting altogether.
+    std::string const useDebugLibraries = cmGeneratorExpression::Evaluate(
+      *useDebugLibrariesProp, this->LocalGenerator, config);
+    if (!useDebugLibraries.empty()) {
+      maybeUseDebugLibraries = cmIsOn(useDebugLibraries);
+    }
+  }
+  if (maybeUseDebugLibraries) {
+    if (*maybeUseDebugLibraries) {
+      e1.Element("UseDebugLibraries", "true");
+    } else {
+      e1.Element("UseDebugLibraries", "false");
+    }
+  }
 }
 
 //----------------------------------------------------------------------------

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -731,6 +731,7 @@ if("${CMAKE_GENERATOR}" MATCHES "Visual Studio ([^9]|9[0-9])")
     -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
     -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION}
     )
+  add_RunCMake_test(VS10ProjectUseDebugLibraries)
   if( vs12 AND wince )
     add_RunCMake_test( VS10ProjectWinCE "-DRunCMake_GENERATOR_PLATFORM=${wince_sdk}")
   endif()

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

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

+ 8 - 0
Tests/RunCMake/VS10ProjectUseDebugLibraries/Default-check.cmake

@@ -0,0 +1,8 @@
+include(${CMAKE_CURRENT_LIST_DIR}/check-common.cmake)
+
+UseDebugLibraries_check(default "" "")
+UseDebugLibraries_check(defaultCLR "" "")
+UseDebugLibraries_check(defaultUtil "" "")
+UseDebugLibraries_check(defaultRTL "" "")
+UseDebugLibraries_check(ALL_BUILD "" "")
+UseDebugLibraries_check(ZERO_CHECK "" "")

+ 10 - 0
Tests/RunCMake/VS10ProjectUseDebugLibraries/Default.cmake

@@ -0,0 +1,10 @@
+set(CMAKE_CONFIGURATION_TYPES Debug Release)
+enable_language(CXX)
+
+# Test several generator code paths covering different target types.
+add_library(default empty.cxx)
+add_library(defaultCLR empty.cxx)
+set_property(TARGET defaultCLR PROPERTY COMMON_LANGUAGE_RUNTIME "")
+add_library(defaultRTL empty.cxx)
+set_property(TARGET defaultRTL PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
+add_custom_target(defaultUtil)

+ 10 - 0
Tests/RunCMake/VS10ProjectUseDebugLibraries/Explicit-check.cmake

@@ -0,0 +1,10 @@
+include(${CMAKE_CURRENT_LIST_DIR}/check-common.cmake)
+
+UseDebugLibraries_check(empty "" "")
+UseDebugLibraries_check(emptyCLR "" "")
+UseDebugLibraries_check(emptyUtil "" "")
+UseDebugLibraries_check(genex "true" "false")
+UseDebugLibraries_check(genexCLR "true" "false")
+UseDebugLibraries_check(genexUtil "true" "false")
+UseDebugLibraries_check(ALL_BUILD "false" "false")
+UseDebugLibraries_check(ZERO_CHECK "false" "false")

+ 20 - 0
Tests/RunCMake/VS10ProjectUseDebugLibraries/Explicit.cmake

@@ -0,0 +1,20 @@
+set(CMAKE_CONFIGURATION_TYPES Debug Release)
+enable_language(CXX)
+
+# An empty string suppresses generation of the setting.
+set(CMAKE_VS_USE_DEBUG_LIBRARIES "")
+add_library(empty empty.cxx)
+add_library(emptyCLR empty.cxx)
+set_property(TARGET emptyCLR PROPERTY COMMON_LANGUAGE_RUNTIME "")
+add_custom_target(emptyUtil)
+
+# A generator expression can encode per-config values.
+set(CMAKE_VS_USE_DEBUG_LIBRARIES "$<CONFIG:Debug>")
+add_library(genex empty.cxx)
+add_library(genexCLR empty.cxx)
+set_property(TARGET genexCLR PROPERTY COMMON_LANGUAGE_RUNTIME "")
+add_custom_target(genexUtil)
+
+# The last setting in the top-level directcory affects
+# the builtin targets like ALL_BUILD and ZERO_CHECK.
+set(CMAKE_VS_USE_DEBUG_LIBRARIES 0)

+ 5 - 0
Tests/RunCMake/VS10ProjectUseDebugLibraries/RunCMakeTest.cmake

@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.29)
+include(RunCMake)
+
+run_cmake(Default)
+run_cmake(Explicit)

+ 42 - 0
Tests/RunCMake/VS10ProjectUseDebugLibraries/check-common.cmake

@@ -0,0 +1,42 @@
+cmake_policy(SET CMP0140 NEW)
+function(UseDebugLibraries_check tgt udl_expect_debug udl_expect_release)
+  set(vcProjectFile "${RunCMake_TEST_BINARY_DIR}/${tgt}.vcxproj")
+  if(NOT EXISTS "${vcProjectFile}")
+    set(RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj does not exist.")
+    return()
+  endif()
+
+  set(have_udl_debug 0)
+  set(have_udl_release 0)
+  set(inConfig "")
+
+  file(STRINGS "${vcProjectFile}" lines)
+  foreach(line IN LISTS lines)
+    if(line MATCHES [[^ *<PropertyGroup Condition="'\$\(Configuration\)\|\$\(Platform\)'=='([^"|]+)\|[^"]+" Label="Configuration">.*$]])
+      string(TOLOWER "${CMAKE_MATCH_1}" inConfig)
+    elseif(inConfig)
+      if(line MATCHES "^ *</PropertyGroup>.*$")
+        set(inConfig "")
+      elseif(line MATCHES "^ *<UseDebugLibraries>([^<>]+)</UseDebugLibraries>")
+        set(udl_actual "${CMAKE_MATCH_1}")
+        set(have_udl_${inConfig} 1)
+        if (NOT "${udl_expect_${inConfig}}" STREQUAL "")
+          if(NOT "${udl_actual}" STREQUAL "${udl_expect_${inConfig}}")
+            string(APPEND RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj has ${inConfig} UseDebugLibraries '${udl_actual}', not '${udl_expect_${inConfig}}'.\n")
+          endif()
+        else()
+          string(APPEND RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj has ${inConfig} UseDebugLibraries '${udl_actual}', but should not have one.\n")
+        endif()
+        unset(udl_actual)
+      endif()
+    endif()
+  endforeach()
+
+  if(NOT have_udl_debug AND NOT "${udl_expect_debug}" STREQUAL "")
+    string(APPEND RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj does not have a debug UseDebugLibraries field, but should have one.\n")
+  endif()
+  if(NOT have_udl_release AND NOT "${udl_expect_release}" STREQUAL "")
+    string(APPEND RunCMake_TEST_FAILED "Project file ${tgt}.vcxproj does not have a release UseDebugLibraries field, but should have one.\n")
+  endif()
+  return(PROPAGATE RunCMake_TEST_FAILED)
+endfunction()

+ 0 - 0
Tests/RunCMake/VS10ProjectUseDebugLibraries/empty.cxx