Compare commits
248 Commits
packages/a
...
hs/fix/sim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd9e28a621 | ||
|
|
3d827b0ea5 | ||
|
|
45ec04ef7c | ||
|
|
fb4811ec3f | ||
|
|
202e64872a | ||
|
|
25ba3124bd | ||
|
|
df57e1ceb3 | ||
|
|
59b4b785e0 | ||
|
|
b1b21a4d02 | ||
|
|
b1af32cb60 | ||
|
|
9d8f94a7c8 | ||
|
|
1297673a70 | ||
|
|
0fee2c234e | ||
|
|
229cf49c71 | ||
|
|
394e6159d6 | ||
|
|
cbe18d9cbc | ||
|
|
2d83707f84 | ||
|
|
9a91f021a7 | ||
|
|
297e43b097 | ||
|
|
900765901d | ||
|
|
680d1f2e58 | ||
|
|
76957f19f9 | ||
|
|
c7001efeb8 | ||
|
|
a60d1084b1 | ||
|
|
497e19f0e3 | ||
|
|
cd6ee96398 | ||
|
|
904d85b53b | ||
|
|
199653dd31 | ||
|
|
fdfb4e9893 | ||
|
|
afa76ebbdc | ||
|
|
e5c045cc1c | ||
|
|
8c92836c39 | ||
|
|
a782e6bc33 | ||
|
|
4ede0ae5e3 | ||
|
|
986325eb0d | ||
|
|
8bcd64ce6c | ||
|
|
f079ad3690 | ||
|
|
8c1d6188e3 | ||
|
|
1d70ab934c | ||
|
|
fa704e867c | ||
|
|
85939b26ae | ||
|
|
a5436b3962 | ||
|
|
a8e25193e0 | ||
|
|
480d6f9911 | ||
|
|
02f329a8af | ||
|
|
2de712b35f | ||
|
|
aa49b3d8ce | ||
|
|
eccecba39a | ||
|
|
94c99fc5d4 | ||
|
|
1f1021a47f | ||
|
|
296e5308a7 | ||
|
|
47e79c32fd | ||
|
|
906799eec5 | ||
|
|
dcdcab7b2c | ||
|
|
86050decb9 | ||
|
|
fff8165f2f | ||
|
|
5a9dbcc0c4 | ||
|
|
bd627465f3 | ||
|
|
f96e8757b8 | ||
|
|
b8cbbb8e2e | ||
|
|
d40f847497 | ||
|
|
ed34dfca96 | ||
|
|
3ee6a5b96f | ||
|
|
88bcfddbbb | ||
|
|
c49269f9dd | ||
|
|
ef45c392e0 | ||
|
|
8b811171c7 | ||
|
|
823a2c1e4b | ||
|
|
ead25b1e9e | ||
|
|
d5eefcba87 | ||
|
|
1bcb1fcebc | ||
|
|
f19b657235 | ||
|
|
8e1bd9a403 | ||
|
|
ba56622574 | ||
|
|
836be2364c | ||
|
|
b623f58782 | ||
|
|
182bc87fe1 | ||
|
|
f93595ba2f | ||
|
|
aa5b17ceb5 | ||
|
|
eb5a1d3b4c | ||
|
|
84f680239e | ||
|
|
c7b693a0df | ||
|
|
7d5ad18573 | ||
|
|
2921e04353 | ||
|
|
33464a7038 | ||
|
|
d3cdb2a344 | ||
|
|
34df21b62c | ||
|
|
e8a13642a0 | ||
|
|
dc3c96dd2f | ||
|
|
c29652580a | ||
|
|
d714a9b223 | ||
|
|
f596a8cdad | ||
|
|
3699a0ec9b | ||
|
|
6c268234ba | ||
|
|
c1736077bb | ||
|
|
85905959dc | ||
|
|
2ae5596e92 | ||
|
|
9d0b9f086f | ||
|
|
da079ed06f | ||
|
|
a69c5b3e32 | ||
|
|
e3cce2bd96 | ||
|
|
0d668e4e92 | ||
|
|
ad6c7380c5 | ||
|
|
c064a30765 | ||
|
|
4a4f156d99 | ||
|
|
cb8878aaf4 | ||
|
|
d49f3a0960 | ||
|
|
15413c7258 | ||
|
|
de754c7a47 | ||
|
|
ac9398ed21 | ||
|
|
57769fac7d | ||
|
|
c65fd7e12d | ||
|
|
c71d778875 | ||
|
|
a7313dc407 | ||
|
|
31477d5dc7 | ||
|
|
382ba0d041 | ||
|
|
886c950423 | ||
|
|
3798b16a29 | ||
|
|
796617054d | ||
|
|
78fc25ec12 | ||
|
|
6de51fdc58 | ||
|
|
430ba496d1 | ||
|
|
e1ede9c04b | ||
|
|
856dd3417b | ||
|
|
e49c6f68bc | ||
|
|
eed7a1af24 | ||
|
|
22e40541c7 | ||
|
|
8561c89c25 | ||
|
|
6501705de2 | ||
|
|
0b3e1fd412 | ||
|
|
c260da5127 | ||
|
|
f63261dc65 | ||
|
|
1c081611ea | ||
|
|
428b4e340a | ||
|
|
20bf239a6a | ||
|
|
cd682613cf | ||
|
|
c1852e3706 | ||
|
|
855a8476e4 | ||
|
|
d4a892f200 | ||
|
|
66e2836ba1 | ||
|
|
52ab0c66fe | ||
|
|
f316068b27 | ||
|
|
553cc3b70a | ||
|
|
f0f9a16e4f | ||
|
|
9ec8eaa0d3 | ||
|
|
00182b19dc | ||
|
|
cc7a29c55a | ||
|
|
61b0f4f84d | ||
|
|
fe3bfa482e | ||
|
|
e5f53a6250 | ||
|
|
a7e8080784 | ||
|
|
f5e934f2dc | ||
|
|
54b57c5d1e | ||
|
|
725ef8f5c8 | ||
|
|
f51a9a9107 | ||
|
|
4f0e336ed0 | ||
|
|
64774f3015 | ||
|
|
4e9fbca033 | ||
|
|
a2fd26bbcc | ||
|
|
067da09b46 | ||
|
|
b1b0c108bb | ||
|
|
c624088a7b | ||
|
|
a965c7c5c8 | ||
|
|
904d43f0e6 | ||
|
|
10b6d7282a | ||
|
|
7112a49d1e | ||
|
|
b11bd6b745 | ||
|
|
4d0b04cf34 | ||
|
|
165c171659 | ||
|
|
aa3c62d936 | ||
|
|
cba2fe914c | ||
|
|
1b82779087 | ||
|
|
55b1b0f3f0 | ||
|
|
4606c8ed68 | ||
|
|
dd53eeb322 | ||
|
|
f42486b684 | ||
|
|
44ecea3813 | ||
|
|
f1114858f5 | ||
|
|
2b6bdc7013 | ||
|
|
586a35be43 | ||
|
|
7a8dc36760 | ||
|
|
e01151a200 | ||
|
|
29b50527a6 | ||
|
|
94961ffe0a | ||
|
|
03a7da1e44 | ||
|
|
97ffe2e575 | ||
|
|
7b10aae356 | ||
|
|
b61cd74707 | ||
|
|
374d94edf7 | ||
|
|
827522d825 | ||
|
|
8ba6e7eed2 | ||
|
|
e40c10509d | ||
|
|
21a2c3a591 | ||
|
|
70eb7506df | ||
|
|
2b95eecb83 | ||
|
|
df8507f470 | ||
|
|
645c8eeaeb | ||
|
|
b693987f95 | ||
|
|
7999686856 | ||
|
|
7001a2a65a | ||
|
|
7c985d6432 | ||
|
|
a66586d749 | ||
|
|
6b73f00310 | ||
|
|
063b987ceb | ||
|
|
fe19394bf9 | ||
|
|
e09955d83b | ||
|
|
d367f14d5e | ||
|
|
6f61e382da | ||
|
|
63e680e4f9 | ||
|
|
27557a133b | ||
|
|
78810e95ed | ||
|
|
553b44473f | ||
|
|
966a775a45 | ||
|
|
327c75386a | ||
|
|
a2cb7ee803 | ||
|
|
2a5d4b2291 | ||
|
|
3b59817ea7 | ||
|
|
06eacdf9d8 | ||
|
|
bfdcdb4851 | ||
|
|
83873d06a1 | ||
|
|
91333919c6 | ||
|
|
cd6237cac4 | ||
|
|
91412fb595 | ||
|
|
678c995415 | ||
|
|
63af548271 | ||
|
|
200dfb0346 | ||
|
|
e2f605f6e9 | ||
|
|
3cf1914b7e | ||
|
|
cd7a49114c | ||
|
|
1144487ee7 | ||
|
|
742b78d2b5 | ||
|
|
633d1d2ccb | ||
|
|
9adefd587e | ||
|
|
102a30a5a2 | ||
|
|
7ddc886d6d | ||
|
|
9e7183fb14 | ||
|
|
18ab3c20ce | ||
|
|
b91b42dc7b | ||
|
|
7900d0b3db | ||
|
|
847d7bc87d | ||
|
|
078984dcf4 | ||
|
|
010324714f | ||
|
|
7ce5ac1e6e | ||
|
|
565165f02d | ||
|
|
e4869cd558 | ||
|
|
990e0dc526 | ||
|
|
f9d8b6b5aa | ||
|
|
2079b888c8 |
2
.github/workflows/build-containers.yml
vendored
2
.github/workflows/build-containers.yml
vendored
@@ -113,7 +113,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build & Deploy ${{ matrix.dockerfile[0] }}
|
||||
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755
|
||||
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85
|
||||
with:
|
||||
context: dockerfiles/${{ matrix.dockerfile[0] }}
|
||||
platforms: ${{ matrix.dockerfile[1] }}
|
||||
|
||||
14
.github/workflows/unit_tests.yaml
vendored
14
.github/workflows/unit_tests.yaml
vendored
@@ -16,38 +16,27 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
|
||||
concretizer: ['clingo']
|
||||
on_develop:
|
||||
- ${{ github.ref == 'refs/heads/develop' }}
|
||||
include:
|
||||
- python-version: '3.11'
|
||||
os: ubuntu-latest
|
||||
concretizer: original
|
||||
on_develop: ${{ github.ref == 'refs/heads/develop' }}
|
||||
- python-version: '3.6'
|
||||
os: ubuntu-20.04
|
||||
concretizer: clingo
|
||||
on_develop: ${{ github.ref == 'refs/heads/develop' }}
|
||||
exclude:
|
||||
- python-version: '3.7'
|
||||
os: ubuntu-latest
|
||||
concretizer: 'clingo'
|
||||
on_develop: false
|
||||
- python-version: '3.8'
|
||||
os: ubuntu-latest
|
||||
concretizer: 'clingo'
|
||||
on_develop: false
|
||||
- python-version: '3.9'
|
||||
os: ubuntu-latest
|
||||
concretizer: 'clingo'
|
||||
on_develop: false
|
||||
- python-version: '3.10'
|
||||
os: ubuntu-latest
|
||||
concretizer: 'clingo'
|
||||
on_develop: false
|
||||
- python-version: '3.11'
|
||||
os: ubuntu-latest
|
||||
concretizer: 'clingo'
|
||||
on_develop: false
|
||||
|
||||
steps:
|
||||
@@ -85,7 +74,6 @@ jobs:
|
||||
- name: Run unit tests
|
||||
env:
|
||||
SPACK_PYTHON: python
|
||||
SPACK_TEST_SOLVER: ${{ matrix.concretizer }}
|
||||
SPACK_TEST_PARALLEL: 2
|
||||
COVERAGE: true
|
||||
UNIT_TEST_COVERAGE: ${{ matrix.python-version == '3.11' }}
|
||||
@@ -182,7 +170,6 @@ jobs:
|
||||
- name: Run unit tests (full suite with coverage)
|
||||
env:
|
||||
COVERAGE: true
|
||||
SPACK_TEST_SOLVER: clingo
|
||||
run: |
|
||||
share/spack/qa/run-unit-tests
|
||||
- uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673
|
||||
@@ -213,7 +200,6 @@ jobs:
|
||||
brew install dash fish gcc gnupg2 kcov
|
||||
- name: Run unit tests
|
||||
env:
|
||||
SPACK_TEST_SOLVER: clingo
|
||||
SPACK_TEST_PARALLEL: 4
|
||||
run: |
|
||||
git --version
|
||||
|
||||
@@ -170,23 +170,6 @@ config:
|
||||
# If set to true, Spack will use ccache to cache C compiles.
|
||||
ccache: false
|
||||
|
||||
|
||||
# The concretization algorithm to use in Spack. Options are:
|
||||
#
|
||||
# 'clingo': Uses a logic solver under the hood to solve DAGs with full
|
||||
# backtracking and optimization for user preferences. Spack will
|
||||
# try to bootstrap the logic solver, if not already available.
|
||||
#
|
||||
# 'original': Spack's original greedy, fixed-point concretizer. This
|
||||
# algorithm can make decisions too early and will not backtrack
|
||||
# sufficiently for many specs. This will soon be deprecated in
|
||||
# favor of clingo.
|
||||
#
|
||||
# See `concretizer.yaml` for more settings you can fine-tune when
|
||||
# using clingo.
|
||||
concretizer: clingo
|
||||
|
||||
|
||||
# How long to wait to lock the Spack installation database. This lock is used
|
||||
# when Spack needs to manage its own package metadata and all operations are
|
||||
# expected to complete within the default time limit. The timeout should
|
||||
|
||||
@@ -20,12 +20,14 @@ packages:
|
||||
awk: [gawk]
|
||||
armci: [armcimpi]
|
||||
blas: [openblas, amdblis]
|
||||
c: [gcc]
|
||||
cxx: [gcc]
|
||||
D: [ldc]
|
||||
daal: [intel-oneapi-daal]
|
||||
elf: [elfutils]
|
||||
fftw-api: [fftw, amdfftw]
|
||||
flame: [libflame, amdlibflame]
|
||||
fortran: [gcc]
|
||||
fortran-rt: [gcc-runtime, intel-oneapi-runtime]
|
||||
fuse: [libfuse]
|
||||
gl: [glx, osmesa]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
config:
|
||||
locks: false
|
||||
concretizer: clingo
|
||||
build_stage::
|
||||
- '$spack/.staging'
|
||||
stage_name: '{name}-{version}-{hash:7}'
|
||||
|
||||
@@ -206,6 +206,7 @@ def setup(sphinx):
|
||||
("py:class", "six.moves.urllib.parse.ParseResult"),
|
||||
("py:class", "TextIO"),
|
||||
("py:class", "hashlib._Hash"),
|
||||
("py:class", "concurrent.futures._base.Executor"),
|
||||
# Spack classes that are private and we don't want to expose
|
||||
("py:class", "spack.provider_index._IndexBase"),
|
||||
("py:class", "spack.repo._PrependFileLoader"),
|
||||
|
||||
@@ -1263,6 +1263,11 @@ Git fetching supports the following parameters to ``version``:
|
||||
option ``--depth 1`` will be used if the version of git and the specified
|
||||
transport protocol support it, and ``--single-branch`` will be used if the
|
||||
version of git supports it.
|
||||
* ``git_sparse_paths``: Use ``sparse-checkout`` to only clone these relative paths.
|
||||
This feature requires ``git`` to be version ``2.25.0`` or later but is useful for
|
||||
large repositories that have separate portions that can be built independently.
|
||||
If paths provided are directories then all the subdirectories and associated files
|
||||
will also be cloned.
|
||||
|
||||
Only one of ``tag``, ``branch``, or ``commit`` can be used at a time.
|
||||
|
||||
@@ -1361,6 +1366,41 @@ Submodules
|
||||
For more information about git submodules see the manpage of git: ``man
|
||||
git-submodule``.
|
||||
|
||||
Sparse-Checkout
|
||||
You can supply ``git_sparse_paths`` at the package or version level to utilize git's
|
||||
sparse-checkout feature. This will only clone the paths that are specified in the
|
||||
``git_sparse_paths`` attribute for the package along with the files in the top level directory.
|
||||
This feature allows you to only clone what you need from a large repository.
|
||||
Note that this is a newer feature in git and requries git ``2.25.0`` or greater.
|
||||
If ``git_sparse_paths`` is supplied and the git version is too old
|
||||
then a warning will be issued and that package will use the standard cloning operations instead.
|
||||
``git_sparse_paths`` should be supplied as a list of paths, a callable function for versions,
|
||||
or a more complex package attribute using the ``@property`` decorator. The return value should be
|
||||
a list for a callable implementation of ``git_sparse_paths``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def sparse_path_function(package)
|
||||
"""a callable function that can be used in side a version"""
|
||||
# paths can be directories or functions, all subdirectories and files are included
|
||||
paths = ["doe", "rae", "me/file.cpp"]
|
||||
if package.spec.version > Version("1.2.0"):
|
||||
paths.extend(["fae"])
|
||||
return paths
|
||||
|
||||
class MyPackage(package):
|
||||
# can also be a package attribute that will be used if not specified in versions
|
||||
git_sparse_paths = ["doe", "rae"]
|
||||
|
||||
# use the package attribute
|
||||
version("1.0.0")
|
||||
version("1.1.0")
|
||||
# use the function
|
||||
version("1.1.5", git_sparse_paths=sparse_path_func)
|
||||
version("1.2.0", git_sparse_paths=sparse_path_func)
|
||||
version("1.2.5", git_sparse_paths=sparse_path_func)
|
||||
version("1.1.5", git_sparse_paths=sparse_path_func)
|
||||
|
||||
.. _github-fetch:
|
||||
|
||||
^^^^^^
|
||||
|
||||
2
lib/spack/external/__init__.py
vendored
2
lib/spack/external/__init__.py
vendored
@@ -18,7 +18,7 @@
|
||||
|
||||
* Homepage: https://pypi.python.org/pypi/archspec
|
||||
* Usage: Labeling, comparison and detection of microarchitectures
|
||||
* Version: 0.2.4 (commit 48b92512b9ce203ded0ebd1ac41b42593e931f7c)
|
||||
* Version: 0.2.5-dev (commit 7e6740012b897ae4a950f0bba7e9726b767e921f)
|
||||
|
||||
astunparse
|
||||
----------------
|
||||
|
||||
24
lib/spack/external/_vendoring/distro/distro.py
vendored
24
lib/spack/external/_vendoring/distro/distro.py
vendored
@@ -1265,27 +1265,29 @@ def _distro_release_info(self) -> Dict[str, str]:
|
||||
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
|
||||
else:
|
||||
try:
|
||||
basenames = [
|
||||
basename
|
||||
for basename in os.listdir(self.etc_dir)
|
||||
if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES
|
||||
and os.path.isfile(os.path.join(self.etc_dir, basename))
|
||||
]
|
||||
with os.scandir(self.etc_dir) as it:
|
||||
etc_files = [
|
||||
p.path for p in it
|
||||
if p.is_file() and p.name not in _DISTRO_RELEASE_IGNORE_BASENAMES
|
||||
]
|
||||
# We sort for repeatability in cases where there are multiple
|
||||
# distro specific files; e.g. CentOS, Oracle, Enterprise all
|
||||
# containing `redhat-release` on top of their own.
|
||||
basenames.sort()
|
||||
etc_files.sort()
|
||||
except OSError:
|
||||
# This may occur when /etc is not readable but we can't be
|
||||
# sure about the *-release files. Check common entries of
|
||||
# /etc for information. If they turn out to not be there the
|
||||
# error is handled in `_parse_distro_release_file()`.
|
||||
basenames = _DISTRO_RELEASE_BASENAMES
|
||||
for basename in basenames:
|
||||
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
|
||||
etc_files = [
|
||||
os.path.join(self.etc_dir, basename)
|
||||
for basename in _DISTRO_RELEASE_BASENAMES
|
||||
]
|
||||
|
||||
for filepath in etc_files:
|
||||
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(os.path.basename(filepath))
|
||||
if match is None:
|
||||
continue
|
||||
filepath = os.path.join(self.etc_dir, basename)
|
||||
distro_info = self._parse_distro_release_file(filepath)
|
||||
# The name is always present if the pattern matches.
|
||||
if "name" not in distro_info:
|
||||
|
||||
173
lib/spack/external/_vendoring/jsonschema/_format.py
vendored
173
lib/spack/external/_vendoring/jsonschema/_format.py
vendored
@@ -231,96 +231,6 @@ def is_host_name(instance):
|
||||
return True
|
||||
|
||||
|
||||
try:
|
||||
# The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
|
||||
import idna
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
@_checks_drafts(draft7="idn-hostname", raises=idna.IDNAError)
|
||||
def is_idn_host_name(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
idna.encode(instance)
|
||||
return True
|
||||
|
||||
|
||||
try:
|
||||
import rfc3987
|
||||
except ImportError:
|
||||
try:
|
||||
from rfc3986_validator import validate_rfc3986
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
@_checks_drafts(name="uri")
|
||||
def is_uri(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return validate_rfc3986(instance, rule="URI")
|
||||
|
||||
@_checks_drafts(
|
||||
draft6="uri-reference",
|
||||
draft7="uri-reference",
|
||||
raises=ValueError,
|
||||
)
|
||||
def is_uri_reference(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return validate_rfc3986(instance, rule="URI_reference")
|
||||
|
||||
else:
|
||||
@_checks_drafts(draft7="iri", raises=ValueError)
|
||||
def is_iri(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return rfc3987.parse(instance, rule="IRI")
|
||||
|
||||
@_checks_drafts(draft7="iri-reference", raises=ValueError)
|
||||
def is_iri_reference(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return rfc3987.parse(instance, rule="IRI_reference")
|
||||
|
||||
@_checks_drafts(name="uri", raises=ValueError)
|
||||
def is_uri(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return rfc3987.parse(instance, rule="URI")
|
||||
|
||||
@_checks_drafts(
|
||||
draft6="uri-reference",
|
||||
draft7="uri-reference",
|
||||
raises=ValueError,
|
||||
)
|
||||
def is_uri_reference(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return rfc3987.parse(instance, rule="URI_reference")
|
||||
|
||||
|
||||
try:
|
||||
from strict_rfc3339 import validate_rfc3339
|
||||
except ImportError:
|
||||
try:
|
||||
from rfc3339_validator import validate_rfc3339
|
||||
except ImportError:
|
||||
validate_rfc3339 = None
|
||||
|
||||
if validate_rfc3339:
|
||||
@_checks_drafts(name="date-time")
|
||||
def is_datetime(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return validate_rfc3339(instance)
|
||||
|
||||
@_checks_drafts(draft7="time")
|
||||
def is_time(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return is_datetime("1970-01-01T" + instance)
|
||||
|
||||
|
||||
@_checks_drafts(name="regex", raises=re.error)
|
||||
def is_regex(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
@@ -340,86 +250,3 @@ def is_draft3_time(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return datetime.datetime.strptime(instance, "%H:%M:%S")
|
||||
|
||||
|
||||
try:
|
||||
import webcolors
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
def is_css_color_code(instance):
|
||||
return webcolors.normalize_hex(instance)
|
||||
|
||||
@_checks_drafts(draft3="color", raises=(ValueError, TypeError))
|
||||
def is_css21_color(instance):
|
||||
if (
|
||||
not isinstance(instance, str_types) or
|
||||
instance.lower() in webcolors.css21_names_to_hex
|
||||
):
|
||||
return True
|
||||
return is_css_color_code(instance)
|
||||
|
||||
def is_css3_color(instance):
|
||||
if instance.lower() in webcolors.css3_names_to_hex:
|
||||
return True
|
||||
return is_css_color_code(instance)
|
||||
|
||||
|
||||
try:
|
||||
import jsonpointer
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
@_checks_drafts(
|
||||
draft6="json-pointer",
|
||||
draft7="json-pointer",
|
||||
raises=jsonpointer.JsonPointerException,
|
||||
)
|
||||
def is_json_pointer(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return jsonpointer.JsonPointer(instance)
|
||||
|
||||
# TODO: I don't want to maintain this, so it
|
||||
# needs to go either into jsonpointer (pending
|
||||
# https://github.com/stefankoegl/python-json-pointer/issues/34) or
|
||||
# into a new external library.
|
||||
@_checks_drafts(
|
||||
draft7="relative-json-pointer",
|
||||
raises=jsonpointer.JsonPointerException,
|
||||
)
|
||||
def is_relative_json_pointer(instance):
|
||||
# Definition taken from:
|
||||
# https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
non_negative_integer, rest = [], ""
|
||||
for i, character in enumerate(instance):
|
||||
if character.isdigit():
|
||||
non_negative_integer.append(character)
|
||||
continue
|
||||
|
||||
if not non_negative_integer:
|
||||
return False
|
||||
|
||||
rest = instance[i:]
|
||||
break
|
||||
return (rest == "#") or jsonpointer.JsonPointer(rest)
|
||||
|
||||
|
||||
try:
|
||||
import uritemplate.exceptions
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
@_checks_drafts(
|
||||
draft6="uri-template",
|
||||
draft7="uri-template",
|
||||
raises=uritemplate.exceptions.InvalidTemplate,
|
||||
)
|
||||
def is_uri_template(
|
||||
instance,
|
||||
template_validator=uritemplate.Validator().force_balanced_braces(),
|
||||
):
|
||||
template = uritemplate.URITemplate(instance)
|
||||
return template_validator.validate(template)
|
||||
|
||||
12
lib/spack/external/archspec/cpu/detect.py
vendored
12
lib/spack/external/archspec/cpu/detect.py
vendored
@@ -47,7 +47,11 @@ def decorator(factory):
|
||||
|
||||
|
||||
def partial_uarch(
|
||||
name: str = "", vendor: str = "", features: Optional[Set[str]] = None, generation: int = 0
|
||||
name: str = "",
|
||||
vendor: str = "",
|
||||
features: Optional[Set[str]] = None,
|
||||
generation: int = 0,
|
||||
cpu_part: str = "",
|
||||
) -> Microarchitecture:
|
||||
"""Construct a partial microarchitecture, from information gathered during system scan."""
|
||||
return Microarchitecture(
|
||||
@@ -57,6 +61,7 @@ def partial_uarch(
|
||||
features=features or set(),
|
||||
compilers={},
|
||||
generation=generation,
|
||||
cpu_part=cpu_part,
|
||||
)
|
||||
|
||||
|
||||
@@ -90,6 +95,7 @@ def proc_cpuinfo() -> Microarchitecture:
|
||||
return partial_uarch(
|
||||
vendor=_canonicalize_aarch64_vendor(data),
|
||||
features=_feature_set(data, key="Features"),
|
||||
cpu_part=data.get("CPU part", ""),
|
||||
)
|
||||
|
||||
if architecture in (PPC64LE, PPC64):
|
||||
@@ -345,6 +351,10 @@ def sorting_fn(item):
|
||||
generic_candidates = [c for c in candidates if c.vendor == "generic"]
|
||||
best_generic = max(generic_candidates, key=sorting_fn)
|
||||
|
||||
# Relevant for AArch64. Filter on "cpu_part" if we have any match
|
||||
if info.cpu_part != "" and any(c for c in candidates if info.cpu_part == c.cpu_part):
|
||||
candidates = [c for c in candidates if info.cpu_part == c.cpu_part]
|
||||
|
||||
# Filter the candidates to be descendant of the best generic candidate.
|
||||
# This is to avoid that the lack of a niche feature that can be disabled
|
||||
# from e.g. BIOS prevents detection of a reasonably performant architecture
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
# Archspec Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
"""Types and functions to manage information
|
||||
on CPU microarchitectures.
|
||||
"""
|
||||
"""Types and functions to manage information on CPU microarchitectures."""
|
||||
import functools
|
||||
import platform
|
||||
import re
|
||||
@@ -65,21 +63,24 @@ class Microarchitecture:
|
||||
passed in as argument above.
|
||||
* versions: versions that support this micro-architecture.
|
||||
|
||||
generation (int): generation of the micro-architecture, if
|
||||
relevant.
|
||||
generation (int): generation of the micro-architecture, if relevant.
|
||||
cpu_part (str): cpu part of the architecture, if relevant.
|
||||
"""
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-arguments,too-many-instance-attributes
|
||||
#: Aliases for micro-architecture's features
|
||||
feature_aliases = FEATURE_ALIASES
|
||||
|
||||
def __init__(self, name, parents, vendor, features, compilers, generation=0):
|
||||
def __init__(self, name, parents, vendor, features, compilers, generation=0, cpu_part=""):
|
||||
self.name = name
|
||||
self.parents = parents
|
||||
self.vendor = vendor
|
||||
self.features = features
|
||||
self.compilers = compilers
|
||||
# Only relevant for PowerPC
|
||||
self.generation = generation
|
||||
# Only relevant for AArch64
|
||||
self.cpu_part = cpu_part
|
||||
# Cache the ancestor computation
|
||||
self._ancestors = None
|
||||
|
||||
@@ -111,6 +112,7 @@ def __eq__(self, other):
|
||||
and self.parents == other.parents # avoid ancestors here
|
||||
and self.compilers == other.compilers
|
||||
and self.generation == other.generation
|
||||
and self.cpu_part == other.cpu_part
|
||||
)
|
||||
|
||||
@coerce_target_names
|
||||
@@ -143,7 +145,8 @@ def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
fmt = (
|
||||
cls_name + "({0.name!r}, {0.parents!r}, {0.vendor!r}, "
|
||||
"{0.features!r}, {0.compilers!r}, {0.generation!r})"
|
||||
"{0.features!r}, {0.compilers!r}, generation={0.generation!r}, "
|
||||
"cpu_part={0.cpu_part!r})"
|
||||
)
|
||||
return fmt.format(self)
|
||||
|
||||
@@ -190,6 +193,7 @@ def to_dict(self):
|
||||
"generation": self.generation,
|
||||
"parents": [str(x) for x in self.parents],
|
||||
"compilers": self.compilers,
|
||||
"cpupart": self.cpu_part,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -202,6 +206,7 @@ def from_dict(data) -> "Microarchitecture":
|
||||
features=set(data["features"]),
|
||||
compilers=data.get("compilers", {}),
|
||||
generation=data.get("generation", 0),
|
||||
cpu_part=data.get("cpupart", ""),
|
||||
)
|
||||
|
||||
def optimization_flags(self, compiler, version):
|
||||
@@ -360,8 +365,11 @@ def fill_target_from_dict(name, data, targets):
|
||||
features = set(values["features"])
|
||||
compilers = values.get("compilers", {})
|
||||
generation = values.get("generation", 0)
|
||||
cpu_part = values.get("cpupart", "")
|
||||
|
||||
targets[name] = Microarchitecture(name, parents, vendor, features, compilers, generation)
|
||||
targets[name] = Microarchitecture(
|
||||
name, parents, vendor, features, compilers, generation=generation, cpu_part=cpu_part
|
||||
)
|
||||
|
||||
known_targets = {}
|
||||
data = archspec.cpu.schema.TARGETS_JSON["microarchitectures"]
|
||||
|
||||
@@ -2225,10 +2225,14 @@
|
||||
],
|
||||
"nvhpc": [
|
||||
{
|
||||
"versions": "21.11:",
|
||||
"versions": "21.11:23.8",
|
||||
"name": "zen3",
|
||||
"flags": "-tp {name}",
|
||||
"warnings": "zen4 is not fully supported by nvhpc yet, falling back to zen3"
|
||||
"warnings": "zen4 is not fully supported by nvhpc versions < 23.9, falling back to zen3"
|
||||
},
|
||||
{
|
||||
"versions": "23.9:",
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2711,7 +2715,8 @@
|
||||
"flags": "-mcpu=thunderx2t99"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x0af"
|
||||
},
|
||||
"a64fx": {
|
||||
"from": ["armv8.2a"],
|
||||
@@ -2779,7 +2784,8 @@
|
||||
"flags": "-march=armv8.2-a+crc+crypto+fp16+sve"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x001"
|
||||
},
|
||||
"cortex_a72": {
|
||||
"from": ["aarch64"],
|
||||
@@ -2816,7 +2822,8 @@
|
||||
"flags" : "-mcpu=cortex-a72"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd08"
|
||||
},
|
||||
"neoverse_n1": {
|
||||
"from": ["cortex_a72", "armv8.2a"],
|
||||
@@ -2902,7 +2909,8 @@
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd0c"
|
||||
},
|
||||
"neoverse_v1": {
|
||||
"from": ["neoverse_n1", "armv8.4a"],
|
||||
@@ -2926,8 +2934,6 @@
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"sm3",
|
||||
"sm4",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"sve",
|
||||
@@ -3028,7 +3034,8 @@
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd40"
|
||||
},
|
||||
"neoverse_v2": {
|
||||
"from": ["neoverse_n1", "armv9.0a"],
|
||||
@@ -3052,13 +3059,10 @@
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"sm3",
|
||||
"sm4",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"sve",
|
||||
"asimdfhm",
|
||||
"dit",
|
||||
"uscat",
|
||||
"ilrcpc",
|
||||
"flagm",
|
||||
@@ -3066,18 +3070,12 @@
|
||||
"sb",
|
||||
"dcpodp",
|
||||
"sve2",
|
||||
"sveaes",
|
||||
"svepmull",
|
||||
"svebitperm",
|
||||
"svesha3",
|
||||
"svesm4",
|
||||
"flagm2",
|
||||
"frint",
|
||||
"svei8mm",
|
||||
"svebf16",
|
||||
"i8mm",
|
||||
"bf16",
|
||||
"dgh"
|
||||
"bf16"
|
||||
],
|
||||
"compilers" : {
|
||||
"gcc": [
|
||||
@@ -3102,15 +3100,19 @@
|
||||
"flags" : "-march=armv8.5-a+sve -mtune=cortex-a76"
|
||||
},
|
||||
{
|
||||
"versions": "10.0:11.99",
|
||||
"versions": "10.0:11.3.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16 -mtune=cortex-a77"
|
||||
},
|
||||
{
|
||||
"versions": "11.4:11.99",
|
||||
"flags" : "-mcpu=neoverse-v2"
|
||||
},
|
||||
{
|
||||
"versions": "12.0:12.99",
|
||||
"versions": "12.0:12.2.99",
|
||||
"flags" : "-march=armv9-a+i8mm+bf16 -mtune=cortex-a710"
|
||||
},
|
||||
{
|
||||
"versions": "13.0:",
|
||||
"versions": "12.3:",
|
||||
"flags" : "-mcpu=neoverse-v2"
|
||||
}
|
||||
],
|
||||
@@ -3145,7 +3147,113 @@
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd4f"
|
||||
},
|
||||
"neoverse_n2": {
|
||||
"from": ["neoverse_n1", "armv9.0a"],
|
||||
"vendor": "ARM",
|
||||
"features": [
|
||||
"fp",
|
||||
"asimd",
|
||||
"evtstrm",
|
||||
"aes",
|
||||
"pmull",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"crc32",
|
||||
"atomics",
|
||||
"fphp",
|
||||
"asimdhp",
|
||||
"cpuid",
|
||||
"asimdrdm",
|
||||
"jscvt",
|
||||
"fcma",
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"sve",
|
||||
"asimdfhm",
|
||||
"uscat",
|
||||
"ilrcpc",
|
||||
"flagm",
|
||||
"ssbs",
|
||||
"sb",
|
||||
"dcpodp",
|
||||
"sve2",
|
||||
"flagm2",
|
||||
"frint",
|
||||
"svei8mm",
|
||||
"svebf16",
|
||||
"i8mm",
|
||||
"bf16"
|
||||
],
|
||||
"compilers" : {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "4.8:5.99",
|
||||
"flags": "-march=armv8-a"
|
||||
},
|
||||
{
|
||||
"versions": "6:6.99",
|
||||
"flags" : "-march=armv8.1-a"
|
||||
},
|
||||
{
|
||||
"versions": "7.0:7.99",
|
||||
"flags" : "-march=armv8.2-a -mtune=cortex-a72"
|
||||
},
|
||||
{
|
||||
"versions": "8.0:8.99",
|
||||
"flags" : "-march=armv8.4-a+sve -mtune=cortex-a72"
|
||||
},
|
||||
{
|
||||
"versions": "9.0:9.99",
|
||||
"flags" : "-march=armv8.5-a+sve -mtune=cortex-a76"
|
||||
},
|
||||
{
|
||||
"versions": "10.0:10.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16 -mtune=cortex-a77"
|
||||
},
|
||||
{
|
||||
"versions": "11.0:",
|
||||
"flags" : "-mcpu=neoverse-n2"
|
||||
}
|
||||
],
|
||||
"clang" : [
|
||||
{
|
||||
"versions": "9.0:10.99",
|
||||
"flags" : "-march=armv8.5-a+sve"
|
||||
},
|
||||
{
|
||||
"versions": "11.0:13.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16"
|
||||
},
|
||||
{
|
||||
"versions": "14.0:15.99",
|
||||
"flags" : "-march=armv9-a+i8mm+bf16"
|
||||
},
|
||||
{
|
||||
"versions": "16.0:",
|
||||
"flags" : "-mcpu=neoverse-n2"
|
||||
}
|
||||
],
|
||||
"arm" : [
|
||||
{
|
||||
"versions": "23.04.0:",
|
||||
"flags" : "-mcpu=neoverse-n2"
|
||||
}
|
||||
],
|
||||
"nvhpc" : [
|
||||
{
|
||||
"versions": "23.3:",
|
||||
"name": "neoverse-n1",
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cpupart": "0xd49"
|
||||
},
|
||||
"m1": {
|
||||
"from": ["armv8.4a"],
|
||||
@@ -3211,7 +3319,8 @@
|
||||
"flags" : "-mcpu=apple-m1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x022"
|
||||
},
|
||||
"m2": {
|
||||
"from": ["m1", "armv8.5a"],
|
||||
@@ -3289,7 +3398,8 @@
|
||||
"flags" : "-mcpu=apple-m2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x032"
|
||||
},
|
||||
"arm": {
|
||||
"from": [],
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cpupart": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -107,4 +110,4 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
lib/spack/external/patches/distro.patch
vendored
Normal file
45
lib/spack/external/patches/distro.patch
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
diff --git a/lib/spack/external/_vendoring/distro/distro.py b/lib/spack/external/_vendoring/distro/distro.py
|
||||
index 89e1868047..50c3b18d4d 100644
|
||||
--- a/lib/spack/external/_vendoring/distro/distro.py
|
||||
+++ b/lib/spack/external/_vendoring/distro/distro.py
|
||||
@@ -1265,27 +1265,29 @@ def _distro_release_info(self) -> Dict[str, str]:
|
||||
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
|
||||
else:
|
||||
try:
|
||||
- basenames = [
|
||||
- basename
|
||||
- for basename in os.listdir(self.etc_dir)
|
||||
- if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES
|
||||
- and os.path.isfile(os.path.join(self.etc_dir, basename))
|
||||
- ]
|
||||
+ with os.scandir(self.etc_dir) as it:
|
||||
+ etc_files = [
|
||||
+ p.path for p in it
|
||||
+ if p.is_file() and p.name not in _DISTRO_RELEASE_IGNORE_BASENAMES
|
||||
+ ]
|
||||
# We sort for repeatability in cases where there are multiple
|
||||
# distro specific files; e.g. CentOS, Oracle, Enterprise all
|
||||
# containing `redhat-release` on top of their own.
|
||||
- basenames.sort()
|
||||
+ etc_files.sort()
|
||||
except OSError:
|
||||
# This may occur when /etc is not readable but we can't be
|
||||
# sure about the *-release files. Check common entries of
|
||||
# /etc for information. If they turn out to not be there the
|
||||
# error is handled in `_parse_distro_release_file()`.
|
||||
- basenames = _DISTRO_RELEASE_BASENAMES
|
||||
- for basename in basenames:
|
||||
- match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
|
||||
+ etc_files = [
|
||||
+ os.path.join(self.etc_dir, basename)
|
||||
+ for basename in _DISTRO_RELEASE_BASENAMES
|
||||
+ ]
|
||||
+
|
||||
+ for filepath in etc_files:
|
||||
+ match = _DISTRO_RELEASE_BASENAME_PATTERN.match(os.path.basename(filepath))
|
||||
if match is None:
|
||||
continue
|
||||
- filepath = os.path.join(self.etc_dir, basename)
|
||||
distro_info = self._parse_distro_release_file(filepath)
|
||||
# The name is always present if the pattern matches.
|
||||
if "name" not in distro_info:
|
||||
188
lib/spack/external/patches/jsonschema.patch
vendored
188
lib/spack/external/patches/jsonschema.patch
vendored
@@ -13,3 +13,191 @@ index 6b630cdfbb..1791fe7fbf 100644
|
||||
-__version__ = metadata.version("jsonschema")
|
||||
+
|
||||
+__version__ = "3.2.0"
|
||||
diff --git a/lib/spack/external/_vendoring/jsonschema/_format.py b/lib/spack/external/_vendoring/jsonschema/_format.py
|
||||
index 281a7cfcff..29061e3661 100644
|
||||
--- a/lib/spack/external/_vendoring/jsonschema/_format.py
|
||||
+++ b/lib/spack/external/_vendoring/jsonschema/_format.py
|
||||
@@ -231,96 +231,6 @@ def is_host_name(instance):
|
||||
return True
|
||||
|
||||
|
||||
-try:
|
||||
- # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
|
||||
- import idna
|
||||
-except ImportError:
|
||||
- pass
|
||||
-else:
|
||||
- @_checks_drafts(draft7="idn-hostname", raises=idna.IDNAError)
|
||||
- def is_idn_host_name(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- idna.encode(instance)
|
||||
- return True
|
||||
-
|
||||
-
|
||||
-try:
|
||||
- import rfc3987
|
||||
-except ImportError:
|
||||
- try:
|
||||
- from rfc3986_validator import validate_rfc3986
|
||||
- except ImportError:
|
||||
- pass
|
||||
- else:
|
||||
- @_checks_drafts(name="uri")
|
||||
- def is_uri(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return validate_rfc3986(instance, rule="URI")
|
||||
-
|
||||
- @_checks_drafts(
|
||||
- draft6="uri-reference",
|
||||
- draft7="uri-reference",
|
||||
- raises=ValueError,
|
||||
- )
|
||||
- def is_uri_reference(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return validate_rfc3986(instance, rule="URI_reference")
|
||||
-
|
||||
-else:
|
||||
- @_checks_drafts(draft7="iri", raises=ValueError)
|
||||
- def is_iri(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return rfc3987.parse(instance, rule="IRI")
|
||||
-
|
||||
- @_checks_drafts(draft7="iri-reference", raises=ValueError)
|
||||
- def is_iri_reference(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return rfc3987.parse(instance, rule="IRI_reference")
|
||||
-
|
||||
- @_checks_drafts(name="uri", raises=ValueError)
|
||||
- def is_uri(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return rfc3987.parse(instance, rule="URI")
|
||||
-
|
||||
- @_checks_drafts(
|
||||
- draft6="uri-reference",
|
||||
- draft7="uri-reference",
|
||||
- raises=ValueError,
|
||||
- )
|
||||
- def is_uri_reference(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return rfc3987.parse(instance, rule="URI_reference")
|
||||
-
|
||||
-
|
||||
-try:
|
||||
- from strict_rfc3339 import validate_rfc3339
|
||||
-except ImportError:
|
||||
- try:
|
||||
- from rfc3339_validator import validate_rfc3339
|
||||
- except ImportError:
|
||||
- validate_rfc3339 = None
|
||||
-
|
||||
-if validate_rfc3339:
|
||||
- @_checks_drafts(name="date-time")
|
||||
- def is_datetime(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return validate_rfc3339(instance)
|
||||
-
|
||||
- @_checks_drafts(draft7="time")
|
||||
- def is_time(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return is_datetime("1970-01-01T" + instance)
|
||||
-
|
||||
-
|
||||
@_checks_drafts(name="regex", raises=re.error)
|
||||
def is_regex(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
@@ -340,86 +250,3 @@ def is_draft3_time(instance):
|
||||
if not isinstance(instance, str_types):
|
||||
return True
|
||||
return datetime.datetime.strptime(instance, "%H:%M:%S")
|
||||
-
|
||||
-
|
||||
-try:
|
||||
- import webcolors
|
||||
-except ImportError:
|
||||
- pass
|
||||
-else:
|
||||
- def is_css_color_code(instance):
|
||||
- return webcolors.normalize_hex(instance)
|
||||
-
|
||||
- @_checks_drafts(draft3="color", raises=(ValueError, TypeError))
|
||||
- def is_css21_color(instance):
|
||||
- if (
|
||||
- not isinstance(instance, str_types) or
|
||||
- instance.lower() in webcolors.css21_names_to_hex
|
||||
- ):
|
||||
- return True
|
||||
- return is_css_color_code(instance)
|
||||
-
|
||||
- def is_css3_color(instance):
|
||||
- if instance.lower() in webcolors.css3_names_to_hex:
|
||||
- return True
|
||||
- return is_css_color_code(instance)
|
||||
-
|
||||
-
|
||||
-try:
|
||||
- import jsonpointer
|
||||
-except ImportError:
|
||||
- pass
|
||||
-else:
|
||||
- @_checks_drafts(
|
||||
- draft6="json-pointer",
|
||||
- draft7="json-pointer",
|
||||
- raises=jsonpointer.JsonPointerException,
|
||||
- )
|
||||
- def is_json_pointer(instance):
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- return jsonpointer.JsonPointer(instance)
|
||||
-
|
||||
- # TODO: I don't want to maintain this, so it
|
||||
- # needs to go either into jsonpointer (pending
|
||||
- # https://github.com/stefankoegl/python-json-pointer/issues/34) or
|
||||
- # into a new external library.
|
||||
- @_checks_drafts(
|
||||
- draft7="relative-json-pointer",
|
||||
- raises=jsonpointer.JsonPointerException,
|
||||
- )
|
||||
- def is_relative_json_pointer(instance):
|
||||
- # Definition taken from:
|
||||
- # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3
|
||||
- if not isinstance(instance, str_types):
|
||||
- return True
|
||||
- non_negative_integer, rest = [], ""
|
||||
- for i, character in enumerate(instance):
|
||||
- if character.isdigit():
|
||||
- non_negative_integer.append(character)
|
||||
- continue
|
||||
-
|
||||
- if not non_negative_integer:
|
||||
- return False
|
||||
-
|
||||
- rest = instance[i:]
|
||||
- break
|
||||
- return (rest == "#") or jsonpointer.JsonPointer(rest)
|
||||
-
|
||||
-
|
||||
-try:
|
||||
- import uritemplate.exceptions
|
||||
-except ImportError:
|
||||
- pass
|
||||
-else:
|
||||
- @_checks_drafts(
|
||||
- draft6="uri-template",
|
||||
- draft7="uri-template",
|
||||
- raises=uritemplate.exceptions.InvalidTemplate,
|
||||
- )
|
||||
- def is_uri_template(
|
||||
- instance,
|
||||
- template_validator=uritemplate.Validator().force_balanced_braces(),
|
||||
- ):
|
||||
- template = uritemplate.URITemplate(instance)
|
||||
- return template_validator.validate(template)
|
||||
|
||||
@@ -1624,6 +1624,12 @@ def remove_linked_tree(path):
|
||||
shutil.rmtree(os.path.realpath(path), **kwargs)
|
||||
os.unlink(path)
|
||||
else:
|
||||
if sys.platform == "win32":
|
||||
# Adding this prefix allows shutil to remove long paths on windows
|
||||
# https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
|
||||
long_path_pfx = "\\\\?\\"
|
||||
if not path.startswith(long_path_pfx):
|
||||
path = long_path_pfx + path
|
||||
shutil.rmtree(path, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import collections.abc
|
||||
import contextlib
|
||||
import functools
|
||||
import inspect
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
@@ -16,7 +15,7 @@
|
||||
from typing import Any, Callable, Iterable, List, Tuple
|
||||
|
||||
# Ignore emacs backups when listing modules
|
||||
ignore_modules = [r"^\.#", "~$"]
|
||||
ignore_modules = r"^\.#|~$"
|
||||
|
||||
|
||||
def index_by(objects, *funcs):
|
||||
@@ -84,20 +83,6 @@ def index_by(objects, *funcs):
|
||||
return result
|
||||
|
||||
|
||||
def caller_locals():
|
||||
"""This will return the locals of the *parent* of the caller.
|
||||
This allows a function to insert variables into its caller's
|
||||
scope. Yes, this is some black magic, and yes it's useful
|
||||
for implementing things like depends_on and provides.
|
||||
"""
|
||||
# Passing zero here skips line context for speed.
|
||||
stack = inspect.stack(0)
|
||||
try:
|
||||
return stack[2][0].f_locals
|
||||
finally:
|
||||
del stack
|
||||
|
||||
|
||||
def attr_setdefault(obj, name, value):
|
||||
"""Like dict.setdefault, but for objects."""
|
||||
if not hasattr(obj, name):
|
||||
@@ -105,15 +90,6 @@ def attr_setdefault(obj, name, value):
|
||||
return getattr(obj, name)
|
||||
|
||||
|
||||
def has_method(cls, name):
|
||||
for base in inspect.getmro(cls):
|
||||
if base is object:
|
||||
continue
|
||||
if name in base.__dict__:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def union_dicts(*dicts):
|
||||
"""Use update() to combine all dicts into one.
|
||||
|
||||
@@ -178,19 +154,22 @@ def list_modules(directory, **kwargs):
|
||||
order."""
|
||||
list_directories = kwargs.setdefault("directories", True)
|
||||
|
||||
for name in os.listdir(directory):
|
||||
if name == "__init__.py":
|
||||
continue
|
||||
ignore = re.compile(ignore_modules)
|
||||
|
||||
path = os.path.join(directory, name)
|
||||
if list_directories and os.path.isdir(path):
|
||||
init_py = os.path.join(path, "__init__.py")
|
||||
if os.path.isfile(init_py):
|
||||
yield name
|
||||
with os.scandir(directory) as it:
|
||||
for entry in it:
|
||||
if entry.name == "__init__.py" or entry.name == "__pycache__":
|
||||
continue
|
||||
|
||||
elif name.endswith(".py"):
|
||||
if not any(re.search(pattern, name) for pattern in ignore_modules):
|
||||
yield re.sub(".py$", "", name)
|
||||
if (
|
||||
list_directories
|
||||
and entry.is_dir()
|
||||
and os.path.isfile(os.path.join(entry.path, "__init__.py"))
|
||||
):
|
||||
yield entry.name
|
||||
|
||||
elif entry.name.endswith(".py") and entry.is_file() and not ignore.search(entry.name):
|
||||
yield entry.name[:-3] # strip .py
|
||||
|
||||
|
||||
def decorator_with_or_without_args(decorator):
|
||||
@@ -237,8 +216,8 @@ def setter(name, value):
|
||||
value.__name__ = name
|
||||
setattr(cls, name, value)
|
||||
|
||||
if not has_method(cls, "_cmp_key"):
|
||||
raise TypeError("'%s' doesn't define _cmp_key()." % cls.__name__)
|
||||
if not hasattr(cls, "_cmp_key"):
|
||||
raise TypeError(f"'{cls.__name__}' doesn't define _cmp_key().")
|
||||
|
||||
setter("__eq__", lambda s, o: (s is o) or (o is not None and s._cmp_key() == o._cmp_key()))
|
||||
setter("__lt__", lambda s, o: o is not None and s._cmp_key() < o._cmp_key())
|
||||
@@ -388,8 +367,8 @@ def cd_fun():
|
||||
TypeError: If the class does not have a ``_cmp_iter`` method
|
||||
|
||||
"""
|
||||
if not has_method(cls, "_cmp_iter"):
|
||||
raise TypeError("'%s' doesn't define _cmp_iter()." % cls.__name__)
|
||||
if not hasattr(cls, "_cmp_iter"):
|
||||
raise TypeError(f"'{cls.__name__}' doesn't define _cmp_iter().")
|
||||
|
||||
# comparison operators are implemented in terms of lazy_eq and lazy_lt
|
||||
def eq(self, other):
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import errno
|
||||
import io
|
||||
import multiprocessing
|
||||
import multiprocessing.connection
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
|
||||
@@ -1,131 +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
|
||||
|
||||
from llnl.util.lang import memoized
|
||||
|
||||
import spack.spec
|
||||
import spack.version
|
||||
from spack.compilers.clang import Clang
|
||||
from spack.util.executable import Executable, ProcessError
|
||||
|
||||
|
||||
class ABI:
|
||||
"""This class provides methods to test ABI compatibility between specs.
|
||||
The current implementation is rather rough and could be improved."""
|
||||
|
||||
def architecture_compatible(
|
||||
self, target: spack.spec.Spec, constraint: spack.spec.Spec
|
||||
) -> bool:
|
||||
"""Return true if architecture of target spec is ABI compatible
|
||||
to the architecture of constraint spec. If either the target
|
||||
or constraint specs have no architecture, target is also defined
|
||||
as architecture ABI compatible to constraint."""
|
||||
return (
|
||||
not target.architecture
|
||||
or not constraint.architecture
|
||||
or target.architecture.intersects(constraint.architecture)
|
||||
)
|
||||
|
||||
@memoized
|
||||
def _gcc_get_libstdcxx_version(self, version):
|
||||
"""Returns gcc ABI compatibility info by getting the library version of
|
||||
a compiler's libstdc++ or libgcc_s"""
|
||||
from spack.build_environment import dso_suffix
|
||||
|
||||
spec = spack.spec.CompilerSpec("gcc", version)
|
||||
compilers = spack.compilers.compilers_for_spec(spec)
|
||||
if not compilers:
|
||||
return None
|
||||
compiler = compilers[0]
|
||||
rungcc = None
|
||||
libname = None
|
||||
output = None
|
||||
if compiler.cxx:
|
||||
rungcc = Executable(compiler.cxx)
|
||||
libname = "libstdc++." + dso_suffix
|
||||
elif compiler.cc:
|
||||
rungcc = Executable(compiler.cc)
|
||||
libname = "libgcc_s." + dso_suffix
|
||||
else:
|
||||
return None
|
||||
try:
|
||||
# Some gcc's are actually clang and don't respond properly to
|
||||
# --print-file-name (they just print the filename, not the
|
||||
# full path). Ignore these and expect them to be handled as clang.
|
||||
if Clang.default_version(rungcc.exe[0]) != "unknown":
|
||||
return None
|
||||
|
||||
output = rungcc("--print-file-name=%s" % libname, output=str)
|
||||
except ProcessError:
|
||||
return None
|
||||
if not output:
|
||||
return None
|
||||
libpath = os.path.realpath(output.strip())
|
||||
if not libpath:
|
||||
return None
|
||||
return os.path.basename(libpath)
|
||||
|
||||
@memoized
|
||||
def _gcc_compiler_compare(self, pversion, cversion):
|
||||
"""Returns true iff the gcc version pversion and cversion
|
||||
are ABI compatible."""
|
||||
plib = self._gcc_get_libstdcxx_version(pversion)
|
||||
clib = self._gcc_get_libstdcxx_version(cversion)
|
||||
if not plib or not clib:
|
||||
return False
|
||||
return plib == clib
|
||||
|
||||
def _intel_compiler_compare(
|
||||
self, pversion: spack.version.ClosedOpenRange, cversion: spack.version.ClosedOpenRange
|
||||
) -> bool:
|
||||
"""Returns true iff the intel version pversion and cversion
|
||||
are ABI compatible"""
|
||||
|
||||
# Test major and minor versions. Ignore build version.
|
||||
pv = pversion.lo
|
||||
cv = cversion.lo
|
||||
return pv.up_to(2) == cv.up_to(2)
|
||||
|
||||
def compiler_compatible(
|
||||
self, parent: spack.spec.Spec, child: spack.spec.Spec, loose: bool = False
|
||||
) -> bool:
|
||||
"""Return true if compilers for parent and child are ABI compatible."""
|
||||
if not parent.compiler or not child.compiler:
|
||||
return True
|
||||
|
||||
if parent.compiler.name != child.compiler.name:
|
||||
# Different compiler families are assumed ABI incompatible
|
||||
return False
|
||||
|
||||
if loose:
|
||||
return True
|
||||
|
||||
# TODO: Can we move the specialized ABI matching stuff
|
||||
# TODO: into compiler classes?
|
||||
for pversion in parent.compiler.versions:
|
||||
for cversion in child.compiler.versions:
|
||||
# For a few compilers use specialized comparisons.
|
||||
# Otherwise match on version match.
|
||||
if pversion.intersects(cversion):
|
||||
return True
|
||||
elif parent.compiler.name == "gcc" and self._gcc_compiler_compare(
|
||||
pversion, cversion
|
||||
):
|
||||
return True
|
||||
elif parent.compiler.name == "intel" and self._intel_compiler_compare(
|
||||
pversion, cversion
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def compatible(
|
||||
self, target: spack.spec.Spec, constraint: spack.spec.Spec, loose: bool = False
|
||||
) -> bool:
|
||||
"""Returns true if target spec is ABI compatible to constraint spec"""
|
||||
return self.architecture_compatible(target, constraint) and self.compiler_compatible(
|
||||
target, constraint, loose=loose
|
||||
)
|
||||
@@ -39,9 +39,9 @@ def _search_duplicate_compilers(error_cls):
|
||||
import collections
|
||||
import collections.abc
|
||||
import glob
|
||||
import inspect
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import pathlib
|
||||
import pickle
|
||||
import re
|
||||
@@ -210,6 +210,11 @@ def _search_duplicate_compilers(error_cls):
|
||||
group="configs", tag="CFG-PACKAGES", description="Sanity checks on packages.yaml", kwargs=()
|
||||
)
|
||||
|
||||
#: Sanity checks on packages.yaml
|
||||
config_repos = AuditClass(
|
||||
group="configs", tag="CFG-REPOS", description="Sanity checks on repositories", kwargs=()
|
||||
)
|
||||
|
||||
|
||||
@config_packages
|
||||
def _search_duplicate_specs_in_externals(error_cls):
|
||||
@@ -367,6 +372,27 @@ def _ensure_all_virtual_packages_have_default_providers(error_cls):
|
||||
]
|
||||
|
||||
|
||||
@config_repos
|
||||
def _ensure_no_folders_without_package_py(error_cls):
|
||||
"""Check that we don't leave any folder without a package.py in repos"""
|
||||
errors = []
|
||||
for repository in spack.repo.PATH.repos:
|
||||
missing = []
|
||||
for entry in os.scandir(repository.packages_path):
|
||||
if not entry.is_dir():
|
||||
continue
|
||||
package_py = pathlib.Path(entry.path) / spack.repo.package_file_name
|
||||
if not package_py.exists():
|
||||
missing.append(entry.path)
|
||||
if missing:
|
||||
summary = (
|
||||
f"The '{repository.namespace}' repository misses a package.py file"
|
||||
f" in the following folders"
|
||||
)
|
||||
errors.append(error_cls(summary=summary, details=[f"{x}" for x in missing]))
|
||||
return errors
|
||||
|
||||
|
||||
def _make_config_error(config_data, summary, error_cls):
|
||||
s = io.StringIO()
|
||||
s.write("Occurring in the following file:\n")
|
||||
@@ -498,7 +524,7 @@ def _search_for_reserved_attributes_names_in_packages(pkgs, error_cls):
|
||||
name_definitions = collections.defaultdict(list)
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||
|
||||
for cls_item in inspect.getmro(pkg_cls):
|
||||
for cls_item in pkg_cls.__mro__:
|
||||
for name in RESERVED_NAMES:
|
||||
current_value = cls_item.__dict__.get(name)
|
||||
if current_value is None:
|
||||
@@ -527,7 +553,7 @@ def _ensure_all_package_names_are_lowercase(pkgs, error_cls):
|
||||
badname_regex, errors = re.compile(r"[_A-Z]"), []
|
||||
for pkg_name in pkgs:
|
||||
if badname_regex.search(pkg_name):
|
||||
error_msg = "Package name '{}' is either lowercase or conatine '_'".format(pkg_name)
|
||||
error_msg = f"Package name '{pkg_name}' should be lowercase and must not contain '_'"
|
||||
errors.append(error_cls(error_msg, []))
|
||||
return errors
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
"""Common basic functions used through the spack.bootstrap package"""
|
||||
import fnmatch
|
||||
import importlib
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
@@ -28,7 +29,7 @@
|
||||
|
||||
def _python_import(module: str) -> bool:
|
||||
try:
|
||||
__import__(module)
|
||||
importlib.import_module(module)
|
||||
except ImportError:
|
||||
return False
|
||||
return True
|
||||
|
||||
154
lib/spack/spack/bootstrap/clingo.py
Normal file
154
lib/spack/spack/bootstrap/clingo.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# 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)
|
||||
"""Bootstrap concrete specs for clingo
|
||||
|
||||
Spack uses clingo to concretize specs. When clingo itself needs to be bootstrapped from sources,
|
||||
we need to rely on another mechanism to get a concrete spec that fits the current host.
|
||||
|
||||
This module contains the logic to get a concrete spec for clingo, starting from a prototype
|
||||
JSON file for a similar platform.
|
||||
"""
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
import spack.compiler
|
||||
import spack.compilers
|
||||
import spack.platforms
|
||||
import spack.spec
|
||||
import spack.traverse
|
||||
|
||||
from .config import spec_for_current_python
|
||||
|
||||
|
||||
class ClingoBootstrapConcretizer:
|
||||
def __init__(self, configuration):
|
||||
self.host_platform = spack.platforms.host()
|
||||
self.host_os = self.host_platform.operating_system("frontend")
|
||||
self.host_target = archspec.cpu.host().family
|
||||
self.host_architecture = spack.spec.ArchSpec.frontend_arch()
|
||||
self.host_architecture.target = str(self.host_target)
|
||||
self.host_compiler = self._valid_compiler_or_raise()
|
||||
self.host_python = self.python_external_spec()
|
||||
if str(self.host_platform) == "linux":
|
||||
self.host_libc = self.libc_external_spec()
|
||||
|
||||
self.external_cmake, self.external_bison = self._externals_from_yaml(configuration)
|
||||
|
||||
def _valid_compiler_or_raise(self) -> "spack.compiler.Compiler":
|
||||
if str(self.host_platform) == "linux":
|
||||
compiler_name = "gcc"
|
||||
elif str(self.host_platform) == "darwin":
|
||||
compiler_name = "apple-clang"
|
||||
elif str(self.host_platform) == "windows":
|
||||
compiler_name = "msvc"
|
||||
elif str(self.host_platform) == "freebsd":
|
||||
compiler_name = "clang"
|
||||
else:
|
||||
raise RuntimeError(f"Cannot bootstrap clingo from sources on {self.host_platform}")
|
||||
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.spec.version, reverse=True)
|
||||
return candidates[0]
|
||||
|
||||
def _externals_from_yaml(
|
||||
self, configuration: "spack.config.Configuration"
|
||||
) -> Tuple[Optional["spack.spec.Spec"], Optional["spack.spec.Spec"]]:
|
||||
packages_yaml = configuration.get("packages")
|
||||
requirements = {"cmake": "@3.20:", "bison": "@2.5:"}
|
||||
selected: Dict[str, Optional["spack.spec.Spec"]] = {"cmake": None, "bison": None}
|
||||
for pkg_name in ["cmake", "bison"]:
|
||||
if pkg_name not in packages_yaml:
|
||||
continue
|
||||
|
||||
candidates = packages_yaml[pkg_name].get("externals", [])
|
||||
for candidate in candidates:
|
||||
s = spack.spec.Spec(candidate["spec"], external_path=candidate["prefix"])
|
||||
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
|
||||
|
||||
selected[pkg_name] = self._external_spec(s)
|
||||
break
|
||||
return selected["cmake"], selected["bison"]
|
||||
|
||||
def prototype_path(self) -> pathlib.Path:
|
||||
"""Path to a prototype concrete specfile for clingo"""
|
||||
parent_dir = pathlib.Path(__file__).parent
|
||||
result = parent_dir / "prototypes" / f"clingo-{self.host_platform}-{self.host_target}.json"
|
||||
if str(self.host_platform) == "linux":
|
||||
# Using aarch64 as a fallback, since it has gnuconfig (x86_64 doesn't have it)
|
||||
if not result.exists():
|
||||
result = parent_dir / "prototypes" / f"clingo-{self.host_platform}-aarch64.json"
|
||||
|
||||
elif str(self.host_platform) == "freebsd":
|
||||
result = parent_dir / "prototypes" / f"clingo-{self.host_platform}-amd64.json"
|
||||
|
||||
elif not result.exists():
|
||||
raise RuntimeError(f"Cannot bootstrap clingo from sources on {self.host_platform}")
|
||||
|
||||
return result
|
||||
|
||||
def concretize(self) -> "spack.spec.Spec":
|
||||
# Read the prototype and mark it NOT concrete
|
||||
s = spack.spec.Spec.from_specfile(str(self.prototype_path()))
|
||||
s._mark_concrete(False)
|
||||
|
||||
# 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.spec.versions
|
||||
|
||||
for edge in spack.traverse.traverse_edges([s], cover="edges"):
|
||||
if edge.spec.name == "python":
|
||||
edge.spec = self.host_python
|
||||
|
||||
if edge.spec.name == "bison" and self.external_bison:
|
||||
edge.spec = self.external_bison
|
||||
|
||||
if edge.spec.name == "cmake" and self.external_cmake:
|
||||
edge.spec = self.external_cmake
|
||||
|
||||
if "libc" in edge.virtuals:
|
||||
edge.spec = self.host_libc
|
||||
|
||||
s._finalize_concretization()
|
||||
|
||||
# Work around the fact that the installer calls Spec.dependents() and
|
||||
# we modified edges inconsistently
|
||||
return s.copy()
|
||||
|
||||
def python_external_spec(self) -> "spack.spec.Spec":
|
||||
"""Python external spec corresponding to the current running interpreter"""
|
||||
result = spack.spec.Spec(spec_for_current_python(), external_path=sys.exec_prefix)
|
||||
return self._external_spec(result)
|
||||
|
||||
def libc_external_spec(self) -> "spack.spec.Spec":
|
||||
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] = []
|
||||
return spack.spec.parse_with_version_concrete(initial_spec)
|
||||
@@ -143,11 +143,7 @@ def _bootstrap_config_scopes() -> Sequence["spack.config.ConfigScope"]:
|
||||
def _add_compilers_if_missing() -> None:
|
||||
arch = spack.spec.ArchSpec.frontend_arch()
|
||||
if not spack.compilers.compilers_for_arch(arch):
|
||||
new_compilers = spack.compilers.find_new_compilers(
|
||||
mixed_toolchain=sys.platform == "darwin"
|
||||
)
|
||||
if new_compilers:
|
||||
spack.compilers.add_compilers_to_config(new_compilers)
|
||||
spack.compilers.find_compilers()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -156,7 +152,7 @@ def _ensure_bootstrap_configuration() -> Generator:
|
||||
bootstrap_store_path = store_path()
|
||||
user_configuration = _read_and_sanitize_configuration()
|
||||
with spack.environment.no_active_environment():
|
||||
with spack.platforms.prevent_cray_detection(), spack.platforms.use_platform(
|
||||
with spack.platforms.use_platform(
|
||||
spack.platforms.real_host()
|
||||
), spack.repo.use_repositories(spack.paths.packages_path):
|
||||
# Default configuration scopes excluding command line
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
import spack.version
|
||||
|
||||
from ._common import _executables_in_store, _python_import, _root_spec, _try_import_from_store
|
||||
from .clingo import ClingoBootstrapConcretizer
|
||||
from .config import spack_python_interpreter, spec_for_current_python
|
||||
|
||||
#: Name of the file containing metadata about the bootstrapping source
|
||||
@@ -268,15 +269,13 @@ def try_import(self, module: str, abstract_spec_str: str) -> bool:
|
||||
|
||||
# Try to build and install from sources
|
||||
with spack_python_interpreter():
|
||||
# Add hint to use frontend operating system on Cray
|
||||
concrete_spec = spack.spec.Spec(abstract_spec_str + " ^" + spec_for_current_python())
|
||||
|
||||
if module == "clingo":
|
||||
# TODO: remove when the old concretizer is deprecated # pylint: disable=fixme
|
||||
concrete_spec._old_concretize( # pylint: disable=protected-access
|
||||
deprecation_warning=False
|
||||
)
|
||||
bootstrapper = ClingoBootstrapConcretizer(configuration=spack.config.CONFIG)
|
||||
concrete_spec = bootstrapper.concretize()
|
||||
else:
|
||||
concrete_spec = spack.spec.Spec(
|
||||
abstract_spec_str + " ^" + spec_for_current_python()
|
||||
)
|
||||
concrete_spec.concretize()
|
||||
|
||||
msg = "[BOOTSTRAP MODULE {0}] Try installing '{1}' from sources"
|
||||
@@ -303,14 +302,7 @@ def try_search_path(self, executables: Tuple[str], abstract_spec_str: str) -> bo
|
||||
# might reduce compilation time by a fair amount
|
||||
_add_externals_if_missing()
|
||||
|
||||
concrete_spec = spack.spec.Spec(abstract_spec_str)
|
||||
if concrete_spec.name == "patchelf":
|
||||
concrete_spec._old_concretize( # pylint: disable=protected-access
|
||||
deprecation_warning=False
|
||||
)
|
||||
else:
|
||||
concrete_spec.concretize()
|
||||
|
||||
concrete_spec = spack.spec.Spec(abstract_spec_str).concretized()
|
||||
msg = "[BOOTSTRAP] Try installing '{0}' from sources"
|
||||
tty.debug(msg.format(abstract_spec_str))
|
||||
with spack.config.override(self.mirror_scope):
|
||||
|
||||
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
@@ -124,7 +124,7 @@ def _development_requirements() -> List[RequiredResponseType]:
|
||||
# Ensure we trigger environment modifications if we have an environment
|
||||
if BootstrapEnvironment.spack_yaml().exists():
|
||||
with BootstrapEnvironment() as env:
|
||||
env.update_syspath_and_environ()
|
||||
env.load()
|
||||
|
||||
return [
|
||||
_required_executable(
|
||||
|
||||
@@ -457,9 +457,12 @@ def set_wrapper_variables(pkg, env):
|
||||
env.set(SPACK_DEBUG_LOG_ID, pkg.spec.format("{name}-{hash:7}"))
|
||||
env.set(SPACK_DEBUG_LOG_DIR, spack.main.spack_working_dir)
|
||||
|
||||
# Find ccache binary and hand it to build environment
|
||||
if spack.config.get("config:ccache"):
|
||||
# Enable ccache in the compiler wrapper
|
||||
env.set(SPACK_CCACHE_BINARY, spack.util.executable.which_string("ccache", required=True))
|
||||
else:
|
||||
# Avoid cache pollution if a build system forces `ccache <compiler wrapper invocation>`.
|
||||
env.set("CCACHE_DISABLE", "1")
|
||||
|
||||
# Gather information about various types of dependencies
|
||||
link_deps = set(pkg.spec.traverse(root=False, deptype=("link")))
|
||||
@@ -552,7 +555,7 @@ def set_package_py_globals(pkg, context: Context = Context.BUILD):
|
||||
"""Populate the Python module of a package with some useful global names.
|
||||
This makes things easier for package writers.
|
||||
"""
|
||||
module = ModuleChangePropagator(pkg)
|
||||
module = SetPackageGlobals(pkg)
|
||||
|
||||
if context == Context.BUILD:
|
||||
module.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg)
|
||||
@@ -1013,7 +1016,7 @@ def set_all_package_py_globals(self):
|
||||
# setting globals for those.
|
||||
if id(spec) not in self.nodes_in_subdag:
|
||||
continue
|
||||
dependent_module = ModuleChangePropagator(spec.package)
|
||||
dependent_module = SetPackageGlobals(spec.package)
|
||||
pkg.setup_dependent_package(dependent_module, spec)
|
||||
dependent_module.propagate_changes_to_mro()
|
||||
|
||||
@@ -1540,7 +1543,7 @@ def write_log_summary(out, log_type, log, last=None):
|
||||
out.write(make_log_context(warnings))
|
||||
|
||||
|
||||
class ModuleChangePropagator:
|
||||
class SetPackageGlobals:
|
||||
"""Wrapper class to accept changes to a package.py Python module, and propagate them in the
|
||||
MRO of the package.
|
||||
|
||||
@@ -1548,41 +1551,22 @@ class ModuleChangePropagator:
|
||||
"setup_dependent_package" function during build environment setup.
|
||||
"""
|
||||
|
||||
_PROTECTED_NAMES = ("package", "current_module", "modules_in_mro", "_set_attributes")
|
||||
|
||||
def __init__(self, package):
|
||||
self._set_self_attributes("package", package)
|
||||
self._set_self_attributes("current_module", package.module)
|
||||
|
||||
def __init__(self, package: spack.package_base.PackageBase) -> None:
|
||||
#: Modules for the classes in the MRO up to PackageBase
|
||||
modules_in_mro = []
|
||||
for cls in inspect.getmro(type(package)):
|
||||
module = cls.module
|
||||
|
||||
if module == self.current_module:
|
||||
continue
|
||||
|
||||
if module == spack.package_base:
|
||||
for cls in package.__class__.__mro__:
|
||||
module = getattr(cls, "module", None)
|
||||
if module is None or module is spack.package_base:
|
||||
break
|
||||
|
||||
modules_in_mro.append(module)
|
||||
self._set_self_attributes("modules_in_mro", modules_in_mro)
|
||||
self._set_self_attributes("_set_attributes", {})
|
||||
|
||||
def _set_self_attributes(self, key, value):
|
||||
super().__setattr__(key, value)
|
||||
super().__setattr__("modules_in_mro", modules_in_mro)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self.current_module, item)
|
||||
return getattr(self.modules_in_mro[0], item)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in ModuleChangePropagator._PROTECTED_NAMES:
|
||||
msg = f'Cannot set attribute "{key}" in ModuleMonkeyPatcher'
|
||||
return AttributeError(msg)
|
||||
|
||||
setattr(self.current_module, key, value)
|
||||
self._set_attributes[key] = value
|
||||
|
||||
def propagate_changes_to_mro(self):
|
||||
for module_in_mro in self.modules_in_mro:
|
||||
module_in_mro.__dict__.update(self._set_attributes)
|
||||
if key == "modules_in_mro":
|
||||
raise AttributeError(f'Cannot set attribute "{key}" in ModuleMonkeyPatcher')
|
||||
for module in self.modules_in_mro:
|
||||
setattr(module, key, value)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
import os
|
||||
import os.path
|
||||
import stat
|
||||
@@ -15,6 +14,7 @@
|
||||
import spack.build_environment
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.build_environment import SetPackageGlobals
|
||||
from spack.directives import build_system, conflicts, depends_on
|
||||
from spack.multimethod import when
|
||||
from spack.operating_systems.mac_os import macos_version
|
||||
@@ -549,13 +549,12 @@ def autoreconf(self, pkg, spec, prefix):
|
||||
tty.warn("* a custom AUTORECONF phase in the package *")
|
||||
tty.warn("*********************************************************")
|
||||
with fs.working_dir(self.configure_directory):
|
||||
m = inspect.getmodule(self.pkg)
|
||||
# This line is what is needed most of the time
|
||||
# --install, --verbose, --force
|
||||
autoreconf_args = ["-ivf"]
|
||||
autoreconf_args += self.autoreconf_search_path_args
|
||||
autoreconf_args += self.autoreconf_extra_args
|
||||
m.autoreconf(*autoreconf_args)
|
||||
self.pkg.module.autoreconf(*autoreconf_args)
|
||||
|
||||
@property
|
||||
def autoreconf_search_path_args(self):
|
||||
@@ -579,7 +578,7 @@ def set_configure_or_die(self):
|
||||
raise RuntimeError(msg.format(self.configure_directory))
|
||||
|
||||
# Monkey-patch the configure script in the corresponding module
|
||||
inspect.getmodule(self.pkg).configure = Executable(self.configure_abs_path)
|
||||
SetPackageGlobals(self.pkg).configure = Executable(self.configure_abs_path)
|
||||
|
||||
def configure_args(self):
|
||||
"""Return the list of all the arguments that must be passed to configure,
|
||||
@@ -596,7 +595,7 @@ def configure(self, pkg, spec, prefix):
|
||||
options += self.configure_args()
|
||||
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).configure(*options)
|
||||
pkg.module.configure(*options)
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
@@ -604,12 +603,12 @@ def build(self, pkg, spec, prefix):
|
||||
params = ["V=1"]
|
||||
params += self.build_targets
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*params)
|
||||
pkg.module.make(*params)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
pkg.module.make(*self.install_targets)
|
||||
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import inspect
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
import spack.builder
|
||||
@@ -72,9 +70,7 @@ def check_args(self):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Runs ``cargo install`` in the source directory"""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(pkg).cargo(
|
||||
"install", "--root", "out", "--path", ".", *self.build_args
|
||||
)
|
||||
pkg.module.cargo("install", "--root", "out", "--path", ".", *self.build_args)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Copy build files into package prefix."""
|
||||
@@ -86,4 +82,4 @@ def install(self, pkg, spec, prefix):
|
||||
def check(self):
|
||||
"""Run "cargo test"."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).cargo("test", *self.check_args)
|
||||
self.pkg.module.cargo("test", *self.check_args)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import collections.abc
|
||||
import inspect
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
@@ -539,24 +538,24 @@ def cmake(self, pkg, spec, prefix):
|
||||
options += self.cmake_args()
|
||||
options.append(os.path.abspath(self.root_cmakelists_dir))
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).cmake(*options)
|
||||
pkg.module.cmake(*options)
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
inspect.getmodule(self.pkg).make(*self.build_targets)
|
||||
pkg.module.make(*self.build_targets)
|
||||
elif self.generator == "Ninja":
|
||||
self.build_targets.append("-v")
|
||||
inspect.getmodule(self.pkg).ninja(*self.build_targets)
|
||||
pkg.module.ninja(*self.build_targets)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
pkg.module.make(*self.install_targets)
|
||||
elif self.generator == "Ninja":
|
||||
inspect.getmodule(self.pkg).ninja(*self.install_targets)
|
||||
pkg.module.ninja(*self.install_targets)
|
||||
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
|
||||
@@ -138,14 +138,14 @@ def cuda_flags(arch_list):
|
||||
conflicts("%gcc@11.2:", when="+cuda ^cuda@:11.5")
|
||||
conflicts("%gcc@12:", when="+cuda ^cuda@:11.8")
|
||||
conflicts("%gcc@13:", when="+cuda ^cuda@:12.3")
|
||||
conflicts("%gcc@14:", when="+cuda ^cuda@:12.5")
|
||||
conflicts("%gcc@14:", when="+cuda ^cuda@:12.6")
|
||||
conflicts("%clang@12:", when="+cuda ^cuda@:11.4.0")
|
||||
conflicts("%clang@13:", when="+cuda ^cuda@:11.5")
|
||||
conflicts("%clang@14:", when="+cuda ^cuda@:11.7")
|
||||
conflicts("%clang@15:", when="+cuda ^cuda@:12.0")
|
||||
conflicts("%clang@16:", when="+cuda ^cuda@:12.1")
|
||||
conflicts("%clang@17:", when="+cuda ^cuda@:12.3")
|
||||
conflicts("%clang@18:", when="+cuda ^cuda@:12.5")
|
||||
conflicts("%clang@18:", when="+cuda ^cuda@:12.6")
|
||||
|
||||
# https://gist.github.com/ax3l/9489132#gistcomment-3860114
|
||||
conflicts("%gcc@10", when="+cuda ^cuda@:11.4.0")
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import inspect
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
import spack.builder
|
||||
@@ -82,7 +80,7 @@ def check_args(self):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Runs ``go build`` in the source directory"""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(pkg).go("build", *self.build_args)
|
||||
pkg.module.go("build", *self.build_args)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install built binaries into prefix bin."""
|
||||
@@ -95,4 +93,4 @@ def install(self, pkg, spec, prefix):
|
||||
def check(self):
|
||||
"""Run ``go test .`` in the source directory"""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).go("test", *self.check_args)
|
||||
self.pkg.module.go("test", *self.check_args)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
from typing import List
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
@@ -103,12 +102,12 @@ def edit(self, pkg, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.build_targets)
|
||||
pkg.module.make(*self.build_targets)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
pkg.module.make(*self.install_targets)
|
||||
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
@@ -195,19 +194,19 @@ def meson(self, pkg, spec, prefix):
|
||||
options += self.std_meson_args
|
||||
options += self.meson_args()
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).meson(*options)
|
||||
pkg.module.meson(*options)
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
options = ["-v"]
|
||||
options += self.build_targets
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).ninja(*options)
|
||||
pkg.module.ninja(*options)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).ninja(*self.install_targets)
|
||||
pkg.module.ninja(*self.install_targets)
|
||||
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
@@ -104,7 +103,7 @@ def msbuild_install_args(self):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "msbuild" on the build targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).msbuild(
|
||||
pkg.module.msbuild(
|
||||
*self.std_msbuild_args,
|
||||
*self.msbuild_args(),
|
||||
self.define_targets(*self.build_targets),
|
||||
@@ -114,6 +113,6 @@ def install(self, pkg, spec, prefix):
|
||||
"""Run "msbuild" on the install targets specified by the builder.
|
||||
This is INSTALL by default"""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).msbuild(
|
||||
pkg.module.msbuild(
|
||||
*self.msbuild_install_args(), self.define_targets(*self.install_targets)
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
@@ -132,9 +131,7 @@ def build(self, pkg, spec, prefix):
|
||||
if self.makefile_name:
|
||||
opts.append("/F{}".format(self.makefile_name))
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).nmake(
|
||||
*opts, *self.build_targets, ignore_quotes=self.ignore_quotes
|
||||
)
|
||||
pkg.module.nmake(*opts, *self.build_targets, ignore_quotes=self.ignore_quotes)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "nmake" on the install targets specified by the builder.
|
||||
@@ -146,6 +143,4 @@ def install(self, pkg, spec, prefix):
|
||||
opts.append("/F{}".format(self.makefile_name))
|
||||
opts.append(self.define("PREFIX", fs.windows_sfn(prefix)))
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).nmake(
|
||||
*opts, *self.install_targets, ignore_quotes=self.ignore_quotes
|
||||
)
|
||||
pkg.module.nmake(*opts, *self.install_targets, ignore_quotes=self.ignore_quotes)
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, extends
|
||||
@@ -47,7 +45,7 @@ class OctaveBuilder(BaseBuilder):
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package from the archive file"""
|
||||
inspect.getmodule(self.pkg).octave(
|
||||
pkg.module.octave(
|
||||
"--quiet",
|
||||
"--norc",
|
||||
"--built-in-docstrings-file=/dev/null",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
"""Common utilities for managing intel oneapi packages."""
|
||||
import getpass
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
@@ -13,6 +12,7 @@
|
||||
from llnl.util.filesystem import HeaderList, LibraryList, find_libraries, join_path, mkdirp
|
||||
from llnl.util.link_tree import LinkTree
|
||||
|
||||
import spack.util.path
|
||||
from spack.build_environment import dso_suffix
|
||||
from spack.directives import conflicts, license, redistribute, variant
|
||||
from spack.package_base import InstallError
|
||||
@@ -99,7 +99,7 @@ def install_component(self, installer_path):
|
||||
# with other install depends on the userid. For root, we
|
||||
# delete the installercache before and after install. For
|
||||
# non root we redefine the HOME environment variable.
|
||||
if getpass.getuser() == "root":
|
||||
if spack.util.path.get_user() == "root":
|
||||
shutil.rmtree("/var/intel/installercache", ignore_errors=True)
|
||||
|
||||
bash = Executable("bash")
|
||||
@@ -122,7 +122,7 @@ def install_component(self, installer_path):
|
||||
self.prefix,
|
||||
)
|
||||
|
||||
if getpass.getuser() == "root":
|
||||
if spack.util.path.get_user() == "root":
|
||||
shutil.rmtree("/var/intel/installercache", ignore_errors=True)
|
||||
|
||||
# Some installers have a bug and do not return an error code when failing
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
import os
|
||||
from typing import Iterable
|
||||
|
||||
@@ -134,7 +133,7 @@ def build_method(self):
|
||||
def build_executable(self):
|
||||
"""Returns the executable method to build the perl package"""
|
||||
if self.build_method == "Makefile.PL":
|
||||
build_executable = inspect.getmodule(self.pkg).make
|
||||
build_executable = self.pkg.module.make
|
||||
elif self.build_method == "Build.PL":
|
||||
build_executable = Executable(os.path.join(self.pkg.stage.source_path, "Build"))
|
||||
return build_executable
|
||||
@@ -158,7 +157,7 @@ def configure(self, pkg, spec, prefix):
|
||||
options = ["Build.PL", "--install_base", prefix]
|
||||
options += self.configure_args()
|
||||
|
||||
inspect.getmodule(self.pkg).perl(*options)
|
||||
pkg.module.perl(*options)
|
||||
|
||||
# It is possible that the shebang in the Build script that is created from
|
||||
# Build.PL may be too long causing the build to fail. Patching the shebang
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import operator
|
||||
import os
|
||||
import re
|
||||
@@ -17,7 +16,7 @@
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang as lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import HeaderList, LibraryList
|
||||
from llnl.util.filesystem import HeaderList, LibraryList, join_path
|
||||
|
||||
import spack.builder
|
||||
import spack.config
|
||||
@@ -120,6 +119,12 @@ def skip_modules(self) -> Iterable[str]:
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def bindir(self) -> str:
|
||||
"""Path to Python package's bindir, bin on unix like OS's Scripts on Windows"""
|
||||
windows = self.spec.satisfies("platform=windows")
|
||||
return join_path(self.spec.prefix, "Scripts" if windows else "bin")
|
||||
|
||||
def view_file_conflicts(self, view, merge_map):
|
||||
"""Report all file conflicts, excepting special cases for python.
|
||||
Specifically, this does not report errors for duplicate
|
||||
@@ -222,7 +227,7 @@ def test_imports(self) -> None:
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
python = inspect.getmodule(self).python # type: ignore[union-attr]
|
||||
python = self.module.python
|
||||
for module in self.import_modules:
|
||||
with test_part(
|
||||
self,
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
import spack.builder
|
||||
@@ -66,17 +64,17 @@ def qmake_args(self):
|
||||
def qmake(self, pkg, spec, prefix):
|
||||
"""Run ``qmake`` to configure the project and generate a Makefile."""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).qmake(*self.qmake_args())
|
||||
pkg.module.qmake(*self.qmake_args())
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make()
|
||||
pkg.module.make()
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make("install")
|
||||
pkg.module.make("install")
|
||||
|
||||
def check(self):
|
||||
"""Search the Makefile for a ``check:`` target and runs it if found."""
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import llnl.util.lang as lang
|
||||
@@ -51,7 +50,7 @@ def install(self, pkg, spec, prefix):
|
||||
|
||||
args.extend(["--library={0}".format(self.pkg.module.r_lib_dir), self.stage.source_path])
|
||||
|
||||
inspect.getmodule(self.pkg).R(*args)
|
||||
pkg.module.R(*args)
|
||||
|
||||
|
||||
class RPackage(Package):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import glob
|
||||
import inspect
|
||||
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
@@ -52,10 +51,10 @@ def build(self, pkg, spec, prefix):
|
||||
gemspecs = glob.glob("*.gemspec")
|
||||
rakefiles = glob.glob("Rakefile")
|
||||
if gemspecs:
|
||||
inspect.getmodule(self.pkg).gem("build", "--norc", gemspecs[0])
|
||||
pkg.module.gem("build", "--norc", gemspecs[0])
|
||||
elif rakefiles:
|
||||
jobs = inspect.getmodule(self.pkg).make_jobs
|
||||
inspect.getmodule(self.pkg).rake("package", "-j{0}".format(jobs))
|
||||
jobs = pkg.module.make_jobs
|
||||
pkg.module.rake("package", "-j{0}".format(jobs))
|
||||
else:
|
||||
# Some Ruby packages only ship `*.gem` files, so nothing to build
|
||||
pass
|
||||
@@ -70,6 +69,6 @@ def install(self, pkg, spec, prefix):
|
||||
# if --install-dir is not used, GEM_PATH is deleted from the
|
||||
# environement, and Gems required to build native extensions will
|
||||
# not be found. Those extensions are built during `gem install`.
|
||||
inspect.getmodule(self.pkg).gem(
|
||||
pkg.module.gem(
|
||||
"install", "--norc", "--ignore-dependencies", "--install-dir", prefix, gems[0]
|
||||
)
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
@@ -63,8 +61,7 @@ def build_args(self, spec, prefix):
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Build the package."""
|
||||
args = self.build_args(spec, prefix)
|
||||
inspect.getmodule(self.pkg).scons(*args)
|
||||
pkg.module.scons(*self.build_args(spec, prefix))
|
||||
|
||||
def install_args(self, spec, prefix):
|
||||
"""Arguments to pass to install."""
|
||||
@@ -72,9 +69,7 @@ def install_args(self, spec, prefix):
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package."""
|
||||
args = self.install_args(spec, prefix)
|
||||
|
||||
inspect.getmodule(self.pkg).scons("install", *args)
|
||||
pkg.module.scons("install", *self.install_args(spec, prefix))
|
||||
|
||||
def build_test(self):
|
||||
"""Run unit tests after build.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
|
||||
@@ -86,14 +85,13 @@ def import_modules(self):
|
||||
|
||||
def python(self, *args, **kwargs):
|
||||
"""The python ``Executable``."""
|
||||
inspect.getmodule(self).python(*args, **kwargs)
|
||||
self.pkg.module.python(*args, **kwargs)
|
||||
|
||||
def test_imports(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
python = inspect.getmodule(self).python
|
||||
for module in self.import_modules:
|
||||
with spack.install_test.test_part(
|
||||
self,
|
||||
@@ -101,7 +99,7 @@ def test_imports(self):
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
):
|
||||
python("-c", "import {0}".format(module))
|
||||
self.python("-c", "import {0}".format(module))
|
||||
|
||||
|
||||
@spack.builder.builder("sip")
|
||||
@@ -136,7 +134,7 @@ def configure(self, pkg, spec, prefix):
|
||||
"""Configure the package."""
|
||||
|
||||
# https://www.riverbankcomputing.com/static/Docs/sip/command_line_tools.html
|
||||
args = ["--verbose", "--target-dir", inspect.getmodule(self.pkg).python_platlib]
|
||||
args = ["--verbose", "--target-dir", pkg.module.python_platlib]
|
||||
args.extend(self.configure_args())
|
||||
|
||||
# https://github.com/Python-SIP/sip/commit/cb0be6cb6e9b756b8b0db3136efb014f6fb9b766
|
||||
@@ -155,7 +153,7 @@ def build(self, pkg, spec, prefix):
|
||||
args = self.build_args()
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*args)
|
||||
pkg.module.make(*args)
|
||||
|
||||
def build_args(self):
|
||||
"""Arguments to pass to build."""
|
||||
@@ -166,7 +164,7 @@ def install(self, pkg, spec, prefix):
|
||||
args = self.install_args()
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make("install", *args)
|
||||
pkg.module.make("install", *args)
|
||||
|
||||
def install_args(self):
|
||||
"""Arguments to pass to install."""
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
import spack.builder
|
||||
@@ -90,11 +88,11 @@ def build_directory(self):
|
||||
|
||||
def python(self, *args, **kwargs):
|
||||
"""The python ``Executable``."""
|
||||
inspect.getmodule(self.pkg).python(*args, **kwargs)
|
||||
self.pkg.module.python(*args, **kwargs)
|
||||
|
||||
def waf(self, *args, **kwargs):
|
||||
"""Runs the waf ``Executable``."""
|
||||
jobs = inspect.getmodule(self.pkg).make_jobs
|
||||
jobs = self.pkg.module.make_jobs
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
self.python("waf", "-j{0}".format(jobs), *args, **kwargs)
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
import collections.abc
|
||||
import copy
|
||||
import functools
|
||||
import inspect
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from llnl.util import lang
|
||||
|
||||
import spack.build_environment
|
||||
import spack.multimethod
|
||||
|
||||
#: Builder classes, as registered by the "builder" decorator
|
||||
BUILDER_CLS = {}
|
||||
@@ -96,11 +96,10 @@ class hierarchy (look at AspellDictPackage for an example of that)
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package object for which we need a builder
|
||||
"""
|
||||
package_module = inspect.getmodule(pkg)
|
||||
package_buildsystem = buildsystem_name(pkg)
|
||||
default_builder_cls = BUILDER_CLS[package_buildsystem]
|
||||
builder_cls_name = default_builder_cls.__name__
|
||||
builder_cls = getattr(package_module, builder_cls_name, None)
|
||||
builder_cls = getattr(pkg.module, builder_cls_name, None)
|
||||
if builder_cls:
|
||||
return builder_cls(pkg)
|
||||
|
||||
@@ -295,7 +294,11 @@ def _decorator(fn):
|
||||
return _decorator
|
||||
|
||||
|
||||
class BuilderMeta(PhaseCallbacksMeta, type(collections.abc.Sequence)): # type: ignore
|
||||
class BuilderMeta(
|
||||
PhaseCallbacksMeta,
|
||||
spack.multimethod.MultiMethodMeta,
|
||||
type(collections.abc.Sequence), # type: ignore
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
import llnl.util.lang
|
||||
from llnl.util.filesystem import mkdirp
|
||||
from llnl.util.symlink import symlink
|
||||
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.fetch_strategy
|
||||
import spack.mirror
|
||||
import spack.paths
|
||||
import spack.util.file_cache
|
||||
import spack.util.path
|
||||
@@ -74,23 +74,6 @@ def store(self, fetcher, relative_dest):
|
||||
mkdirp(os.path.dirname(dst))
|
||||
fetcher.archive(dst)
|
||||
|
||||
def symlink(self, mirror_ref):
|
||||
"""Symlink a human readible path in our mirror to the actual
|
||||
storage location."""
|
||||
|
||||
cosmetic_path = os.path.join(self.root, mirror_ref.cosmetic_path)
|
||||
storage_path = os.path.join(self.root, mirror_ref.storage_path)
|
||||
relative_dst = os.path.relpath(storage_path, start=os.path.dirname(cosmetic_path))
|
||||
|
||||
if not os.path.exists(cosmetic_path):
|
||||
if os.path.lexists(cosmetic_path):
|
||||
# In this case the link itself exists but it is broken: remove
|
||||
# it and recreate it (in order to fix any symlinks broken prior
|
||||
# to https://github.com/spack/spack/pull/13908)
|
||||
os.unlink(cosmetic_path)
|
||||
mkdirp(os.path.dirname(cosmetic_path))
|
||||
symlink(relative_dst, cosmetic_path)
|
||||
|
||||
|
||||
#: Spack's local cache for downloaded source archives
|
||||
FETCH_CACHE: Union[spack.fetch_strategy.FsCache, llnl.util.lang.Singleton] = (
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import spack.paths
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
import spack.stage
|
||||
import spack.util.git
|
||||
import spack.util.gpg as gpg_util
|
||||
import spack.util.spack_yaml as syaml
|
||||
@@ -1107,9 +1108,10 @@ def main_script_replacements(cmd):
|
||||
if cdash_handler and cdash_handler.auth_token:
|
||||
try:
|
||||
cdash_handler.populate_buildgroup(all_job_names)
|
||||
except (SpackError, HTTPError, URLError) as err:
|
||||
except (SpackError, HTTPError, URLError, TimeoutError) as err:
|
||||
tty.warn(f"Problem populating buildgroup: {err}")
|
||||
else:
|
||||
elif cdash_config:
|
||||
# warn only if there was actually a CDash configuration.
|
||||
tty.warn("Unable to populate buildgroup without CDash credentials")
|
||||
|
||||
service_job_retries = {
|
||||
@@ -1370,15 +1372,6 @@ def can_verify_binaries():
|
||||
return len(gpg_util.public_keys()) >= 1
|
||||
|
||||
|
||||
def _push_to_build_cache(spec: spack.spec.Spec, sign_binaries: bool, mirror_url: str) -> None:
|
||||
"""Unchecked version of the public API, for easier mocking"""
|
||||
bindist.push_or_raise(
|
||||
spec,
|
||||
spack.mirror.Mirror.from_url(mirror_url).push_url,
|
||||
bindist.PushOptions(force=True, unsigned=not sign_binaries),
|
||||
)
|
||||
|
||||
|
||||
def push_to_build_cache(spec: spack.spec.Spec, mirror_url: str, sign_binaries: bool) -> bool:
|
||||
"""Push one or more binary packages to the mirror.
|
||||
|
||||
@@ -1389,20 +1382,15 @@ def push_to_build_cache(spec: spack.spec.Spec, mirror_url: str, sign_binaries: b
|
||||
sign_binaries: If True, spack will attempt to sign binary package before pushing.
|
||||
"""
|
||||
tty.debug(f"Pushing to build cache ({'signed' if sign_binaries else 'unsigned'})")
|
||||
signing_key = bindist.select_signing_key() if sign_binaries else None
|
||||
mirror = spack.mirror.Mirror.from_url(mirror_url)
|
||||
try:
|
||||
_push_to_build_cache(spec, sign_binaries, mirror_url)
|
||||
with bindist.make_uploader(mirror, signing_key=signing_key) as uploader:
|
||||
uploader.push_or_raise([spec])
|
||||
return True
|
||||
except bindist.PushToBuildCacheError as e:
|
||||
tty.error(str(e))
|
||||
tty.error(f"Problem writing to {mirror_url}: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
# TODO (zackgalbreath): write an adapter for boto3 exceptions so we can catch a specific
|
||||
# exception instead of parsing str(e)...
|
||||
msg = str(e)
|
||||
if any(x in msg for x in ["Access Denied", "InvalidAccessKeyId"]):
|
||||
tty.error(f"Permission problem writing to {mirror_url}: {msg}")
|
||||
return False
|
||||
raise
|
||||
|
||||
|
||||
def remove_other_mirrors(mirrors_to_keep, scope=None):
|
||||
@@ -1448,10 +1436,6 @@ def copy_stage_logs_to_artifacts(job_spec: spack.spec.Spec, job_log_dir: str) ->
|
||||
job_log_dir: path into which build log should be copied
|
||||
"""
|
||||
tty.debug(f"job spec: {job_spec}")
|
||||
if not job_spec:
|
||||
msg = f"Cannot copy stage logs: job spec ({job_spec}) is required"
|
||||
tty.error(msg)
|
||||
return
|
||||
|
||||
try:
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(job_spec.name)
|
||||
@@ -2083,7 +2067,7 @@ def read_broken_spec(broken_spec_url):
|
||||
"""
|
||||
try:
|
||||
_, _, fs = web_util.read_from_url(broken_spec_url)
|
||||
except (URLError, web_util.SpackWebError, HTTPError):
|
||||
except web_util.SpackWebError:
|
||||
tty.warn(f"Unable to read broken spec from {broken_spec_url}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@@ -114,8 +115,8 @@ def get_module(cmd_name):
|
||||
|
||||
try:
|
||||
# Try to import the command from the built-in directory
|
||||
module_name = "%s.%s" % (__name__, pname)
|
||||
module = __import__(module_name, fromlist=[pname, SETUP_PARSER, DESCRIPTION], level=0)
|
||||
module_name = f"{__name__}.{pname}"
|
||||
module = importlib.import_module(module_name)
|
||||
tty.debug("Imported {0} from built-in commands".format(pname))
|
||||
except ImportError:
|
||||
module = spack.extensions.get_module(cmd_name)
|
||||
|
||||
@@ -3,28 +3,24 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import argparse
|
||||
import copy
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import multiprocessing
|
||||
import multiprocessing.pool
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from typing import List, Tuple
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.string import plural
|
||||
from llnl.util.lang import elide_list
|
||||
from llnl.util.lang import elide_list, stable_partition
|
||||
|
||||
import spack.binary_distribution as bindist
|
||||
import spack.cmd
|
||||
import spack.config
|
||||
import spack.deptypes as dt
|
||||
import spack.environment as ev
|
||||
import spack.error
|
||||
import spack.hash_types as ht
|
||||
import spack.mirror
|
||||
import spack.oci.oci
|
||||
import spack.oci.opener
|
||||
@@ -35,28 +31,12 @@
|
||||
import spack.store
|
||||
import spack.user_environment
|
||||
import spack.util.crypto
|
||||
import spack.util.parallel
|
||||
import spack.util.url as url_util
|
||||
import spack.util.web as web_util
|
||||
from spack import traverse
|
||||
from spack.build_environment import determine_number_of_jobs
|
||||
from spack.cmd import display_specs
|
||||
from spack.cmd.common import arguments
|
||||
from spack.oci.image import (
|
||||
Digest,
|
||||
ImageReference,
|
||||
default_config,
|
||||
default_index_tag,
|
||||
default_manifest,
|
||||
default_tag,
|
||||
tag_is_spec,
|
||||
)
|
||||
from spack.oci.oci import (
|
||||
copy_missing_layers_with_retry,
|
||||
get_manifest_and_config_with_retry,
|
||||
list_tags,
|
||||
upload_blob_with_retry,
|
||||
upload_manifest_with_retry,
|
||||
)
|
||||
from spack.spec import Spec, save_dependency_specfiles
|
||||
|
||||
description = "create, download and install binary packages"
|
||||
@@ -112,6 +92,17 @@ def setup_parser(subparser: argparse.ArgumentParser):
|
||||
"Alternatively, one can decide to build a cache for only the package or only the "
|
||||
"dependencies",
|
||||
)
|
||||
with_or_without_build_deps = push.add_mutually_exclusive_group()
|
||||
with_or_without_build_deps.add_argument(
|
||||
"--with-build-dependencies",
|
||||
action="store_true",
|
||||
help="include build dependencies in the buildcache",
|
||||
)
|
||||
with_or_without_build_deps.add_argument(
|
||||
"--without-build-dependencies",
|
||||
action="store_true",
|
||||
help="exclude build dependencies from the buildcache",
|
||||
)
|
||||
push.add_argument(
|
||||
"--fail-fast",
|
||||
action="store_true",
|
||||
@@ -329,39 +320,6 @@ def _format_spec(spec: Spec) -> str:
|
||||
return spec.cformat("{name}{@version}{/hash:7}")
|
||||
|
||||
|
||||
def _progress(i: int, total: int):
|
||||
if total > 1:
|
||||
digits = len(str(total))
|
||||
return f"[{i+1:{digits}}/{total}] "
|
||||
return ""
|
||||
|
||||
|
||||
class NoPool:
|
||||
def map(self, func, args):
|
||||
return [func(a) for a in args]
|
||||
|
||||
def starmap(self, func, args):
|
||||
return [func(*a) for a in args]
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
MaybePool = Union[multiprocessing.pool.Pool, NoPool]
|
||||
|
||||
|
||||
def _make_pool() -> MaybePool:
|
||||
"""Can't use threading because it's unsafe, and can't use spawned processes because of globals.
|
||||
That leaves only forking"""
|
||||
if multiprocessing.get_start_method() == "fork":
|
||||
return multiprocessing.pool.Pool(determine_number_of_jobs(parallel=True))
|
||||
else:
|
||||
return NoPool()
|
||||
|
||||
|
||||
def _skip_no_redistribute_for_public(specs):
|
||||
remaining_specs = list()
|
||||
removed_specs = list()
|
||||
@@ -381,6 +339,45 @@ def _skip_no_redistribute_for_public(specs):
|
||||
return remaining_specs
|
||||
|
||||
|
||||
class PackagesAreNotInstalledError(spack.error.SpackError):
|
||||
"""Raised when a list of specs is not installed but picked to be packaged."""
|
||||
|
||||
def __init__(self, specs: List[Spec]):
|
||||
super().__init__(
|
||||
"Cannot push non-installed packages",
|
||||
", ".join(elide_list([_format_spec(s) for s in specs], 5)),
|
||||
)
|
||||
|
||||
|
||||
class PackageNotInstalledError(spack.error.SpackError):
|
||||
"""Raised when a spec is not installed but picked to be packaged."""
|
||||
|
||||
|
||||
def _specs_to_be_packaged(
|
||||
requested: List[Spec], things_to_install: str, build_deps: bool
|
||||
) -> List[Spec]:
|
||||
"""Collect all non-external with or without roots and dependencies"""
|
||||
if "dependencies" not in things_to_install:
|
||||
deptype = dt.NONE
|
||||
elif build_deps:
|
||||
deptype = dt.ALL
|
||||
else:
|
||||
deptype = dt.RUN | dt.LINK | dt.TEST
|
||||
specs = [
|
||||
s
|
||||
for s in traverse.traverse_nodes(
|
||||
requested,
|
||||
root="package" in things_to_install,
|
||||
deptype=deptype,
|
||||
order="breadth",
|
||||
key=traverse.by_dag_hash,
|
||||
)
|
||||
if not s.external
|
||||
]
|
||||
specs.reverse()
|
||||
return specs
|
||||
|
||||
|
||||
def push_fn(args):
|
||||
"""create a binary package and push it to a mirror"""
|
||||
if args.spec_file:
|
||||
@@ -394,13 +391,8 @@ def push_fn(args):
|
||||
else:
|
||||
roots = spack.cmd.require_active_env(cmd_name="buildcache push").concrete_roots()
|
||||
|
||||
mirror: spack.mirror.Mirror = args.mirror
|
||||
|
||||
# Check if this is an OCI image.
|
||||
try:
|
||||
target_image = spack.oci.oci.image_from_mirror(mirror)
|
||||
except ValueError:
|
||||
target_image = None
|
||||
mirror = args.mirror
|
||||
assert isinstance(mirror, spack.mirror.Mirror)
|
||||
|
||||
push_url = mirror.push_url
|
||||
|
||||
@@ -411,92 +403,52 @@ def push_fn(args):
|
||||
unsigned = not (args.key or args.signed)
|
||||
|
||||
# For OCI images, we require dependencies to be pushed for now.
|
||||
if target_image:
|
||||
if "dependencies" not in args.things_to_install:
|
||||
tty.die("Dependencies must be pushed for OCI images.")
|
||||
if not unsigned:
|
||||
tty.warn(
|
||||
"Code signing is currently not supported for OCI images. "
|
||||
"Use --unsigned to silence this warning."
|
||||
)
|
||||
if mirror.push_url.startswith("oci://") and not unsigned:
|
||||
tty.warn(
|
||||
"Code signing is currently not supported for OCI images. "
|
||||
"Use --unsigned to silence this warning."
|
||||
)
|
||||
unsigned = True
|
||||
|
||||
# This is a list of installed, non-external specs.
|
||||
specs = bindist.specs_to_be_packaged(
|
||||
# Select a signing key, or None if unsigned.
|
||||
signing_key = None if unsigned else (args.key or bindist.select_signing_key())
|
||||
|
||||
specs = _specs_to_be_packaged(
|
||||
roots,
|
||||
root="package" in args.things_to_install,
|
||||
dependencies="dependencies" in args.things_to_install,
|
||||
things_to_install=args.things_to_install,
|
||||
build_deps=args.with_build_dependencies or not args.without_build_dependencies,
|
||||
)
|
||||
|
||||
if not args.private:
|
||||
specs = _skip_no_redistribute_for_public(specs)
|
||||
|
||||
# When pushing multiple specs, print the url once ahead of time, as well as how
|
||||
# many specs are being pushed.
|
||||
if len(specs) > 1:
|
||||
tty.info(f"Selected {len(specs)} specs to push to {push_url}")
|
||||
|
||||
failed = []
|
||||
|
||||
# TODO: unify this logic in the future.
|
||||
if target_image:
|
||||
base_image = ImageReference.from_string(args.base_image) if args.base_image else None
|
||||
with tempfile.TemporaryDirectory(
|
||||
dir=spack.stage.get_stage_root()
|
||||
) as tmpdir, _make_pool() as pool:
|
||||
skipped, base_images, checksums = _push_oci(
|
||||
target_image=target_image,
|
||||
base_image=base_image,
|
||||
installed_specs_with_deps=specs,
|
||||
force=args.force,
|
||||
tmpdir=tmpdir,
|
||||
pool=pool,
|
||||
)
|
||||
|
||||
# Apart from creating manifests for each individual spec, we allow users to create a
|
||||
# separate image tag for all root specs and their runtime dependencies.
|
||||
if args.tag:
|
||||
tagged_image = target_image.with_tag(args.tag)
|
||||
# _push_oci may not populate base_images if binaries were already in the registry
|
||||
for spec in roots:
|
||||
_update_base_images(
|
||||
base_image=base_image,
|
||||
target_image=target_image,
|
||||
spec=spec,
|
||||
base_image_cache=base_images,
|
||||
)
|
||||
_put_manifest(base_images, checksums, tagged_image, tmpdir, None, None, *roots)
|
||||
tty.info(f"Tagged {tagged_image}")
|
||||
|
||||
else:
|
||||
skipped = []
|
||||
|
||||
for i, spec in enumerate(specs):
|
||||
try:
|
||||
bindist.push_or_raise(
|
||||
spec,
|
||||
push_url,
|
||||
bindist.PushOptions(
|
||||
force=args.force,
|
||||
unsigned=unsigned,
|
||||
key=args.key,
|
||||
regenerate_index=args.update_index,
|
||||
),
|
||||
# Pushing not installed specs is an error. Either fail fast or populate the error list and
|
||||
# push installed package in best effort mode.
|
||||
failed: List[Tuple[Spec, BaseException]] = []
|
||||
with spack.store.STORE.db.read_transaction():
|
||||
if any(not s.installed for s in specs):
|
||||
specs, not_installed = stable_partition(specs, lambda s: s.installed)
|
||||
if args.fail_fast:
|
||||
raise PackagesAreNotInstalledError(not_installed)
|
||||
else:
|
||||
failed.extend(
|
||||
(s, PackageNotInstalledError("package not installed")) for s in not_installed
|
||||
)
|
||||
|
||||
msg = f"{_progress(i, len(specs))}Pushed {_format_spec(spec)}"
|
||||
if len(specs) == 1:
|
||||
msg += f" to {push_url}"
|
||||
tty.info(msg)
|
||||
|
||||
except bindist.NoOverwriteException:
|
||||
skipped.append(_format_spec(spec))
|
||||
|
||||
# Catch any other exception unless the fail fast option is set
|
||||
except Exception as e:
|
||||
if args.fail_fast or isinstance(
|
||||
e, (bindist.PickKeyException, bindist.NoKeyException)
|
||||
):
|
||||
raise
|
||||
failed.append((_format_spec(spec), e))
|
||||
with bindist.make_uploader(
|
||||
mirror=mirror,
|
||||
force=args.force,
|
||||
update_index=args.update_index,
|
||||
signing_key=signing_key,
|
||||
base_image=args.base_image,
|
||||
) as uploader:
|
||||
skipped, upload_errors = uploader.push(specs=specs)
|
||||
failed.extend(upload_errors)
|
||||
if not upload_errors and args.tag:
|
||||
uploader.tag(args.tag, roots)
|
||||
|
||||
if skipped:
|
||||
if len(specs) == 1:
|
||||
@@ -519,389 +471,15 @@ def push_fn(args):
|
||||
raise spack.error.SpackError(
|
||||
f"The following {len(failed)} errors occurred while pushing specs to the buildcache",
|
||||
"\n".join(
|
||||
elide_list([f" {spec}: {e.__class__.__name__}: {e}" for spec, e in failed], 5)
|
||||
),
|
||||
)
|
||||
|
||||
# Update the index if requested
|
||||
# TODO: remove update index logic out of bindist; should be once after all specs are pushed
|
||||
# not once per spec.
|
||||
if target_image and len(skipped) < len(specs) and args.update_index:
|
||||
with tempfile.TemporaryDirectory(
|
||||
dir=spack.stage.get_stage_root()
|
||||
) as tmpdir, _make_pool() as pool:
|
||||
_update_index_oci(target_image, tmpdir, pool)
|
||||
|
||||
|
||||
def _get_spack_binary_blob(image_ref: ImageReference) -> Optional[spack.oci.oci.Blob]:
|
||||
"""Get the spack tarball layer digests and size if it exists"""
|
||||
try:
|
||||
manifest, config = get_manifest_and_config_with_retry(image_ref)
|
||||
|
||||
return spack.oci.oci.Blob(
|
||||
compressed_digest=Digest.from_string(manifest["layers"][-1]["digest"]),
|
||||
uncompressed_digest=Digest.from_string(config["rootfs"]["diff_ids"][-1]),
|
||||
size=manifest["layers"][-1]["size"],
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _push_single_spack_binary_blob(image_ref: ImageReference, spec: spack.spec.Spec, tmpdir: str):
|
||||
filename = os.path.join(tmpdir, f"{spec.dag_hash()}.tar.gz")
|
||||
|
||||
# Create an oci.image.layer aka tarball of the package
|
||||
compressed_tarfile_checksum, tarfile_checksum = spack.oci.oci.create_tarball(spec, filename)
|
||||
|
||||
blob = spack.oci.oci.Blob(
|
||||
Digest.from_sha256(compressed_tarfile_checksum),
|
||||
Digest.from_sha256(tarfile_checksum),
|
||||
os.path.getsize(filename),
|
||||
)
|
||||
|
||||
# Upload the blob
|
||||
upload_blob_with_retry(image_ref, file=filename, digest=blob.compressed_digest)
|
||||
|
||||
# delete the file
|
||||
os.unlink(filename)
|
||||
|
||||
return blob
|
||||
|
||||
|
||||
def _retrieve_env_dict_from_config(config: dict) -> dict:
|
||||
"""Retrieve the environment variables from the image config file.
|
||||
Sets a default value for PATH if it is not present.
|
||||
|
||||
Args:
|
||||
config (dict): The image config file.
|
||||
|
||||
Returns:
|
||||
dict: The environment variables.
|
||||
"""
|
||||
env = {"PATH": "/bin:/usr/bin"}
|
||||
|
||||
if "Env" in config.get("config", {}):
|
||||
for entry in config["config"]["Env"]:
|
||||
key, value = entry.split("=", 1)
|
||||
env[key] = value
|
||||
return env
|
||||
|
||||
|
||||
def _archspec_to_gooarch(spec: spack.spec.Spec) -> str:
|
||||
name = spec.target.family.name
|
||||
name_map = {"aarch64": "arm64", "x86_64": "amd64"}
|
||||
return name_map.get(name, name)
|
||||
|
||||
|
||||
def _put_manifest(
|
||||
base_images: Dict[str, Tuple[dict, dict]],
|
||||
checksums: Dict[str, spack.oci.oci.Blob],
|
||||
image_ref: ImageReference,
|
||||
tmpdir: str,
|
||||
extra_config: Optional[dict],
|
||||
annotations: Optional[dict],
|
||||
*specs: spack.spec.Spec,
|
||||
):
|
||||
architecture = _archspec_to_gooarch(specs[0])
|
||||
|
||||
dependencies = list(
|
||||
reversed(
|
||||
list(
|
||||
s
|
||||
for s in traverse.traverse_nodes(
|
||||
specs, order="topo", deptype=("link", "run"), root=True
|
||||
elide_list(
|
||||
[
|
||||
f" {_format_spec(spec)}: {e.__class__.__name__}: {e}"
|
||||
for spec, e in failed
|
||||
],
|
||||
5,
|
||||
)
|
||||
if not s.external
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
base_manifest, base_config = base_images[architecture]
|
||||
env = _retrieve_env_dict_from_config(base_config)
|
||||
|
||||
# If the base image uses `vnd.docker.distribution.manifest.v2+json`, then we use that too.
|
||||
# This is because Singularity / Apptainer is very strict about not mixing them.
|
||||
base_manifest_mediaType = base_manifest.get(
|
||||
"mediaType", "application/vnd.oci.image.manifest.v1+json"
|
||||
)
|
||||
use_docker_format = (
|
||||
base_manifest_mediaType == "application/vnd.docker.distribution.manifest.v2+json"
|
||||
)
|
||||
|
||||
spack.user_environment.environment_modifications_for_specs(*specs).apply_modifications(env)
|
||||
|
||||
# Create an oci.image.config file
|
||||
config = copy.deepcopy(base_config)
|
||||
|
||||
# Add the diff ids of the dependencies
|
||||
for s in dependencies:
|
||||
config["rootfs"]["diff_ids"].append(str(checksums[s.dag_hash()].uncompressed_digest))
|
||||
|
||||
# Set the environment variables
|
||||
config["config"]["Env"] = [f"{k}={v}" for k, v in env.items()]
|
||||
|
||||
if extra_config:
|
||||
# From the OCI v1.0 spec:
|
||||
# > Any extra fields in the Image JSON struct are considered implementation
|
||||
# > specific and MUST be ignored by any implementations which are unable to
|
||||
# > interpret them.
|
||||
config.update(extra_config)
|
||||
|
||||
config_file = os.path.join(tmpdir, f"{specs[0].dag_hash()}.config.json")
|
||||
|
||||
with open(config_file, "w") as f:
|
||||
json.dump(config, f, separators=(",", ":"))
|
||||
|
||||
config_file_checksum = Digest.from_sha256(
|
||||
spack.util.crypto.checksum(hashlib.sha256, config_file)
|
||||
)
|
||||
|
||||
# Upload the config file
|
||||
upload_blob_with_retry(image_ref, file=config_file, digest=config_file_checksum)
|
||||
|
||||
manifest = {
|
||||
"mediaType": base_manifest_mediaType,
|
||||
"schemaVersion": 2,
|
||||
"config": {
|
||||
"mediaType": base_manifest["config"]["mediaType"],
|
||||
"digest": str(config_file_checksum),
|
||||
"size": os.path.getsize(config_file),
|
||||
},
|
||||
"layers": [
|
||||
*(layer for layer in base_manifest["layers"]),
|
||||
*(
|
||||
{
|
||||
"mediaType": (
|
||||
"application/vnd.docker.image.rootfs.diff.tar.gzip"
|
||||
if use_docker_format
|
||||
else "application/vnd.oci.image.layer.v1.tar+gzip"
|
||||
),
|
||||
"digest": str(checksums[s.dag_hash()].compressed_digest),
|
||||
"size": checksums[s.dag_hash()].size,
|
||||
}
|
||||
for s in dependencies
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
if not use_docker_format and annotations:
|
||||
manifest["annotations"] = annotations
|
||||
|
||||
# Finally upload the manifest
|
||||
upload_manifest_with_retry(image_ref, manifest=manifest)
|
||||
|
||||
# delete the config file
|
||||
os.unlink(config_file)
|
||||
|
||||
|
||||
def _update_base_images(
|
||||
*,
|
||||
base_image: Optional[ImageReference],
|
||||
target_image: ImageReference,
|
||||
spec: spack.spec.Spec,
|
||||
base_image_cache: Dict[str, Tuple[dict, dict]],
|
||||
):
|
||||
"""For a given spec and base image, copy the missing layers of the base image with matching
|
||||
arch to the registry of the target image. If no base image is specified, create a dummy
|
||||
manifest and config file."""
|
||||
architecture = _archspec_to_gooarch(spec)
|
||||
if architecture in base_image_cache:
|
||||
return
|
||||
if base_image is None:
|
||||
base_image_cache[architecture] = (
|
||||
default_manifest(),
|
||||
default_config(architecture, "linux"),
|
||||
)
|
||||
else:
|
||||
base_image_cache[architecture] = copy_missing_layers_with_retry(
|
||||
base_image, target_image, architecture
|
||||
)
|
||||
|
||||
|
||||
def _push_oci(
|
||||
*,
|
||||
target_image: ImageReference,
|
||||
base_image: Optional[ImageReference],
|
||||
installed_specs_with_deps: List[Spec],
|
||||
tmpdir: str,
|
||||
pool: MaybePool,
|
||||
force: bool = False,
|
||||
) -> Tuple[List[str], Dict[str, Tuple[dict, dict]], Dict[str, spack.oci.oci.Blob]]:
|
||||
"""Push specs to an OCI registry
|
||||
|
||||
Args:
|
||||
image_ref: The target OCI image
|
||||
base_image: Optional base image, which will be copied to the target registry.
|
||||
installed_specs_with_deps: The installed specs to push, excluding externals,
|
||||
including deps, ordered from roots to leaves.
|
||||
force: Whether to overwrite existing layers and manifests in the buildcache.
|
||||
|
||||
Returns:
|
||||
A tuple consisting of the list of skipped specs already in the build cache,
|
||||
a dictionary mapping architectures to base image manifests and configs,
|
||||
and a dictionary mapping each spec's dag hash to a blob.
|
||||
"""
|
||||
|
||||
# Reverse the order
|
||||
installed_specs_with_deps = list(reversed(installed_specs_with_deps))
|
||||
|
||||
# Spec dag hash -> blob
|
||||
checksums: Dict[str, spack.oci.oci.Blob] = {}
|
||||
|
||||
# arch -> (manifest, config)
|
||||
base_images: Dict[str, Tuple[dict, dict]] = {}
|
||||
|
||||
# Specs not uploaded because they already exist
|
||||
skipped = []
|
||||
|
||||
if not force:
|
||||
tty.info("Checking for existing specs in the buildcache")
|
||||
to_be_uploaded = []
|
||||
|
||||
tags_to_check = (target_image.with_tag(default_tag(s)) for s in installed_specs_with_deps)
|
||||
available_blobs = pool.map(_get_spack_binary_blob, tags_to_check)
|
||||
|
||||
for spec, maybe_blob in zip(installed_specs_with_deps, available_blobs):
|
||||
if maybe_blob is not None:
|
||||
checksums[spec.dag_hash()] = maybe_blob
|
||||
skipped.append(_format_spec(spec))
|
||||
else:
|
||||
to_be_uploaded.append(spec)
|
||||
else:
|
||||
to_be_uploaded = installed_specs_with_deps
|
||||
|
||||
if not to_be_uploaded:
|
||||
return skipped, base_images, checksums
|
||||
|
||||
tty.info(
|
||||
f"{len(to_be_uploaded)} specs need to be pushed to "
|
||||
f"{target_image.domain}/{target_image.name}"
|
||||
)
|
||||
|
||||
# Upload blobs
|
||||
new_blobs = pool.starmap(
|
||||
_push_single_spack_binary_blob, ((target_image, spec, tmpdir) for spec in to_be_uploaded)
|
||||
)
|
||||
|
||||
# And update the spec to blob mapping
|
||||
for spec, blob in zip(to_be_uploaded, new_blobs):
|
||||
checksums[spec.dag_hash()] = blob
|
||||
|
||||
# Copy base images if necessary
|
||||
for spec in to_be_uploaded:
|
||||
_update_base_images(
|
||||
base_image=base_image,
|
||||
target_image=target_image,
|
||||
spec=spec,
|
||||
base_image_cache=base_images,
|
||||
)
|
||||
|
||||
def extra_config(spec: Spec):
|
||||
spec_dict = spec.to_dict(hash=ht.dag_hash)
|
||||
spec_dict["buildcache_layout_version"] = 1
|
||||
spec_dict["binary_cache_checksum"] = {
|
||||
"hash_algorithm": "sha256",
|
||||
"hash": checksums[spec.dag_hash()].compressed_digest.digest,
|
||||
}
|
||||
return spec_dict
|
||||
|
||||
# Upload manifests
|
||||
tty.info("Uploading manifests")
|
||||
pool.starmap(
|
||||
_put_manifest,
|
||||
(
|
||||
(
|
||||
base_images,
|
||||
checksums,
|
||||
target_image.with_tag(default_tag(spec)),
|
||||
tmpdir,
|
||||
extra_config(spec),
|
||||
{"org.opencontainers.image.description": spec.format()},
|
||||
spec,
|
||||
)
|
||||
for spec in to_be_uploaded
|
||||
),
|
||||
)
|
||||
|
||||
# Print the image names of the top-level specs
|
||||
for spec in to_be_uploaded:
|
||||
tty.info(f"Pushed {_format_spec(spec)} to {target_image.with_tag(default_tag(spec))}")
|
||||
|
||||
return skipped, base_images, checksums
|
||||
|
||||
|
||||
def _config_from_tag(image_ref: ImageReference, tag: str) -> Optional[dict]:
|
||||
# Don't allow recursion here, since Spack itself always uploads
|
||||
# vnd.oci.image.manifest.v1+json, not vnd.oci.image.index.v1+json
|
||||
_, config = get_manifest_and_config_with_retry(image_ref.with_tag(tag), tag, recurse=0)
|
||||
|
||||
# Do very basic validation: if "spec" is a key in the config, it
|
||||
# must be a Spec object too.
|
||||
return config if "spec" in config else None
|
||||
|
||||
|
||||
def _update_index_oci(image_ref: ImageReference, tmpdir: str, pool: MaybePool) -> None:
|
||||
tags = list_tags(image_ref)
|
||||
|
||||
# Fetch all image config files in parallel
|
||||
spec_dicts = pool.starmap(
|
||||
_config_from_tag, ((image_ref, tag) for tag in tags if tag_is_spec(tag))
|
||||
)
|
||||
|
||||
# Populate the database
|
||||
db_root_dir = os.path.join(tmpdir, "db_root")
|
||||
db = bindist.BuildCacheDatabase(db_root_dir)
|
||||
|
||||
for spec_dict in spec_dicts:
|
||||
spec = Spec.from_dict(spec_dict)
|
||||
db.add(spec, directory_layout=None)
|
||||
db.mark(spec, "in_buildcache", True)
|
||||
|
||||
# Create the index.json file
|
||||
index_json_path = os.path.join(tmpdir, "index.json")
|
||||
with open(index_json_path, "w") as f:
|
||||
db._write_to_file(f)
|
||||
|
||||
# Create an empty config.json file
|
||||
empty_config_json_path = os.path.join(tmpdir, "config.json")
|
||||
with open(empty_config_json_path, "wb") as f:
|
||||
f.write(b"{}")
|
||||
|
||||
# Upload the index.json file
|
||||
index_shasum = Digest.from_sha256(spack.util.crypto.checksum(hashlib.sha256, index_json_path))
|
||||
upload_blob_with_retry(image_ref, file=index_json_path, digest=index_shasum)
|
||||
|
||||
# Upload the config.json file
|
||||
empty_config_digest = Digest.from_sha256(
|
||||
spack.util.crypto.checksum(hashlib.sha256, empty_config_json_path)
|
||||
)
|
||||
upload_blob_with_retry(image_ref, file=empty_config_json_path, digest=empty_config_digest)
|
||||
|
||||
# Push a manifest file that references the index.json file as a layer
|
||||
# Notice that we push this as if it is an image, which it of course is not.
|
||||
# When the ORAS spec becomes official, we can use that instead of a fake image.
|
||||
# For now we just use the OCI image spec, so that we don't run into issues with
|
||||
# automatic garbage collection of blobs that are not referenced by any image manifest.
|
||||
oci_manifest = {
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"schemaVersion": 2,
|
||||
# Config is just an empty {} file for now, and irrelevant
|
||||
"config": {
|
||||
"mediaType": "application/vnd.oci.image.config.v1+json",
|
||||
"digest": str(empty_config_digest),
|
||||
"size": os.path.getsize(empty_config_json_path),
|
||||
},
|
||||
# The buildcache index is the only layer, and is not a tarball, we lie here.
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"digest": str(index_shasum),
|
||||
"size": os.path.getsize(index_json_path),
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
upload_manifest_with_retry(image_ref.with_tag(default_index_tag), oci_manifest)
|
||||
|
||||
|
||||
def install_fn(args):
|
||||
@@ -1182,14 +760,15 @@ def update_index(mirror: spack.mirror.Mirror, update_keys=False):
|
||||
if image_ref:
|
||||
with tempfile.TemporaryDirectory(
|
||||
dir=spack.stage.get_stage_root()
|
||||
) as tmpdir, _make_pool() as pool:
|
||||
_update_index_oci(image_ref, tmpdir, pool)
|
||||
) as tmpdir, spack.util.parallel.make_concurrent_executor() as executor:
|
||||
bindist._oci_update_index(image_ref, tmpdir, executor)
|
||||
return
|
||||
|
||||
# Otherwise, assume a normal mirror.
|
||||
url = mirror.push_url
|
||||
|
||||
bindist.generate_package_index(url_util.join(url, bindist.build_cache_relative_path()))
|
||||
with tempfile.TemporaryDirectory(dir=spack.stage.get_stage_root()) as tmpdir:
|
||||
bindist._url_generate_package_index(url, tmpdir)
|
||||
|
||||
if update_keys:
|
||||
keys_url = url_util.join(
|
||||
@@ -1197,7 +776,8 @@ def update_index(mirror: spack.mirror.Mirror, update_keys=False):
|
||||
)
|
||||
|
||||
try:
|
||||
bindist.generate_key_index(keys_url)
|
||||
with tempfile.TemporaryDirectory(dir=spack.stage.get_stage_root()) as tmpdir:
|
||||
bindist.generate_key_index(keys_url, tmpdir)
|
||||
except bindist.CannotListKeys as e:
|
||||
# Do not error out if listing keys went wrong. This usually means that the _gpg path
|
||||
# does not exist. TODO: distinguish between this and other errors.
|
||||
|
||||
@@ -50,6 +50,7 @@ def setup_parser(subparser):
|
||||
default=lambda: spack.config.default_modify_scope("compilers"),
|
||||
help="configuration scope to modify",
|
||||
)
|
||||
arguments.add_common_arguments(find_parser, ["jobs"])
|
||||
|
||||
# Remove
|
||||
remove_parser = sp.add_parser("remove", aliases=["rm"], help="remove compiler by spec")
|
||||
@@ -78,25 +79,21 @@ def setup_parser(subparser):
|
||||
def compiler_find(args):
|
||||
"""Search either $PATH or a list of paths OR MODULES for compilers and
|
||||
add them to Spack's configuration.
|
||||
|
||||
"""
|
||||
# None signals spack.compiler.find_compilers to use its default logic
|
||||
paths = args.add_paths or None
|
||||
|
||||
# Below scope=None because we want new compilers that don't appear
|
||||
# in any other configuration.
|
||||
new_compilers = spack.compilers.find_new_compilers(
|
||||
paths, scope=None, mixed_toolchain=args.mixed_toolchain
|
||||
new_compilers = spack.compilers.find_compilers(
|
||||
path_hints=paths,
|
||||
scope=args.scope,
|
||||
mixed_toolchain=args.mixed_toolchain,
|
||||
max_workers=args.jobs,
|
||||
)
|
||||
if new_compilers:
|
||||
spack.compilers.add_compilers_to_config(new_compilers, scope=args.scope)
|
||||
n = len(new_compilers)
|
||||
s = "s" if n > 1 else ""
|
||||
|
||||
config = spack.config.CONFIG
|
||||
filename = config.get_config_filename(args.scope, "compilers")
|
||||
tty.msg("Added %d new compiler%s to %s" % (n, s, filename))
|
||||
colify(reversed(sorted(c.spec.display_str for c in new_compilers)), indent=4)
|
||||
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"{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:")
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import re
|
||||
import sys
|
||||
import urllib.parse
|
||||
from typing import List
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import mkdirp
|
||||
@@ -14,9 +15,15 @@
|
||||
import spack.stage
|
||||
import spack.util.web
|
||||
from spack.spec import Spec
|
||||
from spack.url import UndetectableNameError, UndetectableVersionError, parse_name, parse_version
|
||||
from spack.url import (
|
||||
UndetectableNameError,
|
||||
UndetectableVersionError,
|
||||
find_versions_of_archive,
|
||||
parse_name,
|
||||
parse_version,
|
||||
)
|
||||
from spack.util.editor import editor
|
||||
from spack.util.executable import ProcessError, which
|
||||
from spack.util.executable import which
|
||||
from spack.util.format import get_version_lines
|
||||
from spack.util.naming import mod_to_class, simplify_name, valid_fully_qualified_module_name
|
||||
|
||||
@@ -89,14 +96,20 @@ class BundlePackageTemplate:
|
||||
url_def = " # There is no URL since there is no code to download."
|
||||
body_def = " # There is no need for install() since there is no code."
|
||||
|
||||
def __init__(self, name, versions):
|
||||
def __init__(self, name: str, versions, languages: List[str]):
|
||||
self.name = name
|
||||
self.class_name = mod_to_class(name)
|
||||
self.versions = versions
|
||||
self.languages = languages
|
||||
|
||||
def write(self, pkg_path):
|
||||
"""Writes the new package file."""
|
||||
|
||||
all_deps = [f' depends_on("{lang}", type="build")' for lang in self.languages]
|
||||
if all_deps and self.dependencies:
|
||||
all_deps.append("")
|
||||
all_deps.append(self.dependencies)
|
||||
|
||||
# Write out a template for the file
|
||||
with open(pkg_path, "w") as pkg_file:
|
||||
pkg_file.write(
|
||||
@@ -106,7 +119,7 @@ def write(self, pkg_path):
|
||||
base_class_name=self.base_class_name,
|
||||
url_def=self.url_def,
|
||||
versions=self.versions,
|
||||
dependencies=self.dependencies,
|
||||
dependencies="\n".join(all_deps),
|
||||
body_def=self.body_def,
|
||||
)
|
||||
)
|
||||
@@ -125,8 +138,8 @@ def install(self, spec, prefix):
|
||||
|
||||
url_line = ' url = "{url}"'
|
||||
|
||||
def __init__(self, name, url, versions):
|
||||
super().__init__(name, versions)
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
super().__init__(name, versions, languages)
|
||||
|
||||
self.url_def = self.url_line.format(url=url)
|
||||
|
||||
@@ -214,13 +227,13 @@ def luarocks_args(self):
|
||||
args = []
|
||||
return args"""
|
||||
|
||||
def __init__(self, name, url, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name lua-lpeg`, don't rename it lua-lua-lpeg
|
||||
if not name.startswith("lua-"):
|
||||
# Make it more obvious that we are renaming the package
|
||||
tty.msg("Changing package name from {0} to lua-{0}".format(name))
|
||||
name = "lua-{0}".format(name)
|
||||
super().__init__(name, url, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
class MesonPackageTemplate(PackageTemplate):
|
||||
@@ -321,14 +334,14 @@ class RacketPackageTemplate(PackageTemplate):
|
||||
# subdirectory = None
|
||||
"""
|
||||
|
||||
def __init__(self, name, url, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name rkt-scribble`, don't rename it rkt-rkt-scribble
|
||||
if not name.startswith("rkt-"):
|
||||
# Make it more obvious that we are renaming the package
|
||||
tty.msg("Changing package name from {0} to rkt-{0}".format(name))
|
||||
name = "rkt-{0}".format(name)
|
||||
self.body_def = self.body_def.format(name[4:])
|
||||
super().__init__(name, url, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
class PythonPackageTemplate(PackageTemplate):
|
||||
@@ -361,7 +374,7 @@ def config_settings(self, spec, prefix):
|
||||
settings = {}
|
||||
return settings"""
|
||||
|
||||
def __init__(self, name, url, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name py-numpy`, don't rename it py-py-numpy
|
||||
if not name.startswith("py-"):
|
||||
# Make it more obvious that we are renaming the package
|
||||
@@ -415,7 +428,7 @@ def __init__(self, name, url, *args, **kwargs):
|
||||
+ self.url_line
|
||||
)
|
||||
|
||||
super().__init__(name, url, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
class RPackageTemplate(PackageTemplate):
|
||||
@@ -434,7 +447,7 @@ def configure_args(self):
|
||||
args = []
|
||||
return args"""
|
||||
|
||||
def __init__(self, name, url, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name r-rcpp`, don't rename it r-r-rcpp
|
||||
if not name.startswith("r-"):
|
||||
# Make it more obvious that we are renaming the package
|
||||
@@ -454,7 +467,7 @@ def __init__(self, name, url, *args, **kwargs):
|
||||
if bioc:
|
||||
self.url_line = ' url = "{0}"\n' ' bioc = "{1}"'.format(url, r_name)
|
||||
|
||||
super().__init__(name, url, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
class PerlmakePackageTemplate(PackageTemplate):
|
||||
@@ -474,14 +487,14 @@ def configure_args(self):
|
||||
args = []
|
||||
return args"""
|
||||
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name perl-cpp`, don't rename it perl-perl-cpp
|
||||
if not name.startswith("perl-"):
|
||||
# Make it more obvious that we are renaming the package
|
||||
tty.msg("Changing package name from {0} to perl-{0}".format(name))
|
||||
name = "perl-{0}".format(name)
|
||||
|
||||
super().__init__(name, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
class PerlbuildPackageTemplate(PerlmakePackageTemplate):
|
||||
@@ -506,7 +519,7 @@ class OctavePackageTemplate(PackageTemplate):
|
||||
# FIXME: Add additional dependencies if required.
|
||||
# depends_on("octave-foo", type=("build", "run"))"""
|
||||
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name octave-splines`, don't rename it
|
||||
# octave-octave-splines
|
||||
if not name.startswith("octave-"):
|
||||
@@ -514,7 +527,7 @@ def __init__(self, name, *args, **kwargs):
|
||||
tty.msg("Changing package name from {0} to octave-{0}".format(name))
|
||||
name = "octave-{0}".format(name)
|
||||
|
||||
super().__init__(name, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
class RubyPackageTemplate(PackageTemplate):
|
||||
@@ -534,7 +547,7 @@ def build(self, spec, prefix):
|
||||
# FIXME: If not needed delete this function
|
||||
pass"""
|
||||
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name ruby-numpy`, don't rename it
|
||||
# ruby-ruby-numpy
|
||||
if not name.startswith("ruby-"):
|
||||
@@ -542,7 +555,7 @@ def __init__(self, name, *args, **kwargs):
|
||||
tty.msg("Changing package name from {0} to ruby-{0}".format(name))
|
||||
name = "ruby-{0}".format(name)
|
||||
|
||||
super().__init__(name, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
class MakefilePackageTemplate(PackageTemplate):
|
||||
@@ -580,14 +593,14 @@ def configure_args(self, spec, prefix):
|
||||
args = []
|
||||
return args"""
|
||||
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
def __init__(self, name, url, versions, languages: List[str]):
|
||||
# If the user provided `--name py-pyqt4`, don't rename it py-py-pyqt4
|
||||
if not name.startswith("py-"):
|
||||
# Make it more obvious that we are renaming the package
|
||||
tty.msg("Changing package name from {0} to py-{0}".format(name))
|
||||
name = "py-{0}".format(name)
|
||||
|
||||
super().__init__(name, *args, **kwargs)
|
||||
super().__init__(name, url, versions, languages)
|
||||
|
||||
|
||||
templates = {
|
||||
@@ -658,8 +671,48 @@ def setup_parser(subparser):
|
||||
)
|
||||
|
||||
|
||||
class BuildSystemGuesser:
|
||||
"""An instance of BuildSystemGuesser provides a callable object to be used
|
||||
#: C file extensions
|
||||
C_EXT = {".c"}
|
||||
|
||||
#: C++ file extensions
|
||||
CXX_EXT = {
|
||||
".C",
|
||||
".c++",
|
||||
".cc",
|
||||
".ccm",
|
||||
".cpp",
|
||||
".CPP",
|
||||
".cxx",
|
||||
".h++",
|
||||
".hh",
|
||||
".hpp",
|
||||
".hxx",
|
||||
".inl",
|
||||
".ipp",
|
||||
".ixx",
|
||||
".tcc",
|
||||
".tpp",
|
||||
}
|
||||
|
||||
#: Fortran file extensions
|
||||
FORTRAN_EXT = {
|
||||
".f77",
|
||||
".F77",
|
||||
".f90",
|
||||
".F90",
|
||||
".f95",
|
||||
".F95",
|
||||
".f",
|
||||
".F",
|
||||
".for",
|
||||
".FOR",
|
||||
".ftn",
|
||||
".FTN",
|
||||
}
|
||||
|
||||
|
||||
class BuildSystemAndLanguageGuesser:
|
||||
"""An instance of BuildSystemAndLanguageGuesser provides a callable object to be used
|
||||
during ``spack create``. By passing this object to ``spack checksum``, we
|
||||
can take a peek at the fetched tarball and discern the build system it uses
|
||||
"""
|
||||
@@ -667,81 +720,119 @@ class BuildSystemGuesser:
|
||||
def __init__(self):
|
||||
"""Sets the default build system."""
|
||||
self.build_system = "generic"
|
||||
self._c = False
|
||||
self._cxx = False
|
||||
self._fortran = False
|
||||
|
||||
def __call__(self, stage, url):
|
||||
# List of files in the archive ordered by their depth in the directory tree.
|
||||
self._file_entries: List[str] = []
|
||||
|
||||
def __call__(self, archive: str, url: str) -> None:
|
||||
"""Try to guess the type of build system used by a project based on
|
||||
the contents of its archive or the URL it was downloaded from."""
|
||||
|
||||
if url is not None:
|
||||
# Most octave extensions are hosted on Octave-Forge:
|
||||
# https://octave.sourceforge.net/index.html
|
||||
# They all have the same base URL.
|
||||
if "downloads.sourceforge.net/octave/" in url:
|
||||
self.build_system = "octave"
|
||||
return
|
||||
if url.endswith(".gem"):
|
||||
self.build_system = "ruby"
|
||||
return
|
||||
if url.endswith(".whl") or ".whl#" in url:
|
||||
self.build_system = "python"
|
||||
return
|
||||
if url.endswith(".rock"):
|
||||
self.build_system = "lua"
|
||||
return
|
||||
|
||||
# A list of clues that give us an idea of the build system a package
|
||||
# uses. If the regular expression matches a file contained in the
|
||||
# archive, the corresponding build system is assumed.
|
||||
# NOTE: Order is important here. If a package supports multiple
|
||||
# build systems, we choose the first match in this list.
|
||||
clues = [
|
||||
(r"/CMakeLists\.txt$", "cmake"),
|
||||
(r"/NAMESPACE$", "r"),
|
||||
(r"/Cargo\.toml$", "cargo"),
|
||||
(r"/go\.mod$", "go"),
|
||||
(r"/configure$", "autotools"),
|
||||
(r"/configure\.(in|ac)$", "autoreconf"),
|
||||
(r"/Makefile\.am$", "autoreconf"),
|
||||
(r"/pom\.xml$", "maven"),
|
||||
(r"/SConstruct$", "scons"),
|
||||
(r"/waf$", "waf"),
|
||||
(r"/pyproject.toml", "python"),
|
||||
(r"/setup\.(py|cfg)$", "python"),
|
||||
(r"/WORKSPACE$", "bazel"),
|
||||
(r"/Build\.PL$", "perlbuild"),
|
||||
(r"/Makefile\.PL$", "perlmake"),
|
||||
(r"/.*\.gemspec$", "ruby"),
|
||||
(r"/Rakefile$", "ruby"),
|
||||
(r"/setup\.rb$", "ruby"),
|
||||
(r"/.*\.pro$", "qmake"),
|
||||
(r"/.*\.rockspec$", "lua"),
|
||||
(r"/(GNU)?[Mm]akefile$", "makefile"),
|
||||
(r"/DESCRIPTION$", "octave"),
|
||||
(r"/meson\.build$", "meson"),
|
||||
(r"/configure\.py$", "sip"),
|
||||
]
|
||||
|
||||
# Peek inside the compressed file.
|
||||
if stage.archive_file.endswith(".zip") or ".zip#" in stage.archive_file:
|
||||
if archive.endswith(".zip") or ".zip#" in archive:
|
||||
try:
|
||||
unzip = which("unzip")
|
||||
output = unzip("-lq", stage.archive_file, output=str)
|
||||
except ProcessError:
|
||||
assert unzip is not None
|
||||
output = unzip("-lq", archive, output=str)
|
||||
except Exception:
|
||||
output = ""
|
||||
else:
|
||||
try:
|
||||
tar = which("tar")
|
||||
output = tar("--exclude=*/*/*", "-tf", stage.archive_file, output=str)
|
||||
except ProcessError:
|
||||
assert tar is not None
|
||||
output = tar("tf", archive, output=str)
|
||||
except Exception:
|
||||
output = ""
|
||||
lines = output.splitlines()
|
||||
self._file_entries[:] = output.splitlines()
|
||||
|
||||
# Determine the build system based on the files contained
|
||||
# in the archive.
|
||||
for pattern, bs in clues:
|
||||
if any(re.search(pattern, line) for line in lines):
|
||||
self.build_system = bs
|
||||
break
|
||||
# Files closest to the root should be considered first when determining build system.
|
||||
self._file_entries.sort(key=lambda p: p.count("/"))
|
||||
|
||||
self._determine_build_system(url)
|
||||
self._determine_language()
|
||||
|
||||
def _determine_build_system(self, url: str) -> None:
|
||||
# Most octave extensions are hosted on Octave-Forge:
|
||||
# https://octave.sourceforge.net/index.html
|
||||
# They all have the same base URL.
|
||||
if "downloads.sourceforge.net/octave/" in url:
|
||||
self.build_system = "octave"
|
||||
elif url.endswith(".gem"):
|
||||
self.build_system = "ruby"
|
||||
elif url.endswith(".whl") or ".whl#" in url:
|
||||
self.build_system = "python"
|
||||
elif url.endswith(".rock"):
|
||||
self.build_system = "lua"
|
||||
elif self._file_entries:
|
||||
# A list of clues that give us an idea of the build system a package
|
||||
# uses. If the regular expression matches a file contained in the
|
||||
# archive, the corresponding build system is assumed.
|
||||
# NOTE: Order is important here. If a package supports multiple
|
||||
# build systems, we choose the first match in this list.
|
||||
clues = [
|
||||
(re.compile(pattern), build_system)
|
||||
for pattern, build_system in (
|
||||
(r"/CMakeLists\.txt$", "cmake"),
|
||||
(r"/NAMESPACE$", "r"),
|
||||
(r"/Cargo\.toml$", "cargo"),
|
||||
(r"/go\.mod$", "go"),
|
||||
(r"/configure$", "autotools"),
|
||||
(r"/configure\.(in|ac)$", "autoreconf"),
|
||||
(r"/Makefile\.am$", "autoreconf"),
|
||||
(r"/pom\.xml$", "maven"),
|
||||
(r"/SConstruct$", "scons"),
|
||||
(r"/waf$", "waf"),
|
||||
(r"/pyproject.toml", "python"),
|
||||
(r"/setup\.(py|cfg)$", "python"),
|
||||
(r"/WORKSPACE$", "bazel"),
|
||||
(r"/Build\.PL$", "perlbuild"),
|
||||
(r"/Makefile\.PL$", "perlmake"),
|
||||
(r"/.*\.gemspec$", "ruby"),
|
||||
(r"/Rakefile$", "ruby"),
|
||||
(r"/setup\.rb$", "ruby"),
|
||||
(r"/.*\.pro$", "qmake"),
|
||||
(r"/.*\.rockspec$", "lua"),
|
||||
(r"/(GNU)?[Mm]akefile$", "makefile"),
|
||||
(r"/DESCRIPTION$", "octave"),
|
||||
(r"/meson\.build$", "meson"),
|
||||
(r"/configure\.py$", "sip"),
|
||||
)
|
||||
]
|
||||
|
||||
# Determine the build system based on the files contained in the archive.
|
||||
for file in self._file_entries:
|
||||
for pattern, build_system in clues:
|
||||
if pattern.search(file):
|
||||
self.build_system = build_system
|
||||
return
|
||||
|
||||
def _determine_language(self):
|
||||
for entry in self._file_entries:
|
||||
_, ext = os.path.splitext(entry)
|
||||
|
||||
if not self._c and ext in C_EXT:
|
||||
self._c = True
|
||||
elif not self._cxx and ext in CXX_EXT:
|
||||
self._cxx = True
|
||||
elif not self._fortran and ext in FORTRAN_EXT:
|
||||
self._fortran = True
|
||||
|
||||
if self._c and self._cxx and self._fortran:
|
||||
return
|
||||
|
||||
@property
|
||||
def languages(self) -> List[str]:
|
||||
langs: List[str] = []
|
||||
if self._c:
|
||||
langs.append("c")
|
||||
if self._cxx:
|
||||
langs.append("cxx")
|
||||
if self._fortran:
|
||||
langs.append("fortran")
|
||||
return langs
|
||||
|
||||
|
||||
def get_name(name, url):
|
||||
@@ -811,7 +902,7 @@ def get_url(url):
|
||||
def get_versions(args, name):
|
||||
"""Returns a list of versions and hashes for a package.
|
||||
|
||||
Also returns a BuildSystemGuesser object.
|
||||
Also returns a BuildSystemAndLanguageGuesser object.
|
||||
|
||||
Returns default values if no URL is provided.
|
||||
|
||||
@@ -820,7 +911,7 @@ def get_versions(args, name):
|
||||
name (str): The name of the package
|
||||
|
||||
Returns:
|
||||
tuple: versions and hashes, and a BuildSystemGuesser object
|
||||
tuple: versions and hashes, and a BuildSystemAndLanguageGuesser object
|
||||
"""
|
||||
|
||||
# Default version with hash
|
||||
@@ -834,7 +925,7 @@ def get_versions(args, name):
|
||||
# version("1.2.4")"""
|
||||
|
||||
# Default guesser
|
||||
guesser = BuildSystemGuesser()
|
||||
guesser = BuildSystemAndLanguageGuesser()
|
||||
|
||||
valid_url = True
|
||||
try:
|
||||
@@ -847,7 +938,7 @@ def get_versions(args, name):
|
||||
if args.url is not None and args.template != "bundle" and valid_url:
|
||||
# Find available versions
|
||||
try:
|
||||
url_dict = spack.url.find_versions_of_archive(args.url)
|
||||
url_dict = find_versions_of_archive(args.url)
|
||||
if len(url_dict) > 1 and not args.batch and sys.stdin.isatty():
|
||||
url_dict_filtered = spack.stage.interactive_version_filter(url_dict)
|
||||
if url_dict_filtered is None:
|
||||
@@ -874,7 +965,7 @@ def get_versions(args, name):
|
||||
return versions, guesser
|
||||
|
||||
|
||||
def get_build_system(template, url, guesser):
|
||||
def get_build_system(template: str, url: str, guesser: BuildSystemAndLanguageGuesser) -> str:
|
||||
"""Determine the build system template.
|
||||
|
||||
If a template is specified, always use that. Otherwise, if a URL
|
||||
@@ -882,11 +973,10 @@ def get_build_system(template, url, guesser):
|
||||
build system it uses. Otherwise, use a generic template by default.
|
||||
|
||||
Args:
|
||||
template (str): ``--template`` argument given to ``spack create``
|
||||
url (str): ``url`` argument given to ``spack create``
|
||||
args (argparse.Namespace): The arguments given to ``spack create``
|
||||
guesser (BuildSystemGuesser): The first_stage_function given to
|
||||
``spack checksum`` which records the build system it detects
|
||||
template: ``--template`` argument given to ``spack create``
|
||||
url: ``url`` argument given to ``spack create``
|
||||
guesser: The first_stage_function given to ``spack checksum`` which records the build
|
||||
system it detects
|
||||
|
||||
Returns:
|
||||
str: The name of the build system template to use
|
||||
@@ -960,7 +1050,7 @@ def create(parser, args):
|
||||
build_system = get_build_system(args.template, url, guesser)
|
||||
|
||||
# Create the package template object
|
||||
constr_args = {"name": name, "versions": versions}
|
||||
constr_args = {"name": name, "versions": versions, "languages": guesser.languages}
|
||||
package_class = templates[build_system]
|
||||
if package_class != BundlePackageTemplate:
|
||||
constr_args["url"] = url
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from glob import glob
|
||||
|
||||
@@ -62,9 +63,10 @@ def create_db_tarball(args):
|
||||
|
||||
base = os.path.basename(str(spack.store.STORE.root))
|
||||
transform_args = []
|
||||
# Currently --transform and -s are not supported by Windows native tar
|
||||
if "GNU" in tar("--version", output=str):
|
||||
transform_args = ["--transform", "s/^%s/%s/" % (base, tarball_name)]
|
||||
else:
|
||||
elif sys.platform != "win32":
|
||||
transform_args = ["-s", "/^%s/%s/" % (base, tarball_name)]
|
||||
|
||||
wd = os.path.dirname(str(spack.store.STORE.root))
|
||||
@@ -90,7 +92,6 @@ def report(args):
|
||||
print("* **Spack:**", get_version())
|
||||
print("* **Python:**", platform.python_version())
|
||||
print("* **Platform:**", architecture)
|
||||
print("* **Concretizer:**", spack.config.get("config:concretizer"))
|
||||
|
||||
|
||||
def debug(parser, args):
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
import spack.cmd
|
||||
import spack.config
|
||||
import spack.fetch_strategy
|
||||
import spack.package_base
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
import spack.stage
|
||||
import spack.util.path
|
||||
import spack.version
|
||||
from spack.cmd.common import arguments
|
||||
@@ -62,7 +64,7 @@ def change_fn(section):
|
||||
spack.config.change_or_add("develop", find_fn, change_fn)
|
||||
|
||||
|
||||
def _retrieve_develop_source(spec, abspath):
|
||||
def _retrieve_develop_source(spec: spack.spec.Spec, abspath: str) -> None:
|
||||
# "steal" the source code via staging API. We ask for a stage
|
||||
# to be created, then copy it afterwards somewhere else. It would be
|
||||
# better if we can create the `source_path` directly into its final
|
||||
@@ -71,13 +73,13 @@ def _retrieve_develop_source(spec, abspath):
|
||||
# We construct a package class ourselves, rather than asking for
|
||||
# Spec.package, since Spec only allows this when it is concrete
|
||||
package = pkg_cls(spec)
|
||||
source_stage = package.stage[0]
|
||||
source_stage: spack.stage.Stage = package.stage[0]
|
||||
if isinstance(source_stage.fetcher, spack.fetch_strategy.GitFetchStrategy):
|
||||
source_stage.fetcher.get_full_repo = True
|
||||
# If we retrieved this version before and cached it, we may have
|
||||
# done so without cloning the full git repo; likewise, any
|
||||
# mirror might store an instance with truncated history.
|
||||
source_stage.disable_mirrors()
|
||||
source_stage.default_fetcher_only = True
|
||||
|
||||
source_stage.fetcher.set_package(package)
|
||||
package.stage.steal_source(abspath)
|
||||
|
||||
@@ -468,32 +468,30 @@ def env_remove(args):
|
||||
This removes an environment managed by Spack. Directory environments
|
||||
and manifests embedded in repositories should be removed manually.
|
||||
"""
|
||||
read_envs = []
|
||||
remove_envs = []
|
||||
valid_envs = []
|
||||
bad_envs = []
|
||||
invalid_envs = []
|
||||
|
||||
for env_name in ev.all_environment_names():
|
||||
try:
|
||||
env = ev.read(env_name)
|
||||
valid_envs.append(env_name)
|
||||
valid_envs.append(env)
|
||||
|
||||
if env_name in args.rm_env:
|
||||
read_envs.append(env)
|
||||
remove_envs.append(env)
|
||||
except (spack.config.ConfigFormatError, ev.SpackEnvironmentConfigError):
|
||||
invalid_envs.append(env_name)
|
||||
|
||||
if env_name in args.rm_env:
|
||||
bad_envs.append(env_name)
|
||||
|
||||
# Check if env is linked to another before trying to remove
|
||||
for name in valid_envs:
|
||||
# Check if remove_env is included from another env before trying to remove
|
||||
for env in valid_envs:
|
||||
for remove_env in remove_envs:
|
||||
# don't check if environment is included to itself
|
||||
if name == env_name:
|
||||
if env.name == remove_env.name:
|
||||
continue
|
||||
environ = ev.Environment(ev.root(name))
|
||||
if ev.root(env_name) in environ.included_concrete_envs:
|
||||
msg = f'Environment "{env_name}" is being used by environment "{name}"'
|
||||
|
||||
if remove_env.path in env.included_concrete_envs:
|
||||
msg = f'Environment "{remove_env.name}" is being used by environment "{env.name}"'
|
||||
if args.force:
|
||||
tty.warn(msg)
|
||||
else:
|
||||
@@ -506,7 +504,7 @@ def env_remove(args):
|
||||
if not answer:
|
||||
tty.die("Will not remove any environments")
|
||||
|
||||
for env in read_envs:
|
||||
for env in remove_envs:
|
||||
name = env.name
|
||||
if env.active:
|
||||
tty.die(f"Environment {name} can't be removed while activated.")
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import spack.binary_distribution
|
||||
import spack.mirror
|
||||
import spack.paths
|
||||
import spack.stage
|
||||
import spack.util.gpg
|
||||
import spack.util.url
|
||||
from spack.cmd.common import arguments
|
||||
@@ -115,6 +117,7 @@ def setup_parser(subparser):
|
||||
help="URL of the mirror where keys will be published",
|
||||
)
|
||||
publish.add_argument(
|
||||
"--update-index",
|
||||
"--rebuild-index",
|
||||
action="store_true",
|
||||
default=False,
|
||||
@@ -220,9 +223,10 @@ def gpg_publish(args):
|
||||
elif args.mirror_url:
|
||||
mirror = spack.mirror.Mirror(args.mirror_url, args.mirror_url)
|
||||
|
||||
spack.binary_distribution.push_keys(
|
||||
mirror, keys=args.keys, regenerate_index=args.rebuild_index
|
||||
)
|
||||
with tempfile.TemporaryDirectory(dir=spack.stage.get_stage_root()) as tmpdir:
|
||||
spack.binary_distribution._url_push_keys(
|
||||
mirror, keys=args.keys, tmpdir=tmpdir, update_index=args.update_index
|
||||
)
|
||||
|
||||
|
||||
def gpg(parser, args):
|
||||
|
||||
@@ -502,7 +502,7 @@ def print_licenses(pkg, args):
|
||||
|
||||
def info(parser, args):
|
||||
spec = spack.spec.Spec(args.package)
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(spec.name)
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(spec.fullname)
|
||||
pkg = pkg_cls(spec)
|
||||
|
||||
# Output core package information
|
||||
|
||||
@@ -23,11 +23,6 @@ def setup_parser(subparser):
|
||||
output.add_argument(
|
||||
"-s", "--safe", action="store_true", help="only list safe versions of the package"
|
||||
)
|
||||
output.add_argument(
|
||||
"--safe-only",
|
||||
action="store_true",
|
||||
help="[deprecated] only list safe versions of the package",
|
||||
)
|
||||
output.add_argument(
|
||||
"-r", "--remote", action="store_true", help="only list remote versions of the package"
|
||||
)
|
||||
@@ -47,17 +42,13 @@ def versions(parser, args):
|
||||
|
||||
safe_versions = pkg.versions
|
||||
|
||||
if args.safe_only:
|
||||
tty.warn('"--safe-only" is deprecated. Use "--safe" instead.')
|
||||
args.safe = args.safe_only
|
||||
|
||||
if not (args.remote or args.new):
|
||||
if sys.stdout.isatty():
|
||||
tty.msg("Safe versions (already checksummed):")
|
||||
|
||||
if not safe_versions:
|
||||
if sys.stdout.isatty():
|
||||
tty.warn("Found no versions for {0}".format(pkg.name))
|
||||
tty.warn(f"Found no versions for {pkg.name}")
|
||||
tty.debug("Manually add versions to the package.")
|
||||
else:
|
||||
colify(sorted(safe_versions, reverse=True), indent=2)
|
||||
@@ -83,12 +74,12 @@ def versions(parser, args):
|
||||
if not remote_versions:
|
||||
if sys.stdout.isatty():
|
||||
if not fetched_versions:
|
||||
tty.warn("Found no versions for {0}".format(pkg.name))
|
||||
tty.warn(f"Found no versions for {pkg.name}")
|
||||
tty.debug(
|
||||
"Check the list_url and list_depth attributes of "
|
||||
"the package to help Spack find versions."
|
||||
)
|
||||
else:
|
||||
tty.warn("Found no unchecksummed versions for {0}".format(pkg.name))
|
||||
tty.warn(f"Found no unchecksummed versions for {pkg.name}")
|
||||
else:
|
||||
colify(sorted(remote_versions, reverse=True), indent=2)
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
|
||||
__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=()):
|
||||
@@ -700,6 +703,30 @@ def compiler_environment(self):
|
||||
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):
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
"""This module contains functions related to finding compilers on the
|
||||
system and configuring Spack to use multiple compilers.
|
||||
"""
|
||||
import collections
|
||||
import itertools
|
||||
import multiprocessing.pool
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
@@ -22,16 +21,15 @@
|
||||
import spack.compiler
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.operating_systems
|
||||
import spack.paths
|
||||
import spack.platforms
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
import spack.version
|
||||
from spack.operating_systems import windows_os
|
||||
from spack.util.environment import get_path
|
||||
from spack.util.naming import mod_to_class
|
||||
|
||||
_path_instance_vars = ["cc", "cxx", "f77", "fc"]
|
||||
_flags_instance_vars = ["cflags", "cppflags", "cxxflags", "fflags"]
|
||||
_other_instance_vars = [
|
||||
"modules",
|
||||
"operating_system",
|
||||
@@ -63,6 +61,10 @@
|
||||
}
|
||||
|
||||
|
||||
#: 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():
|
||||
@@ -85,29 +87,7 @@ def converter(cspec_like, *args, **kwargs):
|
||||
|
||||
def _to_dict(compiler):
|
||||
"""Return a dict version of compiler suitable to insert in YAML."""
|
||||
d = {}
|
||||
d["spec"] = str(compiler.spec)
|
||||
d["paths"] = dict((attr, getattr(compiler, attr, None)) for attr in _path_instance_vars)
|
||||
d["flags"] = dict((fname, " ".join(fvals)) for fname, fvals in compiler.flags.items())
|
||||
d["flags"].update(
|
||||
dict(
|
||||
(attr, getattr(compiler, attr, None))
|
||||
for attr in _flags_instance_vars
|
||||
if hasattr(compiler, attr)
|
||||
)
|
||||
)
|
||||
d["operating_system"] = str(compiler.operating_system)
|
||||
d["target"] = str(compiler.target)
|
||||
d["modules"] = compiler.modules or []
|
||||
d["environment"] = compiler.environment or {}
|
||||
d["extra_rpaths"] = compiler.extra_rpaths or []
|
||||
if compiler.enable_implicit_rpaths is not None:
|
||||
d["implicit_rpaths"] = compiler.enable_implicit_rpaths
|
||||
|
||||
if compiler.alias:
|
||||
d["alias"] = compiler.alias
|
||||
|
||||
return {"compiler": d}
|
||||
return {"compiler": compiler.to_dict()}
|
||||
|
||||
|
||||
def get_compiler_config(
|
||||
@@ -127,7 +107,7 @@ def get_compiler_config(
|
||||
# Do not init config because there is a non-empty scope
|
||||
return config
|
||||
|
||||
_init_compiler_config(configuration, scope=scope)
|
||||
find_compilers(scope=scope)
|
||||
config = configuration.get("compilers", scope=scope)
|
||||
return config
|
||||
|
||||
@@ -136,125 +116,8 @@ def get_compiler_config_from_packages(
|
||||
configuration: "spack.config.Configuration", *, scope: Optional[str] = None
|
||||
) -> List[Dict]:
|
||||
"""Return the compiler configuration from packages.yaml"""
|
||||
config = configuration.get("packages", scope=scope)
|
||||
if not config:
|
||||
return []
|
||||
|
||||
packages = []
|
||||
compiler_package_names = supported_compilers() + list(package_name_to_compiler_name.keys())
|
||||
for name, entry in config.items():
|
||||
if name not in compiler_package_names:
|
||||
continue
|
||||
externals_config = entry.get("externals", None)
|
||||
if not externals_config:
|
||||
continue
|
||||
packages.extend(_compiler_config_from_package_config(externals_config))
|
||||
|
||||
return packages
|
||||
|
||||
|
||||
def _compiler_config_from_package_config(config):
|
||||
compilers = []
|
||||
for entry in config:
|
||||
compiler = _compiler_config_from_external(entry)
|
||||
if compiler:
|
||||
compilers.append(compiler)
|
||||
|
||||
return compilers
|
||||
|
||||
|
||||
def _compiler_config_from_external(config):
|
||||
extra_attributes_key = "extra_attributes"
|
||||
compilers_key = "compilers"
|
||||
c_key, cxx_key, fortran_key = "c", "cxx", "fortran"
|
||||
|
||||
# Allow `@x.y.z` instead of `@=x.y.z`
|
||||
spec = spack.spec.parse_with_version_concrete(config["spec"])
|
||||
|
||||
compiler_spec = spack.spec.CompilerSpec(
|
||||
package_name_to_compiler_name.get(spec.name, spec.name), spec.version
|
||||
)
|
||||
|
||||
err_header = f"The external spec '{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]
|
||||
|
||||
# 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"
|
||||
paths = {
|
||||
"cc": c_compiler,
|
||||
"cxx": attribute_compilers.get(cxx_key, None),
|
||||
"fc": attribute_compilers.get(fortran_key, None),
|
||||
"f77": attribute_compilers.get(fortran_key, None),
|
||||
}
|
||||
|
||||
if not spec.architecture:
|
||||
host_platform = spack.platforms.host()
|
||||
operating_system = host_platform.operating_system("default_os")
|
||||
target = host_platform.target("default_target").microarchitecture
|
||||
else:
|
||||
target = spec.architecture.target
|
||||
if not target:
|
||||
target = spack.platforms.host().target("default_target")
|
||||
target = target.microarchitecture
|
||||
|
||||
operating_system = spec.os
|
||||
if not operating_system:
|
||||
host_platform = spack.platforms.host()
|
||||
operating_system = host_platform.operating_system("default_os")
|
||||
|
||||
compiler_entry = {
|
||||
"compiler": {
|
||||
"spec": str(compiler_spec),
|
||||
"paths": paths,
|
||||
"flags": extra_attributes.get("flags", {}),
|
||||
"operating_system": str(operating_system),
|
||||
"target": str(target.family),
|
||||
"modules": config.get("modules", []),
|
||||
"environment": extra_attributes.get("environment", {}),
|
||||
"extra_rpaths": extra_attributes.get("extra_rpaths", []),
|
||||
"implicit_rpaths": extra_attributes.get("implicit_rpaths", None),
|
||||
}
|
||||
}
|
||||
return compiler_entry
|
||||
|
||||
|
||||
def _init_compiler_config(
|
||||
configuration: "spack.config.Configuration", *, scope: Optional[str]
|
||||
) -> None:
|
||||
"""Compiler search used when Spack has no compilers."""
|
||||
compilers = find_compilers()
|
||||
compilers_dict = []
|
||||
for compiler in compilers:
|
||||
compilers_dict.append(_to_dict(compiler))
|
||||
configuration.set("compilers", compilers_dict, scope=scope)
|
||||
packages_yaml = configuration.get("packages", scope=scope)
|
||||
return CompilerConfigFactory.from_packages_yaml(packages_yaml)
|
||||
|
||||
|
||||
def compiler_config_files():
|
||||
@@ -278,9 +141,7 @@ def add_compilers_to_config(compilers, scope=None):
|
||||
compilers: a list of Compiler objects.
|
||||
scope: configuration scope to modify.
|
||||
"""
|
||||
compiler_config = get_compiler_config(
|
||||
configuration=spack.config.CONFIG, scope=scope, init_config=False
|
||||
)
|
||||
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")
|
||||
@@ -329,9 +190,7 @@ def _remove_compiler_from_scope(compiler_spec, scope):
|
||||
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, init_config=False
|
||||
)
|
||||
compiler_config = get_compiler_config(configuration=spack.config.CONFIG, scope=scope)
|
||||
filtered_compiler_config = [
|
||||
compiler_entry
|
||||
for compiler_entry in compiler_config
|
||||
@@ -380,79 +239,77 @@ def all_compiler_specs(scope=None, init_config=True):
|
||||
|
||||
|
||||
def find_compilers(
|
||||
path_hints: Optional[List[str]] = None, *, mixed_toolchain=False
|
||||
path_hints: Optional[List[str]] = None,
|
||||
*,
|
||||
scope: Optional[str] = None,
|
||||
mixed_toolchain: bool = False,
|
||||
max_workers: Optional[int] = None,
|
||||
) -> List["spack.compiler.Compiler"]:
|
||||
"""Return the list of compilers found in the paths given as arguments.
|
||||
"""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)
|
||||
|
||||
# To detect the version of the compilers, we dispatch a certain number
|
||||
# of function calls to different workers. Here we construct the list
|
||||
# of arguments for each call.
|
||||
arguments = []
|
||||
for o in all_os_classes():
|
||||
search_paths = getattr(o, "compiler_search_paths", default_paths)
|
||||
arguments.extend(arguments_to_detect_version_fn(o, search_paths))
|
||||
|
||||
# Here we map the function arguments to the corresponding calls
|
||||
tp = multiprocessing.pool.ThreadPool()
|
||||
try:
|
||||
detected_versions = tp.map(detect_version, arguments)
|
||||
finally:
|
||||
tp.close()
|
||||
|
||||
def valid_version(item: Tuple[Optional[DetectVersionArgs], Optional[str]]) -> bool:
|
||||
value, error = item
|
||||
if error is None:
|
||||
return True
|
||||
try:
|
||||
# This will fail on Python 2.6 if a non ascii
|
||||
# character is in the error
|
||||
tty.debug(error)
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def remove_errors(
|
||||
item: Tuple[Optional[DetectVersionArgs], Optional[str]]
|
||||
) -> DetectVersionArgs:
|
||||
value, _ = item
|
||||
assert value is not None
|
||||
return value
|
||||
|
||||
return make_compiler_list(
|
||||
[remove_errors(detected) for detected in detected_versions if valid_version(detected)],
|
||||
mixed_toolchain=mixed_toolchain,
|
||||
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.spec)]
|
||||
if not compilers:
|
||||
continue
|
||||
valid_compilers[name] = compilers
|
||||
|
||||
def find_new_compilers(
|
||||
path_hints: Optional[List[str]] = None,
|
||||
scope: Optional[str] = None,
|
||||
*,
|
||||
mixed_toolchain: bool = False,
|
||||
):
|
||||
"""Same as ``find_compilers`` but return only the compilers that are not
|
||||
already in compilers.yaml.
|
||||
def _has_fortran_compilers(x):
|
||||
if "compilers" not in x.spec.extra_attributes:
|
||||
return False
|
||||
|
||||
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: scope to look for a compiler. If None consider the merged configuration.
|
||||
mixed_toolchain: allow mixing compilers from different toolchains if otherwise missing for
|
||||
a certain language
|
||||
"""
|
||||
compilers = find_compilers(path_hints, mixed_toolchain=mixed_toolchain)
|
||||
return "fortran" in x.spec.extra_attributes["compilers"]
|
||||
|
||||
return select_new_compilers(compilers, scope)
|
||||
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.spec).version
|
||||
)[-1]
|
||||
gfortran = best_gcc.spec.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.spec.extra_attributes["compilers"]["fortran"] = gfortran
|
||||
|
||||
new_compilers = []
|
||||
for name, detected in valid_compilers.items():
|
||||
for config in CompilerConfigFactory.from_specs([x.spec for x in 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):
|
||||
@@ -462,7 +319,9 @@ def select_new_compilers(compilers, scope=None):
|
||||
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, scope=scope, init_config=False)
|
||||
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)
|
||||
|
||||
@@ -510,8 +369,9 @@ def replace_apple_clang(name):
|
||||
return [replace_apple_clang(name) for name in all_compiler_module_names()]
|
||||
|
||||
|
||||
@llnl.util.lang.memoized
|
||||
def all_compiler_module_names() -> List[str]:
|
||||
return [name for name in llnl.util.lang.list_modules(spack.paths.compilers_path)]
|
||||
return list(llnl.util.lang.list_modules(spack.paths.compilers_path))
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
@@ -531,7 +391,12 @@ def find(compiler_spec, scope=None, init_config=True):
|
||||
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, scope, True, init_config)]
|
||||
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):
|
||||
@@ -553,14 +418,11 @@ def all_compilers_from(configuration, scope=None, init_config=True):
|
||||
|
||||
|
||||
@_auto_compiler_spec
|
||||
def compilers_for_spec(
|
||||
compiler_spec, arch_spec=None, scope=None, use_cache=True, init_config=True
|
||||
):
|
||||
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:
|
||||
@@ -569,7 +431,7 @@ def compilers_for_spec(
|
||||
|
||||
|
||||
def compilers_for_arch(arch_spec, scope=None):
|
||||
config = all_compilers_config(spack.config.CONFIG, scope=scope)
|
||||
config = all_compilers_config(spack.config.CONFIG, scope=scope, init_config=False)
|
||||
return list(get_compilers(config, arch_spec=arch_spec))
|
||||
|
||||
|
||||
@@ -601,13 +463,15 @@ def compiler_from_dict(items):
|
||||
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 _path_instance_vars)):
|
||||
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 _path_instance_vars:
|
||||
for c in spack.compiler.PATH_INSTANCE_VARS:
|
||||
compiler_path = items["paths"][c]
|
||||
if compiler_path != "None":
|
||||
compiler_paths.append(compiler_path)
|
||||
@@ -766,7 +630,7 @@ def class_for_compiler_name(compiler_name):
|
||||
submodule_name = compiler_name.replace("-", "_")
|
||||
|
||||
module_name = ".".join(["spack", "compilers", submodule_name])
|
||||
module_obj = __import__(module_name, fromlist=[None])
|
||||
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.
|
||||
@@ -775,272 +639,10 @@ def class_for_compiler_name(compiler_name):
|
||||
return cls
|
||||
|
||||
|
||||
def all_os_classes():
|
||||
"""
|
||||
Return the list of classes for all operating systems available on
|
||||
this platform
|
||||
"""
|
||||
classes = []
|
||||
|
||||
platform = spack.platforms.host()
|
||||
for os_class in platform.operating_sys.values():
|
||||
classes.append(os_class)
|
||||
|
||||
return classes
|
||||
|
||||
|
||||
def all_compiler_types():
|
||||
return [class_for_compiler_name(c) for c in supported_compilers()]
|
||||
|
||||
|
||||
#: Gathers the attribute values by which a detected compiler is considered
|
||||
#: unique in Spack.
|
||||
#:
|
||||
#: - os: the operating system
|
||||
#: - compiler_name: the name of the compiler (e.g. 'gcc', 'clang', etc.)
|
||||
#: - version: the version of the compiler
|
||||
#:
|
||||
CompilerID = collections.namedtuple("CompilerID", ["os", "compiler_name", "version"])
|
||||
|
||||
#: Variations on a matched compiler name
|
||||
NameVariation = collections.namedtuple("NameVariation", ["prefix", "suffix"])
|
||||
|
||||
#: Groups together the arguments needed by `detect_version`. The four entries
|
||||
#: in the tuple are:
|
||||
#:
|
||||
#: - id: An instance of the CompilerID named tuple (version can be set to None
|
||||
#: as it will be detected later)
|
||||
#: - variation: a NameVariation for file being tested
|
||||
#: - language: compiler language being tested (one of 'cc', 'cxx', 'fc', 'f77')
|
||||
#: - path: full path to the executable being tested
|
||||
#:
|
||||
DetectVersionArgs = collections.namedtuple(
|
||||
"DetectVersionArgs", ["id", "variation", "language", "path"]
|
||||
)
|
||||
|
||||
|
||||
def arguments_to_detect_version_fn(
|
||||
operating_system: spack.operating_systems.OperatingSystem, paths: List[str]
|
||||
) -> List[DetectVersionArgs]:
|
||||
"""Returns a list of DetectVersionArgs tuples to be used in a
|
||||
corresponding function to detect compiler versions.
|
||||
|
||||
The ``operating_system`` instance can customize the behavior of this
|
||||
function by providing a method called with the same name.
|
||||
|
||||
Args:
|
||||
operating_system: the operating system on which we are looking for compilers
|
||||
paths: paths to search for compilers
|
||||
|
||||
Returns:
|
||||
List of DetectVersionArgs tuples. Each item in the list will be later
|
||||
mapped to the corresponding function call to detect the version of the
|
||||
compilers in this OS.
|
||||
"""
|
||||
|
||||
def _default(search_paths: List[str]) -> List[DetectVersionArgs]:
|
||||
command_arguments: List[DetectVersionArgs] = []
|
||||
files_to_be_tested = fs.files_in(*search_paths)
|
||||
for compiler_name in supported_compilers_for_host_platform():
|
||||
compiler_cls = class_for_compiler_name(compiler_name)
|
||||
|
||||
for language in ("cc", "cxx", "f77", "fc"):
|
||||
# Select only the files matching a regexp
|
||||
for (file, full_path), regexp in itertools.product(
|
||||
files_to_be_tested, compiler_cls.search_regexps(language)
|
||||
):
|
||||
match = regexp.match(file)
|
||||
if match:
|
||||
compiler_id = CompilerID(operating_system, compiler_name, None)
|
||||
detect_version_args = DetectVersionArgs(
|
||||
id=compiler_id,
|
||||
variation=NameVariation(*match.groups()),
|
||||
language=language,
|
||||
path=full_path,
|
||||
)
|
||||
command_arguments.append(detect_version_args)
|
||||
|
||||
return command_arguments
|
||||
|
||||
fn = getattr(operating_system, "arguments_to_detect_version_fn", _default)
|
||||
return fn(paths)
|
||||
|
||||
|
||||
def detect_version(
|
||||
detect_version_args: DetectVersionArgs,
|
||||
) -> Tuple[Optional[DetectVersionArgs], Optional[str]]:
|
||||
"""Computes the version of a compiler and adds it to the information
|
||||
passed as input.
|
||||
|
||||
As this function is meant to be executed by worker processes it won't
|
||||
raise any exception but instead will return a (value, error) tuple that
|
||||
needs to be checked by the code dispatching the calls.
|
||||
|
||||
Args:
|
||||
detect_version_args: information on the compiler for which we should detect the version.
|
||||
|
||||
Returns:
|
||||
A ``(DetectVersionArgs, error)`` tuple. If ``error`` is ``None`` the
|
||||
version of the compiler was computed correctly and the first argument
|
||||
of the tuple will contain it. Otherwise ``error`` is a string
|
||||
containing an explanation on why the version couldn't be computed.
|
||||
"""
|
||||
|
||||
def _default(fn_args):
|
||||
compiler_id = fn_args.id
|
||||
language = fn_args.language
|
||||
compiler_cls = class_for_compiler_name(compiler_id.compiler_name)
|
||||
path = fn_args.path
|
||||
|
||||
# Get compiler names and the callback to detect their versions
|
||||
callback = getattr(compiler_cls, f"{language}_version")
|
||||
|
||||
try:
|
||||
version = callback(path)
|
||||
if version and str(version).strip() and version != "unknown":
|
||||
value = fn_args._replace(id=compiler_id._replace(version=version))
|
||||
return value, None
|
||||
|
||||
error = f"Couldn't get version for compiler {path}".format(path)
|
||||
except spack.util.executable.ProcessError as e:
|
||||
error = f"Couldn't get version for compiler {path}\n" + str(e)
|
||||
except spack.util.executable.ProcessTimeoutError as e:
|
||||
error = f"Couldn't get version for compiler {path}\n" + str(e)
|
||||
except Exception as e:
|
||||
# Catching "Exception" here is fine because it just
|
||||
# means something went wrong running a candidate executable.
|
||||
error = "Error while executing candidate compiler {0}" "\n{1}: {2}".format(
|
||||
path, e.__class__.__name__, str(e)
|
||||
)
|
||||
return None, error
|
||||
|
||||
operating_system = detect_version_args.id.os
|
||||
fn = getattr(operating_system, "detect_version", _default)
|
||||
return fn(detect_version_args)
|
||||
|
||||
|
||||
def make_compiler_list(
|
||||
detected_versions: List[DetectVersionArgs], mixed_toolchain: bool = False
|
||||
) -> List["spack.compiler.Compiler"]:
|
||||
"""Process a list of detected versions and turn them into a list of
|
||||
compiler specs.
|
||||
|
||||
Args:
|
||||
detected_versions: list of DetectVersionArgs containing a valid version
|
||||
mixed_toolchain: allow mixing compilers from different toolchains if langauge is missing
|
||||
|
||||
Returns:
|
||||
list: list of Compiler objects
|
||||
"""
|
||||
group_fn = lambda x: (x.id, x.variation, x.language)
|
||||
sorted_compilers = sorted(detected_versions, key=group_fn)
|
||||
|
||||
# Gather items in a dictionary by the id, name variation and language
|
||||
compilers_d: Dict[CompilerID, Dict[NameVariation, dict]] = {}
|
||||
for sort_key, group in itertools.groupby(sorted_compilers, key=group_fn):
|
||||
compiler_id, name_variation, language = sort_key
|
||||
by_compiler_id = compilers_d.setdefault(compiler_id, {})
|
||||
by_name_variation = by_compiler_id.setdefault(name_variation, {})
|
||||
by_name_variation[language] = next(x.path for x in group)
|
||||
|
||||
def _default_make_compilers(cmp_id, paths):
|
||||
operating_system, compiler_name, version = cmp_id
|
||||
compiler_cls = class_for_compiler_name(compiler_name)
|
||||
spec = spack.spec.CompilerSpec(compiler_cls.name, f"={version}")
|
||||
paths = [paths.get(x, None) for x in ("cc", "cxx", "f77", "fc")]
|
||||
# TODO: johnwparent - revist the following line as per discussion at:
|
||||
# https://github.com/spack/spack/pull/33385/files#r1040036318
|
||||
target = archspec.cpu.host()
|
||||
compiler = compiler_cls(spec, operating_system, str(target.family), paths)
|
||||
return [compiler]
|
||||
|
||||
# For compilers with the same compiler id:
|
||||
#
|
||||
# - Prefer with C compiler to without
|
||||
# - Prefer with C++ compiler to without
|
||||
# - Prefer no variations to variations (e.g., clang to clang-gpu)
|
||||
#
|
||||
sort_fn = lambda variation: (
|
||||
"cc" not in by_compiler_id[variation], # None last
|
||||
"cxx" not in by_compiler_id[variation], # None last
|
||||
getattr(variation, "prefix", None),
|
||||
getattr(variation, "suffix", None),
|
||||
)
|
||||
|
||||
# Flatten to a list of compiler id, primary variation and compiler dictionary
|
||||
flat_compilers: List[Tuple[CompilerID, NameVariation, dict]] = []
|
||||
for compiler_id, by_compiler_id in compilers_d.items():
|
||||
ordered = sorted(by_compiler_id, key=sort_fn)
|
||||
selected_variation = ordered[0]
|
||||
selected = by_compiler_id[selected_variation]
|
||||
|
||||
# Fill any missing parts from subsequent entries (without mixing toolchains)
|
||||
for lang in ["cxx", "f77", "fc"]:
|
||||
if lang not in selected:
|
||||
next_lang = next(
|
||||
(by_compiler_id[v][lang] for v in ordered if lang in by_compiler_id[v]), None
|
||||
)
|
||||
if next_lang:
|
||||
selected[lang] = next_lang
|
||||
|
||||
flat_compilers.append((compiler_id, selected_variation, selected))
|
||||
|
||||
# Next, fill out the blanks of missing compilers by creating a mixed toolchain (if requested)
|
||||
if mixed_toolchain:
|
||||
make_mixed_toolchain(flat_compilers)
|
||||
|
||||
# Finally, create the compiler list
|
||||
compilers: List["spack.compiler.Compiler"] = []
|
||||
for compiler_id, _, compiler in flat_compilers:
|
||||
make_compilers = getattr(compiler_id.os, "make_compilers", _default_make_compilers)
|
||||
candidates = make_compilers(compiler_id, compiler)
|
||||
compilers.extend(x for x in candidates if x.cc is not None)
|
||||
|
||||
return compilers
|
||||
|
||||
|
||||
def make_mixed_toolchain(compilers: List[Tuple[CompilerID, NameVariation, dict]]) -> None:
|
||||
"""Add missing compilers across toolchains when they are missing for a particular language.
|
||||
This currently only adds the most sensible gfortran to (apple)-clang if it doesn't have a
|
||||
fortran compiler (no flang)."""
|
||||
|
||||
# First collect the clangs that are missing a fortran compiler
|
||||
clangs_without_flang = [
|
||||
(id, variation, compiler)
|
||||
for id, variation, compiler in compilers
|
||||
if id.compiler_name in ("clang", "apple-clang")
|
||||
and "f77" not in compiler
|
||||
and "fc" not in compiler
|
||||
]
|
||||
if not clangs_without_flang:
|
||||
return
|
||||
|
||||
# Filter on GCCs with fortran compiler
|
||||
gccs_with_fortran = [
|
||||
(id, variation, compiler)
|
||||
for id, variation, compiler in compilers
|
||||
if id.compiler_name == "gcc" and "f77" in compiler and "fc" in compiler
|
||||
]
|
||||
|
||||
# Sort these GCCs by "best variation" (no prefix / suffix first)
|
||||
gccs_with_fortran.sort(
|
||||
key=lambda x: (getattr(x[1], "prefix", None), getattr(x[1], "suffix", None))
|
||||
)
|
||||
|
||||
# Attach the optimal GCC fortran compiler to the clangs that don't have one
|
||||
for clang_id, _, clang_compiler in clangs_without_flang:
|
||||
gcc_compiler = next(
|
||||
(gcc[2] for gcc in gccs_with_fortran if gcc[0].os == clang_id.os), None
|
||||
)
|
||||
|
||||
if not gcc_compiler:
|
||||
continue
|
||||
|
||||
# Update the fc / f77 entries
|
||||
clang_compiler["f77"] = gcc_compiler["f77"]
|
||||
clang_compiler["fc"] = gcc_compiler["fc"]
|
||||
|
||||
|
||||
def is_mixed_toolchain(compiler):
|
||||
"""Returns True if the current compiler is a mixed toolchain,
|
||||
False otherwise.
|
||||
@@ -1087,20 +689,164 @@ def name_matches(name, name_list):
|
||||
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").microarchitecture
|
||||
else:
|
||||
target = spec.architecture.target
|
||||
if not target:
|
||||
target = spack.platforms.host().target("default_target")
|
||||
target = target.microarchitecture
|
||||
|
||||
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__(
|
||||
'Invalid configuration for [compiler "%s"]: ' % compiler_spec,
|
||||
"Compiler configuration must contain entries for all compilers: %s"
|
||||
% _path_instance_vars,
|
||||
f'Invalid configuration for [compiler "{compiler_spec}"]: ',
|
||||
f"Compiler configuration must contain entries for "
|
||||
f"all compilers: {spack.compiler.PATH_INSTANCE_VARS}",
|
||||
)
|
||||
|
||||
|
||||
class NoCompilersError(spack.error.SpackError):
|
||||
def __init__(self):
|
||||
super().__init__("Spack could not find any compilers!")
|
||||
|
||||
|
||||
class UnknownCompilerError(spack.error.SpackError):
|
||||
def __init__(self, compiler_name):
|
||||
super().__init__("Spack doesn't support the requested compiler: {0}".format(compiler_name))
|
||||
@@ -1111,25 +857,3 @@ def __init__(self, compiler_spec, target):
|
||||
super().__init__(
|
||||
"No compilers for operating system %s satisfy spec %s" % (target, compiler_spec)
|
||||
)
|
||||
|
||||
|
||||
class CompilerDuplicateError(spack.error.SpackError):
|
||||
def __init__(self, compiler_spec, arch_spec):
|
||||
config_file_to_duplicates = get_compiler_duplicates(compiler_spec, arch_spec)
|
||||
duplicate_table = list((x, len(y)) for x, y in config_file_to_duplicates.items())
|
||||
descriptor = lambda num: "time" if num == 1 else "times"
|
||||
duplicate_msg = lambda cfgfile, count: "{0}: {1} {2}".format(
|
||||
cfgfile, str(count), descriptor(count)
|
||||
)
|
||||
msg = (
|
||||
"Compiler configuration contains entries with duplicate"
|
||||
+ " specification ({0}, {1})".format(compiler_spec, arch_spec)
|
||||
+ " in the following files:\n\t"
|
||||
+ "\n\t".join(duplicate_msg(x, y) for x, y in duplicate_table)
|
||||
)
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class CompilerSpecInsufficientlySpecificError(spack.error.SpackError):
|
||||
def __init__(self, compiler_spec):
|
||||
super().__init__("Multiple compilers satisfy spec %s" % compiler_spec)
|
||||
|
||||
@@ -223,6 +223,30 @@ def get_oneapi_root(pth: str):
|
||||
)
|
||||
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
|
||||
|
||||
@@ -2,31 +2,12 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""
|
||||
Functions here are used to take abstract specs and make them concrete.
|
||||
For example, if a spec asks for a version between 1.8 and 1.9, these
|
||||
functions might take will take the most recent 1.9 version of the
|
||||
package available. Or, if the user didn't specify a compiler for a
|
||||
spec, then this will assign a compiler to the spec based on defaults
|
||||
or user preferences.
|
||||
|
||||
TODO: make this customizable and allow users to configure
|
||||
concretization policies.
|
||||
(DEPRECATED) Used to contain the code for the original concretizer
|
||||
"""
|
||||
import functools
|
||||
import platform
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
from itertools import chain
|
||||
from typing import Union
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.abi
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.environment
|
||||
@@ -37,639 +18,20 @@
|
||||
import spack.target
|
||||
import spack.tengine
|
||||
import spack.util.path
|
||||
import spack.variant as vt
|
||||
from spack.package_prefs import PackagePrefs, is_spec_buildable, spec_externals
|
||||
from spack.version import ClosedOpenRange, VersionList, ver
|
||||
|
||||
#: impements rudimentary logic for ABI compatibility
|
||||
_abi: Union[spack.abi.ABI, llnl.util.lang.Singleton] = llnl.util.lang.Singleton(
|
||||
lambda: spack.abi.ABI()
|
||||
)
|
||||
|
||||
|
||||
@functools.total_ordering
|
||||
class reverse_order:
|
||||
"""Helper for creating key functions.
|
||||
|
||||
This is a wrapper that inverts the sense of the natural
|
||||
comparisons on the object.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return other.value == self.value
|
||||
|
||||
def __lt__(self, other):
|
||||
return other.value < self.value
|
||||
|
||||
|
||||
class Concretizer:
|
||||
"""You can subclass this class to override some of the default
|
||||
concretization strategies, or you can override all of them.
|
||||
"""
|
||||
"""(DEPRECATED) Only contains logic to enable/disable compiler existence checks."""
|
||||
|
||||
#: Controls whether we check that compiler versions actually exist
|
||||
#: during concretization. Used for testing and for mirror creation
|
||||
check_for_compiler_existence = None
|
||||
|
||||
#: Packages that the old concretizer cannot deal with correctly, and cannot build anyway.
|
||||
#: Those will not be considered as providers for virtuals.
|
||||
non_buildable_packages = {"glibc", "musl"}
|
||||
|
||||
def __init__(self, abstract_spec=None):
|
||||
def __init__(self):
|
||||
if Concretizer.check_for_compiler_existence is None:
|
||||
Concretizer.check_for_compiler_existence = not spack.config.get(
|
||||
"config:install_missing_compilers", False
|
||||
)
|
||||
self.abstract_spec = abstract_spec
|
||||
self._adjust_target_answer_generator = None
|
||||
|
||||
def concretize_develop(self, spec):
|
||||
"""
|
||||
Add ``dev_path=*`` variant to packages built from local source.
|
||||
"""
|
||||
env = spack.environment.active_environment()
|
||||
dev_info = env.dev_specs.get(spec.name, {}) if env else {}
|
||||
if not dev_info:
|
||||
return False
|
||||
|
||||
path = spack.util.path.canonicalize_path(dev_info["path"], default_wd=env.path)
|
||||
|
||||
if "dev_path" in spec.variants:
|
||||
assert spec.variants["dev_path"].value == path
|
||||
changed = False
|
||||
else:
|
||||
spec.variants.setdefault("dev_path", vt.SingleValuedVariant("dev_path", path))
|
||||
changed = True
|
||||
changed |= spec.constrain(dev_info["spec"])
|
||||
return changed
|
||||
|
||||
def _valid_virtuals_and_externals(self, spec):
|
||||
"""Returns a list of candidate virtual dep providers and external
|
||||
packages that coiuld be used to concretize a spec.
|
||||
|
||||
Preferred specs come first in the list.
|
||||
"""
|
||||
# First construct a list of concrete candidates to replace spec with.
|
||||
candidates = [spec]
|
||||
pref_key = lambda spec: 0 # no-op pref key
|
||||
|
||||
if spec.virtual:
|
||||
candidates = [
|
||||
s
|
||||
for s in spack.repo.PATH.providers_for(spec)
|
||||
if s.name not in self.non_buildable_packages
|
||||
]
|
||||
if not candidates:
|
||||
raise spack.error.UnsatisfiableProviderSpecError(candidates[0], spec)
|
||||
|
||||
# Find nearest spec in the DAG (up then down) that has prefs.
|
||||
spec_w_prefs = find_spec(
|
||||
spec, lambda p: PackagePrefs.has_preferred_providers(p.name, spec.name), spec
|
||||
) # default to spec itself.
|
||||
|
||||
# Create a key to sort candidates by the prefs we found
|
||||
pref_key = PackagePrefs(spec_w_prefs.name, "providers", spec.name)
|
||||
|
||||
# For each candidate package, if it has externals, add those
|
||||
# to the usable list. if it's not buildable, then *only* add
|
||||
# the externals.
|
||||
usable = []
|
||||
for cspec in candidates:
|
||||
if is_spec_buildable(cspec):
|
||||
usable.append(cspec)
|
||||
|
||||
externals = spec_externals(cspec)
|
||||
for ext in externals:
|
||||
if ext.intersects(spec):
|
||||
usable.append(ext)
|
||||
|
||||
# If nothing is in the usable list now, it's because we aren't
|
||||
# allowed to build anything.
|
||||
if not usable:
|
||||
raise NoBuildError(spec)
|
||||
|
||||
# Use a sort key to order the results
|
||||
return sorted(
|
||||
usable,
|
||||
key=lambda spec: (
|
||||
not spec.external, # prefer externals
|
||||
pref_key(spec), # respect prefs
|
||||
spec.name, # group by name
|
||||
reverse_order(spec.versions), # latest version
|
||||
spec, # natural order
|
||||
),
|
||||
)
|
||||
|
||||
def choose_virtual_or_external(self, spec: spack.spec.Spec):
|
||||
"""Given a list of candidate virtual and external packages, try to
|
||||
find one that is most ABI compatible.
|
||||
"""
|
||||
candidates = self._valid_virtuals_and_externals(spec)
|
||||
if not candidates:
|
||||
return candidates
|
||||
|
||||
# Find the nearest spec in the dag that has a compiler. We'll
|
||||
# use that spec to calibrate compiler compatibility.
|
||||
abi_exemplar = find_spec(spec, lambda x: x.compiler)
|
||||
if abi_exemplar is None:
|
||||
abi_exemplar = spec.root
|
||||
|
||||
# Sort candidates from most to least compatibility.
|
||||
# We reverse because True > False.
|
||||
# Sort is stable, so candidates keep their order.
|
||||
return sorted(
|
||||
candidates,
|
||||
reverse=True,
|
||||
key=lambda spec: (
|
||||
_abi.compatible(spec, abi_exemplar, loose=True),
|
||||
_abi.compatible(spec, abi_exemplar),
|
||||
),
|
||||
)
|
||||
|
||||
def concretize_version(self, spec):
|
||||
"""If the spec is already concrete, return. Otherwise take
|
||||
the preferred version from spackconfig, and default to the package's
|
||||
version if there are no available versions.
|
||||
|
||||
TODO: In many cases we probably want to look for installed
|
||||
versions of each package and use an installed version
|
||||
if we can link to it. The policy implemented here will
|
||||
tend to rebuild a lot of stuff becasue it will prefer
|
||||
a compiler in the spec to any compiler already-
|
||||
installed things were built with. There is likely
|
||||
some better policy that finds some middle ground
|
||||
between these two extremes.
|
||||
"""
|
||||
# return if already concrete.
|
||||
if spec.versions.concrete:
|
||||
return False
|
||||
|
||||
# List of versions we could consider, in sorted order
|
||||
pkg_versions = spec.package_class.versions
|
||||
usable = [v for v in pkg_versions if any(v.intersects(sv) for sv in spec.versions)]
|
||||
|
||||
yaml_prefs = PackagePrefs(spec.name, "version")
|
||||
|
||||
# The keys below show the order of precedence of factors used
|
||||
# to select a version when concretizing. The item with
|
||||
# the "largest" key will be selected.
|
||||
#
|
||||
# NOTE: When COMPARING VERSIONS, the '@develop' version is always
|
||||
# larger than other versions. BUT when CONCRETIZING,
|
||||
# the largest NON-develop version is selected by default.
|
||||
keyfn = lambda v: (
|
||||
# ------- Special direction from the user
|
||||
# Respect order listed in packages.yaml
|
||||
-yaml_prefs(v),
|
||||
# The preferred=True flag (packages or packages.yaml or both?)
|
||||
pkg_versions.get(v).get("preferred", False),
|
||||
# ------- Regular case: use latest non-develop version by default.
|
||||
# Avoid @develop version, which would otherwise be the "largest"
|
||||
# in straight version comparisons
|
||||
not v.isdevelop(),
|
||||
# Compare the version itself
|
||||
# This includes the logic:
|
||||
# a) develop > everything (disabled by "not v.isdevelop() above)
|
||||
# b) numeric > non-numeric
|
||||
# c) Numeric or string comparison
|
||||
v,
|
||||
)
|
||||
usable.sort(key=keyfn, reverse=True)
|
||||
|
||||
if usable:
|
||||
spec.versions = ver([usable[0]])
|
||||
else:
|
||||
# We don't know of any SAFE versions that match the given
|
||||
# spec. Grab the spec's versions and grab the highest
|
||||
# *non-open* part of the range of versions it specifies.
|
||||
# Someone else can raise an error if this happens,
|
||||
# e.g. when we go to fetch it and don't know how. But it
|
||||
# *might* work.
|
||||
if not spec.versions or spec.versions == VersionList([":"]):
|
||||
raise NoValidVersionError(spec)
|
||||
else:
|
||||
last = spec.versions[-1]
|
||||
if isinstance(last, ClosedOpenRange):
|
||||
range_as_version = VersionList([last]).concrete_range_as_version
|
||||
if range_as_version:
|
||||
spec.versions = ver([range_as_version])
|
||||
else:
|
||||
raise NoValidVersionError(spec)
|
||||
else:
|
||||
spec.versions = ver([last])
|
||||
|
||||
return True # Things changed
|
||||
|
||||
def concretize_architecture(self, spec):
|
||||
"""If the spec is empty provide the defaults of the platform. If the
|
||||
architecture is not a string type, then check if either the platform,
|
||||
target or operating system are concretized. If any of the fields are
|
||||
changed then return True. If everything is concretized (i.e the
|
||||
architecture attribute is a namedtuple of classes) then return False.
|
||||
If the target is a string type, then convert the string into a
|
||||
concretized architecture. If it has no architecture and the root of the
|
||||
DAG has an architecture, then use the root otherwise use the defaults
|
||||
on the platform.
|
||||
"""
|
||||
# ensure type safety for the architecture
|
||||
if spec.architecture is None:
|
||||
spec.architecture = spack.spec.ArchSpec()
|
||||
|
||||
if spec.architecture.concrete:
|
||||
return False
|
||||
|
||||
# Get platform of nearest spec with a platform, including spec
|
||||
# If spec has a platform, easy
|
||||
if spec.architecture.platform:
|
||||
new_plat = spack.platforms.by_name(spec.architecture.platform)
|
||||
else:
|
||||
# Else if anyone else has a platform, take the closest one
|
||||
# Search up, then down, along build/link deps first
|
||||
# Then any nearest. Algorithm from compilerspec search
|
||||
platform_spec = find_spec(spec, lambda x: x.architecture and x.architecture.platform)
|
||||
if platform_spec:
|
||||
new_plat = spack.platforms.by_name(platform_spec.architecture.platform)
|
||||
else:
|
||||
# If no platform anywhere in this spec, grab the default
|
||||
new_plat = spack.platforms.host()
|
||||
|
||||
# Get nearest spec with relevant platform and an os
|
||||
# Generally, same algorithm as finding platform, except we only
|
||||
# consider specs that have a platform
|
||||
if spec.architecture.os:
|
||||
new_os = spec.architecture.os
|
||||
else:
|
||||
new_os_spec = find_spec(
|
||||
spec,
|
||||
lambda x: (
|
||||
x.architecture
|
||||
and x.architecture.platform == str(new_plat)
|
||||
and x.architecture.os
|
||||
),
|
||||
)
|
||||
if new_os_spec:
|
||||
new_os = new_os_spec.architecture.os
|
||||
else:
|
||||
new_os = new_plat.operating_system("default_os")
|
||||
|
||||
# Get the nearest spec with relevant platform and a target
|
||||
# Generally, same algorithm as finding os
|
||||
curr_target = None
|
||||
if spec.architecture.target:
|
||||
curr_target = spec.architecture.target
|
||||
if spec.architecture.target and spec.architecture.target_concrete:
|
||||
new_target = spec.architecture.target
|
||||
else:
|
||||
new_target_spec = find_spec(
|
||||
spec,
|
||||
lambda x: (
|
||||
x.architecture
|
||||
and x.architecture.platform == str(new_plat)
|
||||
and x.architecture.target
|
||||
and x.architecture.target != curr_target
|
||||
),
|
||||
)
|
||||
if new_target_spec:
|
||||
if curr_target:
|
||||
# constrain one target by the other
|
||||
new_target_arch = spack.spec.ArchSpec(
|
||||
(None, None, new_target_spec.architecture.target)
|
||||
)
|
||||
curr_target_arch = spack.spec.ArchSpec((None, None, curr_target))
|
||||
curr_target_arch.constrain(new_target_arch)
|
||||
new_target = curr_target_arch.target
|
||||
else:
|
||||
new_target = new_target_spec.architecture.target
|
||||
else:
|
||||
# To get default platform, consider package prefs
|
||||
if PackagePrefs.has_preferred_targets(spec.name):
|
||||
new_target = self.target_from_package_preferences(spec)
|
||||
else:
|
||||
new_target = new_plat.target("default_target")
|
||||
if curr_target:
|
||||
# convert to ArchSpec to compare satisfaction
|
||||
new_target_arch = spack.spec.ArchSpec((None, None, str(new_target)))
|
||||
curr_target_arch = spack.spec.ArchSpec((None, None, str(curr_target)))
|
||||
|
||||
if not new_target_arch.intersects(curr_target_arch):
|
||||
# new_target is an incorrect guess based on preferences
|
||||
# and/or default
|
||||
valid_target_ranges = str(curr_target).split(",")
|
||||
for target_range in valid_target_ranges:
|
||||
t_min, t_sep, t_max = target_range.partition(":")
|
||||
if not t_sep:
|
||||
new_target = t_min
|
||||
break
|
||||
elif t_max:
|
||||
new_target = t_max
|
||||
break
|
||||
elif t_min:
|
||||
# TODO: something better than picking first
|
||||
new_target = t_min
|
||||
break
|
||||
|
||||
# Construct new architecture, compute whether spec changed
|
||||
arch_spec = (str(new_plat), str(new_os), str(new_target))
|
||||
new_arch = spack.spec.ArchSpec(arch_spec)
|
||||
spec_changed = new_arch != spec.architecture
|
||||
spec.architecture = new_arch
|
||||
return spec_changed
|
||||
|
||||
def target_from_package_preferences(self, spec):
|
||||
"""Returns the preferred target from the package preferences if
|
||||
there's any.
|
||||
|
||||
Args:
|
||||
spec: abstract spec to be concretized
|
||||
"""
|
||||
target_prefs = PackagePrefs(spec.name, "target")
|
||||
target_specs = [spack.spec.Spec("target=%s" % tname) for tname in archspec.cpu.TARGETS]
|
||||
|
||||
def tspec_filter(s):
|
||||
# Filter target specs by whether the architecture
|
||||
# family is the current machine type. This ensures
|
||||
# we only consider x86_64 targets when on an
|
||||
# x86_64 machine, etc. This may need to change to
|
||||
# enable setting cross compiling as a default
|
||||
target = archspec.cpu.TARGETS[str(s.architecture.target)]
|
||||
arch_family_name = target.family.name
|
||||
return arch_family_name == platform.machine()
|
||||
|
||||
# Sort filtered targets by package prefs
|
||||
target_specs = list(filter(tspec_filter, target_specs))
|
||||
target_specs.sort(key=target_prefs)
|
||||
new_target = target_specs[0].architecture.target
|
||||
return new_target
|
||||
|
||||
def concretize_variants(self, spec):
|
||||
"""If the spec already has variants filled in, return. Otherwise, add
|
||||
the user preferences from packages.yaml or the default variants from
|
||||
the package specification.
|
||||
"""
|
||||
changed = False
|
||||
preferred_variants = PackagePrefs.preferred_variants(spec.name)
|
||||
pkg_cls = spec.package_class
|
||||
for name, entry in pkg_cls.variants.items():
|
||||
variant, when = entry
|
||||
var = spec.variants.get(name, None)
|
||||
if var and "*" in var:
|
||||
# remove variant wildcard before concretizing
|
||||
# wildcard cannot be combined with other variables in a
|
||||
# multivalue variant, a concrete variant cannot have the value
|
||||
# wildcard, and a wildcard does not constrain a variant
|
||||
spec.variants.pop(name)
|
||||
if name not in spec.variants and any(spec.satisfies(w) for w in when):
|
||||
changed = True
|
||||
if name in preferred_variants:
|
||||
spec.variants[name] = preferred_variants.get(name)
|
||||
else:
|
||||
spec.variants[name] = variant.make_default()
|
||||
if name in spec.variants and not any(spec.satisfies(w) for w in when):
|
||||
raise vt.InvalidVariantForSpecError(name, when, spec)
|
||||
|
||||
return changed
|
||||
|
||||
def concretize_compiler(self, spec):
|
||||
"""If the spec already has a compiler, we're done. If not, then take
|
||||
the compiler used for the nearest ancestor with a compiler
|
||||
spec and use that. If the ancestor's compiler is not
|
||||
concrete, then used the preferred compiler as specified in
|
||||
spackconfig.
|
||||
|
||||
Intuition: Use the spackconfig default if no package that depends on
|
||||
this one has a strict compiler requirement. Otherwise, try to
|
||||
build with the compiler that will be used by libraries that
|
||||
link to this one, to maximize compatibility.
|
||||
"""
|
||||
# Pass on concretizing the compiler if the target or operating system
|
||||
# is not yet determined
|
||||
if not spec.architecture.concrete:
|
||||
# We haven't changed, but other changes need to happen before we
|
||||
# continue. `return True` here to force concretization to keep
|
||||
# running.
|
||||
return True
|
||||
|
||||
# Only use a matching compiler if it is of the proper style
|
||||
# Takes advantage of the proper logic already existing in
|
||||
# compiler_for_spec Should think whether this can be more
|
||||
# efficient
|
||||
def _proper_compiler_style(cspec, aspec):
|
||||
compilers = spack.compilers.compilers_for_spec(cspec, arch_spec=aspec)
|
||||
# If the spec passed as argument is concrete we want to check
|
||||
# the versions match exactly
|
||||
if (
|
||||
cspec.concrete
|
||||
and compilers
|
||||
and cspec.version not in [c.version for c in compilers]
|
||||
):
|
||||
return []
|
||||
|
||||
return compilers
|
||||
|
||||
if spec.compiler and spec.compiler.concrete:
|
||||
if self.check_for_compiler_existence and not _proper_compiler_style(
|
||||
spec.compiler, spec.architecture
|
||||
):
|
||||
_compiler_concretization_failure(spec.compiler, spec.architecture)
|
||||
return False
|
||||
|
||||
# Find another spec that has a compiler, or the root if none do
|
||||
other_spec = spec if spec.compiler else find_spec(spec, lambda x: x.compiler, spec.root)
|
||||
other_compiler = other_spec.compiler
|
||||
assert other_spec
|
||||
|
||||
# Check if the compiler is already fully specified
|
||||
if other_compiler and other_compiler.concrete:
|
||||
if self.check_for_compiler_existence and not _proper_compiler_style(
|
||||
other_compiler, spec.architecture
|
||||
):
|
||||
_compiler_concretization_failure(other_compiler, spec.architecture)
|
||||
spec.compiler = other_compiler
|
||||
return True
|
||||
|
||||
if other_compiler: # Another node has abstract compiler information
|
||||
compiler_list = spack.compilers.find_specs_by_arch(other_compiler, spec.architecture)
|
||||
if not compiler_list:
|
||||
# We don't have a matching compiler installed
|
||||
if not self.check_for_compiler_existence:
|
||||
# Concretize compiler spec versions as a package to build
|
||||
cpkg_spec = spack.compilers.pkg_spec_for_compiler(other_compiler)
|
||||
self.concretize_version(cpkg_spec)
|
||||
spec.compiler = spack.spec.CompilerSpec(
|
||||
other_compiler.name, cpkg_spec.versions
|
||||
)
|
||||
return True
|
||||
else:
|
||||
# No compiler with a satisfactory spec was found
|
||||
raise UnavailableCompilerVersionError(other_compiler, spec.architecture)
|
||||
else:
|
||||
# We have no hints to go by, grab any compiler
|
||||
compiler_list = spack.compilers.all_compiler_specs()
|
||||
if not compiler_list:
|
||||
# Spack has no compilers.
|
||||
raise spack.compilers.NoCompilersError()
|
||||
|
||||
# By default, prefer later versions of compilers
|
||||
compiler_list = sorted(compiler_list, key=lambda x: (x.name, x.version), reverse=True)
|
||||
ppk = PackagePrefs(other_spec.name, "compiler")
|
||||
matches = sorted(compiler_list, key=ppk)
|
||||
|
||||
# copy concrete version into other_compiler
|
||||
try:
|
||||
spec.compiler = next(
|
||||
c for c in matches if _proper_compiler_style(c, spec.architecture)
|
||||
).copy()
|
||||
except StopIteration:
|
||||
# No compiler with a satisfactory spec has a suitable arch
|
||||
_compiler_concretization_failure(other_compiler, spec.architecture)
|
||||
|
||||
assert spec.compiler.concrete
|
||||
return True # things changed.
|
||||
|
||||
def concretize_compiler_flags(self, spec):
|
||||
"""
|
||||
The compiler flags are updated to match those of the spec whose
|
||||
compiler is used, defaulting to no compiler flags in the spec.
|
||||
Default specs set at the compiler level will still be added later.
|
||||
"""
|
||||
# Pass on concretizing the compiler flags if the target or operating
|
||||
# system is not set.
|
||||
if not spec.architecture.concrete:
|
||||
# We haven't changed, but other changes need to happen before we
|
||||
# continue. `return True` here to force concretization to keep
|
||||
# running.
|
||||
return True
|
||||
|
||||
compiler_match = lambda other: (
|
||||
spec.compiler == other.compiler and spec.architecture == other.architecture
|
||||
)
|
||||
|
||||
ret = False
|
||||
for flag in spack.spec.FlagMap.valid_compiler_flags():
|
||||
if flag not in spec.compiler_flags:
|
||||
spec.compiler_flags[flag] = list()
|
||||
try:
|
||||
nearest = next(
|
||||
p
|
||||
for p in spec.traverse(direction="parents")
|
||||
if (compiler_match(p) and (p is not spec) and flag in p.compiler_flags)
|
||||
)
|
||||
nearest_flags = nearest.compiler_flags.get(flag, [])
|
||||
flags = spec.compiler_flags.get(flag, [])
|
||||
if set(nearest_flags) - set(flags):
|
||||
spec.compiler_flags[flag] = list(llnl.util.lang.dedupe(nearest_flags + flags))
|
||||
ret = True
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
# Include the compiler flag defaults from the config files
|
||||
# This ensures that spack will detect conflicts that stem from a change
|
||||
# in default compiler flags.
|
||||
try:
|
||||
compiler = spack.compilers.compiler_for_spec(spec.compiler, spec.architecture)
|
||||
except spack.compilers.NoCompilerForSpecError:
|
||||
if self.check_for_compiler_existence:
|
||||
raise
|
||||
return ret
|
||||
for flag in compiler.flags:
|
||||
config_flags = compiler.flags.get(flag, [])
|
||||
flags = spec.compiler_flags.get(flag, [])
|
||||
spec.compiler_flags[flag] = list(llnl.util.lang.dedupe(config_flags + flags))
|
||||
if set(config_flags) - set(flags):
|
||||
ret = True
|
||||
|
||||
return ret
|
||||
|
||||
def adjust_target(self, spec):
|
||||
"""Adjusts the target microarchitecture if the compiler is too old
|
||||
to support the default one.
|
||||
|
||||
Args:
|
||||
spec: spec to be concretized
|
||||
|
||||
Returns:
|
||||
True if spec was modified, False otherwise
|
||||
"""
|
||||
# To minimize the impact on performance this function will attempt
|
||||
# to adjust the target only at the very first call once necessary
|
||||
# information is set. It will just return False on subsequent calls.
|
||||
# The way this is achieved is by initializing a generator and making
|
||||
# this function return the next answer.
|
||||
if not (spec.architecture and spec.architecture.concrete):
|
||||
# Not ready, but keep going because we have work to do later
|
||||
return True
|
||||
|
||||
def _make_only_one_call(spec):
|
||||
yield self._adjust_target(spec)
|
||||
while True:
|
||||
yield False
|
||||
|
||||
if self._adjust_target_answer_generator is None:
|
||||
self._adjust_target_answer_generator = _make_only_one_call(spec)
|
||||
|
||||
return next(self._adjust_target_answer_generator)
|
||||
|
||||
def _adjust_target(self, spec):
|
||||
"""Assumes that the architecture and the compiler have been
|
||||
set already and checks if the current target microarchitecture
|
||||
is the default and can be optimized by the compiler.
|
||||
|
||||
If not, downgrades the microarchitecture until a suitable one
|
||||
is found. If none can be found raise an error.
|
||||
|
||||
Args:
|
||||
spec: spec to be concretized
|
||||
|
||||
Returns:
|
||||
True if any modification happened, False otherwise
|
||||
"""
|
||||
import archspec.cpu
|
||||
|
||||
# Try to adjust the target only if it is the default
|
||||
# target for this platform
|
||||
current_target = spec.architecture.target
|
||||
current_platform = spack.platforms.by_name(spec.architecture.platform)
|
||||
|
||||
default_target = current_platform.target("default_target")
|
||||
if PackagePrefs.has_preferred_targets(spec.name):
|
||||
default_target = self.target_from_package_preferences(spec)
|
||||
|
||||
if current_target != default_target or (
|
||||
self.abstract_spec
|
||||
and self.abstract_spec.architecture
|
||||
and self.abstract_spec.architecture.concrete
|
||||
):
|
||||
return False
|
||||
|
||||
try:
|
||||
current_target.optimization_flags(spec.compiler)
|
||||
except archspec.cpu.UnsupportedMicroarchitecture:
|
||||
microarchitecture = current_target.microarchitecture
|
||||
for ancestor in microarchitecture.ancestors:
|
||||
candidate = None
|
||||
try:
|
||||
candidate = spack.target.Target(ancestor)
|
||||
candidate.optimization_flags(spec.compiler)
|
||||
except archspec.cpu.UnsupportedMicroarchitecture:
|
||||
continue
|
||||
|
||||
if candidate is not None:
|
||||
msg = (
|
||||
"{0.name}@{0.version} cannot build optimized "
|
||||
'binaries for "{1}". Using best target possible: '
|
||||
'"{2}"'
|
||||
)
|
||||
msg = msg.format(spec.compiler, current_target, candidate)
|
||||
tty.warn(msg)
|
||||
spec.architecture.target = candidate
|
||||
return True
|
||||
else:
|
||||
raise
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -719,19 +81,6 @@ def find_spec(spec, condition, default=None):
|
||||
return default # Nothing matched the condition; return default.
|
||||
|
||||
|
||||
def _compiler_concretization_failure(compiler_spec, arch):
|
||||
# Distinguish between the case that there are compilers for
|
||||
# the arch but not with the given compiler spec and the case that
|
||||
# there are no compilers for the arch at all
|
||||
if not spack.compilers.compilers_for_arch(arch):
|
||||
available_os_targets = set(
|
||||
(c.operating_system, c.target) for c in spack.compilers.all_compilers()
|
||||
)
|
||||
raise NoCompilersForArchError(arch, available_os_targets)
|
||||
else:
|
||||
raise UnavailableCompilerVersionError(compiler_spec, arch)
|
||||
|
||||
|
||||
def concretize_specs_together(*abstract_specs, **kwargs):
|
||||
"""Given a number of specs as input, tries to concretize them together.
|
||||
|
||||
@@ -744,12 +93,6 @@ def concretize_specs_together(*abstract_specs, **kwargs):
|
||||
Returns:
|
||||
List of concretized specs
|
||||
"""
|
||||
if spack.config.get("config:concretizer", "clingo") == "original":
|
||||
return _concretize_specs_together_original(*abstract_specs, **kwargs)
|
||||
return _concretize_specs_together_new(*abstract_specs, **kwargs)
|
||||
|
||||
|
||||
def _concretize_specs_together_new(*abstract_specs, **kwargs):
|
||||
import spack.solver.asp
|
||||
|
||||
allow_deprecated = spack.config.get("config:deprecated", False)
|
||||
@@ -760,51 +103,6 @@ def _concretize_specs_together_new(*abstract_specs, **kwargs):
|
||||
return [s.copy() for s in result.specs]
|
||||
|
||||
|
||||
def _concretize_specs_together_original(*abstract_specs, **kwargs):
|
||||
abstract_specs = [spack.spec.Spec(s) for s in abstract_specs]
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
builder = spack.repo.MockRepositoryBuilder(tmpdir)
|
||||
# Split recursive specs, as it seems the concretizer has issue
|
||||
# respecting conditions on dependents expressed like
|
||||
# depends_on('foo ^bar@1.0'), see issue #11160
|
||||
split_specs = [
|
||||
dep.copy(deps=False) for spec1 in abstract_specs for dep in spec1.traverse(root=True)
|
||||
]
|
||||
builder.add_package(
|
||||
"concretizationroot", dependencies=[(str(x), None, None) for x in split_specs]
|
||||
)
|
||||
|
||||
with spack.repo.use_repositories(builder.root, override=False):
|
||||
# Spec from a helper package that depends on all the abstract_specs
|
||||
concretization_root = spack.spec.Spec("concretizationroot")
|
||||
concretization_root.concretize(tests=kwargs.get("tests", False))
|
||||
# Retrieve the direct dependencies
|
||||
concrete_specs = [concretization_root[spec.name].copy() for spec in abstract_specs]
|
||||
|
||||
return concrete_specs
|
||||
|
||||
|
||||
class NoCompilersForArchError(spack.error.SpackError):
|
||||
def __init__(self, arch, available_os_targets):
|
||||
err_msg = (
|
||||
"No compilers found"
|
||||
" for operating system %s and target %s."
|
||||
"\nIf previous installations have succeeded, the"
|
||||
" operating system may have been updated." % (arch.os, arch.target)
|
||||
)
|
||||
|
||||
available_os_target_strs = list()
|
||||
for operating_system, t in available_os_targets:
|
||||
os_target_str = "%s-%s" % (operating_system, t) if t else operating_system
|
||||
available_os_target_strs.append(os_target_str)
|
||||
err_msg += (
|
||||
"\nCompilers are defined for the following"
|
||||
" operating systems and targets:\n\t" + "\n\t".join(available_os_target_strs)
|
||||
)
|
||||
|
||||
super().__init__(err_msg, "Run 'spack compiler find' to add compilers.")
|
||||
|
||||
|
||||
class UnavailableCompilerVersionError(spack.error.SpackError):
|
||||
"""Raised when there is no available compiler that satisfies a
|
||||
compiler spec."""
|
||||
@@ -820,37 +118,3 @@ def __init__(self, compiler_spec, arch=None):
|
||||
"'spack compilers' to see which compilers are already recognized"
|
||||
" by spack.",
|
||||
)
|
||||
|
||||
|
||||
class NoValidVersionError(spack.error.SpackError):
|
||||
"""Raised when there is no way to have a concrete version for a
|
||||
particular spec."""
|
||||
|
||||
def __init__(self, spec):
|
||||
super().__init__(
|
||||
"There are no valid versions for %s that match '%s'" % (spec.name, spec.versions)
|
||||
)
|
||||
|
||||
|
||||
class InsufficientArchitectureInfoError(spack.error.SpackError):
|
||||
"""Raised when details on architecture cannot be collected from the
|
||||
system"""
|
||||
|
||||
def __init__(self, spec, archs):
|
||||
super().__init__(
|
||||
"Cannot determine necessary architecture information for '%s': %s"
|
||||
% (spec.name, str(archs))
|
||||
)
|
||||
|
||||
|
||||
class NoBuildError(spack.error.SpecError):
|
||||
"""Raised when a package is configured with the buildable option False, but
|
||||
no satisfactory external versions can be found
|
||||
"""
|
||||
|
||||
def __init__(self, spec):
|
||||
msg = (
|
||||
"The spec\n '%s'\n is configured as not buildable, "
|
||||
"and no matching external installs were found"
|
||||
)
|
||||
super().__init__(msg % spec)
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
"dirty": False,
|
||||
"build_jobs": min(16, cpus_available()),
|
||||
"build_stage": "$tempdir/spack-stage",
|
||||
"concretizer": "clingo",
|
||||
"license_dir": spack.paths.default_license_dir,
|
||||
}
|
||||
}
|
||||
@@ -1091,7 +1090,7 @@ def validate(
|
||||
|
||||
|
||||
def read_config_file(
|
||||
filename: str, schema: Optional[YamlConfigDict] = None
|
||||
path: str, schema: Optional[YamlConfigDict] = None
|
||||
) -> Optional[YamlConfigDict]:
|
||||
"""Read a YAML configuration file.
|
||||
|
||||
@@ -1101,21 +1100,9 @@ def read_config_file(
|
||||
# to preserve flexibility in calling convention (don't need to provide
|
||||
# schema when it's not necessary) while allowing us to validate against a
|
||||
# known schema when the top-level key could be incorrect.
|
||||
|
||||
if not os.path.exists(filename):
|
||||
# Ignore nonexistent files.
|
||||
tty.debug(f"Skipping nonexistent config path {filename}", level=3)
|
||||
return None
|
||||
|
||||
elif not os.path.isfile(filename):
|
||||
raise ConfigFileError(f"Invalid configuration. {filename} exists but is not a file.")
|
||||
|
||||
elif not os.access(filename, os.R_OK):
|
||||
raise ConfigFileError(f"Config file is not readable: {filename}")
|
||||
|
||||
try:
|
||||
tty.debug(f"Reading config from file {filename}")
|
||||
with open(filename) as f:
|
||||
with open(path) as f:
|
||||
tty.debug(f"Reading config from file {path}")
|
||||
data = syaml.load_config(f)
|
||||
|
||||
if data:
|
||||
@@ -1126,15 +1113,20 @@ def read_config_file(
|
||||
|
||||
return data
|
||||
|
||||
except StopIteration:
|
||||
raise ConfigFileError(f"Config file is empty or is not a valid YAML dict: {filename}")
|
||||
except FileNotFoundError:
|
||||
# Ignore nonexistent files.
|
||||
tty.debug(f"Skipping nonexistent config path {path}", level=3)
|
||||
return None
|
||||
|
||||
except OSError as e:
|
||||
raise ConfigFileError(f"Path is not a file or is not readable: {path}: {str(e)}") from e
|
||||
|
||||
except StopIteration as e:
|
||||
raise ConfigFileError(f"Config file is empty or is not a valid YAML dict: {path}") from e
|
||||
|
||||
except syaml.SpackYAMLError as e:
|
||||
raise ConfigFileError(str(e)) from e
|
||||
|
||||
except OSError as e:
|
||||
raise ConfigFileError(f"Error reading configuration file {filename}: {str(e)}") from e
|
||||
|
||||
|
||||
def _override(string: str) -> bool:
|
||||
"""Test if a spack YAML string is an override.
|
||||
|
||||
@@ -45,7 +45,9 @@ def __reduce__(self):
|
||||
def restore(
|
||||
spec_str: str, prefix: str, extra_attributes: Optional[Dict[str, str]]
|
||||
) -> "DetectedPackage":
|
||||
spec = spack.spec.Spec.from_detection(spec_str=spec_str, extra_attributes=extra_attributes)
|
||||
spec = spack.spec.Spec.from_detection(
|
||||
spec_str=spec_str, external_path=prefix, extra_attributes=extra_attributes
|
||||
)
|
||||
return DetectedPackage(spec=spec, prefix=prefix)
|
||||
|
||||
|
||||
@@ -136,10 +138,10 @@ def path_to_dict(search_paths: List[str]):
|
||||
# entry overrides later entries
|
||||
for search_path in reversed(search_paths):
|
||||
try:
|
||||
for lib in os.listdir(search_path):
|
||||
lib_path = os.path.join(search_path, lib)
|
||||
if llnl.util.filesystem.is_readable_file(lib_path):
|
||||
path_to_lib[lib_path] = lib
|
||||
with os.scandir(search_path) as entries:
|
||||
path_to_lib.update(
|
||||
{entry.path: entry.name for entry in entries if entry.is_file()}
|
||||
)
|
||||
except OSError as e:
|
||||
msg = f"cannot scan '{search_path}' for external software: {str(e)}"
|
||||
llnl.util.tty.debug(msg)
|
||||
@@ -239,7 +241,7 @@ def update_configuration(
|
||||
external_entries = pkg_config.get("externals", [])
|
||||
assert not isinstance(external_entries, bool), "unexpected value for external entry"
|
||||
|
||||
all_new_specs.extend([spack.spec.Spec(x["spec"]) for x in external_entries])
|
||||
all_new_specs.extend([x.spec for x in new_entries])
|
||||
if buildable is False:
|
||||
pkg_config["buildable"] = False
|
||||
pkg_to_cfg[package_name] = pkg_config
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Dict, List, Optional, Set, Tuple, Type
|
||||
from typing import Dict, Iterable, List, Optional, Set, Tuple, Type
|
||||
|
||||
import llnl.util.filesystem
|
||||
import llnl.util.lang
|
||||
@@ -62,7 +62,7 @@ def common_windows_package_paths(pkg_cls=None) -> List[str]:
|
||||
|
||||
def file_identifier(path):
|
||||
s = os.stat(path)
|
||||
return (s.st_dev, s.st_ino)
|
||||
return s.st_dev, s.st_ino
|
||||
|
||||
|
||||
def executables_in_path(path_hints: List[str]) -> Dict[str, str]:
|
||||
@@ -80,6 +80,8 @@ def executables_in_path(path_hints: List[str]) -> Dict[str, str]:
|
||||
constructed based on the PATH environment variable.
|
||||
"""
|
||||
search_paths = llnl.util.filesystem.search_paths_for_executables(*path_hints)
|
||||
# Make use we don't doubly list /usr/lib and /lib etc
|
||||
search_paths = list(llnl.util.lang.dedupe(search_paths, key=file_identifier))
|
||||
return path_to_dict(search_paths)
|
||||
|
||||
|
||||
@@ -187,7 +189,7 @@ def libraries_in_windows_paths(path_hints: Optional[List[str]] = None) -> Dict[s
|
||||
return path_to_dict(search_paths)
|
||||
|
||||
|
||||
def _group_by_prefix(paths: Set[str]) -> Dict[str, Set[str]]:
|
||||
def _group_by_prefix(paths: List[str]) -> Dict[str, Set[str]]:
|
||||
groups = collections.defaultdict(set)
|
||||
for p in paths:
|
||||
groups[os.path.dirname(p)].add(p)
|
||||
@@ -243,7 +245,9 @@ def detect_specs(
|
||||
return []
|
||||
|
||||
result = []
|
||||
for candidate_path, items_in_prefix in sorted(_group_by_prefix(set(paths)).items()):
|
||||
for candidate_path, items_in_prefix in _group_by_prefix(
|
||||
llnl.util.lang.dedupe(paths)
|
||||
).items():
|
||||
# TODO: multiple instances of a package can live in the same
|
||||
# prefix, and a package implementation can return multiple specs
|
||||
# for one prefix, but without additional details (e.g. about the
|
||||
@@ -299,19 +303,17 @@ def detect_specs(
|
||||
return result
|
||||
|
||||
def find(
|
||||
self, *, pkg_name: str, initial_guess: Optional[List[str]] = None
|
||||
self, *, pkg_name: str, repository, initial_guess: Optional[List[str]] = None
|
||||
) -> List[DetectedPackage]:
|
||||
"""For a given package, returns a list of detected specs.
|
||||
|
||||
Args:
|
||||
pkg_name: package being detected
|
||||
initial_guess: initial list of paths to search from the caller
|
||||
if None, default paths are searched. If this
|
||||
is an empty list, nothing will be searched.
|
||||
repository: repository to retrieve the package
|
||||
initial_guess: initial list of paths to search from the caller if None, default paths
|
||||
are searched. If this is an empty list, nothing will be searched.
|
||||
"""
|
||||
import spack.repo
|
||||
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||
pkg_cls = repository.get_pkg_class(pkg_name)
|
||||
patterns = self.search_patterns(pkg=pkg_cls)
|
||||
if not patterns:
|
||||
return []
|
||||
@@ -335,13 +337,10 @@ def search_patterns(self, *, pkg: Type["spack.package_base.PackageBase"]) -> Lis
|
||||
|
||||
def candidate_files(self, *, patterns: List[str], paths: List[str]) -> List[str]:
|
||||
executables_by_path = executables_in_path(path_hints=paths)
|
||||
patterns = [re.compile(x) for x in patterns]
|
||||
result = []
|
||||
for compiled_re in patterns:
|
||||
for path, exe in executables_by_path.items():
|
||||
if compiled_re.search(exe):
|
||||
result.append(path)
|
||||
return list(sorted(set(result)))
|
||||
joined_pattern = re.compile(r"|".join(patterns))
|
||||
result = [path for path, exe in executables_by_path.items() if joined_pattern.search(exe)]
|
||||
result.sort()
|
||||
return result
|
||||
|
||||
def prefix_from_path(self, *, path: str) -> str:
|
||||
result = executable_prefix(path)
|
||||
@@ -385,7 +384,7 @@ def prefix_from_path(self, *, path: str) -> str:
|
||||
|
||||
|
||||
def by_path(
|
||||
packages_to_search: List[str],
|
||||
packages_to_search: Iterable[str],
|
||||
*,
|
||||
path_hints: Optional[List[str]] = None,
|
||||
max_workers: Optional[int] = None,
|
||||
@@ -399,19 +398,28 @@ def by_path(
|
||||
path_hints: initial list of paths to be searched
|
||||
max_workers: maximum number of workers to search for packages in parallel
|
||||
"""
|
||||
import spack.repo
|
||||
|
||||
# TODO: Packages should be able to define both .libraries and .executables in the future
|
||||
# TODO: determine_spec_details should get all relevant libraries and executables in one call
|
||||
executables_finder, libraries_finder = ExecutablesFinder(), LibrariesFinder()
|
||||
detected_specs_by_package: Dict[str, Tuple[concurrent.futures.Future, ...]] = {}
|
||||
|
||||
result = collections.defaultdict(list)
|
||||
repository = spack.repo.PATH.ensure_unwrapped()
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
for pkg in packages_to_search:
|
||||
executable_future = executor.submit(
|
||||
executables_finder.find, pkg_name=pkg, initial_guess=path_hints
|
||||
executables_finder.find,
|
||||
pkg_name=pkg,
|
||||
initial_guess=path_hints,
|
||||
repository=repository,
|
||||
)
|
||||
library_future = executor.submit(
|
||||
libraries_finder.find, pkg_name=pkg, initial_guess=path_hints
|
||||
libraries_finder.find,
|
||||
pkg_name=pkg,
|
||||
initial_guess=path_hints,
|
||||
repository=repository,
|
||||
)
|
||||
detected_specs_by_package[pkg] = executable_future, library_future
|
||||
|
||||
|
||||
@@ -104,7 +104,9 @@ def _create_executable_scripts(self, mock_executables: MockExecutables) -> List[
|
||||
@property
|
||||
def expected_specs(self) -> List[spack.spec.Spec]:
|
||||
return [
|
||||
spack.spec.Spec.from_detection(item.spec, extra_attributes=item.extra_attributes)
|
||||
spack.spec.Spec.from_detection(
|
||||
item.spec, external_path=self.tmpdir.name, extra_attributes=item.extra_attributes
|
||||
)
|
||||
for item in self.test.results
|
||||
]
|
||||
|
||||
|
||||
@@ -32,10 +32,9 @@ class OpenMpi(Package):
|
||||
"""
|
||||
import collections
|
||||
import collections.abc
|
||||
import functools
|
||||
import os.path
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Set, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union
|
||||
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty.color
|
||||
@@ -48,6 +47,7 @@ class OpenMpi(Package):
|
||||
import spack.util.crypto
|
||||
import spack.variant
|
||||
from spack.dependency import Dependency
|
||||
from spack.directives_meta import DirectiveError, DirectiveMeta
|
||||
from spack.fetch_strategy import from_kwargs
|
||||
from spack.resource import Resource
|
||||
from spack.version import (
|
||||
@@ -80,22 +80,6 @@ class OpenMpi(Package):
|
||||
"redistribute",
|
||||
]
|
||||
|
||||
#: These are variant names used by Spack internally; packages can't use them
|
||||
reserved_names = [
|
||||
"arch",
|
||||
"architecture",
|
||||
"dev_path",
|
||||
"namespace",
|
||||
"operating_system",
|
||||
"os",
|
||||
"patches",
|
||||
"platform",
|
||||
"target",
|
||||
]
|
||||
|
||||
#: Names of possible directives. This list is mostly populated using the @directive decorator.
|
||||
#: Some directives leverage others and in that case are not automatically added.
|
||||
directive_names = ["build_system"]
|
||||
|
||||
_patch_order_index = 0
|
||||
|
||||
@@ -155,219 +139,6 @@ def _make_when_spec(value: WhenType) -> Optional["spack.spec.Spec"]:
|
||||
return spack.spec.Spec(value)
|
||||
|
||||
|
||||
class DirectiveMeta(type):
|
||||
"""Flushes the directives that were temporarily stored in the staging
|
||||
area into the package.
|
||||
"""
|
||||
|
||||
# Set of all known directives
|
||||
_directive_dict_names: Set[str] = set()
|
||||
_directives_to_be_executed: List[str] = []
|
||||
_when_constraints_from_context: List[str] = []
|
||||
_default_args: List[dict] = []
|
||||
|
||||
def __new__(cls, name, bases, attr_dict):
|
||||
# Initialize the attribute containing the list of directives
|
||||
# to be executed. Here we go reversed because we want to execute
|
||||
# commands:
|
||||
# 1. in the order they were defined
|
||||
# 2. following the MRO
|
||||
attr_dict["_directives_to_be_executed"] = []
|
||||
for base in reversed(bases):
|
||||
try:
|
||||
directive_from_base = base._directives_to_be_executed
|
||||
attr_dict["_directives_to_be_executed"].extend(directive_from_base)
|
||||
except AttributeError:
|
||||
# The base class didn't have the required attribute.
|
||||
# Continue searching
|
||||
pass
|
||||
|
||||
# De-duplicates directives from base classes
|
||||
attr_dict["_directives_to_be_executed"] = [
|
||||
x for x in llnl.util.lang.dedupe(attr_dict["_directives_to_be_executed"])
|
||||
]
|
||||
|
||||
# Move things to be executed from module scope (where they
|
||||
# are collected first) to class scope
|
||||
if DirectiveMeta._directives_to_be_executed:
|
||||
attr_dict["_directives_to_be_executed"].extend(
|
||||
DirectiveMeta._directives_to_be_executed
|
||||
)
|
||||
DirectiveMeta._directives_to_be_executed = []
|
||||
|
||||
return super(DirectiveMeta, cls).__new__(cls, name, bases, attr_dict)
|
||||
|
||||
def __init__(cls, name, bases, attr_dict):
|
||||
# The instance is being initialized: if it is a package we must ensure
|
||||
# that the directives are called to set it up.
|
||||
|
||||
if "spack.pkg" in cls.__module__:
|
||||
# Ensure the presence of the dictionaries associated with the directives.
|
||||
# All dictionaries are defaultdicts that create lists for missing keys.
|
||||
for d in DirectiveMeta._directive_dict_names:
|
||||
setattr(cls, d, {})
|
||||
|
||||
# Lazily execute directives
|
||||
for directive in cls._directives_to_be_executed:
|
||||
directive(cls)
|
||||
|
||||
# Ignore any directives executed *within* top-level
|
||||
# directives by clearing out the queue they're appended to
|
||||
DirectiveMeta._directives_to_be_executed = []
|
||||
|
||||
super(DirectiveMeta, cls).__init__(name, bases, attr_dict)
|
||||
|
||||
@staticmethod
|
||||
def push_to_context(when_spec):
|
||||
"""Add a spec to the context constraints."""
|
||||
DirectiveMeta._when_constraints_from_context.append(when_spec)
|
||||
|
||||
@staticmethod
|
||||
def pop_from_context():
|
||||
"""Pop the last constraint from the context"""
|
||||
return DirectiveMeta._when_constraints_from_context.pop()
|
||||
|
||||
@staticmethod
|
||||
def push_default_args(default_args):
|
||||
"""Push default arguments"""
|
||||
DirectiveMeta._default_args.append(default_args)
|
||||
|
||||
@staticmethod
|
||||
def pop_default_args():
|
||||
"""Pop default arguments"""
|
||||
return DirectiveMeta._default_args.pop()
|
||||
|
||||
@staticmethod
|
||||
def directive(dicts=None):
|
||||
"""Decorator for Spack directives.
|
||||
|
||||
Spack directives allow you to modify a package while it is being
|
||||
defined, e.g. to add version or dependency information. Directives
|
||||
are one of the key pieces of Spack's package "language", which is
|
||||
embedded in python.
|
||||
|
||||
Here's an example directive:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@directive(dicts='versions')
|
||||
version(pkg, ...):
|
||||
...
|
||||
|
||||
This directive allows you write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(Package):
|
||||
version(...)
|
||||
|
||||
The ``@directive`` decorator handles a couple things for you:
|
||||
|
||||
1. Adds the class scope (pkg) as an initial parameter when
|
||||
called, like a class method would. This allows you to modify
|
||||
a package from within a directive, while the package is still
|
||||
being defined.
|
||||
|
||||
2. It automatically adds a dictionary called "versions" to the
|
||||
package so that you can refer to pkg.versions.
|
||||
|
||||
The ``(dicts='versions')`` part ensures that ALL packages in Spack
|
||||
will have a ``versions`` attribute after they're constructed, and
|
||||
that if no directive actually modified it, it will just be an
|
||||
empty dict.
|
||||
|
||||
This is just a modular way to add storage attributes to the
|
||||
Package class, and it's how Spack gets information from the
|
||||
packages to the core.
|
||||
"""
|
||||
global directive_names
|
||||
|
||||
if isinstance(dicts, str):
|
||||
dicts = (dicts,)
|
||||
|
||||
if not isinstance(dicts, collections.abc.Sequence):
|
||||
message = "dicts arg must be list, tuple, or string. Found {0}"
|
||||
raise TypeError(message.format(type(dicts)))
|
||||
|
||||
# Add the dictionary names if not already there
|
||||
DirectiveMeta._directive_dict_names |= set(dicts)
|
||||
|
||||
# This decorator just returns the directive functions
|
||||
def _decorator(decorated_function):
|
||||
directive_names.append(decorated_function.__name__)
|
||||
|
||||
@functools.wraps(decorated_function)
|
||||
def _wrapper(*args, **_kwargs):
|
||||
# First merge default args with kwargs
|
||||
kwargs = dict()
|
||||
for default_args in DirectiveMeta._default_args:
|
||||
kwargs.update(default_args)
|
||||
kwargs.update(_kwargs)
|
||||
|
||||
# Inject when arguments from the context
|
||||
if DirectiveMeta._when_constraints_from_context:
|
||||
# Check that directives not yet supporting the when= argument
|
||||
# are not used inside the context manager
|
||||
if decorated_function.__name__ == "version":
|
||||
msg = (
|
||||
'directive "{0}" cannot be used within a "when"'
|
||||
' context since it does not support a "when=" '
|
||||
"argument"
|
||||
)
|
||||
msg = msg.format(decorated_function.__name__)
|
||||
raise DirectiveError(msg)
|
||||
|
||||
when_constraints = [
|
||||
spack.spec.Spec(x) for x in DirectiveMeta._when_constraints_from_context
|
||||
]
|
||||
if kwargs.get("when"):
|
||||
when_constraints.append(spack.spec.Spec(kwargs["when"]))
|
||||
when_spec = spack.spec.merge_abstract_anonymous_specs(*when_constraints)
|
||||
|
||||
kwargs["when"] = when_spec
|
||||
|
||||
# If any of the arguments are executors returned by a
|
||||
# directive passed as an argument, don't execute them
|
||||
# lazily. Instead, let the called directive handle them.
|
||||
# This allows nested directive calls in packages. The
|
||||
# caller can return the directive if it should be queued.
|
||||
def remove_directives(arg):
|
||||
directives = DirectiveMeta._directives_to_be_executed
|
||||
if isinstance(arg, (list, tuple)):
|
||||
# Descend into args that are lists or tuples
|
||||
for a in arg:
|
||||
remove_directives(a)
|
||||
else:
|
||||
# Remove directives args from the exec queue
|
||||
remove = next((d for d in directives if d is arg), None)
|
||||
if remove is not None:
|
||||
directives.remove(remove)
|
||||
|
||||
# Nasty, but it's the best way I can think of to avoid
|
||||
# side effects if directive results are passed as args
|
||||
remove_directives(args)
|
||||
remove_directives(list(kwargs.values()))
|
||||
|
||||
# A directive returns either something that is callable on a
|
||||
# package or a sequence of them
|
||||
result = decorated_function(*args, **kwargs)
|
||||
|
||||
# ...so if it is not a sequence make it so
|
||||
values = result
|
||||
if not isinstance(values, collections.abc.Sequence):
|
||||
values = (values,)
|
||||
|
||||
DirectiveMeta._directives_to_be_executed.extend(values)
|
||||
|
||||
# wrapped function returns same result as original so
|
||||
# that we can nest directives
|
||||
return result
|
||||
|
||||
return _wrapper
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
SubmoduleCallback = Callable[["spack.package_base.PackageBase"], Union[str, List[str], bool]]
|
||||
directive = DirectiveMeta.directive
|
||||
|
||||
@@ -846,7 +617,7 @@ def format_error(msg, pkg):
|
||||
msg += " @*r{{[{0}, variant '{1}']}}"
|
||||
return llnl.util.tty.color.colorize(msg.format(pkg.name, name))
|
||||
|
||||
if name in reserved_names:
|
||||
if name in spack.variant.reserved_names:
|
||||
|
||||
def _raise_reserved_name(pkg):
|
||||
msg = "The name '%s' is reserved by Spack" % name
|
||||
@@ -1110,10 +881,6 @@ def _execute_languages(pkg: "spack.package_base.PackageBase"):
|
||||
return _execute_languages
|
||||
|
||||
|
||||
class DirectiveError(spack.error.SpackError):
|
||||
"""This is raised when something is wrong with a package directive."""
|
||||
|
||||
|
||||
class DependencyError(DirectiveError):
|
||||
"""This is raised when a dependency specification is invalid."""
|
||||
|
||||
|
||||
234
lib/spack/spack/directives_meta.py
Normal file
234
lib/spack/spack/directives_meta.py
Normal file
@@ -0,0 +1,234 @@
|
||||
# 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 collections.abc
|
||||
import functools
|
||||
from typing import List, Set
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
import spack.error
|
||||
import spack.spec
|
||||
|
||||
#: Names of possible directives. This list is mostly populated using the @directive decorator.
|
||||
#: Some directives leverage others and in that case are not automatically added.
|
||||
directive_names = ["build_system"]
|
||||
|
||||
|
||||
class DirectiveMeta(type):
|
||||
"""Flushes the directives that were temporarily stored in the staging
|
||||
area into the package.
|
||||
"""
|
||||
|
||||
# Set of all known directives
|
||||
_directive_dict_names: Set[str] = set()
|
||||
_directives_to_be_executed: List[str] = []
|
||||
_when_constraints_from_context: List[str] = []
|
||||
_default_args: List[dict] = []
|
||||
|
||||
def __new__(cls, name, bases, attr_dict):
|
||||
# Initialize the attribute containing the list of directives
|
||||
# to be executed. Here we go reversed because we want to execute
|
||||
# commands:
|
||||
# 1. in the order they were defined
|
||||
# 2. following the MRO
|
||||
attr_dict["_directives_to_be_executed"] = []
|
||||
for base in reversed(bases):
|
||||
try:
|
||||
directive_from_base = base._directives_to_be_executed
|
||||
attr_dict["_directives_to_be_executed"].extend(directive_from_base)
|
||||
except AttributeError:
|
||||
# The base class didn't have the required attribute.
|
||||
# Continue searching
|
||||
pass
|
||||
|
||||
# De-duplicates directives from base classes
|
||||
attr_dict["_directives_to_be_executed"] = [
|
||||
x for x in llnl.util.lang.dedupe(attr_dict["_directives_to_be_executed"])
|
||||
]
|
||||
|
||||
# Move things to be executed from module scope (where they
|
||||
# are collected first) to class scope
|
||||
if DirectiveMeta._directives_to_be_executed:
|
||||
attr_dict["_directives_to_be_executed"].extend(
|
||||
DirectiveMeta._directives_to_be_executed
|
||||
)
|
||||
DirectiveMeta._directives_to_be_executed = []
|
||||
|
||||
return super(DirectiveMeta, cls).__new__(cls, name, bases, attr_dict)
|
||||
|
||||
def __init__(cls, name, bases, attr_dict):
|
||||
# The instance is being initialized: if it is a package we must ensure
|
||||
# that the directives are called to set it up.
|
||||
|
||||
if "spack.pkg" in cls.__module__:
|
||||
# Ensure the presence of the dictionaries associated with the directives.
|
||||
# All dictionaries are defaultdicts that create lists for missing keys.
|
||||
for d in DirectiveMeta._directive_dict_names:
|
||||
setattr(cls, d, {})
|
||||
|
||||
# Lazily execute directives
|
||||
for directive in cls._directives_to_be_executed:
|
||||
directive(cls)
|
||||
|
||||
# Ignore any directives executed *within* top-level
|
||||
# directives by clearing out the queue they're appended to
|
||||
DirectiveMeta._directives_to_be_executed = []
|
||||
|
||||
super(DirectiveMeta, cls).__init__(name, bases, attr_dict)
|
||||
|
||||
@staticmethod
|
||||
def push_to_context(when_spec):
|
||||
"""Add a spec to the context constraints."""
|
||||
DirectiveMeta._when_constraints_from_context.append(when_spec)
|
||||
|
||||
@staticmethod
|
||||
def pop_from_context():
|
||||
"""Pop the last constraint from the context"""
|
||||
return DirectiveMeta._when_constraints_from_context.pop()
|
||||
|
||||
@staticmethod
|
||||
def push_default_args(default_args):
|
||||
"""Push default arguments"""
|
||||
DirectiveMeta._default_args.append(default_args)
|
||||
|
||||
@staticmethod
|
||||
def pop_default_args():
|
||||
"""Pop default arguments"""
|
||||
return DirectiveMeta._default_args.pop()
|
||||
|
||||
@staticmethod
|
||||
def directive(dicts=None):
|
||||
"""Decorator for Spack directives.
|
||||
|
||||
Spack directives allow you to modify a package while it is being
|
||||
defined, e.g. to add version or dependency information. Directives
|
||||
are one of the key pieces of Spack's package "language", which is
|
||||
embedded in python.
|
||||
|
||||
Here's an example directive:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@directive(dicts='versions')
|
||||
version(pkg, ...):
|
||||
...
|
||||
|
||||
This directive allows you write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(Package):
|
||||
version(...)
|
||||
|
||||
The ``@directive`` decorator handles a couple things for you:
|
||||
|
||||
1. Adds the class scope (pkg) as an initial parameter when
|
||||
called, like a class method would. This allows you to modify
|
||||
a package from within a directive, while the package is still
|
||||
being defined.
|
||||
|
||||
2. It automatically adds a dictionary called "versions" to the
|
||||
package so that you can refer to pkg.versions.
|
||||
|
||||
The ``(dicts='versions')`` part ensures that ALL packages in Spack
|
||||
will have a ``versions`` attribute after they're constructed, and
|
||||
that if no directive actually modified it, it will just be an
|
||||
empty dict.
|
||||
|
||||
This is just a modular way to add storage attributes to the
|
||||
Package class, and it's how Spack gets information from the
|
||||
packages to the core.
|
||||
"""
|
||||
global directive_names
|
||||
|
||||
if isinstance(dicts, str):
|
||||
dicts = (dicts,)
|
||||
|
||||
if not isinstance(dicts, collections.abc.Sequence):
|
||||
message = "dicts arg must be list, tuple, or string. Found {0}"
|
||||
raise TypeError(message.format(type(dicts)))
|
||||
|
||||
# Add the dictionary names if not already there
|
||||
DirectiveMeta._directive_dict_names |= set(dicts)
|
||||
|
||||
# This decorator just returns the directive functions
|
||||
def _decorator(decorated_function):
|
||||
directive_names.append(decorated_function.__name__)
|
||||
|
||||
@functools.wraps(decorated_function)
|
||||
def _wrapper(*args, **_kwargs):
|
||||
# First merge default args with kwargs
|
||||
kwargs = dict()
|
||||
for default_args in DirectiveMeta._default_args:
|
||||
kwargs.update(default_args)
|
||||
kwargs.update(_kwargs)
|
||||
|
||||
# Inject when arguments from the context
|
||||
if DirectiveMeta._when_constraints_from_context:
|
||||
# Check that directives not yet supporting the when= argument
|
||||
# are not used inside the context manager
|
||||
if decorated_function.__name__ == "version":
|
||||
msg = (
|
||||
'directive "{0}" cannot be used within a "when"'
|
||||
' context since it does not support a "when=" '
|
||||
"argument"
|
||||
)
|
||||
msg = msg.format(decorated_function.__name__)
|
||||
raise DirectiveError(msg)
|
||||
|
||||
when_constraints = [
|
||||
spack.spec.Spec(x) for x in DirectiveMeta._when_constraints_from_context
|
||||
]
|
||||
if kwargs.get("when"):
|
||||
when_constraints.append(spack.spec.Spec(kwargs["when"]))
|
||||
when_spec = spack.spec.merge_abstract_anonymous_specs(*when_constraints)
|
||||
|
||||
kwargs["when"] = when_spec
|
||||
|
||||
# If any of the arguments are executors returned by a
|
||||
# directive passed as an argument, don't execute them
|
||||
# lazily. Instead, let the called directive handle them.
|
||||
# This allows nested directive calls in packages. The
|
||||
# caller can return the directive if it should be queued.
|
||||
def remove_directives(arg):
|
||||
directives = DirectiveMeta._directives_to_be_executed
|
||||
if isinstance(arg, (list, tuple)):
|
||||
# Descend into args that are lists or tuples
|
||||
for a in arg:
|
||||
remove_directives(a)
|
||||
else:
|
||||
# Remove directives args from the exec queue
|
||||
remove = next((d for d in directives if d is arg), None)
|
||||
if remove is not None:
|
||||
directives.remove(remove)
|
||||
|
||||
# Nasty, but it's the best way I can think of to avoid
|
||||
# side effects if directive results are passed as args
|
||||
remove_directives(args)
|
||||
remove_directives(list(kwargs.values()))
|
||||
|
||||
# A directive returns either something that is callable on a
|
||||
# package or a sequence of them
|
||||
result = decorated_function(*args, **kwargs)
|
||||
|
||||
# ...so if it is not a sequence make it so
|
||||
values = result
|
||||
if not isinstance(values, collections.abc.Sequence):
|
||||
values = (values,)
|
||||
|
||||
DirectiveMeta._directives_to_be_executed.extend(values)
|
||||
|
||||
# wrapped function returns same result as original so
|
||||
# that we can nest directives
|
||||
return result
|
||||
|
||||
return _wrapper
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
class DirectiveError(spack.error.SpackError):
|
||||
"""This is raised when something is wrong with a package directive."""
|
||||
@@ -1214,7 +1214,6 @@ def scope_name(self):
|
||||
def include_concrete_envs(self):
|
||||
"""Copy and save the included envs' specs internally"""
|
||||
|
||||
lockfile_meta = None
|
||||
root_hash_seen = set()
|
||||
concrete_hash_seen = set()
|
||||
self.included_concrete_spec_data = {}
|
||||
@@ -1225,37 +1224,26 @@ def include_concrete_envs(self):
|
||||
raise SpackEnvironmentError(f"Unable to find env at {env_path}")
|
||||
|
||||
env = Environment(env_path)
|
||||
|
||||
with open(env.lock_path) as f:
|
||||
lockfile_as_dict = env._read_lockfile(f)
|
||||
|
||||
# Lockfile_meta must match each env and use at least format version 5
|
||||
if lockfile_meta is None:
|
||||
lockfile_meta = lockfile_as_dict["_meta"]
|
||||
elif lockfile_meta != lockfile_as_dict["_meta"]:
|
||||
raise SpackEnvironmentError("All lockfile _meta values must match")
|
||||
elif lockfile_meta["lockfile-version"] < 5:
|
||||
raise SpackEnvironmentError("The lockfile format must be at version 5 or higher")
|
||||
self.included_concrete_spec_data[env_path] = {"roots": [], "concrete_specs": {}}
|
||||
|
||||
# Copy unique root specs from env
|
||||
self.included_concrete_spec_data[env_path] = {"roots": []}
|
||||
for root_dict in lockfile_as_dict["roots"]:
|
||||
for root_dict in env._concrete_roots_dict():
|
||||
if root_dict["hash"] not in root_hash_seen:
|
||||
self.included_concrete_spec_data[env_path]["roots"].append(root_dict)
|
||||
root_hash_seen.add(root_dict["hash"])
|
||||
|
||||
# Copy unique concrete specs from env
|
||||
for concrete_spec in lockfile_as_dict["concrete_specs"]:
|
||||
if concrete_spec not in concrete_hash_seen:
|
||||
self.included_concrete_spec_data[env_path].update(
|
||||
{"concrete_specs": lockfile_as_dict["concrete_specs"]}
|
||||
for dag_hash, spec_details in env._concrete_specs_dict().items():
|
||||
if dag_hash not in concrete_hash_seen:
|
||||
self.included_concrete_spec_data[env_path]["concrete_specs"].update(
|
||||
{dag_hash: spec_details}
|
||||
)
|
||||
concrete_hash_seen.add(concrete_spec)
|
||||
concrete_hash_seen.add(dag_hash)
|
||||
|
||||
if "include_concrete" in lockfile_as_dict.keys():
|
||||
self.included_concrete_spec_data[env_path]["include_concrete"] = lockfile_as_dict[
|
||||
"include_concrete"
|
||||
]
|
||||
# Copy transitive include data
|
||||
transitive = env.included_concrete_spec_data
|
||||
if transitive:
|
||||
self.included_concrete_spec_data[env_path]["include_concrete"] = transitive
|
||||
|
||||
self._read_lockfile_dict(self._to_lockfile_dict())
|
||||
self.write()
|
||||
@@ -1644,9 +1632,8 @@ def _concretize_separately(self, tests=False):
|
||||
i += 1
|
||||
|
||||
# Ensure we don't try to bootstrap clingo in parallel
|
||||
if spack.config.get("config:concretizer", "clingo") == "clingo":
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
spack.bootstrap.ensure_clingo_importable_or_raise()
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
spack.bootstrap.ensure_clingo_importable_or_raise()
|
||||
|
||||
# Ensure all the indexes have been built or updated, since
|
||||
# otherwise the processes in the pool may timeout on waiting
|
||||
@@ -1657,7 +1644,7 @@ def _concretize_separately(self, tests=False):
|
||||
|
||||
# Ensure we have compilers in compilers.yaml to avoid that
|
||||
# processes try to write the config file in parallel
|
||||
_ = spack.compilers.get_compiler_config(spack.config.CONFIG, init_config=True)
|
||||
_ = spack.compilers.all_compilers_config(spack.config.CONFIG)
|
||||
|
||||
# Early return if there is nothing to do
|
||||
if len(args) == 0:
|
||||
@@ -2174,16 +2161,23 @@ def _get_environment_specs(self, recurse_dependencies=True):
|
||||
|
||||
return specs
|
||||
|
||||
def _to_lockfile_dict(self):
|
||||
"""Create a dictionary to store a lockfile for this environment."""
|
||||
def _concrete_specs_dict(self):
|
||||
concrete_specs = {}
|
||||
for s in traverse.traverse_nodes(self.specs_by_hash.values(), key=traverse.by_dag_hash):
|
||||
spec_dict = s.node_dict_with_hashes(hash=ht.dag_hash)
|
||||
# Assumes no legacy formats, since this was just created.
|
||||
spec_dict[ht.dag_hash.name] = s.dag_hash()
|
||||
concrete_specs[s.dag_hash()] = spec_dict
|
||||
return concrete_specs
|
||||
|
||||
def _concrete_roots_dict(self):
|
||||
hash_spec_list = zip(self.concretized_order, self.concretized_user_specs)
|
||||
return [{"hash": h, "spec": str(s)} for h, s in hash_spec_list]
|
||||
|
||||
def _to_lockfile_dict(self):
|
||||
"""Create a dictionary to store a lockfile for this environment."""
|
||||
concrete_specs = self._concrete_specs_dict()
|
||||
root_specs = self._concrete_roots_dict()
|
||||
|
||||
spack_dict = {"version": spack.spack_version}
|
||||
spack_commit = spack.main.get_spack_commit()
|
||||
@@ -2204,7 +2198,7 @@ def _to_lockfile_dict(self):
|
||||
# spack version information
|
||||
"spack": spack_dict,
|
||||
# users specs + hashes are the 'roots' of the environment
|
||||
"roots": [{"hash": h, "spec": str(s)} for h, s in hash_spec_list],
|
||||
"roots": root_specs,
|
||||
# Concrete specs by hash, including dependencies
|
||||
"concrete_specs": concrete_specs,
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
import shutil
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import PurePath
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -53,7 +54,7 @@
|
||||
import spack.version
|
||||
import spack.version.git_ref_lookup
|
||||
from spack.util.compression import decompressor_for
|
||||
from spack.util.executable import CommandNotFoundError, which
|
||||
from spack.util.executable import CommandNotFoundError, Executable, which
|
||||
|
||||
#: List of all fetch strategies, created by FetchStrategy metaclass.
|
||||
all_strategies = []
|
||||
@@ -245,38 +246,30 @@ class URLFetchStrategy(FetchStrategy):
|
||||
|
||||
# these are checksum types. The generic 'checksum' is deprecated for
|
||||
# specific hash names, but we need it for backward compatibility
|
||||
optional_attrs = list(crypto.hashes.keys()) + ["checksum"]
|
||||
optional_attrs = [*crypto.hashes.keys(), "checksum"]
|
||||
|
||||
def __init__(self, url=None, checksum=None, **kwargs):
|
||||
def __init__(self, *, url: str, checksum: Optional[str] = None, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Prefer values in kwargs to the positionals.
|
||||
self.url = kwargs.get("url", url)
|
||||
self.url = url
|
||||
self.mirrors = kwargs.get("mirrors", [])
|
||||
|
||||
# digest can be set as the first argument, or from an explicit
|
||||
# kwarg by the hash name.
|
||||
self.digest = kwargs.get("checksum", checksum)
|
||||
self.digest: Optional[str] = checksum
|
||||
for h in self.optional_attrs:
|
||||
if h in kwargs:
|
||||
self.digest = kwargs[h]
|
||||
|
||||
self.expand_archive = kwargs.get("expand", True)
|
||||
self.extra_options = kwargs.get("fetch_options", {})
|
||||
self._curl = None
|
||||
|
||||
self.extension = kwargs.get("extension", None)
|
||||
|
||||
if not self.url:
|
||||
raise ValueError("URLFetchStrategy requires a url for fetching.")
|
||||
self.expand_archive: bool = kwargs.get("expand", True)
|
||||
self.extra_options: dict = kwargs.get("fetch_options", {})
|
||||
self._curl: Optional[Executable] = None
|
||||
self.extension: Optional[str] = kwargs.get("extension", None)
|
||||
|
||||
@property
|
||||
def curl(self):
|
||||
def curl(self) -> Executable:
|
||||
if not self._curl:
|
||||
try:
|
||||
self._curl = which("curl", required=True)
|
||||
except CommandNotFoundError as exc:
|
||||
tty.error(str(exc))
|
||||
self._curl = web_util.require_curl()
|
||||
return self._curl
|
||||
|
||||
def source_id(self):
|
||||
@@ -297,27 +290,23 @@ def candidate_urls(self):
|
||||
@_needs_stage
|
||||
def fetch(self):
|
||||
if self.archive_file:
|
||||
tty.debug("Already downloaded {0}".format(self.archive_file))
|
||||
tty.debug(f"Already downloaded {self.archive_file}")
|
||||
return
|
||||
|
||||
url = None
|
||||
errors = []
|
||||
errors: List[Exception] = []
|
||||
for url in self.candidate_urls:
|
||||
if not web_util.url_exists(url):
|
||||
tty.debug("URL does not exist: " + url)
|
||||
continue
|
||||
|
||||
try:
|
||||
self._fetch_from_url(url)
|
||||
break
|
||||
except FailedDownloadError as e:
|
||||
errors.append(str(e))
|
||||
|
||||
for msg in errors:
|
||||
tty.debug(msg)
|
||||
errors.extend(e.exceptions)
|
||||
else:
|
||||
raise FailedDownloadError(*errors)
|
||||
|
||||
if not self.archive_file:
|
||||
raise FailedDownloadError(url)
|
||||
raise FailedDownloadError(
|
||||
RuntimeError(f"Missing archive {self.archive_file} after fetching")
|
||||
)
|
||||
|
||||
def _fetch_from_url(self, url):
|
||||
if spack.config.get("config:url_fetch_method") == "curl":
|
||||
@@ -336,27 +325,28 @@ def _check_headers(self, headers):
|
||||
@_needs_stage
|
||||
def _fetch_urllib(self, url):
|
||||
save_file = self.stage.save_filename
|
||||
tty.msg("Fetching {0}".format(url))
|
||||
|
||||
# Run urllib but grab the mime type from the http headers
|
||||
request = urllib.request.Request(url, headers={"User-Agent": web_util.SPACK_USER_AGENT})
|
||||
|
||||
try:
|
||||
url, headers, response = web_util.read_from_url(url)
|
||||
except web_util.SpackWebError as e:
|
||||
response = web_util.urlopen(request)
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
# clean up archive on failure.
|
||||
if self.archive_file:
|
||||
os.remove(self.archive_file)
|
||||
if os.path.lexists(save_file):
|
||||
os.remove(save_file)
|
||||
msg = "urllib failed to fetch with error {0}".format(e)
|
||||
raise FailedDownloadError(url, msg)
|
||||
raise FailedDownloadError(e) from e
|
||||
|
||||
tty.msg(f"Fetching {url}")
|
||||
|
||||
if os.path.lexists(save_file):
|
||||
os.remove(save_file)
|
||||
|
||||
with open(save_file, "wb") as _open_file:
|
||||
shutil.copyfileobj(response, _open_file)
|
||||
with open(save_file, "wb") as f:
|
||||
shutil.copyfileobj(response, f)
|
||||
|
||||
self._check_headers(str(headers))
|
||||
self._check_headers(str(response.headers))
|
||||
|
||||
@_needs_stage
|
||||
def _fetch_curl(self, url):
|
||||
@@ -365,7 +355,7 @@ def _fetch_curl(self, url):
|
||||
if self.stage.save_filename:
|
||||
save_file = self.stage.save_filename
|
||||
partial_file = self.stage.save_filename + ".part"
|
||||
tty.msg("Fetching {0}".format(url))
|
||||
tty.msg(f"Fetching {url}")
|
||||
if partial_file:
|
||||
save_args = [
|
||||
"-C",
|
||||
@@ -405,8 +395,8 @@ def _fetch_curl(self, url):
|
||||
|
||||
try:
|
||||
web_util.check_curl_code(curl.returncode)
|
||||
except spack.error.FetchError as err:
|
||||
raise spack.fetch_strategy.FailedDownloadError(url, str(err))
|
||||
except spack.error.FetchError as e:
|
||||
raise FailedDownloadError(e) from e
|
||||
|
||||
self._check_headers(headers)
|
||||
|
||||
@@ -473,7 +463,7 @@ def check(self):
|
||||
"""Check the downloaded archive against a checksum digest.
|
||||
No-op if this stage checks code out of a repository."""
|
||||
if not self.digest:
|
||||
raise NoDigestError("Attempt to check URLFetchStrategy with no digest.")
|
||||
raise NoDigestError(f"Attempt to check {self.__class__.__name__} with no digest.")
|
||||
|
||||
verify_checksum(self.archive_file, self.digest)
|
||||
|
||||
@@ -484,8 +474,8 @@ def reset(self):
|
||||
"""
|
||||
if not self.archive_file:
|
||||
raise NoArchiveFileError(
|
||||
"Tried to reset URLFetchStrategy before fetching",
|
||||
"Failed on reset() for URL %s" % self.url,
|
||||
f"Tried to reset {self.__class__.__name__} before fetching",
|
||||
f"Failed on reset() for URL{self.url}",
|
||||
)
|
||||
|
||||
# Remove everything but the archive from the stage
|
||||
@@ -498,14 +488,10 @@ def reset(self):
|
||||
self.expand()
|
||||
|
||||
def __repr__(self):
|
||||
url = self.url if self.url else "no url"
|
||||
return "%s<%s>" % (self.__class__.__name__, url)
|
||||
return f"{self.__class__.__name__}<{self.url}>"
|
||||
|
||||
def __str__(self):
|
||||
if self.url:
|
||||
return self.url
|
||||
else:
|
||||
return "[no url]"
|
||||
return self.url
|
||||
|
||||
|
||||
@fetcher
|
||||
@@ -518,7 +504,7 @@ def fetch(self):
|
||||
|
||||
# check whether the cache file exists.
|
||||
if not os.path.isfile(path):
|
||||
raise NoCacheError("No cache of %s" % path)
|
||||
raise NoCacheError(f"No cache of {path}")
|
||||
|
||||
# remove old symlink if one is there.
|
||||
filename = self.stage.save_filename
|
||||
@@ -528,8 +514,8 @@ def fetch(self):
|
||||
# Symlink to local cached archive.
|
||||
symlink(path, filename)
|
||||
|
||||
# Remove link if checksum fails, or subsequent fetchers
|
||||
# will assume they don't need to download.
|
||||
# Remove link if checksum fails, or subsequent fetchers will assume they don't need to
|
||||
# download.
|
||||
if self.digest:
|
||||
try:
|
||||
self.check()
|
||||
@@ -538,12 +524,12 @@ def fetch(self):
|
||||
raise
|
||||
|
||||
# Notify the user how we fetched.
|
||||
tty.msg("Using cached archive: {0}".format(path))
|
||||
tty.msg(f"Using cached archive: {path}")
|
||||
|
||||
|
||||
class OCIRegistryFetchStrategy(URLFetchStrategy):
|
||||
def __init__(self, url=None, checksum=None, **kwargs):
|
||||
super().__init__(url, checksum, **kwargs)
|
||||
def __init__(self, *, url: str, checksum: Optional[str] = None, **kwargs):
|
||||
super().__init__(url=url, checksum=checksum, **kwargs)
|
||||
|
||||
self._urlopen = kwargs.get("_urlopen", spack.oci.opener.urlopen)
|
||||
|
||||
@@ -554,13 +540,13 @@ def fetch(self):
|
||||
|
||||
try:
|
||||
response = self._urlopen(self.url)
|
||||
except urllib.error.URLError as e:
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
# clean up archive on failure.
|
||||
if self.archive_file:
|
||||
os.remove(self.archive_file)
|
||||
if os.path.lexists(file):
|
||||
os.remove(file)
|
||||
raise FailedDownloadError(self.url, f"Failed to fetch {self.url}: {e}") from e
|
||||
raise FailedDownloadError(e) from e
|
||||
|
||||
if os.path.lexists(file):
|
||||
os.remove(file)
|
||||
@@ -588,18 +574,18 @@ def __init__(self, **kwargs):
|
||||
# Set a URL based on the type of fetch strategy.
|
||||
self.url = kwargs.get(self.url_attr, None)
|
||||
if not self.url:
|
||||
raise ValueError("%s requires %s argument." % (self.__class__, self.url_attr))
|
||||
raise ValueError(f"{self.__class__} requires {self.url_attr} argument.")
|
||||
|
||||
for attr in self.optional_attrs:
|
||||
setattr(self, attr, kwargs.get(attr, None))
|
||||
|
||||
@_needs_stage
|
||||
def check(self):
|
||||
tty.debug("No checksum needed when fetching with {0}".format(self.url_attr))
|
||||
tty.debug(f"No checksum needed when fetching with {self.url_attr}")
|
||||
|
||||
@_needs_stage
|
||||
def expand(self):
|
||||
tty.debug("Source fetched with %s is already expanded." % self.url_attr)
|
||||
tty.debug(f"Source fetched with {self.url_attr} is already expanded.")
|
||||
|
||||
@_needs_stage
|
||||
def archive(self, destination, *, exclude: Optional[str] = None):
|
||||
@@ -619,10 +605,10 @@ def archive(self, destination, *, exclude: Optional[str] = None):
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "VCS: %s" % self.url
|
||||
return f"VCS: {self.url}"
|
||||
|
||||
def __repr__(self):
|
||||
return "%s<%s>" % (self.__class__, self.url)
|
||||
return f"{self.__class__}<{self.url}>"
|
||||
|
||||
|
||||
@fetcher
|
||||
@@ -725,11 +711,17 @@ class GitFetchStrategy(VCSFetchStrategy):
|
||||
"submodules",
|
||||
"get_full_repo",
|
||||
"submodules_delete",
|
||||
"git_sparse_paths",
|
||||
]
|
||||
|
||||
git_version_re = r"git version (\S+)"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
self.commit: Optional[str] = None
|
||||
self.tag: Optional[str] = None
|
||||
self.branch: Optional[str] = None
|
||||
|
||||
# Discards the keywords in kwargs that may conflict with the next call
|
||||
# to __init__
|
||||
forwarded_args = copy.copy(kwargs)
|
||||
@@ -740,6 +732,7 @@ def __init__(self, **kwargs):
|
||||
self.submodules = kwargs.get("submodules", False)
|
||||
self.submodules_delete = kwargs.get("submodules_delete", False)
|
||||
self.get_full_repo = kwargs.get("get_full_repo", False)
|
||||
self.git_sparse_paths = kwargs.get("git_sparse_paths", None)
|
||||
|
||||
@property
|
||||
def git_version(self):
|
||||
@@ -777,68 +770,71 @@ def git(self):
|
||||
|
||||
@property
|
||||
def cachable(self):
|
||||
return self.cache_enabled and bool(self.commit or self.tag)
|
||||
return self.cache_enabled and bool(self.commit)
|
||||
|
||||
def source_id(self):
|
||||
return self.commit or self.tag
|
||||
# TODO: tree-hash would secure download cache and mirrors, commit only secures checkouts.
|
||||
return self.commit
|
||||
|
||||
def mirror_id(self):
|
||||
repo_ref = self.commit or self.tag or self.branch
|
||||
if repo_ref:
|
||||
if self.commit:
|
||||
repo_path = urllib.parse.urlparse(self.url).path
|
||||
result = os.path.sep.join(["git", repo_path, repo_ref])
|
||||
result = os.path.sep.join(["git", repo_path, self.commit])
|
||||
return result
|
||||
|
||||
def _repo_info(self):
|
||||
args = ""
|
||||
|
||||
if self.commit:
|
||||
args = " at commit {0}".format(self.commit)
|
||||
args = f" at commit {self.commit}"
|
||||
elif self.tag:
|
||||
args = " at tag {0}".format(self.tag)
|
||||
args = f" at tag {self.tag}"
|
||||
elif self.branch:
|
||||
args = " on branch {0}".format(self.branch)
|
||||
args = f" on branch {self.branch}"
|
||||
|
||||
return "{0}{1}".format(self.url, args)
|
||||
return f"{self.url}{args}"
|
||||
|
||||
@_needs_stage
|
||||
def fetch(self):
|
||||
if self.stage.expanded:
|
||||
tty.debug("Already fetched {0}".format(self.stage.source_path))
|
||||
tty.debug(f"Already fetched {self.stage.source_path}")
|
||||
return
|
||||
|
||||
self.clone(commit=self.commit, branch=self.branch, tag=self.tag)
|
||||
if self.git_sparse_paths:
|
||||
self._sparse_clone_src()
|
||||
else:
|
||||
self._clone_src()
|
||||
self.submodule_operations()
|
||||
|
||||
def clone(self, dest=None, commit=None, branch=None, tag=None, bare=False):
|
||||
def bare_clone(self, dest: str) -> None:
|
||||
"""
|
||||
Clone a repository to a path.
|
||||
Execute a bare clone for metadata only
|
||||
|
||||
This method handles cloning from git, but does not require a stage.
|
||||
|
||||
Arguments:
|
||||
dest (str or None): The path into which the code is cloned. If None,
|
||||
requires a stage and uses the stage's source path.
|
||||
commit (str or None): A commit to fetch from the remote. Only one of
|
||||
commit, branch, and tag may be non-None.
|
||||
branch (str or None): A branch to fetch from the remote.
|
||||
tag (str or None): A tag to fetch from the remote.
|
||||
bare (bool): Execute a "bare" git clone (--bare option to git)
|
||||
Requires a destination since bare cloning does not provide source
|
||||
and shouldn't be used for staging.
|
||||
"""
|
||||
# Default to spack source path
|
||||
dest = dest or self.stage.source_path
|
||||
tty.debug("Cloning git repository: {0}".format(self._repo_info()))
|
||||
tty.debug(f"Cloning git repository: {self._repo_info()}")
|
||||
|
||||
git = self.git
|
||||
debug = spack.config.get("config:debug")
|
||||
|
||||
if bare:
|
||||
# We don't need to worry about which commit/branch/tag is checked out
|
||||
clone_args = ["clone", "--bare"]
|
||||
if not debug:
|
||||
clone_args.append("--quiet")
|
||||
clone_args.extend([self.url, dest])
|
||||
git(*clone_args)
|
||||
elif commit:
|
||||
# We don't need to worry about which commit/branch/tag is checked out
|
||||
clone_args = ["clone", "--bare"]
|
||||
if not debug:
|
||||
clone_args.append("--quiet")
|
||||
clone_args.extend([self.url, dest])
|
||||
git(*clone_args)
|
||||
|
||||
def _clone_src(self) -> None:
|
||||
"""Clone a repository to a path using git."""
|
||||
# Default to spack source path
|
||||
dest = self.stage.source_path
|
||||
tty.debug(f"Cloning git repository: {self._repo_info()}")
|
||||
|
||||
git = self.git
|
||||
debug = spack.config.get("config:debug")
|
||||
|
||||
if self.commit:
|
||||
# Need to do a regular clone and check out everything if
|
||||
# they asked for a particular commit.
|
||||
clone_args = ["clone", self.url]
|
||||
@@ -857,7 +853,7 @@ def clone(self, dest=None, commit=None, branch=None, tag=None, bare=False):
|
||||
)
|
||||
|
||||
with working_dir(dest):
|
||||
checkout_args = ["checkout", commit]
|
||||
checkout_args = ["checkout", self.commit]
|
||||
if not debug:
|
||||
checkout_args.insert(1, "--quiet")
|
||||
git(*checkout_args)
|
||||
@@ -869,10 +865,10 @@ def clone(self, dest=None, commit=None, branch=None, tag=None, bare=False):
|
||||
args.append("--quiet")
|
||||
|
||||
# If we want a particular branch ask for it.
|
||||
if branch:
|
||||
args.extend(["--branch", branch])
|
||||
elif tag and self.git_version >= spack.version.Version("1.8.5.2"):
|
||||
args.extend(["--branch", tag])
|
||||
if self.branch:
|
||||
args.extend(["--branch", self.branch])
|
||||
elif self.tag and self.git_version >= spack.version.Version("1.8.5.2"):
|
||||
args.extend(["--branch", self.tag])
|
||||
|
||||
# Try to be efficient if we're using a new enough git.
|
||||
# This checks out only one branch's history
|
||||
@@ -904,7 +900,7 @@ def clone(self, dest=None, commit=None, branch=None, tag=None, bare=False):
|
||||
# For tags, be conservative and check them out AFTER
|
||||
# cloning. Later git versions can do this with clone
|
||||
# --branch, but older ones fail.
|
||||
if tag and self.git_version < spack.version.Version("1.8.5.2"):
|
||||
if self.tag and self.git_version < spack.version.Version("1.8.5.2"):
|
||||
# pull --tags returns a "special" error code of 1 in
|
||||
# older versions that we have to ignore.
|
||||
# see: https://github.com/git/git/commit/19d122b
|
||||
@@ -917,6 +913,79 @@ def clone(self, dest=None, commit=None, branch=None, tag=None, bare=False):
|
||||
git(*pull_args, ignore_errors=1)
|
||||
git(*co_args)
|
||||
|
||||
def _sparse_clone_src(self, **kwargs):
|
||||
"""Use git's sparse checkout feature to clone portions of a git repository"""
|
||||
dest = self.stage.source_path
|
||||
git = self.git
|
||||
|
||||
if self.git_version < spack.version.Version("2.26.0"):
|
||||
# technically this should be supported for 2.25, but bumping for OS issues
|
||||
# see https://github.com/spack/spack/issues/45771
|
||||
# code paths exist where the package is not set. Assure some indentifier for the
|
||||
# package that was configured for sparse checkout exists in the error message
|
||||
identifier = str(self.url)
|
||||
if self.package:
|
||||
identifier += f" ({self.package.name})"
|
||||
tty.warn(
|
||||
(
|
||||
f"{identifier} is configured for git sparse-checkout "
|
||||
"but the git version is too old to support sparse cloning. "
|
||||
"Cloning the full repository instead."
|
||||
)
|
||||
)
|
||||
self._clone_src()
|
||||
else:
|
||||
# default to depth=2 to allow for retention of some git properties
|
||||
depth = kwargs.get("depth", 2)
|
||||
needs_fetch = self.branch or self.tag
|
||||
git_ref = self.branch or self.tag or self.commit
|
||||
|
||||
assert git_ref
|
||||
|
||||
clone_args = ["clone"]
|
||||
|
||||
if needs_fetch:
|
||||
clone_args.extend(["--branch", git_ref])
|
||||
|
||||
if self.get_full_repo:
|
||||
clone_args.append("--no-single-branch")
|
||||
else:
|
||||
clone_args.append("--single-branch")
|
||||
|
||||
clone_args.extend(
|
||||
[f"--depth={depth}", "--no-checkout", "--filter=blob:none", self.url]
|
||||
)
|
||||
|
||||
sparse_args = ["sparse-checkout", "set"]
|
||||
|
||||
if callable(self.git_sparse_paths):
|
||||
sparse_args.extend(self.git_sparse_paths())
|
||||
else:
|
||||
sparse_args.extend([p for p in self.git_sparse_paths])
|
||||
|
||||
sparse_args.append("--cone")
|
||||
|
||||
checkout_args = ["checkout", git_ref]
|
||||
|
||||
if not spack.config.get("config:debug"):
|
||||
clone_args.insert(1, "--quiet")
|
||||
checkout_args.insert(1, "--quiet")
|
||||
|
||||
with temp_cwd():
|
||||
git(*clone_args)
|
||||
repo_name = get_single_file(".")
|
||||
if self.stage:
|
||||
self.stage.srcdir = repo_name
|
||||
shutil.move(repo_name, dest)
|
||||
|
||||
with working_dir(dest):
|
||||
git(*sparse_args)
|
||||
git(*checkout_args)
|
||||
|
||||
def submodule_operations(self):
|
||||
dest = self.stage.source_path
|
||||
git = self.git
|
||||
|
||||
if self.submodules_delete:
|
||||
with working_dir(dest):
|
||||
for submodule_to_delete in self.submodules_delete:
|
||||
@@ -969,7 +1038,7 @@ def protocol_supports_shallow_clone(self):
|
||||
return not (self.url.startswith("http://") or self.url.startswith("/"))
|
||||
|
||||
def __str__(self):
|
||||
return "[git] {0}".format(self._repo_info())
|
||||
return f"[git] {self._repo_info()}"
|
||||
|
||||
|
||||
@fetcher
|
||||
@@ -1293,7 +1362,7 @@ def reset(self):
|
||||
shutil.move(scrubbed, source_path)
|
||||
|
||||
def __str__(self):
|
||||
return "[hg] %s" % self.url
|
||||
return f"[hg] {self.url}"
|
||||
|
||||
|
||||
@fetcher
|
||||
@@ -1302,45 +1371,20 @@ class S3FetchStrategy(URLFetchStrategy):
|
||||
|
||||
url_attr = "s3"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
super().__init__(*args, **kwargs)
|
||||
except ValueError:
|
||||
if not kwargs.get("url"):
|
||||
raise ValueError("S3FetchStrategy requires a url for fetching.")
|
||||
|
||||
@_needs_stage
|
||||
def fetch(self):
|
||||
if self.archive_file:
|
||||
tty.debug("Already downloaded {0}".format(self.archive_file))
|
||||
return
|
||||
|
||||
parsed_url = urllib.parse.urlparse(self.url)
|
||||
if parsed_url.scheme != "s3":
|
||||
raise spack.error.FetchError("S3FetchStrategy can only fetch from s3:// urls.")
|
||||
|
||||
tty.debug("Fetching {0}".format(self.url))
|
||||
|
||||
basename = os.path.basename(parsed_url.path)
|
||||
|
||||
with working_dir(self.stage.path):
|
||||
_, headers, stream = web_util.read_from_url(self.url)
|
||||
|
||||
with open(basename, "wb") as f:
|
||||
shutil.copyfileobj(stream, f)
|
||||
|
||||
content_type = web_util.get_header(headers, "Content-type")
|
||||
|
||||
if content_type == "text/html":
|
||||
warn_content_type_mismatch(self.archive_file or "the archive")
|
||||
|
||||
if self.stage.save_filename:
|
||||
llnl.util.filesystem.rename(
|
||||
os.path.join(self.stage.path, basename), self.stage.save_filename
|
||||
if not self.url.startswith("s3://"):
|
||||
raise spack.error.FetchError(
|
||||
f"{self.__class__.__name__} can only fetch from s3:// urls."
|
||||
)
|
||||
|
||||
if self.archive_file:
|
||||
tty.debug(f"Already downloaded {self.archive_file}")
|
||||
return
|
||||
self._fetch_urllib(self.url)
|
||||
if not self.archive_file:
|
||||
raise FailedDownloadError(self.url)
|
||||
raise FailedDownloadError(
|
||||
RuntimeError(f"Missing archive {self.archive_file} after fetching")
|
||||
)
|
||||
|
||||
|
||||
@fetcher
|
||||
@@ -1349,43 +1393,22 @@ class GCSFetchStrategy(URLFetchStrategy):
|
||||
|
||||
url_attr = "gs"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
super().__init__(*args, **kwargs)
|
||||
except ValueError:
|
||||
if not kwargs.get("url"):
|
||||
raise ValueError("GCSFetchStrategy requires a url for fetching.")
|
||||
|
||||
@_needs_stage
|
||||
def fetch(self):
|
||||
if not self.url.startswith("gs"):
|
||||
raise spack.error.FetchError(
|
||||
f"{self.__class__.__name__} can only fetch from gs:// urls."
|
||||
)
|
||||
if self.archive_file:
|
||||
tty.debug("Already downloaded {0}".format(self.archive_file))
|
||||
tty.debug(f"Already downloaded {self.archive_file}")
|
||||
return
|
||||
|
||||
parsed_url = urllib.parse.urlparse(self.url)
|
||||
if parsed_url.scheme != "gs":
|
||||
raise spack.error.FetchError("GCSFetchStrategy can only fetch from gs:// urls.")
|
||||
|
||||
tty.debug("Fetching {0}".format(self.url))
|
||||
|
||||
basename = os.path.basename(parsed_url.path)
|
||||
|
||||
with working_dir(self.stage.path):
|
||||
_, headers, stream = web_util.read_from_url(self.url)
|
||||
|
||||
with open(basename, "wb") as f:
|
||||
shutil.copyfileobj(stream, f)
|
||||
|
||||
content_type = web_util.get_header(headers, "Content-type")
|
||||
|
||||
if content_type == "text/html":
|
||||
warn_content_type_mismatch(self.archive_file or "the archive")
|
||||
|
||||
if self.stage.save_filename:
|
||||
os.rename(os.path.join(self.stage.path, basename), self.stage.save_filename)
|
||||
self._fetch_urllib(self.url)
|
||||
|
||||
if not self.archive_file:
|
||||
raise FailedDownloadError(self.url)
|
||||
raise FailedDownloadError(
|
||||
RuntimeError(f"Missing archive {self.archive_file} after fetching")
|
||||
)
|
||||
|
||||
|
||||
@fetcher
|
||||
@@ -1394,7 +1417,7 @@ class FetchAndVerifyExpandedFile(URLFetchStrategy):
|
||||
as well as after expanding it."""
|
||||
|
||||
def __init__(self, url, archive_sha256: str, expanded_sha256: str):
|
||||
super().__init__(url, archive_sha256)
|
||||
super().__init__(url=url, checksum=archive_sha256)
|
||||
self.expanded_sha256 = expanded_sha256
|
||||
|
||||
def expand(self):
|
||||
@@ -1436,14 +1459,14 @@ def stable_target(fetcher):
|
||||
return False
|
||||
|
||||
|
||||
def from_url(url):
|
||||
def from_url(url: str) -> URLFetchStrategy:
|
||||
"""Given a URL, find an appropriate fetch strategy for it.
|
||||
Currently just gives you a URLFetchStrategy that uses curl.
|
||||
|
||||
TODO: make this return appropriate fetch strategies for other
|
||||
types of URLs.
|
||||
"""
|
||||
return URLFetchStrategy(url)
|
||||
return URLFetchStrategy(url=url)
|
||||
|
||||
|
||||
def from_kwargs(**kwargs):
|
||||
@@ -1512,10 +1535,12 @@ def _check_version_attributes(fetcher, pkg, version):
|
||||
def _extrapolate(pkg, version):
|
||||
"""Create a fetcher from an extrapolated URL for this version."""
|
||||
try:
|
||||
return URLFetchStrategy(pkg.url_for_version(version), fetch_options=pkg.fetch_options)
|
||||
return URLFetchStrategy(url=pkg.url_for_version(version), fetch_options=pkg.fetch_options)
|
||||
except spack.package_base.NoURLError:
|
||||
msg = "Can't extrapolate a URL for version %s " "because package %s defines no URLs"
|
||||
raise ExtrapolationError(msg % (version, pkg.name))
|
||||
raise ExtrapolationError(
|
||||
f"Can't extrapolate a URL for version {version} because "
|
||||
f"package {pkg.name} defines no URLs"
|
||||
)
|
||||
|
||||
|
||||
def _from_merged_attrs(fetcher, pkg, version):
|
||||
@@ -1532,8 +1557,11 @@ def _from_merged_attrs(fetcher, pkg, version):
|
||||
attrs["fetch_options"] = pkg.fetch_options
|
||||
attrs.update(pkg.versions[version])
|
||||
|
||||
if fetcher.url_attr == "git" and hasattr(pkg, "submodules"):
|
||||
attrs.setdefault("submodules", pkg.submodules)
|
||||
if fetcher.url_attr == "git":
|
||||
pkg_attr_list = ["submodules", "git_sparse_paths"]
|
||||
for pkg_attr in pkg_attr_list:
|
||||
if hasattr(pkg, pkg_attr):
|
||||
attrs.setdefault(pkg_attr, getattr(pkg, pkg_attr))
|
||||
|
||||
return fetcher(**attrs)
|
||||
|
||||
@@ -1628,11 +1656,9 @@ def for_package_version(pkg, version=None):
|
||||
raise InvalidArgsError(pkg, version, **args)
|
||||
|
||||
|
||||
def from_url_scheme(url, *args, **kwargs):
|
||||
def from_url_scheme(url: str, **kwargs) -> FetchStrategy:
|
||||
"""Finds a suitable FetchStrategy by matching its url_attr with the scheme
|
||||
in the given url."""
|
||||
|
||||
url = kwargs.get("url", url)
|
||||
parsed_url = urllib.parse.urlparse(url, scheme="file")
|
||||
|
||||
scheme_mapping = kwargs.get("scheme_mapping") or {
|
||||
@@ -1649,11 +1675,9 @@ def from_url_scheme(url, *args, **kwargs):
|
||||
for fetcher in all_strategies:
|
||||
url_attr = getattr(fetcher, "url_attr", None)
|
||||
if url_attr and url_attr == scheme:
|
||||
return fetcher(url, *args, **kwargs)
|
||||
return fetcher(url=url, **kwargs)
|
||||
|
||||
raise ValueError(
|
||||
'No FetchStrategy found for url with scheme: "{SCHEME}"'.format(SCHEME=parsed_url.scheme)
|
||||
)
|
||||
raise ValueError(f'No FetchStrategy found for url with scheme: "{parsed_url.scheme}"')
|
||||
|
||||
|
||||
def from_list_url(pkg):
|
||||
@@ -1678,7 +1702,9 @@ def from_list_url(pkg):
|
||||
)
|
||||
|
||||
# construct a fetcher
|
||||
return URLFetchStrategy(url_from_list, checksum, fetch_options=pkg.fetch_options)
|
||||
return URLFetchStrategy(
|
||||
url=url_from_list, checksum=checksum, fetch_options=pkg.fetch_options
|
||||
)
|
||||
except KeyError as e:
|
||||
tty.debug(e)
|
||||
tty.msg("Cannot find version %s in url_list" % pkg.version)
|
||||
@@ -1706,10 +1732,10 @@ def store(self, fetcher, relative_dest):
|
||||
mkdirp(os.path.dirname(dst))
|
||||
fetcher.archive(dst)
|
||||
|
||||
def fetcher(self, target_path, digest, **kwargs):
|
||||
def fetcher(self, target_path: str, digest: Optional[str], **kwargs) -> CacheURLFetchStrategy:
|
||||
path = os.path.join(self.root, target_path)
|
||||
url = url_util.path_to_file_url(path)
|
||||
return CacheURLFetchStrategy(url, digest, **kwargs)
|
||||
return CacheURLFetchStrategy(url=url, checksum=digest, **kwargs)
|
||||
|
||||
def destroy(self):
|
||||
shutil.rmtree(self.root, ignore_errors=True)
|
||||
@@ -1722,9 +1748,9 @@ class NoCacheError(spack.error.FetchError):
|
||||
class FailedDownloadError(spack.error.FetchError):
|
||||
"""Raised when a download fails."""
|
||||
|
||||
def __init__(self, url, msg=""):
|
||||
super().__init__("Failed to fetch file from URL: %s" % url, msg)
|
||||
self.url = url
|
||||
def __init__(self, *exceptions: Exception):
|
||||
super().__init__("Failed to download")
|
||||
self.exceptions = exceptions
|
||||
|
||||
|
||||
class NoArchiveFileError(spack.error.FetchError):
|
||||
|
||||
@@ -37,6 +37,12 @@ def __call__(self, spec):
|
||||
"""Run this hash on the provided spec."""
|
||||
return spec.spec_hash(self)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"SpecHashDescriptor(depflag={self.depflag!r}, "
|
||||
f"package_hash={self.package_hash!r}, name={self.name!r}, override={self.override!r})"
|
||||
)
|
||||
|
||||
|
||||
#: Spack's deployment hash. Includes all inputs that can affect how a package is built.
|
||||
dag_hash = SpecHashDescriptor(depflag=dt.BUILD | dt.LINK | dt.RUN, package_hash=True, name="hash")
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
systems (e.g. modules, lmod, etc.) or to add other custom
|
||||
features.
|
||||
"""
|
||||
import importlib
|
||||
|
||||
from llnl.util.lang import ensure_last, list_modules
|
||||
|
||||
@@ -46,11 +47,7 @@ def _populate_hooks(cls):
|
||||
|
||||
for name in relative_names:
|
||||
module_name = __name__ + "." + name
|
||||
# When importing a module from a package, __import__('A.B', ...)
|
||||
# returns package A when 'fromlist' is empty. If fromlist is not
|
||||
# empty it returns the submodule B instead
|
||||
# See: https://stackoverflow.com/a/2725668/771663
|
||||
module_obj = __import__(module_name, fromlist=[None])
|
||||
module_obj = importlib.import_module(module_name)
|
||||
cls._hooks.append((module_name, module_obj))
|
||||
|
||||
@property
|
||||
|
||||
@@ -23,9 +23,7 @@ def post_install(spec, explicit):
|
||||
|
||||
# Push the package to all autopush mirrors
|
||||
for mirror in spack.mirror.MirrorCollection(binary=True, autopush=True).values():
|
||||
bindist.push_or_raise(
|
||||
spec,
|
||||
mirror.push_url,
|
||||
bindist.PushOptions(force=True, regenerate_index=False, unsigned=not mirror.signed),
|
||||
)
|
||||
signing_key = bindist.select_signing_key() if mirror.signed else None
|
||||
with bindist.make_uploader(mirror=mirror, force=True, signing_key=signing_key) as uploader:
|
||||
uploader.push_or_raise([spec])
|
||||
tty.msg(f"{spec.name}: Pushed to build cache: '{mirror.name}'")
|
||||
|
||||
@@ -757,6 +757,10 @@ def test_process(pkg: Pb, kwargs):
|
||||
pkg.tester.status(pkg.spec.name, TestStatus.SKIPPED)
|
||||
return
|
||||
|
||||
# Make sure properly named build-time test methods actually run as
|
||||
# stand-alone tests.
|
||||
pkg.run_tests = True
|
||||
|
||||
# run test methods from the package and all virtuals it provides
|
||||
v_names = virtuals(pkg)
|
||||
test_specs = [pkg.spec] + [spack.spec.Spec(v_name) for v_name in sorted(v_names)]
|
||||
|
||||
@@ -1611,9 +1611,7 @@ def _add_tasks(self, request: BuildRequest, all_deps):
|
||||
|
||||
def _add_compiler_package_to_config(self, pkg: "spack.package_base.PackageBase") -> None:
|
||||
compiler_search_prefix = getattr(pkg, "compiler_search_prefix", pkg.spec.prefix)
|
||||
spack.compilers.add_compilers_to_config(
|
||||
spack.compilers.find_compilers([compiler_search_prefix])
|
||||
)
|
||||
spack.compilers.find_compilers([compiler_search_prefix])
|
||||
|
||||
def _install_task(self, task: BuildTask, install_status: InstallStatus) -> None:
|
||||
"""
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import llnl.url
|
||||
import llnl.util.symlink
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import mkdirp
|
||||
|
||||
@@ -30,6 +31,7 @@
|
||||
import spack.fetch_strategy
|
||||
import spack.mirror
|
||||
import spack.oci.image
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
import spack.util.path
|
||||
import spack.util.spack_json as sjson
|
||||
@@ -426,51 +428,74 @@ def _determine_extension(fetcher):
|
||||
return ext
|
||||
|
||||
|
||||
class MirrorReference:
|
||||
"""A ``MirrorReference`` stores the relative paths where you can store a
|
||||
package/resource in a mirror directory.
|
||||
class MirrorLayout:
|
||||
"""A ``MirrorLayout`` object describes the relative path of a mirror entry."""
|
||||
|
||||
The appropriate storage location is given by ``storage_path``. The
|
||||
``cosmetic_path`` property provides a reference that a human could generate
|
||||
themselves based on reading the details of the package.
|
||||
|
||||
A user can iterate over a ``MirrorReference`` object to get all the
|
||||
possible names that might be used to refer to the resource in a mirror;
|
||||
this includes names generated by previous naming schemes that are no-longer
|
||||
reported by ``storage_path`` or ``cosmetic_path``.
|
||||
"""
|
||||
|
||||
def __init__(self, cosmetic_path, global_path=None):
|
||||
self.global_path = global_path
|
||||
self.cosmetic_path = cosmetic_path
|
||||
|
||||
@property
|
||||
def storage_path(self):
|
||||
if self.global_path:
|
||||
return self.global_path
|
||||
else:
|
||||
return self.cosmetic_path
|
||||
def __init__(self, path: str) -> None:
|
||||
self.path = path
|
||||
|
||||
def __iter__(self):
|
||||
if self.global_path:
|
||||
yield self.global_path
|
||||
yield self.cosmetic_path
|
||||
"""Yield all paths including aliases where the resource can be found."""
|
||||
yield self.path
|
||||
|
||||
def make_alias(self, root: str) -> None:
|
||||
"""Make the entry ``root / self.path`` available under a human readable alias"""
|
||||
pass
|
||||
|
||||
|
||||
class OCIImageLayout:
|
||||
"""Follow the OCI Image Layout Specification to archive blobs
|
||||
class DefaultLayout(MirrorLayout):
|
||||
def __init__(self, alias_path: str, digest_path: Optional[str] = None) -> None:
|
||||
# When we have a digest, it is used as the primary storage location. If not, then we use
|
||||
# the human-readable alias. In case of mirrors of a VCS checkout, we currently do not have
|
||||
# a digest, that's why an alias is required and a digest optional.
|
||||
super().__init__(path=digest_path or alias_path)
|
||||
self.alias = alias_path
|
||||
self.digest_path = digest_path
|
||||
|
||||
Paths are of the form `blobs/<algorithm>/<digest>`
|
||||
"""
|
||||
def make_alias(self, root: str) -> None:
|
||||
"""Symlink a human readible path in our mirror to the actual storage location."""
|
||||
# We already use the human-readable path as the main storage location.
|
||||
if not self.digest_path:
|
||||
return
|
||||
|
||||
alias, digest = os.path.join(root, self.alias), os.path.join(root, self.digest_path)
|
||||
|
||||
alias_dir = os.path.dirname(alias)
|
||||
relative_dst = os.path.relpath(digest, start=alias_dir)
|
||||
|
||||
mkdirp(alias_dir)
|
||||
tmp = f"{alias}.tmp"
|
||||
llnl.util.symlink.symlink(relative_dst, tmp)
|
||||
|
||||
try:
|
||||
os.rename(tmp, alias)
|
||||
except OSError:
|
||||
# Clean up the temporary if possible
|
||||
try:
|
||||
os.unlink(tmp)
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
|
||||
def __iter__(self):
|
||||
if self.digest_path:
|
||||
yield self.digest_path
|
||||
yield self.alias
|
||||
|
||||
|
||||
class OCILayout(MirrorLayout):
|
||||
"""Follow the OCI Image Layout Specification to archive blobs where paths are of the form
|
||||
``blobs/<algorithm>/<digest>``"""
|
||||
|
||||
def __init__(self, digest: spack.oci.image.Digest) -> None:
|
||||
self.storage_path = os.path.join("blobs", digest.algorithm, digest.digest)
|
||||
|
||||
def __iter__(self):
|
||||
yield self.storage_path
|
||||
super().__init__(os.path.join("blobs", digest.algorithm, digest.digest))
|
||||
|
||||
|
||||
def mirror_archive_paths(fetcher, per_package_ref, spec=None):
|
||||
def default_mirror_layout(
|
||||
fetcher: "spack.fetch_strategy.FetchStrategy",
|
||||
per_package_ref: str,
|
||||
spec: Optional["spack.spec.Spec"] = None,
|
||||
) -> MirrorLayout:
|
||||
"""Returns a ``MirrorReference`` object which keeps track of the relative
|
||||
storage path of the resource associated with the specified ``fetcher``."""
|
||||
ext = None
|
||||
@@ -494,7 +519,7 @@ def mirror_archive_paths(fetcher, per_package_ref, spec=None):
|
||||
if global_ref and ext:
|
||||
global_ref += ".%s" % ext
|
||||
|
||||
return MirrorReference(per_package_ref, global_ref)
|
||||
return DefaultLayout(per_package_ref, global_ref)
|
||||
|
||||
|
||||
def get_all_versions(specs):
|
||||
|
||||
@@ -25,14 +25,11 @@
|
||||
so package authors should use their judgement.
|
||||
"""
|
||||
import functools
|
||||
import inspect
|
||||
from contextlib import contextmanager
|
||||
|
||||
from llnl.util.lang import caller_locals
|
||||
|
||||
import spack.directives
|
||||
import spack.directives_meta
|
||||
import spack.error
|
||||
from spack.spec import Spec
|
||||
import spack.spec
|
||||
|
||||
|
||||
class MultiMethodMeta(type):
|
||||
@@ -135,7 +132,7 @@ def __call__(self, package_or_builder_self, *args, **kwargs):
|
||||
# its superclasses for successive calls. We don't have that
|
||||
# information within `SpecMultiMethod`, because it is not
|
||||
# associated with the package class.
|
||||
for cls in inspect.getmro(package_or_builder_self.__class__)[1:]:
|
||||
for cls in package_or_builder_self.__class__.__mro__[1:]:
|
||||
superself = cls.__dict__.get(self.__name__, None)
|
||||
|
||||
if isinstance(superself, SpecMultiMethod):
|
||||
@@ -165,9 +162,9 @@ def __init__(self, condition):
|
||||
condition (str): condition to be met
|
||||
"""
|
||||
if isinstance(condition, bool):
|
||||
self.spec = Spec() if condition else None
|
||||
self.spec = spack.spec.Spec() if condition else None
|
||||
else:
|
||||
self.spec = Spec(condition)
|
||||
self.spec = spack.spec.Spec(condition)
|
||||
|
||||
def __call__(self, method):
|
||||
"""This annotation lets packages declare multiple versions of
|
||||
@@ -229,11 +226,9 @@ def install(self, prefix):
|
||||
platform-specific versions. There's not much we can do to get
|
||||
around this because of the way decorators work.
|
||||
"""
|
||||
# In Python 2, Get the first definition of the method in the
|
||||
# calling scope by looking at the caller's locals. In Python 3,
|
||||
# we handle this using MultiMethodMeta.__prepare__.
|
||||
if MultiMethodMeta._locals is None:
|
||||
MultiMethodMeta._locals = caller_locals()
|
||||
assert (
|
||||
MultiMethodMeta._locals is not None
|
||||
), "cannot use multimethod, missing MultiMethodMeta metaclass?"
|
||||
|
||||
# Create a multimethod with this name if there is not one already
|
||||
original_method = MultiMethodMeta._locals.get(method.__name__)
|
||||
@@ -266,17 +261,17 @@ def __enter__(self):
|
||||
and add their constraint to whatever may be already present in the directive
|
||||
`when=` argument.
|
||||
"""
|
||||
spack.directives.DirectiveMeta.push_to_context(str(self.spec))
|
||||
spack.directives_meta.DirectiveMeta.push_to_context(str(self.spec))
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
spack.directives.DirectiveMeta.pop_from_context()
|
||||
spack.directives_meta.DirectiveMeta.pop_from_context()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def default_args(**kwargs):
|
||||
spack.directives.DirectiveMeta.push_default_args(kwargs)
|
||||
spack.directives_meta.DirectiveMeta.push_default_args(kwargs)
|
||||
yield
|
||||
spack.directives.DirectiveMeta.pop_default_args()
|
||||
spack.directives_meta.DirectiveMeta.pop_default_args()
|
||||
|
||||
|
||||
class MultiMethodError(spack.error.SpackError):
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
@@ -43,11 +42,6 @@ def create_tarball(spec: spack.spec.Spec, tarfile_path):
|
||||
return spack.binary_distribution._do_create_tarball(tarfile_path, spec.prefix, buildinfo)
|
||||
|
||||
|
||||
def _log_upload_progress(digest: Digest, size: int, elapsed: float):
|
||||
elapsed = max(elapsed, 0.001) # guard against division by zero
|
||||
tty.info(f"Uploaded {digest} ({elapsed:.2f}s, {size / elapsed / 1024 / 1024:.2f} MB/s)")
|
||||
|
||||
|
||||
def with_query_param(url: str, param: str, value: str) -> str:
|
||||
"""Add a query parameter to a URL
|
||||
|
||||
@@ -141,8 +135,6 @@ def upload_blob(
|
||||
if not force and blob_exists(ref, digest, _urlopen):
|
||||
return False
|
||||
|
||||
start = time.time()
|
||||
|
||||
with open(file, "rb") as f:
|
||||
file_size = os.fstat(f.fileno()).st_size
|
||||
|
||||
@@ -167,7 +159,6 @@ def upload_blob(
|
||||
|
||||
# Created the blob in one go.
|
||||
if response.status == 201:
|
||||
_log_upload_progress(digest, file_size, time.time() - start)
|
||||
return True
|
||||
|
||||
# Otherwise, do another PUT request.
|
||||
@@ -191,8 +182,6 @@ def upload_blob(
|
||||
|
||||
spack.oci.opener.ensure_status(request, response, 201)
|
||||
|
||||
# print elapsed time and # MB/s
|
||||
_log_upload_progress(digest, file_size, time.time() - start)
|
||||
return True
|
||||
|
||||
|
||||
@@ -401,15 +390,12 @@ def make_stage(
|
||||
) -> spack.stage.Stage:
|
||||
_urlopen = _urlopen or spack.oci.opener.urlopen
|
||||
fetch_strategy = spack.fetch_strategy.OCIRegistryFetchStrategy(
|
||||
url, checksum=digest.digest, _urlopen=_urlopen
|
||||
url=url, checksum=digest.digest, _urlopen=_urlopen
|
||||
)
|
||||
# Use blobs/<alg>/<encoded> as the cache path, which follows
|
||||
# the OCI Image Layout Specification. What's missing though,
|
||||
# is the `oci-layout` and `index.json` files, which are
|
||||
# required by the spec.
|
||||
return spack.stage.Stage(
|
||||
fetch_strategy,
|
||||
mirror_paths=spack.mirror.OCIImageLayout(digest),
|
||||
name=digest.digest,
|
||||
keep=keep,
|
||||
fetch_strategy, mirror_paths=spack.mirror.OCILayout(digest), name=digest.digest, keep=keep
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import functools
|
||||
import glob
|
||||
import hashlib
|
||||
import inspect
|
||||
import importlib
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
@@ -197,13 +197,12 @@ def __init__(cls, name, bases, attr_dict):
|
||||
# that "foo" was a possible executable.
|
||||
|
||||
# If a package has the executables or libraries attribute then it's
|
||||
# assumed to be detectable
|
||||
# assumed to be detectable. Add a tag, so finding them is faster
|
||||
if hasattr(cls, "executables") or hasattr(cls, "libraries"):
|
||||
# Append a tag to each detectable package, so that finding them is faster
|
||||
if not hasattr(cls, "tags"):
|
||||
setattr(cls, "tags", [DetectablePackageMeta.TAG])
|
||||
elif DetectablePackageMeta.TAG not in cls.tags:
|
||||
cls.tags.append(DetectablePackageMeta.TAG)
|
||||
# To add the tag, we need to copy the tags attribute, and attach it to
|
||||
# the current class. We don't use append, since it might modify base classes,
|
||||
# if "tags" is retrieved following the MRO.
|
||||
cls.tags = getattr(cls, "tags", []) + [DetectablePackageMeta.TAG]
|
||||
|
||||
@classmethod
|
||||
def platform_executables(cls):
|
||||
@@ -246,10 +245,7 @@ def determine_spec_details(cls, prefix, objs_in_prefix):
|
||||
if version_str:
|
||||
objs_by_version[version_str].append(obj)
|
||||
except Exception as e:
|
||||
msg = (
|
||||
"An error occurred when trying to detect " 'the version of "{0}" [{1}]'
|
||||
)
|
||||
tty.debug(msg.format(obj, str(e)))
|
||||
tty.debug(f"Cannot detect the version of '{obj}' [{str(e)}]")
|
||||
|
||||
specs = []
|
||||
for version_str, objs in objs_by_version.items():
|
||||
@@ -262,27 +258,23 @@ def determine_spec_details(cls, prefix, objs_in_prefix):
|
||||
if isinstance(variant, str):
|
||||
variant = (variant, {})
|
||||
variant_str, extra_attributes = variant
|
||||
spec_str = "{0}@{1} {2}".format(cls.name, version_str, variant_str)
|
||||
spec_str = f"{cls.name}@{version_str} {variant_str}"
|
||||
|
||||
# Pop a few reserved keys from extra attributes, since
|
||||
# they have a different semantics
|
||||
external_path = extra_attributes.pop("prefix", None)
|
||||
external_modules = extra_attributes.pop("modules", None)
|
||||
try:
|
||||
spec = spack.spec.Spec(
|
||||
spec = spack.spec.Spec.from_detection(
|
||||
spec_str,
|
||||
external_path=external_path,
|
||||
external_modules=external_modules,
|
||||
extra_attributes=extra_attributes,
|
||||
)
|
||||
except Exception as e:
|
||||
msg = 'Parsing failed [spec_str="{0}", error={1}]'
|
||||
tty.debug(msg.format(spec_str, str(e)))
|
||||
tty.debug(f'Parsing failed [spec_str="{spec_str}", error={str(e)}]')
|
||||
else:
|
||||
specs.append(
|
||||
spack.spec.Spec.from_detection(
|
||||
spec, extra_attributes=extra_attributes
|
||||
)
|
||||
)
|
||||
specs.append(spec)
|
||||
|
||||
return sorted(specs)
|
||||
|
||||
@@ -741,7 +733,7 @@ def __init__(self, spec):
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
# init internal variables
|
||||
self._stage = None
|
||||
self._stage: Optional[StageComposite] = None
|
||||
self._fetcher = None
|
||||
self._tester: Optional["PackageTest"] = None
|
||||
|
||||
@@ -869,7 +861,7 @@ def module(cls):
|
||||
We use this to add variables to package modules. This makes
|
||||
install() methods easier to write (e.g., can call configure())
|
||||
"""
|
||||
return __import__(cls.__module__, fromlist=[cls.__name__])
|
||||
return importlib.import_module(cls.__module__)
|
||||
|
||||
@classproperty
|
||||
def namespace(cls):
|
||||
@@ -885,7 +877,7 @@ def fullname(cls):
|
||||
def fullnames(cls):
|
||||
"""Fullnames for this package and any packages from which it inherits."""
|
||||
fullnames = []
|
||||
for cls in inspect.getmro(cls):
|
||||
for cls in cls.__mro__:
|
||||
namespace = getattr(cls, "namespace", None)
|
||||
if namespace:
|
||||
fullnames.append("%s.%s" % (namespace, cls.name))
|
||||
@@ -1099,9 +1091,10 @@ def _make_resource_stage(self, root_stage, resource):
|
||||
root=root_stage,
|
||||
resource=resource,
|
||||
name=self._resource_stage(resource),
|
||||
mirror_paths=spack.mirror.mirror_archive_paths(
|
||||
mirror_paths=spack.mirror.default_mirror_layout(
|
||||
resource.fetcher, os.path.join(self.name, pretty_resource_name)
|
||||
),
|
||||
mirrors=spack.mirror.MirrorCollection(source=True).values(),
|
||||
path=self.path,
|
||||
)
|
||||
|
||||
@@ -1113,7 +1106,7 @@ def _make_root_stage(self, fetcher):
|
||||
# Construct a mirror path (TODO: get this out of package.py)
|
||||
format_string = "{name}-{version}"
|
||||
pretty_name = self.spec.format_path(format_string)
|
||||
mirror_paths = spack.mirror.mirror_archive_paths(
|
||||
mirror_paths = spack.mirror.default_mirror_layout(
|
||||
fetcher, os.path.join(self.name, pretty_name), self.spec
|
||||
)
|
||||
# Construct a path where the stage should build..
|
||||
@@ -1122,6 +1115,7 @@ def _make_root_stage(self, fetcher):
|
||||
stage = Stage(
|
||||
fetcher,
|
||||
mirror_paths=mirror_paths,
|
||||
mirrors=spack.mirror.MirrorCollection(source=True).values(),
|
||||
name=stage_name,
|
||||
path=self.path,
|
||||
search_fn=self._download_search,
|
||||
@@ -1178,7 +1172,7 @@ def stage(self):
|
||||
return self._stage
|
||||
|
||||
@stage.setter
|
||||
def stage(self, stage):
|
||||
def stage(self, stage: StageComposite):
|
||||
"""Allow a stage object to be set to override the default."""
|
||||
self._stage = stage
|
||||
|
||||
@@ -1748,7 +1742,7 @@ def _has_make_target(self, target):
|
||||
bool: True if 'target' is found, else False
|
||||
"""
|
||||
# Prevent altering LC_ALL for 'make' outside this function
|
||||
make = copy.deepcopy(inspect.getmodule(self).make)
|
||||
make = copy.deepcopy(self.module.make)
|
||||
|
||||
# Use English locale for missing target message comparison
|
||||
make.add_default_env("LC_ALL", "C")
|
||||
@@ -1798,7 +1792,7 @@ def _if_make_target_execute(self, target, *args, **kwargs):
|
||||
"""
|
||||
if self._has_make_target(target):
|
||||
# Execute target
|
||||
inspect.getmodule(self).make(target, *args, **kwargs)
|
||||
self.module.make(target, *args, **kwargs)
|
||||
|
||||
def _has_ninja_target(self, target):
|
||||
"""Checks to see if 'target' is a valid target in a Ninja build script.
|
||||
@@ -1809,7 +1803,7 @@ def _has_ninja_target(self, target):
|
||||
Returns:
|
||||
bool: True if 'target' is found, else False
|
||||
"""
|
||||
ninja = inspect.getmodule(self).ninja
|
||||
ninja = self.module.ninja
|
||||
|
||||
# Check if we have a Ninja build script
|
||||
if not os.path.exists("build.ninja"):
|
||||
@@ -1838,7 +1832,7 @@ def _if_ninja_target_execute(self, target, *args, **kwargs):
|
||||
"""
|
||||
if self._has_ninja_target(target):
|
||||
# Execute target
|
||||
inspect.getmodule(self).ninja(target, *args, **kwargs)
|
||||
self.module.ninja(target, *args, **kwargs)
|
||||
|
||||
def _get_needed_resources(self):
|
||||
# We use intersects here cause it would also work if self.spec is abstract
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
import stat
|
||||
import warnings
|
||||
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
from spack.config import ConfigError
|
||||
from spack.util.path import canonicalize_path
|
||||
from spack.version import Version
|
||||
|
||||
_lesser_spec_types = {"compiler": spack.spec.CompilerSpec, "version": Version}
|
||||
@@ -154,44 +155,6 @@ def preferred_variants(cls, pkg_name):
|
||||
)
|
||||
|
||||
|
||||
def spec_externals(spec):
|
||||
"""Return a list of external specs (w/external directory path filled in),
|
||||
one for each known external installation.
|
||||
"""
|
||||
# break circular import.
|
||||
from spack.util.module_cmd import path_from_modules # noqa: F401
|
||||
|
||||
def _package(maybe_abstract_spec):
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(spec.name)
|
||||
return pkg_cls(maybe_abstract_spec)
|
||||
|
||||
allpkgs = spack.config.get("packages")
|
||||
names = set([spec.name])
|
||||
names |= set(vspec.name for vspec in _package(spec).virtuals_provided)
|
||||
|
||||
external_specs = []
|
||||
for name in names:
|
||||
pkg_config = allpkgs.get(name, {})
|
||||
pkg_externals = pkg_config.get("externals", [])
|
||||
for entry in pkg_externals:
|
||||
spec_str = entry["spec"]
|
||||
external_path = entry.get("prefix", None)
|
||||
if external_path:
|
||||
external_path = canonicalize_path(external_path)
|
||||
external_modules = entry.get("modules", None)
|
||||
external_spec = spack.spec.Spec.from_detection(
|
||||
spack.spec.Spec(
|
||||
spec_str, external_path=external_path, external_modules=external_modules
|
||||
),
|
||||
extra_attributes=entry.get("extra_attributes", {}),
|
||||
)
|
||||
if external_spec.intersects(spec):
|
||||
external_specs.append(external_spec)
|
||||
|
||||
# Defensively copy returned specs
|
||||
return [s.copy() for s in external_specs]
|
||||
|
||||
|
||||
def is_spec_buildable(spec):
|
||||
"""Return true if the spec is configured as buildable"""
|
||||
allpkgs = spack.config.get("packages")
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import hashlib
|
||||
import inspect
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
@@ -185,8 +184,8 @@ def __init__(
|
||||
# search mro to look for the file
|
||||
abs_path: Optional[str] = None
|
||||
# At different times we call FilePatch on instances and classes
|
||||
pkg_cls = pkg if inspect.isclass(pkg) else pkg.__class__
|
||||
for cls in inspect.getmro(pkg_cls): # type: ignore
|
||||
pkg_cls = pkg if isinstance(pkg, type) else pkg.__class__
|
||||
for cls in pkg_cls.__mro__: # type: ignore
|
||||
if not hasattr(cls, "module"):
|
||||
# We've gone too far up the MRO
|
||||
break
|
||||
@@ -319,18 +318,19 @@ def stage(self) -> "spack.stage.Stage":
|
||||
self.url, archive_sha256=self.archive_sha256, expanded_sha256=self.sha256
|
||||
)
|
||||
else:
|
||||
fetcher = fs.URLFetchStrategy(self.url, sha256=self.sha256, expand=False)
|
||||
fetcher = fs.URLFetchStrategy(url=self.url, sha256=self.sha256, expand=False)
|
||||
|
||||
# The same package can have multiple patches with the same name but
|
||||
# with different contents, therefore apply a subset of the hash.
|
||||
name = "{0}-{1}".format(os.path.basename(self.url), fetch_digest[:7])
|
||||
|
||||
per_package_ref = os.path.join(self.owner.split(".")[-1], name)
|
||||
mirror_ref = spack.mirror.mirror_archive_paths(fetcher, per_package_ref)
|
||||
mirror_ref = spack.mirror.default_mirror_layout(fetcher, per_package_ref)
|
||||
self._stage = spack.stage.Stage(
|
||||
fetcher,
|
||||
name=f"{spack.stage.stage_prefix}patch-{fetch_digest}",
|
||||
mirror_paths=mirror_ref,
|
||||
mirrors=spack.mirror.MirrorCollection(source=True).values(),
|
||||
)
|
||||
return self._stage
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import contextlib
|
||||
|
||||
from ._functions import _host, by_name, platforms, prevent_cray_detection, reset
|
||||
from ._functions import _host, by_name, platforms, reset
|
||||
from ._platform import Platform
|
||||
from .darwin import Darwin
|
||||
from .freebsd import FreeBSD
|
||||
@@ -23,7 +23,6 @@
|
||||
"host",
|
||||
"by_name",
|
||||
"reset",
|
||||
"prevent_cray_detection",
|
||||
]
|
||||
|
||||
#: The "real" platform of the host running Spack. This should not be changed
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import contextlib
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
import spack.util.environment
|
||||
|
||||
from .darwin import Darwin
|
||||
from .freebsd import FreeBSD
|
||||
from .linux import Linux
|
||||
@@ -57,14 +53,3 @@ def by_name(name):
|
||||
"""
|
||||
platform_cls = cls_by_name(name)
|
||||
return platform_cls() if platform_cls else None
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def prevent_cray_detection():
|
||||
"""Context manager that prevents the detection of the Cray platform"""
|
||||
reset()
|
||||
try:
|
||||
with spack.util.environment.set_env(MODULEPATH=""):
|
||||
yield
|
||||
finally:
|
||||
reset()
|
||||
|
||||
@@ -149,12 +149,12 @@ def current_repository(self, value):
|
||||
@contextlib.contextmanager
|
||||
def switch_repo(self, substitute: "RepoType"):
|
||||
"""Switch the current repository list for the duration of the context manager."""
|
||||
old = self.current_repository
|
||||
old = self._repo
|
||||
try:
|
||||
self.current_repository = substitute
|
||||
self._repo = substitute
|
||||
yield
|
||||
finally:
|
||||
self.current_repository = old
|
||||
self._repo = old
|
||||
|
||||
def find_spec(self, fullname, python_path, target=None):
|
||||
# "target" is not None only when calling importlib.reload()
|
||||
@@ -365,9 +365,9 @@ def __init__(self, namespace):
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Getattr lazily loads modules if they're not already loaded."""
|
||||
submodule = self.__package__ + "." + name
|
||||
submodule = f"{self.__package__}.{name}"
|
||||
try:
|
||||
setattr(self, name, __import__(submodule))
|
||||
setattr(self, name, importlib.import_module(submodule))
|
||||
except ImportError:
|
||||
msg = "'{0}' object has no attribute {1}"
|
||||
raise AttributeError(msg.format(type(self), name))
|
||||
@@ -683,7 +683,7 @@ class RepoPath:
|
||||
def __init__(
|
||||
self,
|
||||
*repos: Union[str, "Repo"],
|
||||
cache: "spack.caches.FileCacheType",
|
||||
cache: Optional["spack.caches.FileCacheType"],
|
||||
overrides: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
self.repos: List[Repo] = []
|
||||
@@ -696,6 +696,7 @@ def __init__(
|
||||
for repo in repos:
|
||||
try:
|
||||
if isinstance(repo, str):
|
||||
assert cache is not None, "cache must hold a value, when repo is a string"
|
||||
repo = Repo(repo, cache=cache, overrides=overrides)
|
||||
repo.finder(self)
|
||||
self.put_last(repo)
|
||||
@@ -707,6 +708,10 @@ def __init__(
|
||||
f" spack repo rm {repo}",
|
||||
)
|
||||
|
||||
def ensure_unwrapped(self) -> "RepoPath":
|
||||
"""Ensure we unwrap this object from any dynamic wrapper (like Singleton)"""
|
||||
return self
|
||||
|
||||
def put_first(self, repo: "Repo") -> None:
|
||||
"""Add repo first in the search path."""
|
||||
if isinstance(repo, RepoPath):
|
||||
@@ -930,6 +935,16 @@ def is_virtual_safe(self, pkg_name: str) -> bool:
|
||||
def __contains__(self, pkg_name):
|
||||
return self.exists(pkg_name)
|
||||
|
||||
def marshal(self):
|
||||
return (self.repos,)
|
||||
|
||||
@staticmethod
|
||||
def unmarshal(repos):
|
||||
return RepoPath(*repos, cache=None)
|
||||
|
||||
def __reduce__(self):
|
||||
return RepoPath.unmarshal, self.marshal()
|
||||
|
||||
|
||||
class Repo:
|
||||
"""Class representing a package repository in the filesystem.
|
||||
@@ -1266,7 +1281,7 @@ def get_pkg_class(self, pkg_name: str) -> Type["spack.package_base.PackageBase"]
|
||||
raise RepoError(msg) from e
|
||||
|
||||
cls = getattr(module, class_name)
|
||||
if not inspect.isclass(cls):
|
||||
if not isinstance(cls, type):
|
||||
tty.die(f"{pkg_name}.{class_name} is not a class")
|
||||
|
||||
# Clear any prior changes to class attributes in case the class was loaded from the
|
||||
@@ -1319,6 +1334,20 @@ def __repr__(self) -> str:
|
||||
def __contains__(self, pkg_name: str) -> bool:
|
||||
return self.exists(pkg_name)
|
||||
|
||||
@staticmethod
|
||||
def unmarshal(root, cache, overrides):
|
||||
"""Helper method to unmarshal keyword arguments"""
|
||||
return Repo(root, cache=cache, overrides=overrides)
|
||||
|
||||
def marshal(self):
|
||||
cache = self._cache
|
||||
if isinstance(cache, llnl.util.lang.Singleton):
|
||||
cache = cache.instance
|
||||
return self.root, cache, self.overrides
|
||||
|
||||
def __reduce__(self):
|
||||
return Repo.unmarshal, self.marshal()
|
||||
|
||||
|
||||
RepoType = Union[Repo, RepoPath]
|
||||
|
||||
|
||||
@@ -11,6 +11,26 @@
|
||||
|
||||
import spack.schema.environment
|
||||
|
||||
flags: Dict[str, Any] = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"cflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"cxxflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"fflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"cppflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"ldflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"ldlibs": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
extra_rpaths: Dict[str, Any] = {"type": "array", "default": [], "items": {"type": "string"}}
|
||||
|
||||
implicit_rpaths: Dict[str, Any] = {
|
||||
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "boolean"}]
|
||||
}
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties: Dict[str, Any] = {
|
||||
"compilers": {
|
||||
@@ -35,18 +55,7 @@
|
||||
"fc": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
},
|
||||
},
|
||||
"flags": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"cflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"cxxflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"fflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"cppflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"ldflags": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"ldlibs": {"anyOf": [{"type": "string"}, {"type": "null"}]},
|
||||
},
|
||||
},
|
||||
"flags": flags,
|
||||
"spec": {"type": "string"},
|
||||
"operating_system": {"type": "string"},
|
||||
"target": {"type": "string"},
|
||||
@@ -54,18 +63,9 @@
|
||||
"modules": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}, {"type": "array"}]
|
||||
},
|
||||
"implicit_rpaths": {
|
||||
"anyOf": [
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "boolean"},
|
||||
]
|
||||
},
|
||||
"implicit_rpaths": implicit_rpaths,
|
||||
"environment": spack.schema.environment.definition,
|
||||
"extra_rpaths": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"extra_rpaths": extra_rpaths,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
"build_language": {"type": "string"},
|
||||
"build_jobs": {"type": "integer", "minimum": 1},
|
||||
"ccache": {"type": "boolean"},
|
||||
"concretizer": {"type": "string", "enum": ["original", "clingo"]},
|
||||
"db_lock_timeout": {"type": "integer", "minimum": 1},
|
||||
"package_lock_timeout": {
|
||||
"anyOf": [{"type": "integer", "minimum": 1}, {"type": "null"}]
|
||||
@@ -98,9 +97,9 @@
|
||||
"aliases": {"type": "object", "patternProperties": {r"\w[\w-]*": {"type": "string"}}},
|
||||
},
|
||||
"deprecatedProperties": {
|
||||
"properties": ["terminal_title"],
|
||||
"message": "config:terminal_title has been replaced by "
|
||||
"install_status and is ignored",
|
||||
"properties": ["concretizer"],
|
||||
"message": "Spack supports only clingo as a concretizer from v0.23. "
|
||||
"The config:concretizer config option is ignored.",
|
||||
"error": False,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
import spack.schema.environment
|
||||
|
||||
from .compilers import extra_rpaths, flags, implicit_rpaths
|
||||
|
||||
permissions = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
@@ -184,7 +186,16 @@
|
||||
"type": "object",
|
||||
"additionalProperties": True,
|
||||
"properties": {
|
||||
"environment": spack.schema.environment.definition
|
||||
"compilers": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
r"(^\w[\w-]*)": {"type": "string"}
|
||||
},
|
||||
},
|
||||
"environment": spack.schema.environment.definition,
|
||||
"extra_rpaths": extra_rpaths,
|
||||
"implicit_rpaths": implicit_rpaths,
|
||||
"flags": flags,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
import spack.config
|
||||
import spack.config as sc
|
||||
import spack.deptypes as dt
|
||||
import spack.directives
|
||||
import spack.environment as ev
|
||||
import spack.error
|
||||
import spack.package_base
|
||||
@@ -285,16 +284,14 @@ def _create_counter(specs: List[spack.spec.Spec], tests: bool):
|
||||
return NoDuplicatesCounter(specs, tests=tests)
|
||||
|
||||
|
||||
def all_compilers_in_config(configuration):
|
||||
return spack.compilers.all_compilers_from(configuration)
|
||||
|
||||
|
||||
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 = {
|
||||
c.default_libc for c in all_compilers_in_config(spack.config.CONFIG) if c.default_libc
|
||||
c.default_libc
|
||||
for c in spack.compilers.all_compilers_from(spack.config.CONFIG)
|
||||
if c.default_libc
|
||||
}
|
||||
|
||||
if libcs:
|
||||
@@ -613,7 +610,7 @@ def _external_config_with_implicit_externals(configuration):
|
||||
if not using_libc_compatibility():
|
||||
return packages_yaml
|
||||
|
||||
for compiler in all_compilers_in_config(configuration):
|
||||
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}
|
||||
@@ -1880,8 +1877,7 @@ def _spec_clauses(
|
||||
|
||||
# validate variant value only if spec not concrete
|
||||
if not spec.concrete:
|
||||
reserved_names = spack.directives.reserved_names
|
||||
if not spec.virtual and vname not in reserved_names:
|
||||
if not spec.virtual and vname not in spack.variant.reserved_names:
|
||||
pkg_cls = self.pkg_class(spec.name)
|
||||
try:
|
||||
variant_def, _ = pkg_cls.variants[vname]
|
||||
@@ -3002,7 +2998,7 @@ class CompilerParser:
|
||||
|
||||
def __init__(self, configuration) -> None:
|
||||
self.compilers: Set[KnownCompiler] = set()
|
||||
for c in all_compilers_in_config(configuration):
|
||||
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 {c.cc} does not exist, or does not run correctly."
|
||||
@@ -3466,7 +3462,7 @@ def reorder_flags(self):
|
||||
"""
|
||||
# reverse compilers so we get highest priority compilers that share a spec
|
||||
compilers = dict(
|
||||
(c.spec, c) for c in reversed(all_compilers_in_config(spack.config.CONFIG))
|
||||
(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())
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
import collections
|
||||
import collections.abc
|
||||
import enum
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import pathlib
|
||||
@@ -70,7 +71,6 @@
|
||||
import spack.compiler
|
||||
import spack.compilers
|
||||
import spack.config
|
||||
import spack.dependency as dp
|
||||
import spack.deptypes as dt
|
||||
import spack.error
|
||||
import spack.hash_types as ht
|
||||
@@ -1428,7 +1428,7 @@ def __init__(
|
||||
# init an empty spec that matches anything.
|
||||
self.name = None
|
||||
self.versions = vn.VersionList(":")
|
||||
self.variants = vt.VariantMap(self)
|
||||
self.variants = VariantMap(self)
|
||||
self.architecture = None
|
||||
self.compiler = None
|
||||
self.compiler_flags = FlagMap(self)
|
||||
@@ -1640,7 +1640,7 @@ def _add_flag(self, name, value, propagate):
|
||||
Known flags currently include "arch"
|
||||
"""
|
||||
|
||||
if propagate and name in spack.directives.reserved_names:
|
||||
if propagate and name in vt.reserved_names:
|
||||
raise UnsupportedPropagationError(
|
||||
f"Propagation with '==' is not supported for '{name}'."
|
||||
)
|
||||
@@ -2578,22 +2578,27 @@ def from_signed_json(stream):
|
||||
return Spec.from_dict(extracted_json)
|
||||
|
||||
@staticmethod
|
||||
def from_detection(spec_str, extra_attributes=None):
|
||||
def from_detection(
|
||||
spec_str: str,
|
||||
*,
|
||||
external_path: str,
|
||||
external_modules: Optional[List[str]] = None,
|
||||
extra_attributes: Optional[Dict] = None,
|
||||
) -> "Spec":
|
||||
"""Construct a spec from a spec string determined during external
|
||||
detection and attach extra attributes to it.
|
||||
|
||||
Args:
|
||||
spec_str (str): spec string
|
||||
extra_attributes (dict): dictionary containing extra attributes
|
||||
|
||||
Returns:
|
||||
spack.spec.Spec: external spec
|
||||
spec_str: spec string
|
||||
external_path: prefix of the external spec
|
||||
external_modules: optional module files to be loaded when the external spec is used
|
||||
extra_attributes: dictionary containing extra attributes
|
||||
"""
|
||||
s = Spec(spec_str)
|
||||
s = Spec(spec_str, external_path=external_path, external_modules=external_modules)
|
||||
extra_attributes = syaml.sorted_dict(extra_attributes or {})
|
||||
# This is needed to be able to validate multi-valued variants,
|
||||
# otherwise they'll still be abstract in the context of detection.
|
||||
vt.substitute_abstract_variants(s)
|
||||
substitute_abstract_variants(s)
|
||||
s.extra_attributes = extra_attributes
|
||||
return s
|
||||
|
||||
@@ -2615,294 +2620,6 @@ def validate_detection(self):
|
||||
validate_fn = getattr(pkg_cls, "validate_detected_spec", lambda x, y: None)
|
||||
validate_fn(self, self.extra_attributes)
|
||||
|
||||
def _concretize_helper(self, concretizer, presets=None, visited=None):
|
||||
"""Recursive helper function for concretize().
|
||||
This concretizes everything bottom-up. As things are
|
||||
concretized, they're added to the presets, and ancestors
|
||||
will prefer the settings of their children.
|
||||
"""
|
||||
if presets is None:
|
||||
presets = {}
|
||||
if visited is None:
|
||||
visited = set()
|
||||
|
||||
if self.name in visited:
|
||||
return False
|
||||
|
||||
if self.concrete:
|
||||
visited.add(self.name)
|
||||
return False
|
||||
|
||||
changed = False
|
||||
|
||||
# Concretize deps first -- this is a bottom-up process.
|
||||
for name in sorted(self._dependencies):
|
||||
# WARNING: This function is an implementation detail of the
|
||||
# WARNING: original concretizer. Since with that greedy
|
||||
# WARNING: algorithm we don't allow multiple nodes from
|
||||
# WARNING: the same package in a DAG, here we hard-code
|
||||
# WARNING: using index 0 i.e. we assume that we have only
|
||||
# WARNING: one edge from package "name"
|
||||
changed |= self._dependencies[name][0].spec._concretize_helper(
|
||||
concretizer, presets, visited
|
||||
)
|
||||
|
||||
if self.name in presets:
|
||||
changed |= self.constrain(presets[self.name])
|
||||
else:
|
||||
# Concretize virtual dependencies last. Because they're added
|
||||
# to presets below, their constraints will all be merged, but we'll
|
||||
# still need to select a concrete package later.
|
||||
if not self.virtual:
|
||||
changed |= any(
|
||||
(
|
||||
concretizer.concretize_develop(self), # special variant
|
||||
concretizer.concretize_architecture(self),
|
||||
concretizer.concretize_compiler(self),
|
||||
concretizer.adjust_target(self),
|
||||
# flags must be concretized after compiler
|
||||
concretizer.concretize_compiler_flags(self),
|
||||
concretizer.concretize_version(self),
|
||||
concretizer.concretize_variants(self),
|
||||
)
|
||||
)
|
||||
presets[self.name] = self
|
||||
|
||||
visited.add(self.name)
|
||||
return changed
|
||||
|
||||
def _replace_with(self, concrete):
|
||||
"""Replace this virtual spec with a concrete spec."""
|
||||
assert self.virtual
|
||||
virtuals = (self.name,)
|
||||
for dep_spec in itertools.chain.from_iterable(self._dependents.values()):
|
||||
dependent = dep_spec.parent
|
||||
depflag = dep_spec.depflag
|
||||
|
||||
# remove self from all dependents, unless it is already removed
|
||||
if self.name in dependent._dependencies:
|
||||
del dependent._dependencies.edges[self.name]
|
||||
|
||||
# add the replacement, unless it is already a dep of dependent.
|
||||
if concrete.name not in dependent._dependencies:
|
||||
dependent._add_dependency(concrete, depflag=depflag, virtuals=virtuals)
|
||||
else:
|
||||
dependent.edges_to_dependencies(name=concrete.name)[0].update_virtuals(
|
||||
virtuals=virtuals
|
||||
)
|
||||
|
||||
def _expand_virtual_packages(self, concretizer):
|
||||
"""Find virtual packages in this spec, replace them with providers,
|
||||
and normalize again to include the provider's (potentially virtual)
|
||||
dependencies. Repeat until there are no virtual deps.
|
||||
|
||||
Precondition: spec is normalized.
|
||||
|
||||
.. todo::
|
||||
|
||||
If a provider depends on something that conflicts with
|
||||
other dependencies in the spec being expanded, this can
|
||||
produce a conflicting spec. For example, if mpich depends
|
||||
on hwloc@:1.3 but something in the spec needs hwloc1.4:,
|
||||
then we should choose an MPI other than mpich. Cases like
|
||||
this are infrequent, but should implement this before it is
|
||||
a problem.
|
||||
"""
|
||||
# Make an index of stuff this spec already provides
|
||||
self_index = spack.provider_index.ProviderIndex(
|
||||
repository=spack.repo.PATH, specs=self.traverse(), restrict=True
|
||||
)
|
||||
changed = False
|
||||
done = False
|
||||
|
||||
while not done:
|
||||
done = True
|
||||
for spec in list(self.traverse()):
|
||||
replacement = None
|
||||
if spec.external:
|
||||
continue
|
||||
if spec.virtual:
|
||||
replacement = self._find_provider(spec, self_index)
|
||||
if replacement:
|
||||
# TODO: may break if in-place on self but
|
||||
# shouldn't happen if root is traversed first.
|
||||
spec._replace_with(replacement)
|
||||
done = False
|
||||
break
|
||||
|
||||
if not replacement:
|
||||
# Get a list of possible replacements in order of
|
||||
# preference.
|
||||
candidates = concretizer.choose_virtual_or_external(spec)
|
||||
|
||||
# Try the replacements in order, skipping any that cause
|
||||
# satisfiability problems.
|
||||
for replacement in candidates:
|
||||
if replacement is spec:
|
||||
break
|
||||
|
||||
# Replace spec with the candidate and normalize
|
||||
copy = self.copy()
|
||||
copy[spec.name]._dup(replacement, deps=False)
|
||||
|
||||
try:
|
||||
# If there are duplicate providers or duplicate
|
||||
# provider deps, consolidate them and merge
|
||||
# constraints.
|
||||
copy.normalize(force=True)
|
||||
break
|
||||
except spack.error.SpecError:
|
||||
# On error, we'll try the next replacement.
|
||||
continue
|
||||
|
||||
# If replacement is external then trim the dependencies
|
||||
if replacement.external:
|
||||
if spec._dependencies:
|
||||
for dep in spec.dependencies():
|
||||
del dep._dependents.edges[spec.name]
|
||||
changed = True
|
||||
spec.clear_dependencies()
|
||||
replacement.clear_dependencies()
|
||||
replacement.architecture = self.architecture
|
||||
|
||||
# TODO: could this and the stuff in _dup be cleaned up?
|
||||
def feq(cfield, sfield):
|
||||
return (not cfield) or (cfield == sfield)
|
||||
|
||||
if replacement is spec or (
|
||||
feq(replacement.name, spec.name)
|
||||
and feq(replacement.versions, spec.versions)
|
||||
and feq(replacement.compiler, spec.compiler)
|
||||
and feq(replacement.architecture, spec.architecture)
|
||||
and feq(replacement._dependencies, spec._dependencies)
|
||||
and feq(replacement.variants, spec.variants)
|
||||
and feq(replacement.external_path, spec.external_path)
|
||||
and feq(replacement.external_modules, spec.external_modules)
|
||||
):
|
||||
continue
|
||||
# Refine this spec to the candidate. This uses
|
||||
# replace_with AND dup so that it can work in
|
||||
# place. TODO: make this more efficient.
|
||||
if spec.virtual:
|
||||
spec._replace_with(replacement)
|
||||
changed = True
|
||||
if spec._dup(replacement, deps=False, cleardeps=False):
|
||||
changed = True
|
||||
|
||||
self_index.update(spec)
|
||||
done = False
|
||||
break
|
||||
|
||||
return changed
|
||||
|
||||
def _old_concretize(self, tests=False, deprecation_warning=True):
|
||||
"""A spec is concrete if it describes one build of a package uniquely.
|
||||
This will ensure that this spec is concrete.
|
||||
|
||||
Args:
|
||||
tests (list or bool): list of packages that will need test
|
||||
dependencies, or True/False for test all/none
|
||||
deprecation_warning (bool): enable or disable the deprecation
|
||||
warning for the old concretizer
|
||||
|
||||
If this spec could describe more than one version, variant, or build
|
||||
of a package, this will add constraints to make it concrete.
|
||||
|
||||
Some rigorous validation and checks are also performed on the spec.
|
||||
Concretizing ensures that it is self-consistent and that it's
|
||||
consistent with requirements of its packages. See flatten() and
|
||||
normalize() for more details on this.
|
||||
"""
|
||||
import spack.concretize
|
||||
|
||||
# Add a warning message to inform users that the original concretizer
|
||||
# will be removed
|
||||
if deprecation_warning:
|
||||
msg = (
|
||||
"the original concretizer is currently being used.\n\tUpgrade to "
|
||||
'"clingo" at your earliest convenience. The original concretizer '
|
||||
"will be removed from Spack in a future version."
|
||||
)
|
||||
warnings.warn(msg)
|
||||
|
||||
self.replace_hash()
|
||||
|
||||
if not self.name:
|
||||
raise spack.error.SpecError("Attempting to concretize anonymous spec")
|
||||
|
||||
if self._concrete:
|
||||
return
|
||||
|
||||
# take the spec apart once before starting the main concretization loop and resolving
|
||||
# deps, but don't break dependencies during concretization as the spec is built.
|
||||
user_spec_deps = self.flat_dependencies(disconnect=True)
|
||||
|
||||
changed = True
|
||||
force = False
|
||||
concretizer = spack.concretize.Concretizer(self.copy())
|
||||
while changed:
|
||||
changes = (
|
||||
self.normalize(force, tests, user_spec_deps, disconnect=False),
|
||||
self._expand_virtual_packages(concretizer),
|
||||
self._concretize_helper(concretizer),
|
||||
)
|
||||
changed = any(changes)
|
||||
force = True
|
||||
|
||||
visited_user_specs = set()
|
||||
for dep in self.traverse():
|
||||
visited_user_specs.add(dep.name)
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(dep.name)
|
||||
visited_user_specs.update(pkg_cls(dep).provided_virtual_names())
|
||||
|
||||
extra = set(user_spec_deps.keys()).difference(visited_user_specs)
|
||||
if extra:
|
||||
raise InvalidDependencyError(self.name, extra)
|
||||
|
||||
Spec.inject_patches_variant(self)
|
||||
|
||||
for s in self.traverse():
|
||||
# TODO: Refactor this into a common method to build external specs
|
||||
# TODO: or turn external_path into a lazy property
|
||||
Spec.ensure_external_path_if_external(s)
|
||||
|
||||
# assign hashes and mark concrete
|
||||
self._finalize_concretization()
|
||||
|
||||
# If any spec in the DAG is deprecated, throw an error
|
||||
Spec.ensure_no_deprecated(self)
|
||||
|
||||
# Update externals as needed
|
||||
for dep in self.traverse():
|
||||
if dep.external:
|
||||
dep.package.update_external_dependencies()
|
||||
|
||||
# Now that the spec is concrete we should check if
|
||||
# there are declared conflicts
|
||||
#
|
||||
# TODO: this needs rethinking, as currently we can only express
|
||||
# TODO: internal configuration conflicts within one package.
|
||||
matches = []
|
||||
for x in self.traverse():
|
||||
if x.external:
|
||||
# external specs are already built, don't worry about whether
|
||||
# it's possible to build that configuration with Spack
|
||||
continue
|
||||
|
||||
for when_spec, conflict_list in x.package_class.conflicts.items():
|
||||
if x.satisfies(when_spec):
|
||||
for conflict_spec, msg in conflict_list:
|
||||
if x.satisfies(conflict_spec):
|
||||
when = when_spec.copy()
|
||||
when.name = x.name
|
||||
matches.append((x, conflict_spec, when, msg))
|
||||
if matches:
|
||||
raise ConflictsInSpecError(self, matches)
|
||||
|
||||
# Check if we can produce an optimized binary (will throw if
|
||||
# there are declared inconsistencies)
|
||||
self.architecture.target.optimization_flags(self.compiler)
|
||||
|
||||
def _patches_assigned(self):
|
||||
"""Whether patches have been assigned to this spec by the concretizer."""
|
||||
# FIXME: _patches_in_order_of_appearance is attached after concretization
|
||||
@@ -3032,7 +2749,13 @@ def ensure_no_deprecated(root):
|
||||
msg += " For each package listed, choose another spec\n"
|
||||
raise SpecDeprecatedError(msg)
|
||||
|
||||
def _new_concretize(self, tests=False):
|
||||
def concretize(self, tests: Union[bool, List[str]] = False) -> None:
|
||||
"""Concretize the current spec.
|
||||
|
||||
Args:
|
||||
tests: if False disregard 'test' dependencies, if a list of names activate them for
|
||||
the packages in the list, if True activate 'test' dependencies for all packages.
|
||||
"""
|
||||
import spack.solver.asp
|
||||
|
||||
self.replace_hash()
|
||||
@@ -3066,19 +2789,6 @@ def _new_concretize(self, tests=False):
|
||||
concretized = answer[node]
|
||||
self._dup(concretized)
|
||||
|
||||
def concretize(self, tests=False):
|
||||
"""Concretize the current spec.
|
||||
|
||||
Args:
|
||||
tests (bool or list): if False disregard 'test' dependencies,
|
||||
if a list of names activate them for the packages in the list,
|
||||
if True activate 'test' dependencies for all packages.
|
||||
"""
|
||||
if spack.config.get("config:concretizer", "clingo") == "clingo":
|
||||
self._new_concretize(tests)
|
||||
else:
|
||||
self._old_concretize(tests)
|
||||
|
||||
def _mark_root_concrete(self, value=True):
|
||||
"""Mark just this spec (not dependencies) concrete."""
|
||||
if (not value) and self.concrete and self.installed:
|
||||
@@ -3182,34 +2892,6 @@ def concretized(self, tests=False):
|
||||
clone.concretize(tests=tests)
|
||||
return clone
|
||||
|
||||
def flat_dependencies(self, disconnect: bool = False):
|
||||
"""Build DependencyMap of all of this spec's dependencies with their constraints merged.
|
||||
|
||||
Arguments:
|
||||
disconnect: if True, disconnect all dependents and dependencies among nodes in this
|
||||
spec's DAG.
|
||||
"""
|
||||
flat_deps = {}
|
||||
deptree = self.traverse(root=False)
|
||||
|
||||
for spec in deptree:
|
||||
if spec.name not in flat_deps:
|
||||
flat_deps[spec.name] = spec
|
||||
else:
|
||||
try:
|
||||
flat_deps[spec.name].constrain(spec)
|
||||
except spack.error.UnsatisfiableSpecError as e:
|
||||
# DAG contains two instances of the same package with inconsistent constraints.
|
||||
raise InconsistentSpecError("Invalid Spec DAG: %s" % e.message) from e
|
||||
|
||||
if disconnect:
|
||||
for spec in flat_deps.values():
|
||||
if not spec.concrete:
|
||||
spec.clear_edges()
|
||||
self.clear_dependencies()
|
||||
|
||||
return flat_deps
|
||||
|
||||
def index(self, deptype="all"):
|
||||
"""Return a dictionary that points to all the dependencies in this
|
||||
spec.
|
||||
@@ -3219,312 +2901,6 @@ def index(self, deptype="all"):
|
||||
dm[spec.name].append(spec)
|
||||
return dm
|
||||
|
||||
def _evaluate_dependency_conditions(self, name):
|
||||
"""Evaluate all the conditions on a dependency with this name.
|
||||
|
||||
Args:
|
||||
name (str): name of dependency to evaluate conditions on.
|
||||
|
||||
Returns:
|
||||
(Dependency): new Dependency object combining all constraints.
|
||||
|
||||
If the package depends on <name> in the current spec
|
||||
configuration, return the constrained dependency and
|
||||
corresponding dependency types.
|
||||
|
||||
If no conditions are True (and we don't depend on it), return
|
||||
``(None, None)``.
|
||||
"""
|
||||
vt.substitute_abstract_variants(self)
|
||||
# evaluate when specs to figure out constraints on the dependency.
|
||||
dep = None
|
||||
for when_spec, deps_by_name in self.package_class.dependencies.items():
|
||||
if not self.satisfies(when_spec):
|
||||
continue
|
||||
|
||||
for dep_name, dependency in deps_by_name.items():
|
||||
if dep_name != name:
|
||||
continue
|
||||
|
||||
if dep is None:
|
||||
dep = dp.Dependency(Spec(self.name), Spec(name), depflag=0)
|
||||
try:
|
||||
dep.merge(dependency)
|
||||
except spack.error.UnsatisfiableSpecError as e:
|
||||
e.message = (
|
||||
"Conflicting conditional dependencies for spec"
|
||||
"\n\n\t{0}\n\n"
|
||||
"Cannot merge constraint"
|
||||
"\n\n\t{1}\n\n"
|
||||
"into"
|
||||
"\n\n\t{2}".format(self, dependency.spec, dep.spec)
|
||||
)
|
||||
raise e
|
||||
|
||||
return dep
|
||||
|
||||
def _find_provider(self, vdep, provider_index):
|
||||
"""Find provider for a virtual spec in the provider index.
|
||||
Raise an exception if there is a conflicting virtual
|
||||
dependency already in this spec.
|
||||
"""
|
||||
assert spack.repo.PATH.is_virtual_safe(vdep.name), vdep
|
||||
|
||||
# note that this defensively copies.
|
||||
providers = provider_index.providers_for(vdep)
|
||||
|
||||
# If there is a provider for the vpkg, then use that instead of
|
||||
# the virtual package.
|
||||
if providers:
|
||||
# Remove duplicate providers that can concretize to the same
|
||||
# result.
|
||||
for provider in providers:
|
||||
for spec in providers:
|
||||
if spec is not provider and provider.intersects(spec):
|
||||
providers.remove(spec)
|
||||
# Can't have multiple providers for the same thing in one spec.
|
||||
if len(providers) > 1:
|
||||
raise MultipleProviderError(vdep, providers)
|
||||
return providers[0]
|
||||
else:
|
||||
# The user might have required something insufficient for
|
||||
# pkg_dep -- so we'll get a conflict. e.g., user asked for
|
||||
# mpi@:1.1 but some package required mpi@2.1:.
|
||||
required = provider_index.providers_for(vdep.name)
|
||||
if len(required) > 1:
|
||||
raise MultipleProviderError(vdep, required)
|
||||
elif required:
|
||||
raise UnsatisfiableProviderSpecError(required[0], vdep)
|
||||
|
||||
def _merge_dependency(self, dependency, visited, spec_deps, provider_index, tests):
|
||||
"""Merge dependency information from a Package into this Spec.
|
||||
|
||||
Args:
|
||||
dependency (Dependency): dependency metadata from a package;
|
||||
this is typically the result of merging *all* matching
|
||||
dependency constraints from the package.
|
||||
visited (set): set of dependency nodes already visited by
|
||||
``normalize()``.
|
||||
spec_deps (dict): ``dict`` of all dependencies from the spec
|
||||
being normalized.
|
||||
provider_index (dict): ``provider_index`` of virtual dep
|
||||
providers in the ``Spec`` as normalized so far.
|
||||
|
||||
NOTE: Caller should assume that this routine owns the
|
||||
``dependency`` parameter, i.e., it needs to be a copy of any
|
||||
internal structures.
|
||||
|
||||
This is the core of ``normalize()``. There are some basic steps:
|
||||
|
||||
* If dep is virtual, evaluate whether it corresponds to an
|
||||
existing concrete dependency, and merge if so.
|
||||
|
||||
* If it's real and it provides some virtual dep, see if it provides
|
||||
what some virtual dependency wants and merge if so.
|
||||
|
||||
* Finally, if none of the above, merge dependency and its
|
||||
constraints into this spec.
|
||||
|
||||
This method returns True if the spec was changed, False otherwise.
|
||||
|
||||
"""
|
||||
changed = False
|
||||
dep = dependency.spec
|
||||
|
||||
# If it's a virtual dependency, try to find an existing
|
||||
# provider in the spec, and merge that.
|
||||
virtuals = ()
|
||||
if spack.repo.PATH.is_virtual_safe(dep.name):
|
||||
virtuals = (dep.name,)
|
||||
visited.add(dep.name)
|
||||
provider = self._find_provider(dep, provider_index)
|
||||
if provider:
|
||||
dep = provider
|
||||
else:
|
||||
index = spack.provider_index.ProviderIndex(
|
||||
repository=spack.repo.PATH, specs=[dep], restrict=True
|
||||
)
|
||||
items = list(spec_deps.items())
|
||||
for name, vspec in items:
|
||||
if not spack.repo.PATH.is_virtual_safe(vspec.name):
|
||||
continue
|
||||
|
||||
if index.providers_for(vspec):
|
||||
vspec._replace_with(dep)
|
||||
del spec_deps[vspec.name]
|
||||
changed = True
|
||||
else:
|
||||
required = index.providers_for(vspec.name)
|
||||
if required:
|
||||
raise UnsatisfiableProviderSpecError(required[0], dep)
|
||||
provider_index.update(dep)
|
||||
|
||||
# If the spec isn't already in the set of dependencies, add it.
|
||||
# Note: dep is always owned by this method. If it's from the
|
||||
# caller, it's a copy from _evaluate_dependency_conditions. If it
|
||||
# comes from a vdep, it's a defensive copy from _find_provider.
|
||||
if dep.name not in spec_deps:
|
||||
if self.concrete:
|
||||
return False
|
||||
|
||||
spec_deps[dep.name] = dep
|
||||
changed = True
|
||||
else:
|
||||
# merge package/vdep information into spec
|
||||
try:
|
||||
tty.debug("{0} applying constraint {1}".format(self.name, str(dep)))
|
||||
changed |= spec_deps[dep.name].constrain(dep)
|
||||
except spack.error.UnsatisfiableSpecError as e:
|
||||
fmt = "An unsatisfiable {0}".format(e.constraint_type)
|
||||
fmt += " constraint has been detected for spec:"
|
||||
fmt += "\n\n{0}\n\n".format(spec_deps[dep.name].tree(indent=4))
|
||||
fmt += "while trying to concretize the partial spec:"
|
||||
fmt += "\n\n{0}\n\n".format(self.tree(indent=4))
|
||||
fmt += "{0} requires {1} {2} {3}, but spec asked for {4}"
|
||||
|
||||
e.message = fmt.format(
|
||||
self.name, dep.name, e.constraint_type, e.required, e.provided
|
||||
)
|
||||
|
||||
raise
|
||||
|
||||
# Add merged spec to my deps and recurse
|
||||
spec_dependency = spec_deps[dep.name]
|
||||
if dep.name not in self._dependencies:
|
||||
self._add_dependency(spec_dependency, depflag=dependency.depflag, virtuals=virtuals)
|
||||
|
||||
changed |= spec_dependency._normalize_helper(visited, spec_deps, provider_index, tests)
|
||||
return changed
|
||||
|
||||
def _normalize_helper(self, visited, spec_deps, provider_index, tests):
|
||||
"""Recursive helper function for _normalize."""
|
||||
if self.name in visited:
|
||||
return False
|
||||
visited.add(self.name)
|
||||
|
||||
# If we descend into a virtual spec, there's nothing more
|
||||
# to normalize. Concretize will finish resolving it later.
|
||||
if self.virtual or self.external:
|
||||
return False
|
||||
|
||||
# Avoid recursively adding constraints for already-installed packages:
|
||||
# these may include build dependencies which are not needed for this
|
||||
# install (since this package is already installed).
|
||||
if self.concrete and self.installed:
|
||||
return False
|
||||
|
||||
# Combine constraints from package deps with constraints from
|
||||
# the spec, until nothing changes.
|
||||
any_change = False
|
||||
changed = True
|
||||
|
||||
while changed:
|
||||
changed = False
|
||||
for dep_name in self.package_class.dependency_names():
|
||||
# Do we depend on dep_name? If so pkg_dep is not None.
|
||||
dep = self._evaluate_dependency_conditions(dep_name)
|
||||
|
||||
# If dep is a needed dependency, merge it.
|
||||
if dep:
|
||||
merge = (
|
||||
# caller requested test dependencies
|
||||
tests is True
|
||||
or (tests and self.name in tests)
|
||||
or
|
||||
# this is not a test-only dependency
|
||||
(dep.depflag & ~dt.TEST)
|
||||
)
|
||||
|
||||
if merge:
|
||||
changed |= self._merge_dependency(
|
||||
dep, visited, spec_deps, provider_index, tests
|
||||
)
|
||||
any_change |= changed
|
||||
|
||||
return any_change
|
||||
|
||||
def normalize(self, force=False, tests=False, user_spec_deps=None, disconnect=True):
|
||||
"""When specs are parsed, any dependencies specified are hanging off
|
||||
the root, and ONLY the ones that were explicitly provided are there.
|
||||
Normalization turns a partial flat spec into a DAG, where:
|
||||
|
||||
1. Known dependencies of the root package are in the DAG.
|
||||
2. Each node's dependencies dict only contains its known direct
|
||||
deps.
|
||||
3. There is only ONE unique spec for each package in the DAG.
|
||||
|
||||
* This includes virtual packages. If there a non-virtual
|
||||
package that provides a virtual package that is in the spec,
|
||||
then we replace the virtual package with the non-virtual one.
|
||||
|
||||
TODO: normalize should probably implement some form of cycle
|
||||
detection, to ensure that the spec is actually a DAG.
|
||||
"""
|
||||
if not self.name:
|
||||
raise spack.error.SpecError("Attempting to normalize anonymous spec")
|
||||
|
||||
# Set _normal and _concrete to False when forced
|
||||
if force and not self._concrete:
|
||||
self._normal = False
|
||||
|
||||
if self._normal:
|
||||
return False
|
||||
|
||||
# Ensure first that all packages & compilers in the DAG exist.
|
||||
self.validate_or_raise()
|
||||
# Clear the DAG and collect all dependencies in the DAG, which will be
|
||||
# reapplied as constraints. All dependencies collected this way will
|
||||
# have been created by a previous execution of 'normalize'.
|
||||
# A dependency extracted here will only be reintegrated if it is
|
||||
# discovered to apply according to _normalize_helper, so
|
||||
# user-specified dependencies are recorded separately in case they
|
||||
# refer to specs which take several normalization passes to
|
||||
# materialize.
|
||||
all_spec_deps = self.flat_dependencies(disconnect=disconnect)
|
||||
|
||||
if user_spec_deps:
|
||||
for name, spec in user_spec_deps.items():
|
||||
if not name:
|
||||
msg = "Attempted to normalize anonymous dependency spec"
|
||||
msg += " %s" % spec
|
||||
raise InvalidSpecDetected(msg)
|
||||
if name not in all_spec_deps:
|
||||
all_spec_deps[name] = spec
|
||||
else:
|
||||
all_spec_deps[name].constrain(spec)
|
||||
|
||||
# Initialize index of virtual dependency providers if
|
||||
# concretize didn't pass us one already
|
||||
provider_index = spack.provider_index.ProviderIndex(
|
||||
repository=spack.repo.PATH, specs=[s for s in all_spec_deps.values()], restrict=True
|
||||
)
|
||||
|
||||
# traverse the package DAG and fill out dependencies according
|
||||
# to package files & their 'when' specs
|
||||
visited = set()
|
||||
|
||||
any_change = self._normalize_helper(visited, all_spec_deps, provider_index, tests)
|
||||
|
||||
# remove any leftover dependents outside the spec from, e.g., pruning externals
|
||||
valid = {id(spec) for spec in all_spec_deps.values()} | {id(self)}
|
||||
for spec in all_spec_deps.values():
|
||||
remove = [dep for dep in spec.dependents() if id(dep) not in valid]
|
||||
for dep in remove:
|
||||
del spec._dependents.edges[dep.name]
|
||||
del dep._dependencies.edges[spec.name]
|
||||
|
||||
# Mark the spec as normal once done.
|
||||
self._normal = True
|
||||
return any_change
|
||||
|
||||
def normalized(self):
|
||||
"""
|
||||
Return a normalized copy of this spec without modifying this spec.
|
||||
"""
|
||||
clone = self.copy()
|
||||
clone.normalize()
|
||||
return clone
|
||||
|
||||
def validate_or_raise(self):
|
||||
"""Checks that names and values in this spec are real. If they're not,
|
||||
it will raise an appropriate exception.
|
||||
@@ -3545,7 +2921,7 @@ def validate_or_raise(self):
|
||||
# Ensure correctness of variants (if the spec is not virtual)
|
||||
if not spec.virtual:
|
||||
Spec.ensure_valid_variants(spec)
|
||||
vt.substitute_abstract_variants(spec)
|
||||
substitute_abstract_variants(spec)
|
||||
|
||||
@staticmethod
|
||||
def ensure_valid_variants(spec):
|
||||
@@ -3565,9 +2941,7 @@ def ensure_valid_variants(spec):
|
||||
pkg_variants = pkg_cls.variants
|
||||
# reserved names are variants that may be set on any package
|
||||
# but are not necessarily recorded by the package's class
|
||||
not_existing = set(spec.variants) - (
|
||||
set(pkg_variants) | set(spack.directives.reserved_names)
|
||||
)
|
||||
not_existing = set(spec.variants) - (set(pkg_variants) | set(vt.reserved_names))
|
||||
if not_existing:
|
||||
raise vt.UnknownVariantError(spec, not_existing)
|
||||
|
||||
@@ -4516,7 +3890,7 @@ def format_attribute(match_object: Match) -> str:
|
||||
if part.startswith("_"):
|
||||
raise SpecFormatStringError("Attempted to format private attribute")
|
||||
else:
|
||||
if part == "variants" and isinstance(current, vt.VariantMap):
|
||||
if part == "variants" and isinstance(current, VariantMap):
|
||||
# subscript instead of getattr for variant names
|
||||
current = current[part]
|
||||
else:
|
||||
@@ -4971,9 +4345,155 @@ def attach_git_version_lookup(self):
|
||||
v.attach_lookup(spack.version.git_ref_lookup.GitRefLookup(self.fullname))
|
||||
|
||||
|
||||
def parse_with_version_concrete(string: str, compiler: bool = False):
|
||||
class VariantMap(lang.HashableMap):
|
||||
"""Map containing variant instances. New values can be added only
|
||||
if the key is not already present."""
|
||||
|
||||
def __init__(self, spec: Spec):
|
||||
super().__init__()
|
||||
self.spec = spec
|
||||
|
||||
def __setitem__(self, name, vspec):
|
||||
# Raise a TypeError if vspec is not of the right type
|
||||
if not isinstance(vspec, vt.AbstractVariant):
|
||||
raise TypeError(
|
||||
"VariantMap accepts only values of variant types "
|
||||
f"[got {type(vspec).__name__} instead]"
|
||||
)
|
||||
|
||||
# Raise an error if the variant was already in this map
|
||||
if name in self.dict:
|
||||
msg = 'Cannot specify variant "{0}" twice'.format(name)
|
||||
raise vt.DuplicateVariantError(msg)
|
||||
|
||||
# Raise an error if name and vspec.name don't match
|
||||
if name != vspec.name:
|
||||
raise KeyError(
|
||||
f'Inconsistent key "{name}", must be "{vspec.name}" to ' "match VariantSpec"
|
||||
)
|
||||
|
||||
# Set the item
|
||||
super().__setitem__(name, vspec)
|
||||
|
||||
def substitute(self, vspec):
|
||||
"""Substitutes the entry under ``vspec.name`` with ``vspec``.
|
||||
|
||||
Args:
|
||||
vspec: variant spec to be substituted
|
||||
"""
|
||||
if vspec.name not in self:
|
||||
raise KeyError(f"cannot substitute a key that does not exist [{vspec.name}]")
|
||||
|
||||
# Set the item
|
||||
super().__setitem__(vspec.name, vspec)
|
||||
|
||||
def satisfies(self, other):
|
||||
return all(k in self and self[k].satisfies(other[k]) for k in other)
|
||||
|
||||
def intersects(self, other):
|
||||
return all(self[k].intersects(other[k]) for k in other if k in self)
|
||||
|
||||
def constrain(self, other: "VariantMap") -> bool:
|
||||
"""Add all variants in other that aren't in self to self. Also constrain all multi-valued
|
||||
variants that are already present. Return True iff self changed"""
|
||||
if other.spec is not None and other.spec._concrete:
|
||||
for k in self:
|
||||
if k not in other:
|
||||
raise vt.UnsatisfiableVariantSpecError(self[k], "<absent>")
|
||||
|
||||
changed = False
|
||||
for k in other:
|
||||
if k in self:
|
||||
# If they are not compatible raise an error
|
||||
if not self[k].compatible(other[k]):
|
||||
raise vt.UnsatisfiableVariantSpecError(self[k], other[k])
|
||||
# If they are compatible merge them
|
||||
changed |= self[k].constrain(other[k])
|
||||
else:
|
||||
# If it is not present copy it straight away
|
||||
self[k] = other[k].copy()
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
@property
|
||||
def concrete(self):
|
||||
"""Returns True if the spec is concrete in terms of variants.
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
return self.spec._concrete or all(v in self for v in self.spec.package_class.variants)
|
||||
|
||||
def copy(self) -> "VariantMap":
|
||||
clone = VariantMap(self.spec)
|
||||
for name, variant in self.items():
|
||||
clone[name] = variant.copy()
|
||||
return clone
|
||||
|
||||
def __str__(self):
|
||||
if not self:
|
||||
return ""
|
||||
|
||||
# print keys in order
|
||||
sorted_keys = sorted(self.keys())
|
||||
|
||||
# Separate boolean variants from key-value pairs as they print
|
||||
# differently. All booleans go first to avoid ' ~foo' strings that
|
||||
# break spec reuse in zsh.
|
||||
bool_keys = []
|
||||
kv_keys = []
|
||||
for key in sorted_keys:
|
||||
bool_keys.append(key) if isinstance(self[key].value, bool) else kv_keys.append(key)
|
||||
|
||||
# add spaces before and after key/value variants.
|
||||
string = io.StringIO()
|
||||
|
||||
for key in bool_keys:
|
||||
string.write(str(self[key]))
|
||||
|
||||
for key in kv_keys:
|
||||
string.write(" ")
|
||||
string.write(str(self[key]))
|
||||
|
||||
return string.getvalue()
|
||||
|
||||
|
||||
def substitute_abstract_variants(spec: Spec):
|
||||
"""Uses the information in `spec.package` to turn any variant that needs
|
||||
it into a SingleValuedVariant.
|
||||
|
||||
This method is best effort. All variants that can be substituted will be
|
||||
substituted before any error is raised.
|
||||
|
||||
Args:
|
||||
spec: spec on which to operate the substitution
|
||||
"""
|
||||
# This method needs to be best effort so that it works in matrix exlusion
|
||||
# in $spack/lib/spack/spack/spec_list.py
|
||||
failed = []
|
||||
for name, v in spec.variants.items():
|
||||
if name == "dev_path":
|
||||
spec.variants.substitute(vt.SingleValuedVariant(name, v._original_value))
|
||||
continue
|
||||
elif name in vt.reserved_names:
|
||||
continue
|
||||
elif name not in spec.package_class.variants:
|
||||
failed.append(name)
|
||||
continue
|
||||
pkg_variant, _ = spec.package_class.variants[name]
|
||||
new_variant = pkg_variant.make_variant(v._original_value)
|
||||
pkg_variant.validate_or_raise(new_variant, spec.package_class)
|
||||
spec.variants.substitute(new_variant)
|
||||
|
||||
# Raise all errors at once
|
||||
if failed:
|
||||
raise vt.UnknownVariantError(spec, failed)
|
||||
|
||||
|
||||
def parse_with_version_concrete(spec_like: Union[str, Spec], compiler: bool = False):
|
||||
"""Same as Spec(string), but interprets @x as @=x"""
|
||||
s: Union[CompilerSpec, Spec] = CompilerSpec(string) if compiler else Spec(string)
|
||||
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])
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import itertools
|
||||
from typing import List
|
||||
|
||||
import spack.spec
|
||||
import spack.variant
|
||||
from spack.error import SpackError
|
||||
from spack.spec import Spec
|
||||
@@ -225,7 +226,7 @@ def _expand_matrix_constraints(matrix_config):
|
||||
# Catch exceptions because we want to be able to operate on
|
||||
# abstract specs without needing package information
|
||||
try:
|
||||
spack.variant.substitute_abstract_variants(test_spec)
|
||||
spack.spec.substitute_abstract_variants(test_spec)
|
||||
except spack.variant.UnknownVariantError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Callable, Dict, Iterable, Optional, Set
|
||||
from typing import Callable, Dict, Generator, Iterable, List, Optional, Set
|
||||
|
||||
import llnl.string
|
||||
import llnl.util.lang
|
||||
@@ -40,6 +40,7 @@
|
||||
import spack.resource
|
||||
import spack.spec
|
||||
import spack.stage
|
||||
import spack.util.crypto
|
||||
import spack.util.lock
|
||||
import spack.util.path as sup
|
||||
import spack.util.pattern as pattern
|
||||
@@ -351,8 +352,10 @@ class Stage(LockableStagingDir):
|
||||
def __init__(
|
||||
self,
|
||||
url_or_fetch_strategy,
|
||||
*,
|
||||
name=None,
|
||||
mirror_paths=None,
|
||||
mirror_paths: Optional[spack.mirror.MirrorLayout] = None,
|
||||
mirrors: Optional[Iterable[spack.mirror.Mirror]] = None,
|
||||
keep=False,
|
||||
path=None,
|
||||
lock=True,
|
||||
@@ -361,36 +364,30 @@ def __init__(
|
||||
"""Create a stage object.
|
||||
Parameters:
|
||||
url_or_fetch_strategy
|
||||
URL of the archive to be downloaded into this stage, OR
|
||||
a valid FetchStrategy.
|
||||
URL of the archive to be downloaded into this stage, OR a valid FetchStrategy.
|
||||
|
||||
name
|
||||
If a name is provided, then this stage is a named stage
|
||||
and will persist between runs (or if you construct another
|
||||
stage object later). If name is not provided, then this
|
||||
If a name is provided, then this stage is a named stage and will persist between runs
|
||||
(or if you construct another stage object later). If name is not provided, then this
|
||||
stage will be given a unique name automatically.
|
||||
|
||||
mirror_paths
|
||||
If provided, Stage will search Spack's mirrors for
|
||||
this archive at each of the provided relative mirror paths
|
||||
before using the default fetch strategy.
|
||||
If provided, Stage will search Spack's mirrors for this archive at each of the
|
||||
provided relative mirror paths before using the default fetch strategy.
|
||||
|
||||
keep
|
||||
By default, when used as a context manager, the Stage
|
||||
is deleted on exit when no exceptions are raised.
|
||||
Pass True to keep the stage intact even if no
|
||||
exceptions are raised.
|
||||
By default, when used as a context manager, the Stage is deleted on exit when no
|
||||
exceptions are raised. Pass True to keep the stage intact even if no exceptions are
|
||||
raised.
|
||||
|
||||
path
|
||||
If provided, the stage path to use for associated builds.
|
||||
|
||||
lock
|
||||
True if the stage directory file lock is to be used, False
|
||||
otherwise.
|
||||
True if the stage directory file lock is to be used, False otherwise.
|
||||
|
||||
search_fn
|
||||
The search function that provides the fetch strategy
|
||||
instance.
|
||||
The search function that provides the fetch strategy instance.
|
||||
"""
|
||||
super().__init__(name, path, keep, lock)
|
||||
|
||||
@@ -406,30 +403,37 @@ def __init__(
|
||||
# self.fetcher can change with mirrors.
|
||||
self.default_fetcher = self.fetcher
|
||||
self.search_fn = search_fn
|
||||
# used for mirrored archives of repositories.
|
||||
self.skip_checksum_for_mirror = True
|
||||
# If we fetch from a mirror, but the original data is from say git, we can currently not
|
||||
# prove that they are equal (we don't even have a tree hash in package.py). This bool is
|
||||
# used to skip checksum verification and instead warn the user.
|
||||
if isinstance(self.default_fetcher, fs.URLFetchStrategy):
|
||||
self.skip_checksum_for_mirror = not bool(self.default_fetcher.digest)
|
||||
else:
|
||||
self.skip_checksum_for_mirror = True
|
||||
|
||||
self.srcdir = None
|
||||
|
||||
self.mirror_paths = mirror_paths
|
||||
self.mirror_layout = mirror_paths
|
||||
self.mirrors = list(mirrors) if mirrors else []
|
||||
# Allow users the disable both mirrors and download cache
|
||||
self.default_fetcher_only = False
|
||||
|
||||
@property
|
||||
def expected_archive_files(self):
|
||||
"""Possible archive file paths."""
|
||||
paths = []
|
||||
fnames = []
|
||||
expanded = True
|
||||
if isinstance(self.default_fetcher, fs.URLFetchStrategy):
|
||||
expanded = self.default_fetcher.expand_archive
|
||||
fnames.append(url_util.default_download_filename(self.default_fetcher.url))
|
||||
|
||||
if self.mirror_paths:
|
||||
fnames.extend(os.path.basename(x) for x in self.mirror_paths)
|
||||
if self.mirror_layout:
|
||||
fnames.append(os.path.basename(self.mirror_layout.path))
|
||||
|
||||
paths.extend(os.path.join(self.path, f) for f in fnames)
|
||||
paths = [os.path.join(self.path, f) for f in fnames]
|
||||
if not expanded:
|
||||
# If the download file is not compressed, the "archive" is a
|
||||
# single file placed in Stage.source_path
|
||||
# If the download file is not compressed, the "archive" is a single file placed in
|
||||
# Stage.source_path
|
||||
paths.extend(os.path.join(self.source_path, f) for f in fnames)
|
||||
|
||||
return paths
|
||||
@@ -462,104 +466,84 @@ def source_path(self):
|
||||
"""Returns the well-known source directory path."""
|
||||
return os.path.join(self.path, _source_path_subdir)
|
||||
|
||||
def disable_mirrors(self):
|
||||
"""The Stage will not attempt to look for the associated fetcher
|
||||
target in any of Spack's mirrors (including the local download cache).
|
||||
"""
|
||||
self.mirror_paths = []
|
||||
|
||||
def fetch(self, mirror_only=False, err_msg=None):
|
||||
"""Retrieves the code or archive
|
||||
|
||||
Args:
|
||||
mirror_only (bool): only fetch from a mirror
|
||||
err_msg (str or None): the error message to display if all fetchers
|
||||
fail or ``None`` for the default fetch failure message
|
||||
"""
|
||||
fetchers = []
|
||||
def _generate_fetchers(self, mirror_only=False) -> Generator[fs.FetchStrategy, None, None]:
|
||||
fetchers: List[fs.FetchStrategy] = []
|
||||
if not mirror_only:
|
||||
fetchers.append(self.default_fetcher)
|
||||
|
||||
# If this archive is normally fetched from a URL, then use the same digest.
|
||||
if isinstance(self.default_fetcher, fs.URLFetchStrategy):
|
||||
digest = self.default_fetcher.digest
|
||||
expand = self.default_fetcher.expand_archive
|
||||
extension = self.default_fetcher.extension
|
||||
else:
|
||||
digest = None
|
||||
expand = True
|
||||
extension = None
|
||||
|
||||
# TODO: move mirror logic out of here and clean it up!
|
||||
# TODO: Or @alalazo may have some ideas about how to use a
|
||||
# TODO: CompositeFetchStrategy here.
|
||||
self.skip_checksum_for_mirror = True
|
||||
if self.mirror_paths:
|
||||
# Join URLs of mirror roots with mirror paths. Because
|
||||
# urljoin() will strip everything past the final '/' in
|
||||
# the root, so we add a '/' if it is not present.
|
||||
mirror_urls = [
|
||||
url_util.join(mirror.fetch_url, rel_path)
|
||||
for mirror in spack.mirror.MirrorCollection(source=True).values()
|
||||
if not mirror.fetch_url.startswith("oci://")
|
||||
for rel_path in self.mirror_paths
|
||||
]
|
||||
|
||||
# If this archive is normally fetched from a tarball URL,
|
||||
# then use the same digest. `spack mirror` ensures that
|
||||
# the checksum will be the same.
|
||||
digest = None
|
||||
expand = True
|
||||
extension = None
|
||||
if isinstance(self.default_fetcher, fs.URLFetchStrategy):
|
||||
digest = self.default_fetcher.digest
|
||||
expand = self.default_fetcher.expand_archive
|
||||
extension = self.default_fetcher.extension
|
||||
|
||||
# Have to skip the checksum for things archived from
|
||||
# repositories. How can this be made safer?
|
||||
self.skip_checksum_for_mirror = not bool(digest)
|
||||
|
||||
if not self.default_fetcher_only and self.mirror_layout and self.mirrors:
|
||||
# Add URL strategies for all the mirrors with the digest
|
||||
# Insert fetchers in the order that the URLs are provided.
|
||||
for url in reversed(mirror_urls):
|
||||
fetchers.insert(
|
||||
0, fs.from_url_scheme(url, digest, expand=expand, extension=extension)
|
||||
fetchers[:0] = (
|
||||
fs.from_url_scheme(
|
||||
url_util.join(mirror.fetch_url, self.mirror_layout.path),
|
||||
checksum=digest,
|
||||
expand=expand,
|
||||
extension=extension,
|
||||
)
|
||||
for mirror in self.mirrors
|
||||
if not mirror.fetch_url.startswith("oci://") # no support for mirrors yet
|
||||
)
|
||||
|
||||
if self.default_fetcher.cachable:
|
||||
for rel_path in reversed(list(self.mirror_paths)):
|
||||
cache_fetcher = spack.caches.FETCH_CACHE.fetcher(
|
||||
rel_path, digest, expand=expand, extension=extension
|
||||
)
|
||||
fetchers.insert(0, cache_fetcher)
|
||||
if not self.default_fetcher_only and self.mirror_layout and self.default_fetcher.cachable:
|
||||
fetchers.insert(
|
||||
0,
|
||||
spack.caches.FETCH_CACHE.fetcher(
|
||||
self.mirror_layout.path, digest, expand=expand, extension=extension
|
||||
),
|
||||
)
|
||||
|
||||
def generate_fetchers():
|
||||
for fetcher in fetchers:
|
||||
yield fetcher
|
||||
# The search function may be expensive, so wait until now to
|
||||
# call it so the user can stop if a prior fetcher succeeded
|
||||
if self.search_fn and not mirror_only:
|
||||
dynamic_fetchers = self.search_fn()
|
||||
for fetcher in dynamic_fetchers:
|
||||
yield fetcher
|
||||
yield from fetchers
|
||||
|
||||
def print_errors(errors):
|
||||
for msg in errors:
|
||||
tty.debug(msg)
|
||||
# The search function may be expensive, so wait until now to call it so the user can stop
|
||||
# if a prior fetcher succeeded
|
||||
if self.search_fn and not mirror_only:
|
||||
yield from self.search_fn()
|
||||
|
||||
errors = []
|
||||
for fetcher in generate_fetchers():
|
||||
def fetch(self, mirror_only: bool = False, err_msg: Optional[str] = None) -> None:
|
||||
"""Retrieves the code or archive
|
||||
|
||||
Args:
|
||||
mirror_only: only fetch from a mirror
|
||||
err_msg: the error message to display if all fetchers fail or ``None`` for the default
|
||||
fetch failure message
|
||||
"""
|
||||
errors: List[str] = []
|
||||
for fetcher in self._generate_fetchers(mirror_only):
|
||||
try:
|
||||
fetcher.stage = self
|
||||
self.fetcher = fetcher
|
||||
self.fetcher.fetch()
|
||||
break
|
||||
except spack.fetch_strategy.NoCacheError:
|
||||
except fs.NoCacheError:
|
||||
# Don't bother reporting when something is not cached.
|
||||
continue
|
||||
except fs.FailedDownloadError as f:
|
||||
errors.extend(f"{fetcher}: {e.__class__.__name__}: {e}" for e in f.exceptions)
|
||||
continue
|
||||
except spack.error.SpackError as e:
|
||||
errors.append("Fetching from {0} failed.".format(fetcher))
|
||||
tty.debug(e)
|
||||
errors.append(f"{fetcher}: {e.__class__.__name__}: {e}")
|
||||
continue
|
||||
else:
|
||||
print_errors(errors)
|
||||
|
||||
self.fetcher = self.default_fetcher
|
||||
default_msg = "All fetchers failed for {0}".format(self.name)
|
||||
raise spack.error.FetchError(err_msg or default_msg, None)
|
||||
|
||||
print_errors(errors)
|
||||
if err_msg:
|
||||
raise spack.error.FetchError(err_msg)
|
||||
raise spack.error.FetchError(
|
||||
f"All fetchers failed for {self.name}", "\n".join(f" {e}" for e in errors)
|
||||
)
|
||||
|
||||
def steal_source(self, dest):
|
||||
"""Copy the source_path directory in its entirety to directory dest
|
||||
@@ -597,56 +581,60 @@ def steal_source(self, dest):
|
||||
self.destroy()
|
||||
|
||||
def check(self):
|
||||
"""Check the downloaded archive against a checksum digest.
|
||||
No-op if this stage checks code out of a repository."""
|
||||
"""Check the downloaded archive against a checksum digest."""
|
||||
if self.fetcher is not self.default_fetcher and self.skip_checksum_for_mirror:
|
||||
cache = isinstance(self.fetcher, fs.CacheURLFetchStrategy)
|
||||
if cache:
|
||||
secure_msg = "your download cache is in a secure location"
|
||||
else:
|
||||
secure_msg = "you trust this mirror and have a secure connection"
|
||||
tty.warn(
|
||||
"Fetching from mirror without a checksum!",
|
||||
"This package is normally checked out from a version "
|
||||
"control system, but it has been archived on a spack "
|
||||
"mirror. This means we cannot know a checksum for the "
|
||||
"tarball in advance. Be sure that your connection to "
|
||||
"this mirror is secure!",
|
||||
f"Using {'download cache' if cache else 'a mirror'} instead of version control",
|
||||
"The required sources are normally checked out from a version control system, "
|
||||
f"but have been archived {'in download cache' if cache else 'on a mirror'}: "
|
||||
f"{self.fetcher}. Spack lacks a tree hash to verify the integrity of this "
|
||||
f"archive. Make sure {secure_msg}.",
|
||||
)
|
||||
elif spack.config.get("config:checksum"):
|
||||
self.fetcher.check()
|
||||
|
||||
def cache_local(self):
|
||||
spack.caches.FETCH_CACHE.store(self.fetcher, self.mirror_paths.storage_path)
|
||||
spack.caches.FETCH_CACHE.store(self.fetcher, self.mirror_layout.path)
|
||||
|
||||
def cache_mirror(self, mirror, stats):
|
||||
def cache_mirror(
|
||||
self, mirror: spack.caches.MirrorCache, stats: spack.mirror.MirrorStats
|
||||
) -> None:
|
||||
"""Perform a fetch if the resource is not already cached
|
||||
|
||||
Arguments:
|
||||
mirror (spack.caches.MirrorCache): the mirror to cache this Stage's
|
||||
resource in
|
||||
stats (spack.mirror.MirrorStats): this is updated depending on whether the
|
||||
caching operation succeeded or failed
|
||||
mirror: the mirror to cache this Stage's resource in
|
||||
stats: this is updated depending on whether the caching operation succeeded or failed
|
||||
"""
|
||||
if isinstance(self.default_fetcher, fs.BundleFetchStrategy):
|
||||
# BundleFetchStrategy has no source to fetch. The associated
|
||||
# fetcher does nothing but the associated stage may still exist.
|
||||
# There is currently no method available on the fetcher to
|
||||
# distinguish this ('cachable' refers to whether the fetcher
|
||||
# refers to a resource with a fixed ID, which is not the same
|
||||
# concept as whether there is anything to fetch at all) so we
|
||||
# must examine the type of the fetcher.
|
||||
# BundleFetchStrategy has no source to fetch. The associated fetcher does nothing but
|
||||
# the associated stage may still exist. There is currently no method available on the
|
||||
# fetcher to distinguish this ('cachable' refers to whether the fetcher refers to a
|
||||
# resource with a fixed ID, which is not the same concept as whether there is anything
|
||||
# to fetch at all) so we must examine the type of the fetcher.
|
||||
return
|
||||
|
||||
if mirror.skip_unstable_versions and not fs.stable_target(self.default_fetcher):
|
||||
elif mirror.skip_unstable_versions and not fs.stable_target(self.default_fetcher):
|
||||
return
|
||||
|
||||
absolute_storage_path = os.path.join(mirror.root, self.mirror_paths.storage_path)
|
||||
elif not self.mirror_layout:
|
||||
return
|
||||
|
||||
absolute_storage_path = os.path.join(mirror.root, self.mirror_layout.path)
|
||||
|
||||
if os.path.exists(absolute_storage_path):
|
||||
stats.already_existed(absolute_storage_path)
|
||||
else:
|
||||
self.fetch()
|
||||
self.check()
|
||||
mirror.store(self.fetcher, self.mirror_paths.storage_path)
|
||||
mirror.store(self.fetcher, self.mirror_layout.path)
|
||||
stats.added(absolute_storage_path)
|
||||
|
||||
mirror.symlink(self.mirror_paths)
|
||||
self.mirror_layout.make_alias(mirror.root)
|
||||
|
||||
def expand_archive(self):
|
||||
"""Changes to the stage directory and attempt to expand the downloaded
|
||||
@@ -654,9 +642,9 @@ def expand_archive(self):
|
||||
downloaded."""
|
||||
if not self.expanded:
|
||||
self.fetcher.expand()
|
||||
tty.debug("Created stage in {0}".format(self.path))
|
||||
tty.debug(f"Created stage in {self.path}")
|
||||
else:
|
||||
tty.debug("Already staged {0} in {1}".format(self.name, self.path))
|
||||
tty.debug(f"Already staged {self.name} in {self.path}")
|
||||
|
||||
def restage(self):
|
||||
"""Removes the expanded archive path if it exists, then re-expands
|
||||
@@ -1175,20 +1163,22 @@ def _fetch_and_checksum(url, options, keep_stage, action_fn=None):
|
||||
try:
|
||||
url_or_fs = url
|
||||
if options:
|
||||
url_or_fs = fs.URLFetchStrategy(url, fetch_options=options)
|
||||
url_or_fs = fs.URLFetchStrategy(url=url, fetch_options=options)
|
||||
|
||||
with Stage(url_or_fs, keep=keep_stage) as stage:
|
||||
# Fetch the archive
|
||||
stage.fetch()
|
||||
if action_fn is not None:
|
||||
archive = stage.archive_file
|
||||
assert archive is not None, f"Archive not found for {url}"
|
||||
if action_fn is not None and archive:
|
||||
# Only run first_stage_function the first time,
|
||||
# no need to run it every time
|
||||
action_fn(stage, url)
|
||||
action_fn(archive, url)
|
||||
|
||||
# Checksum the archive and add it to the list
|
||||
checksum = spack.util.crypto.checksum(hashlib.sha256, stage.archive_file)
|
||||
checksum = spack.util.crypto.checksum(hashlib.sha256, archive)
|
||||
return checksum, None
|
||||
except FailedDownloadError:
|
||||
except fs.FailedDownloadError:
|
||||
return None, f"[WORKER] Failed to fetch {url}"
|
||||
except Exception as e:
|
||||
return None, f"[WORKER] Something failed on {url}, skipping. ({e})"
|
||||
@@ -1208,7 +1198,3 @@ class RestageError(StageError):
|
||||
|
||||
class VersionFetchError(StageError):
|
||||
"""Raised when we can't determine a URL to fetch a package."""
|
||||
|
||||
|
||||
# Keep this in namespace for convenience
|
||||
FailedDownloadError = fs.FailedDownloadError
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user