summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn W. Parent <45471568+johnwparent@users.noreply.github.com>2022-11-22 03:27:42 -0500
committerGitHub <noreply@github.com>2022-11-22 00:27:42 -0800
commit793a7bc6a97d86b9b0a6da32c0e3694ddeaa6590 (patch)
treea999bd6e00e88fa91da3705cb7ef50e447f775f2
parent376afd631cc9217f137b63b7bb7f2f9275518cb6 (diff)
downloadspack-793a7bc6a97d86b9b0a6da32c0e3694ddeaa6590.tar.gz
spack-793a7bc6a97d86b9b0a6da32c0e3694ddeaa6590.tar.bz2
spack-793a7bc6a97d86b9b0a6da32c0e3694ddeaa6590.tar.xz
spack-793a7bc6a97d86b9b0a6da32c0e3694ddeaa6590.zip
Windows: add registry query and SDK/WDK packages (#33021)
* Add a WindowsRegistryView class, which can query for existing package installations on Windows. This is particularly important because some Windows packages (including those added here) do not allow two simultaneous installs, and this can be queried in order to provide a clear error message. * Consolidate external path detection logic for Windows into WindowsKitExternalPaths and WindowsCompilerExternalPaths objects. * Add external-only packages win-sdk and wgl * Add win-wdk (including external detection) which depends on win-sdk * Replace prior msmpi implementation with a source-based install (depends on win-wdk). This install can control the install destination (unlike the binary installation). * Update MSVC compiler to choose vcvars based on win-sdk dependency * Provide "msbuild" module-level variable to packages during build * When creating symlinks on Windows, need to explicitly specify when a symlink target is a directory * executables_in_path no-longer defaults to using PATH (this is now expected to be taken care of by the caller)
-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)
+ )