Copy d3dcompiler_47 from WinSDK

Recently, Dawn was improved to load d3dcompiler_47 from absolute path
unless DAWN_FORCE_SYSTEM_COMPONENT_LOAD is false.

In GN builds, ANGLE places d3dcompiler_47.dll next to built
executables. Since no such placement code exists in Dawn nor ANGLE
for CMake, samples like HelloTriangle and ComputeBoids to fail to
load.

This CL refactors the CMake WinSDK detection code which copies
dxil.dll from the WinSDK to also copy d3dcompiler_47.dll from there
as well.

Bug: 441317244
Change-Id: I5ae2857b4251165e253b6138ea0848f48c0791ff
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/261314
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Rafael Cintron <rafael.cintron@microsoft.com>
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index ad8434e..21ef415 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -25,6 +25,9 @@
 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+# Include Windows SDK DLL copying utilities
+include("${CMAKE_CURRENT_SOURCE_DIR}/../../../third_party/CopyWindowsSDKDLL.cmake")
+
 DawnJSONGenerator(
     TARGET "native_utils"
     PRINT_NAME "Dawn native utilities"
@@ -1027,3 +1030,9 @@
 if (DAWN_USE_BUILT_DXC)
     target_link_libraries(dawn_native PRIVATE dxcompiler)
 endif()
+
+# Copy d3dcompiler_47.dll from Windows SDK when not using system component loading
+if (WIN32 AND NOT DAWN_FORCE_SYSTEM_COMPONENT_LOAD AND (DAWN_ENABLE_D3D11 OR DAWN_ENABLE_D3D12))
+    AddCopyWindowsSDKDLLTarget(copy_d3dcompiler_dll "d3dcompiler_47.dll")
+    add_dependencies(dawn_native copy_d3dcompiler_dll)
+endif()
diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt
index b263399..b423c43 100644
--- a/third_party/CMakeLists.txt
+++ b/third_party/CMakeLists.txt
@@ -204,6 +204,9 @@
     add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/google_benchmark/src EXCLUDE_FROM_ALL)
 endif()
 
+# Include Windows SDK DLL copying utilities
+include("${CMAKE_CURRENT_SOURCE_DIR}/CopyWindowsSDKDLL.cmake")
+
 # Populates 'targets' with list of all targets under 'dir'
 # Use 'get_all_targets_recursive' instead of this macro
 macro(do_get_all_targets_recursive targets dir)
@@ -337,50 +340,7 @@
 
     # Create a target that copies dxil.dll from the platform SDK
     if (WIN32)
-        message(STATUS "Finding Windows SDK Directory")
-
-        message(STATUS "Display environment variables:")
-        execute_process(COMMAND ${CMAKE_COMMAND} -E environment COMMAND_ECHO STDOUT)
-
-        if(DEFINED ENV{WINDOWSSDKDIR})
-            # If WINDOWSSDKDIR env var is defined, use its value. This is defined, for example, when
-            # using a Visual Studio command prompt.
-            set(WIN10_SDK_PATH "$ENV{WINDOWSSDKDIR}")
-            message(STATUS "Found WINDOWSSDKDIR environment variable: ${WIN10_SDK_PATH}")
-        else()
-            # There's no easy way to get the Windows SDK path in CMake; however, conveniently, DXC
-            # contains a FindD3D12.cmake file that returns WIN10_SDK_PATH and WIN10_SDK_VERSION,
-            # so let's use that.
-            # TODO(crbug.com/tint/2106): Get the Win10 SDK path and version ourselves until
-            # dxc/cmake/modules/FindD3D12.cmake supports non-VS generators.
-            get_filename_component(WIN10_SDK_PATH "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]" ABSOLUTE CACHE)
-            message(STATUS "WINDOWSSDKDIR environment variable is not defined, retrieving from registry: ${WIN10_SDK_PATH}")
-        endif()
-
-        message(STATUS "Finding Windows SDK version")
-        if (CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION)
-            set (WIN10_SDK_VERSION ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})
-            message(STATUS "Found Windows SDK version from CMake variable 'CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION': ${WIN10_SDK_VERSION}")
-        else()
-            # CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION may not be defined if, for example,
-            # the Ninja generator is used instead of Visual Studio. Attempt to retrieve the
-            # most recent SDK version from the list of paths under "${WIN10_SDK_PATH}/Include/".
-            file(GLOB sdk_dirs RELATIVE "${WIN10_SDK_PATH}/Include/" "${WIN10_SDK_PATH}/Include/10.*")
-            if (sdk_dirs)
-                list(POP_BACK sdk_dirs WIN10_SDK_VERSION)
-            endif()
-            unset(sdk_dirs)
-            message(STATUS "Windows SDK version not found from CMake variable 'CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION', attempted to find from SDK path: ${WIN10_SDK_VERSION}")
-        endif()
-
-        set(DXIL_DLL_PATH "${WIN10_SDK_PATH}/bin/${WIN10_SDK_VERSION}/x64/dxil.dll")
-        add_custom_target(copy_dxil_dll)
-        add_custom_command(
-            TARGET copy_dxil_dll
-            COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DXIL_DLL_PATH} $<TARGET_FILE_DIR:dxcompiler>
-            COMMENT "Copying ${DXIL_DLL_PATH} to $<TARGET_FILE_DIR:dxcompiler>")
-        # Ensure folder "$<TARGET_FILE_DIR:dxcompiler>" exists when copying the dll
-        add_dependencies(copy_dxil_dll dxcompiler)
+        AddCopyWindowsSDKDLLTarget("copy_dxil_dll" "dxil.dll")
         # Make dxc target depend on copy_dxil_dll
         add_dependencies(dxc copy_dxil_dll)
     endif()
