1392 lines
58 KiB
Python
1392 lines
58 KiB
Python
# Copyright 2013-2023 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 inspect
|
|
import os
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
import xml.etree.ElementTree as ElementTree
|
|
|
|
import llnl.util.tty as tty
|
|
from llnl.util.filesystem import (
|
|
HeaderList,
|
|
LibraryList,
|
|
ancestor,
|
|
filter_file,
|
|
find_headers,
|
|
find_libraries,
|
|
find_system_libraries,
|
|
install,
|
|
)
|
|
|
|
import spack.error
|
|
from spack.build_environment import dso_suffix
|
|
from spack.package_base import InstallError
|
|
from spack.util.environment import EnvironmentModifications
|
|
from spack.util.executable import Executable
|
|
from spack.util.prefix import Prefix
|
|
from spack.version import Version, ver
|
|
|
|
from .generic import Package
|
|
|
|
# A couple of utility functions that might be useful in general. If so, they
|
|
# should really be defined elsewhere, unless deemed heretical.
|
|
# (Or na"ive on my part).
|
|
|
|
|
|
def debug_print(msg, *args):
|
|
"""Prints a message (usu. a variable) and the callers' names for a couple
|
|
of stack frames.
|
|
"""
|
|
# https://docs.python.org/2/library/inspect.html#the-interpreter-stack
|
|
stack = inspect.stack()
|
|
_func_name = 3
|
|
tty.debug("%s.%s:\t%s" % (stack[2][_func_name], stack[1][_func_name], msg), *args)
|
|
|
|
|
|
def raise_lib_error(*args):
|
|
"""Bails out with an error message. Shows args after the first as one per
|
|
line, tab-indented, useful for long paths to line up and stand out.
|
|
"""
|
|
raise InstallError("\n\t".join(str(i) for i in args))
|
|
|
|
|
|
def _expand_fields(s):
|
|
"""[Experimental] Expand arch-related fields in a string, typically a
|
|
filename.
|
|
|
|
Supported fields and their typical expansions are::
|
|
|
|
{platform} linux, mac
|
|
{arch} intel64 (including on Mac)
|
|
{libarch} intel64, empty on Mac
|
|
{bits} 64
|
|
|
|
"""
|
|
# Python-native string formatting requires arg list counts to match the
|
|
# replacement field count; optional fields are far easier with regexes.
|
|
|
|
_bits = "64"
|
|
_arch = "intel64" # TBD: ia32
|
|
|
|
if "linux" in sys.platform: # NB: linux2 vs. linux
|
|
s = re.sub("{platform}", "linux", s)
|
|
s = re.sub("{libarch}", _arch, s)
|
|
elif "darwin" in sys.platform:
|
|
s = re.sub("{platform}", "mac", s)
|
|
s = re.sub("{libarch}", "", s) # no arch dirs are used (as of 2018)
|
|
# elif 'win' in sys.platform: # TBD
|
|
# s = re.sub('{platform}', 'windows', s)
|
|
|
|
s = re.sub("{arch}", _arch, s)
|
|
s = re.sub("{bits}", _bits, s)
|
|
return s
|
|
|
|
|
|
class IntelPackage(Package):
|
|
"""Specialized class for licensed Intel software.
|
|
|
|
This class provides two phases that can be overridden:
|
|
|
|
1. :py:meth:`~.IntelPackage.configure`
|
|
2. :py:meth:`~.IntelPackage.install`
|
|
|
|
They both have sensible defaults and for many packages the
|
|
only thing necessary will be to override setup_run_environment
|
|
to set the appropriate environment variables.
|
|
"""
|
|
|
|
#: This attribute is used in UI queries that need to know the build
|
|
#: system base class
|
|
build_system_class = "IntelPackage"
|
|
|
|
#: A dict that maps Spack version specs to release years, needed to infer
|
|
#: the installation directory layout for pre-2016 versions in the family of
|
|
#: Intel packages.
|
|
#
|
|
# Like any property, it can be overridden in client packages, should older
|
|
# versions ever be added there. The initial dict here contains the
|
|
# packages defined in Spack as of 2018-04. Keys could conceivably overlap
|
|
# but preferably should not - only the first key in hash traversal order
|
|
# that satisfies self.spec will be used.
|
|
version_years = {
|
|
# intel-daal is versioned 2016 and later, no divining is needed
|
|
"intel-ipp@9.0:9": 2016,
|
|
"intel-mkl@11.3.0:11.3": 2016,
|
|
"intel-mpi@5.1:5": 2016,
|
|
}
|
|
|
|
# Below is the list of possible values for setting auto dispatch functions
|
|
# for the Intel compilers. Using these allows for the building of fat
|
|
# binaries that will detect the CPU SIMD capabilities at run time and
|
|
# activate the appropriate extensions.
|
|
auto_dispatch_options = (
|
|
"COMMON-AVX512",
|
|
"MIC-AVX512",
|
|
"CORE-AVX512",
|
|
"CORE-AVX2",
|
|
"CORE-AVX-I",
|
|
"AVX",
|
|
"SSE4.2",
|
|
"SSE4.1",
|
|
"SSSE3",
|
|
"SSE3",
|
|
"SSE2",
|
|
)
|
|
|
|
@property
|
|
def license_required(self):
|
|
# The Intel libraries are provided without requiring a license as of
|
|
# version 2017.2. Trying to specify one anyway will fail. See:
|
|
# https://software.intel.com/en-us/articles/free-ipsxe-tools-and-libraries
|
|
return self._has_compilers or self.version < ver("2017.2")
|
|
|
|
#: Comment symbol used in the license.lic file
|
|
license_comment = "#"
|
|
|
|
#: Environment variables that Intel searches for a license file
|
|
license_vars = ["INTEL_LICENSE_FILE"]
|
|
|
|
#: URL providing information on how to acquire a license key
|
|
license_url = "https://software.intel.com/en-us/articles/intel-license-manager-faq"
|
|
|
|
#: Location where Intel searches for a license file
|
|
@property
|
|
def license_files(self):
|
|
dirs = ["Licenses"]
|
|
|
|
if self._has_compilers:
|
|
dirs.append(self.component_bin_dir("compiler"))
|
|
|
|
for variant, component_suite_dir in {
|
|
"+advisor": "advisor",
|
|
"+inspector": "inspector",
|
|
"+itac": "itac",
|
|
"+vtune": "vtune_profiler",
|
|
}.items():
|
|
if variant in self.spec:
|
|
dirs.append(
|
|
self.normalize_path("licenses", component_suite_dir, relative=True)
|
|
)
|
|
|
|
files = [os.path.join(d, "license.lic") for d in dirs]
|
|
return files
|
|
|
|
#: Components to install (list of name patterns from pset/mediaconfig.xml)
|
|
# NB: Renamed from plain components() for coding and maintainability.
|
|
@property
|
|
def pset_components(self):
|
|
# Do not detail single-purpose client packages.
|
|
if not self._has_compilers:
|
|
return ["ALL"]
|
|
|
|
# tty.warn('DEBUG: installing ALL components')
|
|
# return ['ALL']
|
|
|
|
# Always include compilers and closely related components.
|
|
# Pre-2016 compiler components have different names - throw in all.
|
|
# Later releases have overlapping minor parts that differ by "edition".
|
|
# NB: The spack package 'intel' is a subset of
|
|
# 'intel-parallel-studio@composer' without the lib variants.
|
|
c = (
|
|
" intel-icc intel-ifort"
|
|
" intel-ccomp intel-fcomp intel-comp-"
|
|
" intel-compilerproc intel-compilerprof intel-compilerpro-"
|
|
" intel-psxe intel-openmp"
|
|
)
|
|
|
|
additions_for = {
|
|
"cluster": " intel-icsxe",
|
|
"professional": " intel-ips-",
|
|
"composer": " intel-compxe",
|
|
}
|
|
if self._edition in additions_for:
|
|
c += additions_for[self._edition]
|
|
|
|
for variant, components_to_add in {
|
|
"+daal": " intel-daal", # Data Analytics Acceleration Lib
|
|
"+gdb": " intel-gdb", # Integrated Performance Primitives
|
|
"+ipp": " intel-ipp intel-crypto-ipp",
|
|
"+mkl": " intel-mkl", # Math Kernel Library
|
|
"+mpi": " intel-mpi intel-imb", # MPI runtime, SDK, benchm.
|
|
"+tbb": " intel-tbb", # Threading Building Blocks
|
|
"+advisor": " intel-advisor",
|
|
"+clck": " intel_clck", # Cluster Checker
|
|
"+inspector": " intel-inspector",
|
|
"+itac": " intel-itac intel-ta intel-tc" " intel-trace-analyzer intel-trace-collector",
|
|
# Trace Analyzer and Collector
|
|
"+vtune": " intel-vtune"
|
|
# VTune, ..-profiler since 2020, ..-amplifier before
|
|
}.items():
|
|
if variant in self.spec:
|
|
c += components_to_add
|
|
|
|
debug_print(c)
|
|
return c.split()
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Utilities
|
|
# ---------------------------------------------------------------------
|
|
@property
|
|
def _filtered_components(self):
|
|
"""Expands the list of desired component patterns to the exact names
|
|
present in the given download.
|
|
"""
|
|
c = self.pset_components
|
|
if "ALL" in c or "DEFAULTS" in c: # No filter needed
|
|
return c
|
|
|
|
# mediaconfig.xml is known to contain duplicate components.
|
|
# If more than one copy of the same component is used, you
|
|
# will get an error message about invalid components.
|
|
# Use sets to prevent duplicates and for efficient traversal.
|
|
requested = set(c)
|
|
confirmed = set()
|
|
|
|
# NB: To get a reasonable overview in pretty much the documented way:
|
|
#
|
|
# grep -E '<Product|<Abbr|<Name>..[a-z]' pset/mediaconfig.xml
|
|
#
|
|
# https://software.intel.com/en-us/articles/configuration-file-format
|
|
#
|
|
xmltree = ElementTree.parse("pset/mediaconfig.xml")
|
|
for entry in xmltree.getroot().findall(".//Abbr"): # XPath expression
|
|
name_present = entry.text
|
|
for name_requested in requested:
|
|
if name_present.startswith(name_requested):
|
|
confirmed.add(name_present)
|
|
|
|
return list(confirmed)
|
|
|
|
@property
|
|
def intel64_int_suffix(self):
|
|
"""Provide the suffix for Intel library names to match a client
|
|
application's desired int size, conveyed by the active spec variant.
|
|
The possible suffixes and their meanings are:
|
|
|
|
``ilp64`` all of int, long, and pointer are 64 bit,
|
|
`` lp64`` only long and pointer are 64 bit; int will be 32bit.
|
|
"""
|
|
if "+ilp64" in self.spec:
|
|
return "ilp64"
|
|
else:
|
|
return "lp64"
|
|
|
|
@property
|
|
def _has_compilers(self):
|
|
return self.name in ["intel", "intel-parallel-studio"]
|
|
|
|
@property
|
|
def _edition(self):
|
|
if self.name == "intel-parallel-studio":
|
|
return self.version[0] # clearer than .up_to(1), I think.
|
|
elif self.name == "intel":
|
|
return "composer"
|
|
else:
|
|
return ""
|
|
|
|
@property
|
|
def version_yearlike(self):
|
|
"""Return the version in a unified style, suitable for Version class
|
|
conditionals.
|
|
"""
|
|
# Input data for this routine: self.version
|
|
# Returns: YYYY.Nupdate[.Buildseq]
|
|
#
|
|
# Specifics by package:
|
|
#
|
|
# Package Format of self.version
|
|
# ------------------------------------------------------------
|
|
# 'intel-parallel-studio' <edition>.YYYY.Nupdate
|
|
# 'intel' YY.0.Nupdate (some assigned ad-hoc)
|
|
# Recent lib packages YYYY.Nupdate.Buildseq
|
|
# Early lib packages Major.Minor.Patch.Buildseq
|
|
# ------------------------------------------------------------
|
|
#
|
|
# Package Output
|
|
# ------------------------------------------------------------
|
|
# 'intel-parallel-studio' YYYY.Nupdate
|
|
# 'intel' YYYY.Nupdate
|
|
# Recent lib packages YYYY.Nupdate.Buildseq
|
|
# Known early lib packages YYYY.Minor.Patch.Buildseq (*)
|
|
# Unknown early lib packages (2000 + Major).Minor.Patch.Buildseq
|
|
# ----------------------------------------------------------------
|
|
#
|
|
# (*) YYYY is taken from @property "version_years" (a dict of specs)
|
|
#
|
|
try:
|
|
if self.name == "intel":
|
|
# Has a "Minor" version element, but it is always set as 0. To
|
|
# be useful for comparisons, drop it and get YYYY.Nupdate.
|
|
v_tail = self.version[2:] # coerced just fine via __getitem__
|
|
else:
|
|
v_tail = self.version[1:]
|
|
except IndexError:
|
|
# Hmm - this happens on "spack install intel-mkl@11".
|
|
# I thought concretization picks an actual version??
|
|
return self.version # give up
|
|
|
|
if self.name == "intel-parallel-studio":
|
|
return v_tail
|
|
|
|
v_year = self.version[0]
|
|
if v_year < 2000:
|
|
# Shoehorn Major into release year until we know better.
|
|
v_year += 2000
|
|
for spec, year in self.version_years.items():
|
|
if self.spec.satisfies(spec):
|
|
v_year = year
|
|
break
|
|
|
|
return ver("%s.%s" % (v_year, v_tail))
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Directory handling common to all Intel components
|
|
# ---------------------------------------------------------------------
|
|
# For reference: classes using IntelPackage, as of Spack-0.11:
|
|
#
|
|
# intel/ intel-ipp/ intel-mpi/
|
|
# intel-daal/ intel-mkl/ intel-parallel-studio/
|
|
#
|
|
# Not using class IntelPackage:
|
|
# intel-gpu-tools/ intel-mkl-dnn/ intel-tbb/
|
|
#
|
|
def normalize_suite_dir(self, suite_dir_name, version_globs=["*.*.*"]):
|
|
"""Returns the version-specific and absolute path to the directory of
|
|
an Intel product or a suite of product components.
|
|
|
|
Parameters:
|
|
|
|
suite_dir_name (str):
|
|
Name of the product directory, without numeric version.
|
|
|
|
- Examples::
|
|
|
|
composer_xe, parallel_studio_xe, compilers_and_libraries
|
|
|
|
The following will work as well, even though they are not
|
|
directly targets for Spack installation::
|
|
|
|
advisor_xe, inspector_xe, vtune_amplifier_xe,
|
|
performance_snapshots (new name for vtune as of 2018)
|
|
|
|
These are single-component products without subordinate
|
|
components and are normally made available to users by a
|
|
toplevel psxevars.sh or equivalent file to source (and thus by
|
|
the modulefiles that Spack produces).
|
|
|
|
version_globs (list): Suffix glob patterns (most specific
|
|
first) expected to qualify suite_dir_name to its fully
|
|
version-specific install directory (as opposed to a
|
|
compatibility directory or symlink).
|
|
"""
|
|
# See ./README-intel.rst for background and analysis of dir layouts.
|
|
|
|
d = self.prefix
|
|
|
|
# Distinguish between product installations that were done external to
|
|
# Spack (integrated via packages.yaml) and Spack-internal ones. The
|
|
# resulting prefixes may differ in directory depth and specificity.
|
|
unversioned_dirname = ""
|
|
if suite_dir_name and suite_dir_name in d:
|
|
# If e.g. MKL was installed outside of Spack, it is likely just one
|
|
# product or product component among possibly many other Intel
|
|
# products and their releases that were installed in sibling or
|
|
# cousin directories. In such cases, the prefix given to Spack
|
|
# will inevitably be a highly product-specific and preferably fully
|
|
# version-specific directory. This is what we want and need, and
|
|
# nothing more specific than that, i.e., if needed, convert, e.g.:
|
|
# .../compilers_and_libraries*/* -> .../compilers_and_libraries*
|
|
d = re.sub("(%s%s.*?)%s.*" % (os.sep, re.escape(suite_dir_name), os.sep), r"\1", d)
|
|
|
|
# The Intel installer scripts try hard to place compatibility links
|
|
# named like this in the install dir to convey upgrade benefits to
|
|
# traditional client apps. But such a generic name can be trouble
|
|
# when given to Spack: the link target is bound to change outside
|
|
# of Spack's purview and when it does, the outcome of subsequent
|
|
# builds of dependent packages may be affected. (Though Intel has
|
|
# been remarkably good at backward compatibility.)
|
|
# I'm not sure if Spack's package hashing includes link targets.
|
|
if d.endswith(suite_dir_name):
|
|
# NB: This could get tiresome without a seen++ test.
|
|
# tty.warn('Intel product found in a version-neutral directory'
|
|
# ' - future builds may not be reproducible.')
|
|
#
|
|
# Simply doing realpath() would not be enough, because:
|
|
# compilers_and_libraries -> compilers_and_libraries_2018
|
|
# which is mostly a staging directory for symlinks (see next).
|
|
unversioned_dirname = d
|
|
else:
|
|
# By contrast, a Spack-internal MKL installation will inherit its
|
|
# prefix from install.sh of Intel's package distribution, where it
|
|
# means the high-level installation directory that is specific to
|
|
# the *vendor* (think of the default "/opt/intel"). We must now
|
|
# step down into the *product* directory to get the usual
|
|
# hierarchy. But let's not do that in haste ...
|
|
#
|
|
# For a Spack-born install, the fully-qualified release directory
|
|
# desired above may seem less important since product upgrades
|
|
# won't land in the same parent. However, only the fully qualified
|
|
# directory contains the regular files for the compiler commands:
|
|
#
|
|
# $ ls -lF <HASH>/compilers_and_libraries*/linux/bin/intel64/icc
|
|
#
|
|
# <HASH>/compilers_and_libraries_2018.1.163/linux/bin/intel64/icc*
|
|
# A regular file in the actual release directory. Bingo!
|
|
#
|
|
# <HASH>/compilers_and_libraries_2018/linux/bin/intel64/icc -> ...
|
|
# A symlink - no good. Note that "compilers_and_libraries_2018/"
|
|
# is itself a directory (not symlink) but it merely holds a
|
|
# compatibility dir hierarchy with lots of symlinks into the
|
|
# release dir.
|
|
#
|
|
# <HASH>/compilers_and_libraries/linux/bin/intel64/icc -> ...
|
|
# Ditto.
|
|
#
|
|
# Now, the Spack packages for MKL and MPI packges use version
|
|
# triplets, but the one for intel-parallel-studio does not.
|
|
# So, we can't have it quite as easy as:
|
|
# d = Prefix(d.append('compilers_and_libraries_' + self.version))
|
|
# Alright, let's see what we can find instead:
|
|
unversioned_dirname = os.path.join(d, suite_dir_name)
|
|
|
|
if unversioned_dirname:
|
|
for g in version_globs:
|
|
try_glob = unversioned_dirname + g
|
|
debug_print("trying %s" % try_glob)
|
|
|
|
matching_dirs = sorted(glob.glob(try_glob))
|
|
# NB: Python glob() returns results in arbitrary order - ugh!
|
|
# NB2: sorted() is a shortcut that is NOT number-aware.
|
|
|
|
if matching_dirs:
|
|
debug_print("found %d:" % len(matching_dirs), matching_dirs)
|
|
# Take the highest and thus presumably newest match, which
|
|
# better be the sole one anyway.
|
|
d = matching_dirs[-1]
|
|
break
|
|
|
|
if not matching_dirs:
|
|
# No match -- return a sensible value anyway.
|
|
d = unversioned_dirname
|
|
|
|
debug_print(d)
|
|
return Prefix(d)
|
|
|
|
def normalize_path(self, component_path, component_suite_dir=None, relative=False):
|
|
"""Returns the absolute or relative path to a component or file under a
|
|
component suite directory.
|
|
|
|
Intel's product names, scope, and directory layout changed over the
|
|
years. This function provides a unified interface to their directory
|
|
names.
|
|
|
|
Parameters:
|
|
|
|
component_path (str): a component name like 'mkl', or 'mpi', or a
|
|
deeper relative path.
|
|
|
|
component_suite_dir (str): _Unversioned_ name of the expected
|
|
parent directory of component_path. When absent or `None`, an
|
|
appropriate default will be used. A present but empty string
|
|
`""` requests that `component_path` refer to `self.prefix`
|
|
directly.
|
|
|
|
Typical values: `compilers_and_libraries`, `composer_xe`,
|
|
`parallel_studio_xe`.
|
|
|
|
Also supported: `advisor`, `inspector`, `vtune`. The actual
|
|
directory name for these suites varies by release year. The
|
|
name will be corrected as needed for use in the return value.
|
|
|
|
relative (bool): When True, return path relative to self.prefix,
|
|
otherwise, return an absolute path (the default).
|
|
"""
|
|
# Design note: Choosing the default for `component_suite_dir` was a bit
|
|
# tricky since there better be a sensible means to specify direct
|
|
# parentage under self.prefix (even though you normally shouldn't need
|
|
# a function for that). I chose "" to allow that case be represented,
|
|
# and 'None' or the absence of the kwarg to represent the most relevant
|
|
# case for the time of writing.
|
|
#
|
|
# In the 2015 releases (the earliest in Spack as of 2018), there were
|
|
# nominally two separate products that provided the compilers:
|
|
# "Composer" as lower tier, and "Parallel Studio" as upper tier. In
|
|
# Spack, we justifiably retcon both as "intel-parallel-studio@composer"
|
|
# and "...@cluster", respectively. Both of these use the older
|
|
# "composer_xe" dir layout, as do their virtual package personas.
|
|
#
|
|
# All other "intel-foo" packages in Spack as of 2018-04 use the
|
|
# "compilers_and_libraries" layout, including the 2016 releases that
|
|
# are not natively versioned by year.
|
|
|
|
cs = component_suite_dir
|
|
if cs is None and component_path.startswith("ism"):
|
|
cs = "parallel_studio_xe"
|
|
|
|
v = self.version_yearlike
|
|
|
|
# Glob variants to complete component_suite_dir.
|
|
# Helper var for older MPI versions - those are reparented, with each
|
|
# version in their own version-named dir.
|
|
standalone_glob = "[1-9]*.*.*"
|
|
|
|
# Most other components; try most specific glob first.
|
|
# flake8 is far too opinionated about lists - ugh.
|
|
normalize_kwargs = {
|
|
"version_globs": [
|
|
"_%s" % self.version,
|
|
"_%s.*" % v.up_to(2), # should be: YYYY.Nupdate
|
|
"_*.*.*", # last resort
|
|
]
|
|
}
|
|
for rename_rule in [
|
|
# cs given as arg, in years, dir actually used, [version_globs]
|
|
[None, ":2015", "composer_xe"],
|
|
[None, "2016:", "compilers_and_libraries"],
|
|
["advisor", ":2016", "advisor_xe"],
|
|
["inspector", ":2016", "inspector_xe"],
|
|
["vtune_profiler", ":2017", "vtune_amplifier_xe"],
|
|
["vtune", ":2017", "vtune_amplifier_xe"], # alt.
|
|
["vtune_profiler", ":2019", "vtune_amplifier"],
|
|
["itac", ":", "itac", [os.sep + standalone_glob]],
|
|
]:
|
|
if cs == rename_rule[0] and v.satisfies(ver(rename_rule[1])):
|
|
cs = rename_rule[2]
|
|
if len(rename_rule) > 3:
|
|
normalize_kwargs = {"version_globs": rename_rule[3]}
|
|
break
|
|
|
|
d = self.normalize_suite_dir(cs, **normalize_kwargs)
|
|
|
|
# Help find components not located directly under d.
|
|
# NB: ancestor() not well suited if version_globs may contain os.sep .
|
|
parent_dir = re.sub(os.sep + re.escape(cs) + ".*", "", d)
|
|
|
|
reparent_as = {}
|
|
if cs == "compilers_and_libraries": # must qualify further
|
|
d = os.path.join(d, _expand_fields("{platform}"))
|
|
elif cs == "composer_xe":
|
|
reparent_as = {"mpi": "impi"}
|
|
# ignore 'imb' (MPI Benchmarks)
|
|
|
|
for nominal_p, actual_p in reparent_as.items():
|
|
if component_path.startswith(nominal_p):
|
|
dirs = glob.glob(os.path.join(parent_dir, actual_p, standalone_glob))
|
|
debug_print("reparent dirs: %s" % dirs)
|
|
# Brazenly assume last match is the most recent version;
|
|
# convert back to relative of parent_dir, and re-assemble.
|
|
rel_dir = dirs[-1].split(parent_dir + os.sep, 1)[-1]
|
|
component_path = component_path.replace(nominal_p, rel_dir, 1)
|
|
d = parent_dir
|
|
|
|
d = os.path.join(d, component_path)
|
|
|
|
if relative:
|
|
d = os.path.relpath(os.path.realpath(d), parent_dir)
|
|
|
|
debug_print(d)
|
|
return d
|
|
|
|
def component_bin_dir(self, component, **kwargs):
|
|
d = self.normalize_path(component, **kwargs)
|
|
|
|
if component == "compiler": # bin dir is always under PARENT
|
|
d = os.path.join(ancestor(d), "bin", _expand_fields("{libarch}"))
|
|
d = d.rstrip(os.sep) # cosmetics, when {libarch} is empty
|
|
# NB: Works fine even with relative=True, e.g.:
|
|
# composer_xe/compiler -> composer_xe/bin/intel64
|
|
elif component == "mpi":
|
|
d = os.path.join(d, _expand_fields("{libarch}"), "bin")
|
|
else:
|
|
d = os.path.join(d, "bin")
|
|
debug_print(d)
|
|
return d
|
|
|
|
def component_lib_dir(self, component, **kwargs):
|
|
"""Provide directory suitable for find_libraries() and
|
|
SPACK_COMPILER_EXTRA_RPATHS.
|
|
"""
|
|
d = self.normalize_path(component, **kwargs)
|
|
|
|
if component == "mpi":
|
|
d = os.path.join(d, _expand_fields("{libarch}"), "lib")
|
|
else:
|
|
d = os.path.join(d, "lib", _expand_fields("{libarch}"))
|
|
d = d.rstrip(os.sep) # cosmetics, when {libarch} is empty
|
|
|
|
if component == "tbb": # must qualify further for abi
|
|
d = os.path.join(d, self._tbb_abi)
|
|
|
|
debug_print(d)
|
|
return d
|
|
|
|
def component_include_dir(self, component, **kwargs):
|
|
d = self.normalize_path(component, **kwargs)
|
|
|
|
if component == "mpi":
|
|
d = os.path.join(d, _expand_fields("{libarch}"), "include")
|
|
else:
|
|
d = os.path.join(d, "include")
|
|
|
|
debug_print(d)
|
|
return d
|
|
|
|
@property
|
|
def file_to_source(self):
|
|
"""Full path of file to source for initializing an Intel package.
|
|
A client package could override as follows:
|
|
` @property`
|
|
` def file_to_source(self):`
|
|
` return self.normalize_path("apsvars.sh", "vtune_amplifier")`
|
|
"""
|
|
vars_file_info_for = {
|
|
# key (usu. spack package name) -> [rel_path, component_suite_dir]
|
|
# Extension note: handle additions by Spack name or ad-hoc keys.
|
|
"@early_compiler": ["bin/compilervars", None],
|
|
"intel-parallel-studio": ["bin/psxevars", "parallel_studio_xe"],
|
|
"intel": ["bin/compilervars", None],
|
|
"intel-daal": ["daal/bin/daalvars", None],
|
|
"intel-ipp": ["ipp/bin/ippvars", None],
|
|
"intel-mkl": ["mkl/bin/mklvars", None],
|
|
"intel-mpi": ["mpi/{libarch}/bin/mpivars", None],
|
|
}
|
|
key = self.name
|
|
if self.version_yearlike.satisfies(ver(":2015")):
|
|
# Same file as 'intel' but 'None' for component_suite_dir will
|
|
# resolve differently. Listed as a separate entry to serve as
|
|
# example and to avoid pitfalls upon possible refactoring.
|
|
key = "@early_compiler"
|
|
|
|
f, component_suite_dir = vars_file_info_for[key]
|
|
f = _expand_fields(f) + ".sh"
|
|
# TODO?? win32 would have to handle os.sep, '.bat' (unless POSIX??)
|
|
|
|
f = self.normalize_path(f, component_suite_dir)
|
|
return f
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Threading, including (WIP) support for virtual 'tbb'
|
|
# ---------------------------------------------------------------------
|
|
@property
|
|
def openmp_libs(self):
|
|
"""Supply LibraryList for linking OpenMP"""
|
|
|
|
if "%intel" in self.spec:
|
|
# NB: Hunting down explicit library files may be the Spack way of
|
|
# doing things, but be aware that "{icc|ifort} --help openmp"
|
|
# steers us towards options instead: -qopenmp-link={dynamic,static}
|
|
|
|
omp_libnames = ["libiomp5"]
|
|
omp_libs = find_libraries(
|
|
omp_libnames,
|
|
root=self.component_lib_dir("compiler"),
|
|
shared=("+shared" in self.spec),
|
|
)
|
|
# Note about search root here: For MKL, the directory
|
|
# "$MKLROOT/../compiler" will be present even for an MKL-only
|
|
# product installation (as opposed to one being ghosted via
|
|
# packages.yaml), specificially to provide the 'iomp5' libs.
|
|
|
|
elif "%gcc" in self.spec:
|
|
with self.compiler.compiler_environment():
|
|
omp_lib_path = Executable(self.compiler.cc)(
|
|
"--print-file-name", "libgomp.%s" % dso_suffix, output=str
|
|
)
|
|
omp_libs = LibraryList(omp_lib_path.strip())
|
|
|
|
elif "%clang" in self.spec:
|
|
with self.compiler.compiler_environment():
|
|
omp_lib_path = Executable(self.compiler.cc)(
|
|
"--print-file-name", "libomp.%s" % dso_suffix, output=str
|
|
)
|
|
omp_libs = LibraryList(omp_lib_path.strip())
|
|
|
|
if len(omp_libs) < 1:
|
|
raise_lib_error("Cannot locate OpenMP libraries:", omp_libnames)
|
|
|
|
debug_print(omp_libs)
|
|
return omp_libs
|
|
|
|
@property
|
|
def _gcc_executable(self):
|
|
"""Return GCC executable"""
|
|
# Match the available gcc, as it's done in tbbvars.sh.
|
|
gcc_name = "gcc"
|
|
# but first check if -gcc-name is specified in cflags
|
|
for flag in self.spec.compiler_flags["cflags"]:
|
|
if flag.startswith("-gcc-name="):
|
|
gcc_name = flag.split("-gcc-name=")[1]
|
|
break
|
|
debug_print(gcc_name)
|
|
return Executable(gcc_name)
|
|
|
|
@property
|
|
def tbb_headers(self):
|
|
# Note: TBB is included as
|
|
# #include <tbb/task_scheduler_init.h>
|
|
return HeaderList([self.component_include_dir("tbb") + "/dummy.h"])
|
|
|
|
@property
|
|
def tbb_libs(self):
|
|
"""Supply LibraryList for linking TBB"""
|
|
|
|
# TODO: When is 'libtbbmalloc' needed?
|
|
tbb_lib = find_libraries(["libtbb"], root=self.component_lib_dir("tbb"))
|
|
# NB: Like icc with -qopenmp, so does icpc steer us towards using an
|
|
# option: "icpc -tbb"
|
|
|
|
# TODO: clang(?)
|
|
gcc = self._gcc_executable # must be gcc, not self.compiler.cc
|
|
with self.compiler.compiler_environment():
|
|
cxx_lib_path = gcc("--print-file-name", "libstdc++.%s" % dso_suffix, output=str)
|
|
|
|
libs = tbb_lib + LibraryList(cxx_lib_path.rstrip())
|
|
debug_print(libs)
|
|
return libs
|
|
|
|
@property
|
|
def _tbb_abi(self):
|
|
"""Select the ABI needed for linking TBB"""
|
|
gcc = self._gcc_executable
|
|
with self.compiler.compiler_environment():
|
|
matches = re.search(
|
|
r"(gcc|LLVM).* ([0-9]+\.[0-9]+\.[0-9]+).*",
|
|
gcc("--version", output=str),
|
|
re.I | re.M,
|
|
)
|
|
abi = ""
|
|
if sys.platform == "darwin":
|
|
pass
|
|
elif matches:
|
|
# TODO: Confirm that this covers clang (needed on Linux only)
|
|
gcc_version = Version(matches.groups()[1])
|
|
if gcc_version >= ver("4.7"):
|
|
abi = "gcc4.7"
|
|
elif gcc_version >= ver("4.4"):
|
|
abi = "gcc4.4"
|
|
else:
|
|
abi = "gcc4.1" # unlikely, one hopes.
|
|
|
|
# Alrighty then ...
|
|
debug_print(abi)
|
|
return abi
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Support for virtual 'blas/lapack/scalapack'
|
|
# ---------------------------------------------------------------------
|
|
@property
|
|
def blas_libs(self):
|
|
# Main magic here.
|
|
# For reference, see The Intel Math Kernel Library Link Line Advisor:
|
|
# https://software.intel.com/en-us/articles/intel-mkl-link-line-advisor/
|
|
|
|
mkl_integer = "libmkl_intel_" + self.intel64_int_suffix
|
|
|
|
if self.spec.satisfies("threads=openmp"):
|
|
if "%intel" in self.spec:
|
|
mkl_threading = "libmkl_intel_thread"
|
|
elif "%gcc" in self.spec or "%clang" in self.spec:
|
|
mkl_threading = "libmkl_gnu_thread"
|
|
threading_engine_libs = self.openmp_libs
|
|
elif self.spec.satisfies("threads=tbb"):
|
|
mkl_threading = "libmkl_tbb_thread"
|
|
threading_engine_libs = self.tbb_libs
|
|
elif self.spec.satisfies("threads=none"):
|
|
mkl_threading = "libmkl_sequential"
|
|
threading_engine_libs = LibraryList([])
|
|
else:
|
|
raise_lib_error("Cannot determine MKL threading libraries.")
|
|
|
|
mkl_libnames = [mkl_integer, mkl_threading, "libmkl_core"]
|
|
mkl_libs = find_libraries(
|
|
mkl_libnames, root=self.component_lib_dir("mkl"), shared=("+shared" in self.spec)
|
|
)
|
|
debug_print(mkl_libs)
|
|
|
|
if len(mkl_libs) < 3:
|
|
raise_lib_error(
|
|
"Cannot locate core MKL libraries:",
|
|
mkl_libnames,
|
|
"in:",
|
|
self.component_lib_dir("mkl"),
|
|
)
|
|
|
|
# The Intel MKL link line advisor recommends these system libraries
|
|
system_libs = find_system_libraries(
|
|
"libpthread libm libdl".split(), shared=("+shared" in self.spec)
|
|
)
|
|
debug_print(system_libs)
|
|
|
|
return mkl_libs + threading_engine_libs + system_libs
|
|
|
|
@property
|
|
def lapack_libs(self):
|
|
return self.blas_libs
|
|
|
|
@property
|
|
def scalapack_libs(self):
|
|
# Intel MKL does not directly depend on MPI but the BLACS library
|
|
# which underlies ScaLapack does. It comes in several personalities;
|
|
# we must supply a personality matching the MPI implementation that
|
|
# is active for the root package that asked for ScaLapack.
|
|
spec_root = self.spec.root
|
|
if sys.platform == "darwin" and "^mpich" in spec_root:
|
|
# The only supported choice for MKL 2018 on Mac.
|
|
blacs_lib = "libmkl_blacs_mpich"
|
|
elif "^openmpi" in spec_root:
|
|
blacs_lib = "libmkl_blacs_openmpi"
|
|
elif "^mpich@1" in spec_root:
|
|
# Was supported only up to 2015.
|
|
blacs_lib = "libmkl_blacs"
|
|
elif (
|
|
"^mpich@2:" in spec_root
|
|
or "^cray-mpich" in spec_root
|
|
or "^mvapich2" in spec_root
|
|
or "^intel-mpi" in spec_root
|
|
or "^intel-oneapi-mpi" in spec_root
|
|
or "^intel-parallel-studio" in spec_root
|
|
):
|
|
blacs_lib = "libmkl_blacs_intelmpi"
|
|
elif "^mpt" in spec_root:
|
|
blacs_lib = "libmkl_blacs_sgimpt"
|
|
else:
|
|
raise_lib_error("Cannot find a BLACS library for the given MPI.")
|
|
|
|
int_suff = "_" + self.intel64_int_suffix
|
|
scalapack_libnames = [
|
|
"libmkl_scalapack" + int_suff,
|
|
blacs_lib + int_suff,
|
|
]
|
|
sca_libs = find_libraries(
|
|
scalapack_libnames, root=self.component_lib_dir("mkl"), shared=("+shared" in self.spec)
|
|
)
|
|
debug_print(sca_libs)
|
|
|
|
if len(sca_libs) < 2:
|
|
raise_lib_error("Cannot locate ScaLapack/BLACS libraries:", scalapack_libnames)
|
|
# NB: ScaLapack is installed as "cluster" components within MKL or
|
|
# MKL-encompassing products. But those were *optional* for the ca.
|
|
# 2015/2016 product releases, which was easy to overlook, and I have
|
|
# been bitten by that. Thus, complain early because it'd be a sore
|
|
# disappointment to have missing ScaLapack libs show up as a link error
|
|
# near the end phase of a client package's build phase.
|
|
|
|
return sca_libs
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Support for virtual 'mpi'
|
|
# ---------------------------------------------------------------------
|
|
@property
|
|
def mpi_compiler_wrappers(self):
|
|
"""Return paths to compiler wrappers as a dict of env-like names"""
|
|
# Intel comes with 2 different flavors of MPI wrappers:
|
|
#
|
|
# * mpiicc, mpiicpc, and mpiifort are hardcoded to wrap around
|
|
# the Intel compilers.
|
|
# * mpicc, mpicxx, mpif90, and mpif77 allow you to set which
|
|
# compilers to wrap using I_MPI_CC and friends. By default,
|
|
# wraps around the GCC compilers.
|
|
#
|
|
# In theory, these should be equivalent as long as I_MPI_CC
|
|
# and friends are set to point to the Intel compilers, but in
|
|
# practice, mpicc fails to compile some applications while
|
|
# mpiicc works.
|
|
bindir = self.component_bin_dir("mpi")
|
|
if self.compiler.name == "intel":
|
|
wrapper_vars = {
|
|
# eschew Prefix objects -- emphasize the command strings.
|
|
"MPICC": os.path.join(bindir, "mpiicc"),
|
|
"MPICXX": os.path.join(bindir, "mpiicpc"),
|
|
"MPIF77": os.path.join(bindir, "mpiifort"),
|
|
"MPIF90": os.path.join(bindir, "mpiifort"),
|
|
"MPIFC": os.path.join(bindir, "mpiifort"),
|
|
}
|
|
else:
|
|
wrapper_vars = {
|
|
"MPICC": os.path.join(bindir, "mpicc"),
|
|
"MPICXX": os.path.join(bindir, "mpicxx"),
|
|
"MPIF77": os.path.join(bindir, "mpif77"),
|
|
"MPIF90": os.path.join(bindir, "mpif90"),
|
|
"MPIFC": os.path.join(bindir, "mpif90"),
|
|
}
|
|
# debug_print("wrapper_vars =", wrapper_vars)
|
|
return wrapper_vars
|
|
|
|
def mpi_setup_dependent_build_environment(self, env, dependent_spec, compilers_of_client={}):
|
|
"""Unified back-end for setup_dependent_build_environment() of
|
|
Intel packages that provide 'mpi'.
|
|
|
|
Parameters:
|
|
|
|
env, dependent_spec: same as in
|
|
setup_dependent_build_environment().
|
|
|
|
compilers_of_client (dict): Conveys spack_cc, spack_cxx, etc.,
|
|
from the scope of dependent packages; constructed in caller.
|
|
"""
|
|
# See also: setup_dependent_package()
|
|
wrapper_vars = {
|
|
"I_MPI_CC": compilers_of_client["CC"],
|
|
"I_MPI_CXX": compilers_of_client["CXX"],
|
|
"I_MPI_F77": compilers_of_client["F77"],
|
|
"I_MPI_F90": compilers_of_client["F90"],
|
|
"I_MPI_FC": compilers_of_client["FC"],
|
|
# NB: Normally set by the modulefile, but that is not active here:
|
|
"I_MPI_ROOT": self.normalize_path("mpi"),
|
|
}
|
|
|
|
# CAUTION - SIMILAR code in:
|
|
# var/spack/repos/builtin/packages/mpich/package.py
|
|
# var/spack/repos/builtin/packages/openmpi/package.py
|
|
# var/spack/repos/builtin/packages/mvapich2/package.py
|
|
#
|
|
# On Cray, the regular compiler wrappers *are* the MPI wrappers.
|
|
if "platform=cray" in self.spec:
|
|
# TODO: Confirm
|
|
wrapper_vars.update(
|
|
{
|
|
"MPICC": compilers_of_client["CC"],
|
|
"MPICXX": compilers_of_client["CXX"],
|
|
"MPIF77": compilers_of_client["F77"],
|
|
"MPIF90": compilers_of_client["F90"],
|
|
}
|
|
)
|
|
else:
|
|
compiler_wrapper_commands = self.mpi_compiler_wrappers
|
|
wrapper_vars.update(
|
|
{
|
|
"MPICC": compiler_wrapper_commands["MPICC"],
|
|
"MPICXX": compiler_wrapper_commands["MPICXX"],
|
|
"MPIF77": compiler_wrapper_commands["MPIF77"],
|
|
"MPIF90": compiler_wrapper_commands["MPIF90"],
|
|
}
|
|
)
|
|
|
|
# Ensure that the directory containing the compiler wrappers is in the
|
|
# PATH. Spack packages add `prefix.bin` to their dependents' paths,
|
|
# but because of the intel directory hierarchy that is insufficient.
|
|
env.prepend_path("PATH", os.path.dirname(wrapper_vars["MPICC"]))
|
|
|
|
for key, value in wrapper_vars.items():
|
|
env.set(key, value)
|
|
|
|
debug_print("adding to build env:", wrapper_vars)
|
|
|
|
# ---------------------------------------------------------------------
|
|
# General support for child packages
|
|
# ---------------------------------------------------------------------
|
|
@property
|
|
def headers(self):
|
|
result = HeaderList([])
|
|
if "+mpi" in self.spec or self.provides("mpi"):
|
|
result += find_headers(
|
|
["mpi"], root=self.component_include_dir("mpi"), recursive=False
|
|
)
|
|
if "+mkl" in self.spec or self.provides("mkl"):
|
|
result += find_headers(
|
|
["mkl_cblas", "mkl_lapacke"],
|
|
root=self.component_include_dir("mkl"),
|
|
recursive=False,
|
|
)
|
|
if "+tbb" in self.spec or self.provides("tbb"):
|
|
result += self.tbb_headers
|
|
|
|
debug_print(result)
|
|
return result
|
|
|
|
@property
|
|
def libs(self):
|
|
result = LibraryList([])
|
|
if "+tbb" in self.spec or self.provides("tbb"):
|
|
result = self.tbb_libs + result
|
|
if "+mkl" in self.spec or self.provides("blas"):
|
|
result = self.blas_libs + result
|
|
if "+mkl" in self.spec or self.provides("lapack"):
|
|
result = self.lapack_libs + result
|
|
if "+mpi" in self.spec or self.provides("mpi"):
|
|
# If prefix is too general, recursive searches may get files from
|
|
# supported but inappropriate sub-architectures like 'mic'.
|
|
libnames = ["libmpifort", "libmpi"]
|
|
if "cxx" in self.spec.last_query.extra_parameters:
|
|
libnames = ["libmpicxx"] + libnames
|
|
result = (
|
|
find_libraries(
|
|
libnames, root=self.component_lib_dir("mpi"), shared=True, recursive=True
|
|
)
|
|
+ result
|
|
)
|
|
# Intel MPI since 2019 depends on libfabric which is not in the
|
|
# lib directory but in a directory of its own which should be
|
|
# included in the rpath
|
|
if self.version_yearlike >= ver("2019"):
|
|
d = ancestor(self.component_lib_dir("mpi"))
|
|
if "+external-libfabric" in self.spec:
|
|
result += self.spec["libfabric"].libs
|
|
else:
|
|
result += find_libraries(["libfabric"], os.path.join(d, "libfabric", "lib"))
|
|
|
|
if "^mpi" in self.spec.root and ("+mkl" in self.spec or self.provides("scalapack")):
|
|
result = self.scalapack_libs + result
|
|
|
|
debug_print(result)
|
|
return result
|
|
|
|
def setup_run_environment(self, env):
|
|
"""Adds environment variables to the generated module file.
|
|
|
|
These environment variables come from running:
|
|
|
|
.. code-block:: console
|
|
|
|
$ source parallel_studio_xe_2017/bin/psxevars.sh intel64
|
|
[and likewise for MKL, MPI, and other components]
|
|
"""
|
|
f = self.file_to_source
|
|
tty.debug("sourcing " + f)
|
|
|
|
# All Intel packages expect at least the architecture as argument.
|
|
# Some accept more args, but those are not (yet?) handled here.
|
|
args = (_expand_fields("{arch}"),)
|
|
|
|
# On Mac, the platform is *also required*, at least as of 2018.
|
|
# I am not sure about earlier versions.
|
|
# if sys.platform == 'darwin':
|
|
# args = ()
|
|
|
|
env.extend(EnvironmentModifications.from_sourcing_file(f, *args))
|
|
|
|
if self.spec.name in ("intel", "intel-parallel-studio"):
|
|
# this package provides compilers
|
|
# TODO: fix check above when compilers are dependencies
|
|
env.set("CC", self.prefix.bin.icc)
|
|
env.set("CXX", self.prefix.bin.icpc)
|
|
env.set("FC", self.prefix.bin.ifort)
|
|
env.set("F77", self.prefix.bin.ifort)
|
|
env.set("F90", self.prefix.bin.ifort)
|
|
|
|
def setup_dependent_build_environment(self, env, dependent_spec):
|
|
# NB: This function is overwritten by 'mpi' provider packages:
|
|
#
|
|
# var/spack/repos/builtin/packages/intel-mpi/package.py
|
|
# var/spack/repos/builtin/packages/intel-parallel-studio/package.py
|
|
#
|
|
# They call _setup_dependent_env_callback() as well, but with the
|
|
# dictionary kwarg compilers_of_client{} present and populated.
|
|
|
|
# Handle everything in a callback version.
|
|
self._setup_dependent_env_callback(env, dependent_spec)
|
|
|
|
def _setup_dependent_env_callback(self, env, dependent_spec, compilers_of_client={}):
|
|
# Expected to be called from a client's
|
|
# setup_dependent_build_environment(),
|
|
# with args extended to convey the client's compilers as needed.
|
|
|
|
if "+mkl" in self.spec or self.provides("mkl"):
|
|
# Spack's env philosophy demands that we replicate some of the
|
|
# settings normally handled by file_to_source ...
|
|
#
|
|
# TODO: Why is setup_run_environment()
|
|
# [which uses file_to_source()]
|
|
# not called as a matter of course upon entering the current
|
|
# function? (guarding against multiple calls notwithstanding)
|
|
#
|
|
# Use a local dict to facilitate debug_print():
|
|
env_mods = {
|
|
"MKLROOT": self.normalize_path("mkl"),
|
|
"SPACK_COMPILER_EXTRA_RPATHS": self.component_lib_dir("mkl"),
|
|
"CMAKE_PREFIX_PATH": self.normalize_path("mkl"),
|
|
"CMAKE_LIBRARY_PATH": self.component_lib_dir("mkl"),
|
|
"CMAKE_INCLUDE_PATH": self.component_include_dir("mkl"),
|
|
}
|
|
|
|
env.set("MKLROOT", env_mods["MKLROOT"])
|
|
env.append_path("SPACK_COMPILER_EXTRA_RPATHS", env_mods["SPACK_COMPILER_EXTRA_RPATHS"])
|
|
env.append_path("CMAKE_PREFIX_PATH", env_mods["CMAKE_PREFIX_PATH"])
|
|
env.append_path("CMAKE_LIBRARY_PATH", env_mods["CMAKE_LIBRARY_PATH"])
|
|
env.append_path("CMAKE_INCLUDE_PATH", env_mods["CMAKE_INCLUDE_PATH"])
|
|
|
|
debug_print("adding/modifying build env:", env_mods)
|
|
|
|
if "+mpi" in self.spec or self.provides("mpi"):
|
|
if compilers_of_client:
|
|
self.mpi_setup_dependent_build_environment(
|
|
env, dependent_spec, compilers_of_client
|
|
)
|
|
# We could forego this nonce function and inline its code here,
|
|
# but (a) it sisters mpi_compiler_wrappers() [needed twice]
|
|
# which performs dizzyingly similar but necessarily different
|
|
# actions, and (b) function code leaves a bit more breathing
|
|
# room within the suffocating corset of flake8 line length.
|
|
else:
|
|
raise InstallError("compilers_of_client arg required for MPI")
|
|
|
|
def setup_dependent_package(self, module, dep_spec):
|
|
# https://spack.readthedocs.io/en/latest/spack.html#spack.package_base.PackageBase.setup_dependent_package
|
|
# Reminder: "module" refers to Python module.
|
|
# Called before the install() method of dependents.
|
|
|
|
if "+mpi" in self.spec or self.provides("mpi"):
|
|
compiler_wrapper_commands = self.mpi_compiler_wrappers
|
|
self.spec.mpicc = compiler_wrapper_commands["MPICC"]
|
|
self.spec.mpicxx = compiler_wrapper_commands["MPICXX"]
|
|
self.spec.mpif77 = compiler_wrapper_commands["MPIF77"]
|
|
self.spec.mpifc = compiler_wrapper_commands["MPIFC"]
|
|
debug_print(
|
|
("spec '%s' received .mpi* properties:" % self.spec), compiler_wrapper_commands
|
|
)
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Specifics for installation phase
|
|
# ---------------------------------------------------------------------
|
|
@property
|
|
def global_license_file(self):
|
|
"""Returns the path where a Spack-global license file should be stored.
|
|
|
|
All Intel software shares the same license, so we store it in a
|
|
common 'intel' directory."""
|
|
return os.path.join(self.global_license_dir, "intel", "license.lic")
|
|
|
|
@property
|
|
def _determine_license_type(self):
|
|
"""Provide appropriate license tokens for the installer (silent.cfg)."""
|
|
# See:
|
|
# ./README-intel.rst, section "Details for licensing tokens".
|
|
# ./build_systems/README-intel.rst, section "Licenses"
|
|
#
|
|
# Ideally, we just tell the installer to look around on the system.
|
|
# Thankfully, we neither need to care nor emulate where it looks:
|
|
license_type = {
|
|
"ACTIVATION_TYPE": "exist_lic",
|
|
}
|
|
|
|
# However (and only), if the spack-internal Intel license file has been
|
|
# populated beyond its templated explanatory comments, proffer it to
|
|
# the installer instead:
|
|
f = self.global_license_file
|
|
if os.path.isfile(f):
|
|
# The file will have been created upon self.license_required AND
|
|
# self.license_files having been populated, so the "if" is usually
|
|
# true by the time the present function runs; ../hooks/licensing.py
|
|
with open(f) as fh:
|
|
if re.search(r"^[ \t]*[^" + self.license_comment + "\n]", fh.read(), re.MULTILINE):
|
|
license_type = {
|
|
"ACTIVATION_TYPE": "license_file",
|
|
"ACTIVATION_LICENSE_FILE": f,
|
|
}
|
|
|
|
debug_print(license_type)
|
|
return license_type
|
|
|
|
@spack.builder.run_before("install")
|
|
def configure(self):
|
|
"""Generates the silent.cfg file to pass to installer.sh.
|
|
|
|
See https://software.intel.com/en-us/articles/configuration-file-format
|
|
"""
|
|
prefix = self.prefix
|
|
# Both tokens AND values of the configuration file are validated during
|
|
# the run of the underlying binary installer. Any unknown token or
|
|
# unacceptable value will cause that installer to fail. Notably, this
|
|
# applies to trying to specify a license for a product that does not
|
|
# require one.
|
|
#
|
|
# Fortunately, the validator is a script from a solid code base that is
|
|
# only lightly adapted to the token vocabulary of each product and
|
|
# release. Let's get that script so we can preempt its objections.
|
|
#
|
|
# Rather than running the script on a trial file and dissecting its
|
|
# pronouncements, let's brazenly skim it for supported tokens and build
|
|
# our configuration accordingly. We can do this because the tokens are
|
|
# quite long and specific.
|
|
|
|
validator_code = open("pset/check.awk", "r").read()
|
|
# Let's go a little further and distill the tokens (plus some noise).
|
|
tokenlike_words = set(re.findall(r"[A-Z_]{4,}", validator_code))
|
|
|
|
# NB: .cfg files generated with the "--duplicate filename" option have
|
|
# the COMPONENTS string begin with a separator - do not worry about it.
|
|
components_joined = ";".join(self._filtered_components)
|
|
nonrpm_db_dir = os.path.join(prefix, "nonrpm-db")
|
|
|
|
config_draft = {
|
|
# Basics first - these should be accepted in all products.
|
|
"ACCEPT_EULA": "accept",
|
|
"PSET_MODE": "install",
|
|
"CONTINUE_WITH_OPTIONAL_ERROR": "yes",
|
|
"CONTINUE_WITH_INSTALLDIR_OVERWRITE": "yes",
|
|
"SIGNING_ENABLED": "no",
|
|
# Highly variable package specifics:
|
|
"PSET_INSTALL_DIR": prefix,
|
|
"NONRPM_DB_DIR": nonrpm_db_dir,
|
|
"COMPONENTS": components_joined,
|
|
# Conditional tokens; the first is supported post-2015 only.
|
|
# Ignore ia32; most recent products don't even provide it.
|
|
"ARCH_SELECTED": "INTEL64", # was: 'ALL'
|
|
# 'ism' component -- see uninstall_ism(); also varies by release.
|
|
"PHONEHOME_SEND_USAGE_DATA": "no",
|
|
# Ah, as of 2018.2, that somewhat loaded term got replaced by one
|
|
# in business-speak. We uphold our preference, both out of general
|
|
# principles and for technical reasons like overhead and non-routed
|
|
# compute nodes.
|
|
"INTEL_SW_IMPROVEMENT_PROGRAM_CONSENT": "no",
|
|
}
|
|
# Deal with licensing only if truly needed.
|
|
# NB: Token was 'ACTIVATION' pre ~2013, so basically irrelevant here.
|
|
if "ACTIVATION_TYPE" in tokenlike_words:
|
|
config_draft.update(self._determine_license_type)
|
|
|
|
# Write sorted *by token* so the file looks less like a hash dump.
|
|
f = open("silent.cfg", "w")
|
|
for token, value in sorted(config_draft.items()):
|
|
if token in tokenlike_words:
|
|
f.write("%s=%s\n" % (token, value))
|
|
f.close()
|
|
|
|
def install(self, spec, prefix):
|
|
"""Runs Intel's install.sh installation script. Afterwards, save the
|
|
installer config and logs to <prefix>/.spack
|
|
"""
|
|
# prepare
|
|
tmpdir = tempfile.mkdtemp(prefix="spack-intel-")
|
|
|
|
install_script = Executable("./install.sh")
|
|
install_script.add_default_env("TMPDIR", tmpdir)
|
|
|
|
# Need to set HOME to avoid using ~/intel
|
|
install_script.add_default_env("HOME", prefix)
|
|
|
|
# perform
|
|
install_script("--silent", "silent.cfg")
|
|
|
|
# preserve config and logs
|
|
dst = os.path.join(self.prefix, ".spack")
|
|
install("silent.cfg", dst)
|
|
for f in glob.glob("%s/intel*log" % tmpdir):
|
|
install(f, dst)
|
|
|
|
@spack.builder.run_after("install")
|
|
def validate_install(self):
|
|
# Sometimes the installer exits with an error but doesn't pass a
|
|
# non-zero exit code to spack. Check for the existence of a 'bin'
|
|
# directory to catch this error condition.
|
|
if not os.path.exists(self.prefix.bin):
|
|
raise InstallError("The installer has failed to install anything.")
|
|
|
|
@spack.builder.run_after("install")
|
|
def configure_rpath(self):
|
|
if "+rpath" not in self.spec:
|
|
return
|
|
|
|
# https://software.intel.com/en-us/cpp-compiler-18.0-developer-guide-and-reference-using-configuration-files
|
|
compilers_bin_dir = self.component_bin_dir("compiler")
|
|
compilers_lib_dir = self.component_lib_dir("compiler")
|
|
|
|
for compiler_name in "icc icpc ifort".split():
|
|
f = os.path.join(compilers_bin_dir, compiler_name)
|
|
if not os.path.isfile(f):
|
|
raise InstallError("Cannot find compiler command to configure rpath:\n\t" + f)
|
|
|
|
compiler_cfg = os.path.abspath(f + ".cfg")
|
|
with open(compiler_cfg, "w") as fh:
|
|
fh.write("-Xlinker -rpath={0}\n".format(compilers_lib_dir))
|
|
|
|
@spack.builder.run_after("install")
|
|
def configure_auto_dispatch(self):
|
|
if self._has_compilers:
|
|
if "auto_dispatch=none" in self.spec:
|
|
return
|
|
|
|
compilers_bin_dir = self.component_bin_dir("compiler")
|
|
|
|
for compiler_name in "icc icpc ifort".split():
|
|
f = os.path.join(compilers_bin_dir, compiler_name)
|
|
if not os.path.isfile(f):
|
|
raise InstallError(
|
|
"Cannot find compiler command to configure " "auto_dispatch:\n\t" + f
|
|
)
|
|
|
|
ad = []
|
|
for x in IntelPackage.auto_dispatch_options:
|
|
if "auto_dispatch={0}".format(x) in self.spec:
|
|
ad.append(x)
|
|
|
|
compiler_cfg = os.path.abspath(f + ".cfg")
|
|
with open(compiler_cfg, "a") as fh:
|
|
fh.write("-ax{0}\n".format(",".join(ad)))
|
|
|
|
@spack.builder.run_after("install")
|
|
def filter_compiler_wrappers(self):
|
|
if ("+mpi" in self.spec or self.provides("mpi")) and "~newdtags" in self.spec:
|
|
bin_dir = self.component_bin_dir("mpi")
|
|
for f in "mpif77 mpif90 mpigcc mpigxx mpiicc mpiicpc " "mpiifort".split():
|
|
f = os.path.join(bin_dir, f)
|
|
filter_file("-Xlinker --enable-new-dtags", " ", f, string=True)
|
|
|
|
@spack.builder.run_after("install")
|
|
def uninstall_ism(self):
|
|
# The "Intel(R) Software Improvement Program" [ahem] gets installed,
|
|
# apparently regardless of PHONEHOME_SEND_USAGE_DATA.
|
|
#
|
|
# https://software.intel.com/en-us/articles/software-improvement-program
|
|
# https://software.intel.com/en-us/forums/intel-c-compiler/topic/506959
|
|
# Hubert H. (Intel) Mon, 03/10/2014 - 03:02 wrote:
|
|
# "... you can also uninstall the Intel(R) Software Manager
|
|
# completely: <installdir>/intel/ism/uninstall.sh"
|
|
|
|
f = os.path.join(self.normalize_path("ism"), "uninstall.sh")
|
|
if os.path.isfile(f):
|
|
tty.warn('Uninstalling "Intel Software Improvement Program"' "component")
|
|
uninstall = Executable(f)
|
|
uninstall("--silent")
|
|
|
|
# TODO? also try
|
|
# ~/intel/ism/uninstall --silent
|
|
|
|
debug_print(os.getcwd())
|
|
return
|
|
|
|
@property
|
|
def base_lib_dir(self):
|
|
"""Provide the library directory located in the base of Intel installation."""
|
|
d = self.normalize_path("")
|
|
d = os.path.join(d, "lib")
|
|
|
|
debug_print(d)
|
|
return d
|
|
|
|
@spack.builder.run_after("install")
|
|
def modify_LLVMgold_rpath(self):
|
|
"""Add libimf.so and other required libraries to the RUNPATH of LLVMgold.so.
|
|
|
|
These are needed explicitly at dependent link time when
|
|
`ld -plugin LLVMgold.so` is called by the compiler.
|
|
"""
|
|
if self._has_compilers:
|
|
LLVMgold_libs = find_libraries(
|
|
"LLVMgold", self.base_lib_dir, shared=True, recursive=True
|
|
)
|
|
# Ignore ia32 entries as they mostly ignore throughout the rest
|
|
# of the file.
|
|
# The first entry in rpath preserves the original, the seconds entry
|
|
# is the location of libimf.so. If this relative location is changed
|
|
# in compiler releases, then we need to search for libimf.so instead
|
|
# of this static path.
|
|
for lib in LLVMgold_libs:
|
|
if not self.spec.satisfies("^patchelf"):
|
|
raise spack.error.SpackError(
|
|
"Attempting to patch RPATH in LLVMgold.so."
|
|
+ "`patchelf` dependency should be set in package.py"
|
|
)
|
|
patchelf = Executable("patchelf")
|
|
rpath = ":".join(
|
|
[
|
|
patchelf("--print-rpath", lib, output=str).strip(),
|
|
"$ORIGIN/../compiler/lib/intel64_lin",
|
|
]
|
|
)
|
|
patchelf("--set-rpath", rpath, lib)
|