summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorJohn W. Parent <45471568+johnwparent@users.noreply.github.com>2023-08-30 19:19:38 -0400
committerGitHub <noreply@github.com>2023-08-30 23:19:38 +0000
commit1ee7c735ec98c3a810d0280a33bb05ca6bc79987 (patch)
tree2ca784e3729d68d4d633d0af9d8ae1d4aa07371b /lib
parent22deed708e8168a9fafee27380774e13273ec1b7 (diff)
downloadspack-1ee7c735ec98c3a810d0280a33bb05ca6bc79987.tar.gz
spack-1ee7c735ec98c3a810d0280a33bb05ca6bc79987.tar.bz2
spack-1ee7c735ec98c3a810d0280a33bb05ca6bc79987.tar.xz
spack-1ee7c735ec98c3a810d0280a33bb05ca6bc79987.zip
Windows: oneapi/msvc consistency (#39180)
Currently, OneAPI's setvars scripts effectively disregard any arguments we're passing to the MSVC vcvars env setup script, and additionally, completely ignore the requested version of OneAPI, defaulting to whatever the latest installed on the system is. This leads to a scenario where we have improperly constructed Windows native development environments, with potentially multiple versions of MSVC and OneAPI being loaded or called in the same env. Obviously this is far from ideal and leads to some fairly inscrutable errors such as overlapping header files between MSVC and OneAPI and a different version of OneAPI being called than the env was setup for. This PR solves this issue by creating a structured invocation of each relevant script in an order that ensures the correct values are set in the resultant build env. The order needs to be: 1. MSVC vcvarsall 2. The compiler specific env.bat script for the relevant version of the oneapi compiler we're looking for. The root setvars scripts seems to respect this as well, although it is less explicit 3. The root oneapi setvars script, which sets up everything else the oneapi env needs and seems to respect previous env invocations.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/compilers/msvc.py181
1 files changed, 145 insertions, 36 deletions
diff --git a/lib/spack/spack/compilers/msvc.py b/lib/spack/spack/compilers/msvc.py
index c9efa264ee..7e91e8eca2 100644
--- a/lib/spack/spack/compilers/msvc.py
+++ b/lib/spack/spack/compilers/msvc.py
@@ -29,6 +29,90 @@ fortran_mapping = {
}
+class CmdCall:
+ """Compose a call to `cmd` for an ordered series of cmd commands/scripts"""
+
+ def __init__(self, *cmds):
+ if not cmds:
+ raise RuntimeError(
+ """Attempting to run commands from CMD without specifying commands.
+ Please add commands to be run."""
+ )
+ self._cmds = cmds
+
+ def __call__(self):
+ out = subprocess.check_output(self.cmd_line, stderr=subprocess.STDOUT) # novermin
+ return out.decode("utf-16le", errors="replace") # novermin
+
+ @property
+ def cmd_line(self):
+ base_call = "cmd /u /c "
+ commands = " && ".join([x.command_str() for x in self._cmds])
+ # If multiple commands are being invoked by a single subshell
+ # they must be encapsulated by a double quote. Always double
+ # quote to be sure of proper handling
+ # cmd will properly resolve nested double quotes as needed
+ #
+ # `set`` writes out the active env to the subshell stdout,
+ # and in this context we are always trying to obtain env
+ # state so it should always be appended
+ return base_call + f'"{commands} && set"'
+
+
+class VarsInvocation:
+ def __init__(self, script):
+ self._script = script
+
+ def command_str(self):
+ return f'"{self._script}"'
+
+ @property
+ def script(self):
+ return self._script
+
+
+class VCVarsInvocation(VarsInvocation):
+ def __init__(self, script, arch, msvc_version):
+ super(VCVarsInvocation, self).__init__(script)
+ self._arch = arch
+ self._msvc_version = msvc_version
+
+ @property
+ def sdk_ver(self):
+ """Accessor for Windows SDK version property
+
+ Note: This property may not be set by
+ the calling context and as such this property will
+ return an empty string
+
+ This property will ONLY be set if the SDK package
+ is a dependency somewhere in the Spack DAG of the package
+ for which we are constructing an MSVC compiler env.
+ Otherwise this property should be unset to allow the VCVARS
+ script to use its internal heuristics to determine appropriate
+ SDK version
+ """
+ if getattr(self, "_sdk_ver", None):
+ return self._sdk_ver + ".0"
+ return ""
+
+ @sdk_ver.setter
+ def sdk_ver(self, val):
+ self._sdk_ver = val
+
+ @property
+ def arch(self):
+ return self._arch
+
+ @property
+ def vcvars_ver(self):
+ return f"-vcvars_ver={self._msvc_version}"
+
+ def command_str(self):
+ script = super(VCVarsInvocation, self).command_str()
+ return f"{script} {self.arch} {self.sdk_ver} {self.vcvars_ver}"
+
+
def get_valid_fortran_pth(comp_ver):
cl_ver = str(comp_ver)
sort_fn = lambda fc_ver: StrictVersion(fc_ver)
@@ -75,22 +159,48 @@ class Msvc(Compiler):
# file based on compiler executable path.
def __init__(self, *args, **kwargs):
- new_pth = [pth if pth else get_valid_fortran_pth(args[0].version) for pth in args[3]]
- args[3][:] = new_pth
+ # This positional argument "paths" is later parsed and process by the base class
+ # via the call to `super` later in this method
+ paths = args[3]
+ # This positional argument "cspec" is also parsed and handled by the base class
+ # constructor
+ cspec = args[0]
+ new_pth = [pth if pth else get_valid_fortran_pth(cspec.version) for pth in paths]
+ paths[:] = new_pth
super().__init__(*args, **kwargs)
- if os.getenv("ONEAPI_ROOT"):
+ # To use the MSVC compilers, VCVARS must be invoked
+ # VCVARS is located at a fixed location, referencable
+ # idiomatically by the following relative path from the
+ # compiler.
+ # Spack first finds the compilers via VSWHERE
+ # and stores their path, but their respective VCVARS
+ # file must be invoked before useage.
+ env_cmds = []
+ compiler_root = os.path.join(self.cc, "../../../../../../..")
+ vcvars_script_path = os.path.join(compiler_root, "Auxiliary", "Build", "vcvars64.bat")
+ # get current platform architecture and format for vcvars argument
+ arch = spack.platforms.real_host().default.lower()
+ arch = arch.replace("-", "_")
+ self.vcvars_call = VCVarsInvocation(vcvars_script_path, arch, self.msvc_version)
+ env_cmds.append(self.vcvars_call)
+ # Below is a check for a valid fortran path
+ # paths has c, cxx, fc, and f77 paths in that order
+ # paths[2] refers to the fc path and is a generic check
+ # for a fortran compiler
+ if paths[2]:
# If this found, it sets all the vars
- self.setvarsfile = os.path.join(os.getenv("ONEAPI_ROOT"), "setvars.bat")
- else:
- # To use the MSVC compilers, VCVARS must be invoked
- # VCVARS is located at a fixed location, referencable
- # idiomatically by the following relative path from the
- # compiler.
- # Spack first finds the compilers via VSWHERE
- # and stores their path, but their respective VCVARS
- # file must be invoked before useage.
- self.setvarsfile = os.path.abspath(os.path.join(self.cc, "../../../../../../.."))
- self.setvarsfile = os.path.join(self.setvarsfile, "Auxiliary", "Build", "vcvars64.bat")
+ oneapi_root = os.getenv("ONEAPI_ROOT")
+ oneapi_root_setvars = os.path.join(oneapi_root, "setvars.bat")
+ oneapi_version_setvars = os.path.join(
+ oneapi_root, "compiler", str(self.ifx_version), "env", "vars.bat"
+ )
+ # order matters here, the specific version env must be invoked first,
+ # otherwise it will be ignored if the root setvars sets up the oneapi
+ # env first
+ env_cmds.extend(
+ [VarsInvocation(oneapi_version_setvars), VarsInvocation(oneapi_root_setvars)]
+ )
+ self.msvc_compiler_environment = CmdCall(*env_cmds)
@property
def msvc_version(self):
@@ -119,17 +229,31 @@ class Msvc(Compiler):
"""
return self.msvc_version[:2].joined.string[:3]
- @property
- def cl_version(self):
- """Cl toolset version"""
+ def _compiler_version(self, compiler):
+ """Returns version object for given compiler"""
+ # ignore_errors below is true here due to ifx's
+ # non zero return code if it is not provided
+ # and input file
return Version(
re.search(
Msvc.version_regex,
- spack.compiler.get_compiler_version_output(self.cc, version_arg=None),
+ spack.compiler.get_compiler_version_output(
+ compiler, version_arg=None, ignore_errors=True
+ ),
).group(1)
)
@property
+ def cl_version(self):
+ """Cl toolset version"""
+ return self._compiler_version(self.cc)
+
+ @property
+ def ifx_version(self):
+ """Ifx compiler version associated with this version of MSVC"""
+ return self._compiler_version(self.fc)
+
+ @property
def vs_root(self):
# The MSVC install root is located at a fix level above the compiler
# and is referenceable idiomatically via the pattern below
@@ -146,27 +270,12 @@ class Msvc(Compiler):
# 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 or pkg.name == "win-sdk"
- 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, arch, sdk_ver, "-vcvars_ver=%s" % self.msvc_version
- ),
- stderr=subprocess.STDOUT,
- )
- if sys.version_info[0] >= 3:
- out = out.decode("utf-16le", errors="replace") # novermin
+ if pkg.name != "win-sdk" and "win-sdk" in pkg.spec:
+ self.vcvars_call.sdk_ver = pkg.spec["win-sdk"].version.string
+ out = self.msvc_compiler_environment()
int_env = dict(
(key, value)
for key, _, value in (line.partition("=") for line in out.splitlines())