Compare commits
118 Commits
v1.0.0-alp
...
hs/fix/cma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25c74506a3 | ||
|
|
e37e53cfe8 | ||
|
|
cf31d20d4c | ||
|
|
b74db341c8 | ||
|
|
e88a3f6f85 | ||
|
|
9bd7483e73 | ||
|
|
04c76fab63 | ||
|
|
ecbf9fcacf | ||
|
|
69fb594699 | ||
|
|
d28614151f | ||
|
|
f1d6af6c94 | ||
|
|
192821f361 | ||
|
|
18790ca397 | ||
|
|
c22d77a38e | ||
|
|
d82bdb3bf7 | ||
|
|
a042bdfe0b | ||
|
|
60e3e645e8 | ||
|
|
51785437bc | ||
|
|
2e8db0815d | ||
|
|
8a6428746f | ||
|
|
6b9c099af8 | ||
|
|
30814fb4e0 | ||
|
|
3194be2e92 | ||
|
|
41be2f5899 | ||
|
|
02af41ebb3 | ||
|
|
9d33c89030 | ||
|
|
51ab7bad3b | ||
|
|
0b094f2473 | ||
|
|
cd306d0bc6 | ||
|
|
fdb9cf2412 | ||
|
|
a546441d2e | ||
|
|
141cdb6810 | ||
|
|
f2ab74efe5 | ||
|
|
38b838e405 | ||
|
|
c037188b59 | ||
|
|
0835a3c5f2 | ||
|
|
38a2f9c2f2 | ||
|
|
eecd4afe58 | ||
|
|
83624551e0 | ||
|
|
741652caa1 | ||
|
|
8e914308f0 | ||
|
|
3c220d0989 | ||
|
|
8094fa1e2f | ||
|
|
5c67051980 | ||
|
|
c01fb9a6d2 | ||
|
|
bf12bb57e7 | ||
|
|
406c73ae11 | ||
|
|
3f50ccfcdd | ||
|
|
9883a2144d | ||
|
|
94815d2227 | ||
|
|
a15563f890 | ||
|
|
ac2ede8d2f | ||
|
|
b256a7c50d | ||
|
|
21e10d6d98 | ||
|
|
ed39967848 | ||
|
|
eda0c6888e | ||
|
|
66055f903c | ||
|
|
a1c57d86c3 | ||
|
|
9da8dcae97 | ||
|
|
c93f223a73 | ||
|
|
f1faf31735 | ||
|
|
8957ef0df5 | ||
|
|
347ec87fc5 | ||
|
|
cd8c46e54e | ||
|
|
75b03bc12f | ||
|
|
58511a3352 | ||
|
|
325873a4c7 | ||
|
|
9156e4be04 | ||
|
|
12d3abc736 | ||
|
|
4208aa6291 | ||
|
|
0bad754e23 | ||
|
|
cde2620f41 | ||
|
|
a35aa038b0 | ||
|
|
150416919e | ||
|
|
281c274e0b | ||
|
|
16e130ece1 | ||
|
|
7586303fba | ||
|
|
6501880fbf | ||
|
|
c76098038c | ||
|
|
124b616b27 | ||
|
|
1148c8f195 | ||
|
|
c57452dd08 | ||
|
|
a7e57c9a14 | ||
|
|
85d83f9c26 | ||
|
|
39a081d7fd | ||
|
|
71b65bb424 | ||
|
|
3dcbd118df | ||
|
|
5dacb774f6 | ||
|
|
cb3d6549c9 | ||
|
|
559c2f1eb9 | ||
|
|
ed1dbea77b | ||
|
|
6ebafe4631 | ||
|
|
7f0bb7147d | ||
|
|
f41b38e93d | ||
|
|
5fd12b7bea | ||
|
|
fe746bdebb | ||
|
|
453af4b9f7 | ||
|
|
29cf1559cc | ||
|
|
a9b3e1670b | ||
|
|
4f9aa6004b | ||
|
|
aa2c18e4df | ||
|
|
0ff3e86315 | ||
|
|
df208c1095 | ||
|
|
853f70edc8 | ||
|
|
50970f866e | ||
|
|
8821300985 | ||
|
|
adc8e1d996 | ||
|
|
1e0aac6ac3 | ||
|
|
99e2313d81 | ||
|
|
22690a7576 | ||
|
|
5325cfe865 | ||
|
|
5333925dd7 | ||
|
|
2db99e1ff6 | ||
|
|
68aa712a3e | ||
|
|
2e71bc640c | ||
|
|
661f3621a7 | ||
|
|
f182032337 | ||
|
|
066666b7b1 |
11
.github/workflows/build-containers.yml
vendored
11
.github/workflows/build-containers.yml
vendored
@@ -57,7 +57,13 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
- uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
|
||||
- name: Determine latest release tag
|
||||
id: latest
|
||||
run: |
|
||||
git fetch --quiet --tags
|
||||
echo "tag=$(git tag --list --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" | tee -a $GITHUB_OUTPUT
|
||||
|
||||
- uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
|
||||
id: docker_meta
|
||||
with:
|
||||
images: |
|
||||
@@ -71,6 +77,7 @@ jobs:
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/tags/{0}', steps.latest.outputs.tag) }}
|
||||
|
||||
- name: Generate the Dockerfile
|
||||
env:
|
||||
@@ -113,7 +120,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build & Deploy ${{ matrix.dockerfile[0] }}
|
||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75
|
||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355
|
||||
with:
|
||||
context: dockerfiles/${{ matrix.dockerfile[0] }}
|
||||
platforms: ${{ matrix.dockerfile[1] }}
|
||||
|
||||
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -29,6 +29,6 @@ jobs:
|
||||
- run: coverage xml
|
||||
|
||||
- name: "Upload coverage report to CodeCov"
|
||||
uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a
|
||||
uses: codecov/codecov-action@05f5a9cfad807516dbbef9929c4a42df3eb78766
|
||||
with:
|
||||
verbose: true
|
||||
|
||||
7
.github/workflows/unit_tests.yaml
vendored
7
.github/workflows/unit_tests.yaml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
cmake bison libbison-dev kcov
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
pip install --upgrade pip setuptools pytest pytest-xdist pytest-cov clingo
|
||||
pip install --upgrade pip setuptools pytest pytest-xdist pytest-cov
|
||||
pip install --upgrade flake8 "isort>=4.3.5" "mypy>=0.900" "click" "black"
|
||||
- name: Setup git configuration
|
||||
run: |
|
||||
@@ -173,6 +173,7 @@ jobs:
|
||||
spack bootstrap disable github-actions-v0.5
|
||||
spack bootstrap disable github-actions-v0.6
|
||||
spack bootstrap status
|
||||
spack solve zlib
|
||||
spack unit-test --verbose --cov --cov-config=pyproject.toml --cov-report=xml:coverage.xml lib/spack/spack/test/concretization/core.py
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
|
||||
with:
|
||||
@@ -210,7 +211,7 @@ jobs:
|
||||
. share/spack/setup-env.sh
|
||||
$(which spack) bootstrap disable spack-install
|
||||
$(which spack) solve zlib
|
||||
common_args=(--dist loadfile --tx '4*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python')
|
||||
common_args=(--dist loadfile --tx '4*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python' -x)
|
||||
$(which spack) unit-test --verbose --cov --cov-config=pyproject.toml --cov-report=xml:coverage.xml "${common_args[@]}"
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
|
||||
with:
|
||||
@@ -241,7 +242,7 @@ jobs:
|
||||
env:
|
||||
COVERAGE_FILE: coverage/.coverage-windows
|
||||
run: |
|
||||
spack unit-test --verbose --cov --cov-config=pyproject.toml
|
||||
spack unit-test -x --verbose --cov --cov-config=pyproject.toml
|
||||
./share/spack/qa/validate_last_exit.ps1
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
|
||||
with:
|
||||
|
||||
@@ -70,7 +70,7 @@ Tutorial
|
||||
----------------
|
||||
|
||||
We maintain a
|
||||
[**hands-on tutorial**](https://spack.readthedocs.io/en/latest/tutorial.html).
|
||||
[**hands-on tutorial**](https://spack-tutorial.readthedocs.io/).
|
||||
It covers basic to advanced usage, packaging, developer features, and large HPC
|
||||
deployments. You can do all of the exercises on your own laptop using a
|
||||
Docker container.
|
||||
|
||||
@@ -55,3 +55,11 @@ concretizer:
|
||||
splice:
|
||||
explicit: []
|
||||
automatic: false
|
||||
# Maximum time, in seconds, allowed for the 'solve' phase. If set to 0, there is no time limit.
|
||||
timeout: 0
|
||||
# If set to true, exceeding the timeout will always result in a concretization error. If false,
|
||||
# the best (suboptimal) model computed before the timeout is used.
|
||||
#
|
||||
# Setting this to false yields unreproducible results, so we advise to use that value only
|
||||
# for debugging purposes (e.g. check which constraints can help Spack concretize faster).
|
||||
error_on_timeout: true
|
||||
|
||||
@@ -19,7 +19,7 @@ config:
|
||||
install_tree:
|
||||
root: $spack/opt/spack
|
||||
projections:
|
||||
all: "{architecture.platform}/{architecture.target}/{name}-{version}-{hash}"
|
||||
all: "{architecture}/{compiler.name}-{compiler.version}/{name}-{version}-{hash}"
|
||||
# install_tree can include an optional padded length (int or boolean)
|
||||
# default is False (do not pad)
|
||||
# if padded_length is True, Spack will pad as close to the system max path
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
# -------------------------------------------------------------------------
|
||||
packages:
|
||||
all:
|
||||
compiler:
|
||||
- apple-clang
|
||||
- clang
|
||||
- gcc
|
||||
providers:
|
||||
c: [apple-clang, llvm, gcc]
|
||||
cxx: [apple-clang, llvm, gcc]
|
||||
elf: [libelf]
|
||||
fortran: [gcc]
|
||||
fuse: [macfuse]
|
||||
gl: [apple-gl]
|
||||
glu: [apple-glu]
|
||||
|
||||
@@ -15,18 +15,19 @@
|
||||
# -------------------------------------------------------------------------
|
||||
packages:
|
||||
all:
|
||||
compiler: [gcc, clang, oneapi, xl, nag, fj, aocc]
|
||||
providers:
|
||||
awk: [gawk]
|
||||
armci: [armcimpi]
|
||||
blas: [openblas, amdblis]
|
||||
c: [gcc, llvm, intel-oneapi-compilers, xl, aocc]
|
||||
cxx: [gcc, llvm, intel-oneapi-compilers, xl, aocc]
|
||||
c: [gcc]
|
||||
cxx: [gcc]
|
||||
D: [ldc]
|
||||
daal: [intel-oneapi-daal]
|
||||
elf: [elfutils]
|
||||
fftw-api: [fftw, amdfftw]
|
||||
flame: [libflame, amdlibflame]
|
||||
fortran: [gcc, llvm]
|
||||
fortran: [gcc]
|
||||
fortran-rt: [gcc-runtime, intel-oneapi-runtime]
|
||||
fuse: [libfuse]
|
||||
gl: [glx, osmesa]
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
# -------------------------------------------------------------------------
|
||||
packages:
|
||||
all:
|
||||
compiler:
|
||||
- msvc
|
||||
providers:
|
||||
c : [msvc]
|
||||
cxx: [msvc]
|
||||
mpi: [msmpi]
|
||||
gl: [wgl]
|
||||
|
||||
@@ -1326,6 +1326,7 @@ Required:
|
||||
* Microsoft Visual Studio
|
||||
* Python
|
||||
* Git
|
||||
* 7z
|
||||
|
||||
Optional:
|
||||
* Intel Fortran (needed for some packages)
|
||||
@@ -1391,6 +1392,13 @@ as the project providing Git support on Windows. This is additionally the recomm
|
||||
for installing Git on Windows, a link to which can be found above. Spack requires the
|
||||
utilities vendored by this project.
|
||||
|
||||
"""
|
||||
7zip
|
||||
"""
|
||||
|
||||
A tool for extracting ``.xz`` files is required for extracting source tarballs. The latest 7zip
|
||||
can be located at https://sourceforge.net/projects/sevenzip/.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Step 2: Install and setup Spack
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
15
lib/spack/env/cc
vendored
15
lib/spack/env/cc
vendored
@@ -41,6 +41,10 @@ SPACK_ENV_PATH
|
||||
SPACK_DEBUG_LOG_DIR
|
||||
SPACK_DEBUG_LOG_ID
|
||||
SPACK_COMPILER_SPEC
|
||||
SPACK_CC_RPATH_ARG
|
||||
SPACK_CXX_RPATH_ARG
|
||||
SPACK_F77_RPATH_ARG
|
||||
SPACK_FC_RPATH_ARG
|
||||
SPACK_LINKER_ARG
|
||||
SPACK_SHORT_SPEC
|
||||
SPACK_SYSTEM_DIRS
|
||||
@@ -219,7 +223,6 @@ for param in $params; do
|
||||
if eval "test -z \"\${${param}:-}\""; then
|
||||
die "Spack compiler must be run from Spack! Input '$param' is missing."
|
||||
fi
|
||||
# FIXME (compiler as nodes) add checks on whether `SPACK_XX_RPATH` is set if `SPACK_XX` is set
|
||||
done
|
||||
|
||||
# eval this because SPACK_MANAGED_DIRS and SPACK_SYSTEM_DIRS are inputs we don't wanna loop over.
|
||||
@@ -400,7 +403,7 @@ dtags_to_strip="${SPACK_DTAGS_TO_STRIP}"
|
||||
linker_arg="${SPACK_LINKER_ARG}"
|
||||
|
||||
# Set up rpath variable according to language.
|
||||
rpath="ERROR: RPATH ARG WAS NOT SET, MAYBE THE PACKAGE DOES NOT DEPEND ON ${comp}?"
|
||||
rpath="ERROR: RPATH ARG WAS NOT SET"
|
||||
eval "rpath=\${SPACK_${comp}_RPATH_ARG:?${rpath}}"
|
||||
|
||||
# Dump the mode and exit if the command is dump-mode.
|
||||
@@ -785,17 +788,15 @@ case "$mode" in
|
||||
C)
|
||||
extend spack_flags_list SPACK_ALWAYS_CFLAGS
|
||||
extend spack_flags_list SPACK_CFLAGS
|
||||
preextend flags_list SPACK_TARGET_ARGS_CC
|
||||
;;
|
||||
CXX)
|
||||
extend spack_flags_list SPACK_ALWAYS_CXXFLAGS
|
||||
extend spack_flags_list SPACK_CXXFLAGS
|
||||
preextend flags_list SPACK_TARGET_ARGS_CXX
|
||||
;;
|
||||
F)
|
||||
preextend flags_list SPACK_TARGET_ARGS_FORTRAN
|
||||
;;
|
||||
esac
|
||||
|
||||
# prepend target args
|
||||
preextend flags_list SPACK_TARGET_ARGS
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
Callable,
|
||||
Deque,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
Match,
|
||||
@@ -2838,6 +2839,25 @@ def temporary_dir(
|
||||
remove_directory_contents(tmp_dir)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def edit_in_place_through_temporary_file(file_path: str) -> Generator[str, None, None]:
|
||||
"""Context manager for modifying ``file_path`` in place, preserving its inode and hardlinks,
|
||||
for functions or external tools that do not support in-place editing. Notice that this function
|
||||
is unsafe in that it works with paths instead of a file descriptors, but this is by design,
|
||||
since we assume the call site will create a new inode at the same path."""
|
||||
tmp_fd, tmp_path = tempfile.mkstemp(
|
||||
dir=os.path.dirname(file_path), prefix=f"{os.path.basename(file_path)}."
|
||||
)
|
||||
# windows cannot replace a file with open fds, so close since the call site needs to replace.
|
||||
os.close(tmp_fd)
|
||||
try:
|
||||
shutil.copyfile(file_path, tmp_path, follow_symlinks=True)
|
||||
yield tmp_path
|
||||
shutil.copyfile(tmp_path, file_path, follow_symlinks=True)
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
|
||||
def filesummary(path, print_bytes=16) -> Tuple[int, bytes]:
|
||||
"""Create a small summary of the given file. Does not error
|
||||
when file does not exist.
|
||||
|
||||
@@ -73,7 +73,7 @@ def index_by(objects, *funcs):
|
||||
if isinstance(f, str):
|
||||
f = lambda x: getattr(x, funcs[0])
|
||||
elif isinstance(f, tuple):
|
||||
f = lambda x: tuple(getattr(x, p, None) for p in funcs[0])
|
||||
f = lambda x: tuple(getattr(x, p) for p in funcs[0])
|
||||
|
||||
result = {}
|
||||
for o in objects:
|
||||
@@ -995,8 +995,11 @@ def _receive_forwarded(self, context: str, exc: Exception, tb: List[str]):
|
||||
def grouped_message(self, with_tracebacks: bool = True) -> str:
|
||||
"""Print out an error message coalescing all the forwarded errors."""
|
||||
each_exception_message = [
|
||||
"\n\t{0} raised {1}: {2}\n{3}".format(
|
||||
context, exc.__class__.__name__, exc, f"\n{''.join(tb)}" if with_tracebacks else ""
|
||||
"{0} raised {1}: {2}{3}".format(
|
||||
context,
|
||||
exc.__class__.__name__,
|
||||
exc,
|
||||
"\n{0}".format("".join(tb)) if with_tracebacks else "",
|
||||
)
|
||||
for context, exc, tb in self.exceptions
|
||||
]
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import spack.util.git
|
||||
|
||||
#: PEP440 canonical <major>.<minor>.<micro>.<devN> string
|
||||
__version__ = "1.0.0-alpha.1"
|
||||
__version__ = "0.24.0.dev0"
|
||||
spack_version = __version__
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ def _search_duplicate_compilers(error_cls):
|
||||
|
||||
import spack.builder
|
||||
import spack.config
|
||||
import spack.deptypes
|
||||
import spack.fetch_strategy
|
||||
import spack.patch
|
||||
import spack.repo
|
||||
@@ -1015,14 +1014,7 @@ def _issues_in_depends_on_directive(pkgs, error_cls):
|
||||
# depends_on('foo+bar ^fee+baz')
|
||||
#
|
||||
# but we'd like to have two dependencies listed instead.
|
||||
nested_dependencies = dep.spec.edges_to_dependencies()
|
||||
# Filter out pure build dependencies, like:
|
||||
#
|
||||
# depends_on('foo+bar%gcc')
|
||||
#
|
||||
nested_dependencies = [
|
||||
x for x in nested_dependencies if x.depflag != spack.deptypes.BUILD
|
||||
]
|
||||
nested_dependencies = dep.spec.dependencies()
|
||||
if nested_dependencies:
|
||||
summary = f"{pkg_name}: nested dependency declaration '{dep.spec}'"
|
||||
ndir = len(nested_dependencies) + 1
|
||||
|
||||
@@ -765,14 +765,7 @@ def tarball_directory_name(spec):
|
||||
Return name of the tarball directory according to the convention
|
||||
<os>-<architecture>/<compiler>/<package>-<version>/
|
||||
"""
|
||||
if spec.original_spec_format() < 5:
|
||||
compiler = spec.annotations.compiler_node_attribute
|
||||
assert compiler is not None, "a compiler spec is expected"
|
||||
return spec.format_path(
|
||||
f"{spec.architecture}/{compiler.name}-{compiler.version}/{spec.name}-{spec.version}"
|
||||
)
|
||||
|
||||
return spec.format_path(f"{spec.architecture.platform}/{spec.name}-{spec.version}")
|
||||
return spec.format_path("{architecture}/{compiler.name}-{compiler.version}/{name}-{version}")
|
||||
|
||||
|
||||
def tarball_name(spec, ext):
|
||||
@@ -780,17 +773,9 @@ def tarball_name(spec, ext):
|
||||
Return the name of the tarfile according to the convention
|
||||
<os>-<architecture>-<package>-<dag_hash><ext>
|
||||
"""
|
||||
if spec.original_spec_format() < 5:
|
||||
compiler = spec.annotations.compiler_node_attribute
|
||||
assert compiler is not None, "a compiler spec is expected"
|
||||
spec_formatted = (
|
||||
f"{spec.architecture}-{compiler.name}-{compiler.version}-{spec.name}"
|
||||
f"-{spec.version}-{spec.dag_hash()}"
|
||||
)
|
||||
else:
|
||||
spec_formatted = (
|
||||
f"{spec.architecture.platform}-{spec.name}-{spec.version}-{spec.dag_hash()}"
|
||||
)
|
||||
spec_formatted = spec.format_path(
|
||||
"{architecture}-{compiler.name}-{compiler.version}-{name}-{version}-{hash}"
|
||||
)
|
||||
return f"{spec_formatted}{ext}"
|
||||
|
||||
|
||||
@@ -2349,7 +2334,9 @@ def is_backup_file(file):
|
||||
if not codesign:
|
||||
return
|
||||
for binary in changed_files:
|
||||
codesign("-fs-", binary)
|
||||
# preserve the original inode by running codesign on a copy
|
||||
with fsys.edit_in_place_through_temporary_file(binary) as tmp_binary:
|
||||
codesign("-fs-", tmp_binary)
|
||||
|
||||
# If we are installing back to the same location
|
||||
# relocate the sbang location if the spack directory changed
|
||||
|
||||
@@ -227,13 +227,12 @@ def _root_spec(spec_str: str) -> str:
|
||||
# Add a compiler and platform requirement to the root spec.
|
||||
platform = str(spack.platforms.host())
|
||||
|
||||
# FIXME (compiler as nodes): recover the compiler for source bootstrapping
|
||||
# if platform == "darwin":
|
||||
# spec_str += " %apple-clang"
|
||||
if platform == "windows":
|
||||
if platform == "darwin":
|
||||
spec_str += " %apple-clang"
|
||||
elif platform == "windows":
|
||||
spec_str += " %msvc"
|
||||
# elif platform == "linux":
|
||||
# spec_str += " %gcc"
|
||||
elif platform == "linux":
|
||||
spec_str += " %gcc"
|
||||
elif platform == "freebsd":
|
||||
spec_str += " %clang"
|
||||
spec_str += f" platform={platform}"
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
import spack.compilers.config
|
||||
import spack.compilers.libraries
|
||||
import spack.config
|
||||
import spack.compiler
|
||||
import spack.compilers
|
||||
import spack.platforms
|
||||
import spack.spec
|
||||
import spack.traverse
|
||||
@@ -40,7 +39,7 @@ def __init__(self, configuration):
|
||||
|
||||
self.external_cmake, self.external_bison = self._externals_from_yaml(configuration)
|
||||
|
||||
def _valid_compiler_or_raise(self):
|
||||
def _valid_compiler_or_raise(self) -> "spack.compiler.Compiler":
|
||||
if str(self.host_platform) == "linux":
|
||||
compiler_name = "gcc"
|
||||
elif str(self.host_platform) == "darwin":
|
||||
@@ -48,30 +47,17 @@ def _valid_compiler_or_raise(self):
|
||||
elif str(self.host_platform) == "windows":
|
||||
compiler_name = "msvc"
|
||||
elif str(self.host_platform) == "freebsd":
|
||||
compiler_name = "llvm"
|
||||
compiler_name = "clang"
|
||||
else:
|
||||
raise RuntimeError(f"Cannot bootstrap clingo from sources on {self.host_platform}")
|
||||
|
||||
candidates = [
|
||||
x
|
||||
for x in spack.compilers.config.CompilerFactory.from_packages_yaml(spack.config.CONFIG)
|
||||
if x.name == compiler_name
|
||||
]
|
||||
candidates = spack.compilers.compilers_for_spec(
|
||||
compiler_name, arch_spec=self.host_architecture
|
||||
)
|
||||
if not candidates:
|
||||
raise RuntimeError(
|
||||
f"Cannot find any version of {compiler_name} to bootstrap clingo from sources"
|
||||
)
|
||||
candidates.sort(key=lambda x: x.version, reverse=True)
|
||||
best = candidates[0]
|
||||
# Get compilers for bootstrapping from the 'builtin' repository
|
||||
best.namespace = "builtin"
|
||||
# If the compiler does not support C++ 14, fail with a legible error message
|
||||
try:
|
||||
_ = best.package.standard_flag(language="cxx", standard="14")
|
||||
except RuntimeError as e:
|
||||
raise RuntimeError(
|
||||
"cannot find a compiler supporting C++ 14 [needed to bootstrap clingo]"
|
||||
) from e
|
||||
candidates.sort(key=lambda x: x.spec.version, reverse=True)
|
||||
return candidates[0]
|
||||
|
||||
def _externals_from_yaml(
|
||||
@@ -90,6 +76,9 @@ def _externals_from_yaml(
|
||||
if not s.satisfies(requirements[pkg_name]):
|
||||
continue
|
||||
|
||||
if not s.intersects(f"%{self.host_compiler.spec}"):
|
||||
continue
|
||||
|
||||
if not s.intersects(f"arch={self.host_architecture}"):
|
||||
continue
|
||||
|
||||
@@ -122,10 +111,11 @@ def concretize(self) -> "spack.spec.Spec":
|
||||
# Tweak it to conform to the host architecture
|
||||
for node in s.traverse():
|
||||
node.architecture.os = str(self.host_os)
|
||||
node.compiler = self.host_compiler.spec
|
||||
node.architecture = self.host_architecture
|
||||
|
||||
if node.name == "gcc-runtime":
|
||||
node.versions = self.host_compiler.versions
|
||||
node.versions = self.host_compiler.spec.versions
|
||||
|
||||
for edge in spack.traverse.traverse_edges([s], cover="edges"):
|
||||
if edge.spec.name == "python":
|
||||
@@ -137,9 +127,6 @@ def concretize(self) -> "spack.spec.Spec":
|
||||
if edge.spec.name == "cmake" and self.external_cmake:
|
||||
edge.spec = self.external_cmake
|
||||
|
||||
if edge.spec.name == self.host_compiler.name:
|
||||
edge.spec = self.host_compiler
|
||||
|
||||
if "libc" in edge.virtuals:
|
||||
edge.spec = self.host_libc
|
||||
|
||||
@@ -155,12 +142,12 @@ def python_external_spec(self) -> "spack.spec.Spec":
|
||||
return self._external_spec(result)
|
||||
|
||||
def libc_external_spec(self) -> "spack.spec.Spec":
|
||||
detector = spack.compilers.libraries.CompilerPropertyDetector(self.host_compiler)
|
||||
result = detector.default_libc()
|
||||
result = self.host_compiler.default_libc
|
||||
return self._external_spec(result)
|
||||
|
||||
def _external_spec(self, initial_spec) -> "spack.spec.Spec":
|
||||
initial_spec.namespace = "builtin"
|
||||
initial_spec.compiler = self.host_compiler.spec
|
||||
initial_spec.architecture = self.host_architecture
|
||||
for flag_type in spack.spec.FlagMap.valid_compiler_flags():
|
||||
initial_spec.compiler_flags[flag_type] = []
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
from llnl.util import tty
|
||||
|
||||
import spack.compilers.config
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.environment
|
||||
import spack.modules
|
||||
@@ -143,8 +143,8 @@ def _bootstrap_config_scopes() -> Sequence["spack.config.ConfigScope"]:
|
||||
|
||||
def _add_compilers_if_missing() -> None:
|
||||
arch = spack.spec.ArchSpec.frontend_arch()
|
||||
if not spack.compilers.config.compilers_for_arch(arch):
|
||||
spack.compilers.config.find_compilers()
|
||||
if not spack.compilers.compilers_for_arch(arch):
|
||||
spack.compilers.find_compilers()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
||||
@@ -281,12 +281,7 @@ def try_import(self, module: str, abstract_spec_str: str) -> bool:
|
||||
|
||||
# Install the spec that should make the module importable
|
||||
with spack.config.override(self.mirror_scope):
|
||||
PackageInstaller(
|
||||
[concrete_spec.package],
|
||||
fail_fast=True,
|
||||
package_use_cache=False,
|
||||
dependencies_use_cache=False,
|
||||
).install()
|
||||
PackageInstaller([concrete_spec.package], fail_fast=True).install()
|
||||
|
||||
if _try_import_from_store(module, query_spec=concrete_spec, query_info=info):
|
||||
self.last_search = info
|
||||
@@ -322,10 +317,11 @@ def create_bootstrapper(conf: ConfigDictionary):
|
||||
return _bootstrap_methods[btype](conf)
|
||||
|
||||
|
||||
def source_is_enabled(conf: ConfigDictionary):
|
||||
def source_is_enabled_or_raise(conf: ConfigDictionary):
|
||||
"""Raise ValueError if the source is not enabled for bootstrapping"""
|
||||
trusted, name = spack.config.get("bootstrap:trusted"), conf["name"]
|
||||
return trusted.get(name, False)
|
||||
if not trusted.get(name, False):
|
||||
raise ValueError("source is not trusted")
|
||||
|
||||
|
||||
def ensure_module_importable_or_raise(module: str, abstract_spec: Optional[str] = None):
|
||||
@@ -355,10 +351,8 @@ def ensure_module_importable_or_raise(module: str, abstract_spec: Optional[str]
|
||||
exception_handler = GroupedExceptionHandler()
|
||||
|
||||
for current_config in bootstrapping_sources():
|
||||
if not source_is_enabled(current_config):
|
||||
continue
|
||||
|
||||
with exception_handler.forward(current_config["name"], Exception):
|
||||
source_is_enabled_or_raise(current_config)
|
||||
current_bootstrapper = create_bootstrapper(current_config)
|
||||
if current_bootstrapper.try_import(module, abstract_spec):
|
||||
return
|
||||
@@ -370,7 +364,11 @@ def ensure_module_importable_or_raise(module: str, abstract_spec: Optional[str]
|
||||
msg = f'cannot bootstrap the "{module}" Python module '
|
||||
if abstract_spec:
|
||||
msg += f'from spec "{abstract_spec}" '
|
||||
msg += exception_handler.grouped_message(with_tracebacks=tty.is_debug())
|
||||
if tty.is_debug():
|
||||
msg += exception_handler.grouped_message(with_tracebacks=True)
|
||||
else:
|
||||
msg += exception_handler.grouped_message(with_tracebacks=False)
|
||||
msg += "\nRun `spack --debug ...` for more detailed errors"
|
||||
raise ImportError(msg)
|
||||
|
||||
|
||||
@@ -408,9 +406,8 @@ def ensure_executables_in_path_or_raise(
|
||||
exception_handler = GroupedExceptionHandler()
|
||||
|
||||
for current_config in bootstrapping_sources():
|
||||
if not source_is_enabled(current_config):
|
||||
continue
|
||||
with exception_handler.forward(current_config["name"], Exception):
|
||||
source_is_enabled_or_raise(current_config)
|
||||
current_bootstrapper = create_bootstrapper(current_config)
|
||||
if current_bootstrapper.try_search_path(executables, abstract_spec):
|
||||
# Additional environment variables needed
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -37,6 +37,7 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
@@ -59,7 +60,7 @@
|
||||
import spack.build_systems.meson
|
||||
import spack.build_systems.python
|
||||
import spack.builder
|
||||
import spack.compilers.libraries
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.deptypes as dt
|
||||
import spack.error
|
||||
@@ -73,6 +74,7 @@
|
||||
import spack.store
|
||||
import spack.subprocess_context
|
||||
import spack.util.executable
|
||||
import spack.util.libc
|
||||
from spack import traverse
|
||||
from spack.context import Context
|
||||
from spack.error import InstallError, NoHeadersError, NoLibrariesError
|
||||
@@ -295,10 +297,62 @@ def _add_werror_handling(keep_werror, env):
|
||||
env.set("SPACK_COMPILER_FLAGS_REPLACE", " ".join(["|".join(item) for item in replace_flags]))
|
||||
|
||||
|
||||
def set_wrapper_environment_variables_for_flags(pkg, env):
|
||||
def set_compiler_environment_variables(pkg, env):
|
||||
assert pkg.spec.concrete
|
||||
compiler = pkg.compiler
|
||||
spec = pkg.spec
|
||||
|
||||
# Make sure the executables for this compiler exist
|
||||
compiler.verify_executables()
|
||||
|
||||
# Set compiler variables used by CMake and autotools
|
||||
assert all(key in compiler.link_paths for key in ("cc", "cxx", "f77", "fc"))
|
||||
|
||||
# Populate an object with the list of environment modifications
|
||||
# and return it
|
||||
# TODO : add additional kwargs for better diagnostics, like requestor,
|
||||
# ttyout, ttyerr, etc.
|
||||
link_dir = spack.paths.build_env_path
|
||||
|
||||
# Set SPACK compiler variables so that our wrapper knows what to
|
||||
# call. If there is no compiler configured then use a default
|
||||
# wrapper which will emit an error if it is used.
|
||||
if compiler.cc:
|
||||
env.set("SPACK_CC", compiler.cc)
|
||||
env.set("CC", os.path.join(link_dir, compiler.link_paths["cc"]))
|
||||
else:
|
||||
env.set("CC", os.path.join(link_dir, "cc"))
|
||||
if compiler.cxx:
|
||||
env.set("SPACK_CXX", compiler.cxx)
|
||||
env.set("CXX", os.path.join(link_dir, compiler.link_paths["cxx"]))
|
||||
else:
|
||||
env.set("CC", os.path.join(link_dir, "c++"))
|
||||
if compiler.f77:
|
||||
env.set("SPACK_F77", compiler.f77)
|
||||
env.set("F77", os.path.join(link_dir, compiler.link_paths["f77"]))
|
||||
else:
|
||||
env.set("F77", os.path.join(link_dir, "f77"))
|
||||
if compiler.fc:
|
||||
env.set("SPACK_FC", compiler.fc)
|
||||
env.set("FC", os.path.join(link_dir, compiler.link_paths["fc"]))
|
||||
else:
|
||||
env.set("FC", os.path.join(link_dir, "fc"))
|
||||
|
||||
# Set SPACK compiler rpath flags so that our wrapper knows what to use
|
||||
env.set("SPACK_CC_RPATH_ARG", compiler.cc_rpath_arg)
|
||||
env.set("SPACK_CXX_RPATH_ARG", compiler.cxx_rpath_arg)
|
||||
env.set("SPACK_F77_RPATH_ARG", compiler.f77_rpath_arg)
|
||||
env.set("SPACK_FC_RPATH_ARG", compiler.fc_rpath_arg)
|
||||
env.set("SPACK_LINKER_ARG", compiler.linker_arg)
|
||||
|
||||
# Check whether we want to force RPATH or RUNPATH
|
||||
if spack.config.get("config:shared_linking:type") == "rpath":
|
||||
env.set("SPACK_DTAGS_TO_STRIP", compiler.enable_new_dtags)
|
||||
env.set("SPACK_DTAGS_TO_ADD", compiler.disable_new_dtags)
|
||||
else:
|
||||
env.set("SPACK_DTAGS_TO_STRIP", compiler.disable_new_dtags)
|
||||
env.set("SPACK_DTAGS_TO_ADD", compiler.enable_new_dtags)
|
||||
|
||||
if pkg.keep_werror is not None:
|
||||
keep_werror = pkg.keep_werror
|
||||
else:
|
||||
@@ -306,6 +360,10 @@ def set_wrapper_environment_variables_for_flags(pkg, env):
|
||||
|
||||
_add_werror_handling(keep_werror, env)
|
||||
|
||||
# Set the target parameters that the compiler will add
|
||||
isa_arg = optimization_flags(compiler, spec.target)
|
||||
env.set("SPACK_TARGET_ARGS", isa_arg)
|
||||
|
||||
# Trap spack-tracked compiler flags as appropriate.
|
||||
# env_flags are easy to accidentally override.
|
||||
inject_flags = {}
|
||||
@@ -339,27 +397,74 @@ def set_wrapper_environment_variables_for_flags(pkg, env):
|
||||
env.set(flag.upper(), " ".join(f for f in env_flags[flag]))
|
||||
pkg.flags_to_build_system_args(build_system_flags)
|
||||
|
||||
env.set("SPACK_COMPILER_SPEC", str(spec.compiler))
|
||||
|
||||
env.set("SPACK_SYSTEM_DIRS", SYSTEM_DIR_CASE_ENTRY)
|
||||
|
||||
# FIXME (compiler as nodes): recover this one in the correct packages
|
||||
# compiler.setup_custom_environment(pkg, env)
|
||||
compiler.setup_custom_environment(pkg, env)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def optimization_flags(compiler, target):
|
||||
if spack.compilers.is_mixed_toolchain(compiler):
|
||||
msg = (
|
||||
"microarchitecture specific optimizations are not "
|
||||
"supported yet on mixed compiler toolchains [check"
|
||||
f" {compiler.name}@{compiler.version} for further details]"
|
||||
)
|
||||
tty.debug(msg)
|
||||
return ""
|
||||
|
||||
# Try to check if the current compiler comes with a version number or
|
||||
# has an unexpected suffix. If so, treat it as a compiler with a
|
||||
# custom spec.
|
||||
version_number, _ = archspec.cpu.version_components(compiler.version.dotted_numeric_string)
|
||||
compiler_version = compiler.version
|
||||
version_number, suffix = archspec.cpu.version_components(compiler.version)
|
||||
if not version_number or suffix:
|
||||
try:
|
||||
compiler_version = compiler.real_version
|
||||
except spack.util.executable.ProcessError as e:
|
||||
# log this and just return compiler.version instead
|
||||
tty.debug(str(e))
|
||||
|
||||
try:
|
||||
result = target.optimization_flags(compiler.name, version_number)
|
||||
result = target.optimization_flags(compiler.name, compiler_version.dotted_numeric_string)
|
||||
except (ValueError, archspec.cpu.UnsupportedMicroarchitecture):
|
||||
result = ""
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class FilterDefaultDynamicLinkerSearchPaths:
|
||||
"""Remove rpaths to directories that are default search paths of the dynamic linker."""
|
||||
|
||||
def __init__(self, dynamic_linker: Optional[str]) -> None:
|
||||
# Identify directories by (inode, device) tuple, which handles symlinks too.
|
||||
self.default_path_identifiers: Set[Tuple[int, int]] = set()
|
||||
if not dynamic_linker:
|
||||
return
|
||||
for path in spack.util.libc.default_search_paths_from_dynamic_linker(dynamic_linker):
|
||||
try:
|
||||
s = os.stat(path)
|
||||
if stat.S_ISDIR(s.st_mode):
|
||||
self.default_path_identifiers.add((s.st_ino, s.st_dev))
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
def is_dynamic_loader_default_path(self, p: str) -> bool:
|
||||
try:
|
||||
s = os.stat(p)
|
||||
return (s.st_ino, s.st_dev) in self.default_path_identifiers
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def __call__(self, dirs: List[str]) -> List[str]:
|
||||
if not self.default_path_identifiers:
|
||||
return dirs
|
||||
return [p for p in dirs if not self.is_dynamic_loader_default_path(p)]
|
||||
|
||||
|
||||
def set_wrapper_variables(pkg, env):
|
||||
"""Set environment variables used by the Spack compiler wrapper (which have the prefix
|
||||
`SPACK_`) and also add the compiler wrappers to PATH.
|
||||
@@ -368,8 +473,39 @@ def set_wrapper_variables(pkg, env):
|
||||
this function computes these options in a manner that is intended to match the DAG traversal
|
||||
order in `SetupContext`. TODO: this is not the case yet, we're using post order, SetupContext
|
||||
is using topo order."""
|
||||
# Set compiler flags injected from the spec
|
||||
set_wrapper_environment_variables_for_flags(pkg, env)
|
||||
# Set environment variables if specified for
|
||||
# the given compiler
|
||||
compiler = pkg.compiler
|
||||
env.extend(spack.schema.environment.parse(compiler.environment))
|
||||
|
||||
if compiler.extra_rpaths:
|
||||
extra_rpaths = ":".join(compiler.extra_rpaths)
|
||||
env.set("SPACK_COMPILER_EXTRA_RPATHS", extra_rpaths)
|
||||
|
||||
# Add spack build environment path with compiler wrappers first in
|
||||
# the path. We add the compiler wrapper path, which includes default
|
||||
# wrappers (cc, c++, f77, f90), AND a subdirectory containing
|
||||
# compiler-specific symlinks. The latter ensures that builds that
|
||||
# are sensitive to the *name* of the compiler see the right name when
|
||||
# we're building with the wrappers.
|
||||
#
|
||||
# Conflicts on case-insensitive systems (like "CC" and "cc") are
|
||||
# handled by putting one in the <build_env_path>/case-insensitive
|
||||
# directory. Add that to the path too.
|
||||
env_paths = []
|
||||
compiler_specific = os.path.join(
|
||||
spack.paths.build_env_path, os.path.dirname(pkg.compiler.link_paths["cc"])
|
||||
)
|
||||
for item in [spack.paths.build_env_path, compiler_specific]:
|
||||
env_paths.append(item)
|
||||
ci = os.path.join(item, "case-insensitive")
|
||||
if os.path.isdir(ci):
|
||||
env_paths.append(ci)
|
||||
|
||||
tty.debug("Adding compiler bin/ paths: " + " ".join(env_paths))
|
||||
for item in env_paths:
|
||||
env.prepend_path("PATH", item)
|
||||
env.set_path(SPACK_ENV_PATH, env_paths)
|
||||
|
||||
# Working directory for the spack command itself, for debug logs.
|
||||
if spack.config.get("config:debug"):
|
||||
@@ -435,15 +571,22 @@ def set_wrapper_variables(pkg, env):
|
||||
lib_path = os.path.join(pkg.prefix, libdir)
|
||||
rpath_dirs.insert(0, lib_path)
|
||||
|
||||
filter_default_dynamic_linker_search_paths = FilterDefaultDynamicLinkerSearchPaths(
|
||||
pkg.compiler.default_dynamic_linker
|
||||
)
|
||||
|
||||
# TODO: filter_system_paths is again wrong (and probably unnecessary due to the is_system_path
|
||||
# branch above). link_dirs should be filtered with entries from _parse_link_paths.
|
||||
link_dirs = list(dedupe(filter_system_paths(link_dirs)))
|
||||
include_dirs = list(dedupe(filter_system_paths(include_dirs)))
|
||||
rpath_dirs = list(dedupe(filter_system_paths(rpath_dirs)))
|
||||
rpath_dirs = filter_default_dynamic_linker_search_paths(rpath_dirs)
|
||||
|
||||
default_dynamic_linker_filter = spack.compilers.libraries.dynamic_linker_filter_for(pkg.spec)
|
||||
if default_dynamic_linker_filter:
|
||||
rpath_dirs = default_dynamic_linker_filter(rpath_dirs)
|
||||
# TODO: implicit_rpaths is prefiltered by is_system_path, that should be removed in favor of
|
||||
# just this filter.
|
||||
implicit_rpaths = filter_default_dynamic_linker_search_paths(pkg.compiler.implicit_rpaths())
|
||||
if implicit_rpaths:
|
||||
env.set("SPACK_COMPILER_IMPLICIT_RPATHS", ":".join(implicit_rpaths))
|
||||
|
||||
# Spack managed directories include the stage, store and upstream stores. We extend this with
|
||||
# their real paths to make it more robust (e.g. /tmp vs /private/tmp on macOS).
|
||||
@@ -499,19 +642,22 @@ def set_package_py_globals(pkg, context: Context = Context.BUILD):
|
||||
# Put spack compiler paths in module scope. (Some packages use it
|
||||
# in setup_run_environment etc, so don't put it context == build)
|
||||
link_dir = spack.paths.build_env_path
|
||||
pkg_compiler = None
|
||||
try:
|
||||
pkg_compiler = pkg.compiler
|
||||
except spack.compilers.NoCompilerForSpecError as e:
|
||||
tty.debug(f"cannot set 'spack_cc': {str(e)}")
|
||||
|
||||
# FIXME (compiler as nodes): make this more general, and not tied to three languages
|
||||
# Maybe add a callback?
|
||||
global_names = {
|
||||
"c": ("spack_cc",),
|
||||
"cxx": ("spack_cxx",),
|
||||
"fortran": ("spack_fc", "spack_f77"),
|
||||
}
|
||||
for language in ("c", "cxx", "fortran"):
|
||||
spec = pkg.spec.dependencies(virtuals=[language])
|
||||
value = None if not spec else os.path.join(link_dir, spec[0].package.link_paths[language])
|
||||
for name in global_names[language]:
|
||||
setattr(module, name, value)
|
||||
if pkg_compiler is not None:
|
||||
module.spack_cc = os.path.join(link_dir, pkg_compiler.link_paths["cc"])
|
||||
module.spack_cxx = os.path.join(link_dir, pkg_compiler.link_paths["cxx"])
|
||||
module.spack_f77 = os.path.join(link_dir, pkg_compiler.link_paths["f77"])
|
||||
module.spack_fc = os.path.join(link_dir, pkg_compiler.link_paths["fc"])
|
||||
else:
|
||||
module.spack_cc = None
|
||||
module.spack_cxx = None
|
||||
module.spack_f77 = None
|
||||
module.spack_fc = None
|
||||
|
||||
# Useful directories within the prefix are encapsulated in
|
||||
# a Prefix object.
|
||||
@@ -678,6 +824,7 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD):
|
||||
context == Context.TEST and pkg.test_requires_compiler
|
||||
)
|
||||
if need_compiler:
|
||||
set_compiler_environment_variables(pkg, env_mods)
|
||||
set_wrapper_variables(pkg, env_mods)
|
||||
|
||||
# Platform specific setup goes before package specific setup. This is for setting
|
||||
@@ -702,6 +849,11 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD):
|
||||
|
||||
# Load modules on an already clean environment, just before applying Spack's
|
||||
# own environment modifications. This ensures Spack controls CC/CXX/... variables.
|
||||
if need_compiler:
|
||||
tty.debug("setup_package: loading compiler modules")
|
||||
for mod in pkg.compiler.modules:
|
||||
load_module(mod)
|
||||
|
||||
load_external_modules(pkg)
|
||||
|
||||
# Make sure nothing's strange about the Spack environment.
|
||||
@@ -730,6 +882,9 @@ def __init__(self, *roots: spack.spec.Spec, context: Context):
|
||||
elif context == Context.RUN:
|
||||
self.root_depflag = dt.RUN | dt.LINK
|
||||
|
||||
def accept(self, item):
|
||||
return True
|
||||
|
||||
def neighbors(self, item):
|
||||
spec = item.edge.spec
|
||||
if spec.dag_hash() in self.root_hashes:
|
||||
@@ -767,19 +922,19 @@ def effective_deptypes(
|
||||
a flag specifying in what way they do so. The list is ordered topologically
|
||||
from root to leaf, meaning that environment modifications should be applied
|
||||
in reverse so that dependents override dependencies, not the other way around."""
|
||||
visitor = traverse.TopoVisitor(
|
||||
EnvironmentVisitor(*specs, context=context),
|
||||
key=lambda x: x.dag_hash(),
|
||||
topo_sorted_edges = traverse.traverse_topo_edges_generator(
|
||||
traverse.with_artificial_edges(specs),
|
||||
visitor=EnvironmentVisitor(*specs, context=context),
|
||||
key=traverse.by_dag_hash,
|
||||
root=True,
|
||||
all_edges=True,
|
||||
)
|
||||
traverse.traverse_depth_first_with_visitor(traverse.with_artificial_edges(specs), visitor)
|
||||
|
||||
# Dictionary with "no mode" as default value, so it's easy to write modes[x] |= flag.
|
||||
use_modes = defaultdict(lambda: UseMode(0))
|
||||
nodes_with_type = []
|
||||
|
||||
for edge in visitor.edges:
|
||||
for edge in topo_sorted_edges:
|
||||
parent, child, depflag = edge.parent, edge.spec, edge.depflag
|
||||
|
||||
# Mark the starting point
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
import spack.build_environment
|
||||
import spack.builder
|
||||
import spack.compilers.libraries
|
||||
import spack.error
|
||||
import spack.package_base
|
||||
import spack.phase_callbacks
|
||||
@@ -397,44 +396,33 @@ def _do_patch_libtool(self) -> None:
|
||||
markers[tag] = "LIBTOOL TAG CONFIG: {0}".format(tag.upper())
|
||||
|
||||
# Replace empty linker flag prefixes:
|
||||
if self.spec.satisfies("%nag"):
|
||||
if self.pkg.compiler.name == "nag":
|
||||
# Nag is mixed with gcc and g++, which are recognized correctly.
|
||||
# Therefore, we change only Fortran values:
|
||||
nag_pkg = self.spec["fortran"].package
|
||||
for tag in ["fc", "f77"]:
|
||||
marker = markers[tag]
|
||||
x.filter(
|
||||
regex='^wl=""$',
|
||||
repl=f'wl="{nag_pkg.linker_arg}"',
|
||||
start_at=f"# ### BEGIN {marker}",
|
||||
stop_at=f"# ### END {marker}",
|
||||
repl='wl="{0}"'.format(self.pkg.compiler.linker_arg),
|
||||
start_at="# ### BEGIN {0}".format(marker),
|
||||
stop_at="# ### END {0}".format(marker),
|
||||
)
|
||||
else:
|
||||
compiler_spec = spack.compilers.libraries.compiler_spec(self.spec)
|
||||
if compiler_spec:
|
||||
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(compiler_spec.package.linker_arg))
|
||||
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.pkg.compiler.linker_arg))
|
||||
|
||||
# Replace empty PIC flag values:
|
||||
for compiler, marker in markers.items():
|
||||
if compiler == "cc":
|
||||
language = "c"
|
||||
elif compiler == "cxx":
|
||||
language = "cxx"
|
||||
else:
|
||||
language = "fortran"
|
||||
|
||||
if language not in self.spec:
|
||||
continue
|
||||
|
||||
for cc, marker in markers.items():
|
||||
x.filter(
|
||||
regex='^pic_flag=""$',
|
||||
repl=f'pic_flag="{self.spec[language].package.pic_flag}"',
|
||||
start_at=f"# ### BEGIN {marker}",
|
||||
stop_at=f"# ### END {marker}",
|
||||
repl='pic_flag="{0}"'.format(
|
||||
getattr(self.pkg.compiler, "{0}_pic_flag".format(cc))
|
||||
),
|
||||
start_at="# ### BEGIN {0}".format(marker),
|
||||
stop_at="# ### END {0}".format(marker),
|
||||
)
|
||||
|
||||
# Other compiler-specific patches:
|
||||
if self.spec.satisfies("%fj"):
|
||||
if self.pkg.compiler.name == "fj":
|
||||
x.filter(regex="-nostdlib", repl="", string=True)
|
||||
rehead = r"/\S*/"
|
||||
for o in [
|
||||
@@ -447,7 +435,7 @@ def _do_patch_libtool(self) -> None:
|
||||
r"crtendS\.o",
|
||||
]:
|
||||
x.filter(regex=(rehead + o), repl="")
|
||||
elif self.spec.satisfies("%nag"):
|
||||
elif self.pkg.compiler.name == "nag":
|
||||
for tag in ["fc", "f77"]:
|
||||
marker = markers[tag]
|
||||
start_at = "# ### BEGIN {0}".format(marker)
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.phase_callbacks
|
||||
from spack.directives import depends_on
|
||||
|
||||
from .cmake import CMakeBuilder, CMakePackage
|
||||
|
||||
@@ -69,7 +68,12 @@ class CachedCMakeBuilder(CMakeBuilder):
|
||||
|
||||
@property
|
||||
def cache_name(self):
|
||||
return f"{self.pkg.name}-{self.spec.architecture.platform}-{self.spec.dag_hash()}.cmake"
|
||||
return "{0}-{1}-{2}@{3}.cmake".format(
|
||||
self.pkg.name,
|
||||
self.pkg.spec.architecture,
|
||||
self.pkg.spec.compiler.name,
|
||||
self.pkg.spec.compiler.version,
|
||||
)
|
||||
|
||||
@property
|
||||
def cache_path(self):
|
||||
@@ -112,9 +116,7 @@ def initconfig_compiler_entries(self):
|
||||
# Fortran compiler is optional
|
||||
if "FC" in os.environ:
|
||||
spack_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", os.environ["FC"])
|
||||
system_fc_entry = cmake_cache_path(
|
||||
"CMAKE_Fortran_COMPILER", self.spec["fortran"].package.fortran
|
||||
)
|
||||
system_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", self.pkg.compiler.fc)
|
||||
else:
|
||||
spack_fc_entry = "# No Fortran compiler defined in spec"
|
||||
system_fc_entry = "# No Fortran compiler defined in spec"
|
||||
@@ -130,8 +132,8 @@ def initconfig_compiler_entries(self):
|
||||
" " + cmake_cache_path("CMAKE_CXX_COMPILER", os.environ["CXX"]),
|
||||
" " + spack_fc_entry,
|
||||
"else()\n",
|
||||
" " + cmake_cache_path("CMAKE_C_COMPILER", self.spec["c"].package.cc),
|
||||
" " + cmake_cache_path("CMAKE_CXX_COMPILER", self.spec["cxx"].package.cxx),
|
||||
" " + cmake_cache_path("CMAKE_C_COMPILER", self.pkg.compiler.cc),
|
||||
" " + cmake_cache_path("CMAKE_CXX_COMPILER", self.pkg.compiler.cxx),
|
||||
" " + system_fc_entry,
|
||||
"endif()\n",
|
||||
]
|
||||
@@ -350,10 +352,6 @@ class CachedCMakePackage(CMakePackage):
|
||||
|
||||
CMakeBuilder = CachedCMakeBuilder
|
||||
|
||||
# These dependencies are assumed in the builder
|
||||
depends_on("c", type="build")
|
||||
depends_on("cxx", type="build")
|
||||
|
||||
def flag_handler(self, name, flags):
|
||||
if name in ("cflags", "cxxflags", "cppflags", "fflags"):
|
||||
return None, None, None # handled in the cmake cache
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import re
|
||||
import sys
|
||||
from itertools import chain
|
||||
from typing import Any, List, Optional, Set, Tuple
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
from llnl.util.lang import stable_partition
|
||||
@@ -21,6 +21,7 @@
|
||||
import spack.phase_callbacks
|
||||
import spack.spec
|
||||
import spack.util.prefix
|
||||
from spack import traverse
|
||||
from spack.directives import build_system, conflicts, depends_on, variant
|
||||
from spack.multimethod import when
|
||||
from spack.util.environment import filter_system_paths
|
||||
@@ -166,15 +167,18 @@ def _values(x):
|
||||
def get_cmake_prefix_path(pkg: spack.package_base.PackageBase) -> List[str]:
|
||||
"""Obtain the CMAKE_PREFIX_PATH entries for a package, based on the cmake_prefix_path package
|
||||
attribute of direct build/test and transitive link dependencies."""
|
||||
# Add direct build/test deps
|
||||
selected: Set[str] = {s.dag_hash() for s in pkg.spec.dependencies(deptype=dt.BUILD | dt.TEST)}
|
||||
# Add transitive link deps
|
||||
selected.update(s.dag_hash() for s in pkg.spec.traverse(root=False, deptype=dt.LINK))
|
||||
# Separate out externals so they do not shadow Spack prefixes
|
||||
externals, spack_built = stable_partition(
|
||||
(s for s in pkg.spec.traverse(root=False, order="topo") if s.dag_hash() in selected),
|
||||
lambda x: x.external,
|
||||
edges = traverse.traverse_topo_edges_generator(
|
||||
traverse.with_artificial_edges([pkg.spec]),
|
||||
visitor=traverse.MixedDepthVisitor(
|
||||
direct=dt.BUILD | dt.TEST, transitive=dt.LINK | dt.RUN, key=traverse.by_dag_hash
|
||||
),
|
||||
key=traverse.by_dag_hash,
|
||||
root=False,
|
||||
all_edges=False, # cover all nodes, not all edges
|
||||
)
|
||||
ordered_specs = [edge.spec for edge in edges]
|
||||
# Separate out externals so they do not shadow Spack prefixes
|
||||
externals, spack_built = stable_partition((s for s in ordered_specs), lambda x: x.external)
|
||||
|
||||
return filter_system_paths(
|
||||
path for spec in chain(spack_built, externals) for path in spec.package.cmake_prefix_paths
|
||||
|
||||
@@ -5,20 +5,15 @@
|
||||
import itertools
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from typing import Dict, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
import archspec.cpu
|
||||
from typing import Dict, List, Sequence, Tuple, Union
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.lang import classproperty, memoized
|
||||
from llnl.util.lang import classproperty
|
||||
|
||||
import spack.compilers.libraries
|
||||
import spack.config
|
||||
import spack.compiler
|
||||
import spack.package_base
|
||||
import spack.paths
|
||||
import spack.util.executable
|
||||
|
||||
# Local "type" for type hints
|
||||
@@ -49,9 +44,6 @@ class CompilerPackage(spack.package_base.PackageBase):
|
||||
#: Static definition of languages supported by this class
|
||||
compiler_languages: Sequence[str] = ["c", "cxx", "fortran"]
|
||||
|
||||
#: Relative path to compiler wrappers
|
||||
link_paths: Dict[str, str] = {}
|
||||
|
||||
def __init__(self, spec: "spack.spec.Spec"):
|
||||
super().__init__(spec)
|
||||
msg = f"Supported languages for {spec} are not a subset of possible supported languages"
|
||||
@@ -86,14 +78,14 @@ def executables(cls) -> Sequence[str]:
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def determine_version(cls, exe: Path) -> str:
|
||||
def determine_version(cls, exe: Path):
|
||||
version_argument = cls.compiler_version_argument
|
||||
if isinstance(version_argument, str):
|
||||
version_argument = (version_argument,)
|
||||
|
||||
for va in version_argument:
|
||||
try:
|
||||
output = compiler_output(exe, version_argument=va)
|
||||
output = spack.compiler.get_compiler_version_output(exe, va)
|
||||
match = re.search(cls.compiler_version_regex, output)
|
||||
if match:
|
||||
return ".".join(match.groups())
|
||||
@@ -104,7 +96,6 @@ def determine_version(cls, exe: Path) -> str:
|
||||
f"[{__file__}] Cannot detect a valid version for the executable "
|
||||
f"{str(exe)}, for package '{cls.name}': {e}"
|
||||
)
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def compiler_bindir(cls, prefix: Path) -> Path:
|
||||
@@ -152,183 +143,3 @@ def determine_compiler_paths(cls, exes: Sequence[Path]) -> Dict[str, Path]:
|
||||
def determine_variants(cls, exes: Sequence[Path], version_str: str) -> Tuple:
|
||||
# path determination is separated so it can be reused in subclasses
|
||||
return "", {"compilers": cls.determine_compiler_paths(exes=exes)}
|
||||
|
||||
#: Returns the argument needed to set the RPATH, or None if it does not exist
|
||||
rpath_arg: Optional[str] = "-Wl,-rpath,"
|
||||
#: Flag that needs to be used to pass an argument to the linker
|
||||
linker_arg: str = "-Wl,"
|
||||
#: Flag used to produce Position Independent Code
|
||||
pic_flag: str = "-fPIC"
|
||||
#: Flag used to get verbose output
|
||||
verbose_flags: str = "-v"
|
||||
#: Flag to activate OpenMP support
|
||||
openmp_flag: str = "-fopenmp"
|
||||
|
||||
def standard_flag(self, *, language: str, standard: str) -> str:
|
||||
"""Returns the flag used to enforce a given standard for a language"""
|
||||
if language not in self.supported_languages:
|
||||
# FIXME (compiler as nodes): Use UnsupportedCompilerFlag ?
|
||||
raise RuntimeError(f"{self.spec} does not provide the '{language}' language")
|
||||
try:
|
||||
return self._standard_flag(language=language, standard=standard)
|
||||
except (KeyError, RuntimeError) as e:
|
||||
raise RuntimeError(
|
||||
f"{self.spec} does not provide the '{language}' standard {standard}"
|
||||
) from e
|
||||
|
||||
def _standard_flag(self, *, language: str, standard: str) -> str:
|
||||
raise NotImplementedError("Must be implemented by derived classes")
|
||||
|
||||
@property
|
||||
def disable_new_dtags(self) -> str:
|
||||
if platform.system() == "Darwin":
|
||||
return ""
|
||||
return "--disable-new-dtags"
|
||||
|
||||
@property
|
||||
def enable_new_dtags(self) -> str:
|
||||
if platform.system() == "Darwin":
|
||||
return ""
|
||||
return "--enable-new-dtags"
|
||||
|
||||
def setup_dependent_build_environment(self, env, dependent_spec):
|
||||
# FIXME (compiler as nodes): check if this is good enough or should be made more general
|
||||
|
||||
# The package is not used as a compiler, so skip this setup
|
||||
if not any(
|
||||
lang in dependent_spec and dependent_spec[lang].name == self.spec.name
|
||||
for lang in ("c", "cxx", "fortran")
|
||||
):
|
||||
return
|
||||
|
||||
# Populate an object with the list of environment modifications and return it
|
||||
link_dir = pathlib.Path(spack.paths.build_env_path)
|
||||
|
||||
for language, attr_name, wrapper_var_name, spack_var_name in [
|
||||
("c", "cc", "CC", "SPACK_CC"),
|
||||
("cxx", "cxx", "CXX", "SPACK_CXX"),
|
||||
("fortran", "fortran", "F77", "SPACK_F77"),
|
||||
("fortran", "fortran", "FC", "SPACK_FC"),
|
||||
]:
|
||||
if language not in dependent_spec or dependent_spec[language].name != self.spec.name:
|
||||
continue
|
||||
|
||||
if not hasattr(self, attr_name):
|
||||
continue
|
||||
|
||||
compiler = getattr(self, attr_name)
|
||||
env.set(spack_var_name, compiler)
|
||||
|
||||
if language not in self.link_paths:
|
||||
continue
|
||||
|
||||
wrapper_path = link_dir / self.link_paths.get(language)
|
||||
env.set(wrapper_var_name, str(wrapper_path))
|
||||
env.set(f"SPACK_{wrapper_var_name}_RPATH_ARG", self.rpath_arg)
|
||||
|
||||
uarch = dependent_spec.architecture.target
|
||||
version_number, _ = archspec.cpu.version_components(
|
||||
self.spec.version.dotted_numeric_string
|
||||
)
|
||||
try:
|
||||
isa_arg = uarch.optimization_flags(self.spec.name, version_number)
|
||||
except (ValueError, archspec.cpu.UnsupportedMicroarchitecture):
|
||||
isa_arg = ""
|
||||
|
||||
if isa_arg:
|
||||
env.set(f"SPACK_TARGET_ARGS_{attr_name.upper()}", isa_arg)
|
||||
|
||||
# FIXME (compiler as nodes): make these paths language specific
|
||||
env.set("SPACK_LINKER_ARG", self.linker_arg)
|
||||
|
||||
paths = _implicit_rpaths(pkg=self)
|
||||
if paths:
|
||||
env.set("SPACK_COMPILER_IMPLICIT_RPATHS", ":".join(paths))
|
||||
|
||||
# Check whether we want to force RPATH or RUNPATH
|
||||
if spack.config.CONFIG.get("config:shared_linking:type") == "rpath":
|
||||
env.set("SPACK_DTAGS_TO_STRIP", self.enable_new_dtags)
|
||||
env.set("SPACK_DTAGS_TO_ADD", self.disable_new_dtags)
|
||||
else:
|
||||
env.set("SPACK_DTAGS_TO_STRIP", self.disable_new_dtags)
|
||||
env.set("SPACK_DTAGS_TO_ADD", self.enable_new_dtags)
|
||||
|
||||
spec = self.spec
|
||||
env.set("SPACK_COMPILER_SPEC", spec.format("{name}{@version}{variants}{/hash:7}"))
|
||||
|
||||
if spec.extra_attributes:
|
||||
extra_rpaths = spec.extra_attributes.get("extra_rpaths")
|
||||
if extra_rpaths:
|
||||
extra_rpaths = ":".join(compiler.extra_rpaths)
|
||||
env.append_path("SPACK_COMPILER_EXTRA_RPATHS", extra_rpaths)
|
||||
|
||||
# Add spack build environment path with compiler wrappers first in
|
||||
# the path. We add the compiler wrapper path, which includes default
|
||||
# wrappers (cc, c++, f77, f90), AND a subdirectory containing
|
||||
# compiler-specific symlinks. The latter ensures that builds that
|
||||
# are sensitive to the *name* of the compiler see the right name when
|
||||
# we're building with the wrappers.
|
||||
#
|
||||
# Conflicts on case-insensitive systems (like "CC" and "cc") are
|
||||
# handled by putting one in the <build_env_path>/case-insensitive
|
||||
# directory. Add that to the path too.
|
||||
env_paths = []
|
||||
compiler_specific = os.path.join(
|
||||
spack.paths.build_env_path, os.path.dirname(self.link_paths["c"])
|
||||
)
|
||||
for item in [spack.paths.build_env_path, compiler_specific]:
|
||||
env_paths.append(item)
|
||||
ci = os.path.join(item, "case-insensitive")
|
||||
if os.path.isdir(ci):
|
||||
env_paths.append(ci)
|
||||
|
||||
tty.debug("Adding compiler bin/ paths: " + " ".join(env_paths))
|
||||
for item in env_paths:
|
||||
env.prepend_path("PATH", item)
|
||||
env.set_path("SPACK_ENV_PATH", env_paths)
|
||||
|
||||
|
||||
def _implicit_rpaths(pkg: spack.package_base.PackageBase) -> List[str]:
|
||||
detector = spack.compilers.libraries.CompilerPropertyDetector(pkg.spec)
|
||||
paths = detector.implicit_rpaths()
|
||||
return paths
|
||||
|
||||
|
||||
@memoized
|
||||
def _compiler_output(
|
||||
compiler_path: Path, *, version_argument: str, ignore_errors: Tuple[int, ...] = ()
|
||||
) -> str:
|
||||
"""Returns the output from the compiler invoked with the given version argument.
|
||||
|
||||
Args:
|
||||
compiler_path: path of the compiler to be invoked
|
||||
version_argument: the argument used to extract version information
|
||||
"""
|
||||
compiler = spack.util.executable.Executable(compiler_path)
|
||||
compiler_invocation_args = {
|
||||
"output": str,
|
||||
"error": str,
|
||||
"ignore_errors": ignore_errors,
|
||||
"timeout": 120,
|
||||
"fail_on_error": True,
|
||||
}
|
||||
if version_argument:
|
||||
output = compiler(version_argument, **compiler_invocation_args)
|
||||
else:
|
||||
output = compiler(**compiler_invocation_args)
|
||||
return output
|
||||
|
||||
|
||||
def compiler_output(
|
||||
compiler_path: Path, *, version_argument: str, ignore_errors: Tuple[int, ...] = ()
|
||||
) -> str:
|
||||
"""Wrapper for _get_compiler_version_output()."""
|
||||
# This ensures that we memoize compiler output by *absolute path*,
|
||||
# not just executable name. If we don't do this, and the path changes
|
||||
# (e.g., during testing), we can get incorrect results.
|
||||
if not os.path.isabs(compiler_path):
|
||||
compiler_path = spack.util.executable.which_string(compiler_path, required=True)
|
||||
|
||||
return _compiler_output(
|
||||
compiler_path, version_argument=version_argument, ignore_errors=ignore_errors
|
||||
)
|
||||
|
||||
@@ -72,10 +72,7 @@ def v2_layout(self):
|
||||
def component_prefix(self):
|
||||
"""Path to component <prefix>/<component>/<version>."""
|
||||
v = self.spec.version.up_to(2) if self.v2_layout else self.spec.version
|
||||
base_dir = self.prefix
|
||||
if self.component_dir not in str(self.prefix):
|
||||
base_dir = base_dir.join(self.component_dir).join(str(v))
|
||||
return base_dir
|
||||
return self.prefix.join(self.component_dir).join(str(v))
|
||||
|
||||
@property
|
||||
def env_script_args(self):
|
||||
@@ -143,7 +140,7 @@ def setup_run_environment(self, env):
|
||||
$ source {prefix}/{component}/{version}/env/vars.sh
|
||||
"""
|
||||
# Only if environment modifications are desired (default is +envmods)
|
||||
if "+envmods" in self.spec:
|
||||
if "~envmods" not in self.spec:
|
||||
env.extend(
|
||||
EnvironmentModifications.from_sourcing_file(
|
||||
self.component_prefix.env.join("vars.sh"), *self.env_script_args
|
||||
@@ -258,7 +255,7 @@ def libs(self):
|
||||
return find_libraries("*", root=self.component_prefix.lib, recursive=not self.v2_layout)
|
||||
|
||||
|
||||
class IntelOneApiLibraryPackageWithSdk(IntelOneApiPackage):
|
||||
class IntelOneApiLibraryPackageWithSdk(IntelOneApiLibraryPackage):
|
||||
"""Base class for Intel oneAPI library packages with SDK components.
|
||||
|
||||
Contains some convenient default implementations for libraries
|
||||
|
||||
@@ -277,6 +277,10 @@ def update_external_dependencies(self, extendee_spec=None):
|
||||
if not python.architecture.target:
|
||||
python.architecture.target = archspec.cpu.host().family.name
|
||||
|
||||
# Ensure compiler information is present
|
||||
if not python.compiler:
|
||||
python.compiler = self.spec.compiler
|
||||
|
||||
python.external_path = self.spec.external_path
|
||||
python._mark_concrete()
|
||||
self.spec.add_dependency_edge(python, depflag=dt.BUILD | dt.LINK | dt.RUN, virtuals=())
|
||||
|
||||
@@ -2137,7 +2137,7 @@ def build_name(self):
|
||||
Returns: (str) current spec's CDash build name."""
|
||||
spec = self.current_spec
|
||||
if spec:
|
||||
build_name = f"{spec.name}@{spec.version} \
|
||||
build_name = f"{spec.name}@{spec.version}%{spec.compiler} \
|
||||
hash={spec.dag_hash()} arch={spec.architecture} ({self.build_group})"
|
||||
tty.debug(f"Generated CDash build name ({build_name}) from the {spec.name}")
|
||||
return build_name
|
||||
|
||||
@@ -369,13 +369,8 @@ def iter_groups(specs, indent, all_headers):
|
||||
index = index_by(specs, ("architecture", "compiler"))
|
||||
ispace = indent * " "
|
||||
|
||||
def _key(item):
|
||||
if item is None:
|
||||
return ""
|
||||
return str(item)
|
||||
|
||||
# Traverse the index and print out each package
|
||||
for i, (architecture, compiler) in enumerate(sorted(index, key=_key)):
|
||||
for i, (architecture, compiler) in enumerate(sorted(index)):
|
||||
if i > 0:
|
||||
print()
|
||||
|
||||
@@ -433,7 +428,6 @@ def display_specs(specs, args=None, **kwargs):
|
||||
|
||||
"""
|
||||
|
||||
# FIXME (compiler as nodes): remove the "show full compiler" arguments, and its use
|
||||
def get_arg(name, default=None):
|
||||
"""Prefer kwargs, then args, then default."""
|
||||
if name in kwargs:
|
||||
@@ -448,6 +442,7 @@ def get_arg(name, default=None):
|
||||
hashes = get_arg("long", False)
|
||||
namespaces = get_arg("namespaces", False)
|
||||
flags = get_arg("show_flags", False)
|
||||
full_compiler = get_arg("show_full_compiler", False)
|
||||
variants = get_arg("variants", False)
|
||||
groups = get_arg("groups", True)
|
||||
all_headers = get_arg("all_headers", False)
|
||||
@@ -469,7 +464,10 @@ def get_arg(name, default=None):
|
||||
if format_string is None:
|
||||
nfmt = "{fullname}" if namespaces else "{name}"
|
||||
ffmt = ""
|
||||
if flags:
|
||||
if full_compiler or flags:
|
||||
ffmt += "{%compiler.name}"
|
||||
if full_compiler:
|
||||
ffmt += "{@compiler.version}"
|
||||
ffmt += " {compiler_flags}"
|
||||
vfmt = "{variants}" if variants else ""
|
||||
format_string = nfmt + "{@version}" + ffmt + vfmt
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.lang import index_by
|
||||
from llnl.util.tty.colify import colify
|
||||
from llnl.util.tty.color import colorize
|
||||
|
||||
import spack.compilers.config
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.spec
|
||||
from spack.cmd.common import arguments
|
||||
@@ -36,13 +35,13 @@ def setup_parser(subparser):
|
||||
"--mixed-toolchain",
|
||||
action="store_true",
|
||||
default=sys.platform == "darwin",
|
||||
help="(DEPRECATED) Allow mixed toolchains (for example: clang, clang++, gfortran)",
|
||||
help="Allow mixed toolchains (for example: clang, clang++, gfortran)",
|
||||
)
|
||||
mixed_toolchain_group.add_argument(
|
||||
"--no-mixed-toolchain",
|
||||
action="store_false",
|
||||
dest="mixed_toolchain",
|
||||
help="(DEPRECATED) Do not allow mixed toolchains (for example: clang, clang++, gfortran)",
|
||||
help="Do not allow mixed toolchains (for example: clang, clang++, gfortran)",
|
||||
)
|
||||
find_parser.add_argument("add_paths", nargs=argparse.REMAINDER)
|
||||
find_parser.add_argument(
|
||||
@@ -81,97 +80,77 @@ def compiler_find(args):
|
||||
"""Search either $PATH or a list of paths OR MODULES for compilers and
|
||||
add them to Spack's configuration.
|
||||
"""
|
||||
if args.mixed_toolchain:
|
||||
warnings.warn(
|
||||
"The '--mixed-toolchain' option has been deprecated in Spack v0.23, and currently "
|
||||
"has no effect. The option will be removed in Spack v0.25"
|
||||
)
|
||||
|
||||
paths = args.add_paths or None
|
||||
new_compilers = spack.compilers.config.find_compilers(
|
||||
path_hints=paths, scope=args.scope, max_workers=args.jobs
|
||||
new_compilers = spack.compilers.find_compilers(
|
||||
path_hints=paths,
|
||||
scope=args.scope,
|
||||
mixed_toolchain=args.mixed_toolchain,
|
||||
max_workers=args.jobs,
|
||||
)
|
||||
if new_compilers:
|
||||
n = len(new_compilers)
|
||||
s = "s" if n > 1 else ""
|
||||
filename = spack.config.CONFIG.get_config_filename(args.scope, "packages")
|
||||
filename = spack.config.CONFIG.get_config_filename(args.scope, "compilers")
|
||||
tty.msg(f"Added {n:d} new compiler{s} to {filename}")
|
||||
compiler_strs = sorted(f"{spec.name}@{spec.versions}" for spec in new_compilers)
|
||||
compiler_strs = sorted(f"{c.spec.name}@{c.spec.version}" for c in new_compilers)
|
||||
colify(reversed(compiler_strs), indent=4)
|
||||
else:
|
||||
tty.msg("Found no new compilers")
|
||||
tty.msg("Compilers are defined in the following files:")
|
||||
colify(spack.compilers.config.compiler_config_files(), indent=4)
|
||||
colify(spack.compilers.compiler_config_files(), indent=4)
|
||||
|
||||
|
||||
def compiler_remove(args):
|
||||
remover = spack.compilers.config.CompilerRemover(spack.config.CONFIG)
|
||||
candidates = remover.mark_compilers(match=args.compiler_spec, scope=args.scope)
|
||||
if not candidates:
|
||||
tty.die(f"No compiler matches '{args.compiler_spec}'")
|
||||
compiler_spec = spack.spec.CompilerSpec(args.compiler_spec)
|
||||
candidate_compilers = spack.compilers.compilers_for_spec(compiler_spec, scope=args.scope)
|
||||
|
||||
compiler_strs = reversed(sorted(f"{spec.name}@{spec.versions}" for spec in candidates))
|
||||
if not candidate_compilers:
|
||||
tty.die("No compilers match spec %s" % compiler_spec)
|
||||
|
||||
if not args.all and len(candidates) > 1:
|
||||
tty.error(f"multiple compilers match the spec '{args.compiler_spec}':")
|
||||
print()
|
||||
colify(compiler_strs, indent=4)
|
||||
print()
|
||||
print(
|
||||
"Either use a stricter spec to select only one, or use `spack compiler remove -a`"
|
||||
" to remove all of them."
|
||||
)
|
||||
if not args.all and len(candidate_compilers) > 1:
|
||||
tty.error(f"Multiple compilers match spec {compiler_spec}. Choose one:")
|
||||
colify(reversed(sorted([c.spec.display_str for c in candidate_compilers])), indent=4)
|
||||
tty.msg("Or, use `spack compiler remove -a` to remove all of them.")
|
||||
sys.exit(1)
|
||||
|
||||
remover.flush()
|
||||
tty.msg("The following compilers have been removed:")
|
||||
print()
|
||||
colify(compiler_strs, indent=4)
|
||||
print()
|
||||
for current_compiler in candidate_compilers:
|
||||
spack.compilers.remove_compiler_from_config(current_compiler.spec, scope=args.scope)
|
||||
tty.msg(f"{current_compiler.spec.display_str} has been removed")
|
||||
|
||||
|
||||
def compiler_info(args):
|
||||
"""Print info about all compilers matching a spec."""
|
||||
query = spack.spec.Spec(args.compiler_spec)
|
||||
all_compilers = spack.compilers.config.all_compilers(scope=args.scope, init_config=False)
|
||||
|
||||
compilers = [x for x in all_compilers if x.satisfies(query)]
|
||||
cspec = spack.spec.CompilerSpec(args.compiler_spec)
|
||||
compilers = spack.compilers.compilers_for_spec(cspec, scope=args.scope)
|
||||
|
||||
if not compilers:
|
||||
tty.die(f"No compilers match spec {query.cformat()}")
|
||||
tty.die("No compilers match spec %s" % cspec)
|
||||
else:
|
||||
for c in compilers:
|
||||
print(f"{c.cformat()}:")
|
||||
print(f" prefix: {c.external_path}")
|
||||
extra_attributes = getattr(c, "extra_attributes", {})
|
||||
if "compilers" in extra_attributes:
|
||||
print(" compilers:")
|
||||
for language, exe in extra_attributes.get("compilers", {}).items():
|
||||
print(f" {language}: {exe}")
|
||||
if "flags" in extra_attributes:
|
||||
print(" flags:")
|
||||
for flag, flag_value in extra_attributes["flags"].items():
|
||||
print(f" {flag} = {flag_value}")
|
||||
# FIXME (compiler as nodes): recover this printing
|
||||
# if "environment" in extra_attributes:
|
||||
# if len(c.environment.get("set", {})) != 0:
|
||||
# print("\tenvironment:")
|
||||
# print("\t set:")
|
||||
# for key, value in c.environment["set"].items():
|
||||
# print("\t %s = %s" % (key, value))
|
||||
if "extra_rpaths" in extra_attributes:
|
||||
print(" extra rpaths:")
|
||||
for extra_rpath in extra_attributes["extra_rpaths"]:
|
||||
print(f" {extra_rpath}")
|
||||
if getattr(c, "external_modules", []):
|
||||
print(" modules: ")
|
||||
for module in c.external_modules:
|
||||
print(f" {module}")
|
||||
print()
|
||||
print(c.spec.display_str + ":")
|
||||
print("\tpaths:")
|
||||
for cpath in ["cc", "cxx", "f77", "fc"]:
|
||||
print("\t\t%s = %s" % (cpath, getattr(c, cpath, None)))
|
||||
if c.flags:
|
||||
print("\tflags:")
|
||||
for flag, flag_value in c.flags.items():
|
||||
print("\t\t%s = %s" % (flag, flag_value))
|
||||
if len(c.environment) != 0:
|
||||
if len(c.environment.get("set", {})) != 0:
|
||||
print("\tenvironment:")
|
||||
print("\t set:")
|
||||
for key, value in c.environment["set"].items():
|
||||
print("\t %s = %s" % (key, value))
|
||||
if c.extra_rpaths:
|
||||
print("\tExtra rpaths:")
|
||||
for extra_rpath in c.extra_rpaths:
|
||||
print("\t\t%s" % extra_rpath)
|
||||
print("\tmodules = %s" % c.modules)
|
||||
print("\toperating system = %s" % c.operating_system)
|
||||
|
||||
|
||||
def compiler_list(args):
|
||||
compilers = spack.compilers.config.all_compilers(scope=args.scope, init_config=False)
|
||||
compilers = spack.compilers.all_compilers(scope=args.scope, init_config=False)
|
||||
|
||||
# If there are no compilers in any scope, and we're outputting to a tty, give a
|
||||
# hint to the user.
|
||||
@@ -184,7 +163,7 @@ def compiler_list(args):
|
||||
tty.msg(msg)
|
||||
return
|
||||
|
||||
index = index_by(compilers, spack.compilers.config.name_os_target)
|
||||
index = index_by(compilers, lambda c: (c.spec.name, c.operating_system, c.target))
|
||||
|
||||
tty.msg("Available compilers")
|
||||
|
||||
@@ -203,10 +182,10 @@ def compiler_list(args):
|
||||
name, os, target = key
|
||||
os_str = os
|
||||
if target:
|
||||
os_str += f"-{target}"
|
||||
cname = f"{spack.spec.COMPILER_COLOR}{{{name}}} {os_str}"
|
||||
os_str += "-%s" % target
|
||||
cname = "%s{%s} %s" % (spack.spec.COMPILER_COLOR, name, os_str)
|
||||
tty.hline(colorize(cname), char="-")
|
||||
colify(reversed(sorted(c.format("{name}@{version}") for c in compilers)))
|
||||
colify(reversed(sorted(c.spec.display_str for c in compilers)))
|
||||
|
||||
|
||||
def compiler(parser, args):
|
||||
|
||||
@@ -518,6 +518,8 @@ def config_prefer_upstream(args):
|
||||
for spec in pref_specs:
|
||||
# Collect all the upstream compilers and versions for this package.
|
||||
pkg = pkgs.get(spec.name, {"version": []})
|
||||
all = pkgs.get("all", {"compiler": []})
|
||||
pkgs["all"] = all
|
||||
pkgs[spec.name] = pkg
|
||||
|
||||
# We have no existing variant if this is our first added version.
|
||||
@@ -527,6 +529,10 @@ def config_prefer_upstream(args):
|
||||
if version not in pkg["version"]:
|
||||
pkg["version"].append(version)
|
||||
|
||||
compiler = str(spec.compiler)
|
||||
if compiler not in all["compiler"]:
|
||||
all["compiler"].append(compiler)
|
||||
|
||||
# Get and list all the variants that differ from the default.
|
||||
variants = []
|
||||
for var_name, variant in spec.variants.items():
|
||||
|
||||
@@ -99,7 +99,7 @@ def setup_parser(subparser):
|
||||
"--show-full-compiler",
|
||||
action="store_true",
|
||||
dest="show_full_compiler",
|
||||
help="(DEPRECATED) show full compiler specs. Currently it's a no-op",
|
||||
help="show full compiler specs",
|
||||
)
|
||||
implicit_explicit = subparser.add_mutually_exclusive_group()
|
||||
implicit_explicit.add_argument(
|
||||
@@ -279,6 +279,7 @@ def root_decorator(spec, string):
|
||||
# these enforce details in the root specs to show what the user asked for
|
||||
namespaces=True,
|
||||
show_flags=True,
|
||||
show_full_compiler=True,
|
||||
decorator=root_decorator,
|
||||
variants=True,
|
||||
)
|
||||
@@ -301,6 +302,7 @@ def root_decorator(spec, string):
|
||||
decorator=lambda s, f: color.colorize("@*{%s}" % f),
|
||||
namespace=True,
|
||||
show_flags=True,
|
||||
show_full_compiler=True,
|
||||
variants=True,
|
||||
)
|
||||
print()
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import spack.cmd.common.arguments
|
||||
import spack.cmd.modules
|
||||
import spack.config
|
||||
import spack.modules
|
||||
import spack.modules.lmod
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import spack.cmd.common.arguments
|
||||
import spack.cmd.modules
|
||||
import spack.config
|
||||
import spack.modules
|
||||
import spack.modules.tcl
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
import spack.paths
|
||||
import spack.repo
|
||||
import spack.util.git
|
||||
from spack.util.executable import Executable, which
|
||||
|
||||
@@ -38,7 +39,7 @@ def grouper(iterable, n, fillvalue=None):
|
||||
#: double-check the results of other tools (if, e.g., --fix was provided)
|
||||
#: The list maps an executable name to a method to ensure the tool is
|
||||
#: bootstrapped or present in the environment.
|
||||
tool_names = ["import-check", "isort", "black", "flake8", "mypy"]
|
||||
tool_names = ["import", "isort", "black", "flake8", "mypy"]
|
||||
|
||||
#: warnings to ignore in mypy
|
||||
mypy_ignores = [
|
||||
@@ -370,10 +371,19 @@ def run_black(black_cmd, file_list, args):
|
||||
|
||||
def _module_part(root: str, expr: str):
|
||||
parts = expr.split(".")
|
||||
# spack.pkg is for repositories, don't try to resolve it here.
|
||||
if ".".join(parts[:2]) == spack.repo.ROOT_PYTHON_NAMESPACE:
|
||||
return None
|
||||
while parts:
|
||||
f1 = os.path.join(root, "lib", "spack", *parts) + ".py"
|
||||
f2 = os.path.join(root, "lib", "spack", *parts, "__init__.py")
|
||||
if os.path.exists(f1) or os.path.exists(f2):
|
||||
|
||||
if (
|
||||
os.path.exists(f1)
|
||||
# ensure case sensitive match
|
||||
and f"{parts[-1]}.py" in os.listdir(os.path.dirname(f1))
|
||||
or os.path.exists(f2)
|
||||
):
|
||||
return ".".join(parts)
|
||||
parts.pop()
|
||||
return None
|
||||
@@ -389,7 +399,7 @@ def _run_import_check(
|
||||
out=sys.stdout,
|
||||
):
|
||||
if sys.version_info < (3, 9):
|
||||
print("import-check requires Python 3.9 or later")
|
||||
print("import check requires Python 3.9 or later")
|
||||
return 0
|
||||
|
||||
is_use = re.compile(r"(?<!from )(?<!import )(?:llnl|spack)\.[a-zA-Z0-9_\.]+")
|
||||
@@ -431,10 +441,11 @@ def _run_import_check(
|
||||
module = _module_part(root, m.group(0))
|
||||
if not module or module in to_add:
|
||||
continue
|
||||
if f"import {module}" not in filtered_contents:
|
||||
to_add.add(module)
|
||||
exit_code = 1
|
||||
print(f"{pretty_path}: missing import: {module}", file=out)
|
||||
if re.search(rf"import {re.escape(module)}\b(?!\.)", contents):
|
||||
continue
|
||||
to_add.add(module)
|
||||
exit_code = 1
|
||||
print(f"{pretty_path}: missing import: {module} ({m.group(0)})", file=out)
|
||||
|
||||
if not fix or not to_add and not to_remove:
|
||||
continue
|
||||
@@ -465,7 +476,7 @@ def _run_import_check(
|
||||
return exit_code
|
||||
|
||||
|
||||
@tool("import-check", external=False)
|
||||
@tool("import", external=False)
|
||||
def run_import_check(import_check_cmd, file_list, args):
|
||||
exit_code = _run_import_check(
|
||||
file_list,
|
||||
@@ -474,7 +485,7 @@ def run_import_check(import_check_cmd, file_list, args):
|
||||
root=args.root,
|
||||
working_dir=args.initial_working_dir,
|
||||
)
|
||||
print_tool_result("import-check", exit_code)
|
||||
print_tool_result("import", exit_code)
|
||||
return exit_code
|
||||
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ def unit_test(parser, args, unknown_args):
|
||||
# Ensure clingo is available before switching to the
|
||||
# mock configuration used by unit tests
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
spack.bootstrap.ensure_clingo_importable_or_raise()
|
||||
spack.bootstrap.ensure_core_dependencies()
|
||||
if pytest is None:
|
||||
spack.bootstrap.ensure_environment_dependencies()
|
||||
import pytest
|
||||
|
||||
850
lib/spack/spack/compiler.py
Normal file
850
lib/spack/spack/compiler.py
Normal file
@@ -0,0 +1,850 @@
|
||||
# Copyright 2013-2024 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 contextlib
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
|
||||
import llnl.path
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import path_contains_subdirectory, paths_containing_libs
|
||||
|
||||
import spack.caches
|
||||
import spack.error
|
||||
import spack.schema.environment
|
||||
import spack.spec
|
||||
import spack.util.executable
|
||||
import spack.util.libc
|
||||
import spack.util.module_cmd
|
||||
import spack.version
|
||||
from spack.util.environment import filter_system_paths
|
||||
from spack.util.file_cache import FileCache
|
||||
|
||||
__all__ = ["Compiler"]
|
||||
|
||||
PATH_INSTANCE_VARS = ["cc", "cxx", "f77", "fc"]
|
||||
FLAG_INSTANCE_VARS = ["cflags", "cppflags", "cxxflags", "fflags"]
|
||||
|
||||
|
||||
@llnl.util.lang.memoized
|
||||
def _get_compiler_version_output(compiler_path, version_arg, ignore_errors=()) -> str:
|
||||
"""Invokes the compiler at a given path passing a single
|
||||
version argument and returns the output.
|
||||
|
||||
Args:
|
||||
compiler_path (path): path of the compiler to be invoked
|
||||
version_arg (str): the argument used to extract version information
|
||||
"""
|
||||
compiler = spack.util.executable.Executable(compiler_path)
|
||||
compiler_invocation_args = {
|
||||
"output": str,
|
||||
"error": str,
|
||||
"ignore_errors": ignore_errors,
|
||||
"timeout": 120,
|
||||
"fail_on_error": True,
|
||||
}
|
||||
if version_arg:
|
||||
output = compiler(version_arg, **compiler_invocation_args)
|
||||
else:
|
||||
output = compiler(**compiler_invocation_args)
|
||||
return output
|
||||
|
||||
|
||||
def get_compiler_version_output(compiler_path, *args, **kwargs) -> str:
|
||||
"""Wrapper for _get_compiler_version_output()."""
|
||||
# This ensures that we memoize compiler output by *absolute path*,
|
||||
# not just executable name. If we don't do this, and the path changes
|
||||
# (e.g., during testing), we can get incorrect results.
|
||||
if not os.path.isabs(compiler_path):
|
||||
compiler_path = spack.util.executable.which_string(compiler_path, required=True)
|
||||
|
||||
return _get_compiler_version_output(compiler_path, *args, **kwargs)
|
||||
|
||||
|
||||
def tokenize_flags(flags_values, propagate=False):
|
||||
"""Given a compiler flag specification as a string, this returns a list
|
||||
where the entries are the flags. For compiler options which set values
|
||||
using the syntax "-flag value", this function groups flags and their
|
||||
values together. Any token not preceded by a "-" is considered the
|
||||
value of a prior flag."""
|
||||
tokens = flags_values.split()
|
||||
if not tokens:
|
||||
return []
|
||||
flag = tokens[0]
|
||||
flags_with_propagation = []
|
||||
for token in tokens[1:]:
|
||||
if not token.startswith("-"):
|
||||
flag += " " + token
|
||||
else:
|
||||
flags_with_propagation.append((flag, propagate))
|
||||
flag = token
|
||||
flags_with_propagation.append((flag, propagate))
|
||||
return flags_with_propagation
|
||||
|
||||
|
||||
#: regex for parsing linker lines
|
||||
_LINKER_LINE = re.compile(r"^( *|.*[/\\])" r"(link|ld|([^/\\]+-)?ld|collect2)" r"[^/\\]*( |$)")
|
||||
|
||||
#: components of linker lines to ignore
|
||||
_LINKER_LINE_IGNORE = re.compile(r"(collect2 version|^[A-Za-z0-9_]+=|/ldfe )")
|
||||
|
||||
#: regex to match linker search paths
|
||||
_LINK_DIR_ARG = re.compile(r"^-L(.:)?(?P<dir>[/\\].*)")
|
||||
|
||||
#: regex to match linker library path arguments
|
||||
_LIBPATH_ARG = re.compile(r"^[-/](LIBPATH|libpath):(?P<dir>.*)")
|
||||
|
||||
|
||||
def _parse_link_paths(string):
|
||||
"""Parse implicit link paths from compiler debug output.
|
||||
|
||||
This gives the compiler runtime library paths that we need to add to
|
||||
the RPATH of generated binaries and libraries. It allows us to
|
||||
ensure, e.g., that codes load the right libstdc++ for their compiler.
|
||||
"""
|
||||
lib_search_paths = False
|
||||
raw_link_dirs = []
|
||||
for line in string.splitlines():
|
||||
if lib_search_paths:
|
||||
if line.startswith("\t"):
|
||||
raw_link_dirs.append(line[1:])
|
||||
continue
|
||||
else:
|
||||
lib_search_paths = False
|
||||
elif line.startswith("Library search paths:"):
|
||||
lib_search_paths = True
|
||||
|
||||
if not _LINKER_LINE.match(line):
|
||||
continue
|
||||
if _LINKER_LINE_IGNORE.match(line):
|
||||
continue
|
||||
tty.debug(f"implicit link dirs: link line: {line}")
|
||||
|
||||
next_arg = False
|
||||
for arg in line.split():
|
||||
if arg in ("-L", "-Y"):
|
||||
next_arg = True
|
||||
continue
|
||||
|
||||
if next_arg:
|
||||
raw_link_dirs.append(arg)
|
||||
next_arg = False
|
||||
continue
|
||||
|
||||
link_dir_arg = _LINK_DIR_ARG.match(arg)
|
||||
if link_dir_arg:
|
||||
link_dir = link_dir_arg.group("dir")
|
||||
raw_link_dirs.append(link_dir)
|
||||
|
||||
link_dir_arg = _LIBPATH_ARG.match(arg)
|
||||
if link_dir_arg:
|
||||
link_dir = link_dir_arg.group("dir")
|
||||
raw_link_dirs.append(link_dir)
|
||||
|
||||
implicit_link_dirs = list()
|
||||
visited = set()
|
||||
for link_dir in raw_link_dirs:
|
||||
normalized_path = os.path.abspath(link_dir)
|
||||
if normalized_path not in visited:
|
||||
implicit_link_dirs.append(normalized_path)
|
||||
visited.add(normalized_path)
|
||||
|
||||
tty.debug(f"implicit link dirs: result: {', '.join(implicit_link_dirs)}")
|
||||
return implicit_link_dirs
|
||||
|
||||
|
||||
@llnl.path.system_path_filter
|
||||
def _parse_non_system_link_dirs(string: str) -> List[str]:
|
||||
"""Parses link paths out of compiler debug output.
|
||||
|
||||
Args:
|
||||
string: compiler debug output as a string
|
||||
|
||||
Returns:
|
||||
Implicit link paths parsed from the compiler output
|
||||
"""
|
||||
link_dirs = _parse_link_paths(string)
|
||||
|
||||
# Remove directories that do not exist. Some versions of the Cray compiler
|
||||
# report nonexistent directories
|
||||
link_dirs = [d for d in link_dirs if os.path.isdir(d)]
|
||||
|
||||
# Return set of directories containing needed compiler libs, minus
|
||||
# system paths. Note that 'filter_system_paths' only checks for an
|
||||
# exact match, while 'in_system_subdirectory' checks if a path contains
|
||||
# a system directory as a subdirectory
|
||||
link_dirs = filter_system_paths(link_dirs)
|
||||
return list(p for p in link_dirs if not in_system_subdirectory(p))
|
||||
|
||||
|
||||
def in_system_subdirectory(path):
|
||||
system_dirs = [
|
||||
"/lib/",
|
||||
"/lib64/",
|
||||
"/usr/lib/",
|
||||
"/usr/lib64/",
|
||||
"/usr/local/lib/",
|
||||
"/usr/local/lib64/",
|
||||
]
|
||||
return any(path_contains_subdirectory(path, x) for x in system_dirs)
|
||||
|
||||
|
||||
class Compiler:
|
||||
"""This class encapsulates a Spack "compiler", which includes C,
|
||||
C++, and Fortran compilers. Subclasses should implement
|
||||
support for specific compilers, their possible names, arguments,
|
||||
and how to identify the particular type of compiler."""
|
||||
|
||||
# Optional prefix regexes for searching for this type of compiler.
|
||||
# Prefixes are sometimes used for toolchains
|
||||
prefixes: List[str] = []
|
||||
|
||||
# Optional suffix regexes for searching for this type of compiler.
|
||||
# Suffixes are used by some frameworks, e.g. macports uses an '-mp-X.Y'
|
||||
# version suffix for gcc.
|
||||
suffixes = [r"-.*"]
|
||||
|
||||
#: Compiler argument that produces version information
|
||||
version_argument = "-dumpversion"
|
||||
|
||||
#: Return values to ignore when invoking the compiler to get its version
|
||||
ignore_version_errors: Sequence[int] = ()
|
||||
|
||||
#: Regex used to extract version from compiler's output
|
||||
version_regex = "(.*)"
|
||||
|
||||
# These libraries are anticipated to be required by all executables built
|
||||
# by any compiler
|
||||
_all_compiler_rpath_libraries = ["libc", "libc++", "libstdc++"]
|
||||
|
||||
#: Platform matcher for Platform objects supported by compiler
|
||||
is_supported_on_platform = lambda x: True
|
||||
|
||||
# Default flags used by a compiler to set an rpath
|
||||
@property
|
||||
def cc_rpath_arg(self):
|
||||
return "-Wl,-rpath,"
|
||||
|
||||
@property
|
||||
def cxx_rpath_arg(self):
|
||||
return "-Wl,-rpath,"
|
||||
|
||||
@property
|
||||
def f77_rpath_arg(self):
|
||||
return "-Wl,-rpath,"
|
||||
|
||||
@property
|
||||
def fc_rpath_arg(self):
|
||||
return "-Wl,-rpath,"
|
||||
|
||||
@property
|
||||
def linker_arg(self):
|
||||
"""Flag that need to be used to pass an argument to the linker."""
|
||||
return "-Wl,"
|
||||
|
||||
@property
|
||||
def disable_new_dtags(self):
|
||||
if platform.system() == "Darwin":
|
||||
return ""
|
||||
return "--disable-new-dtags"
|
||||
|
||||
@property
|
||||
def enable_new_dtags(self):
|
||||
if platform.system() == "Darwin":
|
||||
return ""
|
||||
return "--enable-new-dtags"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-g"]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cspec,
|
||||
operating_system,
|
||||
target,
|
||||
paths,
|
||||
modules: Optional[List[str]] = None,
|
||||
alias=None,
|
||||
environment=None,
|
||||
extra_rpaths=None,
|
||||
enable_implicit_rpaths=None,
|
||||
**kwargs,
|
||||
):
|
||||
self.spec = cspec
|
||||
self.operating_system = str(operating_system)
|
||||
self.target = target
|
||||
self.modules = modules or []
|
||||
self.alias = alias
|
||||
self.environment = environment or {}
|
||||
self.extra_rpaths = extra_rpaths or []
|
||||
self.enable_implicit_rpaths = enable_implicit_rpaths
|
||||
self.cache = COMPILER_CACHE
|
||||
|
||||
self.cc = paths[0]
|
||||
self.cxx = paths[1]
|
||||
self.f77 = None
|
||||
self.fc = None
|
||||
if len(paths) > 2:
|
||||
self.f77 = paths[2]
|
||||
if len(paths) == 3:
|
||||
self.fc = self.f77
|
||||
else:
|
||||
self.fc = paths[3]
|
||||
|
||||
# Unfortunately have to make sure these params are accepted
|
||||
# in the same order they are returned by sorted(flags)
|
||||
# in compilers/__init__.py
|
||||
self.flags = spack.spec.FlagMap(self.spec)
|
||||
for flag in self.flags.valid_compiler_flags():
|
||||
value = kwargs.get(flag, None)
|
||||
if value is not None:
|
||||
values_with_propagation = tokenize_flags(value, False)
|
||||
for value, propagation in values_with_propagation:
|
||||
self.flags.add_flag(flag, value, propagation)
|
||||
|
||||
# caching value for compiler reported version
|
||||
# used for version checks for API, e.g. C++11 flag
|
||||
self._real_version = None
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
self.cc == other.cc
|
||||
and self.cxx == other.cxx
|
||||
and self.fc == other.fc
|
||||
and self.f77 == other.f77
|
||||
and self.spec == other.spec
|
||||
and self.operating_system == other.operating_system
|
||||
and self.target == other.target
|
||||
and self.flags == other.flags
|
||||
and self.modules == other.modules
|
||||
and self.environment == other.environment
|
||||
and self.extra_rpaths == other.extra_rpaths
|
||||
and self.enable_implicit_rpaths == other.enable_implicit_rpaths
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(
|
||||
(
|
||||
self.cc,
|
||||
self.cxx,
|
||||
self.fc,
|
||||
self.f77,
|
||||
self.spec,
|
||||
self.operating_system,
|
||||
self.target,
|
||||
str(self.flags),
|
||||
str(self.modules),
|
||||
str(self.environment),
|
||||
str(self.extra_rpaths),
|
||||
self.enable_implicit_rpaths,
|
||||
)
|
||||
)
|
||||
|
||||
def verify_executables(self):
|
||||
"""Raise an error if any of the compiler executables is not valid.
|
||||
|
||||
This method confirms that for all of the compilers (cc, cxx, f77, fc)
|
||||
that have paths, those paths exist and are executable by the current
|
||||
user.
|
||||
Raises a CompilerAccessError if any of the non-null paths for the
|
||||
compiler are not accessible.
|
||||
"""
|
||||
|
||||
def accessible_exe(exe):
|
||||
# compilers may contain executable names (on Cray or user edited)
|
||||
if not os.path.isabs(exe):
|
||||
exe = spack.util.executable.which_string(exe)
|
||||
if not exe:
|
||||
return False
|
||||
return os.path.isfile(exe) and os.access(exe, os.X_OK)
|
||||
|
||||
# setup environment before verifying in case we have executable names
|
||||
# instead of absolute paths
|
||||
with self.compiler_environment():
|
||||
missing = [
|
||||
cmp
|
||||
for cmp in (self.cc, self.cxx, self.f77, self.fc)
|
||||
if cmp and not accessible_exe(cmp)
|
||||
]
|
||||
if missing:
|
||||
raise CompilerAccessError(self, missing)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.spec.version
|
||||
|
||||
@property
|
||||
def real_version(self):
|
||||
"""Executable reported compiler version used for API-determinations
|
||||
|
||||
E.g. C++11 flag checks.
|
||||
"""
|
||||
real_version_str = self.cache.get(self).real_version
|
||||
if not real_version_str or real_version_str == "unknown":
|
||||
return self.version
|
||||
|
||||
return spack.version.StandardVersion.from_string(real_version_str)
|
||||
|
||||
def implicit_rpaths(self) -> List[str]:
|
||||
if self.enable_implicit_rpaths is False:
|
||||
return []
|
||||
|
||||
output = self.compiler_verbose_output
|
||||
|
||||
if not output:
|
||||
return []
|
||||
|
||||
link_dirs = _parse_non_system_link_dirs(output)
|
||||
|
||||
all_required_libs = list(self.required_libs) + Compiler._all_compiler_rpath_libraries
|
||||
return list(paths_containing_libs(link_dirs, all_required_libs))
|
||||
|
||||
@property
|
||||
def default_dynamic_linker(self) -> Optional[str]:
|
||||
"""Determine default dynamic linker from compiler link line"""
|
||||
output = self.compiler_verbose_output
|
||||
|
||||
if not output:
|
||||
return None
|
||||
|
||||
return spack.util.libc.parse_dynamic_linker(output)
|
||||
|
||||
@property
|
||||
def default_libc(self) -> Optional["spack.spec.Spec"]:
|
||||
"""Determine libc targeted by the compiler from link line"""
|
||||
# technically this should be testing the target platform of the compiler, but we don't have
|
||||
# that, so stick to host platform for now.
|
||||
if sys.platform in ("darwin", "win32"):
|
||||
return None
|
||||
|
||||
dynamic_linker = self.default_dynamic_linker
|
||||
|
||||
if not dynamic_linker:
|
||||
return None
|
||||
|
||||
return spack.util.libc.libc_from_dynamic_linker(dynamic_linker)
|
||||
|
||||
@property
|
||||
def required_libs(self):
|
||||
"""For executables created with this compiler, the compiler libraries
|
||||
that would be generally required to run it.
|
||||
"""
|
||||
# By default every compiler returns the empty list
|
||||
return []
|
||||
|
||||
@property
|
||||
def compiler_verbose_output(self) -> Optional[str]:
|
||||
"""Verbose output from compiling a dummy C source file. Output is cached."""
|
||||
return self.cache.get(self).c_compiler_output
|
||||
|
||||
def _compile_dummy_c_source(self) -> Optional[str]:
|
||||
if self.cc:
|
||||
cc = self.cc
|
||||
ext = "c"
|
||||
else:
|
||||
cc = self.cxx
|
||||
ext = "cc"
|
||||
|
||||
if not cc or not self.verbose_flag:
|
||||
return None
|
||||
|
||||
try:
|
||||
tmpdir = tempfile.mkdtemp(prefix="spack-implicit-link-info")
|
||||
fout = os.path.join(tmpdir, "output")
|
||||
fin = os.path.join(tmpdir, f"main.{ext}")
|
||||
|
||||
with open(fin, "w") as csource:
|
||||
csource.write(
|
||||
"int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }\n"
|
||||
)
|
||||
cc_exe = spack.util.executable.Executable(cc)
|
||||
for flag_type in ["cflags" if cc == self.cc else "cxxflags", "cppflags", "ldflags"]:
|
||||
cc_exe.add_default_arg(*self.flags.get(flag_type, []))
|
||||
|
||||
with self.compiler_environment():
|
||||
return cc_exe(self.verbose_flag, fin, "-o", fout, output=str, error=str)
|
||||
except spack.util.executable.ProcessError as pe:
|
||||
tty.debug("ProcessError: Command exited with non-zero status: " + pe.long_message)
|
||||
return None
|
||||
finally:
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
|
||||
@property
|
||||
def verbose_flag(self) -> Optional[str]:
|
||||
"""
|
||||
This property should be overridden in the compiler subclass if a
|
||||
verbose flag is available.
|
||||
|
||||
If it is not overridden, it is assumed to not be supported.
|
||||
"""
|
||||
|
||||
# This property should be overridden in the compiler subclass if
|
||||
# OpenMP is supported by that compiler
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
# If it is not overridden, assume it is not supported and warn the user
|
||||
raise UnsupportedCompilerFlag(self, "OpenMP", "openmp_flag")
|
||||
|
||||
# This property should be overridden in the compiler subclass if
|
||||
# C++98 is not the default standard for that compiler
|
||||
@property
|
||||
def cxx98_flag(self):
|
||||
return ""
|
||||
|
||||
# This property should be overridden in the compiler subclass if
|
||||
# C++11 is supported by that compiler
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
# If it is not overridden, assume it is not supported and warn the user
|
||||
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag")
|
||||
|
||||
# This property should be overridden in the compiler subclass if
|
||||
# C++14 is supported by that compiler
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
# If it is not overridden, assume it is not supported and warn the user
|
||||
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag")
|
||||
|
||||
# This property should be overridden in the compiler subclass if
|
||||
# C++17 is supported by that compiler
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
# If it is not overridden, assume it is not supported and warn the user
|
||||
raise UnsupportedCompilerFlag(self, "the C++17 standard", "cxx17_flag")
|
||||
|
||||
# This property should be overridden in the compiler subclass if
|
||||
# C99 is supported by that compiler
|
||||
@property
|
||||
def c99_flag(self):
|
||||
# If it is not overridden, assume it is not supported and warn the user
|
||||
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag")
|
||||
|
||||
# This property should be overridden in the compiler subclass if
|
||||
# C11 is supported by that compiler
|
||||
@property
|
||||
def c11_flag(self):
|
||||
# If it is not overridden, assume it is not supported and warn the user
|
||||
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag")
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
"""Returns the flag used by the C compiler to produce
|
||||
Position Independent Code (PIC)."""
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
"""Returns the flag used by the C++ compiler to produce
|
||||
Position Independent Code (PIC)."""
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
"""Returns the flag used by the F77 compiler to produce
|
||||
Position Independent Code (PIC)."""
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
"""Returns the flag used by the FC compiler to produce
|
||||
Position Independent Code (PIC)."""
|
||||
return "-fPIC"
|
||||
|
||||
# Note: This is not a class method. The class methods are used to detect
|
||||
# compilers on PATH based systems, and do not set up the run environment of
|
||||
# the compiler. This method can be called on `module` based systems as well
|
||||
def get_real_version(self) -> str:
|
||||
"""Query the compiler for its version.
|
||||
|
||||
This is the "real" compiler version, regardless of what is in the
|
||||
compilers.yaml file, which the user can change to name their compiler.
|
||||
|
||||
Use the runtime environment of the compiler (modules and environment
|
||||
modifications) to enable the compiler to run properly on any platform.
|
||||
"""
|
||||
cc = spack.util.executable.Executable(self.cc)
|
||||
try:
|
||||
with self.compiler_environment():
|
||||
output = cc(
|
||||
self.version_argument,
|
||||
output=str,
|
||||
error=str,
|
||||
ignore_errors=tuple(self.ignore_version_errors),
|
||||
)
|
||||
return self.extract_version_from_output(output)
|
||||
except spack.util.executable.ProcessError:
|
||||
return "unknown"
|
||||
|
||||
@property
|
||||
def prefix(self):
|
||||
"""Query the compiler for its install prefix. This is the install
|
||||
path as reported by the compiler. Note that paths for cc, cxx, etc
|
||||
are not enough to find the install prefix of the compiler, since
|
||||
the can be symlinks, wrappers, or filenames instead of absolute paths."""
|
||||
raise NotImplementedError("prefix is not implemented for this compiler")
|
||||
|
||||
#
|
||||
# Compiler classes have methods for querying the version of
|
||||
# specific compiler executables. This is used when discovering compilers.
|
||||
#
|
||||
# Compiler *instances* are just data objects, and can only be
|
||||
# constructed from an actual set of executables.
|
||||
#
|
||||
@classmethod
|
||||
def default_version(cls, cc):
|
||||
"""Override just this to override all compiler version functions."""
|
||||
output = get_compiler_version_output(
|
||||
cc, cls.version_argument, tuple(cls.ignore_version_errors)
|
||||
)
|
||||
return cls.extract_version_from_output(output)
|
||||
|
||||
@classmethod
|
||||
@llnl.util.lang.memoized
|
||||
def extract_version_from_output(cls, output: str) -> str:
|
||||
"""Extracts the version from compiler's output."""
|
||||
match = re.search(cls.version_regex, output)
|
||||
return match.group(1) if match else "unknown"
|
||||
|
||||
@classmethod
|
||||
def cc_version(cls, cc):
|
||||
return cls.default_version(cc)
|
||||
|
||||
@classmethod
|
||||
def search_regexps(cls, language):
|
||||
# Compile all the regular expressions used for files beforehand.
|
||||
# This searches for any combination of <prefix><name><suffix>
|
||||
# defined for the compiler
|
||||
compiler_names = getattr(cls, "{0}_names".format(language))
|
||||
prefixes = [""] + cls.prefixes
|
||||
suffixes = [""]
|
||||
if sys.platform == "win32":
|
||||
ext = r"\.(?:exe|bat)"
|
||||
cls_suf = [suf + ext for suf in cls.suffixes]
|
||||
ext_suf = [ext]
|
||||
suffixes = suffixes + cls.suffixes + cls_suf + ext_suf
|
||||
else:
|
||||
suffixes = suffixes + cls.suffixes
|
||||
regexp_fmt = r"^({0}){1}({2})$"
|
||||
return [
|
||||
re.compile(regexp_fmt.format(prefix, re.escape(name), suffix))
|
||||
for prefix, name, suffix in itertools.product(prefixes, compiler_names, suffixes)
|
||||
]
|
||||
|
||||
def setup_custom_environment(self, pkg, env):
|
||||
"""Set any environment variables necessary to use the compiler."""
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
"""Return a string representation of the compiler toolchain."""
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of the compiler toolchain."""
|
||||
return "%s(%s)" % (
|
||||
self.name,
|
||||
"\n ".join(
|
||||
(
|
||||
str(s)
|
||||
for s in (
|
||||
self.cc,
|
||||
self.cxx,
|
||||
self.f77,
|
||||
self.fc,
|
||||
self.modules,
|
||||
str(self.operating_system),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def compiler_environment(self):
|
||||
# Avoid modifying os.environ if possible.
|
||||
if not self.modules and not self.environment:
|
||||
yield
|
||||
return
|
||||
|
||||
# store environment to replace later
|
||||
backup_env = os.environ.copy()
|
||||
|
||||
try:
|
||||
# load modules and set env variables
|
||||
for module in self.modules:
|
||||
spack.util.module_cmd.load_module(module)
|
||||
|
||||
# apply other compiler environment changes
|
||||
spack.schema.environment.parse(self.environment).apply_modifications()
|
||||
|
||||
yield
|
||||
finally:
|
||||
# Restore environment regardless of whether inner code succeeded
|
||||
os.environ.clear()
|
||||
os.environ.update(backup_env)
|
||||
|
||||
def to_dict(self):
|
||||
flags_dict = {fname: " ".join(fvals) for fname, fvals in self.flags.items()}
|
||||
flags_dict.update(
|
||||
{attr: getattr(self, attr, None) for attr in FLAG_INSTANCE_VARS if hasattr(self, attr)}
|
||||
)
|
||||
result = {
|
||||
"spec": str(self.spec),
|
||||
"paths": {attr: getattr(self, attr, None) for attr in PATH_INSTANCE_VARS},
|
||||
"flags": flags_dict,
|
||||
"operating_system": str(self.operating_system),
|
||||
"target": str(self.target),
|
||||
"modules": self.modules or [],
|
||||
"environment": self.environment or {},
|
||||
"extra_rpaths": self.extra_rpaths or [],
|
||||
}
|
||||
|
||||
if self.enable_implicit_rpaths is not None:
|
||||
result["implicit_rpaths"] = self.enable_implicit_rpaths
|
||||
|
||||
if self.alias:
|
||||
result["alias"] = self.alias
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class CompilerAccessError(spack.error.SpackError):
|
||||
def __init__(self, compiler, paths):
|
||||
msg = "Compiler '%s' has executables that are missing" % compiler.spec
|
||||
msg += " or are not executable: %s" % paths
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class InvalidCompilerError(spack.error.SpackError):
|
||||
def __init__(self):
|
||||
super().__init__("Compiler has no executables.")
|
||||
|
||||
|
||||
class UnsupportedCompilerFlag(spack.error.SpackError):
|
||||
def __init__(self, compiler, feature, flag_name, ver_string=None):
|
||||
super().__init__(
|
||||
"{0} ({1}) does not support {2} (as compiler.{3}).".format(
|
||||
compiler.name, ver_string if ver_string else compiler.version, feature, flag_name
|
||||
),
|
||||
"If you think it should, please edit the compiler.{0} subclass to".format(
|
||||
compiler.name
|
||||
)
|
||||
+ " implement the {0} property and submit a pull request or issue.".format(flag_name),
|
||||
)
|
||||
|
||||
|
||||
class CompilerCacheEntry:
|
||||
"""Deserialized cache entry for a compiler"""
|
||||
|
||||
__slots__ = ["c_compiler_output", "real_version"]
|
||||
|
||||
def __init__(self, c_compiler_output: Optional[str], real_version: str):
|
||||
self.c_compiler_output = c_compiler_output
|
||||
self.real_version = real_version
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Optional[str]]):
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"Invalid {cls.__name__} data")
|
||||
c_compiler_output = data.get("c_compiler_output")
|
||||
real_version = data.get("real_version")
|
||||
if not isinstance(real_version, str) or not isinstance(
|
||||
c_compiler_output, (str, type(None))
|
||||
):
|
||||
raise ValueError(f"Invalid {cls.__name__} data")
|
||||
return cls(c_compiler_output, real_version)
|
||||
|
||||
|
||||
class CompilerCache:
|
||||
"""Base class for compiler output cache. Default implementation does not cache anything."""
|
||||
|
||||
def value(self, compiler: Compiler) -> Dict[str, Optional[str]]:
|
||||
return {
|
||||
"c_compiler_output": compiler._compile_dummy_c_source(),
|
||||
"real_version": compiler.get_real_version(),
|
||||
}
|
||||
|
||||
def get(self, compiler: Compiler) -> CompilerCacheEntry:
|
||||
return CompilerCacheEntry.from_dict(self.value(compiler))
|
||||
|
||||
|
||||
class FileCompilerCache(CompilerCache):
|
||||
"""Cache for compiler output, which is used to determine implicit link paths, the default libc
|
||||
version, and the compiler version."""
|
||||
|
||||
name = os.path.join("compilers", "compilers.json")
|
||||
|
||||
def __init__(self, cache: "FileCache") -> None:
|
||||
self.cache = cache
|
||||
self.cache.init_entry(self.name)
|
||||
self._data: Dict[str, Dict[str, Optional[str]]] = {}
|
||||
|
||||
def _get_entry(self, key: str) -> Optional[CompilerCacheEntry]:
|
||||
try:
|
||||
return CompilerCacheEntry.from_dict(self._data[key])
|
||||
except ValueError:
|
||||
del self._data[key]
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get(self, compiler: Compiler) -> CompilerCacheEntry:
|
||||
# Cache hit
|
||||
try:
|
||||
with self.cache.read_transaction(self.name) as f:
|
||||
assert f is not None
|
||||
self._data = json.loads(f.read())
|
||||
assert isinstance(self._data, dict)
|
||||
except (json.JSONDecodeError, AssertionError):
|
||||
self._data = {}
|
||||
|
||||
key = self._key(compiler)
|
||||
value = self._get_entry(key)
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
# Cache miss
|
||||
with self.cache.write_transaction(self.name) as (old, new):
|
||||
try:
|
||||
assert old is not None
|
||||
self._data = json.loads(old.read())
|
||||
assert isinstance(self._data, dict)
|
||||
except (json.JSONDecodeError, AssertionError):
|
||||
self._data = {}
|
||||
|
||||
# Use cache entry that may have been created by another process in the meantime.
|
||||
entry = self._get_entry(key)
|
||||
|
||||
# Finally compute the cache entry
|
||||
if entry is None:
|
||||
self._data[key] = self.value(compiler)
|
||||
entry = CompilerCacheEntry.from_dict(self._data[key])
|
||||
|
||||
new.write(json.dumps(self._data, separators=(",", ":")))
|
||||
|
||||
return entry
|
||||
|
||||
def _key(self, compiler: Compiler) -> str:
|
||||
as_bytes = json.dumps(compiler.to_dict(), separators=(",", ":")).encode("utf-8")
|
||||
return hashlib.sha256(as_bytes).hexdigest()
|
||||
|
||||
|
||||
def _make_compiler_cache():
|
||||
return FileCompilerCache(spack.caches.MISC_CACHE)
|
||||
|
||||
|
||||
COMPILER_CACHE: CompilerCache = llnl.util.lang.Singleton(_make_compiler_cache) # type: ignore
|
||||
@@ -2,3 +2,836 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""This module contains functions related to finding compilers on the
|
||||
system and configuring Spack to use multiple compilers.
|
||||
"""
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.compiler
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.paths
|
||||
import spack.platforms
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
from spack.operating_systems import windows_os
|
||||
from spack.util.environment import get_path
|
||||
from spack.util.naming import mod_to_class
|
||||
|
||||
_other_instance_vars = [
|
||||
"modules",
|
||||
"operating_system",
|
||||
"environment",
|
||||
"implicit_rpaths",
|
||||
"extra_rpaths",
|
||||
]
|
||||
|
||||
# TODO: Caches at module level make it difficult to mock configurations in
|
||||
# TODO: unit tests. It might be worth reworking their implementation.
|
||||
#: cache of compilers constructed from config data, keyed by config entry id.
|
||||
_compiler_cache: Dict[str, "spack.compiler.Compiler"] = {}
|
||||
|
||||
_compiler_to_pkg = {
|
||||
"clang": "llvm+clang",
|
||||
"oneapi": "intel-oneapi-compilers",
|
||||
"rocmcc": "llvm-amdgpu",
|
||||
"intel@2020:": "intel-oneapi-compilers-classic",
|
||||
"arm": "acfl",
|
||||
}
|
||||
|
||||
# TODO: generating this from the previous dict causes docs errors
|
||||
package_name_to_compiler_name = {
|
||||
"llvm": "clang",
|
||||
"intel-oneapi-compilers": "oneapi",
|
||||
"llvm-amdgpu": "rocmcc",
|
||||
"intel-oneapi-compilers-classic": "intel",
|
||||
"acfl": "arm",
|
||||
}
|
||||
|
||||
|
||||
#: Tag used to identify packages providing a compiler
|
||||
COMPILER_TAG = "compiler"
|
||||
|
||||
|
||||
def pkg_spec_for_compiler(cspec):
|
||||
"""Return the spec of the package that provides the compiler."""
|
||||
for spec, package in _compiler_to_pkg.items():
|
||||
if cspec.satisfies(spec):
|
||||
spec_str = "%s@%s" % (package, cspec.versions)
|
||||
break
|
||||
else:
|
||||
spec_str = str(cspec)
|
||||
return spack.spec.parse_with_version_concrete(spec_str)
|
||||
|
||||
|
||||
def _auto_compiler_spec(function):
|
||||
def converter(cspec_like, *args, **kwargs):
|
||||
if not isinstance(cspec_like, spack.spec.CompilerSpec):
|
||||
cspec_like = spack.spec.CompilerSpec(cspec_like)
|
||||
return function(cspec_like, *args, **kwargs)
|
||||
|
||||
return converter
|
||||
|
||||
|
||||
def _to_dict(compiler):
|
||||
"""Return a dict version of compiler suitable to insert in YAML."""
|
||||
return {"compiler": compiler.to_dict()}
|
||||
|
||||
|
||||
def get_compiler_config(
|
||||
configuration: "spack.config.Configuration",
|
||||
*,
|
||||
scope: Optional[str] = None,
|
||||
init_config: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""Return the compiler configuration for the specified architecture."""
|
||||
config = configuration.get("compilers", scope=scope) or []
|
||||
if config or not init_config:
|
||||
return config
|
||||
|
||||
merged_config = configuration.get("compilers")
|
||||
if merged_config:
|
||||
# Config is empty for this scope
|
||||
# Do not init config because there is a non-empty scope
|
||||
return config
|
||||
|
||||
find_compilers(scope=scope)
|
||||
config = configuration.get("compilers", scope=scope)
|
||||
return config
|
||||
|
||||
|
||||
def get_compiler_config_from_packages(
|
||||
configuration: "spack.config.Configuration", *, scope: Optional[str] = None
|
||||
) -> List[Dict]:
|
||||
"""Return the compiler configuration from packages.yaml"""
|
||||
packages_yaml = configuration.get("packages", scope=scope)
|
||||
return CompilerConfigFactory.from_packages_yaml(packages_yaml)
|
||||
|
||||
|
||||
def compiler_config_files():
|
||||
config_files = list()
|
||||
config = spack.config.CONFIG
|
||||
for scope in config.writable_scopes:
|
||||
name = scope.name
|
||||
compiler_config = config.get("compilers", scope=name)
|
||||
if compiler_config:
|
||||
config_files.append(config.get_config_filename(name, "compilers"))
|
||||
compiler_config_from_packages = get_compiler_config_from_packages(config, scope=name)
|
||||
if compiler_config_from_packages:
|
||||
config_files.append(config.get_config_filename(name, "packages"))
|
||||
return config_files
|
||||
|
||||
|
||||
def add_compilers_to_config(compilers, scope=None):
|
||||
"""Add compilers to the config for the specified architecture.
|
||||
|
||||
Arguments:
|
||||
compilers: a list of Compiler objects.
|
||||
scope: configuration scope to modify.
|
||||
"""
|
||||
compiler_config = get_compiler_config(configuration=spack.config.CONFIG, scope=scope)
|
||||
for compiler in compilers:
|
||||
if not compiler.cc:
|
||||
tty.debug(f"{compiler.spec} does not have a C compiler")
|
||||
if not compiler.cxx:
|
||||
tty.debug(f"{compiler.spec} does not have a C++ compiler")
|
||||
if not compiler.f77:
|
||||
tty.debug(f"{compiler.spec} does not have a Fortran77 compiler")
|
||||
if not compiler.fc:
|
||||
tty.debug(f"{compiler.spec} does not have a Fortran compiler")
|
||||
compiler_config.append(_to_dict(compiler))
|
||||
spack.config.set("compilers", compiler_config, scope=scope)
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
def remove_compiler_from_config(compiler_spec, scope=None):
|
||||
"""Remove compilers from configuration by spec.
|
||||
|
||||
If scope is None, all the scopes are searched for removal.
|
||||
|
||||
Arguments:
|
||||
compiler_spec: compiler to be removed
|
||||
scope: configuration scope to modify
|
||||
"""
|
||||
candidate_scopes = [scope]
|
||||
if scope is None:
|
||||
candidate_scopes = spack.config.CONFIG.scopes.keys()
|
||||
|
||||
removal_happened = False
|
||||
for current_scope in candidate_scopes:
|
||||
removal_happened |= _remove_compiler_from_scope(compiler_spec, scope=current_scope)
|
||||
|
||||
msg = "`spack compiler remove` will not remove compilers defined in packages.yaml"
|
||||
msg += "\nTo remove these compilers, either edit the config or use `spack external remove`"
|
||||
tty.debug(msg)
|
||||
return removal_happened
|
||||
|
||||
|
||||
def _remove_compiler_from_scope(compiler_spec, scope):
|
||||
"""Removes a compiler from a specific configuration scope.
|
||||
|
||||
Args:
|
||||
compiler_spec: compiler to be removed
|
||||
scope: configuration scope under consideration
|
||||
|
||||
Returns:
|
||||
True if one or more compiler entries were actually removed, False otherwise
|
||||
"""
|
||||
assert scope is not None, "a specific scope is needed when calling this function"
|
||||
compiler_config = get_compiler_config(configuration=spack.config.CONFIG, scope=scope)
|
||||
filtered_compiler_config = [
|
||||
compiler_entry
|
||||
for compiler_entry in compiler_config
|
||||
if not spack.spec.parse_with_version_concrete(
|
||||
compiler_entry["compiler"]["spec"], compiler=True
|
||||
).satisfies(compiler_spec)
|
||||
]
|
||||
|
||||
if len(filtered_compiler_config) == len(compiler_config):
|
||||
return False
|
||||
|
||||
# We need to preserve the YAML type for comments, hence we are copying the
|
||||
# items in the list that has just been retrieved
|
||||
compiler_config[:] = filtered_compiler_config
|
||||
spack.config.CONFIG.set("compilers", compiler_config, scope=scope)
|
||||
return True
|
||||
|
||||
|
||||
def all_compilers_config(
|
||||
configuration: "spack.config.Configuration",
|
||||
*,
|
||||
scope: Optional[str] = None,
|
||||
init_config: bool = True,
|
||||
) -> List["spack.compiler.Compiler"]:
|
||||
"""Return a set of specs for all the compiler versions currently
|
||||
available to build with. These are instances of CompilerSpec.
|
||||
"""
|
||||
from_packages_yaml = get_compiler_config_from_packages(configuration, scope=scope)
|
||||
if from_packages_yaml:
|
||||
init_config = False
|
||||
from_compilers_yaml = get_compiler_config(configuration, scope=scope, init_config=init_config)
|
||||
|
||||
result = from_compilers_yaml + from_packages_yaml
|
||||
# Dedupe entries by the compiler they represent
|
||||
# If the entry is invalid, treat it as unique for deduplication
|
||||
key = lambda c: _compiler_from_config_entry(c["compiler"] or id(c))
|
||||
return list(llnl.util.lang.dedupe(result, key=key))
|
||||
|
||||
|
||||
def all_compiler_specs(scope=None, init_config=True):
|
||||
# Return compiler specs from the merged config.
|
||||
return [
|
||||
spack.spec.parse_with_version_concrete(s["compiler"]["spec"], compiler=True)
|
||||
for s in all_compilers_config(spack.config.CONFIG, scope=scope, init_config=init_config)
|
||||
]
|
||||
|
||||
|
||||
def find_compilers(
|
||||
path_hints: Optional[List[str]] = None,
|
||||
*,
|
||||
scope: Optional[str] = None,
|
||||
mixed_toolchain: bool = False,
|
||||
max_workers: Optional[int] = None,
|
||||
) -> List["spack.compiler.Compiler"]:
|
||||
"""Searches for compiler in the paths given as argument. If any new compiler is found, the
|
||||
configuration is updated, and the list of new compiler objects is returned.
|
||||
|
||||
Args:
|
||||
path_hints: list of path hints where to look for. A sensible default based on the ``PATH``
|
||||
environment variable will be used if the value is None
|
||||
scope: configuration scope to modify
|
||||
mixed_toolchain: allow mixing compilers from different toolchains if otherwise missing for
|
||||
a certain language
|
||||
max_workers: number of processes used to search for compilers
|
||||
"""
|
||||
import spack.detection
|
||||
|
||||
known_compilers = set(all_compilers(init_config=False))
|
||||
|
||||
if path_hints is None:
|
||||
path_hints = get_path("PATH")
|
||||
default_paths = fs.search_paths_for_executables(*path_hints)
|
||||
if sys.platform == "win32":
|
||||
default_paths.extend(windows_os.WindowsOs().compiler_search_paths)
|
||||
compiler_pkgs = spack.repo.PATH.packages_with_tags(COMPILER_TAG, full=True)
|
||||
|
||||
detected_packages = spack.detection.by_path(
|
||||
compiler_pkgs, path_hints=default_paths, max_workers=max_workers
|
||||
)
|
||||
|
||||
valid_compilers = {}
|
||||
for name, detected in detected_packages.items():
|
||||
compilers = [x for x in detected if CompilerConfigFactory.from_external_spec(x)]
|
||||
if not compilers:
|
||||
continue
|
||||
valid_compilers[name] = compilers
|
||||
|
||||
def _has_fortran_compilers(x):
|
||||
if "compilers" not in x.extra_attributes:
|
||||
return False
|
||||
|
||||
return "fortran" in x.extra_attributes["compilers"]
|
||||
|
||||
if mixed_toolchain:
|
||||
gccs = [x for x in valid_compilers.get("gcc", []) if _has_fortran_compilers(x)]
|
||||
if gccs:
|
||||
best_gcc = sorted(
|
||||
gccs, key=lambda x: spack.spec.parse_with_version_concrete(x).version
|
||||
)[-1]
|
||||
gfortran = best_gcc.extra_attributes["compilers"]["fortran"]
|
||||
for name in ("llvm", "apple-clang"):
|
||||
if name not in valid_compilers:
|
||||
continue
|
||||
candidates = valid_compilers[name]
|
||||
for candidate in candidates:
|
||||
if _has_fortran_compilers(candidate):
|
||||
continue
|
||||
candidate.extra_attributes["compilers"]["fortran"] = gfortran
|
||||
|
||||
new_compilers = []
|
||||
for name, detected in valid_compilers.items():
|
||||
for config in CompilerConfigFactory.from_specs(detected):
|
||||
c = _compiler_from_config_entry(config["compiler"])
|
||||
if c in known_compilers:
|
||||
continue
|
||||
new_compilers.append(c)
|
||||
|
||||
add_compilers_to_config(new_compilers, scope=scope)
|
||||
return new_compilers
|
||||
|
||||
|
||||
def select_new_compilers(compilers, scope=None):
|
||||
"""Given a list of compilers, remove those that are already defined in
|
||||
the configuration.
|
||||
"""
|
||||
compilers_not_in_config = []
|
||||
for c in compilers:
|
||||
arch_spec = spack.spec.ArchSpec((None, c.operating_system, c.target))
|
||||
same_specs = compilers_for_spec(
|
||||
c.spec, arch_spec=arch_spec, scope=scope, init_config=False
|
||||
)
|
||||
if not same_specs:
|
||||
compilers_not_in_config.append(c)
|
||||
|
||||
return compilers_not_in_config
|
||||
|
||||
|
||||
def supported_compilers() -> List[str]:
|
||||
"""Return a set of names of compilers supported by Spack.
|
||||
|
||||
See available_compilers() to get a list of all the available
|
||||
versions of supported compilers.
|
||||
"""
|
||||
# Hack to be able to call the compiler `apple-clang` while still
|
||||
# using a valid python name for the module
|
||||
return sorted(all_compiler_names())
|
||||
|
||||
|
||||
def supported_compilers_for_host_platform() -> List[str]:
|
||||
"""Return a set of compiler class objects supported by Spack
|
||||
that are also supported by the current host platform
|
||||
"""
|
||||
host_plat = spack.platforms.real_host()
|
||||
return supported_compilers_for_platform(host_plat)
|
||||
|
||||
|
||||
def supported_compilers_for_platform(platform: "spack.platforms.Platform") -> List[str]:
|
||||
"""Return a set of compiler class objects supported by Spack
|
||||
that are also supported by the provided platform
|
||||
|
||||
Args:
|
||||
platform (str): string representation of platform
|
||||
for which compiler compatability should be determined
|
||||
"""
|
||||
return [
|
||||
name
|
||||
for name in supported_compilers()
|
||||
if class_for_compiler_name(name).is_supported_on_platform(platform)
|
||||
]
|
||||
|
||||
|
||||
def all_compiler_names() -> List[str]:
|
||||
def replace_apple_clang(name):
|
||||
return name if name != "apple_clang" else "apple-clang"
|
||||
|
||||
return [replace_apple_clang(name) for name in all_compiler_module_names()]
|
||||
|
||||
|
||||
@llnl.util.lang.memoized
|
||||
def all_compiler_module_names() -> List[str]:
|
||||
return list(llnl.util.lang.list_modules(spack.paths.compilers_path))
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
def supported(compiler_spec):
|
||||
"""Test if a particular compiler is supported."""
|
||||
return compiler_spec.name in supported_compilers()
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
def find(compiler_spec, scope=None, init_config=True):
|
||||
"""Return specs of available compilers that match the supplied
|
||||
compiler spec. Return an empty list if nothing found."""
|
||||
return [c for c in all_compiler_specs(scope, init_config) if c.satisfies(compiler_spec)]
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
def find_specs_by_arch(compiler_spec, arch_spec, scope=None, init_config=True):
|
||||
"""Return specs of available compilers that match the supplied
|
||||
compiler spec. Return an empty list if nothing found."""
|
||||
return [
|
||||
c.spec
|
||||
for c in compilers_for_spec(
|
||||
compiler_spec, arch_spec=arch_spec, scope=scope, init_config=init_config
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def all_compilers(scope=None, init_config=True):
|
||||
return all_compilers_from(
|
||||
configuration=spack.config.CONFIG, scope=scope, init_config=init_config
|
||||
)
|
||||
|
||||
|
||||
def all_compilers_from(configuration, scope=None, init_config=True):
|
||||
compilers = []
|
||||
for items in all_compilers_config(
|
||||
configuration=configuration, scope=scope, init_config=init_config
|
||||
):
|
||||
items = items["compiler"]
|
||||
compiler = _compiler_from_config_entry(items) # can be None in error case
|
||||
if compiler:
|
||||
compilers.append(compiler)
|
||||
return compilers
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
def compilers_for_spec(compiler_spec, *, arch_spec=None, scope=None, init_config=True):
|
||||
"""This gets all compilers that satisfy the supplied CompilerSpec.
|
||||
Returns an empty list if none are found.
|
||||
"""
|
||||
config = all_compilers_config(spack.config.CONFIG, scope=scope, init_config=init_config)
|
||||
matches = set(find(compiler_spec, scope, init_config))
|
||||
compilers = []
|
||||
for cspec in matches:
|
||||
compilers.extend(get_compilers(config, cspec, arch_spec))
|
||||
return compilers
|
||||
|
||||
|
||||
def compilers_for_arch(arch_spec, scope=None):
|
||||
config = all_compilers_config(spack.config.CONFIG, scope=scope, init_config=False)
|
||||
return list(get_compilers(config, arch_spec=arch_spec))
|
||||
|
||||
|
||||
def compiler_specs_for_arch(arch_spec, scope=None):
|
||||
return [c.spec for c in compilers_for_arch(arch_spec, scope)]
|
||||
|
||||
|
||||
class CacheReference:
|
||||
"""This acts as a hashable reference to any object (regardless of whether
|
||||
the object itself is hashable) and also prevents the object from being
|
||||
garbage-collected (so if two CacheReference objects are equal, they
|
||||
will refer to the same object, since it will not have been gc'ed since
|
||||
the creation of the first CacheReference).
|
||||
"""
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
self.id = id(val)
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, CacheReference) and self.id == other.id
|
||||
|
||||
|
||||
def compiler_from_dict(items):
|
||||
cspec = spack.spec.parse_with_version_concrete(items["spec"], compiler=True)
|
||||
os = items.get("operating_system", None)
|
||||
target = items.get("target", None)
|
||||
|
||||
if not (
|
||||
"paths" in items and all(n in items["paths"] for n in spack.compiler.PATH_INSTANCE_VARS)
|
||||
):
|
||||
raise InvalidCompilerConfigurationError(cspec)
|
||||
|
||||
cls = class_for_compiler_name(cspec.name)
|
||||
|
||||
compiler_paths = []
|
||||
for c in spack.compiler.PATH_INSTANCE_VARS:
|
||||
compiler_path = items["paths"][c]
|
||||
if compiler_path != "None":
|
||||
compiler_paths.append(compiler_path)
|
||||
else:
|
||||
compiler_paths.append(None)
|
||||
|
||||
mods = items.get("modules")
|
||||
if mods == "None":
|
||||
mods = []
|
||||
|
||||
alias = items.get("alias", None)
|
||||
compiler_flags = items.get("flags", {})
|
||||
environment = items.get("environment", {})
|
||||
extra_rpaths = items.get("extra_rpaths", [])
|
||||
implicit_rpaths = items.get("implicit_rpaths", None)
|
||||
|
||||
# Starting with c22a145, 'implicit_rpaths' was a list. Now it is a
|
||||
# boolean which can be set by the user to disable all automatic
|
||||
# RPATH insertion of compiler libraries
|
||||
if implicit_rpaths is not None and not isinstance(implicit_rpaths, bool):
|
||||
implicit_rpaths = None
|
||||
|
||||
return cls(
|
||||
cspec,
|
||||
os,
|
||||
target,
|
||||
compiler_paths,
|
||||
mods,
|
||||
alias,
|
||||
environment,
|
||||
extra_rpaths,
|
||||
enable_implicit_rpaths=implicit_rpaths,
|
||||
**compiler_flags,
|
||||
)
|
||||
|
||||
|
||||
def _compiler_from_config_entry(items):
|
||||
"""Note this is intended for internal use only. To avoid re-parsing
|
||||
the same config dictionary this keeps track of its location in
|
||||
memory. If you provide the same dictionary twice it will return
|
||||
the same Compiler object (regardless of whether the dictionary
|
||||
entries have changed).
|
||||
"""
|
||||
config_id = CacheReference(items)
|
||||
compiler = _compiler_cache.get(config_id, None)
|
||||
|
||||
if compiler is None:
|
||||
try:
|
||||
compiler = compiler_from_dict(items)
|
||||
except UnknownCompilerError as e:
|
||||
warnings.warn(e.message)
|
||||
_compiler_cache[config_id] = compiler
|
||||
|
||||
return compiler
|
||||
|
||||
|
||||
def get_compilers(config, cspec=None, arch_spec=None):
|
||||
compilers = []
|
||||
|
||||
for items in config:
|
||||
items = items["compiler"]
|
||||
|
||||
# We might use equality here.
|
||||
if cspec and not spack.spec.parse_with_version_concrete(
|
||||
items["spec"], compiler=True
|
||||
).satisfies(cspec):
|
||||
continue
|
||||
|
||||
# If an arch spec is given, confirm that this compiler
|
||||
# is for the given operating system
|
||||
os = items.get("operating_system", None)
|
||||
if arch_spec and os != arch_spec.os:
|
||||
continue
|
||||
|
||||
# If an arch spec is given, confirm that this compiler
|
||||
# is for the given target. If the target is 'any', match
|
||||
# any given arch spec. If the compiler has no assigned
|
||||
# target this is an old compiler config file, skip this logic.
|
||||
target = items.get("target", None)
|
||||
|
||||
try:
|
||||
current_target = archspec.cpu.TARGETS[str(arch_spec.target)]
|
||||
family = str(current_target.family)
|
||||
except KeyError:
|
||||
# TODO: Check if this exception handling makes sense, or if we
|
||||
# TODO: need to change / refactor tests
|
||||
family = str(arch_spec.target)
|
||||
except AttributeError:
|
||||
assert arch_spec is None
|
||||
|
||||
if arch_spec and target and (target != family and target != "any"):
|
||||
# If the family of the target is the family we are seeking,
|
||||
# there's an error in the underlying configuration
|
||||
if archspec.cpu.TARGETS[target].family == family:
|
||||
msg = (
|
||||
'the "target" field in compilers.yaml accepts only '
|
||||
'target families [replace "{0}" with "{1}"'
|
||||
' in "{2}" specification]'
|
||||
)
|
||||
msg = msg.format(str(target), family, items.get("spec", "??"))
|
||||
raise ValueError(msg)
|
||||
continue
|
||||
|
||||
compiler = _compiler_from_config_entry(items)
|
||||
if compiler:
|
||||
compilers.append(compiler)
|
||||
|
||||
return compilers
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
def compiler_for_spec(compiler_spec, arch_spec):
|
||||
"""Get the compiler that satisfies compiler_spec. compiler_spec must
|
||||
be concrete."""
|
||||
assert compiler_spec.concrete
|
||||
assert arch_spec.concrete
|
||||
|
||||
compilers = compilers_for_spec(compiler_spec, arch_spec=arch_spec)
|
||||
if len(compilers) < 1:
|
||||
raise NoCompilerForSpecError(compiler_spec, arch_spec.os)
|
||||
if len(compilers) > 1:
|
||||
msg = "Multiple definitions of compiler %s " % compiler_spec
|
||||
msg += "for architecture %s:\n %s" % (arch_spec, compilers)
|
||||
tty.debug(msg)
|
||||
return compilers[0]
|
||||
|
||||
|
||||
@llnl.util.lang.memoized
|
||||
def class_for_compiler_name(compiler_name):
|
||||
"""Given a compiler module name, get the corresponding Compiler class."""
|
||||
if not supported(compiler_name):
|
||||
raise UnknownCompilerError(compiler_name)
|
||||
|
||||
# Hack to be able to call the compiler `apple-clang` while still
|
||||
# using a valid python name for the module
|
||||
submodule_name = compiler_name
|
||||
if compiler_name == "apple-clang":
|
||||
submodule_name = compiler_name.replace("-", "_")
|
||||
|
||||
module_name = ".".join(["spack", "compilers", submodule_name])
|
||||
module_obj = importlib.import_module(module_name)
|
||||
cls = getattr(module_obj, mod_to_class(compiler_name))
|
||||
|
||||
# make a note of the name in the module so we can get to it easily.
|
||||
cls.name = compiler_name
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
def all_compiler_types():
|
||||
return [class_for_compiler_name(c) for c in supported_compilers()]
|
||||
|
||||
|
||||
def is_mixed_toolchain(compiler):
|
||||
"""Returns True if the current compiler is a mixed toolchain,
|
||||
False otherwise.
|
||||
|
||||
Args:
|
||||
compiler (spack.compiler.Compiler): a valid compiler object
|
||||
"""
|
||||
import spack.detection.path
|
||||
|
||||
executables = [
|
||||
os.path.basename(compiler.cc or ""),
|
||||
os.path.basename(compiler.cxx or ""),
|
||||
os.path.basename(compiler.f77 or ""),
|
||||
os.path.basename(compiler.fc or ""),
|
||||
]
|
||||
|
||||
toolchains = set()
|
||||
finder = spack.detection.path.ExecutablesFinder()
|
||||
|
||||
for pkg_name in spack.repo.PATH.packages_with_tags(COMPILER_TAG):
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||
patterns = finder.search_patterns(pkg=pkg_cls)
|
||||
if not patterns:
|
||||
continue
|
||||
joined_pattern = re.compile(r"|".join(patterns))
|
||||
|
||||
if any(joined_pattern.search(exe) for exe in executables):
|
||||
tty.debug(f"[TOOLCHAIN] MATCH {pkg_name}")
|
||||
toolchains.add(pkg_name)
|
||||
|
||||
if len(toolchains) > 1:
|
||||
if (
|
||||
toolchains == {"llvm", "apple-clang", "aocc"}
|
||||
# Msvc toolchain uses Intel ifx
|
||||
or toolchains == {"msvc", "intel-oneapi-compilers"}
|
||||
):
|
||||
return False
|
||||
tty.debug("[TOOLCHAINS] {0}".format(toolchains))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
_EXTRA_ATTRIBUTES_KEY = "extra_attributes"
|
||||
_COMPILERS_KEY = "compilers"
|
||||
_C_KEY = "c"
|
||||
_CXX_KEY, _FORTRAN_KEY = "cxx", "fortran"
|
||||
|
||||
|
||||
class CompilerConfigFactory:
|
||||
"""Class aggregating all ways of constructing a list of compiler config entries."""
|
||||
|
||||
@staticmethod
|
||||
def from_specs(specs: List["spack.spec.Spec"]) -> List[dict]:
|
||||
result = []
|
||||
compiler_package_names = supported_compilers() + list(package_name_to_compiler_name.keys())
|
||||
for s in specs:
|
||||
if s.name not in compiler_package_names:
|
||||
continue
|
||||
|
||||
candidate = CompilerConfigFactory.from_external_spec(s)
|
||||
if candidate is None:
|
||||
continue
|
||||
|
||||
result.append(candidate)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def from_packages_yaml(packages_yaml) -> List[dict]:
|
||||
compiler_specs = []
|
||||
compiler_package_names = supported_compilers() + list(package_name_to_compiler_name.keys())
|
||||
for name, entry in packages_yaml.items():
|
||||
if name not in compiler_package_names:
|
||||
continue
|
||||
|
||||
externals_config = entry.get("externals", None)
|
||||
if not externals_config:
|
||||
continue
|
||||
|
||||
current_specs = []
|
||||
for current_external in externals_config:
|
||||
compiler = CompilerConfigFactory._spec_from_external_config(current_external)
|
||||
if compiler:
|
||||
current_specs.append(compiler)
|
||||
compiler_specs.extend(current_specs)
|
||||
|
||||
return CompilerConfigFactory.from_specs(compiler_specs)
|
||||
|
||||
@staticmethod
|
||||
def _spec_from_external_config(config):
|
||||
# Allow `@x.y.z` instead of `@=x.y.z`
|
||||
err_header = f"The external spec '{config['spec']}' cannot be used as a compiler"
|
||||
# If extra_attributes is not there I might not want to use this entry as a compiler,
|
||||
# therefore just leave a debug message, but don't be loud with a warning.
|
||||
if _EXTRA_ATTRIBUTES_KEY not in config:
|
||||
tty.debug(f"[{__file__}] {err_header}: missing the '{_EXTRA_ATTRIBUTES_KEY}' key")
|
||||
return None
|
||||
extra_attributes = config[_EXTRA_ATTRIBUTES_KEY]
|
||||
result = spack.spec.Spec(
|
||||
str(spack.spec.parse_with_version_concrete(config["spec"])),
|
||||
external_modules=config.get("modules"),
|
||||
)
|
||||
result.extra_attributes = extra_attributes
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def from_external_spec(spec: "spack.spec.Spec") -> Optional[dict]:
|
||||
spec = spack.spec.parse_with_version_concrete(spec)
|
||||
extra_attributes = getattr(spec, _EXTRA_ATTRIBUTES_KEY, None)
|
||||
if extra_attributes is None:
|
||||
return None
|
||||
|
||||
paths = CompilerConfigFactory._extract_compiler_paths(spec)
|
||||
if paths is None:
|
||||
return None
|
||||
|
||||
compiler_spec = spack.spec.CompilerSpec(
|
||||
package_name_to_compiler_name.get(spec.name, spec.name), spec.version
|
||||
)
|
||||
|
||||
operating_system, target = CompilerConfigFactory._extract_os_and_target(spec)
|
||||
|
||||
compiler_entry = {
|
||||
"compiler": {
|
||||
"spec": str(compiler_spec),
|
||||
"paths": paths,
|
||||
"flags": extra_attributes.get("flags", {}),
|
||||
"operating_system": str(operating_system),
|
||||
"target": str(target.family),
|
||||
"modules": getattr(spec, "external_modules", []),
|
||||
"environment": extra_attributes.get("environment", {}),
|
||||
"extra_rpaths": extra_attributes.get("extra_rpaths", []),
|
||||
"implicit_rpaths": extra_attributes.get("implicit_rpaths", None),
|
||||
}
|
||||
}
|
||||
return compiler_entry
|
||||
|
||||
@staticmethod
|
||||
def _extract_compiler_paths(spec: "spack.spec.Spec") -> Optional[Dict[str, str]]:
|
||||
err_header = f"The external spec '{spec}' cannot be used as a compiler"
|
||||
extra_attributes = spec.extra_attributes
|
||||
# If I have 'extra_attributes' warn if 'compilers' is missing,
|
||||
# or we don't have a C compiler
|
||||
if _COMPILERS_KEY not in extra_attributes:
|
||||
warnings.warn(
|
||||
f"{err_header}: missing the '{_COMPILERS_KEY}' key under '{_EXTRA_ATTRIBUTES_KEY}'"
|
||||
)
|
||||
return None
|
||||
attribute_compilers = extra_attributes[_COMPILERS_KEY]
|
||||
|
||||
if _C_KEY not in attribute_compilers:
|
||||
warnings.warn(
|
||||
f"{err_header}: missing the C compiler path under "
|
||||
f"'{_EXTRA_ATTRIBUTES_KEY}:{_COMPILERS_KEY}'"
|
||||
)
|
||||
return None
|
||||
c_compiler = attribute_compilers[_C_KEY]
|
||||
|
||||
# C++ and Fortran compilers are not mandatory, so let's just leave a debug trace
|
||||
if _CXX_KEY not in attribute_compilers:
|
||||
tty.debug(f"[{__file__}] The external spec {spec} does not have a C++ compiler")
|
||||
|
||||
if _FORTRAN_KEY not in attribute_compilers:
|
||||
tty.debug(f"[{__file__}] The external spec {spec} does not have a Fortran compiler")
|
||||
|
||||
# compilers format has cc/fc/f77, externals format has "c/fortran"
|
||||
return {
|
||||
"cc": c_compiler,
|
||||
"cxx": attribute_compilers.get(_CXX_KEY, None),
|
||||
"fc": attribute_compilers.get(_FORTRAN_KEY, None),
|
||||
"f77": attribute_compilers.get(_FORTRAN_KEY, None),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _extract_os_and_target(spec: "spack.spec.Spec"):
|
||||
if not spec.architecture:
|
||||
host_platform = spack.platforms.host()
|
||||
operating_system = host_platform.operating_system("default_os")
|
||||
target = host_platform.target("default_target")
|
||||
else:
|
||||
target = spec.architecture.target
|
||||
if not target:
|
||||
target = spack.platforms.host().target("default_target")
|
||||
|
||||
operating_system = spec.os
|
||||
if not operating_system:
|
||||
host_platform = spack.platforms.host()
|
||||
operating_system = host_platform.operating_system("default_os")
|
||||
return operating_system, target
|
||||
|
||||
|
||||
class InvalidCompilerConfigurationError(spack.error.SpackError):
|
||||
def __init__(self, compiler_spec):
|
||||
super().__init__(
|
||||
f'Invalid configuration for [compiler "{compiler_spec}"]: ',
|
||||
f"Compiler configuration must contain entries for "
|
||||
f"all compilers: {spack.compiler.PATH_INSTANCE_VARS}",
|
||||
)
|
||||
|
||||
|
||||
class UnknownCompilerError(spack.error.SpackError):
|
||||
def __init__(self, compiler_name):
|
||||
super().__init__("Spack doesn't support the requested compiler: {0}".format(compiler_name))
|
||||
|
||||
|
||||
class NoCompilerForSpecError(spack.error.SpackError):
|
||||
def __init__(self, compiler_spec, target):
|
||||
super().__init__(
|
||||
"No compilers for operating system %s satisfy spec %s" % (target, compiler_spec)
|
||||
)
|
||||
|
||||
@@ -1,343 +0,0 @@
|
||||
# Copyright 2013-2024 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
|
||||
# import subprocess
|
||||
# import tempfile
|
||||
# from typing import Dict
|
||||
#
|
||||
# import archspec.cpu
|
||||
#
|
||||
# import spack.operating_systems.windows_os
|
||||
# import spack.platforms
|
||||
# import spack.util.executable
|
||||
# from spack.compiler import Compiler
|
||||
# from spack.version import Version, VersionRange
|
||||
#
|
||||
# FC_PATH: Dict[str, str] = dict()
|
||||
#
|
||||
#
|
||||
# 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():
|
||||
# """Assign maximum available fortran compiler version"""
|
||||
# # TODO (johnwparent): validate compatibility w/ try compiler
|
||||
# # functionality when added
|
||||
# sort_fn = lambda fc_ver: Version(fc_ver)
|
||||
# sort_fc_ver = sorted(list(FC_PATH.keys()), key=sort_fn)
|
||||
# return FC_PATH[sort_fc_ver[-1]] if sort_fc_ver else None
|
||||
#
|
||||
#
|
||||
# class Msvc(Compiler):
|
||||
# #: Compiler argument that produces version information
|
||||
# version_argument = ""
|
||||
#
|
||||
# # For getting ifx's version, call it with version_argument
|
||||
# # and ignore the error code
|
||||
# ignore_version_errors = [1]
|
||||
#
|
||||
# #: Regex used to extract version from compiler's output
|
||||
# version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)"
|
||||
# # The MSVC compiler class overrides this to prevent instances
|
||||
# # of erroneous matching on executable names that cannot be msvc
|
||||
# # compilers
|
||||
# suffixes = []
|
||||
#
|
||||
# is_supported_on_platform = lambda x: isinstance(x, spack.platforms.Windows)
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# # 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]
|
||||
# latest_fc = get_valid_fortran_pth()
|
||||
# new_pth = [pth if pth else latest_fc for pth in paths[2:]]
|
||||
# paths[2:] = new_pth
|
||||
# # Initialize, deferring to base class but then adding the vcvarsallfile
|
||||
# # file based on compiler executable path.
|
||||
# super().__init__(*args, **kwargs)
|
||||
# # 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(os.path.dirname(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("-", "_")
|
||||
# if str(archspec.cpu.host().family) == "x86_64":
|
||||
# arch = "amd64"
|
||||
#
|
||||
# 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]:
|
||||
#
|
||||
# def get_oneapi_root(pth: str):
|
||||
# """From within a prefix known to be a oneAPI path
|
||||
# determine the oneAPI root path from arbitrary point
|
||||
# under root
|
||||
#
|
||||
# Args:
|
||||
# pth: path prefixed within oneAPI root
|
||||
# """
|
||||
# if not pth:
|
||||
# return ""
|
||||
# while os.path.basename(pth) and os.path.basename(pth) != "oneAPI":
|
||||
# pth = os.path.dirname(pth)
|
||||
# return pth
|
||||
#
|
||||
# # If this found, it sets all the vars
|
||||
# oneapi_root = get_oneapi_root(self.fc)
|
||||
# if not oneapi_root:
|
||||
# raise RuntimeError(f"Non-oneAPI Fortran compiler {self.fc} assigned to MSVC")
|
||||
# oneapi_root_setvars = os.path.join(oneapi_root, "setvars.bat")
|
||||
# # some oneAPI exes return a version more precise than their
|
||||
# # install paths specify, so we determine path from
|
||||
# # the install path rather than the fc executable itself
|
||||
# numver = r"\d+\.\d+(?:\.\d+)?"
|
||||
# pattern = f"((?:{numver})|(?:latest))"
|
||||
# version_from_path = re.search(pattern, self.fc).group(1)
|
||||
# oneapi_version_setvars = os.path.join(
|
||||
# oneapi_root, "compiler", version_from_path, "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):
|
||||
# """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>
|
||||
# """
|
||||
# return "MSVC" + self.vc_toolset_ver
|
||||
#
|
||||
# @property
|
||||
# def vc_toolset_ver(self):
|
||||
# """
|
||||
# The toolset version is the version of the combined set of cl and link
|
||||
# This typically relates directly to VS version i.e. VS 2022 is v143
|
||||
# VS 19 is v142, etc.
|
||||
# This value is defined by the first three digits of the major + minor
|
||||
# version of the VS toolset (143 for 14.3x.bbbbb). Traditionally the
|
||||
# minor version has remained a static two digit number for a VS release
|
||||
# series, however, as of VS22, this is no longer true, both
|
||||
# 14.4x.bbbbb and 14.3x.bbbbb are considered valid VS22 VC toolset
|
||||
# versions due to a change in toolset minor version sentiment.
|
||||
#
|
||||
# This is *NOT* the full version, for that see
|
||||
# Msvc.msvc_version or MSVC.platform_toolset_ver for the
|
||||
# raw platform toolset version
|
||||
#
|
||||
# """
|
||||
# ver = self.msvc_version[:2].joined.string[:3]
|
||||
# return ver
|
||||
#
|
||||
# @property
|
||||
# def platform_toolset_ver(self):
|
||||
# """
|
||||
# This is the platform toolset version of current MSVC compiler
|
||||
# i.e. 142. The platform toolset is the targeted MSVC library/compiler
|
||||
# versions by compilation (this is different from the VC Toolset)
|
||||
#
|
||||
#
|
||||
# This is different from the VC toolset version as established
|
||||
# by `short_msvc_version`, but typically are represented by the same
|
||||
# three digit value
|
||||
# """
|
||||
# # Typically VS toolset version and platform toolset versions match
|
||||
# # VS22 introduces the first divergence of VS toolset version
|
||||
# # (144 for "recent" releases) and platform toolset version (143)
|
||||
# # so it needs additional handling until MS releases v144
|
||||
# # (assuming v144 is also for VS22)
|
||||
# # or adds better support for detection
|
||||
# # TODO: (johnwparent) Update this logic for the next platform toolset
|
||||
# # or VC toolset version update
|
||||
# toolset_ver = self.vc_toolset_ver
|
||||
# vs22_toolset = Version(toolset_ver) > Version("142")
|
||||
# return toolset_ver if not vs22_toolset else "143"
|
||||
#
|
||||
# @property
|
||||
# def visual_studio_version(self):
|
||||
# """The four digit Visual Studio version (i.e. 2019 or 2022)
|
||||
#
|
||||
# Note: This differs from the msvc version or toolset version as
|
||||
# those properties track the compiler and build tools version
|
||||
# respectively, whereas this tracks the VS release associated
|
||||
# with a given MSVC compiler.
|
||||
# """
|
||||
# return re.search(r"[0-9]{4}", self.cc).group(0)
|
||||
#
|
||||
# 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.build_systems.compiler.compiler_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
|
||||
# # this should be consistent accross versions
|
||||
# return os.path.abspath(os.path.join(self.cc, "../../../../../../../.."))
|
||||
#
|
||||
# def setup_custom_environment(self, pkg, env):
|
||||
# """Set environment variables for MSVC using the
|
||||
# Microsoft-provided script."""
|
||||
# # Set the build environment variables for spack. Just using
|
||||
# # subprocess.call() doesn't work since that operates in its own
|
||||
# # environment which is destroyed (along with the adjusted variables)
|
||||
# # once the process terminates. So go the long way around: examine
|
||||
# # output, sort into dictionary, use that to make the build
|
||||
# # environment.
|
||||
#
|
||||
# # vcvars can target specific sdk versions, force it to pick up concretized sdk
|
||||
# # version, if needed by spec
|
||||
# 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())
|
||||
# if key and value
|
||||
# )
|
||||
#
|
||||
# for env_var in int_env:
|
||||
# if os.pathsep not in int_env[env_var]:
|
||||
# env.set(env_var, int_env[env_var])
|
||||
# else:
|
||||
# env.set_path(env_var, int_env[env_var].split(os.pathsep))
|
||||
#
|
||||
# # certain versions of ifx (2021.3.0:2023.1.0) do not play well with env:TMP
|
||||
# # that has a "." character in the path
|
||||
# # Work around by pointing tmp to the stage for the duration of the build
|
||||
# if self.fc and Version(self.fc_version(self.fc)).satisfies(
|
||||
# VersionRange("2021.3.0", "2023.1.0")
|
||||
# ):
|
||||
# new_tmp = tempfile.mkdtemp(dir=pkg.stage.path)
|
||||
# env.set("TMP", new_tmp)
|
||||
#
|
||||
# env.set("CC", self.cc)
|
||||
# env.set("CXX", self.cxx)
|
||||
# env.set("FC", self.fc)
|
||||
# env.set("F77", self.f77)
|
||||
@@ -1,212 +0,0 @@
|
||||
# Copyright 2013-2024 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 enum
|
||||
import typing
|
||||
from typing import Dict, List
|
||||
|
||||
from llnl.util import lang
|
||||
|
||||
from .libraries import CompilerPropertyDetector
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import spack.spec
|
||||
|
||||
|
||||
class Languages(enum.Enum):
|
||||
C = "c"
|
||||
CXX = "cxx"
|
||||
FORTRAN = "fortran"
|
||||
|
||||
|
||||
class CompilerAdaptor:
|
||||
def __init__(
|
||||
self, compiled_spec: "spack.spec.Spec", compilers: Dict[Languages, "spack.spec.Spec"]
|
||||
) -> None:
|
||||
if not compilers:
|
||||
raise AttributeError(f"{compiled_spec} has no 'compiler' attribute")
|
||||
|
||||
self.compilers = compilers
|
||||
self.compiled_spec = compiled_spec
|
||||
|
||||
def _lang_exists_or_raise(self, name: str, *, lang: Languages) -> None:
|
||||
if lang not in self.compilers:
|
||||
raise AttributeError(
|
||||
f"'{self.compiled_spec}' has no {lang.value} compiler, so the "
|
||||
f"'{name}' property cannot be retrieved"
|
||||
)
|
||||
|
||||
def _maybe_return_attribute(self, name: str, *, lang: Languages) -> str:
|
||||
self._lang_exists_or_raise(name, lang=lang)
|
||||
return getattr(self.compilers[lang].package, name)
|
||||
|
||||
@property
|
||||
def cc_rpath_arg(self) -> str:
|
||||
self._lang_exists_or_raise("cc_rpath_arg", lang=Languages.C)
|
||||
return self.compilers[Languages.C].package.rpath_arg
|
||||
|
||||
@property
|
||||
def cxx_rpath_arg(self) -> str:
|
||||
self._lang_exists_or_raise("cxx_rpath_arg", lang=Languages.CXX)
|
||||
return self.compilers[Languages.CXX].package.rpath_arg
|
||||
|
||||
@property
|
||||
def fc_rpath_arg(self) -> str:
|
||||
self._lang_exists_or_raise("fc_rpath_arg", lang=Languages.FORTRAN)
|
||||
return self.compilers[Languages.FORTRAN].package.rpath_arg
|
||||
|
||||
@property
|
||||
def f77_rpath_arg(self) -> str:
|
||||
self._lang_exists_or_raise("f77_rpath_arg", lang=Languages.FORTRAN)
|
||||
return self.compilers[Languages.FORTRAN].package.rpath_arg
|
||||
|
||||
@property
|
||||
def linker_arg(self) -> str:
|
||||
return self._maybe_return_attribute("linker_arg", lang=Languages.C)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return next(iter(self.compilers.values())).name
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return next(iter(self.compilers.values())).version
|
||||
|
||||
def implicit_rpaths(self) -> List[str]:
|
||||
result, seen = [], set()
|
||||
for compiler in self.compilers.values():
|
||||
if compiler in seen:
|
||||
continue
|
||||
seen.add(compiler)
|
||||
result.extend(CompilerPropertyDetector(compiler).implicit_rpaths())
|
||||
return result
|
||||
|
||||
@property
|
||||
def openmp_flag(self) -> str:
|
||||
return next(iter(self.compilers.values())).package.openmp_flag
|
||||
|
||||
@property
|
||||
def cxx98_flag(self) -> str:
|
||||
return self.compilers[Languages.CXX].package.standard_flag(
|
||||
language=Languages.CXX.value, standard="98"
|
||||
)
|
||||
|
||||
@property
|
||||
def cxx11_flag(self) -> str:
|
||||
return self.compilers[Languages.CXX].package.standard_flag(
|
||||
language=Languages.CXX.value, standard="11"
|
||||
)
|
||||
|
||||
@property
|
||||
def cxx14_flag(self) -> str:
|
||||
return self.compilers[Languages.CXX].package.standard_flag(
|
||||
language=Languages.CXX.value, standard="14"
|
||||
)
|
||||
|
||||
@property
|
||||
def cxx17_flag(self) -> str:
|
||||
return self.compilers[Languages.CXX].package.standard_flag(
|
||||
language=Languages.CXX.value, standard="17"
|
||||
)
|
||||
|
||||
@property
|
||||
def cxx20_flag(self) -> str:
|
||||
return self.compilers[Languages.CXX].package.standard_flag(
|
||||
language=Languages.CXX.value, standard="20"
|
||||
)
|
||||
|
||||
@property
|
||||
def cxx23_flag(self) -> str:
|
||||
return self.compilers[Languages.CXX].package.standard_flag(
|
||||
language=Languages.CXX.value, standard="23"
|
||||
)
|
||||
|
||||
@property
|
||||
def c99_flag(self) -> str:
|
||||
return self.compilers[Languages.C].package.standard_flag(
|
||||
language=Languages.C.value, standard="99"
|
||||
)
|
||||
|
||||
@property
|
||||
def c11_flag(self) -> str:
|
||||
return self.compilers[Languages.C].package.standard_flag(
|
||||
language=Languages.C.value, standard="11"
|
||||
)
|
||||
|
||||
@property
|
||||
def c17_flag(self) -> str:
|
||||
return self.compilers[Languages.C].package.standard_flag(
|
||||
language=Languages.C.value, standard="17"
|
||||
)
|
||||
|
||||
@property
|
||||
def c23_flag(self) -> str:
|
||||
return self.compilers[Languages.C].package.standard_flag(
|
||||
language=Languages.C.value, standard="17"
|
||||
)
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self) -> str:
|
||||
self._lang_exists_or_raise("cc_pic_flag", lang=Languages.C)
|
||||
return self.compilers[Languages.C].package.pic_flag
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self) -> str:
|
||||
self._lang_exists_or_raise("cxx_pic_flag", lang=Languages.CXX)
|
||||
return self.compilers[Languages.CXX].package.pic_flag
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self) -> str:
|
||||
self._lang_exists_or_raise("fc_pic_flag", lang=Languages.FORTRAN)
|
||||
return self.compilers[Languages.FORTRAN].package.pic_flag
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self) -> str:
|
||||
self._lang_exists_or_raise("f77_pic_flag", lang=Languages.FORTRAN)
|
||||
return self.compilers[Languages.FORTRAN].package.pic_flag
|
||||
|
||||
@property
|
||||
def prefix(self) -> str:
|
||||
return next(iter(self.compilers.values())).prefix
|
||||
|
||||
@property
|
||||
def extra_rpaths(self) -> List[str]:
|
||||
compiler = next(iter(self.compilers.values()))
|
||||
return getattr(compiler, "extra_attributes", {}).get("extra_rpaths", [])
|
||||
|
||||
@property
|
||||
def cc(self):
|
||||
return self._maybe_return_attribute("cc", lang=Languages.C)
|
||||
|
||||
@property
|
||||
def cxx(self):
|
||||
return self._maybe_return_attribute("cxx", lang=Languages.CXX)
|
||||
|
||||
@property
|
||||
def fc(self):
|
||||
self._lang_exists_or_raise("fc", lang=Languages.FORTRAN)
|
||||
return self.compilers[Languages.FORTRAN].package.fortran
|
||||
|
||||
@property
|
||||
def f77(self):
|
||||
self._lang_exists_or_raise("f77", lang=Languages.FORTRAN)
|
||||
return self.compilers[Languages.FORTRAN].package.fortran
|
||||
|
||||
|
||||
class DeprecatedCompiler(lang.DeprecatedProperty):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(name="compiler")
|
||||
|
||||
def factory(self, instance, owner) -> CompilerAdaptor:
|
||||
spec = instance.spec
|
||||
if not spec.concrete:
|
||||
raise ValueError("Can only get a compiler for a concrete package.")
|
||||
|
||||
compilers = {}
|
||||
for language in Languages:
|
||||
deps = spec.dependencies(virtuals=[language.value])
|
||||
if deps:
|
||||
compilers[language] = deps[0]
|
||||
|
||||
return CompilerAdaptor(instance, compilers)
|
||||
120
lib/spack/spack/compilers/aocc.py
Normal file
120
lib/spack/spack/compilers/aocc.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# Copyright 2013-2024 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
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
from spack.compiler import Compiler
|
||||
from spack.version import ver
|
||||
|
||||
|
||||
class Aocc(Compiler):
|
||||
version_argument = "--version"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return [
|
||||
"-gcodeview",
|
||||
"-gdwarf-2",
|
||||
"-gdwarf-3",
|
||||
"-gdwarf-4",
|
||||
"-gdwarf-5",
|
||||
"-gline-tables-only",
|
||||
"-gmodules",
|
||||
"-g",
|
||||
]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os", "-Oz", "-Og", "-O", "-O4"]
|
||||
|
||||
@property
|
||||
def link_paths(self):
|
||||
link_paths = {
|
||||
"cc": os.path.join("aocc", "clang"),
|
||||
"cxx": os.path.join("aocc", "clang++"),
|
||||
"f77": os.path.join("aocc", "flang"),
|
||||
"fc": os.path.join("aocc", "flang"),
|
||||
}
|
||||
|
||||
return link_paths
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-fopenmp"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
return "-std=c11"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
required_libs = ["libclang"]
|
||||
|
||||
@classmethod
|
||||
@llnl.util.lang.memoized
|
||||
def extract_version_from_output(cls, output):
|
||||
match = re.search(r"AOCC_(\d+)[._](\d+)[._](\d+)", output)
|
||||
if match:
|
||||
return ".".join(match.groups())
|
||||
return "unknown"
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
return ("-lstdc++",)
|
||||
|
||||
@property
|
||||
def cflags(self):
|
||||
return self._handle_default_flag_addtions()
|
||||
|
||||
@property
|
||||
def cxxflags(self):
|
||||
return self._handle_default_flag_addtions()
|
||||
|
||||
@property
|
||||
def fflags(self):
|
||||
return self._handle_default_flag_addtions()
|
||||
|
||||
def _handle_default_flag_addtions(self):
|
||||
# This is a known issue for AOCC 3.0 see:
|
||||
# https://developer.amd.com/wp-content/resources/AOCC-3.0-Install-Guide.pdf
|
||||
if self.version.satisfies(ver("3.0.0")):
|
||||
return "-Wno-unused-command-line-argument " "-mllvm -eliminate-similar-expr=false"
|
||||
116
lib/spack/spack/compilers/apple_clang.py
Normal file
116
lib/spack/spack/compilers/apple_clang.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Copyright 2013-2024 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 re
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
import spack.compiler
|
||||
import spack.compilers.clang
|
||||
from spack.version import Version
|
||||
|
||||
|
||||
class AppleClang(spack.compilers.clang.Clang):
|
||||
openmp_flag = "-Xpreprocessor -fopenmp"
|
||||
|
||||
@classmethod
|
||||
@llnl.util.lang.memoized
|
||||
def extract_version_from_output(cls, output):
|
||||
ver = "unknown"
|
||||
match = re.search(
|
||||
# Apple's LLVM compiler has its own versions, so suffix them.
|
||||
r"^Apple (?:LLVM|clang) version ([^ )]+)",
|
||||
output,
|
||||
# Multi-line, since 'Apple clang' may not be on the first line
|
||||
# in particular, when run as gcc, it seems to output
|
||||
# "Configured with: --prefix=..." as the first line
|
||||
re.M,
|
||||
)
|
||||
if match:
|
||||
ver = match.group(match.lastindex)
|
||||
return ver
|
||||
|
||||
# C++ flags based on CMake Modules/Compiler/AppleClang-CXX.cmake
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
# Spack's AppleClang detection only valid from Xcode >= 4.6
|
||||
if self.real_version < Version("4.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++11 standard", "cxx11_flag", "Xcode < 4.0"
|
||||
)
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
if self.real_version < Version("5.1"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++14 standard", "cxx14_flag", "Xcode < 5.1"
|
||||
)
|
||||
elif self.real_version < Version("6.1"):
|
||||
return "-std=c++1y"
|
||||
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
if self.real_version < Version("6.1"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++17 standard", "cxx17_flag", "Xcode < 6.1"
|
||||
)
|
||||
elif self.real_version < Version("10.0"):
|
||||
return "-std=c++1z"
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def cxx20_flag(self):
|
||||
if self.real_version < Version("10.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++20 standard", "cxx20_flag", "Xcode < 10.0"
|
||||
)
|
||||
elif self.real_version < Version("13.0"):
|
||||
return "-std=c++2a"
|
||||
return "-std=c++20"
|
||||
|
||||
@property
|
||||
def cxx23_flag(self):
|
||||
if self.real_version < Version("13.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++23 standard", "cxx23_flag", "Xcode < 13.0"
|
||||
)
|
||||
return "-std=c++2b"
|
||||
|
||||
# C flags based on CMake Modules/Compiler/AppleClang-C.cmake
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
if self.real_version < Version("4.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C99 standard", "c99_flag", "< 4.0"
|
||||
)
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
if self.real_version < Version("4.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C11 standard", "c11_flag", "< 4.0"
|
||||
)
|
||||
return "-std=c11"
|
||||
|
||||
@property
|
||||
def c17_flag(self):
|
||||
if self.real_version < Version("11.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C17 standard", "c17_flag", "< 11.0"
|
||||
)
|
||||
return "-std=c17"
|
||||
|
||||
@property
|
||||
def c23_flag(self):
|
||||
if self.real_version < Version("11.0.3"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C23 standard", "c23_flag", "< 11.0.3"
|
||||
)
|
||||
return "-std=c2x"
|
||||
80
lib/spack/spack/compilers/arm.py
Normal file
80
lib/spack/spack/compilers/arm.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# Copyright 2013-2024 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 spack.compiler
|
||||
|
||||
|
||||
class Arm(spack.compiler.Compiler):
|
||||
# Named wrapper links within lib/spack/env
|
||||
link_paths = {
|
||||
"cc": os.path.join("arm", "armclang"),
|
||||
"cxx": os.path.join("arm", "armclang++"),
|
||||
"f77": os.path.join("arm", "armflang"),
|
||||
"fc": os.path.join("arm", "armflang"),
|
||||
}
|
||||
|
||||
# The ``--version`` option seems to be the most consistent one for
|
||||
# arm compilers. Output looks like this:
|
||||
#
|
||||
# $ arm<c/f>lang --version
|
||||
# Arm C/C++/Fortran Compiler version 19.0 (build number 73) (based on LLVM 7.0.2)
|
||||
# Target: aarch64--linux-gnu
|
||||
# Thread model: posix
|
||||
# InstalledDir:
|
||||
# /opt/arm/arm-hpc-compiler-19.0_Generic-AArch64_RHEL-7_aarch64-linux/bin
|
||||
version_argument = "--version"
|
||||
version_regex = r"Arm C\/C\+\+\/Fortran Compiler version ([\d\.]+) "
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-fopenmp"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
return "-std=c++1z"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
return "-std=c11"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
required_libs = ["libclang", "libflang"]
|
||||
128
lib/spack/spack/compilers/cce.py
Normal file
128
lib/spack/spack/compilers/cce.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# Copyright 2013-2024 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
|
||||
|
||||
from spack.compiler import Compiler, UnsupportedCompilerFlag
|
||||
from spack.version import Version
|
||||
|
||||
|
||||
class Cce(Compiler):
|
||||
"""Cray compiler environment compiler."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# For old cray compilers on module based systems we replace
|
||||
# ``version_argument`` with the old value. Cannot be a property
|
||||
# as the new value is used in classmethods for path-based detection
|
||||
if not self.is_clang_based:
|
||||
self.version_argument = "-V"
|
||||
|
||||
# MacPorts builds gcc versions with prefixes and -mp-X.Y suffixes.
|
||||
suffixes = [r"-mp-\d\.\d"]
|
||||
|
||||
@property
|
||||
def link_paths(self):
|
||||
if any("PrgEnv-cray" in m for m in self.modules):
|
||||
# Old module-based interface to cray compilers
|
||||
return {
|
||||
"cc": os.path.join("cce", "cc"),
|
||||
"cxx": os.path.join("case-insensitive", "CC"),
|
||||
"f77": os.path.join("cce", "ftn"),
|
||||
"fc": os.path.join("cce", "ftn"),
|
||||
}
|
||||
|
||||
return {
|
||||
"cc": os.path.join("cce", "craycc"),
|
||||
"cxx": os.path.join("cce", "case-insensitive", "crayCC"),
|
||||
"f77": os.path.join("cce", "crayftn"),
|
||||
"fc": os.path.join("cce", "crayftn"),
|
||||
}
|
||||
|
||||
@property
|
||||
def is_clang_based(self):
|
||||
version = self._real_version or self.version
|
||||
return version >= Version("9.0") and "classic" not in str(version)
|
||||
|
||||
version_argument = "--version"
|
||||
version_regex = r"[Cc]ray (?:clang|C :|C\+\+ :|Fortran :) [Vv]ersion.*?(\d+(\.\d+)+)"
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-g", "-G0", "-G1", "-G2", "-Gfast"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-fopenmp"
|
||||
return "-h omp"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-std=c++11"
|
||||
return "-h std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-std=c++14"
|
||||
return "-h std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-std=c99"
|
||||
elif self.real_version >= Version("8.4"):
|
||||
return "-h std=c99,noconform,gnu"
|
||||
elif self.real_version >= Version("8.1"):
|
||||
return "-h c99,noconform,gnu"
|
||||
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 8.1")
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-std=c11"
|
||||
elif self.real_version >= Version("8.5"):
|
||||
return "-h std=c11,noconform,gnu"
|
||||
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 8.5")
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-fPIC"
|
||||
return "-h PIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-fPIC"
|
||||
return "-h PIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-fPIC"
|
||||
return "-h PIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
if self.is_clang_based:
|
||||
return "-fPIC"
|
||||
return "-h PIC"
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
# Cray compiler wrappers link to the standard C++ library
|
||||
# without additional flags.
|
||||
return ()
|
||||
192
lib/spack/spack/compilers/clang.py
Normal file
192
lib/spack/spack/compilers/clang.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# Copyright 2013-2024 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
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
from spack.compiler import Compiler, UnsupportedCompilerFlag
|
||||
from spack.version import Version
|
||||
|
||||
#: compiler symlink mappings for mixed f77 compilers
|
||||
f77_mapping = [
|
||||
("gfortran", os.path.join("clang", "gfortran")),
|
||||
("xlf_r", os.path.join("xl_r", "xlf_r")),
|
||||
("xlf", os.path.join("xl", "xlf")),
|
||||
("ifort", os.path.join("intel", "ifort")),
|
||||
]
|
||||
|
||||
#: compiler symlink mappings for mixed f90/fc compilers
|
||||
fc_mapping = [
|
||||
("gfortran", os.path.join("clang", "gfortran")),
|
||||
("xlf90_r", os.path.join("xl_r", "xlf90_r")),
|
||||
("xlf90", os.path.join("xl", "xlf90")),
|
||||
("ifort", os.path.join("intel", "ifort")),
|
||||
]
|
||||
|
||||
|
||||
class Clang(Compiler):
|
||||
version_argument = "--version"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return [
|
||||
"-gcodeview",
|
||||
"-gdwarf-2",
|
||||
"-gdwarf-3",
|
||||
"-gdwarf-4",
|
||||
"-gdwarf-5",
|
||||
"-gline-tables-only",
|
||||
"-gmodules",
|
||||
"-g",
|
||||
]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os", "-Oz", "-Og", "-O", "-O4"]
|
||||
|
||||
# Clang has support for using different fortran compilers with the
|
||||
# clang executable.
|
||||
@property
|
||||
def link_paths(self):
|
||||
# clang links are always the same
|
||||
link_paths = {
|
||||
"cc": os.path.join("clang", "clang"),
|
||||
"cxx": os.path.join("clang", "clang++"),
|
||||
}
|
||||
|
||||
# fortran links need to look at the actual compiler names from
|
||||
# compilers.yaml to figure out which named symlink to use
|
||||
for compiler_name, link_path in f77_mapping:
|
||||
if self.f77 and compiler_name in self.f77:
|
||||
link_paths["f77"] = link_path
|
||||
break
|
||||
else:
|
||||
link_paths["f77"] = os.path.join("clang", "flang")
|
||||
|
||||
for compiler_name, link_path in fc_mapping:
|
||||
if self.fc and compiler_name in self.fc:
|
||||
link_paths["fc"] = link_path
|
||||
break
|
||||
else:
|
||||
link_paths["fc"] = os.path.join("clang", "flang")
|
||||
|
||||
return link_paths
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
openmp_flag = "-fopenmp"
|
||||
|
||||
# C++ flags based on CMake Modules/Compiler/Clang.cmake
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
if self.real_version < Version("3.3"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 3.3")
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
if self.real_version < Version("3.4"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 3.5")
|
||||
elif self.real_version < Version("3.5"):
|
||||
return "-std=c++1y"
|
||||
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
if self.real_version < Version("3.5"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++17 standard", "cxx17_flag", "< 3.5")
|
||||
elif self.real_version < Version("5.0"):
|
||||
return "-std=c++1z"
|
||||
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def cxx20_flag(self):
|
||||
if self.real_version < Version("5.0"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++20 standard", "cxx20_flag", "< 5.0")
|
||||
elif self.real_version < Version("11.0"):
|
||||
return "-std=c++2a"
|
||||
else:
|
||||
return "-std=c++20"
|
||||
|
||||
@property
|
||||
def cxx23_flag(self):
|
||||
if self.real_version < Version("12.0"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++23 standard", "cxx23_flag", "< 12.0")
|
||||
elif self.real_version < Version("17.0"):
|
||||
return "-std=c++2b"
|
||||
else:
|
||||
return "-std=c++23"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
if self.real_version < Version("3.0"):
|
||||
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 3.0")
|
||||
if self.real_version < Version("3.1"):
|
||||
return "-std=c1x"
|
||||
return "-std=c11"
|
||||
|
||||
@property
|
||||
def c17_flag(self):
|
||||
if self.real_version < Version("6.0"):
|
||||
raise UnsupportedCompilerFlag(self, "the C17 standard", "c17_flag", "< 6.0")
|
||||
return "-std=c17"
|
||||
|
||||
@property
|
||||
def c23_flag(self):
|
||||
if self.real_version < Version("9.0"):
|
||||
raise UnsupportedCompilerFlag(self, "the C23 standard", "c23_flag", "< 9.0")
|
||||
elif self.real_version < Version("18.0"):
|
||||
return "-std=c2x"
|
||||
else:
|
||||
return "-std=c23"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
required_libs = ["libclang"]
|
||||
|
||||
@classmethod
|
||||
@llnl.util.lang.memoized
|
||||
def extract_version_from_output(cls, output):
|
||||
ver = "unknown"
|
||||
if ("Apple" in output) or ("AMD" in output):
|
||||
return ver
|
||||
|
||||
match = re.search(
|
||||
# Normal clang compiler versions are left as-is
|
||||
r"(?:clang|flang-new) version ([^ )\n]+)-svn[~.\w\d-]*|"
|
||||
# Don't include hyphenated patch numbers in the version
|
||||
# (see https://github.com/spack/spack/pull/14365 for details)
|
||||
r"(?:clang|flang-new) version ([^ )\n]+?)-[~.\w\d-]*|"
|
||||
r"(?:clang|flang-new) version ([^ )\n]+)",
|
||||
output,
|
||||
)
|
||||
if match:
|
||||
ver = match.group(match.lastindex)
|
||||
return ver
|
||||
@@ -1,427 +0,0 @@
|
||||
# Copyright 2013-2024 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)
|
||||
"""This module contains functions related to finding compilers on the system,
|
||||
and configuring Spack to use multiple compilers.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.config
|
||||
import spack.detection
|
||||
import spack.error
|
||||
import spack.platforms
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
from spack.operating_systems import windows_os
|
||||
from spack.util.environment import get_path
|
||||
|
||||
package_name_to_compiler_name = {
|
||||
"llvm": "clang",
|
||||
"intel-oneapi-compilers": "oneapi",
|
||||
"llvm-amdgpu": "rocmcc",
|
||||
"intel-oneapi-compilers-classic": "intel",
|
||||
"acfl": "arm",
|
||||
}
|
||||
|
||||
|
||||
#: Tag used to identify packages providing a compiler
|
||||
COMPILER_TAG = "compiler"
|
||||
|
||||
|
||||
def compiler_config_files():
|
||||
config_files = []
|
||||
configuration = spack.config.CONFIG
|
||||
for scope in configuration.writable_scopes:
|
||||
name = scope.name
|
||||
|
||||
from_packages_yaml = CompilerFactory.from_packages_yaml(configuration, scope=name)
|
||||
if from_packages_yaml:
|
||||
config_files.append(configuration.get_config_filename(name, "packages"))
|
||||
|
||||
compiler_config = configuration.get("compilers", scope=name)
|
||||
if compiler_config:
|
||||
config_files.append(configuration.get_config_filename(name, "compilers"))
|
||||
|
||||
return config_files
|
||||
|
||||
|
||||
def add_compiler_to_config(new_compilers, *, scope=None) -> None:
|
||||
"""Add a Compiler object to the configuration, at the required scope."""
|
||||
# FIXME (compiler as nodes): still needed to read Cray manifest
|
||||
by_name: Dict[str, List["spack.spec.Spec"]] = {}
|
||||
for x in new_compilers:
|
||||
by_name.setdefault(x.name, []).append(x)
|
||||
|
||||
spack.detection.update_configuration(by_name, buildable=True, scope=scope)
|
||||
|
||||
|
||||
def find_compilers(
|
||||
path_hints: Optional[List[str]] = None,
|
||||
*,
|
||||
scope: Optional[str] = None,
|
||||
max_workers: Optional[int] = None,
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Searches for compiler in the paths given as argument. If any new compiler is found, the
|
||||
configuration is updated, and the list of new compiler objects is returned.
|
||||
|
||||
Args:
|
||||
path_hints: list of path hints where to look for. A sensible default based on the ``PATH``
|
||||
environment variable will be used if the value is None
|
||||
scope: configuration scope to modify
|
||||
max_workers: number of processes used to search for compilers
|
||||
"""
|
||||
if path_hints is None:
|
||||
path_hints = get_path("PATH")
|
||||
default_paths = fs.search_paths_for_executables(*path_hints)
|
||||
if sys.platform == "win32":
|
||||
default_paths.extend(windows_os.WindowsOs().compiler_search_paths)
|
||||
compiler_pkgs = spack.repo.PATH.packages_with_tags(COMPILER_TAG, full=True)
|
||||
|
||||
detected_packages = spack.detection.by_path(
|
||||
compiler_pkgs, path_hints=default_paths, max_workers=max_workers
|
||||
)
|
||||
|
||||
new_compilers = spack.detection.update_configuration(
|
||||
detected_packages, buildable=True, scope=scope
|
||||
)
|
||||
return new_compilers
|
||||
|
||||
|
||||
def select_new_compilers(
|
||||
candidates: List["spack.spec.Spec"], *, scope: Optional[str] = None
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Given a list of compilers, remove those that are already defined in
|
||||
the configuration.
|
||||
"""
|
||||
compilers_in_config = all_compilers_from(configuration=spack.config.CONFIG, scope=scope)
|
||||
return [c for c in candidates if c not in compilers_in_config]
|
||||
|
||||
|
||||
def supported_compilers() -> List[str]:
|
||||
"""Returns all the currently supported compiler packages"""
|
||||
return sorted(spack.repo.PATH.packages_with_tags(COMPILER_TAG))
|
||||
|
||||
|
||||
def all_compilers(
|
||||
scope: Optional[str] = None, init_config: bool = True
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Returns all the compilers from the current global configuration.
|
||||
|
||||
Args:
|
||||
scope: configuration scope from which to extract the compilers. If None, the merged
|
||||
configuration is used.
|
||||
init_config: if True, search for compilers if none is found in configuration.
|
||||
"""
|
||||
compilers = all_compilers_from(configuration=spack.config.CONFIG, scope=scope)
|
||||
|
||||
if not compilers and init_config:
|
||||
find_compilers(scope=scope)
|
||||
compilers = all_compilers_from(configuration=spack.config.CONFIG, scope=scope)
|
||||
|
||||
return compilers
|
||||
|
||||
|
||||
def all_compilers_from(
|
||||
configuration: "spack.config.ConfigurationType", scope: Optional[str] = None
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Returns all the compilers from the current global configuration.
|
||||
|
||||
Args:
|
||||
configuration: configuration to be queried
|
||||
scope: configuration scope from which to extract the compilers. If None, the merged
|
||||
configuration is used.
|
||||
"""
|
||||
compilers = CompilerFactory.from_packages_yaml(configuration, scope=scope)
|
||||
|
||||
if os.environ.get("SPACK_EXPERIMENTAL_DEPRECATE_COMPILERS_YAML") != "1":
|
||||
legacy_compilers = CompilerFactory.from_compilers_yaml(configuration, scope=scope)
|
||||
if legacy_compilers:
|
||||
# FIXME (compiler as nodes): write how to update the file. Maybe an ad-hoc command
|
||||
warnings.warn(
|
||||
"Some compilers are still defined in 'compilers.yaml', which has been deprecated "
|
||||
"in v0.23. Those configuration files will be ignored from Spack v0.25.\n"
|
||||
)
|
||||
for legacy in legacy_compilers:
|
||||
if not any(c.satisfies(f"{legacy.name}@{legacy.versions}") for c in compilers):
|
||||
compilers.append(legacy)
|
||||
|
||||
return compilers
|
||||
|
||||
|
||||
class CompilerRemover:
|
||||
"""Removes compiler from configuration."""
|
||||
|
||||
def __init__(self, configuration: "spack.config.ConfigurationType") -> None:
|
||||
self.configuration = configuration
|
||||
self.marked_packages_yaml: List[Tuple[str, Any]] = []
|
||||
self.marked_compilers_yaml: List[Tuple[str, Any]] = []
|
||||
|
||||
def mark_compilers(
|
||||
self, *, match: str, scope: Optional[str] = None
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Marks compilers to be removed in configuration, and returns a corresponding list
|
||||
of specs.
|
||||
|
||||
Args:
|
||||
match: constraint that the compiler must match to be removed.
|
||||
scope: scope where to remove the compiler. If None, all writeable scopes are checked.
|
||||
"""
|
||||
self.marked_packages_yaml = []
|
||||
self.marked_compilers_yaml = []
|
||||
candidate_scopes = [scope]
|
||||
if scope is None:
|
||||
candidate_scopes = [x.name for x in self.configuration.writable_scopes]
|
||||
|
||||
all_removals = self._mark_in_packages_yaml(match, candidate_scopes)
|
||||
all_removals.extend(self._mark_in_compilers_yaml(match, candidate_scopes))
|
||||
|
||||
return all_removals
|
||||
|
||||
def _mark_in_packages_yaml(self, match, candidate_scopes):
|
||||
compiler_package_names = supported_compilers()
|
||||
all_removals = []
|
||||
for current_scope in candidate_scopes:
|
||||
packages_yaml = self.configuration.get("packages", scope=current_scope)
|
||||
if not packages_yaml:
|
||||
continue
|
||||
|
||||
removed_from_scope = []
|
||||
for name, entry in packages_yaml.items():
|
||||
if name not in compiler_package_names:
|
||||
continue
|
||||
|
||||
externals_config = entry.get("externals", None)
|
||||
if not externals_config:
|
||||
continue
|
||||
|
||||
def _partition_match(external_yaml):
|
||||
s = CompilerFactory.from_external_yaml(external_yaml)
|
||||
return not s.satisfies(match)
|
||||
|
||||
to_keep, to_remove = llnl.util.lang.stable_partition(
|
||||
externals_config, _partition_match
|
||||
)
|
||||
if not to_remove:
|
||||
continue
|
||||
|
||||
removed_from_scope.extend(to_remove)
|
||||
entry["externals"] = to_keep
|
||||
|
||||
if not removed_from_scope:
|
||||
continue
|
||||
|
||||
self.marked_packages_yaml.append((current_scope, packages_yaml))
|
||||
all_removals.extend(
|
||||
[CompilerFactory.from_external_yaml(x) for x in removed_from_scope]
|
||||
)
|
||||
return all_removals
|
||||
|
||||
def _mark_in_compilers_yaml(self, match, candidate_scopes):
|
||||
if os.environ.get("SPACK_EXPERIMENTAL_DEPRECATE_COMPILERS_YAML") == "1":
|
||||
return []
|
||||
|
||||
all_removals = []
|
||||
for current_scope in candidate_scopes:
|
||||
compilers_yaml = self.configuration.get("compilers", scope=current_scope)
|
||||
if not compilers_yaml:
|
||||
continue
|
||||
|
||||
def _partition_match(entry):
|
||||
external_specs = CompilerFactory.from_legacy_yaml(entry["compiler"])
|
||||
return not any(x.satisfies(match) for x in external_specs)
|
||||
|
||||
to_keep, to_remove = llnl.util.lang.stable_partition(compilers_yaml, _partition_match)
|
||||
if not to_remove:
|
||||
continue
|
||||
|
||||
compilers_yaml[:] = to_keep
|
||||
self.marked_compilers_yaml.append((current_scope, compilers_yaml))
|
||||
for entry in to_remove:
|
||||
all_removals.extend(CompilerFactory.from_legacy_yaml(entry["compiler"]))
|
||||
|
||||
return all_removals
|
||||
|
||||
def flush(self):
|
||||
"""Removes from configuration the specs that have been marked by the previous call
|
||||
of ``remove_compilers``.
|
||||
"""
|
||||
for scope, packages_yaml in self.marked_packages_yaml:
|
||||
self.configuration.set("packages", packages_yaml, scope=scope)
|
||||
|
||||
for scope, compilers_yaml in self.marked_compilers_yaml:
|
||||
self.configuration.set("compilers", compilers_yaml, scope=scope)
|
||||
|
||||
|
||||
def compilers_for_spec(compiler_spec, *, arch_spec=None, scope=None, init_config=True):
|
||||
"""This gets all compilers that satisfy the supplied CompilerSpec.
|
||||
Returns an empty list if none are found.
|
||||
"""
|
||||
# FIXME (compiler as nodes): to be removed, or reimplemented
|
||||
raise NotImplementedError("still to be implemented")
|
||||
|
||||
|
||||
def compilers_for_arch(
|
||||
arch_spec: "spack.spec.ArchSpec", *, scope: Optional[str] = None
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Returns the compilers that can be used on the input architecture"""
|
||||
compilers = all_compilers_from(spack.config.CONFIG, scope=scope)
|
||||
query = f"platform={arch_spec.platform} target=:{arch_spec.target}"
|
||||
return [x for x in compilers if x.satisfies(query)]
|
||||
|
||||
|
||||
def class_for_compiler_name(compiler_name):
|
||||
"""Given a compiler module name, get the corresponding Compiler class."""
|
||||
# FIXME (compiler as nodes): to be removed, or reimplemented
|
||||
raise NotImplementedError("still to be implemented")
|
||||
|
||||
|
||||
_EXTRA_ATTRIBUTES_KEY = "extra_attributes"
|
||||
_COMPILERS_KEY = "compilers"
|
||||
_C_KEY = "c"
|
||||
_CXX_KEY, _FORTRAN_KEY = "cxx", "fortran"
|
||||
|
||||
|
||||
def name_os_target(spec: "spack.spec.Spec") -> Tuple[str, str, str]:
|
||||
if not spec.architecture:
|
||||
host_platform = spack.platforms.host()
|
||||
operating_system = host_platform.operating_system("default_os")
|
||||
target = host_platform.target("default_target")
|
||||
else:
|
||||
target = spec.architecture.target
|
||||
if not target:
|
||||
target = spack.platforms.host().target("default_target")
|
||||
target = target
|
||||
|
||||
operating_system = spec.os
|
||||
if not operating_system:
|
||||
host_platform = spack.platforms.host()
|
||||
operating_system = host_platform.operating_system("default_os")
|
||||
|
||||
return spec.name, str(operating_system), str(target)
|
||||
|
||||
|
||||
class CompilerFactory:
|
||||
"""Class aggregating all ways of constructing a list of compiler specs from config entries."""
|
||||
|
||||
_PACKAGES_YAML_CACHE: Dict[str, Optional["spack.spec.Spec"]] = {}
|
||||
_COMPILERS_YAML_CACHE: Dict[str, List["spack.spec.Spec"]] = {}
|
||||
_GENERIC_TARGET = None
|
||||
|
||||
@staticmethod
|
||||
def from_packages_yaml(
|
||||
configuration: "spack.config.ConfigurationType", *, scope: Optional[str] = None
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Returns the compiler specs defined in the "packages" section of the configuration"""
|
||||
compilers = []
|
||||
compiler_package_names = supported_compilers()
|
||||
packages_yaml = configuration.get("packages", scope=scope)
|
||||
for name, entry in packages_yaml.items():
|
||||
if name not in compiler_package_names:
|
||||
continue
|
||||
|
||||
externals_config = entry.get("externals", None)
|
||||
if not externals_config:
|
||||
continue
|
||||
|
||||
compiler_specs = []
|
||||
for current_external in externals_config:
|
||||
key = str(current_external)
|
||||
if key not in CompilerFactory._PACKAGES_YAML_CACHE:
|
||||
CompilerFactory._PACKAGES_YAML_CACHE[key] = CompilerFactory.from_external_yaml(
|
||||
current_external
|
||||
)
|
||||
|
||||
compiler = CompilerFactory._PACKAGES_YAML_CACHE[key]
|
||||
if compiler:
|
||||
compiler_specs.append(compiler)
|
||||
|
||||
compilers.extend(compiler_specs)
|
||||
return compilers
|
||||
|
||||
@staticmethod
|
||||
def from_external_yaml(config: Dict[str, Any]) -> Optional["spack.spec.Spec"]:
|
||||
"""Returns a compiler spec from an external definition from packages.yaml."""
|
||||
# Allow `@x.y.z` instead of `@=x.y.z`
|
||||
err_header = f"The external spec '{config['spec']}' cannot be used as a compiler"
|
||||
# If extra_attributes is not there I might not want to use this entry as a compiler,
|
||||
# therefore just leave a debug message, but don't be loud with a warning.
|
||||
if _EXTRA_ATTRIBUTES_KEY not in config:
|
||||
tty.debug(f"[{__file__}] {err_header}: missing the '{_EXTRA_ATTRIBUTES_KEY}' key")
|
||||
return None
|
||||
extra_attributes = config[_EXTRA_ATTRIBUTES_KEY]
|
||||
result = spack.spec.Spec(
|
||||
str(spack.spec.parse_with_version_concrete(config["spec"])),
|
||||
external_path=config.get("prefix"),
|
||||
external_modules=config.get("modules"),
|
||||
)
|
||||
result.extra_attributes = extra_attributes
|
||||
CompilerFactory._finalize_external_concretization(result)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _finalize_external_concretization(abstract_spec):
|
||||
if CompilerFactory._GENERIC_TARGET is None:
|
||||
CompilerFactory._GENERIC_TARGET = archspec.cpu.host().family
|
||||
|
||||
if abstract_spec.architecture:
|
||||
abstract_spec.architecture.complete_with_defaults()
|
||||
else:
|
||||
abstract_spec.constrain(spack.spec.Spec.default_arch())
|
||||
abstract_spec.architecture.target = CompilerFactory._GENERIC_TARGET
|
||||
abstract_spec._finalize_concretization()
|
||||
|
||||
@staticmethod
|
||||
def from_legacy_yaml(compiler_dict: Dict[str, Any]) -> List["spack.spec.Spec"]:
|
||||
"""Returns a list of external specs, corresponding to a compiler entry
|
||||
from compilers.yaml.
|
||||
"""
|
||||
from spack.detection.path import ExecutablesFinder
|
||||
|
||||
# FIXME (compiler as nodes): should we look at targets too?
|
||||
result = []
|
||||
candidate_paths = [x for x in compiler_dict["paths"].values() if x is not None]
|
||||
finder = ExecutablesFinder()
|
||||
|
||||
for pkg_name in spack.repo.PATH.packages_with_tags("compiler"):
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||
pattern = re.compile(r"|".join(finder.search_patterns(pkg=pkg_cls)))
|
||||
filtered_paths = [x for x in candidate_paths if pattern.search(os.path.basename(x))]
|
||||
detected = finder.detect_specs(pkg=pkg_cls, paths=filtered_paths)
|
||||
result.extend(detected)
|
||||
|
||||
for item in result:
|
||||
CompilerFactory._finalize_external_concretization(item)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def from_compilers_yaml(
|
||||
configuration: "spack.config.ConfigurationType", *, scope: Optional[str] = None
|
||||
) -> List["spack.spec.Spec"]:
|
||||
"""Returns the compiler specs defined in the "compilers" section of the configuration"""
|
||||
result: List["spack.spec.Spec"] = []
|
||||
for item in configuration.get("compilers", scope=scope):
|
||||
key = str(item)
|
||||
if key not in CompilerFactory._COMPILERS_YAML_CACHE:
|
||||
CompilerFactory._COMPILERS_YAML_CACHE[key] = CompilerFactory.from_legacy_yaml(
|
||||
item["compiler"]
|
||||
)
|
||||
|
||||
result.extend(CompilerFactory._COMPILERS_YAML_CACHE[key])
|
||||
return result
|
||||
|
||||
|
||||
class UnknownCompilerError(spack.error.SpackError):
|
||||
def __init__(self, compiler_name):
|
||||
super().__init__(f"Spack doesn't support the requested compiler: {compiler_name}")
|
||||
@@ -1,23 +0,0 @@
|
||||
# Copyright 2013-2024 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)
|
||||
from ..error import SpackError
|
||||
|
||||
|
||||
class CompilerAccessError(SpackError):
|
||||
def __init__(self, compiler, paths):
|
||||
super().__init__(
|
||||
f"Compiler '{compiler.spec}' has executables that are missing"
|
||||
f" or are not executable: {paths}"
|
||||
)
|
||||
|
||||
|
||||
class UnsupportedCompilerFlag(SpackError):
|
||||
def __init__(self, compiler, feature, flag_name, ver_string=None):
|
||||
super().__init__(
|
||||
f"{compiler.name} ({ver_string if ver_string else compiler.version}) does not support"
|
||||
f" {feature} (as compiler.{flag_name}). If you think it should, please edit the "
|
||||
f"compiler.{compiler.name} subclass to implement the {flag_name} property and submit "
|
||||
f"a pull request or issue."
|
||||
)
|
||||
79
lib/spack/spack/compilers/fj.py
Normal file
79
lib/spack/spack/compilers/fj.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Copyright 2013-2024 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 spack.compiler
|
||||
|
||||
|
||||
class Fj(spack.compiler.Compiler):
|
||||
# Named wrapper links within build_env_path
|
||||
link_paths = {
|
||||
"cc": os.path.join("fj", "fcc"),
|
||||
"cxx": os.path.join("fj", "case-insensitive", "FCC"),
|
||||
"f77": os.path.join("fj", "frt"),
|
||||
"fc": os.path.join("fj", "frt"),
|
||||
}
|
||||
|
||||
version_argument = "--version"
|
||||
version_regex = r"\((?:FCC|FRT)\) ([a-z\d.]+)"
|
||||
|
||||
required_libs = ["libfj90i", "libfj90f", "libfjsrcinfo"]
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return "-g"
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O0", "-O1", "-O2", "-O3", "-Ofast"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-Kopenmp"
|
||||
|
||||
@property
|
||||
def cxx98_flag(self):
|
||||
return "-std=c++98"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
return "-std=c11"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-KPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-KPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-KPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-KPIC"
|
||||
@@ -1,26 +0,0 @@
|
||||
# Copyright 2013-2024 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)
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
def tokenize_flags(flags_values: str, propagate: bool = False) -> List[Tuple[str, bool]]:
|
||||
"""Given a compiler flag specification as a string, this returns a list
|
||||
where the entries are the flags. For compiler options which set values
|
||||
using the syntax "-flag value", this function groups flags and their
|
||||
values together. Any token not preceded by a "-" is considered the
|
||||
value of a prior flag."""
|
||||
tokens = flags_values.split()
|
||||
if not tokens:
|
||||
return []
|
||||
flag = tokens[0]
|
||||
flags_with_propagation = []
|
||||
for token in tokens[1:]:
|
||||
if not token.startswith("-"):
|
||||
flag += " " + token
|
||||
else:
|
||||
flags_with_propagation.append((flag, propagate))
|
||||
flag = token
|
||||
flags_with_propagation.append((flag, propagate))
|
||||
return flags_with_propagation
|
||||
191
lib/spack/spack/compilers/gcc.py
Normal file
191
lib/spack/spack/compilers/gcc.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# Copyright 2013-2024 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
|
||||
|
||||
from llnl.util.filesystem import ancestor
|
||||
|
||||
import spack.compiler
|
||||
import spack.compilers.apple_clang as apple_clang
|
||||
import spack.util.executable
|
||||
from spack.version import Version
|
||||
|
||||
|
||||
class Gcc(spack.compiler.Compiler):
|
||||
# MacPorts builds gcc versions with prefixes and -mp-X or -mp-X.Y suffixes.
|
||||
# Homebrew and Linuxbrew may build gcc with -X, -X.Y suffixes.
|
||||
# Old compatibility versions may contain XY suffixes.
|
||||
suffixes = [r"-mp-\d+(?:\.\d+)?", r"-\d+(?:\.\d+)?", r"\d\d"]
|
||||
|
||||
# Named wrapper links within build_env_path
|
||||
link_paths = {
|
||||
"cc": os.path.join("gcc", "gcc"),
|
||||
"cxx": os.path.join("gcc", "g++"),
|
||||
"f77": os.path.join("gcc", "gfortran"),
|
||||
"fc": os.path.join("gcc", "gfortran"),
|
||||
}
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-g", "-gstabs+", "-gstabs", "-gxcoff+", "-gxcoff", "-gvms"]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Os", "-Ofast", "-Og"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-fopenmp"
|
||||
|
||||
@property
|
||||
def cxx98_flag(self):
|
||||
if self.real_version < Version("6.0"):
|
||||
return ""
|
||||
else:
|
||||
return "-std=c++98"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
if self.real_version < Version("4.3"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++11 standard", "cxx11_flag", " < 4.3"
|
||||
)
|
||||
elif self.real_version < Version("4.7"):
|
||||
return "-std=c++0x"
|
||||
else:
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
if self.real_version < Version("4.8"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++14 standard", "cxx14_flag", "< 4.8"
|
||||
)
|
||||
elif self.real_version < Version("4.9"):
|
||||
return "-std=c++1y"
|
||||
else:
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
if self.real_version < Version("5.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++17 standard", "cxx17_flag", "< 5.0"
|
||||
)
|
||||
elif self.real_version < Version("6.0"):
|
||||
return "-std=c++1z"
|
||||
else:
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def cxx20_flag(self):
|
||||
if self.real_version < Version("8.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++20 standard", "cxx20_flag", "< 8.0"
|
||||
)
|
||||
elif self.real_version < Version("11.0"):
|
||||
return "-std=c++2a"
|
||||
else:
|
||||
return "-std=c++20"
|
||||
|
||||
@property
|
||||
def cxx23_flag(self):
|
||||
if self.real_version < Version("11.0"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C++23 standard", "cxx23_flag", "< 11.0"
|
||||
)
|
||||
elif self.real_version < Version("14.0"):
|
||||
return "-std=c++2b"
|
||||
else:
|
||||
return "-std=c++23"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
if self.real_version < Version("4.5"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C99 standard", "c99_flag", "< 4.5"
|
||||
)
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
if self.real_version < Version("4.7"):
|
||||
raise spack.compiler.UnsupportedCompilerFlag(
|
||||
self, "the C11 standard", "c11_flag", "< 4.7"
|
||||
)
|
||||
return "-std=c11"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
required_libs = ["libgcc", "libgfortran"]
|
||||
|
||||
@classmethod
|
||||
def default_version(cls, cc):
|
||||
"""Older versions of gcc use the ``-dumpversion`` option.
|
||||
Output looks like this::
|
||||
|
||||
4.4.7
|
||||
|
||||
In GCC 7, this option was changed to only return the major
|
||||
version of the compiler::
|
||||
|
||||
7
|
||||
|
||||
A new ``-dumpfullversion`` option was added that gives us
|
||||
what we want::
|
||||
|
||||
7.2.0
|
||||
"""
|
||||
# Apple's gcc is actually apple clang, so skip it. Returning
|
||||
# "unknown" ensures this compiler is not detected by default.
|
||||
# Users can add it manually to compilers.yaml at their own risk.
|
||||
if apple_clang.AppleClang.default_version(cc) != "unknown":
|
||||
return "unknown"
|
||||
|
||||
version = super(Gcc, cls).default_version(cc)
|
||||
if Version(version) >= Version("7"):
|
||||
output = spack.compiler.get_compiler_version_output(cc, "-dumpfullversion")
|
||||
version = cls.extract_version_from_output(output)
|
||||
return version
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
return ("-lstdc++",)
|
||||
|
||||
@property
|
||||
def prefix(self):
|
||||
# GCC reports its install prefix when running ``-print-search-dirs``
|
||||
# on the first line ``install: <prefix>``.
|
||||
cc = spack.util.executable.Executable(self.cc)
|
||||
with self.compiler_environment():
|
||||
gcc_output = cc("-print-search-dirs", output=str, error=str)
|
||||
|
||||
for line in gcc_output.splitlines():
|
||||
if line.startswith("install:"):
|
||||
gcc_prefix = line.split(":")[1].strip()
|
||||
# Go from <prefix>/lib/gcc/<triplet>/<version>/ to <prefix>
|
||||
return ancestor(gcc_prefix, 4)
|
||||
|
||||
raise RuntimeError(
|
||||
"could not find install prefix of GCC from output:\n\t{}".format(gcc_output)
|
||||
)
|
||||
131
lib/spack/spack/compilers/intel.py
Normal file
131
lib/spack/spack/compilers/intel.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# Copyright 2013-2024 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 sys
|
||||
|
||||
from spack.compiler import Compiler, UnsupportedCompilerFlag
|
||||
from spack.version import Version
|
||||
|
||||
|
||||
class Intel(Compiler):
|
||||
# Named wrapper links within build_env_path
|
||||
link_paths = {
|
||||
"cc": os.path.join("intel", "icc"),
|
||||
"cxx": os.path.join("intel", "icpc"),
|
||||
"f77": os.path.join("intel", "ifort"),
|
||||
"fc": os.path.join("intel", "ifort"),
|
||||
}
|
||||
|
||||
if sys.platform == "win32":
|
||||
version_argument = "/QV"
|
||||
else:
|
||||
version_argument = "--version"
|
||||
|
||||
if sys.platform == "win32":
|
||||
version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)"
|
||||
else:
|
||||
version_regex = r"\((?:IFORT|ICC)\) ([^ ]+)"
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
required_libs = ["libirc", "libifcore", "libifcoremt", "libirng"]
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
if self.real_version < Version("16.0"):
|
||||
return "-openmp"
|
||||
else:
|
||||
return "-qopenmp"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
if self.real_version < Version("11.1"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 11.1")
|
||||
|
||||
elif self.real_version < Version("13"):
|
||||
return "-std=c++0x"
|
||||
else:
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
# Adapted from CMake's Intel-CXX rules.
|
||||
if self.real_version < Version("15"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 15")
|
||||
elif self.real_version < Version("15.0.2"):
|
||||
return "-std=c++1y"
|
||||
else:
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
# https://www.intel.com/content/www/us/en/developer/articles/news/c17-features-supported-by-c-compiler.html
|
||||
if self.real_version < Version("19"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++17 standard", "cxx17_flag", "< 19")
|
||||
else:
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
if self.real_version < Version("12"):
|
||||
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 12")
|
||||
else:
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
if self.real_version < Version("16"):
|
||||
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 16")
|
||||
else:
|
||||
return "-std=c1x"
|
||||
|
||||
@property
|
||||
def c18_flag(self):
|
||||
# c18 supported since oneapi 2022, which is classic version 2021.5.0
|
||||
if self.real_version < Version("21.5.0"):
|
||||
raise UnsupportedCompilerFlag(self, "the C18 standard", "c18_flag", "< 21.5.0")
|
||||
else:
|
||||
return "-std=c18"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
return ("-cxxlib",)
|
||||
|
||||
def setup_custom_environment(self, pkg, env):
|
||||
# Edge cases for Intel's oneAPI compilers when using the legacy classic compilers:
|
||||
# Always pass flags to disable deprecation warnings, since these warnings can
|
||||
# confuse tools that parse the output of compiler commands (e.g. version checks).
|
||||
if self.real_version >= Version("2021") and self.real_version < Version("2024"):
|
||||
env.append_flags("SPACK_ALWAYS_CFLAGS", "-diag-disable=10441")
|
||||
env.append_flags("SPACK_ALWAYS_CXXFLAGS", "-diag-disable=10441")
|
||||
if self.real_version >= Version("2021") and self.real_version < Version("2025"):
|
||||
env.append_flags("SPACK_ALWAYS_FFLAGS", "-diag-disable=10448")
|
||||
@@ -1,426 +0,0 @@
|
||||
# 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 contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import typing
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
|
||||
import llnl.path
|
||||
import llnl.util.lang
|
||||
from llnl.util import tty
|
||||
from llnl.util.filesystem import path_contains_subdirectory, paths_containing_libs
|
||||
|
||||
import spack.caches
|
||||
import spack.util.libc
|
||||
from spack.util.environment import filter_system_paths
|
||||
from spack.util.file_cache import FileCache
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import spack.spec
|
||||
|
||||
|
||||
#: regex for parsing linker lines
|
||||
_LINKER_LINE = re.compile(r"^( *|.*[/\\])" r"(link|ld|([^/\\]+-)?ld|collect2)" r"[^/\\]*( |$)")
|
||||
|
||||
#: components of linker lines to ignore
|
||||
_LINKER_LINE_IGNORE = re.compile(r"(collect2 version|^[A-Za-z0-9_]+=|/ldfe )")
|
||||
|
||||
#: regex to match linker search paths
|
||||
_LINK_DIR_ARG = re.compile(r"^-L(.:)?(?P<dir>[/\\].*)")
|
||||
|
||||
#: regex to match linker library path arguments
|
||||
_LIBPATH_ARG = re.compile(r"^[-/](LIBPATH|libpath):(?P<dir>.*)")
|
||||
|
||||
|
||||
@llnl.path.system_path_filter
|
||||
def parse_non_system_link_dirs(compiler_debug_output: str) -> List[str]:
|
||||
"""Parses link paths out of compiler debug output.
|
||||
|
||||
Args:
|
||||
compiler_debug_output: compiler debug output as a string
|
||||
|
||||
Returns:
|
||||
Implicit link paths parsed from the compiler output
|
||||
"""
|
||||
link_dirs = _parse_link_paths(compiler_debug_output)
|
||||
|
||||
# Remove directories that do not exist. Some versions of the Cray compiler
|
||||
# report nonexistent directories
|
||||
link_dirs = filter_non_existing_dirs(link_dirs)
|
||||
|
||||
# Return set of directories containing needed compiler libs, minus
|
||||
# system paths. Note that 'filter_system_paths' only checks for an
|
||||
# exact match, while 'in_system_subdirectory' checks if a path contains
|
||||
# a system directory as a subdirectory
|
||||
link_dirs = filter_system_paths(link_dirs)
|
||||
return list(p for p in link_dirs if not in_system_subdirectory(p))
|
||||
|
||||
|
||||
def filter_non_existing_dirs(dirs):
|
||||
return [d for d in dirs if os.path.isdir(d)]
|
||||
|
||||
|
||||
def in_system_subdirectory(path):
|
||||
system_dirs = [
|
||||
"/lib/",
|
||||
"/lib64/",
|
||||
"/usr/lib/",
|
||||
"/usr/lib64/",
|
||||
"/usr/local/lib/",
|
||||
"/usr/local/lib64/",
|
||||
]
|
||||
return any(path_contains_subdirectory(path, x) for x in system_dirs)
|
||||
|
||||
|
||||
def _parse_link_paths(string):
|
||||
"""Parse implicit link paths from compiler debug output.
|
||||
|
||||
This gives the compiler runtime library paths that we need to add to
|
||||
the RPATH of generated binaries and libraries. It allows us to
|
||||
ensure, e.g., that codes load the right libstdc++ for their compiler.
|
||||
"""
|
||||
lib_search_paths = False
|
||||
raw_link_dirs = []
|
||||
for line in string.splitlines():
|
||||
if lib_search_paths:
|
||||
if line.startswith("\t"):
|
||||
raw_link_dirs.append(line[1:])
|
||||
continue
|
||||
else:
|
||||
lib_search_paths = False
|
||||
elif line.startswith("Library search paths:"):
|
||||
lib_search_paths = True
|
||||
|
||||
if not _LINKER_LINE.match(line):
|
||||
continue
|
||||
if _LINKER_LINE_IGNORE.match(line):
|
||||
continue
|
||||
tty.debug(f"implicit link dirs: link line: {line}")
|
||||
|
||||
next_arg = False
|
||||
for arg in line.split():
|
||||
if arg in ("-L", "-Y"):
|
||||
next_arg = True
|
||||
continue
|
||||
|
||||
if next_arg:
|
||||
raw_link_dirs.append(arg)
|
||||
next_arg = False
|
||||
continue
|
||||
|
||||
link_dir_arg = _LINK_DIR_ARG.match(arg)
|
||||
if link_dir_arg:
|
||||
link_dir = link_dir_arg.group("dir")
|
||||
raw_link_dirs.append(link_dir)
|
||||
|
||||
link_dir_arg = _LIBPATH_ARG.match(arg)
|
||||
if link_dir_arg:
|
||||
link_dir = link_dir_arg.group("dir")
|
||||
raw_link_dirs.append(link_dir)
|
||||
|
||||
implicit_link_dirs = list()
|
||||
visited = set()
|
||||
for link_dir in raw_link_dirs:
|
||||
normalized_path = os.path.abspath(link_dir)
|
||||
if normalized_path not in visited:
|
||||
implicit_link_dirs.append(normalized_path)
|
||||
visited.add(normalized_path)
|
||||
|
||||
tty.debug(f"implicit link dirs: result: {', '.join(implicit_link_dirs)}")
|
||||
return implicit_link_dirs
|
||||
|
||||
|
||||
class CompilerPropertyDetector:
|
||||
|
||||
def __init__(self, compiler_spec: "spack.spec.Spec"):
|
||||
assert compiler_spec.external, "only external compiler specs are allowed, so far"
|
||||
assert compiler_spec.concrete, "only concrete compiler specs are allowed, so far"
|
||||
self.spec = compiler_spec
|
||||
self.cache = COMPILER_CACHE
|
||||
|
||||
@contextlib.contextmanager
|
||||
def compiler_environment(self):
|
||||
"""Sets the environment to run this compiler"""
|
||||
import spack.schema.environment
|
||||
import spack.util.module_cmd
|
||||
|
||||
# Avoid modifying os.environ if possible.
|
||||
environment = self.spec.extra_attributes.get("environment", {})
|
||||
modules = self.spec.external_modules or []
|
||||
if not self.spec.external_modules and not environment:
|
||||
yield
|
||||
return
|
||||
|
||||
# store environment to replace later
|
||||
backup_env = os.environ.copy()
|
||||
|
||||
try:
|
||||
# load modules and set env variables
|
||||
for module in modules:
|
||||
spack.util.module_cmd.load_module(module)
|
||||
|
||||
# apply other compiler environment changes
|
||||
spack.schema.environment.parse(environment).apply_modifications()
|
||||
|
||||
yield
|
||||
finally:
|
||||
# Restore environment regardless of whether inner code succeeded
|
||||
os.environ.clear()
|
||||
os.environ.update(backup_env)
|
||||
|
||||
def _compile_dummy_c_source(self) -> Optional[str]:
|
||||
import spack.util.executable
|
||||
|
||||
assert self.spec.external, "only external compiler specs are allowed, so far"
|
||||
compiler_pkg = self.spec.package
|
||||
if getattr(compiler_pkg, "cc"):
|
||||
cc = compiler_pkg.cc
|
||||
ext = "c"
|
||||
else:
|
||||
cc = compiler_pkg.cxx
|
||||
ext = "cc"
|
||||
|
||||
if not cc or not self.spec.package.verbose_flags:
|
||||
return None
|
||||
|
||||
try:
|
||||
tmpdir = tempfile.mkdtemp(prefix="spack-implicit-link-info")
|
||||
fout = os.path.join(tmpdir, "output")
|
||||
fin = os.path.join(tmpdir, f"main.{ext}")
|
||||
|
||||
with open(fin, "w") as csource:
|
||||
csource.write(
|
||||
"int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }\n"
|
||||
)
|
||||
cc_exe = spack.util.executable.Executable(cc)
|
||||
|
||||
# FIXME (compiler as nodes): this operation should be encapsulated somewhere else
|
||||
compiler_flags = self.spec.extra_attributes.get("flags", {})
|
||||
for flag_type in [
|
||||
"cflags" if cc == compiler_pkg.cc else "cxxflags",
|
||||
"cppflags",
|
||||
"ldflags",
|
||||
]:
|
||||
current_flags = compiler_flags.get(flag_type, "").strip()
|
||||
if current_flags:
|
||||
cc_exe.add_default_arg(*current_flags.split(" "))
|
||||
|
||||
with self.compiler_environment():
|
||||
return cc_exe("-v", fin, "-o", fout, output=str, error=str)
|
||||
except spack.util.executable.ProcessError as pe:
|
||||
tty.debug(f"ProcessError: Command exited with non-zero status: {pe.long_message}")
|
||||
return None
|
||||
finally:
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
|
||||
def compiler_verbose_output(self) -> Optional[str]:
|
||||
return self.cache.get(self.spec).c_compiler_output
|
||||
|
||||
def default_dynamic_linker(self) -> Optional[str]:
|
||||
output = self.compiler_verbose_output()
|
||||
|
||||
if not output:
|
||||
return None
|
||||
|
||||
return spack.util.libc.parse_dynamic_linker(output)
|
||||
|
||||
def default_libc(self) -> Optional["spack.spec.Spec"]:
|
||||
"""Determine libc targeted by the compiler from link line"""
|
||||
# technically this should be testing the target platform of the compiler, but we don't have
|
||||
# that, so stick to host platform for now.
|
||||
if sys.platform in ("darwin", "win32"):
|
||||
return None
|
||||
|
||||
dynamic_linker = self.default_dynamic_linker()
|
||||
|
||||
if dynamic_linker is None:
|
||||
return None
|
||||
|
||||
return spack.util.libc.libc_from_dynamic_linker(dynamic_linker)
|
||||
|
||||
def implicit_rpaths(self) -> List[str]:
|
||||
output = self.compiler_verbose_output()
|
||||
if output is None:
|
||||
return []
|
||||
|
||||
link_dirs = parse_non_system_link_dirs(output)
|
||||
all_required_libs = list(self.spec.package.required_libs) + ["libc", "libc++", "libstdc++"]
|
||||
dynamic_linker = self.default_dynamic_linker()
|
||||
# FIXME (compiler as nodes): is this needed ?
|
||||
# if dynamic_linker is None:
|
||||
# return []
|
||||
result = DefaultDynamicLinkerFilter(dynamic_linker)(
|
||||
paths_containing_libs(link_dirs, all_required_libs)
|
||||
)
|
||||
return list(result)
|
||||
|
||||
|
||||
class DefaultDynamicLinkerFilter:
|
||||
"""Remove rpaths to directories that are default search paths of the dynamic linker."""
|
||||
|
||||
_CACHE: Dict[Optional[str], Set[Tuple[int, int]]] = {}
|
||||
|
||||
def __init__(self, dynamic_linker: Optional[str]) -> None:
|
||||
if dynamic_linker not in DefaultDynamicLinkerFilter._CACHE:
|
||||
# Identify directories by (inode, device) tuple, which handles symlinks too.
|
||||
default_path_identifiers: Set[Tuple[int, int]] = set()
|
||||
if not dynamic_linker:
|
||||
self.default_path_identifiers = None
|
||||
return
|
||||
for path in spack.util.libc.default_search_paths_from_dynamic_linker(dynamic_linker):
|
||||
try:
|
||||
s = os.stat(path)
|
||||
if stat.S_ISDIR(s.st_mode):
|
||||
default_path_identifiers.add((s.st_ino, s.st_dev))
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
DefaultDynamicLinkerFilter._CACHE[dynamic_linker] = default_path_identifiers
|
||||
|
||||
self.default_path_identifiers = DefaultDynamicLinkerFilter._CACHE[dynamic_linker]
|
||||
|
||||
def is_dynamic_loader_default_path(self, p: str) -> bool:
|
||||
if self.default_path_identifiers is None:
|
||||
return False
|
||||
try:
|
||||
s = os.stat(p)
|
||||
return (s.st_ino, s.st_dev) in self.default_path_identifiers
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def __call__(self, dirs: List[str]) -> List[str]:
|
||||
if not self.default_path_identifiers:
|
||||
return dirs
|
||||
return [p for p in dirs if not self.is_dynamic_loader_default_path(p)]
|
||||
|
||||
|
||||
def dynamic_linker_filter_for(node: "spack.spec.Spec") -> Optional[DefaultDynamicLinkerFilter]:
|
||||
compiler = compiler_spec(node)
|
||||
if compiler is None:
|
||||
return None
|
||||
detector = CompilerPropertyDetector(compiler)
|
||||
dynamic_linker = detector.default_dynamic_linker()
|
||||
if dynamic_linker is None:
|
||||
return None
|
||||
return DefaultDynamicLinkerFilter(dynamic_linker)
|
||||
|
||||
|
||||
def compiler_spec(node: "spack.spec.Spec") -> Optional["spack.spec.Spec"]:
|
||||
"""Returns the compiler spec associated with the node passed as argument.
|
||||
|
||||
The function looks for a "c", "cxx", and "fortran" compiler in that order,
|
||||
and returns the first found. If none is found, returns None.
|
||||
"""
|
||||
for language in ("c", "cxx", "fortran"):
|
||||
candidates = node.dependencies(virtuals=[language])
|
||||
if candidates:
|
||||
break
|
||||
else:
|
||||
return None
|
||||
|
||||
return candidates[0]
|
||||
|
||||
|
||||
class CompilerCacheEntry:
|
||||
"""Deserialized cache entry for a compiler"""
|
||||
|
||||
__slots__ = ["c_compiler_output"]
|
||||
|
||||
def __init__(self, c_compiler_output: Optional[str]):
|
||||
self.c_compiler_output = c_compiler_output
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Optional[str]]):
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError(f"Invalid {cls.__name__} data")
|
||||
c_compiler_output = data.get("c_compiler_output")
|
||||
if not isinstance(c_compiler_output, (str, type(None))):
|
||||
raise ValueError(f"Invalid {cls.__name__} data")
|
||||
return cls(c_compiler_output)
|
||||
|
||||
|
||||
class CompilerCache:
|
||||
"""Base class for compiler output cache. Default implementation does not cache anything."""
|
||||
|
||||
def value(self, compiler: "spack.spec.Spec") -> Dict[str, Optional[str]]:
|
||||
return {"c_compiler_output": CompilerPropertyDetector(compiler)._compile_dummy_c_source()}
|
||||
|
||||
def get(self, compiler: "spack.spec.Spec") -> CompilerCacheEntry:
|
||||
return CompilerCacheEntry.from_dict(self.value(compiler))
|
||||
|
||||
|
||||
class FileCompilerCache(CompilerCache):
|
||||
"""Cache for compiler output, which is used to determine implicit link paths, the default libc
|
||||
version, and the compiler version."""
|
||||
|
||||
name = os.path.join("compilers", "compilers.json")
|
||||
|
||||
def __init__(self, cache: "FileCache") -> None:
|
||||
self.cache = cache
|
||||
self.cache.init_entry(self.name)
|
||||
self._data: Dict[str, Dict[str, Optional[str]]] = {}
|
||||
|
||||
def _get_entry(self, key: str) -> Optional[CompilerCacheEntry]:
|
||||
try:
|
||||
return CompilerCacheEntry.from_dict(self._data[key])
|
||||
except ValueError:
|
||||
del self._data[key]
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get(self, compiler: "spack.spec.Spec") -> CompilerCacheEntry:
|
||||
# Cache hit
|
||||
try:
|
||||
with self.cache.read_transaction(self.name) as f:
|
||||
assert f is not None
|
||||
self._data = json.loads(f.read())
|
||||
assert isinstance(self._data, dict)
|
||||
except (json.JSONDecodeError, AssertionError):
|
||||
self._data = {}
|
||||
|
||||
key = self._key(compiler)
|
||||
value = self._get_entry(key)
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
# Cache miss
|
||||
with self.cache.write_transaction(self.name) as (old, new):
|
||||
try:
|
||||
assert old is not None
|
||||
self._data = json.loads(old.read())
|
||||
assert isinstance(self._data, dict)
|
||||
except (json.JSONDecodeError, AssertionError):
|
||||
self._data = {}
|
||||
|
||||
# Use cache entry that may have been created by another process in the meantime.
|
||||
entry = self._get_entry(key)
|
||||
|
||||
# Finally compute the cache entry
|
||||
if entry is None:
|
||||
self._data[key] = self.value(compiler)
|
||||
entry = CompilerCacheEntry.from_dict(self._data[key])
|
||||
|
||||
new.write(json.dumps(self._data, separators=(",", ":")))
|
||||
|
||||
return entry
|
||||
|
||||
def _key(self, compiler: "spack.spec.Spec") -> str:
|
||||
as_bytes = json.dumps(compiler.to_dict(), separators=(",", ":")).encode("utf-8")
|
||||
return hashlib.sha256(as_bytes).hexdigest()
|
||||
|
||||
|
||||
def _make_compiler_cache():
|
||||
return FileCompilerCache(spack.caches.MISC_CACHE)
|
||||
|
||||
|
||||
COMPILER_CACHE: CompilerCache = llnl.util.lang.Singleton(_make_compiler_cache) # type: ignore
|
||||
394
lib/spack/spack/compilers/msvc.py
Normal file
394
lib/spack/spack/compilers/msvc.py
Normal file
@@ -0,0 +1,394 @@
|
||||
# Copyright 2013-2024 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
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Dict
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
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, VersionRange
|
||||
|
||||
FC_PATH: Dict[str, str] = dict()
|
||||
|
||||
|
||||
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():
|
||||
"""Assign maximum available fortran compiler version"""
|
||||
# TODO (johnwparent): validate compatibility w/ try compiler
|
||||
# functionality when added
|
||||
sort_fn = lambda fc_ver: Version(fc_ver)
|
||||
sort_fc_ver = sorted(list(FC_PATH.keys()), key=sort_fn)
|
||||
return FC_PATH[sort_fc_ver[-1]] if sort_fc_ver else None
|
||||
|
||||
|
||||
class Msvc(Compiler):
|
||||
# Named wrapper links within build_env_path
|
||||
# Due to the challenges of supporting compiler wrappers
|
||||
# in Windows, we leave these blank, and dynamically compute
|
||||
# based on proper versions of MSVC from there
|
||||
# pending acceptance of #28117 for full support using
|
||||
# compiler wrappers
|
||||
link_paths = {"cc": "", "cxx": "", "f77": "", "fc": ""}
|
||||
|
||||
#: Compiler argument that produces version information
|
||||
version_argument = ""
|
||||
|
||||
# For getting ifx's version, call it with version_argument
|
||||
# and ignore the error code
|
||||
ignore_version_errors = [1]
|
||||
|
||||
#: Regex used to extract version from compiler's output
|
||||
version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)"
|
||||
# The MSVC compiler class overrides this to prevent instances
|
||||
# of erroneous matching on executable names that cannot be msvc
|
||||
# compilers
|
||||
suffixes = []
|
||||
|
||||
is_supported_on_platform = lambda x: isinstance(x, spack.platforms.Windows)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# 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]
|
||||
latest_fc = get_valid_fortran_pth()
|
||||
new_pth = [pth if pth else latest_fc for pth in paths[2:]]
|
||||
paths[2:] = new_pth
|
||||
# Initialize, deferring to base class but then adding the vcvarsallfile
|
||||
# file based on compiler executable path.
|
||||
super().__init__(*args, **kwargs)
|
||||
# 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(os.path.dirname(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("-", "_")
|
||||
if str(archspec.cpu.host().family) == "x86_64":
|
||||
arch = "amd64"
|
||||
|
||||
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]:
|
||||
|
||||
def get_oneapi_root(pth: str):
|
||||
"""From within a prefix known to be a oneAPI path
|
||||
determine the oneAPI root path from arbitrary point
|
||||
under root
|
||||
|
||||
Args:
|
||||
pth: path prefixed within oneAPI root
|
||||
"""
|
||||
if not pth:
|
||||
return ""
|
||||
while os.path.basename(pth) and os.path.basename(pth) != "oneAPI":
|
||||
pth = os.path.dirname(pth)
|
||||
return pth
|
||||
|
||||
# If this found, it sets all the vars
|
||||
oneapi_root = get_oneapi_root(self.fc)
|
||||
if not oneapi_root:
|
||||
raise RuntimeError(f"Non-oneAPI Fortran compiler {self.fc} assigned to MSVC")
|
||||
oneapi_root_setvars = os.path.join(oneapi_root, "setvars.bat")
|
||||
# some oneAPI exes return a version more precise than their
|
||||
# install paths specify, so we determine path from
|
||||
# the install path rather than the fc executable itself
|
||||
numver = r"\d+\.\d+(?:\.\d+)?"
|
||||
pattern = f"((?:{numver})|(?:latest))"
|
||||
version_from_path = re.search(pattern, self.fc).group(1)
|
||||
oneapi_version_setvars = os.path.join(
|
||||
oneapi_root, "compiler", version_from_path, "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 cxx11_flag(self):
|
||||
return "/std:c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
return "/std:c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
return "/std:c++17"
|
||||
|
||||
@property
|
||||
def cxx20_flag(self):
|
||||
return "/std:c++20"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
return "/std:c11"
|
||||
|
||||
@property
|
||||
def c17_flag(self):
|
||||
return "/std:c17"
|
||||
|
||||
@property
|
||||
def msvc_version(self):
|
||||
"""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>
|
||||
"""
|
||||
return "MSVC" + self.vc_toolset_ver
|
||||
|
||||
@property
|
||||
def vc_toolset_ver(self):
|
||||
"""
|
||||
The toolset version is the version of the combined set of cl and link
|
||||
This typically relates directly to VS version i.e. VS 2022 is v143
|
||||
VS 19 is v142, etc.
|
||||
This value is defined by the first three digits of the major + minor
|
||||
version of the VS toolset (143 for 14.3x.bbbbb). Traditionally the
|
||||
minor version has remained a static two digit number for a VS release
|
||||
series, however, as of VS22, this is no longer true, both
|
||||
14.4x.bbbbb and 14.3x.bbbbb are considered valid VS22 VC toolset
|
||||
versions due to a change in toolset minor version sentiment.
|
||||
|
||||
This is *NOT* the full version, for that see
|
||||
Msvc.msvc_version or MSVC.platform_toolset_ver for the
|
||||
raw platform toolset version
|
||||
|
||||
"""
|
||||
ver = self.msvc_version[:2].joined.string[:3]
|
||||
return ver
|
||||
|
||||
@property
|
||||
def platform_toolset_ver(self):
|
||||
"""
|
||||
This is the platform toolset version of current MSVC compiler
|
||||
i.e. 142. The platform toolset is the targeted MSVC library/compiler
|
||||
versions by compilation (this is different from the VC Toolset)
|
||||
|
||||
|
||||
This is different from the VC toolset version as established
|
||||
by `short_msvc_version`, but typically are represented by the same
|
||||
three digit value
|
||||
"""
|
||||
# Typically VS toolset version and platform toolset versions match
|
||||
# VS22 introduces the first divergence of VS toolset version
|
||||
# (144 for "recent" releases) and platform toolset version (143)
|
||||
# so it needs additional handling until MS releases v144
|
||||
# (assuming v144 is also for VS22)
|
||||
# or adds better support for detection
|
||||
# TODO: (johnwparent) Update this logic for the next platform toolset
|
||||
# or VC toolset version update
|
||||
toolset_ver = self.vc_toolset_ver
|
||||
vs22_toolset = Version(toolset_ver) > Version("142")
|
||||
return toolset_ver if not vs22_toolset else "143"
|
||||
|
||||
@property
|
||||
def visual_studio_version(self):
|
||||
"""The four digit Visual Studio version (i.e. 2019 or 2022)
|
||||
|
||||
Note: This differs from the msvc version or toolset version as
|
||||
those properties track the compiler and build tools version
|
||||
respectively, whereas this tracks the VS release associated
|
||||
with a given MSVC compiler.
|
||||
"""
|
||||
return re.search(r"[0-9]{4}", self.cc).group(0)
|
||||
|
||||
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(
|
||||
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
|
||||
# this should be consistent accross versions
|
||||
return os.path.abspath(os.path.join(self.cc, "../../../../../../../.."))
|
||||
|
||||
def setup_custom_environment(self, pkg, env):
|
||||
"""Set environment variables for MSVC using the
|
||||
Microsoft-provided script."""
|
||||
# Set the build environment variables for spack. Just using
|
||||
# subprocess.call() doesn't work since that operates in its own
|
||||
# environment which is destroyed (along with the adjusted variables)
|
||||
# once the process terminates. So go the long way around: examine
|
||||
# output, sort into dictionary, use that to make the build
|
||||
# environment.
|
||||
|
||||
# vcvars can target specific sdk versions, force it to pick up concretized sdk
|
||||
# version, if needed by spec
|
||||
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())
|
||||
if key and value
|
||||
)
|
||||
|
||||
for env_var in int_env:
|
||||
if os.pathsep not in int_env[env_var]:
|
||||
env.set(env_var, int_env[env_var])
|
||||
else:
|
||||
env.set_path(env_var, int_env[env_var].split(os.pathsep))
|
||||
|
||||
# certain versions of ifx (2021.3.0:2023.1.0) do not play well with env:TMP
|
||||
# that has a "." character in the path
|
||||
# Work around by pointing tmp to the stage for the duration of the build
|
||||
if self.fc and Version(self.fc_version(self.fc)).satisfies(
|
||||
VersionRange("2021.3.0", "2023.1.0")
|
||||
):
|
||||
new_tmp = tempfile.mkdtemp(dir=pkg.stage.path)
|
||||
env.set("TMP", new_tmp)
|
||||
|
||||
env.set("CC", self.cc)
|
||||
env.set("CXX", self.cxx)
|
||||
env.set("FC", self.fc)
|
||||
env.set("F77", self.f77)
|
||||
|
||||
@classmethod
|
||||
def fc_version(cls, fc):
|
||||
if not sys.platform == "win32":
|
||||
return "unknown"
|
||||
fc_ver = cls.default_version(fc)
|
||||
FC_PATH[fc_ver] = fc
|
||||
try:
|
||||
sps = spack.operating_systems.windows_os.WindowsOs().compiler_search_paths
|
||||
except AttributeError:
|
||||
raise SpackError(
|
||||
"Windows compiler search paths not established, "
|
||||
"please report this behavior to github.com/spack/spack"
|
||||
)
|
||||
clp = spack.util.executable.which_string("cl", path=sps)
|
||||
return cls.default_version(clp) if clp else fc_ver
|
||||
112
lib/spack/spack/compilers/nag.py
Normal file
112
lib/spack/spack/compilers/nag.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# Copyright 2013-2024 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
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
import spack.compiler
|
||||
|
||||
|
||||
class Nag(spack.compiler.Compiler):
|
||||
# Named wrapper links within build_env_path
|
||||
# Use default wrappers for C and C++, in case provided in compilers.yaml
|
||||
link_paths = {
|
||||
"cc": "cc",
|
||||
"cxx": "c++",
|
||||
"f77": os.path.join("nag", "nagfor"),
|
||||
"fc": os.path.join("nag", "nagfor"),
|
||||
}
|
||||
|
||||
version_argument = "-V"
|
||||
|
||||
@classmethod
|
||||
@llnl.util.lang.memoized
|
||||
def extract_version_from_output(cls, output):
|
||||
match = re.search(r"NAG Fortran Compiler Release (\d+).(\d+)\(.*\) Build (\d+)", output)
|
||||
if match:
|
||||
return ".".join(match.groups())
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
# NAG does not support a flag that would enable verbose output and
|
||||
# compilation/linking at the same time (with either '-#' or '-dryrun'
|
||||
# the compiler only prints the commands but does not run them).
|
||||
# Therefore, the only thing we can do is to pass the '-v' argument to
|
||||
# the underlying GCC. In order to get verbose output from the latter
|
||||
# at both compile and linking stages, we need to call NAG with two
|
||||
# additional flags: '-Wc,-v' and '-Wl,-v'. However, we return only
|
||||
# '-Wl,-v' for the following reasons:
|
||||
# 1) the interface of this method does not support multiple flags in
|
||||
# the return value and, at least currently, verbose output at the
|
||||
# linking stage has a higher priority for us;
|
||||
# 2) NAG is usually mixed with GCC compiler, which also accepts
|
||||
# '-Wl,-v' and produces meaningful result with it: '-v' is passed
|
||||
# to the linker and the latter produces verbose output for the
|
||||
# linking stage ('-Wc,-v', however, would break the compilation
|
||||
# with a message from GCC that the flag is not recognized).
|
||||
#
|
||||
# This way, we at least enable the implicit rpath detection, which is
|
||||
# based on compilation of a C file (see method
|
||||
# spack.compiler._compile_dummy_c_source): in the case of a mixed
|
||||
# NAG/GCC toolchain, the flag will be passed to g++ (e.g.
|
||||
# 'g++ -Wl,-v ./main.c'), otherwise, the flag will be passed to nagfor
|
||||
# (e.g. 'nagfor -Wl,-v ./main.c' - note that nagfor recognizes '.c'
|
||||
# extension and treats the file accordingly). The list of detected
|
||||
# rpaths will contain only GCC-related directories and rpaths to
|
||||
# NAG-related directories are injected by nagfor anyway.
|
||||
return "-Wl,-v"
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-openmp"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-g", "-gline", "-g90"]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"]
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
# NAG does not have a C++ compiler
|
||||
# However, it can be mixed with a compiler that does support it
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-PIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-PIC"
|
||||
|
||||
# Unlike other compilers, the NAG compiler passes options to GCC, which
|
||||
# then passes them to the linker. Therefore, we need to doubly wrap the
|
||||
# options with '-Wl,-Wl,,'
|
||||
@property
|
||||
def f77_rpath_arg(self):
|
||||
return "-Wl,-Wl,,-rpath,,"
|
||||
|
||||
@property
|
||||
def fc_rpath_arg(self):
|
||||
return "-Wl,-Wl,,-rpath,,"
|
||||
|
||||
@property
|
||||
def linker_arg(self):
|
||||
return "-Wl,-Wl,,"
|
||||
|
||||
@property
|
||||
def disable_new_dtags(self):
|
||||
# Disable RPATH/RUNPATH forcing for NAG/GCC mixed toolchains:
|
||||
return ""
|
||||
|
||||
@property
|
||||
def enable_new_dtags(self):
|
||||
# Disable RPATH/RUNPATH forcing for NAG/GCC mixed toolchains:
|
||||
return ""
|
||||
79
lib/spack/spack/compilers/nvhpc.py
Normal file
79
lib/spack/spack/compilers/nvhpc.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Copyright 2013-2024 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
|
||||
|
||||
from spack.compiler import Compiler
|
||||
|
||||
|
||||
class Nvhpc(Compiler):
|
||||
# Named wrapper links within build_env_path
|
||||
link_paths = {
|
||||
"cc": os.path.join("nvhpc", "nvc"),
|
||||
"cxx": os.path.join("nvhpc", "nvc++"),
|
||||
"f77": os.path.join("nvhpc", "nvfortran"),
|
||||
"fc": os.path.join("nvhpc", "nvfortran"),
|
||||
}
|
||||
|
||||
version_argument = "--version"
|
||||
version_regex = r"nv[^ ]* (?:[^ ]+ Dev-r)?([0-9.]+)(?:-[0-9]+)?"
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-g", "-gopt"]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-mp"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-fpic"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-fpic"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-fpic"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-fpic"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
return "-c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
return "-c11"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
return "--c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
return "--c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
return "--c++17"
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
return ("-c++libs",)
|
||||
|
||||
required_libs = ["libnvc", "libnvf"]
|
||||
172
lib/spack/spack/compilers/oneapi.py
Normal file
172
lib/spack/spack/compilers/oneapi.py
Normal file
@@ -0,0 +1,172 @@
|
||||
# Copyright 2013-2024 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
|
||||
from os.path import dirname, join
|
||||
|
||||
from llnl.util import tty
|
||||
from llnl.util.filesystem import ancestor
|
||||
|
||||
import spack.util.executable
|
||||
from spack.compiler import Compiler
|
||||
from spack.version import Version
|
||||
|
||||
|
||||
class Oneapi(Compiler):
|
||||
# Named wrapper links within build_env_path
|
||||
link_paths = {
|
||||
"cc": os.path.join("oneapi", "icx"),
|
||||
"cxx": os.path.join("oneapi", "icpx"),
|
||||
"f77": os.path.join("oneapi", "ifx"),
|
||||
"fc": os.path.join("oneapi", "ifx"),
|
||||
}
|
||||
|
||||
version_argument = "--version"
|
||||
version_regex = r"(?:(?:oneAPI DPC\+\+(?:\/C\+\+)? Compiler)|(?:\(IFORT\))|(?:\(IFX\))) (\S+)"
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-v"
|
||||
|
||||
required_libs = [
|
||||
"libirc",
|
||||
"libifcore",
|
||||
"libifcoremt",
|
||||
"libirng",
|
||||
"libsvml",
|
||||
"libintlc",
|
||||
"libimf",
|
||||
"libsycl",
|
||||
"libOpenCL",
|
||||
]
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-fiopenmp"
|
||||
|
||||
# There may be some additional options here for offload, e.g. :
|
||||
# -fopenmp-simd Emit OpenMP code only for SIMD-based constructs.
|
||||
# -fopenmp-targets=<value>
|
||||
# -fopenmp-version=<value>
|
||||
# -fopenmp Parse OpenMP pragmas and generate parallel code.
|
||||
# -qno-openmp Disable OpenMP support
|
||||
# -qopenmp-link=<value> Choose whether to link with the static or
|
||||
# dynamic OpenMP libraries. Default is dynamic.
|
||||
# -qopenmp-simd Emit OpenMP code only for SIMD-based constructs.
|
||||
# -qopenmp-stubs enables the user to compile OpenMP programs in
|
||||
# sequential mode. The OpenMP directives are
|
||||
# ignored and a stub OpenMP library is linked.
|
||||
# -qopenmp-threadprivate=<value>
|
||||
# -qopenmp Parse OpenMP pragmas and generate parallel code.
|
||||
# -static-openmp Use the static host OpenMP runtime while
|
||||
# linking.
|
||||
# -Xopenmp-target=<triple> <arg>
|
||||
# -Xopenmp-target <arg> Pass <arg> to the target offloading toolchain.
|
||||
# Source: icx --help output
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def cxx20_flag(self):
|
||||
return "-std=c++20"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
return "-std=c1x"
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-fPIC"
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
return ("-cxxlib",)
|
||||
|
||||
@property
|
||||
def prefix(self):
|
||||
# OneAPI reports its install prefix when running ``--version``
|
||||
# on the line ``InstalledDir: <prefix>/bin/compiler``.
|
||||
cc = spack.util.executable.Executable(self.cc)
|
||||
with self.compiler_environment():
|
||||
oneapi_output = cc("--version", output=str, error=str)
|
||||
|
||||
for line in oneapi_output.splitlines():
|
||||
if line.startswith("InstalledDir:"):
|
||||
oneapi_prefix = line.split(":")[1].strip()
|
||||
# Go from <prefix>/bin/compiler to <prefix>
|
||||
return ancestor(oneapi_prefix, 2)
|
||||
|
||||
raise RuntimeError(
|
||||
"could not find install prefix of OneAPI from output:\n\t{}".format(oneapi_output)
|
||||
)
|
||||
|
||||
def setup_custom_environment(self, pkg, env):
|
||||
# workaround bug in icpx driver where it requires sycl-post-link is on the PATH
|
||||
# It is located in the same directory as the driver. Error message:
|
||||
# clang++: error: unable to execute command:
|
||||
# Executable "sycl-post-link" doesn't exist!
|
||||
# also ensures that shared objects and libraries required by the compiler,
|
||||
# e.g. libonnx, can be found succesfully
|
||||
# due to a fix, this is no longer required for OneAPI versions >= 2024.2
|
||||
if self.cxx and pkg.spec.satisfies("%oneapi@:2024.1"):
|
||||
env.prepend_path("PATH", dirname(self.cxx))
|
||||
env.prepend_path("LD_LIBRARY_PATH", join(dirname(dirname(self.cxx)), "lib"))
|
||||
|
||||
# Edge cases for Intel's oneAPI compilers when using the legacy classic compilers:
|
||||
# Always pass flags to disable deprecation warnings, since these warnings can
|
||||
# confuse tools that parse the output of compiler commands (e.g. version checks).
|
||||
# This is really only needed for Fortran, since oneapi@ should be using either
|
||||
# icx+icpx+ifx or icx+icpx+ifort. But to be on the safe side (some users may
|
||||
# want to try to swap icpx against icpc, for example), and since the Intel LLVM
|
||||
# compilers accept these diag-disable flags, we apply them for all compilers.
|
||||
if self.real_version >= Version("2021") and self.real_version < Version("2024"):
|
||||
env.append_flags("SPACK_ALWAYS_CFLAGS", "-diag-disable=10441")
|
||||
env.append_flags("SPACK_ALWAYS_CXXFLAGS", "-diag-disable=10441")
|
||||
if self.real_version >= Version("2021") and self.real_version < Version("2025"):
|
||||
env.append_flags("SPACK_ALWAYS_FFLAGS", "-diag-disable=10448")
|
||||
|
||||
# 2024 release bumped the libsycl version because of an ABI
|
||||
# change, 2024 compilers are required. You will see this
|
||||
# error:
|
||||
#
|
||||
# /usr/bin/ld: warning: libsycl.so.7, needed by ...., not found
|
||||
if pkg.spec.satisfies("%oneapi@:2023"):
|
||||
for c in ["dnn"]:
|
||||
if pkg.spec.satisfies(f"^intel-oneapi-{c}@2024:"):
|
||||
tty.warn(f"intel-oneapi-{c}@2024 SYCL APIs requires %oneapi@2024:")
|
||||
54
lib/spack/spack/compilers/rocmcc.py
Normal file
54
lib/spack/spack/compilers/rocmcc.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Copyright 2013-2024 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 re
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
import spack.compilers.clang
|
||||
|
||||
|
||||
class Rocmcc(spack.compilers.clang.Clang):
|
||||
@property
|
||||
def link_paths(self):
|
||||
link_paths = {
|
||||
"cc": "rocmcc/amdclang",
|
||||
"cxx": "rocmcc/amdclang++",
|
||||
"f77": "rocmcc/amdflang",
|
||||
"fc": "rocmcc/amdflang",
|
||||
}
|
||||
|
||||
return link_paths
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
return "-std=c++11"
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
return "-std=c++14"
|
||||
|
||||
@property
|
||||
def cxx17_flag(self):
|
||||
return "-std=c++17"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
return "-std=c99"
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
return "-std=c11"
|
||||
|
||||
@classmethod
|
||||
@llnl.util.lang.memoized
|
||||
def extract_version_from_output(cls, output):
|
||||
match = re.search(r"llvm-project roc-(\d+)[._](\d+)[._](\d+)", output)
|
||||
if match:
|
||||
return ".".join(match.groups())
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
return ("-lstdc++",)
|
||||
93
lib/spack/spack/compilers/xl.py
Normal file
93
lib/spack/spack/compilers/xl.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# Copyright 2013-2024 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
|
||||
|
||||
from spack.compiler import Compiler, UnsupportedCompilerFlag
|
||||
from spack.version import Version
|
||||
|
||||
|
||||
class Xl(Compiler):
|
||||
# Named wrapper links within build_env_path
|
||||
link_paths = {
|
||||
"cc": os.path.join("xl", "xlc"),
|
||||
"cxx": os.path.join("xl", "xlc++"),
|
||||
"f77": os.path.join("xl", "xlf"),
|
||||
"fc": os.path.join("xl", "xlf90"),
|
||||
}
|
||||
|
||||
version_argument = "-qversion"
|
||||
version_regex = r"([0-9]?[0-9]\.[0-9])"
|
||||
|
||||
@property
|
||||
def verbose_flag(self):
|
||||
return "-V"
|
||||
|
||||
@property
|
||||
def debug_flags(self):
|
||||
return ["-g", "-g0", "-g1", "-g2", "-g8", "-g9"]
|
||||
|
||||
@property
|
||||
def opt_flags(self):
|
||||
return ["-O", "-O0", "-O1", "-O2", "-O3", "-O4", "-O5", "-Ofast"]
|
||||
|
||||
@property
|
||||
def openmp_flag(self):
|
||||
return "-qsmp=omp"
|
||||
|
||||
@property
|
||||
def cxx11_flag(self):
|
||||
if self.real_version < Version("13.1"):
|
||||
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 13.1")
|
||||
else:
|
||||
return "-qlanglvl=extended0x"
|
||||
|
||||
@property
|
||||
def c99_flag(self):
|
||||
if self.real_version >= Version("13.1.1"):
|
||||
return "-std=gnu99"
|
||||
if self.real_version >= Version("10.1"):
|
||||
return "-qlanglvl=extc99"
|
||||
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 10.1")
|
||||
|
||||
@property
|
||||
def c11_flag(self):
|
||||
if self.real_version >= Version("13.1.2"):
|
||||
return "-std=gnu11"
|
||||
if self.real_version >= Version("12.1"):
|
||||
return "-qlanglvl=extc1x"
|
||||
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 12.1")
|
||||
|
||||
@property
|
||||
def cxx14_flag(self):
|
||||
# .real_version does not have the "y.z" component of "w.x.y.z", which
|
||||
# is required to distinguish whether support is available
|
||||
if self.version >= Version("16.1.1.8"):
|
||||
return "-std=c++14"
|
||||
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 16.1.1.8")
|
||||
|
||||
@property
|
||||
def cc_pic_flag(self):
|
||||
return "-qpic"
|
||||
|
||||
@property
|
||||
def cxx_pic_flag(self):
|
||||
return "-qpic"
|
||||
|
||||
@property
|
||||
def f77_pic_flag(self):
|
||||
return "-qpic"
|
||||
|
||||
@property
|
||||
def fc_pic_flag(self):
|
||||
return "-qpic"
|
||||
|
||||
@property
|
||||
def fflags(self):
|
||||
# The -qzerosize flag is effective only for the Fortran 77
|
||||
# compilers and allows the use of zero size objects.
|
||||
# For Fortran 90 and beyond, it is set by default and has not impact.
|
||||
# Its use has no negative side effects.
|
||||
return "-qzerosize"
|
||||
18
lib/spack/spack/compilers/xl_r.py
Normal file
18
lib/spack/spack/compilers/xl_r.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copyright 2013-2024 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 spack.compilers.xl
|
||||
|
||||
|
||||
class XlR(spack.compilers.xl.Xl):
|
||||
# Named wrapper links within build_env_path
|
||||
link_paths = {
|
||||
"cc": os.path.join("xl_r", "xlc_r"),
|
||||
"cxx": os.path.join("xl_r", "xlc++_r"),
|
||||
"f77": os.path.join("xl_r", "xlf_r"),
|
||||
"fc": os.path.join("xl_r", "xlf90_r"),
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.compilers
|
||||
import spack.compilers.config
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.repo
|
||||
@@ -147,7 +146,7 @@ def concretize_separately(
|
||||
|
||||
# Ensure we have compilers in compilers.yaml to avoid that
|
||||
# processes try to write the config file in parallel
|
||||
_ = spack.compilers.config.all_compilers_from(spack.config.CONFIG)
|
||||
_ = spack.compilers.all_compilers_config(spack.config.CONFIG)
|
||||
|
||||
# Early return if there is nothing to do
|
||||
if len(args) == 0:
|
||||
|
||||
@@ -715,9 +715,6 @@ def print_section(self, section: str, blame: bool = False, *, scope=None) -> Non
|
||||
raise spack.error.ConfigError(f"cannot read '{section}' configuration") from e
|
||||
|
||||
|
||||
ConfigurationType = Union[Configuration, lang.Singleton]
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def override(
|
||||
path_or_scope: Union[ConfigScope, str], value: Optional[Any] = None
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.cmd
|
||||
import spack.compilers.config
|
||||
import spack.compilers
|
||||
import spack.deptypes as dt
|
||||
import spack.error
|
||||
import spack.hash_types as hash_types
|
||||
@@ -28,13 +28,13 @@
|
||||
#: packages here.
|
||||
default_path = "/opt/cray/pe/cpe-descriptive-manifest/"
|
||||
|
||||
compiler_name_translation = {"nvidia": "nvhpc", "rocm": "rocmcc", "clang": "llvm"}
|
||||
compiler_name_translation = {"nvidia": "nvhpc", "rocm": "rocmcc"}
|
||||
|
||||
|
||||
def translated_compiler_name(manifest_compiler_name):
|
||||
"""
|
||||
When creating a Compiler object, Spack expects a name matching
|
||||
one of the classes in `spack.compilers.config`. Names in the Cray manifest
|
||||
one of the classes in `spack.compilers`. Names in the Cray manifest
|
||||
may differ; for cases where we know the name refers to a compiler in
|
||||
Spack, this function translates it automatically.
|
||||
|
||||
@@ -43,25 +43,25 @@ def translated_compiler_name(manifest_compiler_name):
|
||||
"""
|
||||
if manifest_compiler_name in compiler_name_translation:
|
||||
return compiler_name_translation[manifest_compiler_name]
|
||||
elif manifest_compiler_name in spack.compilers.config.supported_compilers():
|
||||
elif manifest_compiler_name in spack.compilers.supported_compilers():
|
||||
return manifest_compiler_name
|
||||
else:
|
||||
raise spack.compilers.config.UnknownCompilerError(
|
||||
raise spack.compilers.UnknownCompilerError(
|
||||
"Manifest parsing - unknown compiler: {0}".format(manifest_compiler_name)
|
||||
)
|
||||
|
||||
|
||||
def compiler_from_entry(entry: dict, *, manifest_path: str) -> "spack.spec.Spec":
|
||||
def compiler_from_entry(entry: dict, manifest_path: str):
|
||||
# Note that manifest_path is only passed here to compose a
|
||||
# useful warning message when paths appear to be missing.
|
||||
compiler_name = translated_compiler_name(entry["name"])
|
||||
|
||||
prefix = None
|
||||
if "prefix" in entry:
|
||||
prefix = entry["prefix"]
|
||||
paths = {
|
||||
lang: os.path.join(prefix, relpath) for lang, relpath in entry["executables"].items()
|
||||
}
|
||||
paths = dict(
|
||||
(lang, os.path.join(prefix, relpath))
|
||||
for (lang, relpath) in entry["executables"].items()
|
||||
)
|
||||
else:
|
||||
paths = entry["executables"]
|
||||
|
||||
@@ -75,38 +75,25 @@ def compiler_from_entry(entry: dict, *, manifest_path: str) -> "spack.spec.Spec"
|
||||
missing_paths.append(path)
|
||||
|
||||
# to instantiate a compiler class we may need a concrete version:
|
||||
version = "={}".format(entry["version"])
|
||||
arch = entry["arch"]
|
||||
operating_system = arch["os"]
|
||||
target = arch["target"]
|
||||
spec_str = f"{compiler_name}@={entry['version']} os={operating_system} target={target}"
|
||||
|
||||
compilers = {}
|
||||
for x in ("cc", "cxx", "fc"):
|
||||
language = {"cc": "c", "fc": "fortran"}
|
||||
if x not in paths:
|
||||
continue
|
||||
|
||||
if prefix is None:
|
||||
prefix = os.path.dirname(paths[x])
|
||||
|
||||
compilers[language.get(x, x)] = paths[x]
|
||||
compiler_cls = spack.compilers.class_for_compiler_name(compiler_name)
|
||||
spec = spack.spec.CompilerSpec(compiler_cls.name, version)
|
||||
path_list = [paths.get(x, None) for x in ("cc", "cxx", "f77", "fc")]
|
||||
|
||||
if missing_paths:
|
||||
warnings.warn(
|
||||
"Manifest entry refers to nonexistent paths:\n\t"
|
||||
+ "\n\t".join(missing_paths)
|
||||
+ f"\nfor {spec_str}"
|
||||
+ f"\nfor {str(spec)}"
|
||||
+ f"\nin {manifest_path}"
|
||||
+ "\nPlease report this issue"
|
||||
)
|
||||
|
||||
assert prefix is not None, "compiler prefix must be set"
|
||||
result = spack.spec.Spec(
|
||||
str(spack.spec.parse_with_version_concrete(spec_str)), external_path=prefix
|
||||
)
|
||||
result.extra_attributes = {"compilers": compilers}
|
||||
result._finalize_concretization()
|
||||
return result
|
||||
return compiler_cls(spec, operating_system, target, path_list)
|
||||
|
||||
|
||||
def spec_from_entry(entry):
|
||||
@@ -134,7 +121,7 @@ def spec_from_entry(entry):
|
||||
version=entry["compiler"]["version"],
|
||||
)
|
||||
|
||||
spec_format = "{name}@={version} {arch}"
|
||||
spec_format = "{name}@={version} {compiler} {arch}"
|
||||
spec_str = spec_format.format(
|
||||
name=entry["name"], version=entry["version"], compiler=compiler_str, arch=arch_str
|
||||
)
|
||||
@@ -195,7 +182,6 @@ def entries_to_specs(entries):
|
||||
for entry in entries:
|
||||
try:
|
||||
spec = spec_from_entry(entry)
|
||||
assert spec.concrete, f"{spec} is not concrete"
|
||||
spec_dict[spec._hash] = spec
|
||||
except spack.repo.UnknownPackageError:
|
||||
tty.debug("Omitting package {0}: no corresponding repo package".format(entry["name"]))
|
||||
@@ -236,24 +222,23 @@ def read(path, apply_updates):
|
||||
tty.debug("{0}: {1} specs read from manifest".format(path, str(len(specs))))
|
||||
compilers = list()
|
||||
if "compilers" in json_data:
|
||||
compilers.extend(
|
||||
compiler_from_entry(x, manifest_path=path) for x in json_data["compilers"]
|
||||
)
|
||||
tty.debug(f"{path}: {str(len(compilers))} compilers read from manifest")
|
||||
compilers.extend(compiler_from_entry(x, path) for x in json_data["compilers"])
|
||||
tty.debug("{0}: {1} compilers read from manifest".format(path, str(len(compilers))))
|
||||
# Filter out the compilers that already appear in the configuration
|
||||
compilers = spack.compilers.config.select_new_compilers(compilers)
|
||||
compilers = spack.compilers.select_new_compilers(compilers)
|
||||
if apply_updates and compilers:
|
||||
try:
|
||||
spack.compilers.config.add_compiler_to_config(compilers)
|
||||
except Exception:
|
||||
warnings.warn(
|
||||
f"Could not add compilers from manifest: {path}"
|
||||
"\nPlease reexecute with 'spack -d' and include the stack trace"
|
||||
)
|
||||
tty.debug(f"Include this\n{traceback.format_exc()}")
|
||||
for compiler in compilers:
|
||||
try:
|
||||
spack.compilers.add_compilers_to_config([compiler])
|
||||
except Exception:
|
||||
warnings.warn(
|
||||
f"Could not add compiler {str(compiler.spec)}: "
|
||||
f"\n\tfrom manifest: {path}"
|
||||
"\nPlease reexecute with 'spack -d' and include the stack trace"
|
||||
)
|
||||
tty.debug(f"Include this\n{traceback.format_exc()}")
|
||||
if apply_updates:
|
||||
for spec in specs.values():
|
||||
assert spec.concrete, f"{spec} is not concrete"
|
||||
spack.store.STORE.db.add(spec)
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
#: DB version. This is stuck in the DB file to track changes in format.
|
||||
#: Increment by one when the database format changes.
|
||||
#: Versions before 5 were not integers.
|
||||
_DB_VERSION = vn.Version("8")
|
||||
_DB_VERSION = vn.Version("7")
|
||||
|
||||
#: For any version combinations here, skip reindex when upgrading.
|
||||
#: Reindexing can take considerable time and is not always necessary.
|
||||
@@ -93,8 +93,6 @@
|
||||
(vn.Version("0.9.3"), vn.Version("5")),
|
||||
(vn.Version("5"), vn.Version("6")),
|
||||
(vn.Version("6"), vn.Version("7")),
|
||||
(vn.Version("6"), vn.Version("8")),
|
||||
(vn.Version("7"), vn.Version("8")),
|
||||
]
|
||||
|
||||
#: Default timeout for spack database locks in seconds or None (no timeout).
|
||||
@@ -143,7 +141,6 @@ def reader(version: vn.StandardVersion) -> Type["spack.spec.SpecfileReaderBase"]
|
||||
vn.Version("5"): spack.spec.SpecfileV1,
|
||||
vn.Version("6"): spack.spec.SpecfileV3,
|
||||
vn.Version("7"): spack.spec.SpecfileV4,
|
||||
vn.Version("8"): spack.spec.SpecfileV5,
|
||||
}
|
||||
return reader_cls[version]
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ class OpenMpi(Package):
|
||||
PatchesType = Optional[Union[Patcher, str, List[Union[Patcher, str]]]]
|
||||
|
||||
|
||||
SUPPORTED_LANGUAGES = ("fortran", "cxx", "c")
|
||||
|
||||
|
||||
def _make_when_spec(value: WhenType) -> Optional[spack.spec.Spec]:
|
||||
"""Create a ``Spec`` that indicates when a directive should be applied.
|
||||
|
||||
@@ -363,6 +366,9 @@ def depends_on(
|
||||
|
||||
"""
|
||||
dep_spec = spack.spec.Spec(spec)
|
||||
if dep_spec.name in SUPPORTED_LANGUAGES:
|
||||
assert type == "build", "languages must be of 'build' type"
|
||||
return _language(lang_spec_str=spec, when=when)
|
||||
|
||||
def _execute_depends_on(pkg: spack.package_base.PackageBase):
|
||||
_depends_on(pkg, dep_spec, when=when, type=type, patches=patches)
|
||||
@@ -896,6 +902,21 @@ def _execute_requires(pkg: spack.package_base.PackageBase):
|
||||
return _execute_requires
|
||||
|
||||
|
||||
@directive("languages")
|
||||
def _language(lang_spec_str: str, *, when: Optional[Union[str, bool]] = None):
|
||||
"""Temporary implementation of language virtuals, until compilers are proper dependencies."""
|
||||
|
||||
def _execute_languages(pkg: spack.package_base.PackageBase):
|
||||
when_spec = _make_when_spec(when)
|
||||
if not when_spec:
|
||||
return
|
||||
|
||||
languages = pkg.languages.setdefault(when_spec, set())
|
||||
languages.add(lang_spec_str)
|
||||
|
||||
return _execute_languages
|
||||
|
||||
|
||||
class DependencyError(DirectiveError):
|
||||
"""This is raised when a dependency specification is invalid."""
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
from spack.error import SpackError
|
||||
|
||||
default_projections = {
|
||||
"all": "{architecture.platform}/{architecture.target}/{name}-{version}-{hash}"
|
||||
"all": "{architecture}/{compiler.name}-{compiler.version}/{name}-{version}-{hash}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -166,7 +166,9 @@ def __init__(
|
||||
item.target.safe_name(),
|
||||
" ".join(self._install_target(s.safe_name()) for s in item.prereqs),
|
||||
item.target.spec_hash(),
|
||||
item.target.unsafe_format("{name}{@version}{variants}{ arch=architecture}"),
|
||||
item.target.unsafe_format(
|
||||
"{name}{@version}{%compiler}{variants}{arch=architecture}"
|
||||
),
|
||||
item.buildcache_flag,
|
||||
)
|
||||
for item in adjacency_list
|
||||
|
||||
@@ -135,7 +135,7 @@ def default_manifest_yaml():
|
||||
valid_environment_name_re = r"^\w[\w-]*$"
|
||||
|
||||
#: version of the lockfile format. Must increase monotonically.
|
||||
lockfile_format_version = 6
|
||||
lockfile_format_version = 5
|
||||
|
||||
|
||||
READER_CLS = {
|
||||
@@ -144,7 +144,6 @@ def default_manifest_yaml():
|
||||
3: spack.spec.SpecfileV2,
|
||||
4: spack.spec.SpecfileV3,
|
||||
5: spack.spec.SpecfileV4,
|
||||
6: spack.spec.SpecfileV5,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -325,12 +325,7 @@ def write(self, spec, color=None, out=None):
|
||||
self._out = llnl.util.tty.color.ColorStream(out, color=color)
|
||||
|
||||
# We'll traverse the spec in topological order as we graph it.
|
||||
nodes_in_topological_order = [
|
||||
edge.spec
|
||||
for edge in spack.traverse.traverse_edges_topo(
|
||||
[spec], direction="children", deptype=self.depflag
|
||||
)
|
||||
]
|
||||
nodes_in_topological_order = list(spec.traverse(order="topo", deptype=self.depflag))
|
||||
nodes_in_topological_order.reverse()
|
||||
|
||||
# Work on a copy to be nondestructive
|
||||
|
||||
@@ -375,23 +375,16 @@ def phase_tests(self, builder, phase_name: str, method_names: List[str]):
|
||||
|
||||
for name in method_names:
|
||||
try:
|
||||
# Prefer the method in the package over the builder's.
|
||||
# We need this primarily to pick up arbitrarily named test
|
||||
# methods but also some build-time checks.
|
||||
fn = getattr(builder.pkg, name, getattr(builder, name))
|
||||
|
||||
msg = f"RUN-TESTS: {phase_name}-time tests [{name}]"
|
||||
print_message(logger, msg, verbose)
|
||||
|
||||
fn()
|
||||
|
||||
fn = getattr(builder, name, None) or getattr(builder.pkg, name)
|
||||
except AttributeError as e:
|
||||
msg = f"RUN-TESTS: method not implemented [{name}]"
|
||||
print_message(logger, msg, verbose)
|
||||
|
||||
self.add_failure(e, msg)
|
||||
print_message(logger, f"RUN-TESTS: method not implemented [{name}]", verbose)
|
||||
self.add_failure(e, f"RUN-TESTS: method not implemented [{name}]")
|
||||
if fail_fast:
|
||||
break
|
||||
continue
|
||||
|
||||
print_message(logger, f"RUN-TESTS: {phase_name}-time tests [{name}]", verbose)
|
||||
fn()
|
||||
|
||||
if have_tests:
|
||||
print_message(logger, "Completed testing", verbose)
|
||||
|
||||
@@ -71,16 +71,12 @@ def _filter_compiler_wrappers_impl(pkg_or_builder):
|
||||
|
||||
x = llnl.util.filesystem.FileFilter(*abs_files)
|
||||
|
||||
compiler_vars = []
|
||||
if "c" in pkg.spec:
|
||||
compiler_vars.append(("CC", pkg.spec["c"].package.cc))
|
||||
|
||||
if "cxx" in pkg.spec:
|
||||
compiler_vars.append(("CXX", pkg.spec["cxx"].package.cxx))
|
||||
|
||||
if "fortran" in pkg.spec:
|
||||
compiler_vars.append(("FC", pkg.spec["fortran"].package.fortran))
|
||||
compiler_vars.append(("F77", pkg.spec["fortran"].package.fortran))
|
||||
compiler_vars = [
|
||||
("CC", pkg.compiler.cc),
|
||||
("CXX", pkg.compiler.cxx),
|
||||
("F77", pkg.compiler.f77),
|
||||
("FC", pkg.compiler.fc),
|
||||
]
|
||||
|
||||
# Some paths to the compiler wrappers might be substrings of the others.
|
||||
# For example:
|
||||
@@ -108,11 +104,7 @@ def _filter_compiler_wrappers_impl(pkg_or_builder):
|
||||
x.filter(wrapper_path, compiler_path, **filter_kwargs)
|
||||
|
||||
# Remove this linking flag if present (it turns RPATH into RUNPATH)
|
||||
for compiler_lang in ("c", "cxx", "fortran"):
|
||||
if compiler_lang not in pkg.spec:
|
||||
continue
|
||||
compiler_pkg = pkg.spec[compiler_lang].package
|
||||
x.filter(f"{compiler_pkg.linker_arg}--enable-new-dtags", "", **filter_kwargs)
|
||||
x.filter("{0}--enable-new-dtags".format(pkg.compiler.linker_arg), "", **filter_kwargs)
|
||||
|
||||
# NAG compiler is usually mixed with GCC, which has a different
|
||||
# prefix for linker arguments.
|
||||
|
||||
@@ -330,17 +330,18 @@ class BaseConfiguration:
|
||||
default_projections = {"all": "{name}/{version}-{compiler.name}-{compiler.version}"}
|
||||
|
||||
def __init__(self, spec: spack.spec.Spec, module_set_name: str, explicit: bool) -> None:
|
||||
# Module where type(self) is defined
|
||||
m = inspect.getmodule(self)
|
||||
assert m is not None # make mypy happy
|
||||
self.module = m
|
||||
# Spec for which we want to generate a module file
|
||||
self.spec = spec
|
||||
self.name = module_set_name
|
||||
self.explicit = explicit
|
||||
# Dictionary of configuration options that should be applied to the spec
|
||||
# Dictionary of configuration options that should be applied
|
||||
# to the spec
|
||||
self.conf = merge_config_rules(self.module.configuration(self.name), self.spec)
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
return inspect.getmodule(self)
|
||||
|
||||
@property
|
||||
def projections(self):
|
||||
"""Projection from specs to module names"""
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
import collections
|
||||
import itertools
|
||||
import os.path
|
||||
import pathlib
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang as lang
|
||||
|
||||
import spack.compilers.config
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.repo
|
||||
@@ -60,7 +59,7 @@ def make_context(
|
||||
return LmodContext(make_configuration(spec, module_set_name, explicit))
|
||||
|
||||
|
||||
def guess_core_compilers(name, store=False) -> List[spack.spec.Spec]:
|
||||
def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]:
|
||||
"""Guesses the list of core compilers installed in the system.
|
||||
|
||||
Args:
|
||||
@@ -71,12 +70,16 @@ def guess_core_compilers(name, store=False) -> List[spack.spec.Spec]:
|
||||
List of found core compilers
|
||||
"""
|
||||
core_compilers = []
|
||||
for compiler in spack.compilers.config.all_compilers():
|
||||
for compiler in spack.compilers.all_compilers():
|
||||
try:
|
||||
cc_dir = pathlib.Path(compiler.package.cc).parent
|
||||
is_system_compiler = str(cc_dir) in spack.util.environment.SYSTEM_DIRS
|
||||
# A compiler is considered to be a core compiler if any of the
|
||||
# C, C++ or Fortran compilers reside in a system directory
|
||||
is_system_compiler = any(
|
||||
os.path.dirname(getattr(compiler, x, "")) in spack.util.environment.SYSTEM_DIRS
|
||||
for x in ("cc", "cxx", "f77", "fc")
|
||||
)
|
||||
if is_system_compiler:
|
||||
core_compilers.append(compiler)
|
||||
core_compilers.append(compiler.spec)
|
||||
except (KeyError, TypeError, AttributeError):
|
||||
continue
|
||||
|
||||
@@ -98,32 +101,18 @@ class LmodConfiguration(BaseConfiguration):
|
||||
|
||||
default_projections = {"all": "{name}/{version}"}
|
||||
|
||||
def __init__(self, spec: spack.spec.Spec, module_set_name: str, explicit: bool) -> None:
|
||||
super().__init__(spec, module_set_name, explicit)
|
||||
|
||||
# FIXME (compiler as nodes): make this a bit more robust
|
||||
candidates = collections.defaultdict(list)
|
||||
for node in spec.traverse(deptype=("link", "run")):
|
||||
candidates["c"].extend(node.dependencies(virtuals="c"))
|
||||
candidates["cxx"].extend(node.dependencies(virtuals="c"))
|
||||
|
||||
# FIXME (compiler as nodes): decide what to do when we have more than one C compiler
|
||||
if candidates["c"] and len(set(candidates["c"])) == 1:
|
||||
self.compiler = candidates["c"][0]
|
||||
elif not candidates["c"]:
|
||||
self.compiler = None
|
||||
|
||||
@property
|
||||
def core_compilers(self) -> List[spack.spec.Spec]:
|
||||
def core_compilers(self) -> List[spack.spec.CompilerSpec]:
|
||||
"""Returns the list of "Core" compilers
|
||||
|
||||
Raises:
|
||||
CoreCompilersNotFoundError: if the key was not specified in the configuration file or
|
||||
the sequence is empty
|
||||
CoreCompilersNotFoundError: if the key was not
|
||||
specified in the configuration file or the sequence
|
||||
is empty
|
||||
"""
|
||||
compilers = []
|
||||
for c in configuration(self.name).get("core_compilers", []):
|
||||
compilers.extend(spack.spec.Spec(f"%{c}").dependencies())
|
||||
compilers = [
|
||||
spack.spec.CompilerSpec(c) for c in configuration(self.name).get("core_compilers", [])
|
||||
]
|
||||
|
||||
if not compilers:
|
||||
compilers = guess_core_compilers(self.name, store=True)
|
||||
@@ -172,15 +161,12 @@ def hierarchy_tokens(self):
|
||||
@property
|
||||
@lang.memoized
|
||||
def requires(self):
|
||||
"""Returns a dictionary mapping all the requirements of this spec to the actual provider.
|
||||
|
||||
The 'compiler' key is always present among the requirements.
|
||||
"""Returns a dictionary mapping all the requirements of this spec
|
||||
to the actual provider. 'compiler' is always present among the
|
||||
requirements.
|
||||
"""
|
||||
# If it's a core_spec, lie and say it requires a core compiler
|
||||
if (
|
||||
any(self.spec.satisfies(core_spec) for core_spec in self.core_specs)
|
||||
or self.compiler is None
|
||||
):
|
||||
if any(self.spec.satisfies(core_spec) for core_spec in self.core_specs):
|
||||
return {"compiler": self.core_compilers[0]}
|
||||
|
||||
hierarchy_filter_list = []
|
||||
@@ -191,8 +177,7 @@ def requires(self):
|
||||
|
||||
# Keep track of the requirements that this package has in terms
|
||||
# of virtual packages that participate in the hierarchical structure
|
||||
|
||||
requirements = {"compiler": self.compiler}
|
||||
requirements = {"compiler": self.spec.compiler}
|
||||
# For each virtual dependency in the hierarchy
|
||||
for x in self.hierarchy_tokens:
|
||||
# Skip anything filtered for this spec
|
||||
@@ -215,17 +200,17 @@ def provides(self):
|
||||
# virtual dependencies in spack
|
||||
|
||||
# If it is in the list of supported compilers family -> compiler
|
||||
if self.spec.name in spack.compilers.config.supported_compilers():
|
||||
provides["compiler"] = spack.spec.Spec(self.spec.format("{name}{@versions}"))
|
||||
elif self.spec.name in spack.compilers.config.package_name_to_compiler_name:
|
||||
if self.spec.name in spack.compilers.supported_compilers():
|
||||
provides["compiler"] = spack.spec.CompilerSpec(self.spec.format("{name}{@versions}"))
|
||||
elif self.spec.name in spack.compilers.package_name_to_compiler_name:
|
||||
# If it is the package for a supported compiler, but of a different name
|
||||
cname = spack.compilers.config.package_name_to_compiler_name[self.spec.name]
|
||||
provides["compiler"] = spack.spec.Spec(cname, self.spec.versions)
|
||||
cname = spack.compilers.package_name_to_compiler_name[self.spec.name]
|
||||
provides["compiler"] = spack.spec.CompilerSpec(cname, self.spec.versions)
|
||||
|
||||
# All the other tokens in the hierarchy must be virtual dependencies
|
||||
for x in self.hierarchy_tokens:
|
||||
if self.spec.package.provides(x):
|
||||
provides[x] = self.spec
|
||||
provides[x] = self.spec[x]
|
||||
return provides
|
||||
|
||||
@property
|
||||
@@ -316,10 +301,12 @@ def path_part_fmt(token):
|
||||
|
||||
# If we are dealing with a core compiler, return 'Core'
|
||||
core_compilers = self.conf.core_compilers
|
||||
if name == "compiler" and any(spack.spec.Spec(value).satisfies(c) for c in core_compilers):
|
||||
if name == "compiler" and any(
|
||||
spack.spec.CompilerSpec(value).satisfies(c) for c in core_compilers
|
||||
):
|
||||
return "Core"
|
||||
|
||||
# Spec does not have a hash, as we are not allowed to
|
||||
# CompilerSpec does not have a hash, as we are not allowed to
|
||||
# use different flavors of the same compiler
|
||||
if name == "compiler":
|
||||
return path_part_fmt(token=value)
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
from llnl.util.lang import classproperty, memoized
|
||||
from llnl.util.link_tree import LinkTree
|
||||
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.dependency
|
||||
import spack.deptypes as dt
|
||||
@@ -51,7 +52,6 @@
|
||||
import spack.util.path
|
||||
import spack.util.web
|
||||
import spack.variant
|
||||
from spack.compilers.adaptor import DeprecatedCompiler
|
||||
from spack.error import InstallError, NoURLError, PackageError
|
||||
from spack.filesystem_view import YamlFilesystemView
|
||||
from spack.solver.version_order import concretization_version_order
|
||||
@@ -64,9 +64,10 @@
|
||||
]
|
||||
FLAG_HANDLER_TYPE = Callable[[str, Iterable[str]], FLAG_HANDLER_RETURN_TYPE]
|
||||
|
||||
#: Allowed URL schemes for spack packages
|
||||
"""Allowed URL schemes for spack packages."""
|
||||
_ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"]
|
||||
|
||||
|
||||
#: Filename for the Spack build/install log.
|
||||
_spack_build_logfile = "spack-build-out.txt"
|
||||
|
||||
@@ -577,8 +578,6 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta):
|
||||
|
||||
"""
|
||||
|
||||
compiler = DeprecatedCompiler()
|
||||
|
||||
#
|
||||
# These are default values for instance variables.
|
||||
#
|
||||
@@ -1495,6 +1494,15 @@ def prefix(self):
|
||||
def home(self):
|
||||
return self.prefix
|
||||
|
||||
@property # type: ignore[misc]
|
||||
@memoized
|
||||
def compiler(self):
|
||||
"""Get the spack.compiler.Compiler object used to build this package"""
|
||||
if not self.spec.concrete:
|
||||
raise ValueError("Can only get a compiler for a concrete package.")
|
||||
|
||||
return spack.compilers.compiler_for_spec(self.spec.compiler, self.spec.architecture)
|
||||
|
||||
def url_version(self, version):
|
||||
"""
|
||||
Given a version, this returns a string that should be substituted
|
||||
@@ -1606,7 +1614,7 @@ def do_stage(self, mirror_only=False):
|
||||
self.stage.create()
|
||||
|
||||
# Fetch/expand any associated code.
|
||||
if self.has_code and not self.spec.external:
|
||||
if self.has_code:
|
||||
self.do_fetch(mirror_only)
|
||||
self.stage.expand_archive()
|
||||
else:
|
||||
@@ -1936,14 +1944,17 @@ def _resource_stage(self, resource):
|
||||
return resource_stage_folder
|
||||
|
||||
def do_test(self, dirty=False, externals=False):
|
||||
if self.test_requires_compiler and not any(
|
||||
lang in self.spec for lang in ("c", "cxx", "fortran")
|
||||
):
|
||||
tty.error(
|
||||
f"Skipping tests for package {self.spec}, since a compiler is required, "
|
||||
f"but not available"
|
||||
if self.test_requires_compiler:
|
||||
compilers = spack.compilers.compilers_for_spec(
|
||||
self.spec.compiler, arch_spec=self.spec.architecture
|
||||
)
|
||||
return
|
||||
if not compilers:
|
||||
tty.error(
|
||||
"Skipping tests for package %s\n"
|
||||
% self.spec.format("{name}-{version}-{hash:7}")
|
||||
+ "Package test requires missing compiler %s" % self.spec.compiler
|
||||
)
|
||||
return
|
||||
|
||||
kwargs = {
|
||||
"dirty": dirty,
|
||||
|
||||
@@ -375,11 +375,12 @@ def all_specs(self) -> List["spack.spec.Spec"]:
|
||||
class SpecNodeParser:
|
||||
"""Parse a single spec node from a stream of tokens"""
|
||||
|
||||
__slots__ = "ctx", "has_version", "literal_str"
|
||||
__slots__ = "ctx", "has_compiler", "has_version", "literal_str"
|
||||
|
||||
def __init__(self, ctx, literal_str):
|
||||
self.ctx = ctx
|
||||
self.literal_str = literal_str
|
||||
self.has_compiler = False
|
||||
self.has_version = False
|
||||
|
||||
def parse(
|
||||
@@ -426,24 +427,23 @@ def add_flag(name: str, value: str, propagate: bool):
|
||||
raise_parsing_error(str(e), e)
|
||||
|
||||
while True:
|
||||
if self.ctx.accept(TokenType.COMPILER) or self.ctx.accept(
|
||||
TokenType.COMPILER_AND_VERSION
|
||||
):
|
||||
build_dependency = spack.spec.Spec(self.ctx.current_token.value[1:])
|
||||
name_conversion = {
|
||||
"clang": "llvm",
|
||||
"oneapi": "intel-oneapi-compilers",
|
||||
"rocmcc": "llvm-amdgpu",
|
||||
"intel": "intel-oneapi-compiler-classic",
|
||||
"arm": "acfl",
|
||||
}
|
||||
if self.ctx.accept(TokenType.COMPILER):
|
||||
if self.has_compiler:
|
||||
raise_parsing_error("Spec cannot have multiple compilers")
|
||||
|
||||
if build_dependency.name in name_conversion:
|
||||
build_dependency.name = name_conversion[build_dependency.name]
|
||||
compiler_name = self.ctx.current_token.value[1:]
|
||||
initial_spec.compiler = spack.spec.CompilerSpec(compiler_name.strip(), ":")
|
||||
self.has_compiler = True
|
||||
|
||||
initial_spec._add_dependency(
|
||||
build_dependency, depflag=spack.deptypes.BUILD, virtuals=(), direct=True
|
||||
elif self.ctx.accept(TokenType.COMPILER_AND_VERSION):
|
||||
if self.has_compiler:
|
||||
raise_parsing_error("Spec cannot have multiple compilers")
|
||||
|
||||
compiler_name, compiler_version = self.ctx.current_token.value[1:].split("@")
|
||||
initial_spec.compiler = spack.spec.CompilerSpec(
|
||||
compiler_name.strip(), compiler_version
|
||||
)
|
||||
self.has_compiler = True
|
||||
|
||||
elif (
|
||||
self.ctx.accept(TokenType.VERSION_HASH_PAIR)
|
||||
|
||||
@@ -240,10 +240,9 @@ def from_json(stream, repository):
|
||||
providers = data["provider_index"]["providers"]
|
||||
index.providers = _transform(
|
||||
providers,
|
||||
# FIXME (compiler as nodes): avoid to hard-code the Specfile version
|
||||
lambda vpkg, plist: (
|
||||
spack.spec.SpecfileV5.from_node_dict(vpkg),
|
||||
set(spack.spec.SpecfileV5.from_node_dict(p) for p in plist),
|
||||
spack.spec.SpecfileV4.from_node_dict(vpkg),
|
||||
set(spack.spec.SpecfileV4.from_node_dict(p) for p in plist),
|
||||
),
|
||||
)
|
||||
return index
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import macholib.mach_o
|
||||
import macholib.MachO
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.lang import memoized
|
||||
@@ -275,10 +276,10 @@ def modify_macho_object(cur_path, rpaths, deps, idpath, paths_to_paths):
|
||||
|
||||
# Deduplicate and flatten
|
||||
args = list(itertools.chain.from_iterable(llnl.util.lang.dedupe(args)))
|
||||
install_name_tool = executable.Executable("install_name_tool")
|
||||
if args:
|
||||
args.append(str(cur_path))
|
||||
install_name_tool = executable.Executable("install_name_tool")
|
||||
install_name_tool(*args)
|
||||
with fs.edit_in_place_through_temporary_file(cur_path) as temp_path:
|
||||
install_name_tool(*args, temp_path)
|
||||
|
||||
|
||||
def macholib_get_paths(cur_path):
|
||||
@@ -717,8 +718,8 @@ def fixup_macos_rpath(root, filename):
|
||||
# No fixes needed
|
||||
return False
|
||||
|
||||
args.append(abspath)
|
||||
executable.Executable("install_name_tool")(*args)
|
||||
with fs.edit_in_place_through_temporary_file(abspath) as temp_path:
|
||||
executable.Executable("install_name_tool")(*args, temp_path)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ def __enter__(self):
|
||||
"packages": [],
|
||||
}
|
||||
spec_record["properties"].append(Property("architecture", input_spec.architecture))
|
||||
spec_record["properties"].append(Property("compiler", input_spec.compiler))
|
||||
self.init_spec_record(input_spec, spec_record)
|
||||
self.specs.append(spec_record)
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ def build_report_for_package(self, report_dir, package, duration):
|
||||
# something went wrong pre-cdash "configure" phase b/c we have an exception and only
|
||||
# "update" was encounterd.
|
||||
# dump the report in the configure line so teams can see what the issue is
|
||||
if len(phases_encountered) == 1 and package.get("exception"):
|
||||
if len(phases_encountered) == 1 and package["exception"]:
|
||||
# TODO this mapping is not ideal since these are pre-configure errors
|
||||
# we need to determine if a more appropriate cdash phase can be utilized
|
||||
# for now we will add a message to the log explaining this
|
||||
|
||||
@@ -88,6 +88,8 @@
|
||||
"strategy": {"type": "string", "enum": ["none", "minimal", "full"]}
|
||||
},
|
||||
},
|
||||
"timeout": {"type": "integer", "minimum": 0},
|
||||
"error_on_timeout": {"type": "boolean"},
|
||||
"os_compatible": {"type": "object", "additionalProperties": {"type": "array"}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -139,15 +139,6 @@
|
||||
},
|
||||
"variants": variants,
|
||||
},
|
||||
"deprecatedProperties": [
|
||||
{
|
||||
"names": ["compiler"],
|
||||
"message": "The packages:all:compiler preference has been deprecated in "
|
||||
"Spack v0.24, and is currently ignored. It will be removed from config in "
|
||||
"Spack v0.26.",
|
||||
"error": False,
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"patternProperties": {
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
import spack
|
||||
import spack.binary_distribution
|
||||
import spack.compilers.config
|
||||
import spack.compilers.flags
|
||||
import spack.compiler
|
||||
import spack.compilers
|
||||
import spack.concretize
|
||||
import spack.config
|
||||
import spack.deptypes as dt
|
||||
import spack.environment as ev
|
||||
@@ -47,7 +48,6 @@
|
||||
import spack.version as vn
|
||||
import spack.version.git_ref_lookup
|
||||
from spack import traverse
|
||||
from spack.compilers.libraries import CompilerPropertyDetector
|
||||
from spack.config import get_mark_from_yaml_data
|
||||
from spack.error import SpecSyntaxError
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
|
||||
TransformFunction = Callable[["spack.spec.Spec", List[AspFunction]], List[AspFunction]]
|
||||
|
||||
#: Enable the addition of a runtime node
|
||||
WITH_RUNTIME = sys.platform != "win32"
|
||||
|
||||
#: Data class that contain configuration on what a
|
||||
#: clingo solve should output.
|
||||
#:
|
||||
@@ -292,11 +295,12 @@ def _create_counter(specs: List[spack.spec.Spec], tests: bool):
|
||||
def all_libcs() -> Set[spack.spec.Spec]:
|
||||
"""Return a set of all libc specs targeted by any configured compiler. If none, fall back to
|
||||
libc determined from the current Python process if dynamically linked."""
|
||||
libcs = set()
|
||||
for c in spack.compilers.config.all_compilers_from(spack.config.CONFIG):
|
||||
candidate = CompilerPropertyDetector(c).default_libc()
|
||||
if candidate is not None:
|
||||
libcs.add(candidate)
|
||||
|
||||
libcs = {
|
||||
c.default_libc
|
||||
for c in spack.compilers.all_compilers_from(spack.config.CONFIG)
|
||||
if c.default_libc
|
||||
}
|
||||
|
||||
if libcs:
|
||||
return libcs
|
||||
@@ -305,7 +309,7 @@ def all_libcs() -> Set[spack.spec.Spec]:
|
||||
return {libc} if libc else set()
|
||||
|
||||
|
||||
def libc_is_compatible(lhs: spack.spec.Spec, rhs: spack.spec.Spec) -> bool:
|
||||
def libc_is_compatible(lhs: spack.spec.Spec, rhs: spack.spec.Spec) -> List[spack.spec.Spec]:
|
||||
return (
|
||||
lhs.name == rhs.name
|
||||
and lhs.external_path == rhs.external_path
|
||||
@@ -318,8 +322,8 @@ def using_libc_compatibility() -> bool:
|
||||
return spack.platforms.host().name == "linux"
|
||||
|
||||
|
||||
def c_compiler_runs(compiler) -> bool:
|
||||
return CompilerPropertyDetector(compiler).compiler_verbose_output() is not None
|
||||
def c_compiler_runs(compiler: spack.compiler.Compiler) -> bool:
|
||||
return compiler.compiler_verbose_output is not None
|
||||
|
||||
|
||||
def extend_flag_list(flag_list, new_flags):
|
||||
@@ -609,12 +613,10 @@ def _external_config_with_implicit_externals(configuration):
|
||||
if not using_libc_compatibility():
|
||||
return packages_yaml
|
||||
|
||||
seen = set()
|
||||
for compiler in spack.compilers.config.all_compilers_from(configuration):
|
||||
libc = CompilerPropertyDetector(compiler).default_libc()
|
||||
if libc and libc not in seen:
|
||||
seen.add(libc)
|
||||
entry = {"spec": f"{libc}", "prefix": libc.external_path}
|
||||
for compiler in spack.compilers.all_compilers_from(configuration):
|
||||
libc = compiler.default_libc
|
||||
if libc:
|
||||
entry = {"spec": f"{libc} %{compiler.spec}", "prefix": libc.external_path}
|
||||
packages_yaml.setdefault(libc.name, {}).setdefault("externals", []).append(entry)
|
||||
return packages_yaml
|
||||
|
||||
@@ -766,6 +768,27 @@ class RequirementRule(NamedTuple):
|
||||
message: Optional[str]
|
||||
|
||||
|
||||
class KnownCompiler(NamedTuple):
|
||||
"""Data class to collect information on compilers"""
|
||||
|
||||
spec: "spack.spec.Spec"
|
||||
os: str
|
||||
target: str
|
||||
available: bool
|
||||
compiler_obj: Optional["spack.compiler.Compiler"]
|
||||
|
||||
def _key(self):
|
||||
return self.spec, self.os, self.target
|
||||
|
||||
def __eq__(self, other: object):
|
||||
if not isinstance(other, KnownCompiler):
|
||||
return NotImplemented
|
||||
return self._key() == other._key()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._key())
|
||||
|
||||
|
||||
class PyclingoDriver:
|
||||
def __init__(self, cores=True):
|
||||
"""Driver for the Python clingo interface.
|
||||
@@ -863,7 +886,22 @@ def on_model(model):
|
||||
solve_kwargs["on_unsat"] = cores.append
|
||||
|
||||
timer.start("solve")
|
||||
solve_result = self.control.solve(**solve_kwargs)
|
||||
time_limit = spack.config.CONFIG.get("concretizer:timeout", -1)
|
||||
error_on_timeout = spack.config.CONFIG.get("concretizer:error_on_timeout", True)
|
||||
# Spack uses 0 to set no time limit, clingo API uses -1
|
||||
if time_limit == 0:
|
||||
time_limit = -1
|
||||
with self.control.solve(**solve_kwargs, async_=True) as handle:
|
||||
finished = handle.wait(time_limit)
|
||||
if not finished:
|
||||
specs_str = ", ".join(llnl.util.lang.elide_list([str(s) for s in specs], 4))
|
||||
header = f"Spack is taking more than {time_limit} seconds to solve for {specs_str}"
|
||||
if error_on_timeout:
|
||||
raise UnsatisfiableSpecError(f"{header}, stopping concretization")
|
||||
warnings.warn(f"{header}, using the best configuration found so far")
|
||||
handle.cancel()
|
||||
|
||||
solve_result = handle.get()
|
||||
timer.stop("solve")
|
||||
|
||||
# once done, construct the solve result
|
||||
@@ -1047,13 +1085,13 @@ class SourceContext:
|
||||
Facts generated for the spec may include this context.
|
||||
"""
|
||||
|
||||
def __init__(self, *, source: Optional[str] = None):
|
||||
def __init__(self):
|
||||
# This can be "literal" for constraints that come from a user
|
||||
# spec (e.g. from the command line); it can be the output of
|
||||
# `ConstraintOrigin.append_type_suffix`; the default is "none"
|
||||
# (which means it isn't important to keep track of the source
|
||||
# in that case).
|
||||
self.source = "none" if source is None else source
|
||||
self.source = "none"
|
||||
|
||||
|
||||
class ConditionIdContext(SourceContext):
|
||||
@@ -1246,6 +1284,16 @@ def conflict_rules(self, pkg):
|
||||
)
|
||||
self.gen.newline()
|
||||
|
||||
def package_languages(self, pkg):
|
||||
for when_spec, languages in pkg.languages.items():
|
||||
condition_msg = f"{pkg.name} needs the {', '.join(sorted(languages))} language"
|
||||
if when_spec != spack.spec.Spec():
|
||||
condition_msg += f" when {when_spec}"
|
||||
condition_id = self.condition(when_spec, required_name=pkg.name, msg=condition_msg)
|
||||
for language in sorted(languages):
|
||||
self.gen.fact(fn.pkg_fact(pkg.name, fn.language(condition_id, language)))
|
||||
self.gen.newline()
|
||||
|
||||
def config_compatible_os(self):
|
||||
"""Facts about compatible os's specified in configs"""
|
||||
self.gen.h2("Compatible OS from concretizer config file")
|
||||
@@ -1255,6 +1303,34 @@ def config_compatible_os(self):
|
||||
self.gen.fact(fn.os_compatible(recent, old))
|
||||
self.gen.newline()
|
||||
|
||||
def compiler_facts(self):
|
||||
"""Facts about available compilers."""
|
||||
|
||||
self.gen.h2("Available compilers")
|
||||
for compiler_id, compiler in enumerate(self.possible_compilers):
|
||||
self.gen.fact(fn.compiler_id(compiler_id))
|
||||
self.gen.fact(fn.compiler_name(compiler_id, compiler.spec.name))
|
||||
self.gen.fact(fn.compiler_version(compiler_id, compiler.spec.version))
|
||||
|
||||
if compiler.os:
|
||||
self.gen.fact(fn.compiler_os(compiler_id, compiler.os))
|
||||
|
||||
if compiler.target is not None:
|
||||
self.gen.fact(fn.compiler_target(compiler_id, compiler.target))
|
||||
|
||||
if compiler.compiler_obj is not None:
|
||||
c = compiler.compiler_obj
|
||||
for flag_type, flags in c.flags.items():
|
||||
flag_group = " ".join(flags)
|
||||
for flag in flags:
|
||||
self.gen.fact(fn.compiler_flag(compiler_id, flag_type, flag, flag_group))
|
||||
|
||||
if compiler.available:
|
||||
self.gen.fact(fn.compiler_available(compiler_id))
|
||||
|
||||
self.gen.fact(fn.compiler_weight(compiler_id, compiler_id))
|
||||
self.gen.newline()
|
||||
|
||||
def package_requirement_rules(self, pkg):
|
||||
parser = RequirementParser(spack.config.CONFIG)
|
||||
self.emit_facts_from_requirement_rules(parser.rules(pkg))
|
||||
@@ -1269,6 +1345,9 @@ def pkg_rules(self, pkg, tests):
|
||||
self.pkg_version_rules(pkg)
|
||||
self.gen.newline()
|
||||
|
||||
# languages
|
||||
self.package_languages(pkg)
|
||||
|
||||
# variants
|
||||
self.variant_rules(pkg)
|
||||
|
||||
@@ -1285,6 +1364,12 @@ def pkg_rules(self, pkg, tests):
|
||||
if self.enable_splicing:
|
||||
self.package_splice_rules(pkg)
|
||||
|
||||
# virtual preferences
|
||||
self.virtual_preferences(
|
||||
pkg.name,
|
||||
lambda v, p, i: self.gen.fact(fn.pkg_fact(pkg.name, fn.provider_preference(v, p, i))),
|
||||
)
|
||||
|
||||
self.package_requirement_rules(pkg)
|
||||
|
||||
# trigger and effect tables
|
||||
@@ -1814,6 +1899,8 @@ def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
|
||||
|
||||
def external_packages(self):
|
||||
"""Facts on external packages, from packages.yaml and implicit externals."""
|
||||
packages_yaml = _external_config_with_implicit_externals(spack.config.CONFIG)
|
||||
|
||||
self.gen.h1("External packages")
|
||||
spec_filters = []
|
||||
concretizer_yaml = spack.config.get("concretizer")
|
||||
@@ -1821,6 +1908,7 @@ def external_packages(self):
|
||||
if isinstance(reuse_yaml, typing.Mapping):
|
||||
default_include = reuse_yaml.get("include", [])
|
||||
default_exclude = reuse_yaml.get("exclude", [])
|
||||
libc_externals = list(all_libcs())
|
||||
for source in reuse_yaml.get("from", []):
|
||||
if source["type"] != "external":
|
||||
continue
|
||||
@@ -1828,7 +1916,7 @@ def external_packages(self):
|
||||
include = source.get("include", default_include)
|
||||
if include:
|
||||
# Since libcs are implicit externals, we need to implicitly include them
|
||||
include = include + self.libcs
|
||||
include = include + libc_externals
|
||||
exclude = source.get("exclude", default_exclude)
|
||||
spec_filters.append(
|
||||
SpecFilter(
|
||||
@@ -1839,7 +1927,6 @@ def external_packages(self):
|
||||
)
|
||||
)
|
||||
|
||||
packages_yaml = _external_config_with_implicit_externals(spack.config.CONFIG)
|
||||
for pkg_name, data in packages_yaml.items():
|
||||
if pkg_name == "all":
|
||||
continue
|
||||
@@ -1848,15 +1935,10 @@ def external_packages(self):
|
||||
if pkg_name not in self.pkgs:
|
||||
continue
|
||||
|
||||
# This package is not in the possible dependencies
|
||||
if pkg_name not in self.pkgs:
|
||||
continue
|
||||
|
||||
# This package is not among possible dependencies
|
||||
if pkg_name not in self.pkgs:
|
||||
continue
|
||||
|
||||
self.gen.h2(f"External package: {pkg_name}")
|
||||
# Check if the external package is buildable. If it is
|
||||
# not then "external(<pkg>)" is a fact, unless we can
|
||||
# reuse an already installed spec.
|
||||
@@ -2073,6 +2155,28 @@ def _spec_clauses(
|
||||
else:
|
||||
clauses.append(f.variant_value(spec.name, vname, value))
|
||||
|
||||
# compiler and compiler version
|
||||
if spec.compiler:
|
||||
clauses.append(f.node_compiler(spec.name, spec.compiler.name))
|
||||
|
||||
if spec.compiler.concrete:
|
||||
clauses.append(
|
||||
f.node_compiler_version(spec.name, spec.compiler.name, spec.compiler.version)
|
||||
)
|
||||
|
||||
elif spec.compiler.versions and spec.compiler.versions != vn.any_version:
|
||||
# The condition above emits a facts only if we have an actual constraint
|
||||
# on the compiler version, and avoids emitting them if any version is fine
|
||||
clauses.append(
|
||||
fn.attr(
|
||||
"node_compiler_version_satisfies",
|
||||
spec.name,
|
||||
spec.compiler.name,
|
||||
spec.compiler.versions,
|
||||
)
|
||||
)
|
||||
self.compiler_version_constraints.add(spec.compiler)
|
||||
|
||||
# compiler flags
|
||||
source = context.source if context else "none"
|
||||
for flag_type, flags in spec.compiler_flags.items():
|
||||
@@ -2110,7 +2214,6 @@ def _spec_clauses(
|
||||
|
||||
# If the spec is external and concrete, we allow all the libcs on the system
|
||||
if spec.external and spec.concrete and using_libc_compatibility():
|
||||
clauses.append(fn.attr("needs_libc", spec.name))
|
||||
for libc in self.libcs:
|
||||
clauses.append(fn.attr("compatible_libc", spec.name, libc.name, libc.version))
|
||||
|
||||
@@ -2124,17 +2227,11 @@ def _spec_clauses(
|
||||
# GCC runtime is solved again by clingo, even on concrete specs, to give
|
||||
# the possibility to reuse specs built against a different runtime.
|
||||
if dep.name == "gcc-runtime":
|
||||
clauses.append(
|
||||
fn.attr("compatible_runtime", spec.name, dep.name, f"{dep.version}:")
|
||||
)
|
||||
constraint_spec = spack.spec.Spec(f"{dep.name}@{dep.version}")
|
||||
self.spec_versions(constraint_spec)
|
||||
continue
|
||||
|
||||
# libc is also solved again by clingo, but in this case the compatibility
|
||||
# is not encoded in the parent node - so we need to emit explicit facts
|
||||
if "libc" in dspec.virtuals:
|
||||
clauses.append(fn.attr("needs_libc", spec.name))
|
||||
for libc in self.libcs:
|
||||
if libc_is_compatible(libc, dep):
|
||||
clauses.append(
|
||||
@@ -2169,23 +2266,15 @@ def _spec_clauses(
|
||||
# if it's concrete, then the hashes above take care of dependency
|
||||
# constraints, but expand the hashes if asked for.
|
||||
if not spec.concrete or expand_hashes:
|
||||
dependency_clauses = self._spec_clauses(
|
||||
dep,
|
||||
body=body,
|
||||
expand_hashes=expand_hashes,
|
||||
concrete_build_deps=concrete_build_deps,
|
||||
context=context,
|
||||
clauses.extend(
|
||||
self._spec_clauses(
|
||||
dep,
|
||||
body=body,
|
||||
expand_hashes=expand_hashes,
|
||||
concrete_build_deps=concrete_build_deps,
|
||||
context=context,
|
||||
)
|
||||
)
|
||||
if dspec.depflag == dt.BUILD:
|
||||
clauses.append(fn.attr("depends_on", spec.name, dep.name, "build"))
|
||||
if body is False:
|
||||
for clause in dependency_clauses:
|
||||
clause.name = "build_requirement"
|
||||
clauses.append(fn.attr("build_requirement", spec.name, clause))
|
||||
else:
|
||||
clauses.extend(dependency_clauses)
|
||||
else:
|
||||
clauses.extend(dependency_clauses)
|
||||
|
||||
return clauses
|
||||
|
||||
@@ -2283,9 +2372,7 @@ def _supported_targets(self, compiler_name, compiler_version, targets):
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
target.optimization_flags(
|
||||
compiler_name, compiler_version.dotted_numeric_string
|
||||
)
|
||||
target.optimization_flags(compiler_name, str(compiler_version))
|
||||
supported.append(target)
|
||||
except archspec.cpu.UnsupportedMicroarchitecture:
|
||||
continue
|
||||
@@ -2382,21 +2469,39 @@ def target_defaults(self, specs):
|
||||
candidate_targets.append(ancestor)
|
||||
|
||||
best_targets = {uarch.family.name}
|
||||
for compiler in self.possible_compilers:
|
||||
for compiler_id, known_compiler in enumerate(self.possible_compilers):
|
||||
if not known_compiler.available:
|
||||
continue
|
||||
|
||||
compiler = known_compiler.compiler_obj
|
||||
# Stub support for cross-compilation, to be expanded later
|
||||
if known_compiler.target is not None and compiler.target not in (
|
||||
str(uarch.family),
|
||||
"any",
|
||||
):
|
||||
self.gen.fact(fn.compiler_supports_target(compiler_id, compiler.target))
|
||||
self.gen.newline()
|
||||
continue
|
||||
|
||||
supported = self._supported_targets(compiler.name, compiler.version, candidate_targets)
|
||||
|
||||
# If we can't find supported targets it may be due to custom
|
||||
# versions in the spec, e.g. gcc@foo. Try to match the
|
||||
# real_version from the compiler object to get more accurate
|
||||
# results.
|
||||
if not supported:
|
||||
supported = self._supported_targets(
|
||||
compiler.name, compiler.real_version, candidate_targets
|
||||
)
|
||||
|
||||
if not supported:
|
||||
continue
|
||||
|
||||
for target in supported:
|
||||
best_targets.add(target.name)
|
||||
self.gen.fact(
|
||||
fn.compiler_supports_target(compiler.name, compiler.version, target.name)
|
||||
)
|
||||
self.gen.fact(fn.compiler_supports_target(compiler_id, target.name))
|
||||
|
||||
self.gen.fact(
|
||||
fn.compiler_supports_target(compiler.name, compiler.version, uarch.family.name)
|
||||
)
|
||||
self.gen.fact(fn.compiler_supports_target(compiler_id, uarch.family.name))
|
||||
self.gen.newline()
|
||||
|
||||
i = 0 # TODO compute per-target offset?
|
||||
@@ -2613,6 +2718,8 @@ def setup(
|
||||
self.explicitly_required_namespaces[node.name] = node.namespace
|
||||
|
||||
self.gen = ProblemInstanceBuilder()
|
||||
compiler_parser = CompilerParser(configuration=spack.config.CONFIG).with_input_specs(specs)
|
||||
|
||||
if using_libc_compatibility():
|
||||
for libc in self.libcs:
|
||||
self.gen.fact(fn.host_libc(libc.name, libc.version))
|
||||
@@ -2641,11 +2748,11 @@ def setup(
|
||||
if reuse:
|
||||
self.gen.fact(fn.optimize_for_reuse())
|
||||
for reusable_spec in reuse:
|
||||
compiler_parser.add_compiler_from_concrete_spec(reusable_spec)
|
||||
self.register_concrete_spec(reusable_spec, self.pkgs)
|
||||
self.concrete_specs()
|
||||
|
||||
_ = spack.compilers.config.all_compilers(init_config=True)
|
||||
self.possible_compilers = possible_compilers(configuration=spack.config.CONFIG)
|
||||
self.possible_compilers = compiler_parser.possible_compilers()
|
||||
|
||||
self.gen.h1("Generic statements on possible packages")
|
||||
node_counter.possible_packages_facts(self.gen, fn)
|
||||
@@ -2657,6 +2764,7 @@ def setup(
|
||||
|
||||
self.gen.h1("General Constraints")
|
||||
self.config_compatible_os()
|
||||
self.compiler_facts()
|
||||
|
||||
# architecture defaults
|
||||
self.platform_defaults()
|
||||
@@ -2710,8 +2818,9 @@ def setup(
|
||||
self.gen.h1("Variant Values defined in specs")
|
||||
self.define_variant_values()
|
||||
|
||||
self.gen.h1("Runtimes")
|
||||
self.define_runtime_constraints()
|
||||
if WITH_RUNTIME:
|
||||
self.gen.h1("Runtimes")
|
||||
self.define_runtime_constraints()
|
||||
|
||||
self.gen.h1("Version Constraints")
|
||||
self.collect_virtual_constraints()
|
||||
@@ -2751,37 +2860,35 @@ def define_runtime_constraints(self):
|
||||
recorder = RuntimePropertyRecorder(self)
|
||||
|
||||
for compiler in self.possible_compilers:
|
||||
compiler_with_different_cls_names = {
|
||||
"oneapi": "intel-oneapi-compilers",
|
||||
"clang": "llvm",
|
||||
}
|
||||
compiler_cls_name = compiler_with_different_cls_names.get(
|
||||
compiler.spec.name, compiler.spec.name
|
||||
)
|
||||
try:
|
||||
compiler_cls = spack.repo.PATH.get_pkg_class(compiler.name)
|
||||
compiler_cls = spack.repo.PATH.get_pkg_class(compiler_cls_name)
|
||||
if hasattr(compiler_cls, "runtime_constraints"):
|
||||
compiler_cls.runtime_constraints(spec=compiler.spec, pkg=recorder)
|
||||
except spack.repo.UnknownPackageError:
|
||||
pass
|
||||
else:
|
||||
if hasattr(compiler_cls, "runtime_constraints"):
|
||||
compiler_cls.runtime_constraints(spec=compiler, pkg=recorder)
|
||||
# Inject default flags for compilers
|
||||
recorder("*").default_flags(compiler)
|
||||
|
||||
if not using_libc_compatibility():
|
||||
# Inject libc from available compilers, on Linux
|
||||
if not compiler.available:
|
||||
continue
|
||||
|
||||
current_libc = CompilerPropertyDetector(compiler).default_libc()
|
||||
# If this is a compiler yet to be built infer libc from the Python process
|
||||
# FIXME (compiler as nodes): recover this use case
|
||||
# if not current_libc and compiler.compiler_obj.cc is None:
|
||||
# current_libc = spack.util.libc.libc_from_current_python_process()
|
||||
current_libc = compiler.compiler_obj.default_libc
|
||||
|
||||
if current_libc:
|
||||
if using_libc_compatibility() and current_libc:
|
||||
recorder("*").depends_on(
|
||||
"libc",
|
||||
when=f"%{compiler.name}@{compiler.versions}",
|
||||
type="link",
|
||||
description=f"Add libc when using {compiler}",
|
||||
"libc", when=f"%{compiler.spec}", type="link", description="Add libc"
|
||||
)
|
||||
recorder("*").depends_on(
|
||||
str(current_libc),
|
||||
when=f"%{compiler.name}@{compiler.versions}",
|
||||
when=f"%{compiler.spec}",
|
||||
type="link",
|
||||
description=f"Libc is {current_libc} when using {compiler}",
|
||||
description="Add libc",
|
||||
)
|
||||
|
||||
recorder.consume_facts()
|
||||
@@ -2817,9 +2924,6 @@ def literal_specs(self, specs):
|
||||
# These facts are needed to compute the "condition_set" of the root
|
||||
pkg_name = clause.args[1]
|
||||
self.gen.fact(fn.mentioned_in_literal(trigger_id, root_name, pkg_name))
|
||||
elif clause_name == "depends_on":
|
||||
pkg_name = clause.args[2]
|
||||
self.gen.fact(fn.mentioned_in_literal(trigger_id, root_name, pkg_name))
|
||||
|
||||
requirements.append(fn.attr("virtual_root" if spec.virtual else "root", spec.name))
|
||||
cache[imposed_spec_key] = (effect_id, requirements)
|
||||
@@ -2916,6 +3020,8 @@ class _Head:
|
||||
node_os = fn.attr("node_os_set")
|
||||
node_target = fn.attr("node_target_set")
|
||||
variant_value = fn.attr("variant_set")
|
||||
node_compiler = fn.attr("node_compiler_set")
|
||||
node_compiler_version = fn.attr("node_compiler_version_set")
|
||||
node_flag = fn.attr("node_flag_set")
|
||||
propagate = fn.attr("propagate")
|
||||
|
||||
@@ -2930,6 +3036,8 @@ class _Body:
|
||||
node_os = fn.attr("node_os")
|
||||
node_target = fn.attr("node_target")
|
||||
variant_value = fn.attr("variant_value")
|
||||
node_compiler = fn.attr("node_compiler")
|
||||
node_compiler_version = fn.attr("node_compiler_version")
|
||||
node_flag = fn.attr("node_flag")
|
||||
propagate = fn.attr("propagate")
|
||||
|
||||
@@ -3159,66 +3267,119 @@ def reject_requirement_constraint(
|
||||
self, pkg_name: str, *, constraint: spack.spec.Spec, kind: RequirementKind
|
||||
) -> bool:
|
||||
"""Returns True if a requirement constraint should be rejected"""
|
||||
# If it's a specific package requirement, it's never rejected
|
||||
if kind != RequirementKind.DEFAULT:
|
||||
return False
|
||||
|
||||
# Reject default requirements for runtimes and compilers
|
||||
if pkg_name in spack.repo.PATH.packages_with_tags("runtime"):
|
||||
return True
|
||||
|
||||
if pkg_name in spack.repo.PATH.packages_with_tags("compiler"):
|
||||
return True
|
||||
|
||||
# Requirements under all: are applied only if they are satisfiable considering only
|
||||
# package rules, so e.g. variants must exist etc. Otherwise, they are rejected.
|
||||
try:
|
||||
s = spack.spec.Spec(pkg_name)
|
||||
s.constrain(constraint)
|
||||
s.validate_or_raise()
|
||||
except spack.error.SpackError as e:
|
||||
tty.debug(
|
||||
f"[SETUP] Rejecting the default '{constraint}' requirement "
|
||||
f"on '{pkg_name}': {str(e)}",
|
||||
level=2,
|
||||
)
|
||||
return True
|
||||
if kind == RequirementKind.DEFAULT:
|
||||
# Requirements under all: are applied only if they are satisfiable considering only
|
||||
# package rules, so e.g. variants must exist etc. Otherwise, they are rejected.
|
||||
try:
|
||||
s = spack.spec.Spec(pkg_name)
|
||||
s.constrain(constraint)
|
||||
s.validate_or_raise()
|
||||
except spack.error.SpackError as e:
|
||||
tty.debug(
|
||||
f"[SETUP] Rejecting the default '{constraint}' requirement "
|
||||
f"on '{pkg_name}': {str(e)}",
|
||||
level=2,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def possible_compilers(*, configuration) -> List["spack.spec.Spec"]:
|
||||
result = set()
|
||||
for c in spack.compilers.config.all_compilers_from(configuration):
|
||||
# FIXME (compiler as nodes): Discard early specs that are not marked for this target?
|
||||
class CompilerParser:
|
||||
"""Parses configuration files, and builds a list of possible compilers for the solve."""
|
||||
|
||||
if using_libc_compatibility() and not c_compiler_runs(c):
|
||||
try:
|
||||
compiler = c.extra_attributes["compilers"]["c"]
|
||||
def __init__(self, configuration) -> None:
|
||||
self.compilers: Set[KnownCompiler] = set()
|
||||
for c in spack.compilers.all_compilers_from(configuration):
|
||||
if using_libc_compatibility() and not c_compiler_runs(c):
|
||||
tty.debug(
|
||||
f"the C compiler {compiler} does not exist, or does not run correctly."
|
||||
f" The compiler {c} will not be used during concretization."
|
||||
f"the C compiler {c.cc} does not exist, or does not run correctly."
|
||||
f" The compiler {c.spec} will not be used during concretization."
|
||||
)
|
||||
except KeyError:
|
||||
tty.debug(f"the spec {c} does not provide a C compiler.")
|
||||
continue
|
||||
|
||||
continue
|
||||
if using_libc_compatibility() and not c.default_libc:
|
||||
warnings.warn(
|
||||
f"cannot detect libc from {c.spec}. The compiler will not be used "
|
||||
f"during concretization."
|
||||
)
|
||||
continue
|
||||
|
||||
if using_libc_compatibility() and not CompilerPropertyDetector(c).default_libc():
|
||||
warnings.warn(
|
||||
f"cannot detect libc from {c}. The compiler will not be used "
|
||||
f"during concretization."
|
||||
target = c.target if c.target != "any" else None
|
||||
candidate = KnownCompiler(
|
||||
spec=c.spec, os=c.operating_system, target=target, available=True, compiler_obj=c
|
||||
)
|
||||
continue
|
||||
if candidate in self.compilers:
|
||||
warnings.warn(
|
||||
f"duplicate found for {c.spec} on {c.operating_system}/{c.target}. "
|
||||
f"Edit your compilers.yaml configuration to remove it."
|
||||
)
|
||||
continue
|
||||
|
||||
if c in result:
|
||||
warnings.warn(f"duplicate {c} compiler found. Edit your packages.yaml to remove it.")
|
||||
continue
|
||||
self.compilers.add(candidate)
|
||||
|
||||
result.add(c)
|
||||
def with_input_specs(self, input_specs: List["spack.spec.Spec"]) -> "CompilerParser":
|
||||
"""Accounts for input specs when building the list of possible compilers.
|
||||
|
||||
result = list(result)
|
||||
result.sort()
|
||||
return result
|
||||
Args:
|
||||
input_specs: specs to be concretized
|
||||
"""
|
||||
strict = spack.concretize.CHECK_COMPILER_EXISTENCE
|
||||
default_os = str(spack.platforms.host().default_os)
|
||||
default_target = str(archspec.cpu.host().family)
|
||||
for s in traverse.traverse_nodes(input_specs):
|
||||
# we don't need to validate compilers for already-built specs
|
||||
if s.concrete or not s.compiler:
|
||||
continue
|
||||
|
||||
version = s.compiler.versions.concrete
|
||||
|
||||
if not version or any(item.spec.satisfies(s.compiler) for item in self.compilers):
|
||||
continue
|
||||
|
||||
# Error when a compiler is not found and strict mode is enabled
|
||||
if strict:
|
||||
raise spack.concretize.UnavailableCompilerVersionError(s.compiler)
|
||||
|
||||
# Make up a compiler matching the input spec. This is for bootstrapping.
|
||||
compiler_cls = spack.compilers.class_for_compiler_name(s.compiler.name)
|
||||
compiler_obj = compiler_cls(
|
||||
s.compiler, operating_system=default_os, target=default_target, paths=[None] * 4
|
||||
)
|
||||
self.compilers.add(
|
||||
KnownCompiler(
|
||||
spec=s.compiler,
|
||||
os=default_os,
|
||||
target=default_target,
|
||||
available=True,
|
||||
compiler_obj=compiler_obj,
|
||||
)
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
def add_compiler_from_concrete_spec(self, spec: "spack.spec.Spec") -> None:
|
||||
"""Account for compilers that are coming from concrete specs, through reuse.
|
||||
|
||||
Args:
|
||||
spec: concrete spec to be reused
|
||||
"""
|
||||
assert spec.concrete, "the spec argument must be concrete"
|
||||
candidate = KnownCompiler(
|
||||
spec=spec.compiler,
|
||||
os=str(spec.architecture.os),
|
||||
target=str(spec.architecture.target.family),
|
||||
available=False,
|
||||
compiler_obj=None,
|
||||
)
|
||||
self.compilers.add(candidate)
|
||||
|
||||
def possible_compilers(self) -> List[KnownCompiler]:
|
||||
# Here we have to sort two times, first sort by name and ascending version
|
||||
result = sorted(self.compilers, key=lambda x: (x.spec.name, x.spec.version), reverse=True)
|
||||
# Then stable sort to prefer available compilers and account for preferences
|
||||
ppk = spack.package_prefs.PackagePrefs("all", "compiler", all=False)
|
||||
result.sort(key=lambda x: (not x.available, ppk(x.spec)))
|
||||
return result
|
||||
|
||||
|
||||
class RuntimePropertyRecorder:
|
||||
@@ -3261,7 +3422,15 @@ def reset(self):
|
||||
"""Resets the current state."""
|
||||
self.current_package = None
|
||||
|
||||
def depends_on(self, dependency_str: str, *, when: str, type: str, description: str) -> None:
|
||||
def depends_on(
|
||||
self,
|
||||
dependency_str: str,
|
||||
*,
|
||||
when: str,
|
||||
type: str,
|
||||
description: str,
|
||||
languages: Optional[List[str]] = None,
|
||||
) -> None:
|
||||
"""Injects conditional dependencies on packages.
|
||||
|
||||
Conditional dependencies can be either "real" packages or virtual dependencies.
|
||||
@@ -3270,6 +3439,7 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
|
||||
dependency_str: the dependency spec to inject
|
||||
when: anonymous condition to be met on a package to have the dependency
|
||||
type: dependency type
|
||||
languages: languages needed by the package for the dependency to be considered
|
||||
description: human-readable description of the rule for adding the dependency
|
||||
"""
|
||||
# TODO: The API for this function is not final, and is still subject to change. At
|
||||
@@ -3285,10 +3455,25 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
|
||||
if dependency_spec.versions != vn.any_version:
|
||||
self._setup.version_constraints.add((dependency_spec.name, dependency_spec.versions))
|
||||
|
||||
body_str, node_variable = self.rule_body_from(when_spec)
|
||||
placeholder = "XXX"
|
||||
node_variable = "node(ID, Package)"
|
||||
when_spec.name = placeholder
|
||||
|
||||
body_clauses = self._setup.spec_clauses(when_spec, body=True)
|
||||
body_str = (
|
||||
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
|
||||
f" not external({node_variable}),\n"
|
||||
f" not runtime(Package)"
|
||||
).replace(f'"{placeholder}"', f"{node_variable}")
|
||||
if languages:
|
||||
body_str += ",\n"
|
||||
for language in languages:
|
||||
body_str += f' attr("language", {node_variable}, "{language}")'
|
||||
|
||||
head_clauses = self._setup.spec_clauses(dependency_spec, body=False)
|
||||
|
||||
runtime_pkg = dependency_spec.name
|
||||
|
||||
is_virtual = head_clauses[0].args[0] == "virtual_node"
|
||||
main_rule = (
|
||||
f"% {description}\n"
|
||||
@@ -3323,38 +3508,6 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
|
||||
|
||||
self.reset()
|
||||
|
||||
@staticmethod
|
||||
def node_for(name: str) -> str:
|
||||
return f'node(ID{name.replace("-", "_")}, "{name}")'
|
||||
|
||||
def rule_body_from(self, when_spec: "spack.spec.Spec") -> Tuple[str, str]:
|
||||
"""Computes the rule body from a "when" spec, and returns it, along with the
|
||||
node variable.
|
||||
"""
|
||||
|
||||
node_placeholder = "XXX"
|
||||
node_variable = "node(ID, Package)"
|
||||
when_substitutions = {}
|
||||
for s in when_spec.traverse(root=False):
|
||||
when_substitutions[f'"{s.name}"'] = self.node_for(s.name)
|
||||
when_spec.name = node_placeholder
|
||||
body_clauses = self._setup.spec_clauses(when_spec, body=True)
|
||||
for clause in body_clauses:
|
||||
if clause.args[0] == "virtual_on_incoming_edges":
|
||||
# Substitute: attr("virtual_on_incoming_edges", ProviderNode, Virtual)
|
||||
# with: attr("virtual_on_edge", ParentNode, ProviderNode, Virtual)
|
||||
# (avoid adding virtuals everywhere, if a single edge needs it)
|
||||
_, provider, virtual = clause.args
|
||||
clause.args = "virtual_on_edge", node_placeholder, provider, virtual
|
||||
body_str = (
|
||||
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
|
||||
f" not external({node_variable}),\n"
|
||||
f" not runtime(Package)"
|
||||
).replace(f'"{node_placeholder}"', f"{node_variable}")
|
||||
for old, replacement in when_substitutions.items():
|
||||
body_str = body_str.replace(old, replacement)
|
||||
return body_str, node_variable
|
||||
|
||||
def requires(self, impose: str, *, when: str):
|
||||
"""Injects conditional requirements on a given package.
|
||||
|
||||
@@ -3369,6 +3522,7 @@ def requires(self, impose: str, *, when: str):
|
||||
when_spec = spack.spec.Spec(f"{self.current_package}{when}")
|
||||
|
||||
assert imposed_spec.versions.concrete, f"{impose} must have a concrete version"
|
||||
assert when_spec.compiler.concrete, f"{when} must have a concrete compiler"
|
||||
|
||||
# Add versions to possible versions
|
||||
for s in (imposed_spec, when_spec):
|
||||
@@ -3389,54 +3543,32 @@ def propagate(self, constraint_str: str, *, when: str):
|
||||
when_spec = spack.spec.Spec(when)
|
||||
assert when_spec.name is None, "only anonymous when specs are accepted"
|
||||
|
||||
when_substitutions = {}
|
||||
for s in when_spec.traverse(root=False):
|
||||
when_substitutions[f'"{s.name}"'] = self.node_for(s.name)
|
||||
placeholder = "XXX"
|
||||
node_variable = "node(ID, Package)"
|
||||
when_spec.name = placeholder
|
||||
|
||||
body_clauses = self._setup.spec_clauses(when_spec, body=True)
|
||||
body_str = (
|
||||
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
|
||||
f" not external({node_variable}),\n"
|
||||
f" not runtime(Package)"
|
||||
).replace(f'"{placeholder}"', f"{node_variable}")
|
||||
|
||||
body_str, node_variable = self.rule_body_from(when_spec)
|
||||
constraint_spec = spack.spec.Spec(constraint_str)
|
||||
assert constraint_spec.name is None, "only anonymous constraint specs are accepted"
|
||||
|
||||
# constraint_spec.name = placeholder
|
||||
constraint_spec.name = placeholder
|
||||
constraint_clauses = self._setup.spec_clauses(constraint_spec, body=False)
|
||||
for clause in constraint_clauses:
|
||||
if clause.args[0] == "node_version_satisfies":
|
||||
self._setup.version_constraints.add(
|
||||
(constraint_spec.name, constraint_spec.versions)
|
||||
)
|
||||
args = f'"{constraint_spec.name}", "{constraint_spec.versions}"'
|
||||
head_str = f"propagate({node_variable}, node_version_satisfies({args}))"
|
||||
if clause.args[0] == "node_compiler_version_satisfies":
|
||||
self._setup.compiler_version_constraints.add(constraint_spec.compiler)
|
||||
args = f'"{constraint_spec.compiler.name}", "{constraint_spec.compiler.versions}"'
|
||||
head_str = f"propagate({node_variable}, node_compiler_version_satisfies({args}))"
|
||||
rule = f"{head_str} :-\n{body_str}.\n\n"
|
||||
self.rules.append(rule)
|
||||
|
||||
self.reset()
|
||||
|
||||
def default_flags(self, spec: "spack.spec.Spec"):
|
||||
if not spec.external or "flags" not in spec.extra_attributes:
|
||||
self.reset()
|
||||
return
|
||||
|
||||
when_spec = spack.spec.Spec(f"^[deptypes=build] {spec}")
|
||||
body_str, node_variable = self.rule_body_from(when_spec)
|
||||
|
||||
node_placeholder = "XXX"
|
||||
flags = spec.extra_attributes["flags"]
|
||||
root_spec_str = f"{node_placeholder}"
|
||||
for flag_type, default_values in flags.items():
|
||||
root_spec_str = f"{root_spec_str} {flag_type}='{default_values}'"
|
||||
root_spec = spack.spec.Spec(root_spec_str)
|
||||
head_clauses = self._setup.spec_clauses(
|
||||
root_spec, body=False, context=SourceContext(source="compiler")
|
||||
)
|
||||
self.rules.append(f"% Default compiler flags for {spec}\n")
|
||||
for clause in head_clauses:
|
||||
if clause.args[0] == "node":
|
||||
continue
|
||||
head_str = str(clause).replace(f'"{node_placeholder}"', f"{node_variable}")
|
||||
rule = f"{head_str} :-\n{body_str}.\n\n"
|
||||
self.rules.append(rule)
|
||||
|
||||
self.reset()
|
||||
|
||||
def consume_facts(self):
|
||||
"""Consume the facts collected by this object, and emits rules and
|
||||
facts for the runtimes.
|
||||
@@ -3484,6 +3616,7 @@ class SpecBuilder:
|
||||
r"^compatible_libc$",
|
||||
r"^dependency_holds$",
|
||||
r"^external_conditions_hold$",
|
||||
r"^node_compiler$",
|
||||
r"^package_hash$",
|
||||
r"^root$",
|
||||
r"^track_dependencies$",
|
||||
@@ -3564,6 +3697,10 @@ def variant_selected(self, node, name, value, variant_type, variant_id):
|
||||
def version(self, node, version):
|
||||
self._specs[node].versions = vn.VersionList([vn.Version(version)])
|
||||
|
||||
def node_compiler_version(self, node, compiler, version):
|
||||
self._specs[node].compiler = spack.spec.CompilerSpec(compiler)
|
||||
self._specs[node].compiler.versions = vn.VersionList([vn.Version(version)])
|
||||
|
||||
def node_flag(self, node, node_flag):
|
||||
self._specs[node].compiler_flags.add_flag(
|
||||
node_flag.flag_type, node_flag.flag, False, node_flag.flag_group, node_flag.source
|
||||
@@ -3618,14 +3755,17 @@ def reorder_flags(self):
|
||||
e.g. for `y cflags="-z -a"` "-z" and "-a" should never have any intervening
|
||||
flags inserted, and should always appear in that order.
|
||||
"""
|
||||
cmd_specs = {s.name: s for spec in self._command_line_specs for s in spec.traverse()}
|
||||
# reverse compilers so we get highest priority compilers that share a spec
|
||||
compilers = dict(
|
||||
(c.spec, c) for c in reversed(spack.compilers.all_compilers_from(spack.config.CONFIG))
|
||||
)
|
||||
cmd_specs = dict((s.name, s) for spec in self._command_line_specs for s in spec.traverse())
|
||||
|
||||
for spec in self._specs.values():
|
||||
# if bootstrapping, compiler is not in config and has no flags
|
||||
flagmap_from_compiler = {
|
||||
flag_type: [x for x in values if x.source == "compiler"]
|
||||
for flag_type, values in spec.compiler_flags.items()
|
||||
}
|
||||
flagmap_from_compiler = {}
|
||||
if spec.compiler in compilers:
|
||||
flagmap_from_compiler = compilers[spec.compiler].flags
|
||||
|
||||
for flag_type in spec.compiler_flags.valid_compiler_flags():
|
||||
node = SpecBuilder.make_node(pkg=spec.name)
|
||||
@@ -3675,7 +3815,7 @@ def _order_index(flag_group):
|
||||
|
||||
for grp in prioritized_groups:
|
||||
grp_flags = tuple(
|
||||
x for (x, y) in spack.compilers.flags.tokenize_flags(grp.flag_group)
|
||||
x for (x, y) in spack.compiler.tokenize_flags(grp.flag_group)
|
||||
)
|
||||
if grp_flags == from_compiler:
|
||||
continue
|
||||
@@ -3780,8 +3920,9 @@ def sort_fn(function_tuple) -> Tuple[int, int]:
|
||||
return (-1, 0)
|
||||
|
||||
def build_specs(self, function_tuples):
|
||||
# Functions don't seem to be in particular order in output. Sort them here so that
|
||||
# directives that build objects, like node, are called in the right order.
|
||||
# Functions don't seem to be in particular order in output. Sort
|
||||
# them here so that directives that build objects (like node and
|
||||
# node_compiler) are called in the right order.
|
||||
self.function_tuples = sorted(set(function_tuples), key=self.sort_fn)
|
||||
self._specs = {}
|
||||
for name, args in self.function_tuples:
|
||||
@@ -3845,14 +3986,6 @@ def build_specs(self, function_tuples):
|
||||
for root in roots.values():
|
||||
root._finalize_concretization()
|
||||
|
||||
# Unify hashes (this is to avoid duplicates of runtimes and compilers)
|
||||
unifier = ConcreteSpecsByHash()
|
||||
keys = list(self._specs)
|
||||
for key in keys:
|
||||
current_spec = self._specs[key]
|
||||
unifier.add(current_spec)
|
||||
self._specs[key] = unifier[current_spec.dag_hash()]
|
||||
|
||||
self._resolve_automatic_splices()
|
||||
|
||||
for s in self._specs.values():
|
||||
@@ -3973,10 +4106,13 @@ def _is_reusable(spec: spack.spec.Spec, packages, local: bool) -> bool:
|
||||
|
||||
|
||||
def _has_runtime_dependencies(spec: spack.spec.Spec) -> bool:
|
||||
if "gcc" in spec and "gcc-runtime" not in spec:
|
||||
if not WITH_RUNTIME:
|
||||
return True
|
||||
|
||||
if spec.compiler.name == "gcc" and not spec.dependencies("gcc-runtime"):
|
||||
return False
|
||||
|
||||
if "intel-oneapi-compilers" in spec and "intel-oneapi-runtime" not in spec:
|
||||
if spec.compiler.name == "oneapi" and not spec.dependencies("intel-oneapi-runtime"):
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -4225,41 +4361,10 @@ def _check_input_and_extract_concrete_specs(specs):
|
||||
reusable = []
|
||||
for root in specs:
|
||||
for s in root.traverse():
|
||||
candidates = s.edges_to_dependencies(depflag=dt.BUILD)
|
||||
if candidates:
|
||||
virtuals = set()
|
||||
non_virtuals = spack.package_base.possible_dependencies(
|
||||
s, transitive=False, virtuals=virtuals
|
||||
)
|
||||
possible_direct_deps = set(non_virtuals) | virtuals
|
||||
not_possible = set(
|
||||
[
|
||||
x.spec.name
|
||||
for x in candidates
|
||||
if x.direct and x.spec.name not in possible_direct_deps
|
||||
]
|
||||
)
|
||||
if not_possible:
|
||||
start_str = f"'{s}' in '{root}'"
|
||||
if s == root:
|
||||
start_str = f"'{root}'"
|
||||
raise UnsatisfiableSpecError(
|
||||
f"{start_str} cannot have a dependency on {', '.join(not_possible)}, "
|
||||
f"according to its recipe"
|
||||
)
|
||||
|
||||
if s.virtual:
|
||||
continue
|
||||
if s.concrete:
|
||||
reusable.append(s)
|
||||
|
||||
try:
|
||||
s.package_class
|
||||
except spack.repo.UnknownPackageError:
|
||||
raise UnsatisfiableSpecError(
|
||||
f"cannot concretize '{root}', since '{s.name}' does not exist"
|
||||
)
|
||||
|
||||
spack.spec.Spec.ensure_valid_variants(s)
|
||||
return reusable
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
internal_error("Only nodes can have node_os").
|
||||
:- attr("node_target", PackageNode, _), not attr("node", PackageNode),
|
||||
internal_error("Only nodes can have node_target").
|
||||
:- attr("node_compiler_version", PackageNode, _, _), not attr("node", PackageNode),
|
||||
internal_error("Only nodes can have node_compiler_version").
|
||||
:- attr("variant_value", PackageNode, _, _), not attr("node", PackageNode),
|
||||
internal_error("variant_value true for a non-node").
|
||||
:- attr("node_flag", PackageNode, _), not attr("node", PackageNode),
|
||||
@@ -145,15 +147,6 @@ unification_set(SetID, VirtualNode)
|
||||
max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1,
|
||||
internal_error("virtual node skipped id number").
|
||||
|
||||
% Prefer to assign lower ID to virtuals associated with a lower penalty provider
|
||||
:- not unification_set("root", node(X, Virtual)),
|
||||
not unification_set("root", node(Y, Virtual)),
|
||||
X < Y,
|
||||
provider_weight(_, node(X, Virtual), WeightX),
|
||||
provider_weight(_, node(Y, Virtual), WeightY),
|
||||
WeightY < WeightX.
|
||||
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Map literal input specs to facts that drive the solve
|
||||
%-----------------------------------------------------------------------------
|
||||
@@ -224,6 +217,14 @@ error(100, multiple_values_error, Attribute, Package)
|
||||
attr_single_value(Attribute),
|
||||
2 { attr(Attribute, node(ID, Package), Value) }.
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Languages used
|
||||
%-----------------------------------------------------------------------------
|
||||
|
||||
attr("language", node(X, Package), Language) :-
|
||||
condition_holds(ConditionID, node(X, Package)),
|
||||
pkg_fact(Package,language(ConditionID, Language)).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Version semantics
|
||||
%-----------------------------------------------------------------------------
|
||||
@@ -316,21 +317,18 @@ possible_version_weight(node(ID, Package), Weight)
|
||||
|
||||
% If there is at least a version that satisfy the constraint, impose a lower
|
||||
% bound on the choice rule to avoid false positives with the error below
|
||||
{ attr("version", node(ID, Package), Version) : pkg_fact(Package, version_satisfies(Constraint, Version)) }
|
||||
1 { attr("version", node(ID, Package), Version) : pkg_fact(Package, version_satisfies(Constraint, Version)) }
|
||||
:- attr("node_version_satisfies", node(ID, Package), Constraint),
|
||||
pkg_fact(Package, version_satisfies(Constraint, _)).
|
||||
pkg_fact(Package, version_satisfies(Constraint, _)),
|
||||
internal_error("must choose a single version to satisfy version constraints").
|
||||
|
||||
% More specific error message if the version cannot satisfy some constraint
|
||||
% Otherwise covered by `no_version_error` and `versions_conflict_error`.
|
||||
error(1, "Cannot satisfy '{0}@{1}'", Package, Constraint)
|
||||
error(10, "Cannot satisfy '{0}@{1}'", Package, Constraint)
|
||||
:- attr("node_version_satisfies", node(ID, Package), Constraint),
|
||||
attr("version", node(ID, Package), Version),
|
||||
not pkg_fact(Package, version_satisfies(Constraint, Version)).
|
||||
|
||||
error(10, "Cannot satisfy '{0}@{1}'", Package, Constraint)
|
||||
:- attr("node_version_satisfies", node(ID, Package), Constraint),
|
||||
not attr("version", node(ID, Package), _).
|
||||
|
||||
attr("node_version_satisfies", node(ID, Package), Constraint)
|
||||
:- attr("version", node(ID, Package), Version),
|
||||
pkg_fact(Package, version_satisfies(Constraint, Version)).
|
||||
@@ -379,7 +377,6 @@ trigger_node(ID, node(PackageID, Package), node(VirtualID, Virtual)) :- pkg_fact
|
||||
condition_nodes(TriggerID, PackageNode, node(X, A1))
|
||||
:- condition_packages(TriggerID, A1),
|
||||
condition_set(PackageNode, node(X, A1)),
|
||||
not self_build_requirement(PackageNode, node(X, A1)),
|
||||
trigger_node(TriggerID, PackageNode, _).
|
||||
|
||||
cannot_hold(TriggerID, PackageNode)
|
||||
@@ -394,7 +391,6 @@ trigger_condition_holds(ID, RequestorNode) :-
|
||||
attr(Name, node(X, A1), A2, A3) : condition_requirement(ID, Name, A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), not multiple_nodes_attribute(Name);
|
||||
attr(Name, node(X, A1), A2, A3, A4) : condition_requirement(ID, Name, A1, A2, A3, A4), condition_nodes(ID, PackageNode, node(X, A1));
|
||||
% Special cases
|
||||
attr("depends_on", node(X, A1), node(Y, A2), A3) : condition_requirement(ID, "depends_on", A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), condition_nodes(ID, PackageNode, node(Y, A2));
|
||||
not cannot_hold(ID, PackageNode).
|
||||
|
||||
condition_holds(ConditionID, node(X, Package))
|
||||
@@ -424,12 +420,7 @@ imposed_nodes(EffectID, node(NodeID, Package), node(X, A1))
|
||||
pkg_fact(Package, condition_effect(ID, EffectID)),
|
||||
imposed_packages(EffectID, A1),
|
||||
condition_set(node(NodeID, Package), node(X, A1)),
|
||||
trigger_node(TriggerID, _, node(NodeID, Package)),
|
||||
% We don't want to add build requirements to imposed nodes, to avoid
|
||||
% unsat problems when we deal with self-dependencies: gcc@14 %gcc@10
|
||||
not self_build_requirement(node(NodeID, Package), node(X, A1)).
|
||||
|
||||
self_build_requirement(node(X, Package), node(Y, Package)) :- build_requirement(node(X, Package), node(Y, Package)).
|
||||
trigger_node(TriggerID, _, node(NodeID, Package)).
|
||||
|
||||
imposed_nodes(ConditionID, PackageNode, node(X, A1))
|
||||
:- imposed_packages(ConditionID, A1),
|
||||
@@ -465,45 +456,6 @@ provider(ProviderNode, VirtualNode) :- attr("provider_set", ProviderNode, Virtua
|
||||
imposed_constraint(ID, "depends_on", A1, A2, A3),
|
||||
internal_error("Build deps must land in exactly one duplicate").
|
||||
|
||||
% The rule below accounts for expressions like:
|
||||
%
|
||||
% root ^dep %compiler
|
||||
%
|
||||
% where "compiler" is a dependency of "dep", but is enforced by a condition imposed by "root"
|
||||
1 { attr("depends_on", node(min_dupe_id, A1), node(0..Y-1, A2), A3) : max_dupes(A2, Y) } 1
|
||||
:- impose(ID, RootNode),
|
||||
unification_set("root", RootNode),
|
||||
unification_set("root", node(min_dupe_id, A1)),
|
||||
imposed_constraint(ID, "depends_on", A1, A2, A3),
|
||||
internal_error("Build deps must land in exactly one duplicate").
|
||||
|
||||
1 { build_requirement(node(X, Parent), node(0..Y-1, BuildDependency)) : max_dupes(BuildDependency, Y) } 1
|
||||
:- attr("build_requirement", node(X, Parent), build_requirement("node", BuildDependency)),
|
||||
impose(ID, node(X, Parent)),
|
||||
imposed_constraint(ID,"build_requirement",Parent,_).
|
||||
|
||||
1 { virtual_build_requirement(ParentNode, node(0..Y-1, Virtual)) : max_dupes(Virtual, Y) } 1
|
||||
:- attr("dependency_holds", ParentNode, Virtual, "build"),
|
||||
not attr("dependency_holds", ParentNode, Virtual,"link"),
|
||||
not attr("dependency_holds", ParentNode, Virtual,"run"),
|
||||
virtual(Virtual).
|
||||
|
||||
attr("virtual_node", VirtualNode) :- virtual_build_requirement(ParentNode, VirtualNode).
|
||||
build_requirement(ParentNode, ProviderNode) :- virtual_build_requirement(ParentNode, VirtualNode), provider(ProviderNode, VirtualNode).
|
||||
|
||||
% From cli we can have literal expressions like:
|
||||
%
|
||||
% root %gcc@12.0 ^dep %gcc@11.2
|
||||
%
|
||||
% Adding a "build_requirement" is a way to discriminate between the incompatible
|
||||
% version constraints on "gcc" in the "imposed_constraint".
|
||||
attr("node_version_satisfies", node(X, BuildDependency), Constraint) :-
|
||||
attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, Constraint)),
|
||||
build_requirement(ParentNode, node(X, BuildDependency)).
|
||||
|
||||
attr("depends_on", node(X, Parent), node(Y, BuildDependency), "build") :- build_requirement(node(X, Parent), node(Y, BuildDependency)).
|
||||
|
||||
|
||||
% Reconstruct virtual dependencies for reused specs
|
||||
attr("virtual_on_edge", node(X, A1), node(Y, A2), Virtual)
|
||||
:- impose(ID, node(X, A1)),
|
||||
@@ -543,12 +495,9 @@ virtual_condition_holds(node(Y, A2), Virtual)
|
||||
%-----------------------------------------------------------------------------
|
||||
% Concrete specs
|
||||
%-----------------------------------------------------------------------------
|
||||
|
||||
% if a package is assigned a hash, it's concrete.
|
||||
concrete(PackageNode) :- attr("hash", PackageNode, _), attr("node", PackageNode).
|
||||
|
||||
:- concrete(PackageNode), depends_on(PackageNode, DependencyNode), not concrete(DependencyNode).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Dependency semantics
|
||||
%-----------------------------------------------------------------------------
|
||||
@@ -570,45 +519,11 @@ attr("track_dependencies", Node) :- build(Node), not external(Node).
|
||||
% this ensures a user can't say `zlib ^libiconv` (neither of which have any
|
||||
% dependencies) and get a two-node unconnected graph
|
||||
needed(PackageNode) :- attr("root", PackageNode).
|
||||
needed(ChildNode) :- edge_needed(ParentNode, ChildNode).
|
||||
|
||||
edge_needed(ParentNode, node(X, Child)) :- depends_on(ParentNode, node(X, Child)), runtime(Child).
|
||||
edge_needed(ParentNode, ChildNode) :- depends_on(ParentNode, ChildNode) , concrete(ParentNode).
|
||||
|
||||
edge_needed(ParentNode, node(X, Child)) :-
|
||||
depends_on(ParentNode, node(X, Child)),
|
||||
build(ParentNode),
|
||||
attr("dependency_holds", ParentNode, Child, _).
|
||||
|
||||
virtual_edge_needed(ParentNode, ChildNode, node(X, Virtual)) :-
|
||||
depends_on(ParentNode, ChildNode),
|
||||
build(ParentNode),
|
||||
node_depends_on_virtual(ParentNode, Virtual),
|
||||
provider(ChildNode, node(X, Virtual)).
|
||||
|
||||
virtual_edge_needed(ParentNode, ChildNode, node(X, Virtual)) :-
|
||||
concrete(ParentNode),
|
||||
concrete(ChildNode),
|
||||
provider(ChildNode, node(X, Virtual)),
|
||||
attr("virtual_on_edge", ParentNode, ChildNode, Virtual).
|
||||
|
||||
edge_needed(ParentNode, ChildNode) :- virtual_edge_needed(ParentNode, ChildNode, _).
|
||||
provider_needed(ChildNode, VirtualNode) :- virtual_edge_needed(_, ChildNode, VirtualNode).
|
||||
provider_needed(ChildNode, VirtualNode) :- attr("virtual_root", VirtualNode), provider(ChildNode, VirtualNode).
|
||||
|
||||
needed(DependencyNode) :- needed(PackageNode), depends_on(PackageNode, DependencyNode).
|
||||
error(10, "'{0}' is not a valid dependency for any package in the DAG", Package)
|
||||
:- attr("node", node(ID, Package)),
|
||||
not needed(node(ID, Package)).
|
||||
|
||||
:- depends_on(ParentNode, ChildNode),
|
||||
not edge_needed(ParentNode, ChildNode),
|
||||
build(ParentNode).
|
||||
|
||||
:- provider(PackageNode, VirtualNode),
|
||||
not provider_needed(PackageNode, VirtualNode),
|
||||
not attr("virtual_root", VirtualNode).
|
||||
|
||||
|
||||
#defined dependency_type/2.
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
@@ -635,18 +550,14 @@ possible_provider_weight(ProviderNode, VirtualNode, 0, "Set on the command line"
|
||||
|
||||
% Enforces all virtuals to be provided, if multiple of them are provided together
|
||||
error(100, "Package '{0}' needs to provide both '{1}' and '{2}' together, but provides only '{1}'", Package, Virtual1, Virtual2)
|
||||
:- % This package provides 2 or more virtuals together
|
||||
condition_holds(ID, node(X, Package)),
|
||||
:- condition_holds(ID, node(X, Package)),
|
||||
pkg_fact(Package, provided_together(ID, SetID, Virtual1)),
|
||||
pkg_fact(Package, provided_together(ID, SetID, Virtual2)),
|
||||
Virtual1 != Virtual2,
|
||||
% One node depends on those virtuals AND on this package
|
||||
node_depends_on_virtual(ClientNode, Virtual1),
|
||||
node_depends_on_virtual(ClientNode, Virtual2),
|
||||
depends_on(ClientNode, node(X, Package)),
|
||||
% But this package is a provider of only one of them
|
||||
provider(node(X, Package), node(_, Virtual1)),
|
||||
not provider(node(X, Package), node(_, Virtual2)).
|
||||
attr("virtual_on_incoming_edges", node(X, Package), Virtual1),
|
||||
not attr("virtual_on_incoming_edges", node(X, Package), Virtual2),
|
||||
attr("virtual_node", node(_, Virtual1)),
|
||||
attr("virtual_node", node(_, Virtual2)).
|
||||
|
||||
% if a package depends on a virtual, it's not external and we have a
|
||||
% provider for that virtual then it depends on the provider
|
||||
@@ -725,7 +636,6 @@ do_not_impose(EffectID, node(X, Package))
|
||||
virtual_condition_holds(_, PossibleProvider, Virtual),
|
||||
PossibleProvider != ProviderNode,
|
||||
explicitly_requested_root(PossibleProvider),
|
||||
not self_build_requirement(PossibleProvider, ProviderNode),
|
||||
not explicitly_requested_root(ProviderNode),
|
||||
internal_error("If a root can provide a virtual, it must be the provider").
|
||||
|
||||
@@ -1225,10 +1135,9 @@ error(100, "Cannot propagate the variant '{0}' from the package: {1} because pac
|
||||
propagated_flag(node(PackageID, Package), node_flag(FlagType, Flag, FlagGroup, Source), SourceNode) :-
|
||||
propagate(node(PackageID, Package), node_flag(FlagType, Flag, FlagGroup, Source), _),
|
||||
not attr("node_flag_set", node(PackageID, Package), node_flag(FlagType, _, _, "literal")),
|
||||
% FIXME (compiler as nodes): do we need to match the compiler?
|
||||
% Same compiler as propagation source
|
||||
% node_compiler(node(PackageID, Package), CompilerID),
|
||||
% node_compiler(SourceNode, CompilerID),
|
||||
node_compiler(node(PackageID, Package), CompilerID),
|
||||
node_compiler(SourceNode, CompilerID),
|
||||
attr("propagate", SourceNode, node_flag(FlagType, Flag, FlagGroup, Source), _),
|
||||
node(PackageID, Package) != SourceNode,
|
||||
not runtime(Package).
|
||||
@@ -1236,7 +1145,7 @@ propagated_flag(node(PackageID, Package), node_flag(FlagType, Flag, FlagGroup, S
|
||||
attr("node_flag", PackageNode, NodeFlag) :- propagated_flag(PackageNode, NodeFlag, _).
|
||||
|
||||
% Cannot propagate the same flag from two distinct sources
|
||||
error(100, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Source1, Source2, FlagType, Package) :-
|
||||
error(100, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Source1, Source2, Package, FlagType) :-
|
||||
propagated_flag(node(ID, Package), node_flag(FlagType, _, _, _), node(_, Source1)),
|
||||
propagated_flag(node(ID, Package), node_flag(FlagType, _, _, _), node(_, Source2)),
|
||||
Source1 < Source2.
|
||||
@@ -1245,17 +1154,12 @@ error(100, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Sour
|
||||
% Compiler constraints
|
||||
%----
|
||||
|
||||
% If a node is built, impose constraints on the compiler coming from dependents
|
||||
attr("node_version_satisfies", node(Y, Compiler), VersionRange) :-
|
||||
propagate(node(X, Package), node_version_satisfies(Compiler, VersionRange)),
|
||||
attr("depends_on", node(X, Package), node(Y, Compiler), "build"),
|
||||
not external(node(X, Package)),
|
||||
not runtime(Package).
|
||||
|
||||
attr("node_version_satisfies", node(X, Runtime), VersionRange) :-
|
||||
attr("node", node(X, Runtime)),
|
||||
attr("compatible_runtime", PackageNode, Runtime, VersionRange),
|
||||
concrete(PackageNode).
|
||||
attr("node_compiler_version_satisfies", node(ID, Package), Compiler, Version) :-
|
||||
propagate(node(ID, Package), node_compiler_version_satisfies(Compiler, Version)),
|
||||
node_compiler(node(ID, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
not runtime(Package),
|
||||
not external(Package).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Runtimes
|
||||
@@ -1264,19 +1168,11 @@ attr("node_version_satisfies", node(X, Runtime), VersionRange) :-
|
||||
% Check whether the DAG has any built package
|
||||
has_built_packages() :- build(X), not external(X).
|
||||
|
||||
% "gcc-runtime" is always built
|
||||
:- concrete(node(X, "gcc-runtime")), has_built_packages().
|
||||
|
||||
% FIXME (compiler as nodes): is this version constraint always required and better than the callback?
|
||||
% "gcc-runtime" and the "gcc" it depends on must be at the same version
|
||||
% attr("version", node(Y, "gcc"), Version) :-
|
||||
% attr("version", node(X, "gcc-runtime"), Version),
|
||||
% attr("depends_on", node(X, "gcc-runtime"), node(Y, "gcc"), "build").
|
||||
|
||||
% The "gcc" linked to "gcc-runtime" must be used by at least another package
|
||||
:- attr("depends_on", node(X, "gcc-runtime"), node(Y, "gcc"), "build"),
|
||||
not 2 { attr("depends_on", PackageNode, node(Y, "gcc"), "build") : attr("node", PackageNode) }.
|
||||
|
||||
% If we build packages, the runtime nodes must use an available compiler
|
||||
1 { node_compiler(PackageNode, CompilerID) : build(PackageNode), not external(PackageNode) } :-
|
||||
has_built_packages(),
|
||||
runtime(RuntimePackage),
|
||||
node_compiler(node(_, RuntimePackage), CompilerID).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Platform semantics
|
||||
@@ -1349,7 +1245,7 @@ attr("node_target_satisfies", PackageNode, Constraint)
|
||||
|
||||
% If a node has a target, all of its dependencies must be compatible with that target
|
||||
error(100, "Cannot find compatible targets for {0} and {1}", Package, Dependency)
|
||||
:- attr("depends_on", node(X, Package), node(Y, Dependency), Type), Type != "build",
|
||||
:- depends_on(node(X, Package), node(Y, Dependency)),
|
||||
attr("node_target", node(X, Package), Target),
|
||||
not node_target_compatible(node(Y, Dependency), Target).
|
||||
|
||||
@@ -1361,27 +1257,29 @@ node_target_compatible(PackageNode, Target)
|
||||
target_compatible(Target, MyTarget).
|
||||
|
||||
#defined target_satisfies/2.
|
||||
compiler(Compiler) :- compiler_supports_target(Compiler, _, _).
|
||||
|
||||
% Can't use targets on node if the compiler for the node doesn't support them
|
||||
language("c").
|
||||
language("cxx").
|
||||
language("fortran").
|
||||
|
||||
error(10, "{0} compiler '{2}@{3}' incompatible with 'target={1}'", Package, Target, Compiler, Version)
|
||||
% can't use targets on node if the compiler for the node doesn't support them
|
||||
error(100, "{0} compiler '{2}@{3}' incompatible with 'target={1}'", Package, Target, Compiler, Version)
|
||||
:- attr("node_target", node(X, Package), Target),
|
||||
attr("virtual_on_edge", node(X, Package), node(Y, Compiler), Language),
|
||||
attr("version", node(Y, Compiler), Version),
|
||||
compiler(Compiler), language(Language),
|
||||
not compiler_supports_target(Compiler, Version, Target),
|
||||
node_compiler(node(X, Package), CompilerID),
|
||||
not compiler_supports_target(CompilerID, Target),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version(CompilerID, Version),
|
||||
build(node(X, Package)).
|
||||
|
||||
#defined compiler_supports_target/2.
|
||||
#defined compiler_available/1.
|
||||
|
||||
% if a target is set explicitly, respect it
|
||||
attr("node_target", PackageNode, Target)
|
||||
:- attr("node", PackageNode), attr("node_target_set", PackageNode, Target).
|
||||
|
||||
% each node has the weight of its assigned target
|
||||
target_weight(Target, 0)
|
||||
:- attr("node", PackageNode),
|
||||
attr("node_target", PackageNode, Target),
|
||||
attr("node_target_set", PackageNode, Target).
|
||||
|
||||
node_target_weight(PackageNode, MinWeight)
|
||||
:- attr("node", PackageNode),
|
||||
attr("node_target", PackageNode, Target),
|
||||
@@ -1406,12 +1304,150 @@ error(100, "'{0} target={1}' is not compatible with this machine", Package, Targ
|
||||
attr("node_target", node(X, Package), Target),
|
||||
not target(Target).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Compiler semantics
|
||||
%-----------------------------------------------------------------------------
|
||||
% There must be only one compiler set per built node.
|
||||
{ node_compiler(PackageNode, CompilerID) : compiler_id(CompilerID), compiler_available(CompilerID) } :-
|
||||
attr("node", PackageNode),
|
||||
build(PackageNode).
|
||||
|
||||
% Infer the compiler that matches a reused node
|
||||
node_compiler(PackageNode, CompilerID)
|
||||
:- attr("node_compiler_version", PackageNode, CompilerName, CompilerVersion),
|
||||
attr("node", PackageNode),
|
||||
compiler_name(CompilerID, CompilerName),
|
||||
compiler_version(CompilerID, CompilerVersion),
|
||||
concrete(PackageNode).
|
||||
|
||||
% Expand the internal attribute into "attr("node_compiler_version")
|
||||
attr("node_compiler_version", PackageNode, CompilerName, CompilerVersion)
|
||||
:- node_compiler(PackageNode, CompilerID),
|
||||
compiler_name(CompilerID, CompilerName),
|
||||
compiler_version(CompilerID, CompilerVersion),
|
||||
compiler_available(CompilerID),
|
||||
build(PackageNode).
|
||||
|
||||
attr("node_compiler", PackageNode, CompilerName)
|
||||
:- attr("node_compiler_version", PackageNode, CompilerName, CompilerVersion).
|
||||
|
||||
error(100, "No valid compiler version found for '{0}'", Package)
|
||||
:- attr("node", node(X, Package)),
|
||||
not node_compiler(node(X, Package), _).
|
||||
|
||||
% We can't have a compiler be enforced and select the version from another compiler
|
||||
error(100, "Cannot select a single compiler for package {0}", Package)
|
||||
:- attr("node", node(X, Package)),
|
||||
2 { attr("node_compiler_version", node(X, Package), C, V) }.
|
||||
|
||||
% If the compiler of a node cannot be satisfied, raise
|
||||
error(10, "No valid compiler for {0} satisfies '%{1}'", Package, Compiler)
|
||||
:- attr("node", node(X, Package)),
|
||||
attr("node_compiler_version_satisfies", node(X, Package), Compiler, ":"),
|
||||
not compiler_version_satisfies(Compiler, ":", _).
|
||||
|
||||
% If the compiler of a node must satisfy a constraint, then its version
|
||||
% must be chosen among the ones that satisfy said constraint
|
||||
error(100, "Package {0} cannot satisfy '%{1}@{2}'", Package, Compiler, Constraint)
|
||||
:- attr("node", node(X, Package)),
|
||||
attr("node_compiler_version_satisfies", node(X, Package), Compiler, Constraint),
|
||||
not compiler_version_satisfies(Compiler, Constraint, _).
|
||||
|
||||
error(100, "Package {0} cannot satisfy '%{1}@{2}'", Package, Compiler, Constraint)
|
||||
:- attr("node", node(X, Package)),
|
||||
attr("node_compiler_version_satisfies", node(X, Package), Compiler, Constraint),
|
||||
not compiler_version_satisfies(Compiler, Constraint, ID),
|
||||
node_compiler(node(X, Package), ID).
|
||||
|
||||
% If the node is associated with a compiler and the compiler satisfy a constraint, then
|
||||
% the compiler associated with the node satisfy the same constraint
|
||||
attr("node_compiler_version_satisfies", PackageNode, Compiler, Constraint)
|
||||
:- node_compiler(PackageNode, CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version_satisfies(Compiler, Constraint, CompilerID).
|
||||
|
||||
#defined compiler_version_satisfies/3.
|
||||
|
||||
% If the compiler version was set from the command line,
|
||||
% respect it verbatim
|
||||
error(100, "Cannot set the required compiler: {2}%{0}@{1}", Compiler, Version, Package)
|
||||
:- attr("node_compiler_version_set", node(X, Package), Compiler, Version),
|
||||
not attr("node_compiler_version", node(X, Package), Compiler, Version).
|
||||
|
||||
error(100, "Cannot set the required compiler: {1}%{0}", Compiler, Package)
|
||||
:- attr("node_compiler_set", node(X, Package), Compiler),
|
||||
not attr("node_compiler_version", node(X, Package), Compiler, _).
|
||||
|
||||
% Cannot select a compiler if it is not supported on the OS
|
||||
% Compilers that are explicitly marked as allowed
|
||||
% are excluded from this check
|
||||
error(100, "{0} compiler '%{1}@{2}' incompatible with 'os={3}'", Package, Compiler, Version, OS)
|
||||
:- attr("node_os", node(X, Package), OS),
|
||||
node_compiler(node(X, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version(CompilerID, Version),
|
||||
compiler_os(CompilerID, CompilerOS),
|
||||
not os_compatible(CompilerOS, OS),
|
||||
build(node(X, Package)).
|
||||
|
||||
% If a package and one of its dependencies don't have the
|
||||
% same compiler there's a mismatch.
|
||||
compiler_match(PackageNode, DependencyNode)
|
||||
:- depends_on(PackageNode, DependencyNode),
|
||||
node_compiler(PackageNode, CompilerID),
|
||||
node_compiler(DependencyNode, CompilerID).
|
||||
|
||||
compiler_mismatch(PackageNode, DependencyNode)
|
||||
:- depends_on(PackageNode, DependencyNode),
|
||||
not attr("node_compiler_set", DependencyNode, _),
|
||||
not compiler_match(PackageNode, DependencyNode).
|
||||
|
||||
compiler_mismatch_required(PackageNode, DependencyNode)
|
||||
:- depends_on(PackageNode, DependencyNode),
|
||||
attr("node_compiler_set", DependencyNode, _),
|
||||
not compiler_match(PackageNode, DependencyNode).
|
||||
|
||||
#defined compiler_os/3.
|
||||
|
||||
% compilers weighted by preference according to packages.yaml
|
||||
node_compiler_weight(node(ID, Package), Weight)
|
||||
:- node_compiler(node(ID, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version(CompilerID, V),
|
||||
compiler_weight(CompilerID, Weight).
|
||||
|
||||
node_compiler_weight(node(ID, Package), 100)
|
||||
:- node_compiler(node(ID, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version(CompilerID, V),
|
||||
not compiler_weight(CompilerID, _).
|
||||
|
||||
% For the time being, be strict and reuse only if the compiler match one we have on the system
|
||||
error(100, "Compiler {1}@{2} requested for {0} cannot be found.", Package, Compiler, Version)
|
||||
:- attr("node_compiler_version", node(ID, Package), Compiler, Version),
|
||||
not node_compiler(node(ID, Package), _).
|
||||
|
||||
#defined node_compiler_preference/4.
|
||||
#defined compiler_weight/3.
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Compiler flags
|
||||
%-----------------------------------------------------------------------------
|
||||
|
||||
% compiler flags from compilers.yaml are put on nodes if compiler matches
|
||||
attr("node_flag", PackageNode, node_flag(FlagType, Flag, FlagGroup, CompilerID))
|
||||
:- compiler_flag(CompilerID, FlagType, Flag, FlagGroup),
|
||||
node_compiler(PackageNode, CompilerID),
|
||||
flag_type(FlagType),
|
||||
compiler_id(CompilerID),
|
||||
compiler_name(CompilerID, CompilerName),
|
||||
compiler_version(CompilerID, Version).
|
||||
|
||||
attr("node_flag", PackageNode, NodeFlag) :- attr("node_flag_set", PackageNode, NodeFlag).
|
||||
|
||||
#defined compiler_flag/4.
|
||||
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Installed Packages
|
||||
%-----------------------------------------------------------------------------
|
||||
@@ -1554,7 +1590,7 @@ opt_criterion(310, "requirement weight").
|
||||
% Try hard to reuse installed packages (i.e., minimize the number built)
|
||||
opt_criterion(110, "number of packages to build (vs. reuse)").
|
||||
#minimize { 0@110: #true }.
|
||||
#minimize { 1@110,PackageNode : build(PackageNode) }.
|
||||
#minimize { 1@110,PackageNode : build(PackageNode), optimize_for_reuse() }.
|
||||
|
||||
opt_criterion(100, "number of nodes from the same package").
|
||||
#minimize { 0@100: #true }.
|
||||
@@ -1628,17 +1664,6 @@ opt_criterion(50, "number of non-default variants (non-roots)").
|
||||
build_priority(PackageNode, Priority)
|
||||
}.
|
||||
|
||||
% Minimize the weights of the providers, i.e. use as much as
|
||||
% possible the first providers
|
||||
opt_criterion(48, "number of duplicate virtuals needed").
|
||||
#minimize{ 0@248: #true }.
|
||||
#minimize{ 0@48: #true }.
|
||||
#minimize{
|
||||
Weight@48+Priority,ProviderNode,Virtual
|
||||
: provider(ProviderNode, node(Weight, Virtual)),
|
||||
build_priority(ProviderNode, Priority)
|
||||
}.
|
||||
|
||||
% Minimize the weights of the providers, i.e. use as much as
|
||||
% possible the most preferred providers
|
||||
opt_criterion(45, "preferred providers (non-roots)").
|
||||
@@ -1651,6 +1676,27 @@ opt_criterion(45, "preferred providers (non-roots)").
|
||||
build_priority(ProviderNode, Priority)
|
||||
}.
|
||||
|
||||
% Try to minimize the number of compiler mismatches in the DAG.
|
||||
opt_criterion(40, "compiler mismatches that are not required").
|
||||
#minimize{ 0@240: #true }.
|
||||
#minimize{ 0@40: #true }.
|
||||
#minimize{
|
||||
1@40+Priority,PackageNode,node(ID, Dependency)
|
||||
: compiler_mismatch(PackageNode, node(ID, Dependency)),
|
||||
build_priority(node(ID, Dependency), Priority),
|
||||
not runtime(Dependency)
|
||||
}.
|
||||
|
||||
opt_criterion(39, "compiler mismatches that are required").
|
||||
#minimize{ 0@239: #true }.
|
||||
#minimize{ 0@39: #true }.
|
||||
#minimize{
|
||||
1@39+Priority,PackageNode,node(ID, Dependency)
|
||||
: compiler_mismatch_required(PackageNode, node(ID, Dependency)),
|
||||
build_priority(node(ID, Dependency), Priority),
|
||||
not runtime(Dependency)
|
||||
}.
|
||||
|
||||
opt_criterion(30, "non-preferred OS's").
|
||||
#minimize{ 0@230: #true }.
|
||||
#minimize{ 0@30: #true }.
|
||||
@@ -1683,6 +1729,17 @@ opt_criterion(20, "default values of variants not being used (non-roots)").
|
||||
build_priority(PackageNode, Priority)
|
||||
}.
|
||||
|
||||
% Try to use preferred compilers
|
||||
opt_criterion(15, "non-preferred compilers").
|
||||
#minimize{ 0@215: #true }.
|
||||
#minimize{ 0@15: #true }.
|
||||
#minimize{
|
||||
Weight@15+Priority,node(X, Package)
|
||||
: node_compiler_weight(node(X, Package), Weight),
|
||||
build_priority(node(X, Package), Priority),
|
||||
not runtime(Package)
|
||||
}.
|
||||
|
||||
% Minimize the number of mismatches for targets in the DAG, try
|
||||
% to select the preferred target.
|
||||
opt_criterion(10, "target mismatches").
|
||||
@@ -1706,6 +1763,20 @@ opt_criterion(5, "non-preferred targets").
|
||||
}.
|
||||
|
||||
|
||||
% Minimize the number of compiler mismatches for runtimes
|
||||
opt_criterion(4, "compiler mismatches (runtimes)").
|
||||
#minimize{ 0@204: #true }.
|
||||
#minimize{ 0@4: #true }.
|
||||
#minimize{
|
||||
1@4,PackageNode,node(ID, Dependency)
|
||||
: compiler_mismatch(PackageNode, node(ID, Dependency)), runtime(Dependency)
|
||||
}.
|
||||
#minimize{
|
||||
1@4,PackageNode,node(ID, Dependency)
|
||||
: compiler_mismatch_required(PackageNode, node(ID, Dependency)), runtime(Dependency)
|
||||
}.
|
||||
|
||||
|
||||
% Choose more recent versions for runtimes
|
||||
opt_criterion(3, "version badness (runtimes)").
|
||||
#minimize{ 0@203: #true }.
|
||||
|
||||
@@ -126,28 +126,22 @@ def _compute_cache_values(self):
|
||||
self._possible_dependencies = set(self._link_run) | set(self._total_build)
|
||||
|
||||
def possible_packages_facts(self, gen, fn):
|
||||
build_tools = set()
|
||||
for current_tag in ("build-tools", "compiler"):
|
||||
build_tools.update(spack.repo.PATH.packages_with_tags(current_tag))
|
||||
|
||||
build_tools = spack.repo.PATH.packages_with_tags("build-tools")
|
||||
gen.h2("Packages with at most a single node")
|
||||
for package_name in sorted(self.possible_dependencies() - build_tools):
|
||||
gen.fact(fn.max_dupes(package_name, 1))
|
||||
gen.newline()
|
||||
|
||||
gen.h2("Packages with multiple possible nodes (build-tools)")
|
||||
gen.h2("Packages with at multiple possible nodes (build-tools)")
|
||||
for package_name in sorted(self.possible_dependencies() & build_tools):
|
||||
gen.fact(fn.max_dupes(package_name, 2))
|
||||
gen.fact(fn.multiple_unification_sets(package_name))
|
||||
gen.newline()
|
||||
|
||||
gen.h2("Maximum number of nodes (virtual packages)")
|
||||
for package_name in sorted(self._link_run_virtuals):
|
||||
for package_name in sorted(self.possible_virtuals()):
|
||||
gen.fact(fn.max_dupes(package_name, 1))
|
||||
gen.newline()
|
||||
for package_name in sorted(self.possible_virtuals() - self._link_run_virtuals):
|
||||
gen.fact(fn.max_dupes(package_name, 2))
|
||||
gen.newline()
|
||||
|
||||
gen.h2("Possible package in link-run subDAG")
|
||||
for name in sorted(self._link_run):
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
#heuristic attr("virtual_node", node(X, Virtual)). [60, init]
|
||||
#heuristic attr("virtual_node", node(X, Virtual)). [-1, sign]
|
||||
#heuristic attr("virtual_node", node(0, Virtual)) : node_depends_on_virtual(PackageNode, Virtual). [1@2, sign]
|
||||
#heuristic attr("virtual_node", node(0, "c")). [1@3, sign]
|
||||
#heuristic attr("virtual_node", node(0, "cxx")). [1@3, sign]
|
||||
|
||||
#heuristic attr("depends_on", ParentNode, ChildNode, Type). [150, init]
|
||||
#heuristic attr("depends_on", ParentNode, ChildNode, Type). [4, factor]
|
||||
@@ -40,3 +38,6 @@
|
||||
% Use default targets
|
||||
#heuristic attr("node_target", node(PackageID, Package), Target). [-1, sign]
|
||||
#heuristic attr("node_target", node(PackageID, Package), Target) : target_weight(Target, 0), attr("node", node(PackageID, Package)). [1@2, sign]
|
||||
|
||||
% Use the default compilers
|
||||
#heuristic node_compiler(node(PackageID, Package), ID) : compiler_weight(ID, 0), compiler_id(ID), attr("node", node(PackageID, Package)). [30, init]
|
||||
|
||||
@@ -9,29 +9,25 @@
|
||||
% These rules are used on Linux
|
||||
%=============================================================================
|
||||
|
||||
|
||||
% A package cannot be reused if it needs a libc that is not compatible with the current one
|
||||
% A package cannot be reused if the libc is not compatible with it
|
||||
error(100, "Cannot reuse {0} since we cannot determine libc compatibility", ReusedPackage)
|
||||
:- provider(node(X, LibcPackage), node(0, "libc")),
|
||||
attr("version", node(X, LibcPackage), LibcVersion),
|
||||
concrete(node(R, ReusedPackage)),
|
||||
attr("needs_libc", node(R, ReusedPackage)),
|
||||
attr("hash", node(R, ReusedPackage), Hash),
|
||||
% Libc packages can be reused without the "compatible_libc" attribute
|
||||
ReusedPackage != LibcPackage,
|
||||
not attr("compatible_libc", node(R, ReusedPackage), LibcPackage, LibcVersion).
|
||||
|
||||
% In case we don't need a provider for libc, ensure there's at least one compatible libc on the host
|
||||
error(100, "Cannot reuse {0} since we cannot determine libc compatibility", ReusedPackage)
|
||||
:- not provider(_, node(0, "libc")),
|
||||
concrete(node(R, ReusedPackage)),
|
||||
attr("needs_libc", node(R, ReusedPackage)),
|
||||
not attr("compatible_libc", node(R, ReusedPackage), _, _).
|
||||
% A libc is needed in the DAG
|
||||
:- has_built_packages(), not provider(_, node(0, "libc")).
|
||||
|
||||
% Non-libc reused specs must be host libc compatible. In case we build packages, we get a
|
||||
% host compatible libc provider from other rules. If nothing is built, there is no libc provider,
|
||||
% since it's pruned from reusable specs, meaning we have to explicitly impose reused specs are host
|
||||
% compatible.
|
||||
%:- attr("hash", node(R, ReusedPackage), Hash),
|
||||
% not provider(node(R, ReusedPackage), node(0, "libc")),
|
||||
% not attr("compatible_libc", node(R, ReusedPackage), _, _).
|
||||
:- attr("hash", node(R, ReusedPackage), Hash),
|
||||
not provider(node(R, ReusedPackage), node(0, "libc")),
|
||||
not attr("compatible_libc", node(R, ReusedPackage), _, _).
|
||||
|
||||
% The libc provider must be one that a compiler can target
|
||||
:- has_built_packages(),
|
||||
@@ -39,3 +35,9 @@ error(100, "Cannot reuse {0} since we cannot determine libc compatibility", Reus
|
||||
attr("node", node(X, LibcPackage)),
|
||||
attr("version", node(X, LibcPackage), LibcVersion),
|
||||
not host_libc(LibcPackage, LibcVersion).
|
||||
|
||||
% A built node must depend on libc
|
||||
:- build(PackageNode),
|
||||
provider(LibcNode, node(0, "libc")),
|
||||
not external(PackageNode),
|
||||
not depends_on(PackageNode, LibcNode).
|
||||
|
||||
@@ -71,7 +71,8 @@
|
||||
import llnl.util.tty.color as clr
|
||||
|
||||
import spack
|
||||
import spack.compilers.flags
|
||||
import spack.compiler
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.deptypes as dt
|
||||
import spack.error
|
||||
@@ -82,7 +83,6 @@
|
||||
import spack.provider_index
|
||||
import spack.repo
|
||||
import spack.solver
|
||||
import spack.spec
|
||||
import spack.store
|
||||
import spack.traverse as traverse
|
||||
import spack.util.executable
|
||||
@@ -163,13 +163,15 @@
|
||||
#: Default format for Spec.format(). This format can be round-tripped, so that:
|
||||
#: Spec(Spec("string").format()) == Spec("string)"
|
||||
DEFAULT_FORMAT = (
|
||||
"{name}{@versions}{compiler_flags}"
|
||||
"{name}{@versions}"
|
||||
"{%compiler.name}{@compiler.versions}{compiler_flags}"
|
||||
"{variants}{ namespace=namespace_if_anonymous}{ arch=architecture}{/abstract_hash}"
|
||||
)
|
||||
|
||||
#: Display format, which eliminates extra `@=` in the output, for readability.
|
||||
DISPLAY_FORMAT = (
|
||||
"{name}{@version}{compiler_flags}"
|
||||
"{name}{@version}"
|
||||
"{%compiler.name}{@compiler.version}{compiler_flags}"
|
||||
"{variants}{ namespace=namespace_if_anonymous}{ arch=architecture}{/abstract_hash}"
|
||||
)
|
||||
|
||||
@@ -184,7 +186,7 @@
|
||||
)
|
||||
|
||||
#: specfile format version. Must increase monotonically
|
||||
SPECFILE_FORMAT_VERSION = 5
|
||||
SPECFILE_FORMAT_VERSION = 4
|
||||
|
||||
|
||||
class InstallStatus(enum.Enum):
|
||||
@@ -594,82 +596,137 @@ def __repr__(self):
|
||||
def __contains__(self, string):
|
||||
return string in str(self) or string in self.target
|
||||
|
||||
def complete_with_defaults(self) -> None:
|
||||
default_architecture = ArchSpec.default_arch()
|
||||
if not self.platform:
|
||||
self.platform = default_architecture.platform
|
||||
|
||||
if not self.os:
|
||||
self.os = default_architecture.os
|
||||
|
||||
if not self.target:
|
||||
self.target = default_architecture.target
|
||||
|
||||
|
||||
@lang.lazy_lexicographic_ordering
|
||||
class CompilerSpec:
|
||||
"""Adaptor to the old compiler spec interface. Exposes just a few attributes"""
|
||||
"""The CompilerSpec field represents the compiler or range of compiler
|
||||
versions that a package should be built with. CompilerSpecs have a
|
||||
name and a version list."""
|
||||
|
||||
def __init__(self, spec):
|
||||
self.spec = spec
|
||||
__slots__ = "name", "versions"
|
||||
|
||||
def __init__(self, *args):
|
||||
nargs = len(args)
|
||||
if nargs == 1:
|
||||
arg = args[0]
|
||||
# If there is one argument, it's either another CompilerSpec
|
||||
# to copy or a string to parse
|
||||
if isinstance(arg, str):
|
||||
spec = spack.parser.parse_one_or_raise(f"%{arg}")
|
||||
self.name = spec.compiler.name
|
||||
self.versions = spec.compiler.versions
|
||||
|
||||
elif isinstance(arg, CompilerSpec):
|
||||
self.name = arg.name
|
||||
self.versions = arg.versions.copy()
|
||||
|
||||
else:
|
||||
raise TypeError(
|
||||
"Can only build CompilerSpec from string or "
|
||||
+ "CompilerSpec. Found %s" % type(arg)
|
||||
)
|
||||
|
||||
elif nargs == 2:
|
||||
name, version = args
|
||||
self.name = name
|
||||
self.versions = vn.VersionList([vn.ver(version)])
|
||||
|
||||
else:
|
||||
raise TypeError("__init__ takes 1 or 2 arguments. (%d given)" % nargs)
|
||||
|
||||
def _autospec(self, compiler_spec_like):
|
||||
if isinstance(compiler_spec_like, CompilerSpec):
|
||||
return compiler_spec_like
|
||||
return CompilerSpec(compiler_spec_like)
|
||||
|
||||
def intersects(self, other: "CompilerSpec") -> bool:
|
||||
"""Return True if all concrete specs matching self also match other, otherwise False.
|
||||
|
||||
For compiler specs this means that the name of the compiler must be the same for
|
||||
self and other, and that the versions ranges should intersect.
|
||||
|
||||
Args:
|
||||
other: spec to be satisfied
|
||||
"""
|
||||
other = self._autospec(other)
|
||||
return self.name == other.name and self.versions.intersects(other.versions)
|
||||
|
||||
def satisfies(self, other: "CompilerSpec") -> bool:
|
||||
"""Return True if all concrete specs matching self also match other, otherwise False.
|
||||
|
||||
For compiler specs this means that the name of the compiler must be the same for
|
||||
self and other, and that the version range of self is a subset of that of other.
|
||||
|
||||
Args:
|
||||
other: spec to be satisfied
|
||||
"""
|
||||
other = self._autospec(other)
|
||||
return self.name == other.name and self.versions.satisfies(other.versions)
|
||||
|
||||
def constrain(self, other: "CompilerSpec") -> bool:
|
||||
"""Intersect self's versions with other.
|
||||
|
||||
Return whether the CompilerSpec changed.
|
||||
"""
|
||||
other = self._autospec(other)
|
||||
|
||||
# ensure that other will actually constrain this spec.
|
||||
if not other.intersects(self):
|
||||
raise UnsatisfiableCompilerSpecError(other, self)
|
||||
|
||||
return self.versions.intersect(other.versions)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.spec.name
|
||||
def concrete(self):
|
||||
"""A CompilerSpec is concrete if its versions are concrete and there
|
||||
is an available compiler with the right version."""
|
||||
return self.versions.concrete
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.spec.version
|
||||
if not self.concrete:
|
||||
raise spack.error.SpecError("Spec is not concrete: " + str(self))
|
||||
return self.versions[0]
|
||||
|
||||
@property
|
||||
def versions(self):
|
||||
return self.spec.versions
|
||||
def copy(self):
|
||||
clone = CompilerSpec.__new__(CompilerSpec)
|
||||
clone.name = self.name
|
||||
clone.versions = self.versions.copy()
|
||||
return clone
|
||||
|
||||
def _cmp_iter(self):
|
||||
yield self.name
|
||||
yield self.versions
|
||||
|
||||
def to_dict(self):
|
||||
d = syaml.syaml_dict([("name", self.name)])
|
||||
d.update(self.versions.to_dict())
|
||||
|
||||
return syaml.syaml_dict([("compiler", d)])
|
||||
|
||||
@staticmethod
|
||||
def from_dict(d):
|
||||
d = d["compiler"]
|
||||
return CompilerSpec(d["name"], vn.VersionList.from_dict(d))
|
||||
|
||||
@property
|
||||
def display_str(self):
|
||||
"""Equivalent to {compiler.name}{@compiler.version} for Specs, without extra
|
||||
@= for readability."""
|
||||
if self.spec.concrete:
|
||||
if self.concrete:
|
||||
return f"{self.name}@{self.version}"
|
||||
elif self.versions != vn.any_version:
|
||||
return f"{self.name}@{self.versions}"
|
||||
return self.name
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, CompilerSpec):
|
||||
return self.spec < other
|
||||
return self.spec < other.spec
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, CompilerSpec):
|
||||
return self.spec == other
|
||||
return self.spec == other.spec
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.spec)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.spec)
|
||||
out = self.name
|
||||
if self.versions and self.versions != vn.any_version:
|
||||
out += f"@{self.versions}"
|
||||
return out
|
||||
|
||||
def _cmp_iter(self):
|
||||
return self.spec._cmp_iter()
|
||||
|
||||
def __bool__(self):
|
||||
if self.spec == Spec():
|
||||
return False
|
||||
return bool(self.spec)
|
||||
|
||||
|
||||
class DeprecatedCompilerSpec(lang.DeprecatedProperty):
|
||||
def __init__(self):
|
||||
super().__init__(name="compiler")
|
||||
|
||||
def factory(self, instance, owner):
|
||||
for language in ("c", "cxx", "fortran"):
|
||||
deps = instance.dependencies(virtuals=language)
|
||||
if deps:
|
||||
return CompilerSpec(deps[0])
|
||||
|
||||
raise AttributeError(f"{instance} has no C, C++, or Fortran compiler")
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
@lang.lazy_lexicographic_ordering
|
||||
@@ -690,22 +747,15 @@ class DependencySpec:
|
||||
virtuals: virtual packages provided from child to parent node.
|
||||
"""
|
||||
|
||||
__slots__ = "parent", "spec", "depflag", "virtuals", "direct"
|
||||
__slots__ = "parent", "spec", "depflag", "virtuals"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: "Spec",
|
||||
spec: "Spec",
|
||||
*,
|
||||
depflag: dt.DepFlag,
|
||||
virtuals: Tuple[str, ...],
|
||||
direct: bool = False,
|
||||
self, parent: "Spec", spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...]
|
||||
):
|
||||
self.parent = parent
|
||||
self.spec = spec
|
||||
self.depflag = depflag
|
||||
self.virtuals = tuple(sorted(set(virtuals)))
|
||||
self.direct = direct
|
||||
|
||||
def update_deptypes(self, depflag: dt.DepFlag) -> bool:
|
||||
"""Update the current dependency types"""
|
||||
@@ -724,13 +774,7 @@ def update_virtuals(self, virtuals: Tuple[str, ...]) -> bool:
|
||||
|
||||
def copy(self) -> "DependencySpec":
|
||||
"""Return a copy of this edge"""
|
||||
return DependencySpec(
|
||||
self.parent,
|
||||
self.spec,
|
||||
depflag=self.depflag,
|
||||
virtuals=self.virtuals,
|
||||
direct=self.direct,
|
||||
)
|
||||
return DependencySpec(self.parent, self.spec, depflag=self.depflag, virtuals=self.virtuals)
|
||||
|
||||
def _cmp_iter(self):
|
||||
yield self.parent.name if self.parent else None
|
||||
@@ -1385,34 +1429,12 @@ def tree(
|
||||
return out
|
||||
|
||||
|
||||
class SpecAnnotations:
|
||||
def __init__(self) -> None:
|
||||
self.original_spec_format = SPECFILE_FORMAT_VERSION
|
||||
self.compiler_node_attribute: Optional["Spec"] = None
|
||||
|
||||
def with_spec_format(self, spec_format: int) -> "SpecAnnotations":
|
||||
self.original_spec_format = spec_format
|
||||
return self
|
||||
|
||||
def with_compiler(self, compiler: "Spec") -> "SpecAnnotations":
|
||||
self.compiler_node_attribute = compiler
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
result = f"SpecAnnotations().with_spec_format({self.original_spec_format})"
|
||||
if self.compiler_node_attribute:
|
||||
result += f"with_compiler({str(self.compiler_node_attribute)})"
|
||||
return result
|
||||
|
||||
|
||||
@lang.lazy_lexicographic_ordering(set_hash=False)
|
||||
class Spec:
|
||||
#: Cache for spec's prefix, computed lazily in the corresponding property
|
||||
_prefix = None
|
||||
abstract_hash = None
|
||||
|
||||
compiler = DeprecatedCompilerSpec()
|
||||
|
||||
@staticmethod
|
||||
def default_arch():
|
||||
"""Return an anonymous spec for the default architecture"""
|
||||
@@ -1452,6 +1474,7 @@ def __init__(
|
||||
self.versions = vn.VersionList(":")
|
||||
self.variants = VariantMap(self)
|
||||
self.architecture = None
|
||||
self.compiler = None
|
||||
self.compiler_flags = FlagMap(self)
|
||||
self._dependents = _EdgeMap(store_by_child=False)
|
||||
self._dependencies = _EdgeMap(store_by_child=True)
|
||||
@@ -1488,13 +1511,12 @@ def __init__(
|
||||
# is deployed "as built."
|
||||
# Build spec should be the actual build spec unless marked dirty.
|
||||
self._build_spec = None
|
||||
self.annotations = SpecAnnotations()
|
||||
|
||||
if isinstance(spec_like, str):
|
||||
spack.parser.parse_one_or_raise(spec_like, self)
|
||||
|
||||
elif spec_like is not None:
|
||||
raise TypeError(f"Can't make spec out of {type(spec_like)}")
|
||||
raise TypeError("Can't make spec out of %s" % type(spec_like))
|
||||
|
||||
@staticmethod
|
||||
def _format_module_list(modules):
|
||||
@@ -1698,7 +1720,7 @@ def _add_flag(self, name, value, propagate):
|
||||
self.namespace = value
|
||||
elif name in valid_flags:
|
||||
assert self.compiler_flags is not None
|
||||
flags_and_propagation = spack.compilers.flags.tokenize_flags(value, propagate)
|
||||
flags_and_propagation = spack.compiler.tokenize_flags(value, propagate)
|
||||
flag_group = " ".join(x for (x, y) in flags_and_propagation)
|
||||
for flag, propagation in flags_and_propagation:
|
||||
self.compiler_flags.add_flag(name, flag, propagation, flag_group)
|
||||
@@ -1729,18 +1751,10 @@ def _set_architecture(self, **kwargs):
|
||||
else:
|
||||
setattr(self.architecture, new_attr, new_value)
|
||||
|
||||
def _add_dependency(
|
||||
self, spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...], direct: bool = False
|
||||
):
|
||||
"""Called by the parser to add another spec as a dependency.
|
||||
|
||||
Args:
|
||||
depflag: dependency type for this edge
|
||||
virtuals: virtuals on this edge
|
||||
direct: if True denotes a direct dependency (associated with the % sigil)
|
||||
"""
|
||||
def _add_dependency(self, spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...]):
|
||||
"""Called by the parser to add another spec as a dependency."""
|
||||
if spec.name not in self._dependencies or not spec.name:
|
||||
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals, direct=direct)
|
||||
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals)
|
||||
return
|
||||
|
||||
# Keep the intersection of constraints when a dependency is added multiple times with
|
||||
@@ -1763,7 +1777,7 @@ def _add_dependency(
|
||||
f"\t'{str(self)}' cannot depend on '{required_dep_str}'"
|
||||
)
|
||||
|
||||
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals, direct=direct)
|
||||
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -1775,12 +1789,7 @@ def _add_dependency(
|
||||
)
|
||||
|
||||
def add_dependency_edge(
|
||||
self,
|
||||
dependency_spec: "Spec",
|
||||
*,
|
||||
depflag: dt.DepFlag,
|
||||
virtuals: Tuple[str, ...],
|
||||
direct: bool = False,
|
||||
self, dependency_spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...]
|
||||
):
|
||||
"""Add a dependency edge to this spec.
|
||||
|
||||
@@ -1788,7 +1797,6 @@ def add_dependency_edge(
|
||||
dependency_spec: spec of the dependency
|
||||
deptypes: dependency types for this edge
|
||||
virtuals: virtuals provided by this edge
|
||||
direct: if True denotes a direct dependency
|
||||
"""
|
||||
# Check if we need to update edges that are already present
|
||||
selected = self._dependencies.select(child=dependency_spec.name)
|
||||
@@ -1827,9 +1835,7 @@ def add_dependency_edge(
|
||||
edge.update_virtuals(virtuals=virtuals)
|
||||
return
|
||||
|
||||
edge = DependencySpec(
|
||||
self, dependency_spec, depflag=depflag, virtuals=virtuals, direct=direct
|
||||
)
|
||||
edge = DependencySpec(self, dependency_spec, depflag=depflag, virtuals=virtuals)
|
||||
self._dependencies.add(edge)
|
||||
dependency_spec._dependents.add(edge)
|
||||
|
||||
@@ -2132,6 +2138,10 @@ def to_node_dict(self, hash=ht.dag_hash):
|
||||
'platform_os': 'mojave',
|
||||
'target': 'x86_64',
|
||||
},
|
||||
'compiler': {
|
||||
'name': 'apple-clang',
|
||||
'version': '10.0.0',
|
||||
},
|
||||
'namespace': 'builtin',
|
||||
'parameters': {
|
||||
'fts': 'true',
|
||||
@@ -2174,6 +2184,9 @@ def to_node_dict(self, hash=ht.dag_hash):
|
||||
if self.architecture:
|
||||
d.update(self.architecture.to_dict())
|
||||
|
||||
if self.compiler:
|
||||
d.update(self.compiler.to_dict())
|
||||
|
||||
if self.namespace:
|
||||
d["namespace"] = self.namespace
|
||||
|
||||
@@ -2263,14 +2276,6 @@ def to_node_dict(self, hash=ht.dag_hash):
|
||||
d["build_spec"] = syaml.syaml_dict(
|
||||
[("name", self.build_spec.name), (hash.name, self.build_spec._cached_hash(hash))]
|
||||
)
|
||||
|
||||
# Annotations
|
||||
d["annotations"] = syaml.syaml_dict(
|
||||
[("original_specfile_version", self.annotations.original_spec_format)]
|
||||
)
|
||||
if self.annotations.original_spec_format < 5:
|
||||
d["annotations"]["compiler"] = str(self.annotations.compiler_node_attribute)
|
||||
|
||||
return d
|
||||
|
||||
def to_dict(self, hash=ht.dag_hash):
|
||||
@@ -2427,6 +2432,8 @@ def override(init_spec, change_spec):
|
||||
else:
|
||||
raise ValueError("{0} is not a variant of {1}".format(vname, new_spec.name))
|
||||
|
||||
if change_spec.compiler:
|
||||
new_spec.compiler = change_spec.compiler
|
||||
if change_spec.compiler_flags:
|
||||
for flagname, flagvals in change_spec.compiler_flags.items():
|
||||
new_spec.compiler_flags[flagname] = flagvals
|
||||
@@ -2598,10 +2605,8 @@ def from_dict(data):
|
||||
spec = SpecfileV2.load(data)
|
||||
elif int(data["spec"]["_meta"]["version"]) == 3:
|
||||
spec = SpecfileV3.load(data)
|
||||
elif int(data["spec"]["_meta"]["version"]) == 4:
|
||||
spec = SpecfileV4.load(data)
|
||||
else:
|
||||
spec = SpecfileV5.load(data)
|
||||
spec = SpecfileV4.load(data)
|
||||
|
||||
# Any git version should
|
||||
for s in spec.traverse():
|
||||
@@ -2786,6 +2791,12 @@ def inject_patches_variant(root):
|
||||
@staticmethod
|
||||
def ensure_external_path_if_external(external_spec):
|
||||
if external_spec.external_modules and not external_spec.external_path:
|
||||
compiler = spack.compilers.compiler_for_spec(
|
||||
external_spec.compiler, external_spec.architecture
|
||||
)
|
||||
for mod in compiler.modules:
|
||||
md.load_module(mod)
|
||||
|
||||
# Get the path from the module the package can override the default
|
||||
# (this is mostly needed for Cray)
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(external_spec.name)
|
||||
@@ -2947,7 +2958,7 @@ def _finalize_concretization(self):
|
||||
for spec in self.traverse():
|
||||
spec._cached_hash(ht.dag_hash)
|
||||
|
||||
def concretized(self, tests: Union[bool, Iterable[str]] = False) -> "Spec":
|
||||
def concretized(self, tests: Union[bool, Iterable[str]] = False) -> "spack.spec.Spec":
|
||||
"""This is a non-destructive version of concretize().
|
||||
|
||||
First clones, then returns a concrete version of this package
|
||||
@@ -2984,14 +2995,9 @@ def validate_or_raise(self):
|
||||
spack.repo.PATH.get_pkg_class(spec.fullname)
|
||||
|
||||
# validate compiler in addition to the package name.
|
||||
if spec.dependencies(deptype="build"):
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(spec.fullname)
|
||||
# FIXME (compiler as nodes): raise if we use %gcc on pkgs that do not depend on C
|
||||
pkg_dependencies = pkg_cls.dependency_names()
|
||||
if not any(x in pkg_dependencies for x in ("c", "cxx", "fortran")):
|
||||
raise UnsupportedCompilerError(
|
||||
f"{spec.fullname} does not depend on 'c', 'cxx, or 'fortran'"
|
||||
)
|
||||
if spec.compiler:
|
||||
if not spack.compilers.supported(spec.compiler):
|
||||
raise UnsupportedCompilerError(spec.compiler.name)
|
||||
|
||||
# Ensure correctness of variants (if the spec is not virtual)
|
||||
if not spec.virtual:
|
||||
@@ -3098,6 +3104,12 @@ def constrain(self, other, deps=True):
|
||||
self.namespace = other.namespace
|
||||
changed = True
|
||||
|
||||
if self.compiler is not None and other.compiler is not None:
|
||||
changed |= self.compiler.constrain(other.compiler)
|
||||
elif self.compiler is None:
|
||||
changed |= self.compiler != other.compiler
|
||||
self.compiler = other.compiler
|
||||
|
||||
changed |= self.versions.intersect(other.versions)
|
||||
changed |= self.variants.constrain(other.variants)
|
||||
|
||||
@@ -3124,8 +3136,10 @@ def constrain(self, other, deps=True):
|
||||
|
||||
return changed
|
||||
|
||||
def _constrain_dependencies(self, other: "Spec") -> bool:
|
||||
def _constrain_dependencies(self, other):
|
||||
"""Apply constraints of other spec's dependencies to this spec."""
|
||||
other = self._autospec(other)
|
||||
|
||||
if not other._dependencies:
|
||||
return False
|
||||
|
||||
@@ -3140,10 +3154,8 @@ def _constrain_dependencies(self, other: "Spec") -> bool:
|
||||
|
||||
# Handle common first-order constraints directly
|
||||
changed = False
|
||||
common_dependencies = {x.name for x in self.dependencies()}
|
||||
common_dependencies &= {x.name for x in other.dependencies()}
|
||||
for name in common_dependencies:
|
||||
changed |= self[name].constrain(other[name], deps=True)
|
||||
for name in self.common_dependencies(other):
|
||||
changed |= self[name].constrain(other[name], deps=False)
|
||||
if name in self._dependencies:
|
||||
# WARNING: This function is an implementation detail of the
|
||||
# WARNING: original concretizer. Since with that greedy
|
||||
@@ -3166,14 +3178,13 @@ def _constrain_dependencies(self, other: "Spec") -> bool:
|
||||
dep_spec_copy.spec.copy(),
|
||||
depflag=dep_spec_copy.depflag,
|
||||
virtuals=dep_spec_copy.virtuals,
|
||||
direct=dep_spec_copy.direct,
|
||||
)
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
def common_dependencies(self, other):
|
||||
"""Return names of dependencies that self and other have in common."""
|
||||
"""Return names of dependencies that self an other have in common."""
|
||||
common = set(s.name for s in self.traverse(root=False))
|
||||
common.intersection_update(s.name for s in other.traverse(root=False))
|
||||
return common
|
||||
@@ -3273,6 +3284,10 @@ def intersects(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
|
||||
if not self.versions.intersects(other.versions):
|
||||
return False
|
||||
|
||||
if self.compiler and other.compiler:
|
||||
if not self.compiler.intersects(other.compiler):
|
||||
return False
|
||||
|
||||
if not self.variants.intersects(other.variants):
|
||||
return False
|
||||
|
||||
@@ -3295,10 +3310,8 @@ def _intersects_dependencies(self, other):
|
||||
return True
|
||||
|
||||
# Handle first-order constraints directly
|
||||
common_dependencies = {x.name for x in self.dependencies()}
|
||||
common_dependencies &= {x.name for x in other.dependencies()}
|
||||
for name in common_dependencies:
|
||||
if not self[name].intersects(other[name], deps=True):
|
||||
for name in self.common_dependencies(other):
|
||||
if not self[name].intersects(other[name], deps=False):
|
||||
return False
|
||||
|
||||
# For virtual dependencies, we need to dig a little deeper.
|
||||
@@ -3375,6 +3388,12 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
|
||||
if not self.versions.satisfies(other.versions):
|
||||
return False
|
||||
|
||||
if self.compiler and other.compiler:
|
||||
if not self.compiler.satisfies(other.compiler):
|
||||
return False
|
||||
elif other.compiler and not self.compiler:
|
||||
return False
|
||||
|
||||
if not self.variants.satisfies(other.variants):
|
||||
return False
|
||||
|
||||
@@ -3412,19 +3431,6 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
|
||||
if rhs_edge.spec.virtual:
|
||||
rhs_edge.update_virtuals(virtuals=(rhs_edge.spec.name,))
|
||||
|
||||
if rhs_edge.direct:
|
||||
# Note: this relies on abstract specs from string not being deeper than 2 levels
|
||||
# e.g. in foo %fee ^bar %baz we cannot go deeper than "baz" and e.g. specify its
|
||||
# dependencies too.
|
||||
current_node = self if rhs_edge.parent.name is None else self[rhs_edge.parent.name]
|
||||
candidates = current_node.dependencies(
|
||||
name=rhs_edge.spec.name,
|
||||
deptype=rhs_edge.depflag,
|
||||
virtuals=rhs_edge.virtuals or None,
|
||||
)
|
||||
if not candidates or not any(x.satisfies(rhs_edge.spec) for x in candidates):
|
||||
return False
|
||||
|
||||
if not rhs_edge.virtuals:
|
||||
continue
|
||||
|
||||
@@ -3528,6 +3534,7 @@ def _dup(self, other, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, clearde
|
||||
self.name != other.name
|
||||
and self.versions != other.versions
|
||||
and self.architecture != other.architecture
|
||||
and self.compiler != other.compiler
|
||||
and self.variants != other.variants
|
||||
and self._normal != other._normal
|
||||
and self.concrete != other.concrete
|
||||
@@ -3543,10 +3550,10 @@ def _dup(self, other, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, clearde
|
||||
self.name = other.name
|
||||
self.versions = other.versions.copy()
|
||||
self.architecture = other.architecture.copy() if other.architecture else None
|
||||
self.compiler = other.compiler.copy() if other.compiler else None
|
||||
if cleardeps:
|
||||
self._dependents = _EdgeMap(store_by_child=False)
|
||||
self._dependencies = _EdgeMap(store_by_child=True)
|
||||
|
||||
self.compiler_flags = other.compiler_flags.copy()
|
||||
self.compiler_flags.spec = self
|
||||
self.variants = other.variants.copy()
|
||||
@@ -3565,7 +3572,6 @@ def _dup(self, other, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, clearde
|
||||
self.external_modules = other.external_modules
|
||||
self.extra_attributes = other.extra_attributes
|
||||
self.namespace = other.namespace
|
||||
self.annotations = other.annotations
|
||||
|
||||
# If we copy dependencies, preserve DAG structure in the new spec
|
||||
if deps:
|
||||
@@ -3611,10 +3617,7 @@ def spid(spec):
|
||||
new_specs[spid(edge.spec)] = edge.spec.copy(deps=False)
|
||||
|
||||
new_specs[spid(edge.parent)].add_dependency_edge(
|
||||
new_specs[spid(edge.spec)],
|
||||
depflag=edge.depflag,
|
||||
virtuals=edge.virtuals,
|
||||
direct=edge.direct,
|
||||
new_specs[spid(edge.spec)], depflag=edge.depflag, virtuals=edge.virtuals
|
||||
)
|
||||
|
||||
def copy(self, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, **kwargs):
|
||||
@@ -3675,14 +3678,21 @@ def __getitem__(self, name: str):
|
||||
query_parameters = re.split(r"\s*,\s*", csv)
|
||||
|
||||
order = lambda: itertools.chain(
|
||||
self.traverse_edges(deptype=dt.LINK | dt.RUN, order="breadth", cover="edges"),
|
||||
self.edges_to_dependencies(depflag=dt.BUILD | dt.TEST),
|
||||
self.traverse_edges(deptype=dt.LINK, order="breadth", cover="edges"),
|
||||
self.edges_to_dependencies(depflag=dt.BUILD | dt.RUN | dt.TEST),
|
||||
self.traverse_edges(deptype=dt.ALL, order="breadth", cover="edges"),
|
||||
)
|
||||
|
||||
# Consider runtime dependencies and direct build/test deps only
|
||||
# Consider runtime dependencies and direct build/test deps before transitive dependencies,
|
||||
# and prefer matches closest to the root.
|
||||
try:
|
||||
child: Spec = next(
|
||||
e.spec for e in order() if e.spec.name == name or name in e.virtuals
|
||||
e.spec
|
||||
for e in itertools.chain(
|
||||
(e for e in order() if e.spec.name == name or name in e.virtuals),
|
||||
# for historical reasons
|
||||
(e for e in order() if e.spec.concrete and e.spec.package.provides(name)),
|
||||
)
|
||||
)
|
||||
except StopIteration:
|
||||
raise KeyError(f"No spec with name {name} in {self}")
|
||||
@@ -3706,11 +3716,8 @@ def __contains__(self, spec):
|
||||
# if anonymous or same name, we only have to look at the root
|
||||
if not spec.name or spec.name == self.name:
|
||||
return self.satisfies(spec)
|
||||
|
||||
try:
|
||||
return self[spec.name].satisfies(spec)
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
return any(s.satisfies(spec) for s in self.traverse(root=False))
|
||||
|
||||
def eq_dag(self, other, deptypes=True, vs=None, vo=None):
|
||||
"""True if the full dependency DAGs of specs are equal."""
|
||||
@@ -3760,6 +3767,7 @@ def _cmp_node(self):
|
||||
yield self.namespace
|
||||
yield self.versions
|
||||
yield self.variants
|
||||
yield self.compiler
|
||||
yield self.compiler_flags
|
||||
yield self.architecture
|
||||
yield self.abstract_hash
|
||||
@@ -3813,6 +3821,9 @@ def format(self, format_string: str = DEFAULT_FORMAT, color: Optional[bool] = Fa
|
||||
|
||||
name
|
||||
version
|
||||
compiler
|
||||
compiler.name
|
||||
compiler.version
|
||||
compiler_flags
|
||||
variants
|
||||
architecture
|
||||
@@ -3954,9 +3965,6 @@ def format_attribute(match_object: Match) -> str:
|
||||
try:
|
||||
current = getattr(current, part)
|
||||
except AttributeError:
|
||||
if part == "compiler":
|
||||
return "none"
|
||||
|
||||
raise SpecFormatStringError(
|
||||
f"Attempted to format attribute {attribute}. "
|
||||
f"Spec {'.'.join(parts[:idx])} has no attribute {part}"
|
||||
@@ -4058,28 +4066,15 @@ def __str__(self):
|
||||
if not self._dependencies:
|
||||
return self.format()
|
||||
|
||||
name_conversion = {
|
||||
"llvm": "clang",
|
||||
"intel-oneapi-compilers": "oneapi",
|
||||
"llvm-amdgpu": "rocmcc",
|
||||
"intel-oneapi-compiler-classic": "intel",
|
||||
"acfl": "arm",
|
||||
}
|
||||
parts = [self.format()]
|
||||
direct, transitive = lang.stable_partition(
|
||||
self.edges_to_dependencies(), predicate_fn=lambda x: x.direct
|
||||
root_str = [self.format()]
|
||||
sorted_dependencies = sorted(
|
||||
self.traverse(root=False), key=lambda x: (x.name, x.abstract_hash)
|
||||
)
|
||||
for item in sorted(direct, key=lambda x: x.spec.name):
|
||||
current_name = item.spec.name
|
||||
new_name = name_conversion.get(current_name, current_name)
|
||||
parts.append(f"%{item.spec.format()}".replace(current_name, new_name))
|
||||
for item in sorted(transitive, key=lambda x: x.spec.name):
|
||||
# Recurse to attach build deps in order
|
||||
edge_attributes = ""
|
||||
if item.virtuals or item.depflag:
|
||||
edge_attributes = item.spec.format("{edge_attributes}") + " "
|
||||
parts.append(f"^{edge_attributes}{str(item.spec)}")
|
||||
return " ".join(parts).strip()
|
||||
sorted_dependencies = [
|
||||
d.format("{edge_attributes} " + DEFAULT_FORMAT) for d in sorted_dependencies
|
||||
]
|
||||
spec_str = " ^".join(root_str + sorted_dependencies)
|
||||
return spec_str.strip()
|
||||
|
||||
@property
|
||||
def colored_str(self):
|
||||
@@ -4500,10 +4495,6 @@ def attach_git_version_lookup(self):
|
||||
if isinstance(v, vn.GitVersion) and v._ref_version is None:
|
||||
v.attach_lookup(spack.version.git_ref_lookup.GitRefLookup(self.fullname))
|
||||
|
||||
def original_spec_format(self) -> int:
|
||||
"""Returns the spec format originally used for this spec."""
|
||||
return self.annotations.original_spec_format
|
||||
|
||||
|
||||
class VariantMap(lang.HashableMap):
|
||||
"""Map containing variant instances. New values can be added only
|
||||
@@ -4733,9 +4724,9 @@ def substitute_abstract_variants(spec: Spec):
|
||||
)
|
||||
|
||||
|
||||
def parse_with_version_concrete(spec_like: Union[str, Spec]):
|
||||
def parse_with_version_concrete(spec_like: Union[str, Spec], compiler: bool = False):
|
||||
"""Same as Spec(string), but interprets @x as @=x"""
|
||||
s = Spec(spec_like)
|
||||
s: Union[CompilerSpec, Spec] = CompilerSpec(spec_like) if compiler else Spec(spec_like)
|
||||
interpreted_version = s.versions.concrete_range_as_version
|
||||
if interpreted_version:
|
||||
s.versions = vn.VersionList([interpreted_version])
|
||||
@@ -4817,6 +4808,11 @@ def from_node_dict(cls, node):
|
||||
if "arch" in node:
|
||||
spec.architecture = ArchSpec.from_dict(node)
|
||||
|
||||
if "compiler" in node:
|
||||
spec.compiler = CompilerSpec.from_dict(node)
|
||||
else:
|
||||
spec.compiler = None
|
||||
|
||||
propagated_names = node.get("propagate", [])
|
||||
for name, values in node.get("parameters", {}).items():
|
||||
propagate = name in propagated_names
|
||||
@@ -4857,28 +4853,12 @@ def from_node_dict(cls, node):
|
||||
# FIXME: Monkey patches mvar to store patches order
|
||||
mvar._patches_in_order_of_appearance = patches
|
||||
|
||||
# Annotate the compiler spec, might be used later
|
||||
if "annotations" not in node:
|
||||
# Specfile v4 and earlier
|
||||
spec.annotations.with_spec_format(cls.SPEC_VERSION)
|
||||
if "compiler" in node:
|
||||
spec.annotations.with_compiler(cls.legacy_compiler(node))
|
||||
else:
|
||||
spec.annotations.with_spec_format(node["annotations"]["original_specfile_version"])
|
||||
if "compiler" in node["annotations"]:
|
||||
spec.annotations.with_compiler(Spec(f"{node['annotations']['compiler']}"))
|
||||
|
||||
# Don't read dependencies here; from_dict() is used by
|
||||
# from_yaml() and from_json() to read the root *and* each dependency
|
||||
# spec.
|
||||
|
||||
return spec
|
||||
|
||||
@classmethod
|
||||
def legacy_compiler(cls, node):
|
||||
d = node["compiler"]
|
||||
return Spec(f"{d['name']}@{vn.VersionList.from_dict(d)}")
|
||||
|
||||
@classmethod
|
||||
def _load(cls, data):
|
||||
"""Construct a spec from JSON/YAML using the format version 2.
|
||||
@@ -4945,8 +4925,6 @@ def read_specfile_dep_specs(cls, deps, hash_type=ht.dag_hash.name):
|
||||
|
||||
|
||||
class SpecfileV1(SpecfileReaderBase):
|
||||
SPEC_VERSION = 1
|
||||
|
||||
@classmethod
|
||||
def load(cls, data):
|
||||
"""Construct a spec from JSON/YAML using the format version 1.
|
||||
@@ -5016,8 +4994,6 @@ def read_specfile_dep_specs(cls, deps, hash_type=ht.dag_hash.name):
|
||||
|
||||
|
||||
class SpecfileV2(SpecfileReaderBase):
|
||||
SPEC_VERSION = 2
|
||||
|
||||
@classmethod
|
||||
def load(cls, data):
|
||||
result = cls._load(data)
|
||||
@@ -5072,12 +5048,10 @@ def extract_build_spec_info_from_node_dict(cls, node, hash_type=ht.dag_hash.name
|
||||
|
||||
|
||||
class SpecfileV3(SpecfileV2):
|
||||
SPEC_VERSION = 3
|
||||
pass
|
||||
|
||||
|
||||
class SpecfileV4(SpecfileV2):
|
||||
SPEC_VERSION = 4
|
||||
|
||||
@classmethod
|
||||
def extract_info_from_dep(cls, elt, hash):
|
||||
dep_hash = elt[hash.name]
|
||||
@@ -5091,14 +5065,6 @@ def load(cls, data):
|
||||
return cls._load(data)
|
||||
|
||||
|
||||
class SpecfileV5(SpecfileV4):
|
||||
SPEC_VERSION = 5
|
||||
|
||||
@classmethod
|
||||
def legacy_compiler(cls, node):
|
||||
raise RuntimeError("The 'compiler' option is unexpected in specfiles at v5 or greater")
|
||||
|
||||
|
||||
class LazySpecCache(collections.defaultdict):
|
||||
"""Cache for Specs that uses a spec_like as key, and computes lazily
|
||||
the corresponding value ``Spec(spec_like``.
|
||||
@@ -5216,6 +5182,9 @@ class DuplicateCompilerSpecError(spack.error.SpecError):
|
||||
class UnsupportedCompilerError(spack.error.SpecError):
|
||||
"""Raised when the user asks for a compiler spack doesn't know about."""
|
||||
|
||||
def __init__(self, compiler_name):
|
||||
super().__init__("The '%s' compiler is not yet supported." % compiler_name)
|
||||
|
||||
|
||||
class DuplicateArchitectureError(spack.error.SpecError):
|
||||
"""Raised when the same architecture occurs in a spec twice."""
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
from spack.installer import PackageInstaller
|
||||
from spack.spec import Spec
|
||||
|
||||
pytestmark = [pytest.mark.skip(reason="FIXME (compiler as nodes): fix splicing tests")]
|
||||
|
||||
|
||||
class CacheManager:
|
||||
def __init__(self, specs: List[str]) -> None:
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
import spack.binary_distribution as bindist
|
||||
import spack.caches
|
||||
import spack.compilers.config
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.fetch_strategy
|
||||
import spack.hooks.sbang as sbang
|
||||
@@ -84,7 +84,7 @@ def config_directory(tmp_path_factory):
|
||||
for name in [f"site/{platform.system().lower()}", "site", "user"]
|
||||
]
|
||||
with spack.config.use_configuration(*cfg_scopes):
|
||||
_ = spack.compilers.config.find_compilers(scope="site")
|
||||
_ = spack.compilers.find_compilers(scope="site")
|
||||
|
||||
yield defaults_dir
|
||||
|
||||
@@ -392,7 +392,7 @@ def test_spec_needs_rebuild(monkeypatch, tmpdir):
|
||||
s = Spec("libdwarf").concretized()
|
||||
|
||||
# Install a package
|
||||
install_cmd("--fake", s.name)
|
||||
install_cmd(s.name)
|
||||
|
||||
# Put installed package in the buildcache
|
||||
buildcache_cmd("push", "-u", mirror_dir.strpath, s.name)
|
||||
@@ -421,7 +421,7 @@ def test_generate_index_missing(monkeypatch, tmpdir, mutable_config):
|
||||
s = Spec("libdwarf").concretized()
|
||||
|
||||
# Install a package
|
||||
install_cmd("--fake", "--no-cache", s.name)
|
||||
install_cmd("--no-cache", s.name)
|
||||
|
||||
# Create a buildcache and update index
|
||||
buildcache_cmd("push", "-u", mirror_dir.strpath, s.name)
|
||||
@@ -573,8 +573,11 @@ def test_install_legacy_buildcache_layout(mutable_config, compiler_factory, inst
|
||||
where the .spack file contained a repeated spec.json and another
|
||||
compressed archive file containing the install tree. This test
|
||||
makes sure we can still read that layout."""
|
||||
mutable_config.set(
|
||||
"compilers", [compiler_factory(spec="gcc@4.5.0", operating_system="debian6")]
|
||||
)
|
||||
legacy_layout_dir = os.path.join(test_path, "data", "mirrors", "legacy_layout")
|
||||
mirror_url = f"file://{legacy_layout_dir}"
|
||||
mirror_url = "file://{0}".format(legacy_layout_dir)
|
||||
filename = (
|
||||
"test-debian6-core2-gcc-4.5.0-archive-files-2.0-"
|
||||
"l3vdiqvbobmspwyb4q2b62fz6nitd4hk.spec.json"
|
||||
@@ -583,7 +586,9 @@ def test_install_legacy_buildcache_layout(mutable_config, compiler_factory, inst
|
||||
mirror_cmd("add", "--scope", "site", "test-legacy-layout", mirror_url)
|
||||
output = install_cmd("--no-check-signature", "--cache-only", "-f", spec_json_path, output=str)
|
||||
mirror_cmd("rm", "--scope=site", "test-legacy-layout")
|
||||
expect_line = "Extracting archive-files-2.0-l3vdiqvbobmspwyb4q2b62fz6nitd4hk from binary cache"
|
||||
expect_line = (
|
||||
"Extracting archive-files-2.0-" "l3vdiqvbobmspwyb4q2b62fz6nitd4hk from binary cache"
|
||||
)
|
||||
assert expect_line in output
|
||||
|
||||
|
||||
@@ -1185,11 +1190,10 @@ def test_get_valid_spec_file_no_json(tmp_path, filename):
|
||||
bindist._get_valid_spec_file(str(tmp_path / filename), max_supported_layout=1)
|
||||
|
||||
|
||||
def test_download_tarball_with_unsupported_layout_fails(
|
||||
tmp_path, mock_packages, mutable_config, capsys
|
||||
):
|
||||
def test_download_tarball_with_unsupported_layout_fails(tmp_path, mutable_config, capsys):
|
||||
layout_version = bindist.CURRENT_BUILD_CACHE_LAYOUT_VERSION + 1
|
||||
spec = Spec("pkg-c").concretized()
|
||||
spec = Spec("gmake@4.4.1%gcc@13.1.0 arch=linux-ubuntu23.04-zen2")
|
||||
spec._mark_concrete()
|
||||
spec_dict = spec.to_dict()
|
||||
spec_dict["buildcache_layout_version"] = layout_version
|
||||
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
import spack.bootstrap
|
||||
import spack.bootstrap.config
|
||||
import spack.bootstrap.core
|
||||
import spack.compilers.config
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.environment
|
||||
import spack.store
|
||||
import spack.util.path
|
||||
|
||||
from .conftest import _true
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def active_mock_environment(mutable_config, mutable_mock_env_path):
|
||||
@@ -96,14 +94,12 @@ def test_raising_exception_if_bootstrap_disabled(mutable_config):
|
||||
spack.bootstrap.config.store_path()
|
||||
|
||||
|
||||
def test_raising_exception_module_importable(config, monkeypatch):
|
||||
monkeypatch.setattr(spack.bootstrap.core, "source_is_enabled", _true)
|
||||
def test_raising_exception_module_importable():
|
||||
with pytest.raises(ImportError, match='cannot bootstrap the "asdf" Python module'):
|
||||
spack.bootstrap.core.ensure_module_importable_or_raise("asdf")
|
||||
|
||||
|
||||
def test_raising_exception_executables_in_path(config, monkeypatch):
|
||||
monkeypatch.setattr(spack.bootstrap.core, "source_is_enabled", _true)
|
||||
def test_raising_exception_executables_in_path():
|
||||
with pytest.raises(RuntimeError, match="cannot bootstrap any of the asdf, fdsa executables"):
|
||||
spack.bootstrap.core.ensure_executables_in_path_or_raise(["asdf", "fdsa"], "python")
|
||||
|
||||
@@ -132,22 +128,22 @@ def test_bootstrap_disables_modulefile_generation(mutable_config):
|
||||
|
||||
@pytest.mark.regression("25992")
|
||||
@pytest.mark.requires_executables("gcc")
|
||||
def test_bootstrap_search_for_compilers_with_no_environment(no_packages_yaml):
|
||||
assert not spack.compilers.config.all_compilers(init_config=False)
|
||||
def test_bootstrap_search_for_compilers_with_no_environment(no_compilers_yaml):
|
||||
assert not spack.compilers.all_compiler_specs(init_config=False)
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
assert spack.compilers.config.all_compilers(init_config=False)
|
||||
assert not spack.compilers.config.all_compilers(init_config=False)
|
||||
assert spack.compilers.all_compiler_specs(init_config=False)
|
||||
assert not spack.compilers.all_compiler_specs(init_config=False)
|
||||
|
||||
|
||||
@pytest.mark.regression("25992")
|
||||
@pytest.mark.requires_executables("gcc")
|
||||
def test_bootstrap_search_for_compilers_with_environment_active(
|
||||
no_packages_yaml, active_mock_environment
|
||||
no_compilers_yaml, active_mock_environment
|
||||
):
|
||||
assert not spack.compilers.config.all_compilers(init_config=False)
|
||||
assert not spack.compilers.all_compiler_specs(init_config=False)
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
assert spack.compilers.config.all_compilers(init_config=False)
|
||||
assert not spack.compilers.config.all_compilers(init_config=False)
|
||||
assert spack.compilers.all_compiler_specs(init_config=False)
|
||||
assert not spack.compilers.all_compiler_specs(init_config=False)
|
||||
|
||||
|
||||
@pytest.mark.regression("26189")
|
||||
@@ -223,12 +219,16 @@ def test_source_is_disabled(mutable_config):
|
||||
# Get the configuration dictionary of the current bootstrapping source
|
||||
conf = next(iter(spack.bootstrap.core.bootstrapping_sources()))
|
||||
|
||||
# The source is not explicitly enabled or disabled, so the following should return False
|
||||
assert not spack.bootstrap.core.source_is_enabled(conf)
|
||||
# The source is not explicitly enabled or disabled, so the following
|
||||
# call should raise to skip using it for bootstrapping
|
||||
with pytest.raises(ValueError):
|
||||
spack.bootstrap.core.source_is_enabled_or_raise(conf)
|
||||
|
||||
# Try to explicitly disable the source and verify that the behavior is the same as above
|
||||
# Try to explicitly disable the source and verify that the behavior
|
||||
# is the same as above
|
||||
spack.config.add("bootstrap:trusted:{0}:{1}".format(conf["name"], False))
|
||||
assert not spack.bootstrap.core.source_is_enabled(conf)
|
||||
with pytest.raises(ValueError):
|
||||
spack.bootstrap.core.source_is_enabled_or_raise(conf)
|
||||
|
||||
|
||||
@pytest.mark.regression("45247")
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import os
|
||||
import platform
|
||||
import posixpath
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -14,13 +15,13 @@
|
||||
from llnl.util.filesystem import HeaderList, LibraryList
|
||||
|
||||
import spack.build_environment
|
||||
import spack.build_systems.compiler
|
||||
import spack.compiler
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.deptypes as dt
|
||||
import spack.package_base
|
||||
import spack.paths
|
||||
import spack.spec
|
||||
import spack.util.environment
|
||||
import spack.util.spack_yaml as syaml
|
||||
from spack.build_environment import UseMode, _static_to_shared_library, dso_suffix
|
||||
from spack.context import Context
|
||||
@@ -96,7 +97,7 @@ def build_environment(working_env):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ensure_env_variables(mutable_config, mock_packages, monkeypatch, working_env):
|
||||
def ensure_env_variables(config, mock_packages, monkeypatch, working_env):
|
||||
"""Returns a function that takes a dictionary and updates os.environ
|
||||
for the test lifetime accordingly. Plugs-in mock config and repo.
|
||||
"""
|
||||
@@ -161,24 +162,20 @@ def test_static_to_shared_library(build_environment):
|
||||
|
||||
|
||||
@pytest.mark.regression("8345")
|
||||
@pytest.mark.usefixtures("mock_packages")
|
||||
@pytest.mark.not_on_windows("Module files are not supported on Windows")
|
||||
def test_cc_not_changed_by_modules(monkeypatch, mutable_config, working_env, compiler_factory):
|
||||
"""Tests that external module files that are loaded cannot change the
|
||||
CC environment variable.
|
||||
"""
|
||||
gcc_entry = compiler_factory(spec="gcc@14.0.1 languages=c,c++")
|
||||
gcc_entry["modules"] = ["some_module"]
|
||||
mutable_config.set("packages", {"gcc": {"externals": [gcc_entry]}})
|
||||
@pytest.mark.usefixtures("config", "mock_packages")
|
||||
def test_cc_not_changed_by_modules(monkeypatch, working_env):
|
||||
s = spack.spec.Spec("cmake")
|
||||
s.concretize()
|
||||
pkg = s.package
|
||||
|
||||
def _set_wrong_cc(x):
|
||||
os.environ["CC"] = "NOT_THIS_PLEASE"
|
||||
os.environ["ANOTHER_VAR"] = "THIS_IS_SET"
|
||||
|
||||
monkeypatch.setattr(spack.build_environment, "load_module", _set_wrong_cc)
|
||||
monkeypatch.setattr(pkg.compiler, "modules", ["some_module"])
|
||||
|
||||
s = spack.spec.Spec("cmake %gcc@14").concretized()
|
||||
spack.build_environment.setup_package(s.package, dirty=False)
|
||||
spack.build_environment.setup_package(pkg, False)
|
||||
|
||||
assert os.environ["CC"] != "NOT_THIS_PLEASE"
|
||||
assert os.environ["ANOTHER_VAR"] == "THIS_IS_SET"
|
||||
@@ -189,7 +186,7 @@ def test_setup_dependent_package_inherited_modules(
|
||||
):
|
||||
# This will raise on regression
|
||||
s = spack.spec.Spec("cmake-client-inheritor").concretized()
|
||||
PackageInstaller([s.package], fake=True).install()
|
||||
PackageInstaller([s.package]).install()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -269,30 +266,22 @@ def test_setup_dependent_package_inherited_modules(
|
||||
],
|
||||
)
|
||||
def test_compiler_config_modifications(
|
||||
initial,
|
||||
modifications,
|
||||
expected,
|
||||
ensure_env_variables,
|
||||
compiler_factory,
|
||||
mutable_config,
|
||||
monkeypatch,
|
||||
initial, modifications, expected, ensure_env_variables, monkeypatch
|
||||
):
|
||||
# Set the environment as per prerequisites
|
||||
ensure_env_variables(initial)
|
||||
|
||||
gcc_entry = compiler_factory(spec="gcc@14.0.1 languages=c,c++")
|
||||
gcc_entry["extra_attributes"]["environment"] = modifications
|
||||
mutable_config.set("packages", {"gcc": {"externals": [gcc_entry]}})
|
||||
|
||||
def platform_pathsep(pathlist):
|
||||
if Path.platform_path == Path.windows:
|
||||
pathlist = pathlist.replace(":", ";")
|
||||
|
||||
return convert_to_platform_path(pathlist)
|
||||
|
||||
pkg = spack.spec.Spec("cmake %gcc@14").concretized().package
|
||||
# Monkeypatch a pkg.compiler.environment with the required modifications
|
||||
pkg = spack.spec.Spec("cmake").concretized().package
|
||||
monkeypatch.setattr(pkg.compiler, "environment", modifications)
|
||||
# Trigger the modifications
|
||||
spack.build_environment.setup_package(pkg, dirty=False)
|
||||
spack.build_environment.setup_package(pkg, False)
|
||||
|
||||
# Check they were applied
|
||||
for name, value in expected.items():
|
||||
@@ -303,6 +292,25 @@ def platform_pathsep(pathlist):
|
||||
assert name not in os.environ
|
||||
|
||||
|
||||
def test_compiler_custom_env(config, mock_packages, monkeypatch, working_env):
|
||||
if sys.platform == "win32":
|
||||
test_path = r"C:\test\path\element\custom-env" + "\\"
|
||||
else:
|
||||
test_path = r"/test/path/element/custom-env/"
|
||||
|
||||
def custom_env(pkg, env):
|
||||
env.prepend_path("PATH", test_path)
|
||||
env.append_flags("ENV_CUSTOM_CC_FLAGS", "--custom-env-flag1")
|
||||
|
||||
pkg = spack.spec.Spec("cmake").concretized().package
|
||||
monkeypatch.setattr(pkg.compiler, "setup_custom_environment", custom_env)
|
||||
spack.build_environment.setup_package(pkg, False)
|
||||
|
||||
# Note: trailing slash may be stripped by internal logic
|
||||
assert test_path[:-1] in os.environ["PATH"]
|
||||
assert "--custom-env-flag1" in os.environ["ENV_CUSTOM_CC_FLAGS"]
|
||||
|
||||
|
||||
def test_external_config_env(mock_packages, mutable_config, working_env):
|
||||
cmake_config = {
|
||||
"externals": [
|
||||
@@ -322,27 +330,25 @@ def test_external_config_env(mock_packages, mutable_config, working_env):
|
||||
|
||||
|
||||
@pytest.mark.regression("9107")
|
||||
@pytest.mark.not_on_windows("Windows does not support module files")
|
||||
def test_spack_paths_before_module_paths(
|
||||
mutable_config, mock_packages, compiler_factory, monkeypatch, working_env
|
||||
):
|
||||
gcc_entry = compiler_factory(spec="gcc@14.0.1 languages=c,c++")
|
||||
gcc_entry["modules"] = ["some_module"]
|
||||
mutable_config.set("packages", {"gcc": {"externals": [gcc_entry]}})
|
||||
def test_spack_paths_before_module_paths(config, mock_packages, monkeypatch, working_env):
|
||||
s = spack.spec.Spec("cmake")
|
||||
s.concretize()
|
||||
pkg = s.package
|
||||
|
||||
module_path = os.path.join("path", "to", "module")
|
||||
spack_path = os.path.join(spack.paths.prefix, os.path.join("lib", "spack", "env"))
|
||||
|
||||
def _set_wrong_cc(x):
|
||||
os.environ["PATH"] = module_path + os.pathsep + os.environ["PATH"]
|
||||
|
||||
monkeypatch.setattr(spack.build_environment, "load_module", _set_wrong_cc)
|
||||
monkeypatch.setattr(pkg.compiler, "modules", ["some_module"])
|
||||
|
||||
s = spack.spec.Spec("cmake").concretized()
|
||||
spack.build_environment.setup_package(pkg, False)
|
||||
|
||||
spack.build_environment.setup_package(s.package, dirty=False)
|
||||
spack_path = os.path.join(spack.paths.prefix, os.path.join("lib", "spack", "env"))
|
||||
|
||||
paths = os.environ["PATH"].split(os.pathsep)
|
||||
|
||||
assert paths.index(spack_path) < paths.index(module_path)
|
||||
|
||||
|
||||
@@ -493,13 +499,15 @@ def test_parallel_false_is_not_propagating(default_mock_concretization):
|
||||
("rpath", "" if platform.system() == "Darwin" else "--disable-new-dtags"),
|
||||
],
|
||||
)
|
||||
def test_setting_dtags_based_on_config(
|
||||
config_setting, expected_flag, config, mock_packages, working_env
|
||||
):
|
||||
def test_setting_dtags_based_on_config(config_setting, expected_flag, config, mock_packages):
|
||||
# Pick a random package to be able to set compiler's variables
|
||||
s = spack.spec.Spec("cmake").concretized()
|
||||
s = spack.spec.Spec("cmake")
|
||||
s.concretize()
|
||||
pkg = s.package
|
||||
|
||||
env = EnvironmentModifications()
|
||||
with spack.config.override("config:shared_linking", {"type": config_setting, "bind": False}):
|
||||
env = spack.build_environment.setup_package(s.package, dirty=False)
|
||||
spack.build_environment.set_compiler_environment_variables(pkg, env)
|
||||
modifications = env.group_by_name()
|
||||
assert "SPACK_DTAGS_TO_STRIP" in modifications
|
||||
assert "SPACK_DTAGS_TO_ADD" in modifications
|
||||
@@ -762,44 +770,59 @@ def test_rpath_with_duplicate_link_deps():
|
||||
@pytest.mark.parametrize(
|
||||
"compiler_spec,target_name,expected_flags",
|
||||
[
|
||||
# Semver versions
|
||||
# Homogeneous compilers
|
||||
("gcc@4.7.2", "ivybridge", "-march=core-avx-i -mtune=core-avx-i"),
|
||||
("clang@3.5", "x86_64", "-march=x86-64 -mtune=generic"),
|
||||
("apple-clang@9.1.0", "x86_64", "-march=x86-64"),
|
||||
("gcc@=9.2.0", "haswell", "-march=haswell -mtune=haswell"),
|
||||
# Check that custom string versions are accepted
|
||||
("gcc@=9.2.0-foo", "icelake", "-march=icelake-client -mtune=icelake-client"),
|
||||
# Check that the special case for Apple's clang is treated correctly
|
||||
# i.e. it won't try to detect the version again
|
||||
("apple-clang@=9.1.0", "x86_64", "-march=x86-64"),
|
||||
# FIXME (compiler as nodes): Check mixed toolchain
|
||||
# ("clang@8.0.0", "broadwell", ""),
|
||||
# Mixed toolchain
|
||||
("clang@8.0.0", "broadwell", ""),
|
||||
],
|
||||
)
|
||||
@pytest.mark.filterwarnings("ignore:microarchitecture specific")
|
||||
@pytest.mark.not_on_windows("Windows doesn't support the compiler wrapper")
|
||||
def test_optimization_flags(compiler_spec, target_name, expected_flags, compiler_factory):
|
||||
target = archspec.cpu.TARGETS[target_name]
|
||||
compiler = spack.spec.parse_with_version_concrete(compiler_spec)
|
||||
compiler_dict = compiler_factory(spec=compiler_spec, operating_system="")["compiler"]
|
||||
if compiler_spec == "clang@8.0.0":
|
||||
compiler_dict["paths"] = {
|
||||
"cc": "/path/to/clang-8",
|
||||
"cxx": "/path/to/clang++-8",
|
||||
"f77": "/path/to/gfortran-9",
|
||||
"fc": "/path/to/gfortran-9",
|
||||
}
|
||||
compiler = spack.compilers.compiler_from_dict(compiler_dict)
|
||||
opt_flags = spack.build_environment.optimization_flags(compiler, target)
|
||||
assert opt_flags == expected_flags
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
str(archspec.cpu.host().family) != "x86_64", reason="tests check specific x86_64 uarch flags"
|
||||
@pytest.mark.parametrize(
|
||||
"compiler_str,real_version,target_str,expected_flags",
|
||||
[
|
||||
("gcc@=9.2.0", None, "haswell", "-march=haswell -mtune=haswell"),
|
||||
# Check that custom string versions are accepted
|
||||
("gcc@=10foo", "9.2.0", "icelake", "-march=icelake-client -mtune=icelake-client"),
|
||||
# Check that we run version detection (4.4.0 doesn't support icelake)
|
||||
("gcc@=4.4.0-special", "9.2.0", "icelake", "-march=icelake-client -mtune=icelake-client"),
|
||||
# Check that the special case for Apple's clang is treated correctly
|
||||
# i.e. it won't try to detect the version again
|
||||
("apple-clang@=9.1.0", None, "x86_64", "-march=x86-64"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.not_on_windows("Windows doesn't support the compiler wrapper")
|
||||
def test_optimization_flags_are_using_node_target(default_mock_concretization, monkeypatch):
|
||||
"""Tests that we are using the target on the node to be compiled to retrieve the uarch
|
||||
specific flags, and not the target of the compiler.
|
||||
"""
|
||||
monkeypatch.setattr(spack.build_systems.compiler, "_implicit_rpaths", lambda pkg: [])
|
||||
gcc = default_mock_concretization("gcc target=core2")
|
||||
mpileaks = default_mock_concretization("mpileaks target=x86_64")
|
||||
def test_optimization_flags_with_custom_versions(
|
||||
compiler_str,
|
||||
real_version,
|
||||
target_str,
|
||||
expected_flags,
|
||||
monkeypatch,
|
||||
mutable_config,
|
||||
compiler_factory,
|
||||
):
|
||||
target = archspec.cpu.TARGETS[target_str]
|
||||
compiler_dict = compiler_factory(spec=compiler_str, operating_system="redhat6")
|
||||
mutable_config.set("compilers", [compiler_dict])
|
||||
if real_version:
|
||||
monkeypatch.setattr(spack.compiler.Compiler, "get_real_version", lambda x: real_version)
|
||||
compiler = spack.compilers.compiler_from_dict(compiler_dict["compiler"])
|
||||
|
||||
env = EnvironmentModifications()
|
||||
gcc.package.setup_dependent_build_environment(env, mpileaks)
|
||||
actions = env.group_by_name()["SPACK_TARGET_ARGS_CC"]
|
||||
|
||||
assert len(actions) == 1 and isinstance(actions[0], spack.util.environment.SetEnv)
|
||||
assert actions[0].value == "-march=x86-64 -mtune=generic"
|
||||
opt_flags = spack.build_environment.optimization_flags(compiler, target)
|
||||
assert opt_flags == expected_flags
|
||||
|
||||
@@ -133,7 +133,6 @@
|
||||
headerpad = ["-headerpad_max_install_names"]
|
||||
|
||||
target_args = ["-march=znver2", "-mtune=znver2"]
|
||||
target_args_fc = ["-march=znver4", "-mtune=znver4"]
|
||||
|
||||
# common compile arguments: includes, libs, -Wl linker args, other args
|
||||
common_compile_args = (
|
||||
@@ -168,9 +167,7 @@ def wrapper_environment(working_env):
|
||||
SPACK_LINK_DIRS=None,
|
||||
SPACK_INCLUDE_DIRS=None,
|
||||
SPACK_RPATH_DIRS=None,
|
||||
SPACK_TARGET_ARGS_CC="-march=znver2 -mtune=znver2",
|
||||
SPACK_TARGET_ARGS_CXX="-march=znver2 -mtune=znver2",
|
||||
SPACK_TARGET_ARGS_FORTRAN="-march=znver4 -mtune=znver4",
|
||||
SPACK_TARGET_ARGS="-march=znver2 -mtune=znver2",
|
||||
SPACK_LINKER_ARG="-Wl,",
|
||||
SPACK_DTAGS_TO_ADD="--disable-new-dtags",
|
||||
SPACK_DTAGS_TO_STRIP="--enable-new-dtags",
|
||||
@@ -380,7 +377,7 @@ def test_fc_flags(wrapper_environment, wrapper_flags):
|
||||
fc,
|
||||
test_args,
|
||||
[real_cc]
|
||||
+ target_args_fc
|
||||
+ target_args
|
||||
+ test_include_paths
|
||||
+ ["-Lfoo"]
|
||||
+ test_library_paths
|
||||
@@ -427,7 +424,7 @@ def test_Wl_parsing_NAG_is_ignored(wrapper_environment):
|
||||
check_args(
|
||||
fc,
|
||||
["-Wl,-Wl,,x,,y,,z"],
|
||||
[real_cc] + target_args_fc + ["-Wl,--disable-new-dtags", "-Wl,-Wl,,x,,y,,z"],
|
||||
[real_cc] + target_args + ["-Wl,--disable-new-dtags", "-Wl,-Wl,,x,,y,,z"],
|
||||
)
|
||||
|
||||
|
||||
@@ -836,14 +833,14 @@ def test_no_ccache_prepend_for_fc(wrapper_environment):
|
||||
fc,
|
||||
test_args,
|
||||
# no ccache for Fortran
|
||||
[real_cc] + target_args_fc + common_compile_args,
|
||||
[real_cc] + target_args + common_compile_args,
|
||||
)
|
||||
os.environ["SPACK_SHORT_SPEC"] = "foo@1.2=darwin-x86_64"
|
||||
check_args(
|
||||
fc,
|
||||
test_args,
|
||||
# no ccache for Fortran
|
||||
[real_cc] + target_args_fc + lheaderpad + common_compile_args,
|
||||
[real_cc] + target_args + lheaderpad + common_compile_args,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -198,7 +198,6 @@ def __call__(self, *args, **kwargs):
|
||||
assert "Unable to merge {0}".format(c1) in err
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="FIXME (compiler as nodes): revisit this test")
|
||||
def test_get_spec_filter_list(mutable_mock_env_path, mutable_mock_repo):
|
||||
"""Test that given an active environment and list of touched pkgs,
|
||||
we get the right list of possibly-changed env specs"""
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
build_env = SpackCommand("build-env")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("pkg", [("pkg-c",), ("pkg-c", "--")])
|
||||
@pytest.mark.parametrize("pkg", [("zlib",), ("zlib", "--")])
|
||||
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
|
||||
def test_it_just_runs(pkg):
|
||||
build_env(*pkg)
|
||||
@@ -39,7 +39,7 @@ def test_build_env_requires_a_spec(args):
|
||||
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
|
||||
def test_dump(shell_as, shell, tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
build_env("--dump", _out_file, "pkg-c")
|
||||
build_env("--dump", _out_file, "zlib")
|
||||
with open(_out_file) as f:
|
||||
if shell == "pwsh":
|
||||
assert any(line.startswith("$Env:PATH") for line in f.readlines())
|
||||
@@ -52,7 +52,7 @@ def test_dump(shell_as, shell, tmpdir):
|
||||
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
|
||||
def test_pickle(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
build_env("--pickle", _out_file, "pkg-c")
|
||||
build_env("--pickle", _out_file, "zlib")
|
||||
environment = pickle.load(open(_out_file, "rb"))
|
||||
assert isinstance(environment, dict)
|
||||
assert "PATH" in environment
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user