diff --git a/scripts/package-native-runtime.sh b/scripts/package-native-runtime.sh index 40d2f0a64a..31cb4acd92 100755 --- a/scripts/package-native-runtime.sh +++ b/scripts/package-native-runtime.sh @@ -458,6 +458,37 @@ if [[ "$runtime_os" == "windows" ]]; then for library in "${runtime_libraries[@]}"; do dependency_args+=(--search-dir "$(dirname "$library")") done + # The Vulkan SDK can contain an older MinGW runtime. Let the dependency + # resolver discover the compiler runtime before searching SDK directories. + # This also keeps CPU packages self-contained when their DLLs use MinGW. + if [[ "$BACKEND" == "cpu" || "$BACKEND" == "vulkan" ]]; then + mingw_compiler_spec="${MINGW_CXX:-${CXX:-}}" + if [[ -x "$mingw_compiler_spec" ]]; then + mingw_compiler="$mingw_compiler_spec" + elif [[ -n "$mingw_compiler_spec" ]]; then + # CXX may include a wrapper or compiler arguments. Resolve only + # the executable selected by the build instead of looking up the + # entire command string. + read -r mingw_compiler _ <<< "$mingw_compiler_spec" + else + mingw_compiler=g++ + fi + if [[ "$mingw_compiler" == */* ]]; then + if [[ -x "$mingw_compiler" ]]; then + mingw_compiler_path="$mingw_compiler" + else + mingw_compiler_path="" + fi + else + mingw_compiler_path="$(command -v "$mingw_compiler" || true)" + fi + if [[ -n "$mingw_compiler_path" ]]; then + dependency_args+=(--search-dir "$(dirname "$mingw_compiler_path")") + elif [[ -n "$mingw_compiler_spec" ]]; then + echo "configured MinGW C++ compiler was not found: $mingw_compiler_spec" >&2 + exit 1 + fi + fi for dependency_root in CUDA_PATH ROCM_PATH VULKAN_SDK; do dependency_dir="${!dependency_root:-}" if [[ -n "$dependency_dir" ]]; then diff --git a/scripts/tests/test_windows_native_runtime_deps.py b/scripts/tests/test_windows_native_runtime_deps.py index 637695945f..fcf3730a42 100644 --- a/scripts/tests/test_windows_native_runtime_deps.py +++ b/scripts/tests/test_windows_native_runtime_deps.py @@ -8,6 +8,7 @@ import subprocess import tempfile import unittest +from unittest import mock SCRIPT = pathlib.Path(__file__).parents[1] / "windows-native-runtime-deps.py" @@ -88,6 +89,38 @@ def test_collects_mingw_dependency_closure(self): ) DEPS.verify_dependencies(lib_dir) + def test_compiler_runtime_wins_over_vulkan_sdk_runtime(self): + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + lib_dir = root / "artifact" / "lib" + compiler_dir = root / "mingw" / "bin" + sdk_dir = root / "vulkan" / "Bin" + lib_dir.mkdir(parents=True) + compiler_dir.mkdir(parents=True) + sdk_dir.mkdir(parents=True) + write_pe(lib_dir / "ggml-vulkan.dll", ["libstdc++-6.dll"]) + write_pe(compiler_dir / "libstdc++-6.dll", ["KERNEL32.dll"]) + write_pe(sdk_dir / "libstdc++-6.dll", ["missing-sdk-runtime.dll"]) + + with mock.patch.object( + DEPS.shutil, "which", return_value=str(compiler_dir / "g++.exe") + ), mock.patch.dict( + DEPS.os.environ, + { + "PATH": str(sdk_dir), + "VULKAN_SDK": str(root / "vulkan"), + }, + clear=False, + ): + copied = DEPS.collect_dependencies(lib_dir, []) + + self.assertEqual([path.name for path in copied], ["libstdc++-6.dll"]) + self.assertEqual( + (lib_dir / "libstdc++-6.dll").read_bytes(), + (compiler_dir / "libstdc++-6.dll").read_bytes(), + ) + DEPS.verify_dependencies(lib_dir) + def test_verification_rejects_missing_non_system_dependency(self): with tempfile.TemporaryDirectory() as directory: lib_dir = pathlib.Path(directory) diff --git a/scripts/windows-native-runtime-deps.py b/scripts/windows-native-runtime-deps.py index cbf499b5b4..c33339aba6 100644 --- a/scripts/windows-native-runtime-deps.py +++ b/scripts/windows-native-runtime-deps.py @@ -136,16 +136,21 @@ def is_host_dll(name: str) -> bool: def default_search_dirs() -> list[pathlib.Path]: candidates: list[pathlib.Path] = [] - vulkan_sdk = os.environ.get("VULKAN_SDK") - if vulkan_sdk: - candidates.extend( - [pathlib.Path(vulkan_sdk) / "Bin", pathlib.Path(vulkan_sdk) / "Bin32"] - ) + for compiler in ("g++", "gcc"): + compiler_path = shutil.which(compiler) + if compiler_path: + candidates.append(pathlib.Path(compiler_path).parent) + break candidates.extend( pathlib.Path(entry) for entry in os.environ.get("PATH", "").split(os.pathsep) if entry ) + vulkan_sdk = os.environ.get("VULKAN_SDK") + if vulkan_sdk: + candidates.extend( + [pathlib.Path(vulkan_sdk) / "Bin", pathlib.Path(vulkan_sdk) / "Bin32"] + ) return candidates