summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/llnl/util/symlink.py2
-rw-r--r--lib/spack/spack/build_environment.py1
-rw-r--r--lib/spack/spack/compilers/msvc.py43
-rw-r--r--lib/spack/spack/detection/common.py171
-rw-r--r--lib/spack/spack/detection/path.py94
-rwxr-xr-xlib/spack/spack/operating_systems/windows_os.py14
-rw-r--r--lib/spack/spack/test/cmd/external.py4
-rw-r--r--lib/spack/spack/util/windows_registry.py291
-rw-r--r--var/spack/repos/builtin/packages/msmpi/ifort_compat.patch34
-rw-r--r--var/spack/repos/builtin/packages/msmpi/package.py61
-rw-r--r--var/spack/repos/builtin/packages/perl/package.py2
-rw-r--r--var/spack/repos/builtin/packages/wgl/package.py96
-rw-r--r--var/spack/repos/builtin/packages/win-sdk/package.py90
-rw-r--r--var/spack/repos/builtin/packages/win-wdk/package.py147
14 files changed, 961 insertions, 89 deletions
diff --git a/lib/spack/llnl/util/symlink.py b/lib/spack/llnl/util/symlink.py
index 103c5b4c38..2b71441d4b 100644
--- a/lib/spack/llnl/util/symlink.py
+++ b/lib/spack/llnl/util/symlink.py
@@ -24,7 +24,7 @@ def symlink(real_path, link_path):
On Windows, use junctions if os.symlink fails.
"""
if not is_windows or _win32_can_symlink():
- os.symlink(real_path, link_path)
+ os.symlink(real_path, link_path, target_is_directory=os.path.isdir(real_path))
else:
try:
# Try to use junctions
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index 5b9120d2ff..262c2683b5 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -566,6 +566,7 @@ def _set_variables_for_single_module(pkg, module):
if sys.platform == "win32":
m.nmake = Executable("nmake")
+ m.msbuild = Executable("msbuild")
# Standard CMake arguments
m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg)
m.std_meson_args = spack.build_systems.meson.MesonBuilder.std_args(pkg)
diff --git a/lib/spack/spack/compilers/msvc.py b/lib/spack/spack/compilers/msvc.py
index 110ef8099e..c79647b0bc 100644
--- a/lib/spack/spack/compilers/msvc.py
+++ b/lib/spack/spack/compilers/msvc.py
@@ -6,13 +6,17 @@
import os
import re
import subprocess
+import sys
from distutils.version import StrictVersion
from typing import Dict, List, Set # novm
+import spack.compiler
import spack.operating_systems.windows_os
+import spack.platforms
import spack.util.executable
from spack.compiler import Compiler
from spack.error import SpackError
+from spack.version import Version
avail_fc_version = set() # type: Set[str]
fc_path = dict() # type: Dict[str, str]
@@ -38,10 +42,10 @@ def get_valid_fortran_pth(comp_ver):
class Msvc(Compiler):
# Subclasses use possible names of C compiler
- cc_names = ["cl.exe"]
+ cc_names = ["cl.exe"] # type: List[str]
# Subclasses use possible names of C++ compiler
- cxx_names = ["cl.exe"]
+ cxx_names = ["cl.exe"] # type: List[str]
# Subclasses use possible names of Fortran 77 compiler
f77_names = ["ifx.exe"] # type: List[str]
@@ -90,10 +94,25 @@ class Msvc(Compiler):
@property
def msvc_version(self):
- ver = re.search(Msvc.version_regex, self.cc).group(1)
- ver = "".join(ver.split(".")[:2])[:-1]
+ """This is the VCToolset version *NOT* the actual version of the cl compiler
+ For CL version, query `Msvc.cl_version`"""
+ return Version(re.search(Msvc.version_regex, self.cc).group(1))
+
+ @property
+ def short_msvc_version(self):
+ """
+ This is the shorthand VCToolset version of form
+ MSVC<short-ver> *NOT* the full version, for that see
+ Msvc.msvc_version
+ """
+ ver = self.msvc_version[:2].joined.string[:3]
return "MSVC" + ver
+ @property
+ def cl_version(self):
+ """Cl toolset version"""
+ return spack.compiler.get_compiler_version_output(self.cc)
+
def setup_custom_environment(self, pkg, env):
"""Set environment variables for MSVC using the
Microsoft-provided script."""
@@ -103,11 +122,23 @@ class Msvc(Compiler):
# once the process terminates. So go the long way around: examine
# output, sort into dictionary, use that to make the build
# environment.
+
+ # get current platform architecture and format for vcvars argument
+ arch = spack.platforms.real_host().default.lower()
+ arch = arch.replace("-", "_")
+ # vcvars can target specific sdk versions, force it to pick up concretized sdk
+ # version, if needed by spec
+ sdk_ver = "" if "win-sdk" not in pkg.spec else pkg.spec["win-sdk"].version.string + ".0"
+ # provide vcvars with msvc version selected by concretization,
+ # not whatever it happens to pick up on the system (highest available version)
out = subprocess.check_output( # novermin
- 'cmd /u /c "{}" {} && set'.format(self.setvarsfile, "amd64"),
+ 'cmd /u /c "{}" {} {} {} && set'.format(
+ self.setvarsfile, arch, sdk_ver, "-vcvars_ver=%s" % self.msvc_version
+ ),
stderr=subprocess.STDOUT,
)
- out = out.decode("utf-16le", errors="replace") # novermin
+ if sys.version_info[0] >= 3:
+ out = out.decode("utf-16le", errors="replace") # novermin
int_env = dict(
(key.lower(), value)
diff --git a/lib/spack/spack/detection/common.py b/lib/spack/spack/detection/common.py
index 8233f47eb8..4e5bf0efcc 100644
--- a/lib/spack/spack/detection/common.py
+++ b/lib/spack/spack/detection/common.py
@@ -14,6 +14,7 @@ The module also contains other functions that might be useful across different
detection mechanisms.
"""
import collections
+import glob
import itertools
import os
import os.path
@@ -23,8 +24,10 @@ import sys
import llnl.util.tty
import spack.config
+import spack.operating_systems.windows_os as winOs
import spack.spec
import spack.util.spack_yaml
+import spack.util.windows_registry
is_windows = sys.platform == "win32"
#: Information on a package that has been detected
@@ -104,6 +107,19 @@ def _spec_is_valid(spec):
return True
+def path_to_dict(search_paths):
+ """Return dictionary[fullpath]: basename from list of paths"""
+ path_to_lib = {}
+ # Reverse order of search directories so that a lib in the first
+ # entry overrides later entries
+ for search_path in reversed(search_paths):
+ for lib in os.listdir(search_path):
+ lib_path = os.path.join(search_path, lib)
+ if llnl.util.filesystem.is_readable_file(lib_path):
+ path_to_lib[lib_path] = lib
+ return path_to_lib
+
+
def is_executable(file_path):
"""Return True if the path passed as argument is that of an executable"""
return os.path.isfile(file_path) and os.access(file_path, os.X_OK)
@@ -139,9 +155,11 @@ def executable_prefix(executable_dir):
assert os.path.isdir(executable_dir)
components = executable_dir.split(os.sep)
- if "bin" not in components:
+ # convert to lower to match Bin, BIN, bin
+ lowered_components = executable_dir.lower().split(os.sep)
+ if "bin" not in lowered_components:
return executable_dir
- idx = components.index("bin")
+ idx = lowered_components.index("bin")
return os.sep.join(components[:idx])
@@ -158,11 +176,16 @@ def library_prefix(library_dir):
assert os.path.isdir(library_dir)
components = library_dir.split(os.sep)
- if "lib64" in components:
- idx = components.index("lib64")
+ # covert to lowercase to match lib, LIB, Lib, etc.
+ lowered_components = library_dir.lower().split(os.sep)
+ if "lib64" in lowered_components:
+ idx = lowered_components.index("lib64")
return os.sep.join(components[:idx])
- elif "lib" in components:
- idx = components.index("lib")
+ elif "lib" in lowered_components:
+ idx = lowered_components.index("lib")
+ return os.sep.join(components[:idx])
+ elif is_windows and "bin" in lowered_components:
+ idx = lowered_components.index("bin")
return os.sep.join(components[:idx])
else:
return library_dir
@@ -195,10 +218,117 @@ def update_configuration(detected_packages, scope=None, buildable=True):
return all_new_specs
+def _windows_drive():
+ """Return Windows drive string"""
+ return os.environ["HOMEDRIVE"]
+
+
+class WindowsCompilerExternalPaths(object):
+ @staticmethod
+ def find_windows_compiler_root_paths():
+ """Helper for Windows compiler installation root discovery
+
+ At the moment simply returns location of VS install paths from VSWhere
+ But should be extended to include more information as relevant"""
+ return list(winOs.WindowsOs.vs_install_paths)
+
+ @staticmethod
+ def find_windows_compiler_cmake_paths():
+ """Semi hard-coded search path for cmake bundled with MSVC"""
+ return [
+ os.path.join(
+ path, "Common7", "IDE", "CommonExtensions", "Microsoft", "CMake", "CMake", "bin"
+ )
+ for path in WindowsCompilerExternalPaths.find_windows_compiler_root_paths()
+ ]
+
+ @staticmethod
+ def find_windows_compiler_ninja_paths():
+ """Semi hard-coded search heuristic for locating ninja bundled with MSVC"""
+ return [
+ os.path.join(path, "Common7", "IDE", "CommonExtensions", "Microsoft", "CMake", "Ninja")
+ for path in WindowsCompilerExternalPaths.find_windows_compiler_root_paths()
+ ]
+
+ @staticmethod
+ def find_windows_compiler_bundled_packages():
+ """Return all MSVC compiler bundled packages"""
+ return (
+ WindowsCompilerExternalPaths.find_windows_compiler_cmake_paths()
+ + WindowsCompilerExternalPaths.find_windows_compiler_ninja_paths()
+ )
+
+
+class WindowsKitExternalPaths(object):
+ if is_windows:
+ plat_major_ver = str(winOs.windows_version()[0])
+
+ @staticmethod
+ def find_windows_kit_roots():
+ """Return Windows kit root, typically %programfiles%\\Windows Kits\\10|11\\"""
+ if not is_windows:
+ return []
+ program_files = os.environ["PROGRAMFILES(x86)"]
+ kit_base = os.path.join(
+ program_files, "Windows Kits", WindowsKitExternalPaths.plat_major_ver
+ )
+ return kit_base
+
+ @staticmethod
+ def find_windows_kit_bin_paths(kit_base=None):
+ """Returns Windows kit bin directory per version"""
+ kit_base = WindowsKitExternalPaths.find_windows_kit_roots() if not kit_base else kit_base
+ kit_bin = os.path.join(kit_base, "bin")
+ return glob.glob(os.path.join(kit_bin, "[0-9]*", "*\\"))
+
+ @staticmethod
+ def find_windows_kit_lib_paths(kit_base=None):
+ """Returns Windows kit lib directory per version"""
+ kit_base = WindowsKitExternalPaths.find_windows_kit_roots() if not kit_base else kit_base
+ kit_lib = os.path.join(kit_base, "Lib")
+ return glob.glob(os.path.join(kit_lib, "[0-9]*", "*", "*\\"))
+
+ @staticmethod
+ def find_windows_driver_development_kit_paths():
+ """Provides a list of all installation paths
+ for the WDK by version and architecture
+ """
+ wdk_content_root = os.getenv("WDKContentRoot")
+ return WindowsKitExternalPaths.find_windows_kit_lib_paths(wdk_content_root)
+
+ @staticmethod
+ def find_windows_kit_reg_installed_roots_paths():
+ reg = spack.util.windows_registry.WindowsRegistryView(
+ "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots",
+ root_key=spack.util.windows_registry.HKEY.HKEY_LOCAL_MACHINE,
+ )
+ if not reg:
+ # couldn't find key, return empty list
+ return []
+ return WindowsKitExternalPaths.find_windows_kit_lib_paths(
+ reg.get_value("KitsRoot%s" % WindowsKitExternalPaths.plat_major_ver).value
+ )
+
+ @staticmethod
+ def find_windows_kit_reg_sdk_paths():
+ reg = spack.util.windows_registry.WindowsRegistryView(
+ "SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v%s.0"
+ % WindowsKitExternalPaths.plat_major_ver,
+ root_key=spack.util.windows_registry.HKEY.HKEY_LOCAL_MACHINE,
+ )
+ if not reg:
+ # couldn't find key, return empty list
+ return []
+ return WindowsKitExternalPaths.find_windows_kit_lib_paths(
+ reg.get_value("InstallationFolder").value
+ )
+
+
def find_win32_additional_install_paths():
"""Not all programs on Windows live on the PATH
Return a list of other potential install locations.
"""
+ drive_letter = _windows_drive()
windows_search_ext = []
cuda_re = r"CUDA_PATH[a-zA-Z1-9_]*"
# The list below should be expanded with other
@@ -211,7 +341,7 @@ def find_win32_additional_install_paths():
# to interact with Windows
# Add search path for default Chocolatey (https://github.com/chocolatey/choco)
# install directory
- windows_search_ext.append("C:\\ProgramData\\chocolatey\\bin")
+ windows_search_ext.append("%s\\ProgramData\\chocolatey\\bin" % drive_letter)
# Add search path for NuGet package manager default install location
windows_search_ext.append(os.path.join(user, ".nuget", "packages"))
windows_search_ext.extend(
@@ -233,9 +363,32 @@ def compute_windows_program_path_for_package(pkg):
return []
# note windows paths are fine here as this method should only ever be invoked
# to interact with Windows
- program_files = "C:\\Program Files{}\\{}"
+ program_files = "{}\\Program Files{}\\{}"
+ drive_letter = _windows_drive()
return [
- program_files.format(arch, name)
+ program_files.format(drive_letter, arch, name)
for arch, name in itertools.product(("", " (x86)"), (pkg.name, pkg.name.capitalize()))
]
+
+
+def compute_windows_user_path_for_package(pkg):
+ """Given a package attempt to compute its user scoped
+ install location, return list of potential locations based
+ on common heuristics. For more info on Windows user specific
+ installs see:
+ https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder?view=netframework-4.8"""
+ if not is_windows:
+ return []
+
+ # Current user directory
+ user = os.environ["USERPROFILE"]
+ app_data = "AppData"
+ app_data_locations = ["Local", "Roaming"]
+ user_appdata_install_stubs = [os.path.join(app_data, x) for x in app_data_locations]
+ return [
+ os.path.join(user, app_data, name)
+ for app_data, name in list(
+ itertools.product(user_appdata_install_stubs, (pkg.name, pkg.name.capitalize()))
+ )
+ ] + [os.path.join(user, name) for name in (pkg.name, pkg.name.capitalize())]
diff --git a/lib/spack/spack/detection/path.py b/lib/spack/spack/detection/path.py
index bf42fa6144..ae297d86d2 100644
--- a/lib/spack/spack/detection/path.py
+++ b/lib/spack/spack/detection/path.py
@@ -15,22 +15,35 @@ import warnings
import llnl.util.filesystem
import llnl.util.tty
-import spack.operating_systems.windows_os as winOs
import spack.util.environment
import spack.util.ld_so_conf
-from .common import (
+from .common import ( # find_windows_compiler_bundled_packages,
DetectedPackage,
+ WindowsCompilerExternalPaths,
+ WindowsKitExternalPaths,
_convert_to_iterable,
compute_windows_program_path_for_package,
+ compute_windows_user_path_for_package,
executable_prefix,
find_win32_additional_install_paths,
- is_executable,
library_prefix,
+ path_to_dict,
)
+is_windows = sys.platform == "win32"
-def executables_in_path(path_hints=None):
+
+def common_windows_package_paths():
+ paths = WindowsCompilerExternalPaths.find_windows_compiler_bundled_packages()
+ paths.extend(find_win32_additional_install_paths())
+ paths.extend(WindowsKitExternalPaths.find_windows_kit_bin_paths())
+ paths.extend(WindowsKitExternalPaths.find_windows_kit_reg_installed_roots_paths())
+ paths.extend(WindowsKitExternalPaths.find_windows_kit_reg_sdk_paths())
+ return paths
+
+
+def executables_in_path(path_hints):
"""Get the paths of all executables available from the current PATH.
For convenience, this is constructed as a dictionary where the keys are
@@ -44,36 +57,10 @@ def executables_in_path(path_hints=None):
path_hints (list): list of paths to be searched. If None the list will be
constructed based on the PATH environment variable.
"""
- # If we're on a Windows box, run vswhere,
- # steal the installationPath using windows_os.py logic,
- # construct paths to CMake and Ninja, add to PATH
- path_hints = path_hints or spack.util.environment.get_path("PATH")
- if sys.platform == "win32":
- msvc_paths = list(winOs.WindowsOs.vs_install_paths)
- msvc_cmake_paths = [
- os.path.join(
- path, "Common7", "IDE", "CommonExtensions", "Microsoft", "CMake", "CMake", "bin"
- )
- for path in msvc_paths
- ]
- path_hints = msvc_cmake_paths + path_hints
- msvc_ninja_paths = [
- os.path.join(path, "Common7", "IDE", "CommonExtensions", "Microsoft", "CMake", "Ninja")
- for path in msvc_paths
- ]
- path_hints = msvc_ninja_paths + path_hints
- path_hints.extend(find_win32_additional_install_paths())
+ if is_windows:
+ path_hints.extend(common_windows_package_paths())
search_paths = llnl.util.filesystem.search_paths_for_executables(*path_hints)
-
- path_to_exe = {}
- # Reverse order of search directories so that an exe in the first PATH
- # entry overrides later entries
- for search_path in reversed(search_paths):
- for exe in os.listdir(search_path):
- exe_path = os.path.join(search_path, exe)
- if is_executable(exe_path):
- path_to_exe[exe_path] = exe
- return path_to_exe
+ return path_to_dict(search_paths)
def libraries_in_ld_and_system_library_path(path_hints=None):
@@ -102,16 +89,23 @@ def libraries_in_ld_and_system_library_path(path_hints=None):
+ spack.util.ld_so_conf.host_dynamic_linker_search_paths()
)
search_paths = llnl.util.filesystem.search_paths_for_libraries(*path_hints)
+ return path_to_dict(search_paths)
+
- path_to_lib = {}
- # Reverse order of search directories so that a lib in the first
- # LD_LIBRARY_PATH entry overrides later entries
- for search_path in reversed(search_paths):
- for lib in os.listdir(search_path):
- lib_path = os.path.join(search_path, lib)
- if llnl.util.filesystem.is_readable_file(lib_path):
- path_to_lib[lib_path] = lib
- return path_to_lib
+def libraries_in_windows_paths(path_hints):
+ path_hints.extend(spack.util.environment.get_path("PATH"))
+ search_paths = llnl.util.filesystem.search_paths_for_libraries(*path_hints)
+ # on Windows, some libraries (.dlls) are found in the bin directory or sometimes
+ # at the search root. Add both of those options to the search scheme
+ search_paths.extend(llnl.util.filesystem.search_paths_for_executables(*path_hints))
+ search_paths.extend(WindowsKitExternalPaths.find_windows_kit_lib_paths())
+ search_paths.extend(WindowsKitExternalPaths.find_windows_kit_bin_paths())
+ search_paths.extend(WindowsKitExternalPaths.find_windows_kit_reg_installed_roots_paths())
+ search_paths.extend(WindowsKitExternalPaths.find_windows_kit_reg_sdk_paths())
+ # SDK and WGL should be handled by above, however on occasion the WDK is in an atypical
+ # location, so we handle that case specifically.
+ search_paths.extend(WindowsKitExternalPaths.find_windows_driver_development_kit_paths())
+ return path_to_dict(search_paths)
def _group_by_prefix(paths):
@@ -141,12 +135,23 @@ def by_library(packages_to_check, path_hints=None):
DYLD_LIBRARY_PATH, DYLD_FALLBACK_LIBRARY_PATH environment variables
and standard system library paths.
"""
- path_to_lib_name = libraries_in_ld_and_system_library_path(path_hints=path_hints)
+ # If no path hints from command line, intialize to empty list so
+ # we can add default hints on a per package basis
+ path_hints = [] if path_hints is None else path_hints
+
lib_pattern_to_pkgs = collections.defaultdict(list)
for pkg in packages_to_check:
if hasattr(pkg, "libraries"):
for lib in pkg.libraries:
lib_pattern_to_pkgs[lib].append(pkg)
+ path_hints.extend(compute_windows_user_path_for_package(pkg))
+ path_hints.extend(compute_windows_program_path_for_package(pkg))
+
+ path_to_lib_name = (
+ libraries_in_ld_and_system_library_path(path_hints=path_hints)
+ if not is_windows
+ else libraries_in_windows_paths(path_hints)
+ )
pkg_to_found_libs = collections.defaultdict(set)
for lib_pattern, pkgs in lib_pattern_to_pkgs.items():
@@ -231,13 +236,14 @@ def by_executable(packages_to_check, path_hints=None):
path_hints (list): list of paths to be searched. If None the list will be
constructed based on the PATH environment variable.
"""
- path_hints = [] if path_hints is None else path_hints
+ path_hints = spack.util.environment.get_path("PATH") if path_hints is None else path_hints
exe_pattern_to_pkgs = collections.defaultdict(list)
for pkg in packages_to_check:
if hasattr(pkg, "executables"):
for exe in pkg.platform_executables():
exe_pattern_to_pkgs[exe].append(pkg)
# Add Windows specific, package related paths to the search paths
+ path_hints.extend(compute_windows_user_path_for_package(pkg))
path_hints.extend(compute_windows_program_path_for_package(pkg))
path_to_exe_name = executables_in_path(path_hints=path_hints)
diff --git a/lib/spack/spack/operating_systems/windows_os.py b/lib/spack/spack/operating_systems/windows_os.py
index ec563f5336..312a845d19 100755
--- a/lib/spack/spack/operating_systems/windows_os.py
+++ b/lib/spack/spack/operating_systems/windows_os.py
@@ -1,4 +1,4 @@
-# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
+# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
@@ -15,8 +15,12 @@ from ._operating_system import OperatingSystem
def windows_version():
- """temporary workaround to return a Windows version as a Version object"""
- return Version(platform.release())
+ """Windows version as a Version object"""
+ # include the build number as this provides important information
+ # for low lever packages and components like the SDK and WDK
+ # The build number is the version component that would otherwise
+ # be the patch version in sematic versioning, i.e. z of x.y.z
+ return Version(platform.version())
class WindowsOs(OperatingSystem):
@@ -65,8 +69,8 @@ class WindowsOs(OperatingSystem):
compiler_search_paths = comp_search_paths
def __init__(self):
- plat_ver = platform.release()
- if Version(plat_ver) < Version("10"):
+ plat_ver = windows_version()
+ if plat_ver < Version("10"):
raise SpackError("Spack is not supported on Windows versions older than 10")
super(WindowsOs, self).__init__("windows{}".format(plat_ver), plat_ver)
diff --git a/lib/spack/spack/test/cmd/external.py b/lib/spack/spack/test/cmd/external.py
index ec9923139c..1944a2e940 100644
--- a/lib/spack/spack/test/cmd/external.py
+++ b/lib/spack/spack/test/cmd/external.py
@@ -107,9 +107,7 @@ def test_find_external_update_config(mutable_config):
def test_get_executables(working_env, mock_executable):
cmake_path1 = mock_executable("cmake", output="echo cmake version 1.foo")
-
- os.environ["PATH"] = os.pathsep.join([os.path.dirname(cmake_path1)])
- path_to_exe = spack.detection.executables_in_path()
+ path_to_exe = spack.detection.executables_in_path([os.path.dirname(cmake_path1)])
cmake_exe = define_plat_exe("cmake")
assert path_to_exe[cmake_path1] == cmake_exe
diff --git a/lib/spack/spack/util/windows_registry.py b/lib/spack/spack/util/windows_registry.py
new file mode 100644
index 0000000000..bdf84b5b95
--- /dev/null
+++ b/lib/spack/spack/util/windows_registry.py
@@ -0,0 +1,291 @@
+# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+"""
+Utility module for dealing with Windows Registry.
+"""
+
+import os
+import sys
+from contextlib import contextmanager
+
+from llnl.util import tty
+
+is_windows = sys.platform == "win32"
+if is_windows:
+ import winreg
+
+
+class RegistryValue(object):
+ """
+ Class defining a Windows registry entry
+ """
+
+ def __init__(self, name, value, parent_key):
+ self.path = name
+ self.value = value
+ self.key = parent_key
+
+
+class RegistryKey(object):
+ """
+ Class wrapping a Windows registry key
+ """
+
+ def __init__(self, name, handle):
+ self.path = name
+ self.name = os.path.split(name)[-1]
+ self._handle = handle
+ self._keys = []
+ self._values = {}
+
+ @property
+ def values(self):
+ """Returns all subvalues of this key as RegistryValue objects in dictionary
+ of value name : RegistryValue object
+ """
+ self._gather_value_info()
+ return self._values
+
+ @property
+ def subkeys(self):
+ """Returns list of all subkeys of this key as RegistryKey objects"""
+ self._gather_subkey_info()
+ return self._keys
+
+ @property
+ def hkey(self):
+ return self._handle
+
+ def __str__(self):
+ return self.name
+
+ def _gather_subkey_info(self):
+ """Composes all subkeys into a list for access"""
+ if self._keys:
+ return
+ sub_keys, _, _ = winreg.QueryInfoKey(self.hkey)
+ for i in range(sub_keys):
+ sub_name = winreg.EnumKey(self.hkey, i)
+ sub_handle = winreg.OpenKeyEx(self.hkey, sub_name, access=winreg.KEY_READ)
+ self._keys.append(RegistryKey(os.path.join(self.path, sub_name), sub_handle))
+
+ def _gather_value_info(self):
+ """Compose all values for this key into a dict of form value name: RegistryValue Object"""
+ if self._values:
+ return
+ _, values, _ = winreg.QueryInfoKey(self.hkey)
+ for i in range(values):
+ value_name, value_data, _ = winreg.EnumValue(self.hkey, i)
+ self._values[value_name] = RegistryValue(value_name, value_data, self.hkey)
+
+ def get_subkey(self, sub_key):
+ """Returns subkey of name sub_key in a RegistryKey objects"""
+ return RegistryKey(
+ os.path.join(self.path, sub_key),
+ winreg.OpenKeyEx(self.hkey, sub_key, access=winreg.KEY_READ),
+ )
+
+ def get_value(self, val_name):
+ """Returns value associated with this key in RegistryValue object"""
+ return RegistryValue(val_name, winreg.QueryValueEx(self.hkey, val_name)[0], self.hkey)
+
+
+class _HKEY_CONSTANT(RegistryKey):
+ """Subclass of RegistryKey to represent the prebaked, always open registry HKEY constants"""
+
+ def __init__(self, hkey_constant):
+ hkey_name = hkey_constant
+ # This class is instantiated at module import time
+ # on non Windows platforms, winreg would not have been
+ # imported. For this reason we can't reference winreg yet,
+ # so handle is none for now to avoid invalid references to a module.
+ # _handle provides a workaround to prevent null references to self.handle
+ # when coupled with the handle property.
+ super(_HKEY_CONSTANT, self).__init__(hkey_name, None)
+
+ def _get_hkey(self, key):
+ return getattr(winreg, key)
+
+ @property
+ def hkey(self):
+ if not self._handle:
+ self._handle = self._get_hkey(self.path)
+ return self._handle
+
+
+class HKEY(object):
+ """
+ Predefined, open registry HKEYs
+ From the Microsoft docs:
+ An application must open a key before it can read data from the registry.
+ To open a key, an application must supply a handle to another key in
+ the registry that is already open. The system defines predefined keys
+ that are always open. Predefined keys help an application navigate in
+ the registry."""
+
+ HKEY_CLASSES_ROOT = _HKEY_CONSTANT("HKEY_CLASSES_ROOT")
+ HKEY_CURRENT_USER = _HKEY_CONSTANT("HKEY_CURRENT_USER")
+ HKEY_USERS = _HKEY_CONSTANT("HKEY_USERS")
+ HKEY_LOCAL_MACHINE = _HKEY_CONSTANT("HKEY_LOCAL_MACHINE")
+ HKEY_CURRENT_CONFIG = _HKEY_CONSTANT("HKEY_CURRENT_CONFIG")
+ HKEY_PERFORMANCE_DATA = _HKEY_CONSTANT("HKEY_PERFORMANCE_DATA")
+
+
+class WindowsRegistryView(object):
+ """
+ Interface to provide access, querying, and searching to Windows registry entries.
+ This class represents a single key entrypoint into the Windows registry
+ and provides an interface to this key's values, its subkeys, and those subkey's values.
+ This class cannot be used to move freely about the registry, only subkeys/values of
+ the root key used to instantiate this class.
+ """
+
+ def __init__(self, key, root_key=HKEY.HKEY_CURRENT_USER):
+ """Constructs a Windows Registry entrypoint to key provided
+ root_key should be an already open root key or an hkey constant if provided
+
+ Args:
+ key (str): registry key to provide root for registry key for this clas
+ root_key: Already open registry key or HKEY constant to provide access into
+ the Windows registry. Registry access requires an already open key
+ to get an entrypoint, the HKEY constants are always open, or an already
+ open key can be used instead.
+ """
+ if not is_windows:
+ raise RuntimeError(
+ "Cannot instantiate Windows Registry class on non Windows platforms"
+ )
+ self.key = key
+ self.root = root_key
+ self._reg = None
+
+ @contextmanager
+ def invalid_reg_ref_error_handler(self):
+ try:
+ yield
+ except FileNotFoundError as e:
+ if e.winerror == 2:
+ tty.debug("Key %s at position %s does not exist" % (self.key, str(self.root)))
+ else:
+ raise e
+
+ def __bool__(self):
+ return self.reg != -1
+
+ def _load_key(self):
+ try:
+ self._reg = RegistryKey(
+ os.path.join(str(self.root), self.key),
+ winreg.OpenKeyEx(self.root.hkey, self.key, access=winreg.KEY_READ),
+ )
+ except FileNotFoundError as e:
+ if e.winerror == 2:
+ self._reg = -1
+ tty.debug("Key %s at position %s does not exist" % (self.key, str(self.root)))
+ else:
+ raise e
+
+ def _valid_reg_check(self):
+ if self.reg == -1:
+ tty.debug("Cannot perform operation for nonexistent key %s" % self.key)
+ return False
+ return True
+
+ @property
+ def reg(self):
+ if not self._reg:
+ self._load_key()
+ return self._reg
+
+ def get_value(self, value_name):
+ """Return registry value corresponding to provided argument (if it exists)"""
+ if not self._valid_reg_check():
+ raise RegistryError("Cannot query value from invalid key %s" % self.key)
+ with self.invalid_reg_ref_error_handler():
+ return self.reg.get_value(value_name)
+
+ def get_subkey(self, subkey_name):
+ if not self._valid_reg_check():
+ raise RegistryError("Cannot query subkey from invalid key %s" % self.key)
+ with self.invalid_reg_ref_error_handler():
+ return self.reg.get_subkey(subkey_name)
+
+ def get_subkeys(self):
+ if not self._valid_reg_check():
+ raise RegistryError("Cannot query subkeys from invalid key %s" % self.key)
+ with self.invalid_reg_ref_error_handler():
+ return self.reg.subkeys
+
+ def get_values(self):
+ if not self._valid_reg_check():
+ raise RegistryError("Cannot query values from invalid key %s" % self.key)
+ with self.invalid_reg_ref_error_handler():
+ return self.reg.values
+
+ def _traverse_subkeys(self, stop_condition):
+ """Perform simple BFS of subkeys, returning the key
+ that successfully triggers the stop condition.
+ Args:
+ stop_condition: lambda or function pointer that takes a single argument
+ a key and returns a boolean value based on that key
+ Return:
+ the key if stop_condition is triggered, or None if not
+ """
+ if not self._valid_reg_check():
+ raise RegistryError("Cannot query values from invalid key %s" % self.key)
+ with self.invalid_reg_ref_error_handler():
+ queue = self.reg.subkeys
+ for key in queue:
+ if stop_condition(key):
+ return key
+ queue.extend(key.subkeys)
+ return None
+
+ def find_subkey(self, subkey_name, recursive=True):
+ """If non recursive, this method is the same as get subkey with error handling
+ Otherwise perform a BFS of subkeys until desired key is found
+ Returns None or RegistryKey object corresponding to requested key name
+
+ Args:
+ subkey_name (str): string representing subkey to be searched for
+ recursive (bool): optional argument, if True, subkey need not be a direct
+ sub key of this registry entry, and this method will
+ search all subkeys recursively.
+ Default is True
+ Return:
+ the desired subkey as a RegistryKey object, or none
+ """
+
+ if not recursive:
+ return self.get_subkey(subkey_name)
+
+ else:
+ return self._traverse_subkeys(lambda x: x.name == subkey_name)
+
+ def find_value(self, val_name, recursive=True):
+ """
+ If non recursive, return RegistryValue object corresponding to name
+
+ Args:
+ val_name (str): name of value desired from registry
+ recursive (bool): optional argument, if True, the registry is searched recursively
+ for the value of name val_name, else only the current key is searched
+ Return:
+ The desired registry value as a RegistryValue object if it exists, otherwise, None
+ """
+ if not recursive:
+ return self.get_value(val_name)
+
+ else:
+ key = self._traverse_subkeys(lambda x: val_name in x.values)
+ if not key:
+ return None
+ else:
+ return key.values[val_name]
+
+
+class RegistryError(RuntimeError):
+ """Runtime Error describing issue with invalid key access to Windows registry"""
diff --git a/var/spack/repos/builtin/packages/msmpi/ifort_compat.patch b/var/spack/repos/builtin/packages/msmpi/ifort_compat.patch
new file mode 100644
index 0000000000..54902d8f5f
--- /dev/null
+++ b/var/spack/repos/builtin/packages/msmpi/ifort_compat.patch
@@ -0,0 +1,34 @@
+diff --git a/src/mpi/msmpi/dll/msmpi.vcxproj b/src/mpi/msmpi/dll/msmpi.vcxproj
+index 255b9f5..cc4f096 100644
+--- a/src/mpi/msmpi/dll/msmpi.vcxproj
++++ b/src/mpi/msmpi/dll/msmpi.vcxproj
+@@ -57,6 +57,9 @@
+ $(OutDir)\..\mpi_debugger\mpi_debugger.lib;
+ $(CRT_Libs);
+ </AdditionalDependencies>
++ <AdditionalLibraryDirectories>
++ $(SPACK_IFORT)compiler\lib\intel64
++ </AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>.\msmpi.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+diff --git a/src/mpi/msmpi/fortran/lib/mpifort.vcxproj b/src/mpi/msmpi/fortran/lib/mpifort.vcxproj
+index 24bd29d..57d0292 100644
+--- a/src/mpi/msmpi/fortran/lib/mpifort.vcxproj
++++ b/src/mpi/msmpi/fortran/lib/mpifort.vcxproj
+@@ -8,12 +8,12 @@
+ </PropertyGroup>
+ <Target Name="CompileFortran" AfterTargets="ClCompile" Inputs="@(ForCompile)" Outputs="@(ForCompile->'$(O)%(FileName).obj')">
+ <PropertyGroup Condition="'$(BuildArchitecture)'=='i386'">
+- <Fort_Flags>-fno-underscoring -D_X86_=1 -Di386=1 -march=x86-64 -m32</Fort_Flags>
++ <Fort_Flags>/D _X86_=1 /D i386=1 -march=x86-64 -m32</Fort_Flags>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(BuildArchitecture)'=='amd64'">
+- <Fort_Flags>-fno-underscoring -D_WIN64 -D_AMD64_ -DAMD64</Fort_Flags>
++ <Fort_Flags>/D _WIN64=1 /D _AMD64_=1 /D AMD64=1</Fort_Flags>
+ </PropertyGroup>
+- <Exec Command="$(GFORTRAN_BIN)\gfortran.exe -I$(MPI_INC_ROOT) -c %(ForCompile.Identity) $(Fort_Flags) -o $(O)\%(ForCompile.FileName).obj" />
++ <Exec Command="$(IFORT_BIN)\ifort.exe /I$(MPI_INC_ROOT) /c %(ForCompile.Identity) $(Fort_Flags) /names:lowercase /assume:nounderscore /o $(O)\%(ForCompile.FileName).obj" />
+ <ItemGroup>
+ <Lib Condition="'$(ConfigurationType)'=='StaticLibrary'" Include="@(ForCompile->'$(O)\%(Filename).obj')" />
+ </ItemGroup>
diff --git a/var/spack/repos/builtin/packages/msmpi/package.py b/var/spack/repos/builtin/packages/msmpi/package.py
index 0e5584cf29..a2206d797f 100644
--- a/var/spack/repos/builtin/packages/msmpi/package.py
+++ b/var/spack/repos/builtin/packages/msmpi/package.py
@@ -3,40 +3,61 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+import platform
+import re
+
+from spack.build_systems.generic import GenericBuilder
from spack.package import *
class Msmpi(Package):
- """A Windows-specced build of MPICH provided directly by
- Microsoft Support Team
- """
+ """MSMPI is a Windows port of MPICH provided by the Windows team"""
- homepage = "https://www.microsoft.com/en-us/download/default.aspx"
- maintainers = ["jpopelar"]
+ homepage = "https://docs.microsoft.com/en-us/message-passing-interface/microsoft-mpi"
+ url = "https://github.com/microsoft/Microsoft-MPI/archive/refs/tags/v10.1.1.tar.gz"
+ git = "https://github.com/microsoft/Microsoft-MPI.git"
executable = ["mpiexec.exe"]
- version(
- "10.0",
- sha256="7dae13797627726f67fab9c1d251aec2df9ecd25939984645ec05748bdffd396",
- extension="exe",
- expand=False,
- )
+ version("10.1.1", sha256="63c7da941fc4ffb05a0f97bd54a67968c71f63389a0d162d3182eabba1beab3d")
+ version("10.0.0", sha256="cfb53cf53c3cf0d4935ab58be13f013a0f7ccb1189109a5b8eea0fcfdcaef8c1")
provides("mpi")
- conflicts("platform=linux")
- conflicts("platform=darwin")
- conflicts("platform=cray")
+ depends_on("win-wdk")
- def url_for_version(self, version):
- return "https://download.microsoft.com/download/A/E/0/AE002626-9D9D-448D-8197-1EA510E297CE/msmpisetup.exe"
+ patch("ifort_compat.patch")
- def determine_version(self, exe):
- output = Executable("mpiexec.exe")
+ @classmethod
+ def determine_version(cls, exe):
+ output = Executable(exe)()
ver_str = re.search("[Version ([0-9.]+)]", output)
return Version(ver_str.group(0)) if ver_str else None
+
+class GenericBuilder(GenericBuilder):
+ def setup_build_environment(self, env):
+ ifort_root = os.path.join(*self.compiler.fc.split(os.path.sep)[:-2])
+ env.set("SPACK_IFORT", ifort_root)
+
+ def is_64bit(self):
+ return platform.machine().endswith("64")
+
+ def build_command_line(self):
+ args = ["-noLogo"]
+ ifort_bin = self.compiler.fc
+ if not ifort_bin:
+ raise InstallError(
+ "Cannot install MSMPI without fortran"
+ "please select a compiler with fortran support."
+ )
+ args.append("/p:IFORT_BIN=%s" % os.path.dirname(ifort_bin))
+ args.append("/p:VCToolsVersion=%s" % self.compiler.msvc_version)
+ args.append("/p:WindowsTargetPlatformVersion=%s" % str(self.spec["wdk"].version))
+ args.append("/p:PlatformToolset=%s" % self.compiler.cc_version)
+ return args
+
def install(self, spec, prefix):
- installer = Executable("msmpisetup.exe")
- installer("-unattend")
+ with working_dir(self.stage.build_directory, create=True):
+ msbuild(*self.build_command_line())
diff --git a/var/spack/repos/builtin/packages/perl/package.py b/var/spack/repos/builtin/packages/perl/package.py
index 76ee382ef4..cca48bf2cf 100644
--- a/var/spack/repos/builtin/packages/perl/package.py
+++ b/var/spack/repos/builtin/packages/perl/package.py
@@ -229,7 +229,7 @@ class Perl(Package): # Perl doesn't use Autotools, it should subclass Package
def nmake_arguments(self):
args = []
if self.spec.satisfies("%msvc"):
- args.append("CCTYPE=%s" % self.compiler.msvc_version)
+ args.append("CCTYPE=%s" % self.compiler.short_msvc_version)
else:
raise RuntimeError("Perl unsupported for non MSVC compilers on Windows")
args.append("INST_TOP=%s" % self.prefix.replace("/", "\\"))
diff --git a/var/spack/repos/builtin/packages/wgl/package.py b/var/spack/repos/builtin/packages/wgl/package.py
new file mode 100644
index 0000000000..0c119b769e
--- /dev/null
+++ b/var/spack/repos/builtin/packages/wgl/package.py
@@ -0,0 +1,96 @@
+# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import os
+import re
+
+from spack.package import *
+
+
+class Wgl(Package):
+ """External WGl and Windows OpenGL emulation representation in Spack"""
+
+ homepage = "https://learn.microsoft.com/en-us/windows/win32/opengl/wgl-and-windows-reference"
+ has_code = False
+
+ # hard code the extension as shared lib
+ libraries = ["OpenGL32.Lib"]
+
+ # versions here are in no way related to actual WGL versions
+ # (there is only one on a system at a time)
+ # but instead reflects the Windows Kit version that a particular WGL library file is found in
+ # Windows Kits are intended to be more or less contained environments so this allows us to
+ # marshall our SDK and WDK to their respective WGLs. The purpose here is to better reflect
+ # the concept of an MS build toolchain version W.R.T. to MSVC
+ version("10.0.22621")
+ version("10.0.19041")
+ version("10.0.18362")
+ version("10.0.17763")
+ version("10.0.17134")
+ version("10.0.16299")
+ version("10.0.15063")
+ version("10.0.14393")
+ version("10.0.10586")
+ version("10.0.26639")
+
+ # As per https://github.com/spack/spack/pull/31748 this provisory version represents
+ # an arbitrary openGL version designed for maximum compatibility with calling packages
+ # this current version simply reflects the latest OpenGL vesion available at the time of
+ # package creation and is set in a way that all specs currently depending on GL are
+ # satisfied appropriately
+ provides("gl@4.6")
+
+ variant("plat", values=("x64", "x86", "arm", "arm64"), default="x64")
+
+ # WGL exists on all Windows systems post win 98, however the headers
+ # needed to use OpenGL are found in the SDK (GL/gl.h)
+ # Dep is needed to consolidate sdk version to locate header files for
+ # version of SDK being used
+ depends_on("win-sdk@10.0.19041", when="@10.0.19041")
+ depends_on("win-sdk@10.0.18362", when="@10.0.18362")
+ depends_on("win-sdk@10.0.17763", when="@10.0.17763")
+ depends_on("win-sdk@10.0.17134", when="@10.0.17134")
+ depends_on("win-sdk@10.0.16299", when="@10.0.16299")
+ depends_on("win-sdk@10.0.15063", when="@10.0.15063")
+ depends_on("win-sdk@10.0.14393", when="@10.0.14393")
+
+ # WGL has no meaning on other platforms, should not be able to spec
+ for plat in ["linux", "darwin", "cray"]:
+ conflicts("platform=%s" % plat)
+
+ @classmethod
+ def determine_version(cls, lib):
+ """Allow for WGL to be externally detectable"""
+ version_match_pat = re.compile(r"[0-9][0-9].[0-9]+.[0-9][0-9][0-9][0-9][0-9]")
+ ver_str = re.search(version_match_pat, lib)
+ return ver_str if not ver_str else Version(ver_str.group())
+
+ @classmethod
+ def determine_variants(cls, libs, ver_str):
+ """Allow for determination of toolchain arch for detected WGL"""
+ variants = []
+ for lib in libs:
+ base, lib_name = os.path.split(lib)
+ _, arch = os.path.split(base)
+ variants.append("plat=%s" % arch)
+ return variants
+
+ # As noted above, the headers neccesary to include
+ @property
+ def headers(self):
+ return find_headers("GL/gl.h", root=self.spec["win-sdk"].prefix.includes, recursive=True)
+
+ @property
+ def libs(self):
+ return find_libraries("opengl32", shared=False, root=self.prefix, recursive=True)
+
+ def install(self, spec, prefix):
+ raise RuntimeError(
+ "This package is not installable from Spack\
+ and should be installed on the system prior to Spack use.\
+ If not installed this package should be installed via\
+ the Visual Studio installer in order to use the \
+ MSVC compiler on Windows."
+ )
diff --git a/var/spack/repos/builtin/packages/win-sdk/package.py b/var/spack/repos/builtin/packages/win-sdk/package.py
new file mode 100644
index 0000000000..8ac2992205
--- /dev/null
+++ b/var/spack/repos/builtin/packages/win-sdk/package.py
@@ -0,0 +1,90 @@
+# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+
+import os
+import re
+
+from spack.package import *
+
+
+class WinSdk(Package):
+ """
+ Windows Desktop C++ development SDK
+ Spack packaged used to define search heuristics
+ to locate the SDK on a filesystem
+ """
+
+ homepage = "https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/"
+ has_code = False
+
+ # The sdk has many libraries and executables. Record one for detection purposes
+ libraries = ["rcdll.dll"]
+
+ version("10.0.22621")
+ version("10.0.19041")
+ version("10.0.18362")
+ version("10.0.17763")
+ version("10.0.17134")
+ version("10.0.16299")
+ version("10.0.15063")
+ version("10.0.14393")
+ version("10.0.10586")
+ version("10.0.26639")
+
+ variant("plat", values=("x64", "x86", "arm", "arm64"), default="x64")
+
+ # WinSDK versions depend on compatible compilers
+ # WDK versions do as well, but due to their one to one dep on the SDK
+ # we can ensure that requirment here
+ # WinSDK is very backwards compatible, however older
+ # MSVC editions may have problems with newer SDKs
+ conflicts("%msvc@:19.16.00000", when="@10.0.19041")
+ conflicts("%msvc@:19.16.00000", when="@10.0.18362")
+ conflicts("%msvc@:19.15.00000", when="@10.0.17763")
+ conflicts("%msvc@:19.14.00000", when="@10.0.17134")
+ conflicts("%msvc@:19.11.00000", when="@10.0.16299")
+ conflicts("%msvc@:19.10.00000", when="@10.0.15063")
+ conflicts("%msvc@:19.10.00000", when="@10.0.14393")
+ conflicts("%msvc@:19.00.00000", when="@10.0.10586")
+
+ # For now we don't support Windows development env
+ # on other platforms
+ for plat in ["linux", "darwin", "cray"]:
+ conflicts("platform=%s" % plat)
+
+ @classmethod
+ def determine_version(cls, lib):
+ """
+ WinSDK that we would like to
+ be discoverable externally by Spack.
+ """
+ # This version is found in the package's path
+ # not by calling an exe or a libraries name
+ version_match_pat = re.compile(r"[0-9][0-9].[0-9]+.[0-9][0-9][0-9][0-9][0-9]")
+ ver_str = re.search(version_match_pat, lib)
+ return ver_str if not ver_str else Version(ver_str.group())
+
+ @classmethod
+ def determine_variants(cls, libs, ver_str):
+ """Allow for determination of toolchain arch for detected WGL"""
+ variants = []
+ for lib in libs:
+ base, lib_name = os.path.split(lib)
+ _, arch = os.path.split(base)
+ variants.append("plat=%s" % arch)
+ return variants
+
+ def install(self, spec, prefix):
+ raise RuntimeError(
+ "This package is not installable from Spack\
+ and should be installed on the system prior to Spack use.\
+ If not installed this package should be installed via\
+ the Visual Studio installer in order to use the \
+ MSVC compiler on Windows."
+ "If absolutely neccesary this SDK can be installed directly from Microsoft\
+ but this approach is not recommended unless you know what you're doing \
+ or if you're on Windows 11 you have no choice for the moment."
+ )
diff --git a/var/spack/repos/builtin/packages/win-wdk/package.py b/var/spack/repos/builtin/packages/win-wdk/package.py
new file mode 100644
index 0000000000..652932ccf8
--- /dev/null
+++ b/var/spack/repos/builtin/packages/win-wdk/package.py
@@ -0,0 +1,147 @@
+# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+
+import glob
+import os
+import re
+
+import spack.util.windows_registry as winreg
+from spack.package import *
+
+
+class WinWdk(Package):
+ """
+ Windows Driver Kit development package
+ """
+
+ homepage = "https://learn.microsoft.com/en-us/windows-hardware/drivers/"
+
+ # The wdk has many libraries and executables. Record one for detection purposes
+ libraries = ["mmos.lib"]
+
+ version(
+ "10.0.19041",
+ sha256="5f4ea0c55af099f97cb569a927c3a290c211f17edcfc65009f5b9253b9827925",
+ url="https://go.microsoft.com/fwlink/?linkid=2128854",
+ expand=False,
+ )
+ version(
+ "10.0.18362",
+ sha256="c35057cb294096c63bbea093e5024a5fb4120103b20c13fa755c92f227b644e5",
+ url="https://go.microsoft.com/fwlink/?linkid=2085767",
+ expand=False,
+ )
+ version(
+ "10.0.17763",
+ sha256="e6e5a57bf0a58242363cd6ca4762f44739f19351efc06cad382cca944b097235",
+ url="https://go.microsoft.com/fwlink/?linkid=2026156",
+ expand=False,
+ )
+ version(
+ "10.0.17134",
+ sha256="48e636117bb7bfe66b1ade793cc8e885c42c880fadaee471782d31b5c4d13e9b",
+ url="https://go.microsoft.com/fwlink/?linkid=873060",
+ expand=False,
+ )
+ version(
+ "10.0.16299",
+ sha256="14efbcc849e5977417e962f1cd68357d21abf27393110b9d95983ad03fc22ef4",
+ url="https://go.microsoft.com/fwlink/p/?linkid=859232",
+ expand=False,
+ )
+ version(
+ "10.0.15063",
+ sha256="489b497111bc791d9021b3573bfd93086a28b598c7325ab255e81c6f5d80a820",
+ url="https://go.microsoft.com/fwlink/p/?LinkID=845980",
+ expand=False,
+ )
+ version(
+ "10.0.14393",
+ sha256="0bfb2ac9db446e0d98c29ef7341a8c8e8e7aa24bc72b00c5704a88b13f48b3cb",
+ url="https://go.microsoft.com/fwlink/p/?LinkId=526733",
+ expand=False,
+ )
+
+ variant("plat", values=("x64", "x86", "arm", "arm64"), default="x64")
+
+ # need one to one dep on SDK per https://github.com/MicrosoftDocs/windows-driver-docs/issues/1550
+ # additionally, the WDK needs to be paired with a version of the Windows SDK
+ # as per https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk#download-icon-step-2-install-windows-11-version-22h2-sdk
+ depends_on("win-sdk@10.0.19041", when="@10.0.19041")
+ depends_on("win-sdk@10.0.18362", when="@10.0.18362")
+ depends_on("win-sdk@10.0.17763", when="@10.0.17763")
+ depends_on("win-sdk@10.0.17134", when="@10.0.17134")
+ depends_on("win-sdk@10.0.16299", when="@10.0.16299")
+ depends_on("win-sdk@10.0.15063", when="@10.0.15063")
+ depends_on("win-sdk@10.0.14393", when="@10.0.14393")
+
+ for plat in ["linux", "darwin", "cray"]:
+ conflicts("platform=%s" % plat)
+
+ @classmethod
+ def determine_version(cls, lib):
+ """
+ WDK is a set of drivers that we would like to
+ be discoverable externally by Spack.
+ The lib does not provide the WDK
+ version so we derive from the lib path
+ """
+ version_match_pat = re.compile(r"[0-9][0-9].[0-9]+.[0-9][0-9][0-9][0-9][0-9]")
+ ver_str = re.search(version_match_pat, lib)
+ return ver_str if not ver_str else Version(ver_str.group())
+
+ @classmethod
+ def determine_variants(cls, libs, ver_str):
+ """Allow for determination of toolchain arch for detected WGL"""
+ variants = []
+ for lib in libs:
+ base, lib_name = os.path.split(lib)
+ _, arch = os.path.split(base)
+ variants.append("plat=%s" % arch)
+ return variants
+
+ def setup_dependent_environment(self):
+ # This points to all core build extensions needed to build
+ # drivers on Windows
+ os.environ["WDKContentRoot"] = self.prefix
+
+ @run_before("install")
+ def rename_downloaded_executable(self):
+ """WGL download is named by fetch based on name derived from Link redirection
+ This name is not properly formated so that Windows understands it as an executable
+ We rename so as to allow Windows to run the WGL installer"""
+ installer = glob.glob(os.path.join(self.stage.source_path, "linkid=**"))
+ if len(installer) > 1:
+ raise RuntimeError(
+ "Fetch has failed, unable to determine installer path from:\n%s"
+ % "\n".join(installer)
+ )
+ installer = installer[0]
+ os.rename(installer, os.path.join(self.stage.source_path, "wdksetup.exe"))
+
+ def install(self, spec, prefix):
+ install_args = ["/features", "+", "/quiet", "/installpath", self.prefix]
+ with working_dir(self.stage.source_path):
+ try:
+ Executable("wdksetup.exe")(*install_args)
+ except ProcessError as pe:
+ reg = winreg.WindowsRegistryView(
+ "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots",
+ root_key=spack.util.windows_registry.HKEY_LOCAL_MACHINE,
+ )
+ if not reg:
+ # No Kits are available, failure was genuine
+ raise pe
+ else:
+ versions = [str(subkey) for subkey in reg.get_subkeys()]
+ versions = ",".join(versions) if len(versions) > 1 else versions[0]
+ plural = "s" if len(versions) > 1 else ""
+ raise InstallError(
+ "Cannot install WDK version %s. "
+ "Version%s %s already present on system."
+ "Please run `spack external find win-wdk` to use the WDK"
+ % (self.version, plural, versions)
+ )