diff --git a/third_party/CopyWindowsSDKDLL.cmake b/third_party/CopyWindowsSDKDLL.cmake
new file mode 100644
index 0000000..848d4d8
--- /dev/null
+++ b/third_party/CopyWindowsSDKDLL.cmake
@@ -0,0 +1,97 @@
+# Copyright 2025 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Function to detect Windows SDK path and version
+# Returns WIN10_SDK_PATH and WIN10_SDK_VERSION via output parameters
+function(DetectWindowsSDK out_sdk_path out_sdk_version)
+    message(STATUS "Finding Windows SDK Directory")
+
+    message(STATUS "Display environment variables:")
+    execute_process(COMMAND ${CMAKE_COMMAND} -E environment COMMAND_ECHO STDOUT)
+
+    if(DEFINED ENV{WINDOWSSDKDIR})
+        # If WINDOWSSDKDIR env var is defined, use its value. This is defined, for example, when
+        # using a Visual Studio command prompt.
+        set(sdk_path "$ENV{WINDOWSSDKDIR}")
+        message(STATUS "Found WINDOWSSDKDIR environment variable: $ENV{WINDOWSSDKDIR}")
+    else()
+        # There's no easy way to get the Windows SDK path in CMake; however, conveniently, DXC
+        # contains a FindD3D12.cmake file that returns WIN10_SDK_PATH and WIN10_SDK_VERSION,
+        # so let's use that.
+        # TODO(crbug.com/tint/2106): Get the Win10 SDK path and version ourselves until
+        # dxc/cmake/modules/FindD3D12.cmake supports non-VS generators.
+        get_filename_component(sdk_path "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]" ABSOLUTE CACHE)
+        message(STATUS "WINDOWSSDKDIR environment variable is not defined, retrieving from registry: ${sdk_path}")
+    endif()
+
+    message(STATUS "Finding Windows SDK version")
+    if (CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION)
+        set(sdk_version ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})
+        message(STATUS "Found Windows SDK version from CMake variable 'CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION': ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
+    else()
+        # CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION may not be defined if, for example,
+        # the Ninja generator is used instead of Visual Studio. Attempt to retrieve the
+        # most recent SDK version from the list of paths under "${WIN10_SDK_PATH}/Include/".
+        file(GLOB sdk_dirs RELATIVE "${sdk_path}/Include/" "${sdk_path}/Include/10.*")
+        if (sdk_dirs)
+            list(POP_BACK sdk_dirs sdk_version)
+        endif()
+        unset(sdk_dirs)
+        message(STATUS "Windows SDK version not found from CMake variable 'CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION', attempted to find from SDK path: ${sdk_version}")
+    endif()
+
+    # Return values via output parameters
+    set(${out_sdk_path} ${sdk_path} PARENT_SCOPE)
+    set(${out_sdk_version} ${sdk_version} PARENT_SCOPE)
+endfunction()
+
+# Function to add a target that copies a DLL from Windows SDK to main build directory
+# Parameters:
+#   - target_name: Name of the custom target to create
+#   - dll_name: Name of the DLL (e.g., "dxil.dll", "d3dcompiler_47.dll")
+function(AddCopyWindowsSDKDLLTarget target_name dll_name)
+    if (NOT WIN32)
+        message(FATAL_ERROR "AddCopyWindowsSDKDLLTarget can only be called on Windows")
+    endif()
+
+    DetectWindowsSDK(WIN10_SDK_PATH WIN10_SDK_VERSION)
+
+    set(DLL_PATH "${WIN10_SDK_PATH}/bin/${WIN10_SDK_VERSION}/x64/${dll_name}")
+
+    # Copy directly to main build directory
+    get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+    if (isMultiConfig)
+        set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/$<CONFIG>")
+    else()
+        set(OUTPUT_DIR "${CMAKE_BINARY_DIR}")
+    endif()
+    set(DEST_PATH "${OUTPUT_DIR}/${dll_name}")
+
+    add_custom_target(${target_name}
+        COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DLL_PATH} ${DEST_PATH}
+        COMMENT "Copying ${DLL_PATH} to ${DEST_PATH}")
+endfunction()