summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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())