Compare commits

..

1 Commits

Author SHA1 Message Date
Harmen Stoppels
a3cbafd87c PackageBase: revamp libs and headers API
Add `find_libs`, `find_headers`, `query_libs`, and `query_headers` to
`PackageBase` and implement `SpecBuildInterface` in terms of those.

The old style `libs` and `headers` properties are deprecated but take
priority over the new style `find_libs` and `find_headers` methods.
2025-03-27 10:22:11 +01:00
2215 changed files with 11856 additions and 14296 deletions

3
.gitattributes vendored
View File

@@ -1,3 +1,4 @@
*.py diff=python
*.lp linguist-language=Prolog
lib/spack/external/* linguist-vendored
*.bat text eol=crlf
*.bat text eol=crlf

View File

@@ -59,6 +59,7 @@ jobs:
- name: Package audits (without coverage)
if: ${{ runner.os == 'Windows' }}
run: |
. share/spack/setup-env.sh
spack -d audit packages
./share/spack/qa/validate_last_exit.ps1
spack -d audit configs

View File

@@ -26,7 +26,7 @@ jobs:
dnf install -y \
bzip2 curl file gcc-c++ gcc gcc-gfortran git gzip \
make patch unzip which xz python3 python3-devel tree \
cmake bison bison-devel libstdc++-static gawk
cmake bison bison-devel libstdc++-static
- name: Setup OpenSUSE
if: ${{ matrix.image == 'opensuse/leap:latest' }}
run: |

View File

@@ -25,16 +25,14 @@ jobs:
with:
python-version: '3.13'
cache: 'pip'
cache-dependency-path: '.github/workflows/requirements/style/requirements.txt'
- name: Install Python Packages
run: |
pip install --upgrade pip setuptools
pip install -r .github/workflows/requirements/style/requirements.txt
- name: vermin (Spack's Core)
run: |
vermin --backport importlib --backport argparse --violations --backport typing -t=3.6- -vvv lib/spack/spack/ lib/spack/llnl/ bin/
run: vermin --backport importlib --backport argparse --violations --backport typing -t=3.6- -vvv lib/spack/spack/ lib/spack/llnl/ bin/
- name: vermin (Repositories)
run: |
vermin --backport importlib --backport argparse --violations --backport typing -t=3.6- -vvv var/spack/repos
run: vermin --backport importlib --backport argparse --violations --backport typing -t=3.6- -vvv var/spack/repos
# Run style checks on the files that have been changed
style:
@@ -42,20 +40,23 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 2
fetch-depth: 0
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b
with:
python-version: '3.13'
cache: 'pip'
cache-dependency-path: '.github/workflows/requirements/style/requirements.txt'
- name: Install Python packages
run: |
pip install --upgrade pip setuptools
pip install -r .github/workflows/requirements/style/requirements.txt
- name: Setup git configuration
run: |
# Need this for the git tests to succeed.
git --version
. .github/workflows/bin/setup_git.sh
- name: Run style tests
run: |
bin/spack style --base HEAD^1
bin/spack license verify
pylint -j $(nproc) --disable=all --enable=unspecified-encoding --ignore-paths=lib/spack/external lib
share/spack/qa/run-style-tests
audit:
uses: ./.github/workflows/audit.yaml
@@ -102,3 +103,21 @@ jobs:
spack -d bootstrap now --dev
spack -d style -t black
spack unit-test -V
# Further style checks from pylint
pylint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b
with:
python-version: '3.13'
cache: 'pip'
- name: Install Python packages
run: |
pip install --upgrade pip setuptools pylint
- name: Pylint (Spack Core)
run: |
pylint -j 4 --disable=all --enable=unspecified-encoding --ignore-paths=lib/spack/external lib

View File

@@ -1,8 +1,7 @@
black==25.1.0
clingo==5.8.0
flake8==7.2.0
clingo==5.7.1
flake8==7.1.2
isort==6.0.1
mypy==1.15.0
types-six==1.17.0.20250403
types-six==1.17.0.20250304
vermin==1.6.0
pylint==3.3.6

View File

@@ -19,6 +19,9 @@ jobs:
on_develop:
- ${{ github.ref == 'refs/heads/develop' }}
include:
- python-version: '3.6'
os: ubuntu-20.04
on_develop: ${{ github.ref == 'refs/heads/develop' }}
- python-version: '3.7'
os: ubuntu-22.04
on_develop: ${{ github.ref == 'refs/heads/develop' }}

View File

@@ -46,42 +46,18 @@ See the
[Feature Overview](https://spack.readthedocs.io/en/latest/features.html)
for examples and highlights.
Installation
----------------
To install spack, first make sure you have Python & Git.
To install spack and your first package, make sure you have Python & Git.
Then:
```bash
git clone -c feature.manyFiles=true --depth=2 https://github.com/spack/spack.git
```
<details>
<summary>What are <code>manyFiles=true</code> and <code>--depth=2</code>?</summary>
<br>
$ git clone -c feature.manyFiles=true --depth=2 https://github.com/spack/spack.git
$ cd spack/bin
$ ./spack install zlib
> [!TIP]
> `-c feature.manyFiles=true` improves git's performance on repositories with 1,000+ files.
>
> `--depth=2` prunes the git history to reduce the size of the Spack installation.
</details>
```bash
# For bash/zsh/sh
. spack/share/spack/setup-env.sh
# For tcsh/csh
source spack/share/spack/setup-env.csh
# For fish
. spack/share/spack/setup-env.fish
```
```bash
# Now you're ready to install a package!
spack install zlib-ng
```
Documentation
----------------

View File

@@ -90,9 +90,10 @@ config:
misc_cache: $user_cache_path/cache
# Abort downloads after this many seconds if not data is received.
# Setting this to 0 will disable the timeout.
connect_timeout: 30
# Timeout in seconds used for downloading sources etc. This only applies
# to the connection phase and can be increased for slow connections or
# servers. 0 means no timeout.
connect_timeout: 10
# If this is false, tools like curl that use SSL will not verify

View File

@@ -25,8 +25,6 @@ packages:
glu: [apple-glu]
unwind: [apple-libunwind]
uuid: [apple-libuuid]
apple-clang:
buildable: false
apple-gl:
buildable: false
externals:

View File

@@ -72,8 +72,6 @@ packages:
permissions:
read: world
write: user
cce:
buildable: false
cray-fftw:
buildable: false
cray-libsci:
@@ -88,23 +86,13 @@ packages:
buildable: false
essl:
buildable: false
fj:
buildable: false
fujitsu-mpi:
buildable: false
fujitsu-ssl2:
buildable: false
glibc:
buildable: false
hpcx-mpi:
buildable: false
iconv:
prefer: [libiconv]
mpt:
buildable: false
musl:
buildable: false
spectrum-mpi:
buildable: false
xl:
buildable: false

View File

@@ -20,8 +20,3 @@ packages:
cxx: [msvc]
mpi: [msmpi]
gl: [wgl]
mpi:
require:
- one_of: [msmpi]
msvc:
buildable: false

View File

@@ -1291,61 +1291,55 @@ based on site policies.
Variants
^^^^^^^^
Variants are named options associated with a particular package and are
typically used to enable or disable certain features at build time. They
are optional, as each package must provide default values for each variant
it makes available.
The names of variants available for a particular package depend on
Variants are named options associated with a particular package. They are
optional, as each package must provide default values for each variant it
makes available. Variants can be specified using
a flexible parameter syntax ``name=<value>``. For example,
``spack install mercury debug=True`` will install mercury built with debug
flags. The names of particular variants available for a package depend on
what was provided by the package author. ``spack info <package>`` will
provide information on what build variants are available.
There are different types of variants:
For compatibility with earlier versions, variants which happen to be
boolean in nature can be specified by a syntax that represents turning
options on and off. For example, in the previous spec we could have
supplied ``mercury +debug`` with the same effect of enabling the debug
compile time option for the libelf package.
1. Boolean variants. Typically used to enable or disable a feature at
compile time. For example, a package might have a ``debug`` variant that
can be explicitly enabled with ``+debug`` and disabled with ``~debug``.
2. Single-valued variants. Often used to set defaults. For example, a package
might have a ``compression`` variant that determines the default
compression algorithm, which users could set to ``compression=gzip`` or
``compression=zstd``.
3. Multi-valued variants. A package might have a ``fabrics`` variant that
determines which network fabrics to support. Users could set this to
``fabrics=verbs,ofi`` to enable both InfiniBand verbs and OpenFabrics
interfaces. The values are separated by commas.
Depending on the package a variant may have any default value. For
``mercury`` here, ``debug`` is ``False`` by default, and we turned it on
with ``debug=True`` or ``+debug``. If a variant is ``True`` by default
you can turn it off by either adding ``-name`` or ``~name`` to the spec.
The meaning of ``fabrics=verbs,ofi`` is to enable *at least* the specified
fabrics, but other fabrics may be enabled as well. If the intent is to
enable *only* the specified fabrics, then the ``fabrics:=verbs,ofi``
syntax should be used with the ``:=`` operator.
There are two syntaxes here because, depending on context, ``~`` and
``-`` may mean different things. In most shells, the following will
result in the shell performing home directory substitution:
.. note::
.. code-block:: sh
In certain shells, the the ``~`` character is expanded to the home
directory. To avoid these issues, avoid whitespace between the package
name and the variant:
mpileaks ~debug # shell may try to substitute this!
mpileaks~debug # use this instead
.. code-block:: sh
If there is a user called ``debug``, the ``~`` will be incorrectly
expanded. In this situation, you would want to write ``libelf
-debug``. However, ``-`` can be ambiguous when included after a
package name without spaces:
mpileaks ~debug # shell may try to substitute this!
mpileaks~debug # use this instead
.. code-block:: sh
Alternatively, you can use the ``-`` character to disable a variant,
but be aware that this requires a space between the package name and
the variant:
mpileaks-debug # wrong!
mpileaks -debug # right
.. code-block:: sh
Spack allows the ``-`` character to be part of package names, so the
above will be interpreted as a request for the ``mpileaks-debug``
package, not a request for ``mpileaks`` built without ``debug``
options. In this scenario, you should write ``mpileaks~debug`` to
avoid ambiguity.
mpileaks-debug # wrong: refers to a package named "mpileaks-debug"
mpileaks -debug # right: refers to a package named mpileaks with debug disabled
As a last resort, ``debug=False`` can also be used to disable a boolean variant.
"""""""""""""""""""""""""""""""""""
Variant propagation to dependencies
"""""""""""""""""""""""""""""""""""
When spack normalizes specs, it prints them out with no spaces boolean
variants using the backwards compatibility syntax and uses only ``~``
for disabled boolean variants. The ``-`` and spaces on the command
line are provided for convenience and legibility.
Spack allows variants to propagate their value to the package's
dependency by using ``++``, ``--``, and ``~~`` for boolean variants.
@@ -1415,29 +1409,27 @@ that executables will run without the need to set ``LD_LIBRARY_PATH``.
.. code-block:: yaml
packages:
gcc:
externals:
- spec: gcc@4.9.3
prefix: /opt/gcc
extra_attributes:
compilers:
c: /opt/gcc/bin/gcc
cxx: /opt/gcc/bin/g++
fortran: /opt/gcc/bin/gfortran
environment:
unset:
- BAD_VARIABLE
set:
GOOD_VARIABLE_NUM: 1
GOOD_VARIABLE_STR: good
prepend_path:
PATH: /path/to/binutils
append_path:
LD_LIBRARY_PATH: /opt/gcc/lib
extra_rpaths:
- /path/to/some/compiler/runtime/directory
- /path/to/some/other/compiler/runtime/directory
compilers:
- compiler:
spec: gcc@4.9.3
paths:
cc: /opt/gcc/bin/gcc
c++: /opt/gcc/bin/g++
f77: /opt/gcc/bin/gfortran
fc: /opt/gcc/bin/gfortran
environment:
unset:
- BAD_VARIABLE
set:
GOOD_VARIABLE_NUM: 1
GOOD_VARIABLE_STR: good
prepend_path:
PATH: /path/to/binutils
append_path:
LD_LIBRARY_PATH: /opt/gcc/lib
extra_rpaths:
- /path/to/some/compiler/runtime/directory
- /path/to/some/other/compiler/runtime/directory
^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -63,6 +63,7 @@ on these ideas for each distinct build system that Spack supports:
build_systems/cudapackage
build_systems/custompackage
build_systems/inteloneapipackage
build_systems/intelpackage
build_systems/rocmpackage
build_systems/sourceforgepackage

View File

@@ -33,6 +33,9 @@ For more information on a specific package, do::
spack info --all <package-name>
Intel no longer releases new versions of Parallel Studio, which can be
used in Spack via the :ref:`intelpackage`. All of its components can
now be found in oneAPI.
Examples
========
@@ -47,8 +50,34 @@ Install the oneAPI compilers::
spack install intel-oneapi-compilers
Add the compilers to your ``compilers.yaml`` so spack can use them::
To build the ``patchelf`` Spack package with ``icx``, do::
spack compiler add `spack location -i intel-oneapi-compilers`/compiler/latest/bin
Verify that the compilers are available::
spack compiler list
Note that 2024 and later releases do not include ``icc``. Before 2024,
the package layout was different::
spack compiler add `spack location -i intel-oneapi-compilers`/compiler/latest/linux/bin/intel64
spack compiler add `spack location -i intel-oneapi-compilers`/compiler/latest/linux/bin
The ``intel-oneapi-compilers`` package includes 2 families of
compilers:
* ``intel``: ``icc``, ``icpc``, ``ifort``. Intel's *classic*
compilers. 2024 and later releases contain ``ifort``, but not
``icc`` and ``icpc``.
* ``oneapi``: ``icx``, ``icpx``, ``ifx``. Intel's new generation of
compilers based on LLVM.
To build the ``patchelf`` Spack package with ``icc``, do::
spack install patchelf%intel
To build with with ``icx``, do ::
spack install patchelf%oneapi
@@ -63,6 +92,15 @@ Install the oneAPI compilers::
spack install intel-oneapi-compilers
Add the compilers to your ``compilers.yaml`` so Spack can use them::
spack compiler add `spack location -i intel-oneapi-compilers`/compiler/latest/bin
spack compiler add `spack location -i intel-oneapi-compilers`/compiler/latest/bin
Verify that the compilers are available::
spack compiler list
Clone `spack-configs <https://github.com/spack/spack-configs>`_ repo and activate Intel oneAPI CPU environment::
git clone https://github.com/spack/spack-configs
@@ -111,7 +149,7 @@ Compilers
---------
To use the compilers, add some information about the installation to
``packages.yaml``. For most users, it is sufficient to do::
``compilers.yaml``. For most users, it is sufficient to do::
spack compiler add /opt/intel/oneapi/compiler/latest/bin
@@ -119,7 +157,7 @@ Adapt the paths above if you did not install the tools in the default
location. After adding the compilers, using them is the same
as if you had installed the ``intel-oneapi-compilers`` package.
Another option is to manually add the configuration to
``packages.yaml`` as described in :ref:`Compiler configuration
``compilers.yaml`` as described in :ref:`Compiler configuration
<compiler-config>`.
Before 2024, the directory structure was different::
@@ -162,5 +200,15 @@ You can also use Spack-installed libraries. For example::
Will update your environment CPATH, LIBRARY_PATH, and other
environment variables for building an application with oneMKL.
More information
================
This section describes basic use of oneAPI, especially if it has
changed compared to Parallel Studio. See :ref:`intelpackage` for more
information on :ref:`intel-virtual-packages`,
:ref:`intel-unrelated-packages`,
:ref:`intel-integrating-external-libraries`, and
:ref:`using-mkl-tips`.
.. _`Intel installers`: https://software.intel.com/content/www/us/en/develop/documentation/installation-guide-for-intel-oneapi-toolkits-linux/top.html

File diff suppressed because it is too large Load Diff

View File

@@ -91,7 +91,7 @@ there are any other variables you need to set, you can do this in the
.. code-block:: python
def setup_build_environment(self, env: EnvironmentModifications) -> None:
def setup_build_environment(self, env):
env.set("PREFIX", prefix)
env.set("BLASLIB", spec["blas"].libs.ld_flags)

View File

@@ -12,7 +12,8 @@ The ``ROCmPackage`` is not a build system but a helper package. Like ``CudaPacka
it provides standard variants, dependencies, and conflicts to facilitate building
packages using GPUs though for AMD in this case.
You can find the source for this package (and suggestions for setting up your ``packages.yaml`` file) at
You can find the source for this package (and suggestions for setting up your
``compilers.yaml`` and ``packages.yaml`` files) at
`<https://github.com/spack/spack/blob/develop/lib/spack/spack/build_systems/rocm.py>`__.
^^^^^^^^

View File

@@ -225,10 +225,8 @@ def setup(sphinx):
("py:class", "llnl.util.lang.T"),
("py:class", "llnl.util.lang.KT"),
("py:class", "llnl.util.lang.VT"),
("py:class", "llnl.util.lang.ClassPropertyType"),
("py:obj", "llnl.util.lang.KT"),
("py:obj", "llnl.util.lang.VT"),
("py:obj", "llnl.util.lang.ClassPropertyType"),
]
# The reST default role (used for this markup: `text`) to use for all documents.

View File

@@ -148,16 +148,15 @@ this can expose you to attacks. Use at your own risk.
``ssl_certs``
--------------------
Path to custom certificats for SSL verification. The value can be a
Path to custom certificats for SSL verification. The value can be a
filesytem path, or an environment variable that expands to an absolute file path.
The default value is set to the environment variable ``SSL_CERT_FILE``
to use the same syntax used by many other applications that automatically
detect custom certificates.
When ``url_fetch_method:curl`` the ``config:ssl_certs`` should resolve to
a single file. Spack will then set the environment variable ``CURL_CA_BUNDLE``
in the subprocess calling ``curl``. If additional ``curl`` arguments are required,
they can be set in the config, e.g. ``url_fetch_method:'curl -k -q'``.
If ``url_fetch_method:urllib`` then files and directories are supported i.e.
in the subprocess calling ``curl``.
If ``url_fetch_method:urllib`` then files and directories are supported i.e.
``config:ssl_certs:$SSL_CERT_FILE`` or ``config:ssl_certs:$SSL_CERT_DIR``
will work.
In all cases the expanded path must be absolute for Spack to use the certificates.

View File

@@ -11,7 +11,7 @@ Configuration Files
Spack has many configuration files. Here is a quick list of them, in
case you want to skip directly to specific docs:
* :ref:`packages.yaml <compiler-config>`
* :ref:`compilers.yaml <compiler-config>`
* :ref:`concretizer.yaml <concretizer-options>`
* :ref:`config.yaml <config-yaml>`
* :ref:`include.yaml <include-yaml>`
@@ -46,12 +46,6 @@ Each Spack configuration file is nested under a top-level section
corresponding to its name. So, ``config.yaml`` starts with ``config:``,
``mirrors.yaml`` starts with ``mirrors:``, etc.
.. tip::
Validation and autocompletion of Spack config files can be enabled in
your editor with the YAML language server. See `spack/schemas
<https://github.com/spack/schemas>`_ for more information.
.. _configuration-scopes:
--------------------
@@ -101,7 +95,7 @@ are six configuration scopes. From lowest to highest:
precedence over all other scopes.
Each configuration directory may contain several configuration files,
such as ``config.yaml``, ``packages.yaml``, or ``mirrors.yaml``. When
such as ``config.yaml``, ``compilers.yaml``, or ``mirrors.yaml``. When
configurations conflict, settings from higher-precedence scopes override
lower-precedence settings.

View File

@@ -1,34 +0,0 @@
.. Copyright Spack Project Developers. See COPYRIGHT file for details.
SPDX-License-Identifier: (Apache-2.0 OR MIT)
.. _env-vars-yaml:
=============================================
Environment Variable Settings (env_vars.yaml)
=============================================
Spack allows you to include shell environment variable modifications
for a spack environment by including an ``env_vars.yaml``. Environment
varaibles can be modified by setting, unsetting, appending, and prepending
variables in the shell environment.
The changes to the shell environment will take effect when the spack
environment is activated.
for example,
.. code-block:: yaml
env_vars:
set:
ENVAR_TO_SET_IN_ENV_LOAD: "FOO"
unset:
ENVAR_TO_UNSET_IN_ENV_LOAD:
prepend_path:
PATH_LIST: "path/to/prepend"
append_path:
PATH_LIST: "path/to/append"
remove_path:
PATH_LIST: "path/to/remove"

View File

@@ -667,11 +667,11 @@ a ``packages.yaml`` file) could contain:
# ...
packages:
all:
providers:
mpi: [openmpi]
compiler: [intel]
# ...
This configuration sets the default mpi provider to be openmpi.
This configuration sets the default compiler for all packages to
``intel``.
^^^^^^^^^^^^^^^^^^^^^^^
Included configurations
@@ -686,8 +686,7 @@ the environment.
spack:
include:
- environment/relative/path/to/config.yaml
- path: https://github.com/path/to/raw/config/compilers.yaml
sha256: 26e871804a92cd07bb3d611b31b4156ae93d35b6a6d6e0ef3a67871fcb1d258b
- https://github.com/path/to/raw/config/compilers.yaml
- /absolute/path/to/packages.yaml
- path: /path/to/$os/$target/environment
optional: true
@@ -701,11 +700,11 @@ with the ``optional`` clause and conditional with the ``when`` clause. (See
Files are listed using paths to individual files or directories containing them.
Path entries may be absolute or relative to the environment or specified as
URLs. URLs to individual files must link to the **raw** form of the file's
URLs. URLs to individual files need link to the **raw** form of the file's
contents (e.g., `GitHub
<https://docs.github.com/en/repositories/working-with-files/using-files/viewing-and-understanding-files#viewing-or-copying-the-raw-file-content>`_
or `GitLab
<https://docs.gitlab.com/ee/api/repository_files.html#get-raw-file-from-repository>`_) **and** include a valid sha256 for the file.
<https://docs.gitlab.com/ee/api/repository_files.html#get-raw-file-from-repository>`_).
Only the ``file``, ``ftp``, ``http`` and ``https`` protocols (or schemes) are
supported. Spack-specific, environment and user path variables can be used.
(See :ref:`config-file-variables` for more information.)
@@ -1000,28 +999,6 @@ For example, the following environment has three root packages:
This allows for a much-needed reduction in redundancy between packages
and constraints.
-------------------------------
Modifying Environment Variables
-------------------------------
Spack Environments can modify the active shell's environment variables when activated. The environment can be
configured to set, unset, prepend, or append using ``env_vars`` configuration in the ``spack.yaml`` or through config scopes
file:
.. code-block:: yaml
spack:
env_vars:
set:
ENVAR_TO_SET_IN_ENV_LOAD: "FOO"
unset:
ENVAR_TO_UNSET_IN_ENV_LOAD:
prepend_path:
PATH_LIST: "path/to/prepend"
append_path:
PATH_LIST: "path/to/append"
remove_path:
PATH_LIST: "path/to/remove"
-----------------
Environment Views

View File

@@ -0,0 +1,161 @@
spack:
definitions:
- compiler-pkgs:
- 'llvm+clang@6.0.1 os=centos7'
- 'gcc@6.5.0 os=centos7'
- 'llvm+clang@6.0.1 os=ubuntu18.04'
- 'gcc@6.5.0 os=ubuntu18.04'
- pkgs:
- readline@7.0
# - xsdk@0.4.0
- compilers:
- '%gcc@5.5.0'
- '%gcc@6.5.0'
- '%gcc@7.3.0'
- '%clang@6.0.0'
- '%clang@6.0.1'
- oses:
- os=ubuntu18.04
- os=centos7
specs:
- matrix:
- [$pkgs]
- [$compilers]
- [$oses]
exclude:
- '%gcc@7.3.0 os=centos7'
- '%gcc@5.5.0 os=ubuntu18.04'
mirrors:
cloud_gitlab: https://mirror.spack.io
compilers:
# The .gitlab-ci.yml for this project picks a Docker container which does
# not have any compilers pre-built and ready to use, so we need to fake the
# existence of those here.
- compiler:
operating_system: centos7
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: gcc@5.5.0
target: x86_64
- compiler:
operating_system: centos7
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: gcc@6.5.0
target: x86_64
- compiler:
operating_system: centos7
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: clang@6.0.0
target: x86_64
- compiler:
operating_system: centos7
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: clang@6.0.1
target: x86_64
- compiler:
operating_system: ubuntu18.04
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: clang@6.0.0
target: x86_64
- compiler:
operating_system: ubuntu18.04
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: clang@6.0.1
target: x86_64
- compiler:
operating_system: ubuntu18.04
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: gcc@6.5.0
target: x86_64
- compiler:
operating_system: ubuntu18.04
modules: []
paths:
cc: /not/used
cxx: /not/used
f77: /not/used
fc: /not/used
spec: gcc@7.3.0
target: x86_64
gitlab-ci:
bootstrap:
- name: compiler-pkgs
compiler-agnostic: true
mappings:
- # spack-cloud-ubuntu
match:
# these are specs, if *any* match the spec under consideration, this
# 'mapping' will be used to generate the CI job
- os=ubuntu18.04
runner-attributes:
# 'tags' and 'image' go directly onto the job, 'variables' will
# be added to what we already necessarily create for the job as
# a part of the CI workflow
tags:
- spack-k8s
image:
name: scottwittenburg/spack_builder_ubuntu_18.04
entrypoint: [""]
- # spack-cloud-centos
match:
# these are specs, if *any* match the spec under consideration, this
# 'mapping' will be used to generate the CI job
- 'os=centos7'
runner-attributes:
tags:
- spack-k8s
image:
name: scottwittenburg/spack_builder_centos_7
entrypoint: [""]
cdash:
build-group: Release Testing
url: http://cdash
project: Spack Testing
site: Spack Docker-Compose Workflow
repos: []
upstreams: {}
modules:
enable: []
packages: {}
config: {}

View File

@@ -254,11 +254,12 @@ directory.
Compiler configuration
----------------------
Spack has the ability to build packages with multiple compilers and compiler versions.
Compilers can be made available to Spack by specifying them manually in ``packages.yaml``,
or automatically by running ``spack compiler find``.
For convenience, Spack will automatically detect compilers the first time it needs them,
if none is available.
Spack has the ability to build packages with multiple compilers and
compiler versions. Compilers can be made available to Spack by
specifying them manually in ``compilers.yaml`` or ``packages.yaml``,
or automatically by running ``spack compiler find``, but for
convenience Spack will automatically detect compilers the first time
it needs them.
.. _cmd-spack-compilers:
@@ -273,11 +274,16 @@ compilers`` or ``spack compiler list``:
$ spack compilers
==> Available compilers
-- gcc ubuntu20.04-x86_64 ---------------------------------------
gcc@9.4.0 gcc@8.4.0 gcc@10.5.0
-- llvm ubuntu20.04-x86_64 --------------------------------------
llvm@12.0.0 llvm@11.0.0 llvm@10.0.0
-- gcc ---------------------------------------------------------
gcc@4.9.0 gcc@4.8.0 gcc@4.7.0 gcc@4.6.2 gcc@4.4.7
gcc@4.8.2 gcc@4.7.1 gcc@4.6.3 gcc@4.6.1 gcc@4.1.2
-- intel -------------------------------------------------------
intel@15.0.0 intel@14.0.0 intel@13.0.0 intel@12.1.0 intel@10.0
intel@14.0.3 intel@13.1.1 intel@12.1.5 intel@12.0.4 intel@9.1
intel@14.0.2 intel@13.1.0 intel@12.1.3 intel@11.1
intel@14.0.1 intel@13.0.1 intel@12.1.2 intel@10.1
-- clang -------------------------------------------------------
clang@3.4 clang@3.3 clang@3.2 clang@3.1
Any of these compilers can be used to build Spack packages. More on
how this is done is in :ref:`sec-specs`.
@@ -296,22 +302,16 @@ An alias for ``spack compiler find``.
``spack compiler find``
^^^^^^^^^^^^^^^^^^^^^^^
If you do not see a compiler in the list shown by:
Lists the compilers currently available to Spack. If you do not see
a compiler in this list, but you want to use it with Spack, you can
simply run ``spack compiler find`` with the path to where the
compiler is installed. For example:
.. code-block:: console
$ spack compiler list
but you want to use it with Spack, you can simply run ``spack compiler find`` with the
path to where the compiler is installed. For example:
.. code-block:: console
$ spack compiler find /opt/intel/oneapi/compiler/2025.1/bin/
==> Added 1 new compiler to /home/user/.spack/packages.yaml
intel-oneapi-compilers@2025.1.0
==> Compilers are defined in the following files:
/home/user/.spack/packages.yaml
$ spack compiler find /usr/local/tools/ic-13.0.079
==> Added 1 new compiler to ~/.spack/linux/compilers.yaml
intel@13.0.079
Or you can run ``spack compiler find`` with no arguments to force
auto-detection. This is useful if you do not know where compilers are
@@ -322,7 +322,7 @@ installed, but you know that new compilers have been added to your
$ module load gcc/4.9.0
$ spack compiler find
==> Added 1 new compiler to /home/user/.spack/packages.yaml
==> Added 1 new compiler to ~/.spack/linux/compilers.yaml
gcc@4.9.0
This loads the environment module for gcc-4.9.0 to add it to
@@ -331,7 +331,7 @@ This loads the environment module for gcc-4.9.0 to add it to
.. note::
By default, spack does not fill in the ``modules:`` field in the
``packages.yaml`` file. If you are using a compiler from a
``compilers.yaml`` file. If you are using a compiler from a
module, then you should add this field manually.
See the section on :ref:`compilers-requiring-modules`.
@@ -341,82 +341,91 @@ This loads the environment module for gcc-4.9.0 to add it to
``spack compiler info``
^^^^^^^^^^^^^^^^^^^^^^^
If you want to see additional information on some specific compilers, you can run ``spack compiler info`` on it:
If you want to see specifics on a particular compiler, you can run
``spack compiler info`` on it:
.. code-block:: console
$ spack compiler info gcc
gcc@=8.4.0 languages='c,c++,fortran' arch=linux-ubuntu20.04-x86_64:
prefix: /usr
compilers:
c: /usr/bin/gcc-8
cxx: /usr/bin/g++-8
fortran: /usr/bin/gfortran-8
$ spack compiler info intel@15
intel@15.0.0:
paths:
cc = /usr/local/bin/icc-15.0.090
cxx = /usr/local/bin/icpc-15.0.090
f77 = /usr/local/bin/ifort-15.0.090
fc = /usr/local/bin/ifort-15.0.090
modules = []
operating_system = centos6
...
gcc@=9.4.0 languages='c,c++,fortran' arch=linux-ubuntu20.04-x86_64:
prefix: /usr
compilers:
c: /usr/bin/gcc
cxx: /usr/bin/g++
fortran: /usr/bin/gfortran
gcc@=10.5.0 languages='c,c++,fortran' arch=linux-ubuntu20.04-x86_64:
prefix: /usr
compilers:
c: /usr/bin/gcc-10
cxx: /usr/bin/g++-10
fortran: /usr/bin/gfortran-10
This shows the details of the compilers that were detected by Spack.
Notice also that we didn't have to be too specific about the version. We just said ``gcc``, and we got information
about all the matching compilers.
This shows which C, C++, and Fortran compilers were detected by Spack.
Notice also that we didn't have to be too specific about the
version. We just said ``intel@15``, and information about the only
matching Intel compiler was displayed.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Manual compiler configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If auto-detection fails, you can manually configure a compiler by editing your ``~/.spack/packages.yaml`` file.
You can do this by running ``spack config edit packages``, which will open the file in
If auto-detection fails, you can manually configure a compiler by
editing your ``~/.spack/<platform>/compilers.yaml`` file. You can do this by running
``spack config edit compilers``, which will open the file in
:ref:`your favorite editor <controlling-the-editor>`.
Each compiler has an "external" entry in the file with some ``extra_attributes``:
Each compiler configuration in the file looks like this:
.. code-block:: yaml
packages:
gcc:
externals:
- spec: gcc@10.5.0 languages='c,c++,fortran'
prefix: /usr
extra_attributes:
compilers:
c: /usr/bin/gcc-10
cxx: /usr/bin/g++-10
fortran: /usr/bin/gfortran-10
compilers:
- compiler:
modules: []
operating_system: centos6
paths:
cc: /usr/local/bin/icc-15.0.024-beta
cxx: /usr/local/bin/icpc-15.0.024-beta
f77: /usr/local/bin/ifort-15.0.024-beta
fc: /usr/local/bin/ifort-15.0.024-beta
spec: intel@15.0.0
The compiler executables are listed under ``extra_attributes:compilers``, and are keyed by language.
Once you save the file, the configured compilers will show up in the list displayed by ``spack compilers``.
For compilers that do not support Fortran (like ``clang``), put
``None`` for ``f77`` and ``fc``:
You can also add compiler flags to manually configured compilers. These flags should be specified in the
``flags`` section of the compiler specification. The valid flags are ``cflags``, ``cxxflags``, ``fflags``,
.. code-block:: yaml
compilers:
- compiler:
modules: []
operating_system: centos6
paths:
cc: /usr/bin/clang
cxx: /usr/bin/clang++
f77: None
fc: None
spec: clang@3.3svn
Once you save the file, the configured compilers will show up in the
list displayed by ``spack compilers``.
You can also add compiler flags to manually configured compilers. These
flags should be specified in the ``flags`` section of the compiler
specification. The valid flags are ``cflags``, ``cxxflags``, ``fflags``,
``cppflags``, ``ldflags``, and ``ldlibs``. For example:
.. code-block:: yaml
packages:
gcc:
externals:
- spec: gcc@10.5.0 languages='c,c++,fortran'
prefix: /usr
extra_attributes:
compilers:
c: /usr/bin/gcc-10
cxx: /usr/bin/g++-10
fortran: /usr/bin/gfortran-10
flags:
cflags: -O3 -fPIC
cxxflags: -O3 -fPIC
cppflags: -O3 -fPIC
compilers:
- compiler:
modules: []
operating_system: centos6
paths:
cc: /usr/bin/gcc
cxx: /usr/bin/g++
f77: /usr/bin/gfortran
fc: /usr/bin/gfortran
flags:
cflags: -O3 -fPIC
cxxflags: -O3 -fPIC
cppflags: -O3 -fPIC
spec: gcc@4.7.2
These flags will be treated by spack as if they were entered from
the command line each time this compiler is used. The compiler wrappers
@@ -431,44 +440,95 @@ These variables should be specified in the ``environment`` section of the compil
specification. The operations available to modify the environment are ``set``, ``unset``,
``prepend_path``, ``append_path``, and ``remove_path``. For example:
.. code-block:: yaml
compilers:
- compiler:
modules: []
operating_system: centos6
paths:
cc: /opt/intel/oneapi/compiler/latest/linux/bin/icx
cxx: /opt/intel/oneapi/compiler/latest/linux/bin/icpx
f77: /opt/intel/oneapi/compiler/latest/linux/bin/ifx
fc: /opt/intel/oneapi/compiler/latest/linux/bin/ifx
spec: oneapi@latest
environment:
set:
MKL_ROOT: "/path/to/mkl/root"
unset: # A list of environment variables to unset
- CC
prepend_path: # Similar for append|remove_path
LD_LIBRARY_PATH: /ld/paths/added/by/setvars/sh
.. note::
Spack is in the process of moving compilers from a separate
attribute to be handled like all other packages. As part of this
process, the ``compilers.yaml`` section will eventually be replaced
by configuration in the ``packages.yaml`` section. This new
configuration is now available, although it is not yet the default
behavior.
Compilers can also be configured as external packages in the
``packages.yaml`` config file. Any external package for a compiler
(e.g. ``gcc`` or ``llvm``) will be treated as a configured compiler
assuming the paths to the compiler executables are determinable from
the prefix.
If the paths to the compiler executable are not determinable from the
prefix, you can add them to the ``extra_attributes`` field. Similarly,
all other fields from the compilers config can be added to the
``extra_attributes`` field for an external representing a compiler.
Note that the format for the ``paths`` field in the
``extra_attributes`` section is different than in the ``compilers``
config. For compilers configured as external packages, the section is
named ``compilers`` and the dictionary maps language names (``c``,
``cxx``, ``fortran``) to paths, rather than using the names ``cc``,
``fc``, and ``f77``.
.. code-block:: yaml
packages:
intel-oneapi-compilers:
externals:
- spec: intel-oneapi-compilers@2025.1.0
prefix: /opt/intel/oneapi
gcc:
external:
- spec: gcc@12.2.0 arch=linux-rhel8-skylake
prefix: /usr
extra_attributes:
compilers:
c: /opt/intel/oneapi/compiler/2025.1/bin/icx
cxx: /opt/intel/oneapi/compiler/2025.1/bin/icpx
fortran: /opt/intel/oneapi/compiler/2025.1/bin/ifx
environment:
set:
MKL_ROOT: "/path/to/mkl/root"
unset: # A list of environment variables to unset
- CC
prepend_path: # Similar for append|remove_path
LD_LIBRARY_PATH: /ld/paths/added/by/setvars/sh
GCC_ROOT: /usr
external:
- spec: llvm+clang@15.0.0 arch=linux-rhel8-skylake
prefix: /usr
extra_attributes:
compilers:
c: /usr/bin/clang-with-suffix
cxx: /usr/bin/clang++-with-extra-info
fortran: /usr/bin/gfortran
extra_rpaths:
- /usr/lib/llvm/
^^^^^^^^^^^^^^^^^^^^^^^
Build Your Own Compiler
^^^^^^^^^^^^^^^^^^^^^^^
If you are particular about which compiler/version you use, you might wish to have Spack build it for you.
For example:
If you are particular about which compiler/version you use, you might
wish to have Spack build it for you. For example:
.. code-block:: console
$ spack install gcc@14+binutils
$ spack install gcc@4.9.3
Once the compiler is installed, you can start using it without additional configuration:
Once that has finished, you will need to add it to your
``compilers.yaml`` file. You can then set Spack to use it by default
by adding the following to your ``packages.yaml`` file:
.. code-block:: console
.. code-block:: yaml
$ spack install hdf5~mpi %gcc@14
The same holds true for compilers that are made available from buildcaches, when reusing them is allowed.
packages:
all:
compiler: [gcc@4.9.3]
.. _compilers-requiring-modules:
@@ -476,26 +536,30 @@ The same holds true for compilers that are made available from buildcaches, when
Compilers Requiring Modules
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Many installed compilers will work regardless of the environment they are called with.
However, some installed compilers require environment variables to be set in order to run;
this is typical for Intel and other proprietary compilers.
Many installed compilers will work regardless of the environment they
are called with. However, some installed compilers require
``$LD_LIBRARY_PATH`` or other environment variables to be set in order
to run; this is typical for Intel and other proprietary compilers.
On typical HPC clusters, these environment modifications are usually delegated to some "module" system.
In such a case, you should tell Spack which module(s) to load in order to run the chosen compiler:
In such a case, you should tell Spack which module(s) to load in order
to run the chosen compiler (If the compiler does not come with a
module file, you might consider making one by hand). Spack will load
this module into the environment ONLY when the compiler is run, and
NOT in general for a package's ``install()`` method. See, for
example, this ``compilers.yaml`` file:
.. code-block:: yaml
packages:
gcc:
externals:
- spec: gcc@10.5.0 languages='c,c++,fortran'
prefix: /opt/compilers
extra_attributes:
compilers:
c: /opt/compilers/bin/gcc-10
cxx: /opt/compilers/bin/g++-10
fortran: /opt/compilers/bin/gfortran-10
modules: [gcc/10.5.0]
compilers:
- compiler:
modules: [other/comp/gcc-5.3-sp3]
operating_system: SuSE11
paths:
cc: /usr/local/other/SLES11.3/gcc/5.3.0/bin/gcc
cxx: /usr/local/other/SLES11.3/gcc/5.3.0/bin/g++
f77: /usr/local/other/SLES11.3/gcc/5.3.0/bin/gfortran
fc: /usr/local/other/SLES11.3/gcc/5.3.0/bin/gfortran
spec: gcc@5.3.0
Some compilers require special environment settings to be loaded not just
to run, but also to execute the code they build, breaking packages that
@@ -516,7 +580,7 @@ Licensed Compilers
^^^^^^^^^^^^^^^^^^
Some proprietary compilers require licensing to use. If you need to
use a licensed compiler, the process is similar to a mix of
use a licensed compiler (eg, PGI), the process is similar to a mix of
build your own, plus modules:
#. Create a Spack package (if it doesn't exist already) to install
@@ -526,21 +590,24 @@ build your own, plus modules:
using Spack to load the module it just created, and running simple
builds (eg: ``cc helloWorld.c && ./a.out``)
#. Add the newly-installed compiler to ``packages.yaml`` as shown above.
#. Add the newly-installed compiler to ``compilers.yaml`` as shown
above.
.. _mixed-toolchains:
^^^^^^^^^^^^^^^^^^^^^^^^^^
Fortran compilers on macOS
^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^
Mixed Toolchains
^^^^^^^^^^^^^^^^
Modern compilers typically come with related compilers for C, C++ and
Fortran bundled together. When possible, results are best if the same
compiler is used for all languages.
In some cases, this is not possible. For example, XCode on macOS provides no Fortran compilers.
The user is therefore forced to use a mixed toolchain: XCode-provided Clang for C/C++ and e.g.
GNU ``gfortran`` for Fortran.
In some cases, this is not possible. For example, starting with macOS El
Capitan (10.11), many packages no longer build with GCC, but XCode
provides no Fortran compilers. The user is therefore forced to use a
mixed toolchain: XCode-provided Clang for C/C++ and GNU ``gfortran`` for
Fortran.
#. You need to make sure that Xcode is installed. Run the following command:
@@ -593,25 +660,45 @@ GNU ``gfortran`` for Fortran.
Note: the flag is ``-license``, not ``--license``.
#. Run ``spack compiler find`` to locate Clang.
#. There are different ways to get ``gfortran`` on macOS. For example, you can
install GCC with Spack (``spack install gcc``), with Homebrew (``brew install
gcc``), or from a `DMG installer
<https://github.com/fxcoudert/gfortran-for-macOS/releases>`_.
#. Run ``spack compiler find`` to locate both Apple-Clang and GCC.
#. The only thing left to do is to edit ``~/.spack/darwin/compilers.yaml`` to provide
the path to ``gfortran``:
Since languages in Spack are modeled as virtual packages, ``apple-clang`` will be used to provide
C and C++, while GCC will be used for Fortran.
.. code-block:: yaml
compilers:
- compiler:
# ...
paths:
cc: /usr/bin/clang
cxx: /usr/bin/clang++
f77: /path/to/bin/gfortran
fc: /path/to/bin/gfortran
spec: apple-clang@11.0.0
If you used Spack to install GCC, you can get the installation prefix by
``spack location -i gcc`` (this will only work if you have a single version
of GCC installed). Whereas for Homebrew, GCC is installed in
``/usr/local/Cellar/gcc/x.y.z``. With the DMG installer, the correct path
will be ``/usr/local/gfortran``.
^^^^^^^^^^^^^^^^^^^^^
Compiler Verification
^^^^^^^^^^^^^^^^^^^^^
You can verify that your compilers are configured properly by installing a simple package. For example:
You can verify that your compilers are configured properly by installing a
simple package. For example:
.. code-block:: console
$ spack install zlib-ng%gcc@5.3.0
$ spack install zlib%gcc@5.3.0
.. _vendor-specific-compiler-configuration:
@@ -620,7 +707,9 @@ You can verify that your compilers are configured properly by installing a simpl
Vendor-Specific Compiler Configuration
--------------------------------------
This section provides details on how to get vendor-specific compilers working.
With Spack, things usually "just work" with GCC. Not so for other
compilers. This section provides details on how to get specific
compilers working.
^^^^^^^^^^^^^^^
Intel Compilers
@@ -642,8 +731,8 @@ compilers:
you have installed from the ``PATH`` environment variable.
If you want use a version of ``gcc`` or ``g++`` other than the default
version on your system, you need to use either the ``--gcc-install-dir``
or ``--gcc-toolchain`` compiler option to specify the path to the version of
version on your system, you need to use either the ``-gcc-name``
or ``-gxx-name`` compiler option to specify the path to the version of
``gcc`` or ``g++`` that you want to use."
-- `Intel Reference Guide <https://software.intel.com/en-us/node/522750>`_
@@ -651,12 +740,76 @@ compilers:
Intel compilers may therefore be configured in one of two ways with
Spack: using modules, or using compiler flags.
""""""""""""""""""""""""""
Configuration with Modules
""""""""""""""""""""""""""
One can control which GCC is seen by the Intel compiler with modules.
A module must be loaded both for the Intel Compiler (so it will run)
and GCC (so the compiler can find the intended GCC). The following
configuration in ``compilers.yaml`` illustrates this technique:
.. code-block:: yaml
compilers:
- compiler:
modules: [gcc-4.9.3, intel-15.0.24]
operating_system: centos7
paths:
cc: /opt/intel-15.0.24/bin/icc-15.0.24-beta
cxx: /opt/intel-15.0.24/bin/icpc-15.0.24-beta
f77: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
fc: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
spec: intel@15.0.24.4.9.3
.. note::
The version number on the Intel compiler is a combination of
the "native" Intel version number and the GNU compiler it is
targeting.
""""""""""""""""""""""""""
Command Line Configuration
""""""""""""""""""""""""""
One can also control which GCC is seen by the Intel compiler by adding
flags to the ``icc`` command:
#. Identify the location of the compiler you just installed:
.. code-block:: console
$ spack location --install-dir gcc
~/spack/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw...
#. Set up ``compilers.yaml``, for example:
.. code-block:: yaml
compilers:
- compiler:
modules: [intel-15.0.24]
operating_system: centos7
paths:
cc: /opt/intel-15.0.24/bin/icc-15.0.24-beta
cxx: /opt/intel-15.0.24/bin/icpc-15.0.24-beta
f77: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
fc: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
flags:
cflags: -gcc-name ~/spack/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw.../bin/gcc
cxxflags: -gxx-name ~/spack/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw.../bin/g++
fflags: -gcc-name ~/spack/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw.../bin/gcc
spec: intel@15.0.24.4.9.3
^^^
NAG
^^^
The Numerical Algorithms Group provides a licensed Fortran compiler.
It is recommended to use GCC for your C/C++ compilers.
The Numerical Algorithms Group provides a licensed Fortran compiler. Like Clang,
this requires you to set up a :ref:`mixed-toolchains`. It is recommended to use
GCC for your C/C++ compilers.
The NAG Fortran compilers are a bit more strict than other compilers, and many
packages will fail to install with error messages like:
@@ -673,40 +826,44 @@ the command line:
$ spack install openmpi fflags="-mismatch"
Or it can be set permanently in your ``packages.yaml``:
Or it can be set permanently in your ``compilers.yaml``:
.. code-block:: yaml
packages:
nag:
externals:
- spec: nag@6.1
prefix: /opt/nag/bin
extra_attributes:
compilers:
fortran: /opt/nag/bin/nagfor
flags:
fflags: -mismatch
- compiler:
modules: []
operating_system: centos6
paths:
cc: /soft/spack/opt/spack/linux-x86_64/gcc-5.3.0/gcc-6.1.0-q2zosj3igepi3pjnqt74bwazmptr5gpj/bin/gcc
cxx: /soft/spack/opt/spack/linux-x86_64/gcc-5.3.0/gcc-6.1.0-q2zosj3igepi3pjnqt74bwazmptr5gpj/bin/g++
f77: /soft/spack/opt/spack/linux-x86_64/gcc-4.4.7/nag-6.1-jt3h5hwt5myezgqguhfsan52zcskqene/bin/nagfor
fc: /soft/spack/opt/spack/linux-x86_64/gcc-4.4.7/nag-6.1-jt3h5hwt5myezgqguhfsan52zcskqene/bin/nagfor
flags:
fflags: -mismatch
spec: nag@6.1
---------------
System Packages
---------------
Once compilers are configured, one needs to determine which pre-installed system packages,
if any, to use in builds. These are also configured in the ``~/.spack/packages.yaml`` file.
For example, to use an OpenMPI installed in /opt/local, one would use:
Once compilers are configured, one needs to determine which
pre-installed system packages, if any, to use in builds. This is
configured in the file ``~/.spack/packages.yaml``. For example, to use
an OpenMPI installed in /opt/local, one would use:
.. code-block:: yaml
packages:
openmpi:
buildable: False
externals:
- spec: openmpi@1.10.1
prefix: /opt/local
packages:
openmpi:
externals:
- spec: openmpi@1.10.1
prefix: /opt/local
buildable: False
In general, *Spack is easier to use and more reliable if it builds all of its own dependencies*.
However, there are several packages for which one commonly needs to use system versions:
In general, Spack is easier to use and more reliable if it builds all of
its own dependencies. However, there are several packages for which one
commonly needs to use system versions:
^^^
MPI
@@ -719,7 +876,8 @@ you are unlikely to get a working MPI from Spack. Instead, use an
appropriate pre-installed MPI.
If you choose a pre-installed MPI, you should consider using the
pre-installed compiler used to build that MPI.
pre-installed compiler used to build that MPI; see above on
``compilers.yaml``.
^^^^^^^
OpenSSL
@@ -1283,9 +1441,9 @@ To configure Spack, first run the following command inside the Spack console:
spack compiler find
This creates a ``.staging`` directory in our Spack prefix, along with a ``windows`` subdirectory
containing a ``packages.yaml`` file. On a fresh Windows install with the above packages
containing a ``compilers.yaml`` file. On a fresh Windows install with the above packages
installed, this command should only detect Microsoft Visual Studio and the Intel Fortran
compiler will be integrated within the first version of MSVC present in the ``packages.yaml``
compiler will be integrated within the first version of MSVC present in the ``compilers.yaml``
output.
Spack provides a default ``config.yaml`` file for Windows that it will use unless overridden.

View File

@@ -23,6 +23,7 @@ components for use by dependent packages:
packages:
all:
compiler: [rocmcc@=5.3.0]
variants: amdgpu_target=gfx90a
hip:
buildable: false
@@ -69,15 +70,16 @@ This is in combination with the following compiler definition:
.. code-block:: yaml
packages:
llvm-amdgpu:
externals:
- spec: llvm-amdgpu@=5.3.0
prefix: /opt/rocm-5.3.0
compilers:
c: /opt/rocm-5.3.0/bin/amdclang
cxx: /opt/rocm-5.3.0/bin/amdclang++
fortran: null
compilers:
- compiler:
spec: rocmcc@=5.3.0
paths:
cc: /opt/rocm-5.3.0/bin/amdclang
cxx: /opt/rocm-5.3.0/bin/amdclang++
f77: null
fc: /opt/rocm-5.3.0/bin/amdflang
operating_system: rhel8
target: x86_64
This includes the following considerations:

View File

@@ -43,20 +43,6 @@ or specified as URLs. Only the ``file``, ``ftp``, ``http`` and ``https`` protoco
schemes) are supported. Spack-specific, environment and user path variables
can be used. (See :ref:`config-file-variables` for more information.)
A ``sha256`` is required for remote file URLs and must be specified as follows:
.. code-block:: yaml
include:
- path: https://github.com/path/to/raw/config/compilers.yaml
sha256: 26e871804a92cd07bb3d611b31b4156ae93d35b6a6d6e0ef3a67871fcb1d258b
Additionally, remote file URLs must link to the **raw** form of the file's
contents (e.g., `GitHub
<https://docs.github.com/en/repositories/working-with-files/using-files/viewing-and-understanding-files#viewing-or-copying-the-raw-file-content>`_
or `GitLab
<https://docs.gitlab.com/ee/api/repository_files.html#get-raw-file-from-repository>`_).
.. warning::
Recursive includes are not currently processed in a breadth-first manner

View File

@@ -75,7 +75,6 @@ or refer to the full manual below.
packages_yaml
build_settings
environments
env_vars_yaml
containers
mirrors
module_file_support

View File

@@ -128,7 +128,7 @@ depend on the spec:
.. code-block:: python
def setup_run_environment(self, env: EnvironmentModifications) -> None:
def setup_run_environment(self, env):
if self.spec.satisfies("+foo"):
env.set("FOO", "bar")
@@ -142,7 +142,7 @@ For example, a simplified version of the ``python`` package could look like this
.. code-block:: python
def setup_dependent_run_environment(self, env: EnvironmentModifications, dependent_spec: Spec) -> None:
def setup_dependent_run_environment(self, env, dependent_spec):
if dependent_spec.package.extends(self.spec):
env.prepend_path("PYTHONPATH", dependent_spec.prefix.lib.python)

View File

@@ -557,13 +557,14 @@ preferences.
FAQ: :ref:`Why does Spack pick particular versions and variants? <faq-concretizer-precedence>`
The ``target`` and ``providers`` preferences
Most package preferences (``compilers``, ``target`` and ``providers``)
can only be set globally under the ``all`` section of ``packages.yaml``:
.. code-block:: yaml
packages:
all:
compiler: [gcc@12.2.0, clang@12:, oneapi@2023:]
target: [x86_64_v3]
providers:
mpi: [mvapich2, mpich, openmpi]

View File

@@ -5,9 +5,9 @@ sphinx-rtd-theme==3.0.2
python-levenshtein==0.27.1
docutils==0.21.2
pygments==2.19.1
urllib3==2.4.0
urllib3==2.3.0
pytest==8.3.5
isort==6.0.1
black==25.1.0
flake8==7.2.0
flake8==7.1.2
mypy==1.11.1

View File

@@ -11,7 +11,6 @@
* Homepage: https://altgraph.readthedocs.io/en/latest/index.html
* Usage: dependency of macholib
* Version: 0.17.3
* License: MIT
archspec
--------
@@ -19,7 +18,6 @@
* Homepage: https://pypi.python.org/pypi/archspec
* Usage: Labeling, comparison and detection of microarchitectures
* Version: 0.2.5 (commit 38ce485258ffc4fc6dd6688f8dc90cb269478c47)
* License: Apache-2.0 or MIT
astunparse
----------------
@@ -27,7 +25,6 @@
* Homepage: https://github.com/simonpercivall/astunparse
* Usage: Unparsing Python ASTs for package hashes in Spack
* Version: 1.6.3 (plus modifications)
* License: PSF-2.0
* Note: This is in ``spack.util.unparse`` because it's very heavily
modified, and we want to track coverage for it.
Specifically, we have modified this library to generate consistent unparsed ASTs
@@ -44,7 +41,6 @@
* Homepage: https://github.com/python-attrs/attrs
* Usage: Needed by jsonschema.
* Version: 22.1.0
* License: MIT
ctest_log_parser
----------------
@@ -52,7 +48,6 @@
* Homepage: https://github.com/Kitware/CMake/blob/master/Source/CTest/cmCTestBuildHandler.cxx
* Usage: Functions to parse build logs and extract error messages.
* Version: Unversioned
* License: BSD-3-Clause
* Note: This is a homemade port of Kitware's CTest build handler.
distro
@@ -61,7 +56,6 @@
* Homepage: https://pypi.python.org/pypi/distro
* Usage: Provides a more stable linux distribution detection.
* Version: 1.8.0
* License: Apache-2.0
jinja2
------
@@ -69,7 +63,6 @@
* Homepage: https://pypi.python.org/pypi/Jinja2
* Usage: A modern and designer-friendly templating language for Python.
* Version: 3.0.3 (last version supporting Python 3.6)
* License: BSD-3-Clause
jsonschema
----------
@@ -77,7 +70,6 @@
* Homepage: https://pypi.python.org/pypi/jsonschema
* Usage: An implementation of JSON Schema for Python.
* Version: 3.2.0 (last version before 2.7 and 3.6 support was dropped)
* License: MIT
* Note: We don't include tests or benchmarks; just what Spack needs.
macholib
@@ -86,7 +78,6 @@
* Homepage: https://macholib.readthedocs.io/en/latest/index.html#
* Usage: Manipulation of Mach-o binaries for relocating macOS buildcaches on Linux
* Version: 1.16.2
* License: MIT
markupsafe
----------
@@ -94,7 +85,6 @@
* Homepage: https://pypi.python.org/pypi/MarkupSafe
* Usage: Implements a XML/HTML/XHTML Markup safe string for Python.
* Version: 2.0.1 (last version supporting Python 3.6)
* License: BSD-3-Clause
pyrsistent
----------
@@ -102,7 +92,6 @@
* Homepage: http://github.com/tobgu/pyrsistent/
* Usage: Needed by `jsonschema`
* Version: 0.18.0
* License: MIT
ruamel.yaml
------
@@ -112,7 +101,6 @@
actively maintained and has more features, including round-tripping
comments read from config files.
* Version: 0.17.21
* License: MIT
six
---
@@ -120,6 +108,5 @@
* Homepage: https://pypi.python.org/pypi/six
* Usage: Python 2 and 3 compatibility utilities.
* Version: 1.16.0
* License: MIT
"""

View File

@@ -764,7 +764,7 @@ def copy_tree(
files = glob.glob(src)
if not files:
raise OSError("No such file or directory: '{0}'".format(src), errno.ENOENT)
raise OSError("No such file or directory: '{0}'".format(src))
# For Windows hard-links and junctions, the source path must exist to make a symlink. Add
# all symlinks to this list while traversing the tree, then when finished, make all

View File

@@ -15,19 +15,7 @@
import typing
import warnings
from datetime import datetime, timedelta
from typing import (
Any,
Callable,
Dict,
Generic,
Iterable,
List,
Mapping,
Optional,
Tuple,
TypeVar,
Union,
)
from typing import Callable, Dict, Iterable, List, Mapping, Optional, Tuple, TypeVar
# Ignore emacs backups when listing modules
ignore_modules = r"^\.#|~$"
@@ -1059,28 +1047,19 @@ def __exit__(self, exc_type, exc_value, tb):
return True
ClassPropertyType = TypeVar("ClassPropertyType")
class classproperty(Generic[ClassPropertyType]):
class classproperty:
"""Non-data descriptor to evaluate a class-level property. The function that performs
the evaluation is injected at creation time and takes an owner (i.e., the class that
originated the instance).
the evaluation is injected at creation time and take an instance (could be None) and
an owner (i.e. the class that originated the instance)
"""
def __init__(self, callback: Callable[[Any], ClassPropertyType]) -> None:
def __init__(self, callback):
self.callback = callback
def __get__(self, instance, owner) -> ClassPropertyType:
def __get__(self, instance, owner):
return self.callback(owner)
#: A type alias that represents either a classproperty descriptor or a constant value of the same
#: type. This allows derived classes to override a computed class-level property with a constant
#: value while retaining type compatibility.
ClassProperty = Union[ClassPropertyType, classproperty[ClassPropertyType]]
class DeprecatedProperty:
"""Data descriptor to error or warn when a deprecated property is accessed.

View File

@@ -7,7 +7,7 @@
"llvm": "clang",
"intel-oneapi-compilers": "oneapi",
"llvm-amdgpu": "rocmcc",
"intel-oneapi-compilers-classic": "intel",
"intel-oneapi-compiler-classic": "intel",
"acfl": "arm",
}
@@ -15,6 +15,6 @@
"clang": "llvm",
"oneapi": "intel-oneapi-compilers",
"rocmcc": "llvm-amdgpu",
"intel": "intel-oneapi-compilers-classic",
"intel": "intel-oneapi-compiler-classic",
"arm": "acfl",
}

View File

@@ -133,7 +133,7 @@ def mypy_root_spec() -> str:
def black_root_spec() -> str:
"""Return the root spec used to bootstrap black"""
return _root_spec("py-black@:25.1.0")
return _root_spec("py-black@:24.1.0")
def flake8_root_spec() -> str:

View File

@@ -36,11 +36,9 @@
import multiprocessing
import os
import re
import signal
import sys
import traceback
import types
import warnings
from collections import defaultdict
from enum import Flag, auto
from itertools import chain
@@ -574,10 +572,12 @@ def set_package_py_globals(pkg, context: Context = Context.BUILD):
module.make = DeprecatedExecutable(pkg.name, "make", "gmake")
module.gmake = DeprecatedExecutable(pkg.name, "gmake", "gmake")
module.ninja = DeprecatedExecutable(pkg.name, "ninja", "ninja")
# TODO: johnwparent: add package or builder support to define these build tools
# for now there is no entrypoint for builders to define these on their
# own
if sys.platform == "win32":
module.nmake = DeprecatedExecutable(pkg.name, "nmake", "msvc")
module.msbuild = DeprecatedExecutable(pkg.name, "msbuild", "msvc")
module.nmake = Executable("nmake")
module.msbuild = Executable("msbuild")
# analog to configure for win32
module.cscript = Executable("cscript")
@@ -1189,9 +1189,11 @@ def _setup_pkg_and_run(
if isinstance(e, (spack.multimethod.NoSuchMethodError, AttributeError)):
process = "test the installation" if context == "test" else "build from sources"
error_msg = (
"The '{}' package cannot find an attribute while trying to {}. You can fix this "
"by updating the {} recipe, and you can also report the issue as a build-error or "
"a bug at https://github.com/spack/spack/issues"
"The '{}' package cannot find an attribute while trying to {}. "
"This might be due to a change in Spack's package format "
"to support multiple build-systems for a single package. You can fix this "
"by updating the {} recipe, and you can also report the issue as a bug. "
"More information at https://spack.readthedocs.io/en/latest/packaging_guide.html#installation-procedure"
).format(pkg.name, process, context)
error_msg = colorize("@*R{{{}}}".format(error_msg))
error_msg = "{}\n\n{}".format(str(e), error_msg)
@@ -1216,45 +1218,15 @@ def _setup_pkg_and_run(
input_pipe.close()
class BuildProcess:
def __init__(self, *, target, args) -> None:
self.p = multiprocessing.Process(target=target, args=args)
def start(self) -> None:
self.p.start()
def is_alive(self) -> bool:
return self.p.is_alive()
def join(self, *, timeout: Optional[int] = None):
self.p.join(timeout=timeout)
def terminate(self):
# Opportunity for graceful termination
self.p.terminate()
self.p.join(timeout=1)
# If the process didn't gracefully terminate, forcefully kill
if self.p.is_alive():
# TODO (python 3.6 removal): use self.p.kill() instead, consider removing this class
assert isinstance(self.p.pid, int), f"unexpected value for PID: {self.p.pid}"
os.kill(self.p.pid, signal.SIGKILL)
self.p.join()
@property
def exitcode(self):
return self.p.exitcode
def start_build_process(pkg, function, kwargs, *, timeout: Optional[int] = None):
def start_build_process(pkg, function, kwargs):
"""Create a child process to do part of a spack build.
Args:
pkg (spack.package_base.PackageBase): package whose environment we should set up the
child process for.
function (typing.Callable): argless function to run in the child process.
timeout: maximum time allowed to finish the execution of function
function (typing.Callable): argless function to run in the child
process.
Usage::
@@ -1282,14 +1254,14 @@ def child_fun():
# Forward sys.stdin when appropriate, to allow toggling verbosity
if sys.platform != "win32" and sys.stdin.isatty() and hasattr(sys.stdin, "fileno"):
input_fd = Connection(os.dup(sys.stdin.fileno()))
mflags = os.environ.get("MAKEFLAGS")
if mflags is not None:
mflags = os.environ.get("MAKEFLAGS", False)
if mflags:
m = re.search(r"--jobserver-[^=]*=(\d),(\d)", mflags)
if m:
jobserver_fd1 = Connection(int(m.group(1)))
jobserver_fd2 = Connection(int(m.group(2)))
p = BuildProcess(
p = multiprocessing.Process(
target=_setup_pkg_and_run,
args=(
serialized_pkg,
@@ -1323,17 +1295,14 @@ def exitcode_msg(p):
typ = "exit" if p.exitcode >= 0 else "signal"
return f"{typ} {abs(p.exitcode)}"
p.join(timeout=timeout)
if p.is_alive():
warnings.warn(f"Terminating process, since the timeout of {timeout}s was exceeded")
p.terminate()
p.join()
try:
child_result = read_pipe.recv()
except EOFError:
p.join()
raise InstallError(f"The process has stopped unexpectedly ({exitcode_msg(p)})")
p.join()
# If returns a StopPhase, raise it
if isinstance(child_result, spack.error.StopPhase):
# do not print

View File

@@ -16,7 +16,6 @@
import spack.package_base
import spack.phase_callbacks
import spack.spec
import spack.util.environment
import spack.util.prefix
from spack.directives import build_system, conflicts, depends_on
from spack.multimethod import when
@@ -847,9 +846,7 @@ def _remove_libtool_archives(self) -> None:
with open(self._removed_la_files_log, mode="w", encoding="utf-8") as f:
f.write("\n".join(libtool_files))
def setup_build_environment(
self, env: spack.util.environment.EnvironmentModifications
) -> None:
def setup_build_environment(self, env):
if self.spec.platform == "darwin" and macos_version() >= Version("11"):
# Many configure files rely on matching '10.*' for macOS version
# detection and fail to add flags if it shows as version 11.

View File

@@ -2,10 +2,9 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import collections.abc
import enum
import os
import re
from typing import Optional, Tuple
from typing import Tuple
import llnl.util.filesystem as fs
import llnl.util.tty as tty
@@ -14,7 +13,6 @@
import spack.spec
import spack.util.prefix
from spack.directives import depends_on
from spack.util.executable import which_string
from .cmake import CMakeBuilder, CMakePackage
@@ -180,64 +178,6 @@ def initconfig_compiler_entries(self):
return entries
class Scheduler(enum.Enum):
LSF = enum.auto()
SLURM = enum.auto()
FLUX = enum.auto()
def get_scheduler(self) -> Optional[Scheduler]:
spec = self.pkg.spec
# Check for Spectrum-mpi, which always uses LSF or LSF MPI variant
if spec.satisfies("^spectrum-mpi") or spec["mpi"].satisfies("schedulers=lsf"):
return self.Scheduler.LSF
# Check for Slurm MPI variants
slurm_checks = ["+slurm", "schedulers=slurm", "process_managers=slurm"]
if any(spec["mpi"].satisfies(variant) for variant in slurm_checks):
return self.Scheduler.SLURM
# TODO improve this when MPI implementations support flux
# Do this check last to avoid using a flux wrapper present next to Slurm/ LSF schedulers
if which_string("flux") is not None:
return self.Scheduler.FLUX
return None
def get_mpi_exec(self) -> Optional[str]:
spec = self.pkg.spec
scheduler = self.get_scheduler()
if scheduler == self.Scheduler.LSF:
return which_string("lrun")
elif scheduler == self.Scheduler.SLURM:
if spec["mpi"].external:
return which_string("srun")
else:
return os.path.join(spec["slurm"].prefix.bin, "srun")
elif scheduler == self.Scheduler.FLUX:
flux = which_string("flux")
return f"{flux};run" if flux else None
elif hasattr(spec["mpi"].package, "mpiexec"):
return spec["mpi"].package.mpiexec
else:
mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpirun")
if not os.path.exists(mpiexec):
mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpiexec")
return mpiexec
def get_mpi_exec_num_proc(self) -> str:
scheduler = self.get_scheduler()
if scheduler in [self.Scheduler.FLUX, self.Scheduler.LSF, self.Scheduler.SLURM]:
return "-n"
else:
return "-np"
def initconfig_mpi_entries(self):
spec = self.pkg.spec
@@ -257,10 +197,27 @@ def initconfig_mpi_entries(self):
if hasattr(spec["mpi"], "mpifc"):
entries.append(cmake_cache_path("MPI_Fortran_COMPILER", spec["mpi"].mpifc))
# Determine MPIEXEC
mpiexec = self.get_mpi_exec()
# Check for slurm
using_slurm = False
slurm_checks = ["+slurm", "schedulers=slurm", "process_managers=slurm"]
if any(spec["mpi"].satisfies(variant) for variant in slurm_checks):
using_slurm = True
if mpiexec is None or not os.path.exists(mpiexec.split(";")[0]):
# Determine MPIEXEC
if using_slurm:
if spec["mpi"].external:
# Heuristic until we have dependents on externals
mpiexec = "/usr/bin/srun"
else:
mpiexec = os.path.join(spec["slurm"].prefix.bin, "srun")
elif hasattr(spec["mpi"].package, "mpiexec"):
mpiexec = spec["mpi"].package.mpiexec
else:
mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpirun")
if not os.path.exists(mpiexec):
mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpiexec")
if not os.path.exists(mpiexec):
msg = "Unable to determine MPIEXEC, %s tests may fail" % self.pkg.name
entries.append("# {0}\n".format(msg))
tty.warn(msg)
@@ -273,7 +230,10 @@ def initconfig_mpi_entries(self):
entries.append(cmake_cache_path("MPIEXEC", mpiexec))
# Determine MPIEXEC_NUMPROC_FLAG
entries.append(cmake_cache_string("MPIEXEC_NUMPROC_FLAG", self.get_mpi_exec_num_proc()))
if using_slurm:
entries.append(cmake_cache_string("MPIEXEC_NUMPROC_FLAG", "-n"))
else:
entries.append(cmake_cache_string("MPIEXEC_NUMPROC_FLAG", "-np"))
return entries
@@ -316,18 +276,30 @@ def initconfig_hardware_entries(self):
entries.append("# ROCm")
entries.append("#------------------{0}\n".format("-" * 30))
rocm_root = os.path.dirname(spec["llvm-amdgpu"].prefix)
entries.append(cmake_cache_path("ROCM_PATH", rocm_root))
if spec.satisfies("^blt@0.7:"):
rocm_root = os.path.dirname(spec["llvm-amdgpu"].prefix)
entries.append(cmake_cache_path("ROCM_PATH", rocm_root))
else:
# Explicitly setting HIP_ROOT_DIR may be a patch that is no longer necessary
entries.append(cmake_cache_path("HIP_ROOT_DIR", "{0}".format(spec["hip"].prefix)))
llvm_bin = spec["llvm-amdgpu"].prefix.bin
llvm_prefix = spec["llvm-amdgpu"].prefix
# Some ROCm systems seem to point to /<path>/rocm-<ver>/ and
# others point to /<path>/rocm-<ver>/llvm
if os.path.basename(os.path.normpath(llvm_prefix)) != "llvm":
llvm_bin = os.path.join(llvm_prefix, "llvm/bin/")
entries.append(
cmake_cache_filepath(
"CMAKE_HIP_COMPILER", os.path.join(llvm_bin, "amdclang++")
)
)
archs = self.spec.variants["amdgpu_target"].value
if archs[0] != "none":
arch_str = ";".join(archs)
entries.append(cmake_cache_string("CMAKE_HIP_ARCHITECTURES", arch_str))
llvm_bin = spec["llvm-amdgpu"].prefix.bin
entries.append(
cmake_cache_filepath("CMAKE_HIP_COMPILER", os.path.join(llvm_bin, "amdclang++"))
)
entries.append(cmake_cache_string("AMDGPU_TARGETS", arch_str))
entries.append(cmake_cache_string("GPU_TARGETS", arch_str))
if spec.satisfies("%gcc"):
entries.append(
@@ -336,15 +308,6 @@ def initconfig_hardware_entries(self):
)
)
# Extra definitions that might be required in other cases
if not spec.satisfies("^blt"):
entries.append(cmake_cache_path("HIP_ROOT_DIR", "{0}".format(spec["hip"].prefix)))
if archs[0] != "none":
arch_str = ";".join(archs)
entries.append(cmake_cache_string("AMDGPU_TARGETS", arch_str))
entries.append(cmake_cache_string("GPU_TARGETS", arch_str))
return entries
def std_initconfig_entries(self):

View File

@@ -8,7 +8,6 @@
import spack.package_base
import spack.phase_callbacks
import spack.spec
import spack.util.environment
import spack.util.prefix
from spack.directives import build_system, depends_on
from spack.multimethod import when
@@ -87,9 +86,7 @@ def check_args(self):
"""Argument for ``cargo test`` during check phase"""
return []
def setup_build_environment(
self, env: spack.util.environment.EnvironmentModifications
) -> None:
def setup_build_environment(self, env):
env.set("CARGO_HOME", self.stage.path)
def build(

View File

@@ -47,11 +47,6 @@ class CompilerPackage(spack.package_base.PackageBase):
#: Relative path to compiler wrappers
compiler_wrapper_link_paths: Dict[str, str] = {}
#: Optimization flags
opt_flags: Sequence[str] = []
#: Flags for generating debug information
debug_flags: Sequence[str] = []
def __init__(self, spec: "spack.spec.Spec"):
super().__init__(spec)
msg = f"Supported languages for {spec} are not a subset of possible supported languages"

View File

@@ -8,7 +8,6 @@
import spack.package_base
import spack.phase_callbacks
import spack.spec
import spack.util.environment
import spack.util.prefix
from spack.directives import build_system, depends_on
from spack.multimethod import when
@@ -69,9 +68,7 @@ class GoBuilder(BuilderWithDefaults):
#: Callback names for install-time test
install_time_test_callbacks = ["check"]
def setup_build_environment(
self, env: spack.util.environment.EnvironmentModifications
) -> None:
def setup_build_environment(self, env):
env.set("GO111MODULE", "on")
env.set("GOTOOLCHAIN", "local")
env.set("GOPATH", fs.join_path(self.pkg.stage.path, "go"))

View File

@@ -23,7 +23,6 @@
import spack.error
import spack.phase_callbacks
import spack.spec
from spack.build_environment import dso_suffix
from spack.error import InstallError
from spack.util.environment import EnvironmentModifications
@@ -1017,7 +1016,7 @@ def libs(self):
debug_print(result)
return result
def setup_run_environment(self, env: EnvironmentModifications) -> None:
def setup_run_environment(self, env):
"""Adds environment variables to the generated module file.
These environment variables come from running:
@@ -1050,9 +1049,7 @@ def setup_run_environment(self, env: EnvironmentModifications) -> None:
env.set("F77", self.prefix.bin.ifort)
env.set("F90", self.prefix.bin.ifort)
def setup_dependent_build_environment(
self, env: EnvironmentModifications, dependent_spec: spack.spec.Spec
) -> None:
def setup_dependent_build_environment(self, env, dependent_spec):
# NB: This function is overwritten by 'mpi' provider packages:
#
# var/spack/repos/builtin/packages/intel-mpi/package.py
@@ -1064,12 +1061,7 @@ def setup_dependent_build_environment(
# Handle everything in a callback version.
self._setup_dependent_env_callback(env, dependent_spec)
def _setup_dependent_env_callback(
self,
env: EnvironmentModifications,
dependent_spec: spack.spec.Spec,
compilers_of_client={},
) -> None:
def _setup_dependent_env_callback(self, env, dependent_spec, compilers_of_client={}):
# Expected to be called from a client's
# setup_dependent_build_environment(),
# with args extended to convey the client's compilers as needed.

View File

@@ -8,7 +8,6 @@
import spack.builder
import spack.package_base
import spack.spec
import spack.util.environment
import spack.util.executable
import spack.util.prefix
from spack.directives import build_system, depends_on, extends
@@ -115,7 +114,5 @@ def install(
def _luarocks_config_path(self):
return os.path.join(self.pkg.stage.source_path, "spack_luarocks.lua")
def setup_build_environment(
self, env: spack.util.environment.EnvironmentModifications
) -> None:
def setup_build_environment(self, env):
env.set("LUAROCKS_CONFIG", self._luarocks_config_path())

View File

@@ -4,7 +4,6 @@
import spack.builder
import spack.package_base
import spack.spec
import spack.util.environment
import spack.util.prefix
from spack.directives import build_system, extends
from spack.multimethod import when
@@ -58,9 +57,7 @@ def install(
"pkg prefix %s; pkg install %s" % (prefix, self.pkg.stage.archive_file),
)
def setup_build_environment(
self, env: spack.util.environment.EnvironmentModifications
) -> None:
def setup_build_environment(self, env):
# octave does not like those environment variables to be set:
env.unset("CC")
env.unset("CXX")

View File

@@ -106,8 +106,8 @@ def install_component(self, installer_path):
bash = Executable("bash")
# Installer writes files in ~/intel set HOME so it goes to staging directory
bash.add_default_env("HOME", join_path(self.stage.path, "home"))
# Installer writes files in ~/intel set HOME so it goes to prefix
bash.add_default_env("HOME", self.prefix)
# Installer checks $XDG_RUNTIME_DIR/.bootstrapper_lock_file as well
bash.add_default_env("XDG_RUNTIME_DIR", join_path(self.stage.path, "runtime"))
@@ -132,7 +132,7 @@ def install_component(self, installer_path):
if not isdir(install_dir):
raise RuntimeError("install failed to directory: {0}".format(install_dir))
def setup_run_environment(self, env: EnvironmentModifications) -> None:
def setup_run_environment(self, env):
"""Adds environment variables to the generated module file.
These environment variables come from running:
@@ -311,4 +311,4 @@ def ld_flags(self):
#: Tuple of Intel math libraries, exported to packages
INTEL_MATH_LIBRARIES = ("intel-oneapi-mkl",)
INTEL_MATH_LIBRARIES = ("intel-mkl", "intel-oneapi-mkl", "intel-parallel-studio")

View File

@@ -13,9 +13,9 @@
import archspec
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, join_path
from llnl.util.lang import ClassProperty, classproperty, match_predicate
import spack.builder
import spack.config
@@ -139,7 +139,7 @@ def view_file_conflicts(self, view, merge_map):
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
namespaces = set(x.package.py_namespace for x in ext_map.values())
namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace)
find_namespace = match_predicate(namespace_re)
find_namespace = lang.match_predicate(namespace_re)
if self.py_namespace in namespaces:
conflicts = list(x for x in conflicts if not find_namespace(x))
@@ -206,7 +206,7 @@ def remove_files_from_view(self, view, merge_map):
spec.package.py_namespace for name, spec in ext_map.items() if name != self.name
)
if self.py_namespace in remaining_namespaces:
namespace_init = match_predicate(
namespace_init = lang.match_predicate(
r"site-packages/{0}/__init__.py".format(self.py_namespace)
)
ignore_namespace = True
@@ -324,27 +324,6 @@ def get_external_python_for_prefix(self):
raise StopIteration("No external python could be detected for %s to depend on" % self.spec)
def _homepage(cls: "PythonPackage") -> Optional[str]:
"""Get the homepage from PyPI if available."""
if cls.pypi:
name = cls.pypi.split("/")[0]
return f"https://pypi.org/project/{name}/"
return None
def _url(cls: "PythonPackage") -> Optional[str]:
if cls.pypi:
return f"https://files.pythonhosted.org/packages/source/{cls.pypi[0]}/{cls.pypi}"
return None
def _list_url(cls: "PythonPackage") -> Optional[str]:
if cls.pypi:
name = cls.pypi.split("/")[0]
return f"https://pypi.org/simple/{name}/"
return None
class PythonPackage(PythonExtension):
"""Specialized class for packages that are built using pip."""
@@ -372,9 +351,25 @@ class PythonPackage(PythonExtension):
py_namespace: Optional[str] = None
homepage: ClassProperty[Optional[str]] = classproperty(_homepage)
url: ClassProperty[Optional[str]] = classproperty(_url)
list_url: ClassProperty[Optional[str]] = classproperty(_list_url)
@lang.classproperty
def homepage(cls) -> Optional[str]: # type: ignore[override]
if cls.pypi:
name = cls.pypi.split("/")[0]
return f"https://pypi.org/project/{name}/"
return None
@lang.classproperty
def url(cls) -> Optional[str]:
if cls.pypi:
return f"https://files.pythonhosted.org/packages/source/{cls.pypi[0]}/{cls.pypi}"
return None
@lang.classproperty
def list_url(cls) -> Optional[str]: # type: ignore[override]
if cls.pypi:
name = cls.pypi.split("/")[0]
return f"https://pypi.org/simple/{name}/"
return None
@property
def python_spec(self) -> Spec:

View File

@@ -3,8 +3,8 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from typing import Optional, Tuple
import llnl.util.lang as lang
from llnl.util.filesystem import mkdirp
from llnl.util.lang import ClassProperty, classproperty
from spack.directives import extends
@@ -54,32 +54,6 @@ def install(self, pkg, spec, prefix):
pkg.module.R(*args)
def _homepage(cls: "RPackage") -> Optional[str]:
if cls.cran:
return f"https://cloud.r-project.org/package={cls.cran}"
elif cls.bioc:
return f"https://bioconductor.org/packages/{cls.bioc}"
return None
def _url(cls: "RPackage") -> Optional[str]:
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/{cls.cran}_{str(list(cls.versions)[0])}.tar.gz"
return None
def _list_url(cls: "RPackage") -> Optional[str]:
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/Archive/{cls.cran}/"
return None
def _git(cls: "RPackage") -> Optional[str]:
if cls.bioc:
return f"https://git.bioconductor.org/packages/{cls.bioc}"
return None
class RPackage(Package):
"""Specialized class for packages that are built using R.
@@ -103,7 +77,24 @@ class RPackage(Package):
extends("r")
homepage: ClassProperty[Optional[str]] = classproperty(_homepage)
url: ClassProperty[Optional[str]] = classproperty(_url)
list_url: ClassProperty[Optional[str]] = classproperty(_list_url)
git: ClassProperty[Optional[str]] = classproperty(_git)
@lang.classproperty
def homepage(cls):
if cls.cran:
return f"https://cloud.r-project.org/package={cls.cran}"
elif cls.bioc:
return f"https://bioconductor.org/packages/{cls.bioc}"
@lang.classproperty
def url(cls):
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/{cls.cran}_{str(list(cls.versions)[0])}.tar.gz"
@lang.classproperty
def list_url(cls):
if cls.cran:
return f"https://cloud.r-project.org/src/contrib/Archive/{cls.cran}/"
@lang.classproperty
def git(cls):
if cls.bioc:
return f"https://git.bioconductor.org/packages/{cls.bioc}"

View File

@@ -5,8 +5,8 @@
from typing import Optional, Tuple
import llnl.util.filesystem as fs
import llnl.util.lang as lang
import llnl.util.tty as tty
from llnl.util.lang import ClassProperty, classproperty
import spack.builder
import spack.spec
@@ -19,12 +19,6 @@
from spack.util.executable import Executable, ProcessError
def _homepage(cls: "RacketPackage") -> Optional[str]:
if cls.racket_name:
return f"https://pkgs.racket-lang.org/package/{cls.racket_name}"
return None
class RacketPackage(PackageBase):
"""Specialized class for packages that are built using Racket's
`raco pkg install` and `raco setup` commands.
@@ -43,7 +37,13 @@ class RacketPackage(PackageBase):
extends("racket", when="build_system=racket")
racket_name: Optional[str] = None
homepage: ClassProperty[Optional[str]] = classproperty(_homepage)
parallel = True
@lang.classproperty
def homepage(cls):
if cls.racket_name:
return "https://pkgs.racket-lang.org/package/{0}".format(cls.racket_name)
return None
@spack.builder.builder("racket")

View File

@@ -185,16 +185,10 @@ def __init__(self, pkg):
# These two methods don't follow the (self, spec, prefix) signature of phases nor
# the (self) signature of methods, so they are added explicitly to avoid using a
# catch-all (*args, **kwargs)
def setup_build_environment(
self, env: spack.util.environment.EnvironmentModifications
) -> None:
def setup_build_environment(self, env):
return self.pkg_with_dispatcher.setup_build_environment(env)
def setup_dependent_build_environment(
self,
env: spack.util.environment.EnvironmentModifications,
dependent_spec: spack.spec.Spec,
) -> None:
def setup_dependent_build_environment(self, env, dependent_spec):
return self.pkg_with_dispatcher.setup_dependent_build_environment(env, dependent_spec)
return Adapter(pkg)
@@ -408,7 +402,7 @@ def fixup_install(self):
# do something after the package is installed
pass
def setup_build_environment(self, env: EnvironmentModifications) -> None:
def setup_build_environment(self, env):
env.set("MY_ENV_VAR", "my_value")
class CMakeBuilder(cmake.CMakeBuilder, AnyBuilder):

View File

@@ -14,7 +14,7 @@
import tempfile
import zipfile
from collections import namedtuple
from typing import Callable, Dict, List, Optional, Set, Union
from typing import Callable, Dict, List, Set, Union
from urllib.request import Request
import llnl.path
@@ -24,7 +24,6 @@
import spack
import spack.binary_distribution as bindist
import spack.builder
import spack.config as cfg
import spack.environment as ev
import spack.error
@@ -614,40 +613,32 @@ def copy_stage_logs_to_artifacts(job_spec: spack.spec.Spec, job_log_dir: str) ->
job_spec, and attempts to copy the files into the directory given
by job_log_dir.
Parameters:
Args:
job_spec: spec associated with spack install log
job_log_dir: path into which build log should be copied
"""
tty.debug(f"job spec: {job_spec}")
if not job_spec.concrete:
tty.warn("Cannot copy artifacts for non-concrete specs")
try:
package_metadata_root = pathlib.Path(spack.store.STORE.layout.metadata_path(job_spec))
except spack.error.SpackError as e:
tty.error(f"Cannot copy logs: {str(e)}")
return
package_metadata_root = pathlib.Path(spack.store.STORE.layout.metadata_path(job_spec))
if not os.path.isdir(package_metadata_root):
# Fallback to using the stage directory
job_pkg = job_spec.package
package_metadata_root = pathlib.Path(job_pkg.stage.path)
archive_files = spack.builder.create(job_pkg).archive_files
tty.warn("Package not installed, falling back to use stage dir")
tty.debug(f"stage dir: {package_metadata_root}")
# Get the package's archived files
archive_files = []
archive_root = package_metadata_root / "archived-files"
if archive_root.is_dir():
archive_files = [f for f in archive_root.rglob("*") if f.is_file()]
else:
# Get the package's archived files
archive_files = []
archive_root = package_metadata_root / "archived-files"
if os.path.isdir(archive_root):
archive_files = [str(f) for f in archive_root.rglob("*") if os.path.isfile(f)]
else:
tty.debug(f"No archived files detected at {archive_root}")
msg = "Cannot copy package archived files: archived-files must be a directory"
tty.warn(msg)
# Try zipped and unzipped versions of the build log
build_log_zipped = package_metadata_root / "spack-build-out.txt.gz"
build_log = package_metadata_root / "spack-build-out.txt"
build_env_mods = package_metadata_root / "spack-build-env.txt"
for f in [build_log_zipped, build_log, build_env_mods, *archive_files]:
copy_files_to_artifacts(str(f), job_log_dir, compress_artifacts=True)
for f in [build_log_zipped, build_env_mods, *archive_files]:
copy_files_to_artifacts(str(f), job_log_dir)
def copy_test_logs_to_artifacts(test_stage, job_test_dir):
@@ -660,12 +651,11 @@ def copy_test_logs_to_artifacts(test_stage, job_test_dir):
"""
tty.debug(f"test stage: {test_stage}")
if not os.path.exists(test_stage):
tty.error(f"Cannot copy test logs: job test stage ({test_stage}) does not exist")
msg = f"Cannot copy test logs: job test stage ({test_stage}) does not exist"
tty.error(msg)
return
copy_files_to_artifacts(
os.path.join(test_stage, "*", "*.txt"), job_test_dir, compress_artifacts=True
)
copy_files_to_artifacts(os.path.join(test_stage, "*", "*.txt"), job_test_dir)
def download_and_extract_artifacts(url, work_dir) -> str:
@@ -1304,34 +1294,35 @@ def display_broken_spec_messages(base_url, hashes):
tty.msg(msg)
def run_standalone_tests(
*,
cdash: Optional[CDashHandler] = None,
fail_fast: bool = False,
log_file: Optional[str] = None,
job_spec: Optional[spack.spec.Spec] = None,
repro_dir: Optional[str] = None,
timeout: Optional[int] = None,
):
def run_standalone_tests(**kwargs):
"""Run stand-alone tests on the current spec.
Args:
cdash: cdash handler instance
fail_fast: terminate tests after the first failure
log_file: test log file name if NOT CDash reporting
job_spec: spec that was built
repro_dir: reproduction directory
timeout: maximum time (in seconds) that tests are allowed to run
Arguments:
kwargs (dict): dictionary of arguments used to run the tests
List of recognized keys:
* "cdash" (CDashHandler): (optional) cdash handler instance
* "fail_fast" (bool): (optional) terminate tests after the first failure
* "log_file" (str): (optional) test log file name if NOT CDash reporting
* "job_spec" (Spec): spec that was built
* "repro_dir" (str): reproduction directory
"""
cdash = kwargs.get("cdash")
fail_fast = kwargs.get("fail_fast")
log_file = kwargs.get("log_file")
if cdash and log_file:
tty.msg(f"The test log file {log_file} option is ignored with CDash reporting")
log_file = None
# Error out but do NOT terminate if there are missing required arguments.
job_spec = kwargs.get("job_spec")
if not job_spec:
tty.error("Job spec is required to run stand-alone tests")
return
repro_dir = kwargs.get("repro_dir")
if not repro_dir:
tty.error("Reproduction directory is required for stand-alone tests")
return
@@ -1340,9 +1331,6 @@ def run_standalone_tests(
if fail_fast:
test_args.append("--fail-fast")
if timeout is not None:
test_args.extend(["--timeout", str(timeout)])
if cdash:
test_args.extend(cdash.args())
else:

View File

@@ -2,13 +2,9 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import copy
import errno
import glob
import gzip
import json
import os
import re
import shutil
import sys
import time
from collections import deque
@@ -29,7 +25,6 @@
import spack.mirrors.mirror
import spack.schema
import spack.spec
import spack.util.compression as compression
import spack.util.spack_yaml as syaml
import spack.util.url as url_util
import spack.util.web as web_util
@@ -45,67 +40,22 @@
_urlopen = web_util.urlopen
def copy_gzipped(glob_or_path: str, dest: str) -> None:
"""Copy all of the files in the source glob/path to the destination.
Args:
glob_or_path: path to file to test
dest: destination path to copy to
"""
files = glob.glob(glob_or_path)
if not files:
raise OSError("No such file or directory: '{0}'".format(glob_or_path), errno.ENOENT)
if len(files) > 1 and not os.path.isdir(dest):
raise ValueError(
"'{0}' matches multiple files but '{1}' is not a directory".format(glob_or_path, dest)
)
def is_gzipped(path):
with open(path, "rb") as fd:
return compression.GZipFileType().matches_magic(fd)
for src in files:
if is_gzipped(src):
fs.copy(src, dest)
else:
# Compress and copy in one step
src_name = os.path.basename(src)
if os.path.isdir(dest):
zipped = os.path.join(dest, f"{src_name}.gz")
elif not dest.endswith(".gz"):
zipped = f"{dest}.gz"
else:
zipped = dest
with open(src, "rb") as fin, gzip.open(zipped, "wb") as fout:
shutil.copyfileobj(fin, fout)
def copy_files_to_artifacts(
src: str, artifacts_dir: str, *, compress_artifacts: bool = False
) -> None:
def copy_files_to_artifacts(src, artifacts_dir):
"""
Copy file(s) to the given artifacts directory
Args:
Parameters:
src (str): the glob-friendly path expression for the file(s) to copy
artifacts_dir (str): the destination directory
compress_artifacts (bool): option to compress copied artifacts using Gzip
"""
try:
if compress_artifacts:
copy_gzipped(src, artifacts_dir)
else:
fs.copy(src, artifacts_dir)
fs.copy(src, artifacts_dir)
except Exception as err:
tty.warn(
(
f"Unable to copy files ({src}) to artifacts {artifacts_dir} due to "
f"exception: {str(err)}"
)
msg = (
f"Unable to copy files ({src}) to artifacts {artifacts_dir} due to "
f"exception: {str(err)}"
)
tty.warn(msg)
def win_quote(quote_str: str) -> str:

View File

@@ -436,7 +436,7 @@ def display_specs(specs, args=None, **kwargs):
all_headers (bool): show headers even when arch/compiler aren't defined
status_fn (typing.Callable): if provided, prepend install-status info
output (typing.IO): A file object to write to. Default is ``sys.stdout``
specfile_format (bool): specfile format of the current spec
"""
def get_arg(name, default=None):
@@ -458,7 +458,6 @@ def get_arg(name, default=None):
all_headers = get_arg("all_headers", False)
output = get_arg("output", sys.stdout)
status_fn = get_arg("status_fn", None)
specfile_format = get_arg("specfile_format", False)
decorator = get_arg("decorator", None)
if decorator is None:
@@ -480,9 +479,6 @@ def get_arg(name, default=None):
vfmt = "{variants}" if variants else ""
format_string = nfmt + "{@version}" + vfmt + ffmt
if specfile_format:
format_string = "[{specfile_version}] " + format_string
def fmt(s, depth=0):
"""Formatter function for all output specs"""
string = ""

View File

@@ -76,6 +76,9 @@ def setup_parser(subparser: argparse.ArgumentParser):
default=False,
help="regenerate buildcache index after building package(s)",
)
push.add_argument(
"--spec-file", default=None, help="create buildcache entry for spec from json or yaml file"
)
push.add_argument(
"--only",
default="package,dependencies",
@@ -189,14 +192,28 @@ def setup_parser(subparser: argparse.ArgumentParser):
default=lambda: spack.config.default_modify_scope(),
help="configuration scope containing mirrors to check",
)
# Unfortunately there are 3 ways to do the same thing here:
check_specs = check.add_mutually_exclusive_group()
check_specs.add_argument(
"-s", "--spec", help="check single spec instead of release specs file"
)
check_specs.add_argument(
"--spec-file",
help="check single spec from json or yaml file instead of release specs file",
)
arguments.add_common_arguments(check, ["specs"])
check.set_defaults(func=check_fn)
# Download tarball and specfile
download = subparsers.add_parser("download", help=download_fn.__doc__)
download.add_argument("-s", "--spec", help="download built tarball for spec from mirror")
download_spec_or_specfile = download.add_mutually_exclusive_group(required=True)
download_spec_or_specfile.add_argument(
"-s", "--spec", help="download built tarball for spec from mirror"
)
download_spec_or_specfile.add_argument(
"--spec-file", help="download built tarball for spec (from json or yaml file) from mirror"
)
download.add_argument(
"-p",
"--path",
@@ -206,10 +223,28 @@ def setup_parser(subparser: argparse.ArgumentParser):
)
download.set_defaults(func=download_fn)
# Get buildcache name
getbuildcachename = subparsers.add_parser(
"get-buildcache-name", help=get_buildcache_name_fn.__doc__
)
getbuildcachename_spec_or_specfile = getbuildcachename.add_mutually_exclusive_group(
required=True
)
getbuildcachename_spec_or_specfile.add_argument(
"-s", "--spec", help="spec string for which buildcache name is desired"
)
getbuildcachename_spec_or_specfile.add_argument(
"--spec-file", help="path to spec json or yaml file for which buildcache name is desired"
)
getbuildcachename.set_defaults(func=get_buildcache_name_fn)
# Given the root spec, save the yaml of the dependent spec to a file
savespecfile = subparsers.add_parser("save-specfile", help=save_specfile_fn.__doc__)
savespecfile_spec_or_specfile = savespecfile.add_mutually_exclusive_group(required=True)
savespecfile_spec_or_specfile.add_argument("--root-spec", help="root spec of dependent spec")
savespecfile_spec_or_specfile.add_argument(
"--root-specfile", help="path to json or yaml file containing root spec of dependent spec"
)
savespecfile.add_argument(
"-s",
"--specs",
@@ -345,8 +380,14 @@ def _specs_to_be_packaged(
def push_fn(args):
"""create a binary package and push it to a mirror"""
if args.specs:
roots = _matching_specs(spack.cmd.parse_specs(args.specs))
if args.spec_file:
tty.warn(
"The flag `--spec-file` is deprecated and will be removed in Spack 0.22. "
"Use positional arguments instead."
)
if args.specs or args.spec_file:
roots = _matching_specs(spack.cmd.parse_specs(args.specs or args.spec_file))
else:
roots = spack.cmd.require_active_env(cmd_name="buildcache push").concrete_roots()
@@ -488,7 +529,22 @@ def check_fn(args: argparse.Namespace):
this command uses the process exit code to indicate its result, specifically, if the
exit code is non-zero, then at least one of the indicated specs needs to be rebuilt
"""
specs_arg = args.specs
if args.spec_file:
specs_arg = (
args.spec_file if os.path.sep in args.spec_file else os.path.join(".", args.spec_file)
)
tty.warn(
"The flag `--spec-file` is deprecated and will be removed in Spack 0.22. "
f"Use `spack buildcache check {specs_arg}` instead."
)
elif args.spec:
specs_arg = args.spec
tty.warn(
"The flag `--spec` is deprecated and will be removed in Spack 0.23. "
f"Use `spack buildcache check {specs_arg}` instead."
)
else:
specs_arg = args.specs
if specs_arg:
specs = _matching_specs(spack.cmd.parse_specs(specs_arg))
@@ -522,7 +578,13 @@ def download_fn(args):
code indicates that the command failed to download at least one of the required buildcache
components
"""
specs = _matching_specs(spack.cmd.parse_specs(args.spec))
if args.spec_file:
tty.warn(
"The flag `--spec-file` is deprecated and will be removed in Spack 0.22. "
"Use --spec instead."
)
specs = _matching_specs(spack.cmd.parse_specs(args.spec or args.spec_file))
if len(specs) != 1:
tty.die("a single spec argument is required to download from a buildcache")
@@ -531,6 +593,15 @@ def download_fn(args):
sys.exit(1)
def get_buildcache_name_fn(args):
"""get name (prefix) of buildcache entries for this spec"""
tty.warn("This command is deprecated and will be removed in Spack 0.22.")
specs = _matching_specs(spack.cmd.parse_specs(args.spec or args.spec_file))
if len(specs) != 1:
tty.die("a single spec argument is required to get buildcache name")
print(bindist.tarball_name(specs[0], ""))
def save_specfile_fn(args):
"""get full spec for dependencies and write them to files in the specified output directory
@@ -538,7 +609,13 @@ def save_specfile_fn(args):
successful. if any errors or exceptions are encountered, or if expected command-line arguments
are not provided, then the exit code will be non-zero
"""
specs = spack.cmd.parse_specs(args.root_spec)
if args.root_specfile:
tty.warn(
"The flag `--root-specfile` is deprecated and will be removed in Spack 0.22. "
"Use --root-spec instead."
)
specs = spack.cmd.parse_specs(args.root_spec or args.root_specfile)
if len(specs) != 1:
tty.die("a single spec argument is required to save specfile")

View File

@@ -160,12 +160,6 @@ def setup_parser(subparser):
default=False,
help="stop stand-alone tests after the first failure",
)
rebuild.add_argument(
"--timeout",
type=int,
default=None,
help="maximum time (in seconds) that tests are allowed to run",
)
rebuild.set_defaults(func=ci_rebuild)
spack.cmd.common.arguments.add_common_arguments(rebuild, ["jobs"])
@@ -453,7 +447,7 @@ def ci_rebuild(args):
# Arguments when installing the root from sources
deps_install_args = install_args + ["--only=dependencies"]
root_install_args = install_args + ["--keep-stage", "--only=package"]
root_install_args = install_args + ["--only=package"]
if cdash_handler:
# Add additional arguments to `spack install` for CDash reporting.
@@ -493,9 +487,6 @@ def ci_rebuild(args):
# Copy logs and archived files from the install metadata (.spack) directory to artifacts now
spack_ci.copy_stage_logs_to_artifacts(job_spec, job_log_dir)
# Clear the stage directory
spack.stage.purge()
# If the installation succeeded and we're running stand-alone tests for
# the package, run them and copy the output. Failures of any kind should
# *not* terminate the build process or preclude creating the build cache.
@@ -530,7 +521,6 @@ def ci_rebuild(args):
fail_fast=args.fail_fast,
log_file=log_file,
repro_dir=repro_dir,
timeout=args.timeout,
)
except Exception as err:

View File

@@ -63,7 +63,7 @@ def setup_parser(subparser):
)
# List
list_parser = sp.add_parser("list", aliases=["ls"], help="list available compilers")
list_parser = sp.add_parser("list", help="list available compilers")
list_parser.add_argument(
"--scope", action=arguments.ConfigScope, help="configuration scope to read from"
)
@@ -216,6 +216,5 @@ def compiler(parser, args):
"rm": compiler_remove,
"info": compiler_info,
"list": compiler_list,
"ls": compiler_list,
}
action[args.compiler_command](args)

View File

@@ -572,7 +572,7 @@ def edit(self, spec, prefix):
class IntelPackageTemplate(PackageTemplate):
"""Provides appropriate overrides for licensed Intel software"""
base_class_name = "IntelOneApiPackage"
base_class_name = "IntelPackage"
body_def = """\
# FIXME: Override `setup_environment` if necessary."""

View File

@@ -102,7 +102,7 @@ def assure_concrete_spec(env: spack.environment.Environment, spec: spack.spec.Sp
)
else:
# look up the maximum version so infintiy versions are preferred for develop
version = max(spack.repo.PATH.get_pkg_class(spec.fullname).versions.keys())
version = max(spec.package_class.versions.keys())
tty.msg(f"Defaulting to highest version: {spec.name}@{version}")
spec.versions = spack.version.VersionList([version])

View File

@@ -62,7 +62,7 @@ def setup_parser(subparser):
"package Spack knows how to find."
)
sp.add_parser("list", aliases=["ls"], help="list detectable packages, by repository and name")
sp.add_parser("list", help="list detectable packages, by repository and name")
read_cray_manifest = sp.add_parser(
"read-cray-manifest",
@@ -259,7 +259,6 @@ def external(parser, args):
action = {
"find": external_find,
"list": external_list,
"ls": external_list,
"read-cray-manifest": external_read_cray_manifest,
}
action[args.external_command](args)

View File

@@ -51,12 +51,6 @@ def setup_parser(subparser):
"-I", "--install-status", action="store_true", help="show install status of packages"
)
subparser.add_argument(
"--specfile-format",
action="store_true",
help="show the specfile format for installed deps ",
)
subparser.add_argument(
"-d", "--deps", action="store_true", help="output dependencies along with found specs"
)
@@ -286,7 +280,6 @@ def root_decorator(spec, string):
show_flags=True,
decorator=root_decorator,
variants=True,
specfile_format=args.specfile_format,
)
print()
@@ -308,7 +301,6 @@ def root_decorator(spec, string):
namespace=True,
show_flags=True,
variants=True,
specfile_format=args.specfile_format,
)
print()
@@ -398,12 +390,7 @@ def find(parser, args):
if args.show_concretized:
display_results += concretized_but_not_installed
cmd.display_specs(
display_results,
args,
decorator=decorator,
all_headers=True,
status_fn=status_fn,
specfile_format=args.specfile_format,
display_results, args, decorator=decorator, all_headers=True, status_fn=status_fn
)
# print number of installed packages last (as the list may be long)

View File

@@ -136,7 +136,20 @@ def solve(parser, args):
setup_only = set(show) == {"asp"}
unify = spack.config.get("concretizer:unify")
allow_deprecated = spack.config.get("config:deprecated", False)
if unify == "when_possible":
if unify != "when_possible":
# set up solver parameters
# Note: reuse and other concretizer prefs are passed as configuration
result = solver.solve(
specs,
out=output,
timers=args.timers,
stats=args.stats,
setup_only=setup_only,
allow_deprecated=allow_deprecated,
)
if not setup_only:
_process_result(result, show, required_format, kwargs)
else:
for idx, result in enumerate(
solver.solve_in_rounds(
specs,
@@ -153,29 +166,3 @@ def solve(parser, args):
print("% END ROUND {0}\n".format(idx))
if not setup_only:
_process_result(result, show, required_format, kwargs)
elif unify:
# set up solver parameters
# Note: reuse and other concretizer prefs are passed as configuration
result = solver.solve(
specs,
out=output,
timers=args.timers,
stats=args.stats,
setup_only=setup_only,
allow_deprecated=allow_deprecated,
)
if not setup_only:
_process_result(result, show, required_format, kwargs)
else:
for spec in specs:
tty.msg("SOLVING SPEC:", spec)
result = solver.solve(
[spec],
out=output,
timers=args.timers,
stats=args.stats,
setup_only=setup_only,
allow_deprecated=allow_deprecated,
)
if not setup_only:
_process_result(result, show, required_format, kwargs)

View File

@@ -65,12 +65,6 @@ def setup_parser(subparser):
run_parser.add_argument(
"--help-cdash", action="store_true", help="show usage instructions for CDash reporting"
)
run_parser.add_argument(
"--timeout",
type=int,
default=None,
help="maximum time (in seconds) that tests are allowed to run",
)
cd_group = run_parser.add_mutually_exclusive_group()
arguments.add_common_arguments(cd_group, ["clean", "dirty"])
@@ -182,7 +176,7 @@ def test_run(args):
for spec in specs:
matching = spack.store.STORE.db.query_local(spec, hashes=hashes, explicit=explicit)
if spec and not matching:
tty.warn(f"No {explicit_str}installed packages match spec {spec}")
tty.warn("No {0}installed packages match spec {1}".format(explicit_str, spec))
# TODO: Need to write out a log message and/or CDASH Testing
# output that package not installed IF continue to process
@@ -198,7 +192,7 @@ def test_run(args):
# test_stage_dir
test_suite = spack.install_test.TestSuite(specs_to_test, args.alias)
test_suite.ensure_stage()
tty.msg(f"Spack test {test_suite.name}")
tty.msg("Spack test %s" % test_suite.name)
# Set up reporter
setattr(args, "package", [s.format() for s in test_suite.specs])
@@ -210,7 +204,6 @@ def test_run(args):
dirty=args.dirty,
fail_first=args.fail_first,
externals=args.externals,
timeout=args.timeout,
)

View File

@@ -18,10 +18,6 @@ class Languages(enum.Enum):
class CompilerAdaptor:
"""Provides access to compiler attributes via `Package.compiler`. Useful for
packages which do not yet access compiler properties via `self.spec[language]`.
"""
def __init__(
self, compiled_spec: spack.spec.Spec, compilers: Dict[Languages, spack.spec.Spec]
) -> None:
@@ -83,14 +79,6 @@ def implicit_rpaths(self) -> List[str]:
result.extend(CompilerPropertyDetector(compiler).implicit_rpaths())
return result
@property
def opt_flags(self) -> List[str]:
return next(iter(self.compilers.values())).package.opt_flags
@property
def debug_flags(self) -> List[str]:
return next(iter(self.compilers.values())).package.debug_flags
@property
def openmp_flag(self) -> str:
return next(iter(self.compilers.values())).package.openmp_flag
@@ -152,7 +140,7 @@ def c17_flag(self) -> str:
@property
def c23_flag(self) -> str:
return self.compilers[Languages.C].package.standard_flag(
language=Languages.C.value, standard="23"
language=Languages.C.value, standard="17"
)
@property
@@ -202,10 +190,6 @@ def f77(self):
self._lang_exists_or_raise("f77", lang=Languages.FORTRAN)
return self.compilers[Languages.FORTRAN].package.fortran
@property
def stdcxx_libs(self):
return self._maybe_return_attribute("stdcxx_libs", lang=Languages.CXX)
class DeprecatedCompiler(lang.DeprecatedProperty):
def __init__(self) -> None:

View File

@@ -7,7 +7,6 @@
import os
import re
import sys
import warnings
from typing import Any, Dict, List, Optional, Tuple
import archspec.cpu
@@ -338,15 +337,7 @@ def from_legacy_yaml(compiler_dict: Dict[str, Any]) -> List[spack.spec.Spec]:
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
pattern = re.compile(r"|".join(finder.search_patterns(pkg=pkg_cls)))
filtered_paths = [x for x in candidate_paths if pattern.search(os.path.basename(x))]
try:
detected = finder.detect_specs(pkg=pkg_cls, paths=filtered_paths)
except Exception:
warnings.warn(
f"[{__name__}] cannot detect {pkg_name} from the "
f"following paths: {', '.join(filtered_paths)}"
)
continue
detected = finder.detect_specs(pkg=pkg_cls, paths=filtered_paths)
for s in detected:
for key in ("flags", "environment", "extra_rpaths"):
if key in compiler_dict:

View File

@@ -149,12 +149,12 @@ def _getfqdn():
return socket.getfqdn()
def reader(version: vn.StandardVersion) -> Type["spack.spec.SpecfileReaderBase"]:
def reader(version: vn.ConcreteVersion) -> Type["spack.spec.SpecfileReaderBase"]:
reader_cls = {
vn.StandardVersion.from_string("5"): spack.spec.SpecfileV1,
vn.StandardVersion.from_string("6"): spack.spec.SpecfileV3,
vn.StandardVersion.from_string("7"): spack.spec.SpecfileV4,
vn.StandardVersion.from_string("8"): spack.spec.SpecfileV5,
vn.Version("5"): spack.spec.SpecfileV1,
vn.Version("6"): spack.spec.SpecfileV3,
vn.Version("7"): spack.spec.SpecfileV4,
vn.Version("8"): spack.spec.SpecfileV5,
}
return reader_cls[version]
@@ -824,7 +824,7 @@ def check(cond, msg):
db = fdata["database"]
check("version" in db, "no 'version' in JSON DB.")
self.db_version = vn.StandardVersion.from_string(db["version"])
self.db_version = vn.Version(db["version"])
if self.db_version > _DB_VERSION:
raise InvalidDatabaseVersionError(self, _DB_VERSION, self.db_version)
elif self.db_version < _DB_VERSION:

View File

@@ -20,7 +20,7 @@
import sys
from typing import Dict, List, Optional, Set, Tuple, Union
from llnl.util import tty
import llnl.util.tty
import spack.config
import spack.error
@@ -93,13 +93,14 @@ def _spec_is_valid(spec: spack.spec.Spec) -> bool:
except spack.error.SpackError:
# It is assumed here that we can at least extract the package name from the spec so we
# can look up the implementation of determine_spec_details
tty.warn(f"Constructed spec for {spec.name} does not have a string representation")
msg = f"Constructed spec for {spec.name} does not have a string representation"
llnl.util.tty.warn(msg)
return False
try:
spack.spec.Spec(str(spec))
except spack.error.SpackError:
tty.warn(
llnl.util.tty.warn(
"Constructed spec has a string representation but the string"
" representation does not evaluate to a valid spec: {0}".format(str(spec))
)
@@ -108,24 +109,20 @@ def _spec_is_valid(spec: spack.spec.Spec) -> bool:
return True
def path_to_dict(search_paths: List[str]) -> Dict[str, str]:
def path_to_dict(search_paths: List[str]):
"""Return dictionary[fullpath]: basename from list of paths"""
path_to_lib: Dict[str, str] = {}
path_to_lib = {}
# Reverse order of search directories so that a lib in the first
# entry overrides later entries
for search_path in reversed(search_paths):
try:
dir_iter = os.scandir(search_path)
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:
tty.debug(f"cannot scan '{search_path}' for external software: {e}")
continue
with dir_iter as entries:
for entry in entries:
try:
if entry.is_file():
path_to_lib[entry.path] = entry.name
except OSError as e:
tty.debug(f"cannot scan '{search_path}' for external software: {e}")
msg = f"cannot scan '{search_path}' for external software: {str(e)}"
llnl.util.tty.debug(msg)
return path_to_lib

View File

@@ -34,13 +34,11 @@ class OpenMpi(Package):
import collections.abc
import os
import re
import warnings
from typing import Any, Callable, List, Optional, Tuple, Type, Union
import llnl.util.tty.color
import spack.deptypes as dt
import spack.error
import spack.fetch_strategy
import spack.package_base
import spack.patch
@@ -610,7 +608,7 @@ def _execute_patch(
return _execute_patch
def conditional(*values: Union[str, bool], when: Optional[WhenType] = None):
def conditional(*values: List[Any], when: Optional[WhenType] = None):
"""Conditional values that can be used in variant declarations."""
# _make_when_spec returns None when the condition is statically false.
when = _make_when_spec(when)
@@ -622,7 +620,7 @@ def conditional(*values: Union[str, bool], when: Optional[WhenType] = None):
@directive("variants")
def variant(
name: str,
default: Optional[Union[bool, str, Tuple[str, ...]]] = None,
default: Optional[Any] = None,
description: str = "",
values: Optional[Union[collections.abc.Sequence, Callable[[Any], bool]]] = None,
multi: Optional[bool] = None,
@@ -652,29 +650,11 @@ def variant(
DirectiveError: If arguments passed to the directive are invalid
"""
# This validation can be removed at runtime and enforced with an audit in Spack v1.0.
# For now it's a warning to let people migrate faster.
if not (
default is None
or type(default) in (bool, str)
or (type(default) is tuple and all(type(x) is str for x in default))
):
if isinstance(default, (list, tuple)):
did_you_mean = f"default={','.join(str(x) for x in default)!r}"
else:
did_you_mean = f"default={str(default)!r}"
warnings.warn(
f"default value for variant '{name}' is not a boolean or string: default={default!r}. "
f"Did you mean {did_you_mean}?",
stacklevel=3,
category=spack.error.SpackAPIWarning,
)
def format_error(msg, pkg):
msg += " @*r{{[{0}, variant '{1}']}}"
return llnl.util.tty.color.colorize(msg.format(pkg.name, name))
if name in spack.variant.RESERVED_NAMES:
if name in spack.variant.reserved_names:
def _raise_reserved_name(pkg):
msg = "The name '%s' is reserved by Spack" % name
@@ -685,11 +665,7 @@ def _raise_reserved_name(pkg):
# Ensure we have a sequence of allowed variant values, or a
# predicate for it.
if values is None:
if (
default in (True, False)
or type(default) is str
and default.upper() in ("TRUE", "FALSE")
):
if str(default).upper() in ("TRUE", "FALSE"):
values = (True, False)
else:
values = lambda x: True
@@ -722,15 +698,12 @@ def _raise_argument_error(pkg):
# or the empty string, as the former indicates that a default
# was not set while the latter will make the variant unparsable
# from the command line
if isinstance(default, tuple):
default = ",".join(default)
if default is None or default == "":
def _raise_default_not_set(pkg):
if default is None:
msg = "either a default was not explicitly set, or 'None' was used"
else:
msg = "either a default was not explicitly set, " "or 'None' was used"
elif default == "":
msg = "the default cannot be an empty string"
raise DirectiveError(format_error(msg, pkg))

View File

@@ -144,6 +144,7 @@ class Foo(Package):
Package class, and it's how Spack gets information from the
packages to the core.
"""
global directive_names
if isinstance(dicts, str):
dicts = (dicts,)

View File

@@ -566,7 +566,7 @@
display_specs,
environment_dir_from_name,
environment_from_name_or_dir,
environment_path_scope,
environment_path_scopes,
exists,
initialize_environment_dir,
installed_specs,
@@ -603,7 +603,7 @@
"display_specs",
"environment_dir_from_name",
"environment_from_name_or_dir",
"environment_path_scope",
"environment_path_scopes",
"exists",
"initialize_environment_dir",
"installed_specs",

View File

@@ -31,6 +31,7 @@
import spack.repo
import spack.schema.env
import spack.spec
import spack.spec_list
import spack.store
import spack.user_environment as uenv
import spack.util.environment
@@ -43,10 +44,10 @@
from spack.installer import PackageInstaller
from spack.schema.env import TOP_LEVEL_KEY
from spack.spec import Spec
from spack.spec_list import SpecList
from spack.util.path import substitute_path_variables
from ..enums import ConfigScopePriority
from .list import SpecList, SpecListError, SpecListParser
SpecPair = spack.concretize.SpecPair
@@ -96,15 +97,16 @@ def environment_name(path: Union[str, pathlib.Path]) -> str:
return path_str
def ensure_no_disallowed_env_config_mods(scope: spack.config.ConfigScope) -> None:
config = scope.get_section("config")
if config and "environments_root" in config["config"]:
raise SpackEnvironmentError(
"Spack environments are prohibited from modifying 'config:environments_root' "
"because it can make the definition of the environment ill-posed. Please "
"remove from your environment and place it in a permanent scope such as "
"defaults, system, site, etc."
)
def ensure_no_disallowed_env_config_mods(scopes: List[spack.config.ConfigScope]) -> None:
for scope in scopes:
config = scope.get_section("config")
if config and "environments_root" in config["config"]:
raise SpackEnvironmentError(
"Spack environments are prohibited from modifying 'config:environments_root' "
"because it can make the definition of the environment ill-posed. Please "
"remove from your environment and place it in a permanent scope such as "
"defaults, system, site, etc."
)
def default_manifest_yaml():
@@ -931,10 +933,8 @@ def __init__(self, manifest_dir: Union[str, pathlib.Path]) -> None:
self.new_specs: List[Spec] = []
self.views: Dict[str, ViewDescriptor] = {}
#: Parser for spec lists
self._spec_lists_parser = SpecListParser()
#: Specs from "spack.yaml"
self.spec_lists: Dict[str, SpecList] = {}
self.spec_lists: Dict[str, SpecList] = {user_speclist_name: SpecList()}
#: User specs from the last concretization
self.concretized_user_specs: List[Spec] = []
#: Roots associated with the last concretization, in order
@@ -1002,6 +1002,26 @@ def write_transaction(self):
"""Get a write lock context manager for use in a `with` block."""
return lk.WriteTransaction(self.txlock, acquire=self._re_read)
def _process_definition(self, entry):
"""Process a single spec definition item."""
when_string = entry.get("when")
if when_string is not None:
when = spack.spec.eval_conditional(when_string)
assert len([x for x in entry if x != "when"]) == 1
else:
when = True
assert len(entry) == 1
if when:
for name, spec_list in entry.items():
if name == "when":
continue
user_specs = SpecList(name, spec_list, self.spec_lists.copy())
if name in self.spec_lists:
self.spec_lists[name].extend(user_specs)
else:
self.spec_lists[name] = user_specs
def _process_view(self, env_view: Optional[Union[bool, str, Dict]]):
"""Process view option(s), which can be boolean, string, or None.
@@ -1063,24 +1083,21 @@ def _process_concrete_includes(self):
def _construct_state_from_manifest(self):
"""Set up user specs and views from the manifest file."""
self.spec_lists = collections.OrderedDict()
self.views = {}
self._sync_speclists()
self._process_view(spack.config.get("view", True))
self._process_concrete_includes()
def _sync_speclists(self):
self.spec_lists = {}
self.spec_lists.update(
self._spec_lists_parser.parse_definitions(
data=spack.config.CONFIG.get("definitions", [])
)
)
for item in spack.config.get("definitions", []):
self._process_definition(item)
env_configuration = self.manifest[TOP_LEVEL_KEY]
spec_list = env_configuration.get(user_speclist_name, [])
self.spec_lists[user_speclist_name] = self._spec_lists_parser.parse_user_specs(
name=user_speclist_name, yaml_list=spec_list
user_specs = SpecList(
user_speclist_name, [s for s in spec_list if s], self.spec_lists.copy()
)
self.spec_lists[user_speclist_name] = user_specs
self._process_view(spack.config.get("view", True))
self._process_concrete_includes()
def all_concretized_user_specs(self) -> List[Spec]:
"""Returns all of the concretized user specs of the environment and
@@ -1151,7 +1168,9 @@ def clear(self, re_read=False):
re_read: If ``True``, do not clear ``new_specs``. This value cannot be read from yaml,
and needs to be maintained when re-reading an existing environment.
"""
self.spec_lists = {}
self.spec_lists = collections.OrderedDict()
self.spec_lists[user_speclist_name] = SpecList()
self._dev_specs = {}
self.concretized_order = [] # roots of last concretize, in order
self.concretized_user_specs = [] # user specs from last concretize
@@ -1258,6 +1277,22 @@ def destroy(self):
"""Remove this environment from Spack entirely."""
shutil.rmtree(self.path)
def update_stale_references(self, from_list=None):
"""Iterate over spec lists updating references."""
if not from_list:
from_list = next(iter(self.spec_lists.keys()))
index = list(self.spec_lists.keys()).index(from_list)
# spec_lists is an OrderedDict to ensure lists read from the manifest
# are maintainted in order, hence, all list entries after the modified
# list may refer to the modified list requiring stale references to be
# updated.
for i, (name, speclist) in enumerate(
list(self.spec_lists.items())[index + 1 :], index + 1
):
new_reference = dict((n, self.spec_lists[n]) for n in list(self.spec_lists.keys())[:i])
speclist.update_reference(new_reference)
def add(self, user_spec, list_name=user_speclist_name):
"""Add a single user_spec (non-concretized) to the Environment
@@ -1277,17 +1312,18 @@ def add(self, user_spec, list_name=user_speclist_name):
elif not spack.repo.PATH.exists(spec.name) and not spec.abstract_hash:
virtuals = spack.repo.PATH.provider_index.providers.keys()
if spec.name not in virtuals:
raise SpackEnvironmentError(f"no such package: {spec.name}")
msg = "no such package: %s" % spec.name
raise SpackEnvironmentError(msg)
list_to_change = self.spec_lists[list_name]
existing = str(spec) in list_to_change.yaml_list
if not existing:
list_to_change.add(str(spec))
self.update_stale_references(list_name)
if list_name == user_speclist_name:
self.manifest.add_user_spec(str(user_spec))
else:
self.manifest.add_definition(str(user_spec), list_name=list_name)
self._sync_speclists()
return bool(not existing)
@@ -1331,17 +1367,18 @@ def change_existing_spec(
"There are no specs named {0} in {1}".format(match_spec.name, list_name)
)
elif len(matches) > 1 and not allow_changing_multiple_specs:
raise ValueError(f"{str(match_spec)} matches multiple specs")
raise ValueError("{0} matches multiple specs".format(str(match_spec)))
for idx, spec in matches:
override_spec = Spec.override(spec, change_spec)
self.spec_lists[list_name].replace(idx, str(override_spec))
if list_name == user_speclist_name:
self.manifest.override_user_spec(str(override_spec), idx=idx)
else:
self.manifest.override_definition(
str(spec), override=str(override_spec), list_name=list_name
)
self._sync_speclists()
self.update_stale_references(from_list=list_name)
def remove(self, query_spec, list_name=user_speclist_name, force=False):
"""Remove specs from an environment that match a query_spec"""
@@ -1369,17 +1406,22 @@ def remove(self, query_spec, list_name=user_speclist_name, force=False):
raise SpackEnvironmentError(f"{err_msg_header}, no spec matches")
old_specs = set(self.user_specs)
# Remove specs from the appropriate spec list
new_specs = set()
for spec in matches:
if spec not in list_to_change:
continue
try:
list_to_change.remove(spec)
except SpecListError as e:
self.update_stale_references(list_name)
new_specs = set(self.user_specs)
except spack.spec_list.SpecListError as e:
# define new specs list
new_specs = set(self.user_specs)
msg = str(e)
if force:
msg += " It will be removed from the concrete specs."
# Mock new specs, so we can remove this spec from concrete spec lists
new_specs.remove(spec)
tty.warn(msg)
else:
if list_name == user_speclist_name:
@@ -1387,11 +1429,7 @@ def remove(self, query_spec, list_name=user_speclist_name, force=False):
else:
self.manifest.remove_definition(str(spec), list_name=list_name)
# Recompute "definitions" and user specs
self._sync_speclists()
new_specs = set(self.user_specs)
# If 'force', update stale concretized specs
# If force, update stale concretized specs
for spec in old_specs - new_specs:
if force and spec in self.concretized_user_specs:
i = self.concretized_user_specs.index(spec)
@@ -1605,6 +1643,23 @@ def _concretize_separately(self, tests=False):
# Unify the specs objects, so we get correct references to all parents
self._read_lockfile_dict(self._to_lockfile_dict())
# Re-attach information on test dependencies
if tests:
# This is slow, but the information on test dependency is lost
# after unification or when reading from a lockfile.
for h in self.specs_by_hash:
current_spec, computed_spec = self.specs_by_hash[h], by_hash[h]
for node in computed_spec.traverse():
test_edges = node.edges_to_dependencies(depflag=dt.TEST)
for current_edge in test_edges:
test_dependency = current_edge.spec
if test_dependency in current_spec[node.name]:
continue
current_spec[node.name].add_dependency_edge(
test_dependency.copy(), depflag=dt.TEST, virtuals=current_edge.virtuals
)
return concretized_specs
@property
@@ -2662,9 +2717,9 @@ def __init__(self, manifest_dir: Union[pathlib.Path, str], name: Optional[str] =
self.scope_name = f"env:{self.name}"
self.config_stage_dir = os.path.join(env_subdir_path(manifest_dir), "config")
#: Configuration scope associated with this environment. Note that this is not
#: Configuration scopes associated with this environment. Note that these are not
#: invalidated by a re-read of the manifest file.
self._env_config_scope: Optional[spack.config.ConfigScope] = None
self._config_scopes: Optional[List[spack.config.ConfigScope]] = None
if not self.manifest_file.exists():
msg = f"cannot find '{manifest_name}' in {self.manifest_dir}"
@@ -2773,8 +2828,6 @@ def add_definition(self, user_spec: str, list_name: str) -> None:
item[list_name].append(user_spec)
break
# "definitions" can be remote, so we need to update the global config too
spack.config.CONFIG.set("definitions", defs, scope=self.scope_name)
self.changed = True
def remove_definition(self, user_spec: str, list_name: str) -> None:
@@ -2801,8 +2854,6 @@ def remove_definition(self, user_spec: str, list_name: str) -> None:
except ValueError:
pass
# "definitions" can be remote, so we need to update the global config too
spack.config.CONFIG.set("definitions", defs, scope=self.scope_name)
self.changed = True
def override_definition(self, user_spec: str, *, override: str, list_name: str) -> None:
@@ -2828,8 +2879,6 @@ def override_definition(self, user_spec: str, *, override: str, list_name: str)
except ValueError:
pass
# "definitions" can be remote, so we need to update the global config too
spack.config.CONFIG.set("definitions", defs, scope=self.scope_name)
self.changed = True
def _iterate_on_definitions(self, definitions, *, list_name, err_msg):
@@ -2908,27 +2957,33 @@ def __str__(self):
return str(self.manifest_file)
@property
def env_config_scope(self) -> spack.config.ConfigScope:
"""The configuration scope for the environment manifest"""
if self._env_config_scope is None:
self._env_config_scope = spack.config.SingleFileScope(
def env_config_scopes(self) -> List[spack.config.ConfigScope]:
"""A list of all configuration scopes for the environment manifest. On the first call this
instantiates all the scopes, on subsequent calls it returns the cached list."""
if self._config_scopes is not None:
return self._config_scopes
scopes: List[spack.config.ConfigScope] = [
spack.config.SingleFileScope(
self.scope_name,
str(self.manifest_file),
spack.schema.env.schema,
yaml_path=[TOP_LEVEL_KEY],
)
ensure_no_disallowed_env_config_mods(self._env_config_scope)
return self._env_config_scope
]
ensure_no_disallowed_env_config_mods(scopes)
self._config_scopes = scopes
return scopes
def prepare_config_scope(self) -> None:
"""Add the manifest's scope to the global configuration search path."""
spack.config.CONFIG.push_scope(
self.env_config_scope, priority=ConfigScopePriority.ENVIRONMENT
)
"""Add the manifest's scopes to the global configuration search path."""
for scope in self.env_config_scopes:
spack.config.CONFIG.push_scope(scope, priority=ConfigScopePriority.ENVIRONMENT)
def deactivate_config_scope(self) -> None:
"""Remove the manifest's scope from the global config path."""
spack.config.CONFIG.remove_scope(self.env_config_scope.name)
"""Remove any of the manifest's scopes from the global config path."""
for scope in self.env_config_scopes:
spack.config.CONFIG.remove_scope(scope.name)
@contextlib.contextmanager
def use_config(self):
@@ -2939,8 +2994,8 @@ def use_config(self):
self.deactivate_config_scope()
def environment_path_scope(name: str, path: str) -> Optional[spack.config.ConfigScope]:
"""Retrieve the suitably named environment path scope
def environment_path_scopes(name: str, path: str) -> Optional[List[spack.config.ConfigScope]]:
"""Retrieve the suitably named environment path scopes
Arguments:
name: configuration scope name
@@ -2955,9 +3010,11 @@ def environment_path_scope(name: str, path: str) -> Optional[spack.config.Config
else:
return None
manifest.env_config_scope.name = f"{name}:{manifest.env_config_scope.name}"
manifest.env_config_scope.writable = False
return manifest.env_config_scope
for scope in manifest.env_config_scopes:
scope.name = f"{name}:{scope.name}"
scope.writable = False
return manifest.env_config_scopes
class SpackEnvironmentError(spack.error.SpackError):

View File

@@ -49,23 +49,10 @@ def activate_header(env, shell, prompt=None, view: Optional[str] = None):
cmds += 'set "SPACK_ENV=%s"\n' % env.path
if view:
cmds += 'set "SPACK_ENV_VIEW=%s"\n' % view
if prompt:
old_prompt = os.environ.get("SPACK_OLD_PROMPT")
if not old_prompt:
old_prompt = os.environ.get("PROMPT")
cmds += f'set "SPACK_OLD_PROMPT={old_prompt}"\n'
cmds += f'set "PROMPT={prompt} $P$G"\n'
elif shell == "pwsh":
cmds += "$Env:SPACK_ENV='%s'\n" % env.path
if view:
cmds += "$Env:SPACK_ENV_VIEW='%s'\n" % view
if prompt:
cmds += (
"function global:prompt { $pth = $(Convert-Path $(Get-Location))"
' | Split-Path -leaf; if(!"$Env:SPACK_OLD_PROMPT") '
'{$Env:SPACK_OLD_PROMPT="[spack] PS $pth>"}; '
'"%s PS $pth>"}\n' % prompt
)
else:
bash_color_prompt = colorize(f"@G{{{prompt}}}", color=True, enclose=True)
zsh_color_prompt = colorize(f"@G{{{prompt}}}", color=True, enclose=False, zsh=True)
@@ -120,19 +107,10 @@ def deactivate_header(shell):
cmds += 'set "SPACK_ENV="\n'
cmds += 'set "SPACK_ENV_VIEW="\n'
# TODO: despacktivate
old_prompt = os.environ.get("SPACK_OLD_PROMPT")
if old_prompt:
cmds += f'set "PROMPT={old_prompt}"\n'
cmds += 'set "SPACK_OLD_PROMPT="\n'
# TODO: prompt
elif shell == "pwsh":
cmds += "Set-Item -Path Env:SPACK_ENV\n"
cmds += "Set-Item -Path Env:SPACK_ENV_VIEW\n"
cmds += (
"function global:prompt { $pth = $(Convert-Path $(Get-Location))"
' | Split-Path -leaf; $spack_prompt = "[spack] $pth >"; '
'if("$Env:SPACK_OLD_PROMPT") {$spack_prompt=$Env:SPACK_OLD_PROMPT};'
" $spack_prompt}\n"
)
else:
cmds += "if [ ! -z ${SPACK_ENV+x} ]; then\n"
cmds += "unset SPACK_ENV; export SPACK_ENV;\n"

View File

@@ -27,14 +27,11 @@
import os
import re
import shutil
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
import urllib.response
from pathlib import PurePath
from typing import Callable, List, Mapping, Optional
from typing import List, Optional
import llnl.url
import llnl.util
@@ -222,114 +219,6 @@ def mirror_id(self):
"""BundlePackages don't have a mirror id."""
def _format_speed(total_bytes: int, elapsed: float) -> str:
"""Return a human-readable average download speed string."""
elapsed = 1 if elapsed <= 0 else elapsed # avoid divide by zero
speed = total_bytes / elapsed
if speed >= 1e9:
return f"{speed / 1e9:6.1f} GB/s"
elif speed >= 1e6:
return f"{speed / 1e6:6.1f} MB/s"
elif speed >= 1e3:
return f"{speed / 1e3:6.1f} KB/s"
return f"{speed:6.1f} B/s"
def _format_bytes(total_bytes: int) -> str:
"""Return a human-readable total bytes string."""
if total_bytes >= 1e9:
return f"{total_bytes / 1e9:7.2f} GB"
elif total_bytes >= 1e6:
return f"{total_bytes / 1e6:7.2f} MB"
elif total_bytes >= 1e3:
return f"{total_bytes / 1e3:7.2f} KB"
return f"{total_bytes:7.2f} B"
class FetchProgress:
#: Characters to rotate in the spinner.
spinner = ["|", "/", "-", "\\"]
def __init__(
self,
total_bytes: Optional[int] = None,
enabled: bool = True,
get_time: Callable[[], float] = time.time,
) -> None:
"""Initialize a FetchProgress instance.
Args:
total_bytes: Total number of bytes to download, if known.
enabled: Whether to print progress information.
get_time: Function to get the current time."""
#: Number of bytes downloaded so far.
self.current_bytes = 0
#: Delta time between progress prints
self.delta = 0.1
#: Whether to print progress information.
self.enabled = enabled
#: Function to get the current time.
self.get_time = get_time
#: Time of last progress print to limit output
self.last_printed = 0.0
#: Time of start of download
self.start_time = get_time() if enabled else 0.0
#: Total number of bytes to download, if known.
self.total_bytes = total_bytes if total_bytes and total_bytes > 0 else 0
#: Index of spinner character to print (used if total bytes is unknown)
self.index = 0
@classmethod
def from_headers(
cls,
headers: Mapping[str, str],
enabled: bool = True,
get_time: Callable[[], float] = time.time,
) -> "FetchProgress":
"""Create a FetchProgress instance from HTTP headers."""
# headers.get is case-insensitive if it's from a HTTPResponse object.
content_length = headers.get("Content-Length")
try:
total_bytes = int(content_length) if content_length else None
except ValueError:
total_bytes = None
return cls(total_bytes=total_bytes, enabled=enabled, get_time=get_time)
def advance(self, num_bytes: int, out=sys.stdout) -> None:
if not self.enabled:
return
self.current_bytes += num_bytes
self.print(out=out)
def print(self, final: bool = False, out=sys.stdout) -> None:
if not self.enabled:
return
current_time = self.get_time()
if self.last_printed + self.delta < current_time or final:
self.last_printed = current_time
# print a newline if this is the final update
maybe_newline = "\n" if final else ""
# if we know the total bytes, show a percentage, otherwise a spinner
if self.total_bytes > 0:
percentage = min(100 * self.current_bytes / self.total_bytes, 100.0)
percent_or_spinner = f"[{percentage:3.0f}%] "
else:
# only show the spinner if we are not at 100%
if final:
percent_or_spinner = "[100%] "
else:
percent_or_spinner = f"[ {self.spinner[self.index]} ] "
self.index = (self.index + 1) % len(self.spinner)
print(
f"\r {percent_or_spinner}{_format_bytes(self.current_bytes)} "
f"@ {_format_speed(self.current_bytes, current_time - self.start_time)}"
f"{maybe_newline}",
end="",
flush=True,
file=out,
)
@fetcher
class URLFetchStrategy(FetchStrategy):
"""URLFetchStrategy pulls source code from a URL for an archive, check the
@@ -406,9 +295,8 @@ def fetch(self):
)
def _fetch_from_url(self, url):
fetch_method = spack.config.get("config:url_fetch_method", "urllib")
if fetch_method.startswith("curl"):
return self._fetch_curl(url, config_args=fetch_method.split()[1:])
if spack.config.get("config:url_fetch_method") == "curl":
return self._fetch_curl(url)
else:
return self._fetch_urllib(url)
@@ -427,7 +315,7 @@ def _check_headers(self, headers):
tty.warn(msg)
@_needs_stage
def _fetch_urllib(self, url, chunk_size=65536):
def _fetch_urllib(self, url):
save_file = self.stage.save_filename
request = urllib.request.Request(url, headers={"User-Agent": web_util.SPACK_USER_AGENT})
@@ -438,15 +326,8 @@ def _fetch_urllib(self, url, chunk_size=65536):
try:
response = web_util.urlopen(request)
tty.msg(f"Fetching {url}")
progress = FetchProgress.from_headers(response.headers, enabled=sys.stdout.isatty())
with open(save_file, "wb") as f:
while True:
chunk = response.read(chunk_size)
if not chunk:
break
f.write(chunk)
progress.advance(len(chunk))
progress.print(final=True)
shutil.copyfileobj(response, f)
except OSError as e:
# clean up archive on failure.
if self.archive_file:
@@ -464,7 +345,7 @@ def _fetch_urllib(self, url, chunk_size=65536):
self._check_headers(str(response.headers))
@_needs_stage
def _fetch_curl(self, url, config_args=[]):
def _fetch_curl(self, url):
save_file = None
partial_file = None
if self.stage.save_filename:
@@ -493,7 +374,7 @@ def _fetch_curl(self, url, config_args=[]):
timeout = self.extra_options.get("timeout")
base_args = web_util.base_curl_fetch_args(url, timeout)
curl_args = config_args + save_args + base_args + cookie_args
curl_args = save_args + base_args + cookie_args
# Run curl but grab the mime type from the http headers
curl = self.curl

View File

@@ -12,7 +12,7 @@
import shutil
import sys
from collections import Counter, OrderedDict
from typing import Callable, Iterable, List, Optional, Tuple, Type, TypeVar, Union
from typing import Callable, List, Optional, Tuple, Type, TypeVar, Union
import llnl.util.filesystem as fs
import llnl.util.tty as tty
@@ -391,7 +391,7 @@ def phase_tests(self, builder, phase_name: str, method_names: List[str]):
if self.test_failures:
raise TestFailure(self.test_failures)
def stand_alone_tests(self, kwargs, timeout: Optional[int] = None) -> None:
def stand_alone_tests(self, kwargs):
"""Run the package's stand-alone tests.
Args:
@@ -399,9 +399,7 @@ def stand_alone_tests(self, kwargs, timeout: Optional[int] = None) -> None:
"""
import spack.build_environment # avoid circular dependency
spack.build_environment.start_build_process(
self.pkg, test_process, kwargs, timeout=timeout
)
spack.build_environment.start_build_process(self.pkg, test_process, kwargs)
def parts(self) -> int:
"""The total number of (checked) test parts."""
@@ -849,7 +847,7 @@ def write_test_summary(counts: "Counter"):
class TestSuite:
"""The class that manages specs for ``spack test run`` execution."""
def __init__(self, specs: Iterable[Spec], alias: Optional[str] = None) -> None:
def __init__(self, specs, alias=None):
# copy so that different test suites have different package objects
# even if they contain the same spec
self.specs = [spec.copy() for spec in specs]
@@ -857,43 +855,42 @@ def __init__(self, specs: Iterable[Spec], alias: Optional[str] = None) -> None:
self.current_base_spec = None # spec currently running do_test
self.alias = alias
self._hash: Optional[str] = None
self._stage: Optional[Prefix] = None
self._hash = None
self._stage = None
self.counts: "Counter" = Counter()
@property
def name(self) -> str:
def name(self):
"""The name (alias or, if none, hash) of the test suite."""
return self.alias if self.alias else self.content_hash
@property
def content_hash(self) -> str:
def content_hash(self):
"""The hash used to uniquely identify the test suite."""
if not self._hash:
json_text = sjson.dump(self.to_dict())
assert json_text is not None, f"{__name__} unexpected value for 'json_text'"
sha = hashlib.sha1(json_text.encode("utf-8"))
b32_hash = base64.b32encode(sha.digest()).lower()
b32_hash = b32_hash.decode("utf-8")
self._hash = b32_hash
return self._hash
def __call__(
self,
*,
remove_directory: bool = True,
dirty: bool = False,
fail_first: bool = False,
externals: bool = False,
timeout: Optional[int] = None,
):
def __call__(self, *args, **kwargs):
self.write_reproducibility_data()
remove_directory = kwargs.get("remove_directory", True)
dirty = kwargs.get("dirty", False)
fail_first = kwargs.get("fail_first", False)
externals = kwargs.get("externals", False)
for spec in self.specs:
try:
if spec.package.test_suite:
raise TestSuiteSpecError(
f"Package {spec.package.name} cannot be run in two test suites at once"
"Package {} cannot be run in two test suites at once".format(
spec.package.name
)
)
# Set up the test suite to know which test is running
@@ -908,7 +905,7 @@ def __call__(
fs.mkdirp(test_dir)
# run the package tests
spec.package.do_test(dirty=dirty, externals=externals, timeout=timeout)
spec.package.do_test(dirty=dirty, externals=externals)
# Clean up on success
if remove_directory:
@@ -959,12 +956,15 @@ def __call__(
if failures:
raise TestSuiteFailure(failures)
def test_status(self, spec: spack.spec.Spec, externals: bool) -> TestStatus:
"""Returns the overall test results status for the spec.
def test_status(self, spec: spack.spec.Spec, externals: bool) -> Optional[TestStatus]:
"""Determine the overall test results status for the spec.
Args:
spec: instance of the spec under test
externals: ``True`` if externals are to be tested, else ``False``
Returns:
the spec's test status if available or ``None``
"""
tests_status_file = self.tested_file_for_spec(spec)
if not os.path.exists(tests_status_file):
@@ -981,84 +981,109 @@ def test_status(self, spec: spack.spec.Spec, externals: bool) -> TestStatus:
value = (f.read()).strip("\n")
return TestStatus(int(value)) if value else TestStatus.NO_TESTS
def ensure_stage(self) -> None:
def ensure_stage(self):
"""Ensure the test suite stage directory exists."""
if not os.path.exists(self.stage):
fs.mkdirp(self.stage)
@property
def stage(self) -> Prefix:
"""The root test suite stage directory"""
def stage(self):
"""The root test suite stage directory.
Returns:
str: the spec's test stage directory path
"""
if not self._stage:
self._stage = Prefix(fs.join_path(get_test_stage_dir(), self.content_hash))
return self._stage
@stage.setter
def stage(self, value: Union[Prefix, str]) -> None:
def stage(self, value):
"""Set the value of a non-default stage directory."""
self._stage = value if isinstance(value, Prefix) else Prefix(value)
@property
def results_file(self) -> Prefix:
def results_file(self):
"""The path to the results summary file."""
return self.stage.join(results_filename)
@classmethod
def test_pkg_id(cls, spec: Spec) -> str:
def test_pkg_id(cls, spec):
"""The standard install test package identifier.
Args:
spec: instance of the spec under test
Returns:
str: the install test package identifier
"""
return spec.format_path("{name}-{version}-{hash:7}")
@classmethod
def test_log_name(cls, spec: Spec) -> str:
def test_log_name(cls, spec):
"""The standard log filename for a spec.
Args:
spec: instance of the spec under test
"""
return f"{cls.test_pkg_id(spec)}-test-out.txt"
spec (spack.spec.Spec): instance of the spec under test
def log_file_for_spec(self, spec: Spec) -> Prefix:
Returns:
str: the spec's log filename
"""
return "%s-test-out.txt" % cls.test_pkg_id(spec)
def log_file_for_spec(self, spec):
"""The test log file path for the provided spec.
Args:
spec: instance of the spec under test
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the path to the spec's log file
"""
return self.stage.join(self.test_log_name(spec))
def test_dir_for_spec(self, spec: Spec) -> Prefix:
def test_dir_for_spec(self, spec):
"""The path to the test stage directory for the provided spec.
Args:
spec: instance of the spec under test
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the spec's test stage directory path
"""
return Prefix(self.stage.join(self.test_pkg_id(spec)))
@classmethod
def tested_file_name(cls, spec: Spec) -> str:
def tested_file_name(cls, spec):
"""The standard test status filename for the spec.
Args:
spec: instance of the spec under test
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the spec's test status filename
"""
return "%s-tested.txt" % cls.test_pkg_id(spec)
def tested_file_for_spec(self, spec: Spec) -> str:
def tested_file_for_spec(self, spec):
"""The test status file path for the spec.
Args:
spec: instance of the spec under test
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the spec's test status file path
"""
return fs.join_path(self.stage, self.tested_file_name(spec))
@property
def current_test_cache_dir(self) -> str:
def current_test_cache_dir(self):
"""Path to the test stage directory where the current spec's cached
build-time files were automatically copied.
Returns:
str: path to the current spec's staged, cached build-time files.
Raises:
TestSuiteSpecError: If there is no spec being tested
"""
@@ -1070,10 +1095,13 @@ def current_test_cache_dir(self) -> str:
return self.test_dir_for_spec(base_spec).cache.join(test_spec.name)
@property
def current_test_data_dir(self) -> str:
def current_test_data_dir(self):
"""Path to the test stage directory where the current spec's custom
package (data) files were automatically copied.
Returns:
str: path to the current spec's staged, custom package (data) files
Raises:
TestSuiteSpecError: If there is no spec being tested
"""
@@ -1084,17 +1112,17 @@ def current_test_data_dir(self) -> str:
base_spec = self.current_base_spec
return self.test_dir_for_spec(base_spec).data.join(test_spec.name)
def write_test_result(self, spec: Spec, result: TestStatus) -> None:
def write_test_result(self, spec, result):
"""Write the spec's test result to the test suite results file.
Args:
spec: instance of the spec under test
result: result from the spec's test execution (e.g, PASSED)
spec (spack.spec.Spec): instance of the spec under test
result (str): result from the spec's test execution (e.g, PASSED)
"""
msg = f"{self.test_pkg_id(spec)} {result}"
_add_msg_to_file(self.results_file, msg)
def write_reproducibility_data(self) -> None:
def write_reproducibility_data(self):
for spec in self.specs:
repo_cache_path = self.stage.repo.join(spec.name)
spack.repo.PATH.dump_provenance(spec, repo_cache_path)
@@ -1139,12 +1167,12 @@ def from_dict(d):
return TestSuite(specs, alias)
@staticmethod
def from_file(filename: str) -> "TestSuite":
def from_file(filename):
"""Instantiate a TestSuite using the specs and optional alias
provided in the given file.
Args:
filename: The path to the JSON file containing the test
filename (str): The path to the JSON file containing the test
suite specs and optional alias.
Raises:

View File

@@ -20,7 +20,6 @@
import signal
import subprocess as sp
import sys
import tempfile
import traceback
import warnings
from typing import List, Tuple
@@ -42,7 +41,6 @@
import spack.paths
import spack.platforms
import spack.repo
import spack.solver.asp
import spack.spec
import spack.store
import spack.util.debug
@@ -873,8 +871,8 @@ def add_command_line_scopes(
"""
for i, path in enumerate(command_line_scopes):
name = f"cmd_scope_{i}"
scope = ev.environment_path_scope(name, path)
if scope is None:
scopes = ev.environment_path_scopes(name, path)
if scopes is None:
if os.path.isdir(path): # directory with config files
cfg.push_scope(
spack.config.DirectoryConfigScope(name, path, writable=False),
@@ -887,7 +885,8 @@ def add_command_line_scopes(
else:
raise spack.error.ConfigError(f"Invalid configuration scope: {path}")
cfg.push_scope(scope, priority=ConfigScopePriority.CUSTOM)
for scope in scopes:
cfg.push_scope(scope, priority=ConfigScopePriority.CUSTOM)
def _main(argv=None):
@@ -1048,10 +1047,6 @@ def main(argv=None):
try:
return _main(argv)
except spack.solver.asp.OutputDoesNotSatisfyInputError as e:
_handle_solver_bug(e)
return 1
except spack.error.SpackError as e:
tty.debug(e)
e.die() # gracefully die on any SpackErrors
@@ -1075,45 +1070,5 @@ def main(argv=None):
return 3
def _handle_solver_bug(
e: spack.solver.asp.OutputDoesNotSatisfyInputError, out=sys.stderr, root=None
) -> None:
# when the solver outputs specs that do not satisfy the input and spack is used as a command
# line tool, we dump the incorrect output specs to json so users can upload them in bug reports
wrong_output = [(input, output) for input, output in e.input_to_output if output is not None]
no_output = [input for input, output in e.input_to_output if output is None]
if no_output:
tty.error(
"internal solver error: the following specs were not solved:\n - "
+ "\n - ".join(str(s) for s in no_output),
stream=out,
)
if wrong_output:
msg = (
"internal solver error: the following specs were concretized, but do not satisfy the "
"input:\n - "
+ "\n - ".join(str(s) for s, _ in wrong_output)
+ "\n Please report a bug at https://github.com/spack/spack/issues"
)
# try to write the input/output specs to a temporary directory for bug reports
try:
tmpdir = tempfile.mkdtemp(prefix="spack-asp-", dir=root)
files = []
for i, (input, output) in enumerate(wrong_output, start=1):
in_file = os.path.join(tmpdir, f"input-{i}.json")
out_file = os.path.join(tmpdir, f"output-{i}.json")
files.append(in_file)
files.append(out_file)
with open(in_file, "w", encoding="utf-8") as f:
input.to_json(f)
with open(out_file, "w", encoding="utf-8") as f:
output.to_json(f)
msg += " and attach the following files:\n - " + "\n - ".join(files)
except Exception:
msg += "."
tty.error(msg, stream=out)
class SpackCommandError(Exception):
"""Raised when SpackCommand execution fails."""

View File

@@ -162,7 +162,6 @@ class tty:
configure: Executable
make_jobs: int
make: MakeExecutable
nmake: Executable
ninja: MakeExecutable
python_include: str
python_platlib: str

View File

@@ -14,6 +14,7 @@
import functools
import glob
import hashlib
import importlib
import io
import os
import re
@@ -21,13 +22,26 @@
import textwrap
import time
import traceback
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Optional,
Sequence,
Set,
Tuple,
Type,
TypeVar,
Union,
)
from typing_extensions import Literal
from typing_extensions import Literal, final
import llnl.util.filesystem as fsys
import llnl.util.tty as tty
from llnl.util.lang import ClassProperty, classproperty, memoized
from llnl.util.lang import classproperty, memoized
import spack.config
import spack.dependency
@@ -700,10 +714,10 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta):
_verbose = None
#: Package homepage where users can find more information about the package
homepage: ClassProperty[Optional[str]] = None
homepage: Optional[str] = None
#: Default list URL (place to find available versions)
list_url: ClassProperty[Optional[str]] = None
list_url: Optional[str] = None
#: Link depth to which list_url should be searched for new versions
list_depth = 0
@@ -817,12 +831,12 @@ def package_dir(cls):
@classproperty
def module(cls):
"""Module instance that this package class is defined in.
"""Module object (not just the name) that this package is defined in.
We use this to add variables to package modules. This makes
install() methods easier to write (e.g., can call configure())
"""
return sys.modules[cls.__module__]
return importlib.import_module(cls.__module__)
@classproperty
def namespace(cls):
@@ -1380,6 +1394,75 @@ def command(self) -> spack.util.executable.Executable:
return spack.util.executable.Executable(path)
raise RuntimeError(f"Unable to locate {self.spec.name} command in {self.home.bin}")
def find_headers(
self, *, features: Sequence[str] = (), virtual: Optional[str] = None
) -> fsys.HeaderList:
"""Return the header list for this package based on the query. This method can be
overridden by individual packages to return package specific headers.
Args:
features: query argument to filter or extend the header list.
virtual: when set, return headers relevant for the virtual provided by this package.
Raises:
spack.error.NoHeadersError: if there was an error locating the headers.
"""
spec = self.spec
home = self.home
headers = fsys.find_headers("*", root=home.include, recursive=True)
if headers:
return headers
raise spack.error.NoHeadersError(f"Unable to locate {spec.name} headers in {home}")
def find_libs(
self, *, features: Sequence[str] = (), virtual: Optional[str] = None
) -> fsys.LibraryList:
"""Return the library list for this package based on the query. This method can be
overridden by individual packages to return package specific libraries.
Args:
features: query argument to filter or extend the library list.
virtual: when set, return libraries relevant for the virtual provided by this package.
Raises:
spack.error.NoLibrariesError: if there was an error locating the libraries.
"""
spec = self.spec
home = self.home
name = self.spec.name.replace("-", "?")
# Avoid double 'lib' for packages whose names already start with lib
if not name.startswith("lib") and not spec.satisfies("platform=windows"):
name = "lib" + name
# If '+shared' search only for shared library; if '~shared' search only for
# static library; otherwise, first search for shared and then for static.
search_shared = (
[True] if ("+shared" in spec) else ([False] if ("~shared" in spec) else [True, False])
)
for shared in search_shared:
# Since we are searching for link libraries, on Windows search only for
# ".Lib" extensions by default as those represent import libraries for implicit links.
libs = fsys.find_libraries(name, home, shared=shared, recursive=True, runtime=False)
if libs:
return libs
raise spack.error.NoLibrariesError(
f"Unable to recursively locate {spec.name} libraries in {home}"
)
@final
def query_headers(self, name: str, *, features: Sequence[str] = ()) -> fsys.HeaderList:
"""Returns the header list for a dependency ``name``."""
spec, is_virtual = self.spec._get_dependency_by_name(name)
return spec.package.find_headers(features=features, virtual=name if is_virtual else None)
@final
def query_libs(self, name: str, *, features: Sequence[str] = ()) -> fsys.LibraryList:
"""Returns the library list for a dependency ``name``."""
spec, is_virtual = self.spec._get_dependency_by_name(name)
return spec.package.find_libs(features=features, virtual=name if is_virtual else None)
def url_version(self, version):
"""
Given a version, this returns a string that should be substituted
@@ -1820,7 +1903,7 @@ def _resource_stage(self, resource):
resource_stage_folder = "-".join(pieces)
return resource_stage_folder
def do_test(self, *, dirty=False, externals=False, timeout: Optional[int] = None):
def do_test(self, dirty=False, externals=False):
if self.test_requires_compiler and not any(
lang in self.spec for lang in ("c", "cxx", "fortran")
):
@@ -1838,7 +1921,7 @@ def do_test(self, *, dirty=False, externals=False, timeout: Optional[int] = None
"verbose": tty.is_verbose(),
}
self.tester.stand_alone_tests(kwargs, timeout=timeout)
self.tester.stand_alone_tests(kwargs)
def unit_test_check(self):
"""Hook for unit tests to assert things about package internals.

View File

@@ -100,7 +100,7 @@
"allow_sgid": {"type": "boolean"},
"install_status": {"type": "boolean"},
"binary_index_root": {"type": "string"},
"url_fetch_method": {"type": "string", "pattern": r"^urllib$|^curl( .*)*"},
"url_fetch_method": {"type": "string", "enum": ["urllib", "curl"]},
"additional_external_search_paths": {"type": "array", "items": {"type": "string"}},
"binary_index_ttl": {"type": "integer", "minimum": 0},
"aliases": {"type": "object", "patternProperties": {r"\w[\w-]*": {"type": "string"}}},

View File

@@ -287,33 +287,9 @@ def specify(spec):
return spack.spec.Spec(spec)
def remove_facts(
*to_be_removed: str,
) -> Callable[[spack.spec.Spec, List[AspFunction]], List[AspFunction]]:
"""Returns a transformation function that removes facts from the input list of facts."""
def _remove(spec: spack.spec.Spec, facts: List[AspFunction]) -> List[AspFunction]:
return list(filter(lambda x: x.args[0] not in to_be_removed, facts))
return _remove
def remove_build_deps(spec: spack.spec.Spec, facts: List[AspFunction]) -> List[AspFunction]:
build_deps = {x.args[2]: x.args[1] for x in facts if x.args[0] == "depends_on"}
result = []
for x in facts:
current_name = x.args[1]
if current_name in build_deps:
x.name = "build_requirement"
result.append(fn.attr("build_requirement", build_deps[current_name], x))
continue
if x.args[0] == "depends_on":
continue
result.append(x)
return result
def remove_node(spec: spack.spec.Spec, facts: List[AspFunction]) -> List[AspFunction]:
"""Transformation that removes all "node" and "virtual_node" from the input list of facts."""
return list(filter(lambda x: x.args[0] not in ("node", "virtual_node"), facts))
def all_libcs() -> Set[spack.spec.Spec]:
@@ -1214,7 +1190,7 @@ def solve(self, setup, specs, reuse=None, output=None, control=None, allow_depre
problem_repr += "\n" + f.read()
result = None
conc_cache_enabled = spack.config.get("config:concretization_cache:enable", False)
conc_cache_enabled = spack.config.get("config:concretization_cache:enable", True)
if conc_cache_enabled:
result, concretization_stats = CONC_CACHE.fetch(problem_repr)
@@ -1311,8 +1287,12 @@ def on_model(model):
result.raise_if_unsat()
if result.satisfiable and result.unsolved_specs and setup.concretize_everything:
raise OutputDoesNotSatisfyInputError(result.unsolved_specs)
unsolved_str = Result.format_unsolved(result.unsolved_specs)
raise InternalConcretizerError(
"Internal Spack error: the solver completed but produced specs"
" that do not satisfy the request. Please report a bug at "
f"https://github.com/spack/spack/issues\n\t{unsolved_str}"
)
if conc_cache_enabled:
CONC_CACHE.store(problem_repr, result, self.control.statistics, test=setup.tests)
concretization_stats = self.control.statistics
@@ -1755,17 +1735,15 @@ def define_variant(
pkg_fact(fn.variant_condition(name, vid, cond_id))
# record type so we can construct the variant when we read it back in
self.gen.fact(fn.variant_type(vid, variant_def.variant_type.string))
self.gen.fact(fn.variant_type(vid, variant_def.variant_type.value))
if variant_def.sticky:
pkg_fact(fn.variant_sticky(vid))
# define defaults for this variant definition
if variant_def.multi:
for val in sorted(variant_def.make_default().values):
pkg_fact(fn.variant_default_value_from_package_py(vid, val))
else:
pkg_fact(fn.variant_default_value_from_package_py(vid, variant_def.default))
defaults = variant_def.make_default().value if variant_def.multi else [variant_def.default]
for val in sorted(defaults):
pkg_fact(fn.variant_default_value_from_package_py(vid, val))
# define possible values for this variant definition
values = variant_def.values
@@ -1793,9 +1771,7 @@ def define_variant(
# make a spec indicating whether the variant has this conditional value
variant_has_value = spack.spec.Spec()
variant_has_value.variants[name] = vt.VariantValue(
vt.VariantType.MULTI, name, (value.value,)
)
variant_has_value.variants[name] = spack.variant.AbstractVariant(name, value.value)
if value.when:
# the conditional value is always "possible", but it imposes its when condition as
@@ -1908,7 +1884,7 @@ def condition(
if not context:
context = ConditionContext()
context.transform_imposed = remove_facts("node", "virtual_node")
context.transform_imposed = remove_node
if imposed_spec:
imposed_name = imposed_spec.name or imposed_name
@@ -2008,7 +1984,7 @@ def track_dependencies(input_spec, requirements):
return requirements + [fn.attr("track_dependencies", input_spec.name)]
def dependency_holds(input_spec, requirements):
result = remove_facts("node", "virtual_node")(input_spec, requirements) + [
result = remove_node(input_spec, requirements) + [
fn.attr(
"dependency_holds", pkg.name, input_spec.name, dt.flag_to_string(t)
)
@@ -2198,10 +2174,7 @@ def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
pkg_name, ConstraintOrigin.REQUIRE
)
if not virtual:
context.transform_required = remove_build_deps
context.transform_imposed = remove_facts(
"node", "virtual_node", "depends_on"
)
context.transform_imposed = remove_node
# else: for virtuals we want to emit "node" and
# "virtual_node" in imposed specs
@@ -2263,18 +2236,16 @@ def external_packages(self):
if pkg_name not in self.pkgs:
continue
self.gen.h2(f"External package: {pkg_name}")
# Check if the external package is buildable. If it is
# not then "external(<pkg>)" is a fact, unless we can
# reuse an already installed spec.
external_buildable = data.get("buildable", True)
externals = data.get("externals", [])
if not external_buildable or externals:
self.gen.h2(f"External package: {pkg_name}")
if not external_buildable:
self.gen.fact(fn.buildable_false(pkg_name))
# Read a list of all the specs for this package
externals = data.get("externals", [])
candidate_specs = [
spack.spec.parse_with_version_concrete(x["spec"]) for x in externals
]
@@ -2363,8 +2334,6 @@ def preferred_variants(self, pkg_name):
if not preferred_variants:
return
self.gen.h2(f"Package preferences: {pkg_name}")
for variant_name in sorted(preferred_variants):
variant = preferred_variants[variant_name]
@@ -2377,7 +2346,7 @@ def preferred_variants(self, pkg_name):
)
continue
for value in variant.values:
for value in variant.value_as_tuple:
for variant_def in variant_defs:
self.variant_values_from_specs.add((pkg_name, id(variant_def), value))
self.gen.fact(
@@ -2495,7 +2464,7 @@ def _spec_clauses(
if variant.value == ("*",):
continue
for value in variant.values:
for value in variant.value_as_tuple:
# ensure that the value *can* be valid for the spec
if spec.name and not spec.concrete and not spack.repo.PATH.is_virtual(spec.name):
variant_defs = vt.prevalidate_variant_value(
@@ -2605,16 +2574,6 @@ def _spec_clauses(
# already-installed concrete specs.
if concrete_build_deps or dspec.depflag != dt.BUILD:
clauses.append(fn.attr("hash", dep.name, dep.dag_hash()))
elif not concrete_build_deps and dspec.depflag:
clauses.append(
fn.attr(
"concrete_build_dependency", spec.name, dep.name, dep.dag_hash()
)
)
for virtual_name in dspec.virtuals:
clauses.append(
fn.attr("virtual_on_build_edge", spec.name, dep.name, virtual_name)
)
# if the spec is abstract, descend into dependencies.
# if it's concrete, then the hashes above take care of dependency
@@ -3038,36 +2997,8 @@ def setup(
"""
reuse = reuse or []
check_packages_exist(specs)
self.gen = ProblemInstanceBuilder()
# Compute possible compilers first, so we can record which dependencies they might inject
_ = spack.compilers.config.all_compilers(init_config=True)
# Get compilers from buildcache only if injected through "reuse" specs
supported_compilers = spack.compilers.config.supported_compilers()
compilers_from_reuse = {
x for x in reuse if x.name in supported_compilers and not x.external
}
candidate_compilers, self.rejected_compilers = possible_compilers(
configuration=spack.config.CONFIG
)
for x in candidate_compilers:
if x.external or x in reuse:
continue
reuse.append(x)
for dep in x.traverse(root=False, deptype="run"):
reuse.extend(dep.traverse(deptype=("link", "run")))
candidate_compilers.update(compilers_from_reuse)
self.possible_compilers = list(candidate_compilers)
self.possible_compilers.sort() # type: ignore[call-overload]
self.gen.h1("Runtimes")
injected_dependencies = self.define_runtime_constraints()
node_counter = create_counter(
specs + injected_dependencies, tests=self.tests, possible_graph=self.possible_graph
)
node_counter = create_counter(specs, tests=self.tests, possible_graph=self.possible_graph)
self.possible_virtuals = node_counter.possible_virtuals()
self.pkgs = node_counter.possible_dependencies()
self.libcs = sorted(all_libcs()) # type: ignore[type-var]
@@ -3090,6 +3021,7 @@ def setup(
if node.namespace is not None:
self.explicitly_required_namespaces[node.name] = node.namespace
self.gen = ProblemInstanceBuilder()
self.gen.h1("Generic information")
if using_libc_compatibility():
for libc in self.libcs:
@@ -3118,6 +3050,27 @@ def setup(
specs = tuple(specs) # ensure compatible types to add
_ = spack.compilers.config.all_compilers(init_config=True)
# Get compilers from buildcache only if injected through "reuse" specs
supported_compilers = spack.compilers.config.supported_compilers()
compilers_from_reuse = {
x for x in reuse if x.name in supported_compilers and not x.external
}
candidate_compilers, self.rejected_compilers = possible_compilers(
configuration=spack.config.CONFIG
)
for x in candidate_compilers:
if x.external or x in reuse:
continue
reuse.append(x)
for dep in x.traverse(root=False, deptype="run"):
reuse.extend(dep.traverse(deptype=("link", "run")))
candidate_compilers.update(compilers_from_reuse)
self.possible_compilers = list(candidate_compilers)
self.possible_compilers.sort() # type: ignore[call-overload]
self.gen.h1("Reusable concrete specs")
self.define_concrete_input_specs(specs, self.pkgs)
if reuse:
@@ -3169,6 +3122,7 @@ def setup(
for pkg in sorted(self.pkgs):
self.gen.h2("Package rules: %s" % pkg)
self.pkg_rules(pkg, tests=self.tests)
self.gen.h2("Package preferences: %s" % pkg)
self.preferred_variants(pkg)
self.gen.h1("Special variants")
@@ -3188,6 +3142,9 @@ def setup(
self.gen.h1("Variant Values defined in specs")
self.define_variant_values()
self.gen.h1("Runtimes")
self.define_runtime_constraints()
self.gen.h1("Version Constraints")
self.collect_virtual_constraints()
self.define_version_constraints()
@@ -3221,10 +3178,8 @@ def visit(node):
path = os.path.join(parent_dir, "concretize.lp")
parse_files([path], visit)
def define_runtime_constraints(self) -> List[spack.spec.Spec]:
"""Define the constraints to be imposed on the runtimes, and returns a list of
injected packages.
"""
def define_runtime_constraints(self):
"""Define the constraints to be imposed on the runtimes"""
recorder = RuntimePropertyRecorder(self)
for compiler in self.possible_compilers:
@@ -3240,13 +3195,12 @@ def define_runtime_constraints(self) -> List[spack.spec.Spec]:
# FIXME (compiler as nodes): think of using isinstance(compiler_cls, WrappedCompiler)
# Add a dependency on the compiler wrapper
for language in ("c", "cxx", "fortran"):
recorder("*").depends_on(
"compiler-wrapper",
when=f"%[virtuals={language}] {compiler.name}@{compiler.versions}",
type="build",
description=f"Add the compiler wrapper when using {compiler} for {language}",
)
recorder("*").depends_on(
"compiler-wrapper",
when=f"%{compiler.name}@{compiler.versions}",
type="build",
description=f"Add the compiler wrapper when using {compiler}",
)
if not using_libc_compatibility():
continue
@@ -3275,7 +3229,6 @@ def define_runtime_constraints(self) -> List[spack.spec.Spec]:
)
recorder.consume_facts()
return sorted(recorder.injected_dependencies)
def literal_specs(self, specs):
for spec in sorted(specs):
@@ -3308,13 +3261,15 @@ def literal_specs(self, specs):
# These facts are needed to compute the "condition_set" of the root
pkg_name = clause.args[1]
self.gen.fact(fn.mentioned_in_literal(trigger_id, root_name, pkg_name))
elif clause_name == "depends_on":
pkg_name = clause.args[2]
self.gen.fact(fn.mentioned_in_literal(trigger_id, root_name, pkg_name))
requirements.append(
fn.attr(
"virtual_root" if spack.repo.PATH.is_virtual(spec.name) else "root", spec.name
)
)
requirements = [x for x in requirements if x.args[0] != "depends_on"]
cache[imposed_spec_key] = (effect_id, requirements)
self.gen.fact(fn.pkg_fact(spec.name, fn.condition_effect(condition_id, effect_id)))
@@ -3538,7 +3493,6 @@ def __init__(self, setup):
self._setup = setup
self.rules = []
self.runtime_conditions = set()
self.injected_dependencies = set()
# State of this object set in the __call__ method, and reset after
# each directive-like method
self.current_package = None
@@ -3577,7 +3531,6 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
if dependency_spec.versions != vn.any_version:
self._setup.version_constraints.add((dependency_spec.name, dependency_spec.versions))
self.injected_dependencies.add(dependency_spec)
body_str, node_variable = self.rule_body_from(when_spec)
head_clauses = self._setup.spec_clauses(dependency_spec, body=False)
@@ -3639,9 +3592,11 @@ def rule_body_from(self, when_spec: "spack.spec.Spec") -> Tuple[str, str]:
# (avoid adding virtuals everywhere, if a single edge needs it)
_, provider, virtual = clause.args
clause.args = "virtual_on_edge", node_placeholder, provider, virtual
body_str = ",\n".join(f" {x}" for x in body_clauses)
body_str += f",\n not external({node_variable})"
body_str = body_str.replace(f'"{node_placeholder}"', f"{node_variable}")
body_str = (
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
f" not external({node_variable}),\n"
f" not runtime(Package)"
).replace(f'"{node_placeholder}"', f"{node_variable}")
for old, replacement in when_substitutions.items():
body_str = body_str.replace(old, replacement)
return body_str, node_variable
@@ -3731,21 +3686,20 @@ def consume_facts(self):
"""Consume the facts collected by this object, and emits rules and
facts for the runtimes.
"""
self._setup.gen.h2("Runtimes: declarations")
runtime_pkgs = sorted(
{x.name for x in self.injected_dependencies if not spack.repo.PATH.is_virtual(x.name)}
)
for runtime_pkg in runtime_pkgs:
self._setup.gen.fact(fn.runtime(runtime_pkg))
self._setup.gen.newline()
self._setup.gen.h2("Runtimes: rules")
self._setup.gen.newline()
for rule in self.rules:
self._setup.gen.append(rule)
self._setup.gen.newline()
self._setup.gen.h2("Runtimes: requirements")
self._setup.gen.h2("Runtimes: conditions")
for runtime_pkg in spack.repo.PATH.packages_with_tags("runtime"):
self._setup.gen.fact(fn.runtime(runtime_pkg))
self._setup.gen.fact(fn.possible_in_link_run(runtime_pkg))
self._setup.gen.newline()
# Inject version rules for runtimes (versions are declared based
# on the available compilers)
self._setup.pkg_version_rules(runtime_pkg)
for imposed_spec, when_spec in sorted(self.runtime_conditions):
msg = f"{when_spec} requires {imposed_spec} at runtime"
_ = self._setup.condition(when_spec, imposed_spec=imposed_spec, msg=msg)
@@ -3832,13 +3786,13 @@ def node_os(self, node, os):
def node_target(self, node, target):
self._arch(node).target = target
def variant_selected(self, node, name: str, value: str, variant_type: str, variant_id):
def variant_selected(self, node, name, value, variant_type, variant_id):
spec = self._specs[node]
variant = spec.variants.get(name)
if not variant:
spec.variants[name] = vt.VariantValue.from_concretizer(name, value, variant_type)
spec.variants[name] = vt.VariantType(variant_type).variant_class(name, value)
else:
assert variant_type == "multi", (
assert variant_type == vt.VariantType.MULTI.value, (
f"Can't have multiple values for single-valued variant: "
f"{node}, {name}, {value}, {variant_type}, {variant_id}"
)
@@ -3862,17 +3816,6 @@ def external_spec_selected(self, node, idx):
)
self._specs[node].extra_attributes = spec_info.get("extra_attributes", {})
# Annotate compiler specs from externals
external_spec = spack.spec.Spec(spec_info["spec"])
external_spec_deps = external_spec.dependencies()
if len(external_spec_deps) > 1:
raise InvalidExternalError(
f"external spec {spec_info['spec']} cannot have more than one dependency"
)
elif len(external_spec_deps) == 1:
compiler_str = external_spec_deps[0]
self._specs[node].annotations.with_compiler(spack.spec.Spec(compiler_str))
# If this is an extension, update the dependencies to include the extendee
package = spack.repo.PATH.get_pkg_class(self._specs[node].fullname)(self._specs[node])
extendee_spec = package.extendee_spec
@@ -3891,7 +3834,7 @@ def virtual_on_edge(self, parent_node, provider_node, virtual):
provider_spec = self._specs[provider_node]
dependencies = [x for x in dependencies if id(x.spec) == id(provider_spec)]
assert len(dependencies) == 1, f"{virtual}: {provider_node.pkg}"
dependencies[0].update_virtuals(virtual)
dependencies[0].update_virtuals((virtual,))
def reorder_flags(self):
"""For each spec, determine the order of compiler flags applied to it.
@@ -4228,10 +4171,10 @@ def _inject_patches_variant(root: spack.spec.Spec) -> None:
continue
patches = list(spec_to_patches[id(spec)])
variant: vt.VariantValue = spec.variants.setdefault(
variant: vt.MultiValuedVariant = spec.variants.setdefault(
"patches", vt.MultiValuedVariant("patches", ())
)
variant.set(*(p.sha256 for p in patches))
variant.value = tuple(p.sha256 for p in patches)
# FIXME: Monkey patches variant to store patches order
ordered_hashes = [(*p.ordering_key, p.sha256) for p in patches if p.ordering_key]
ordered_hashes.sort()
@@ -4699,9 +4642,13 @@ def solve_in_rounds(
break
if not result.specs:
# This is also a problem: no specs were solved for, which means we would be in a
# loop if we tried again
raise OutputDoesNotSatisfyInputError(result.unsolved_specs)
# This is also a problem: no specs were solved for, which
# means we would be in a loop if we tried again
unsolved_str = Result.format_unsolved(result.unsolved_specs)
raise InternalConcretizerError(
"Internal Spack error: a subset of input specs could not"
f" be solved for.\n\t{unsolved_str}"
)
input_specs = list(x for (x, y) in result.unsolved_specs)
for spec in result.specs:
@@ -4731,19 +4678,6 @@ def __init__(self, msg):
self.constraint_type = None
class OutputDoesNotSatisfyInputError(InternalConcretizerError):
def __init__(
self, input_to_output: List[Tuple[spack.spec.Spec, Optional[spack.spec.Spec]]]
) -> None:
self.input_to_output = input_to_output
super().__init__(
"internal solver error: the solver completed but produced specs"
" that do not satisfy the request. Please report a bug at "
f"https://github.com/spack/spack/issues\n\t{Result.format_unsolved(input_to_output)}"
)
class SolverError(InternalConcretizerError):
"""For cases where the solver is unable to produce a solution.
@@ -4776,7 +4710,3 @@ class InvalidSpliceError(spack.error.SpackError):
class NoCompilerFoundError(spack.error.SpackError):
"""Raised when there is no possible compiler"""
class InvalidExternalError(spack.error.SpackError):
"""Raised when there is no possible compiler"""

View File

@@ -175,24 +175,12 @@ trigger_node(TriggerID, Node, Node) :-
% Since we trigger the existence of literal nodes from a condition, we need to construct the condition_set/2
mentioned_in_literal(Root, Mentioned) :- mentioned_in_literal(TriggerID, Root, Mentioned), solve_literal(TriggerID).
literal_node(Root, node(min_dupe_id, Root)) :- mentioned_in_literal(Root, Root).
condition_set(node(min_dupe_id, Root), node(min_dupe_id, Root)) :- mentioned_in_literal(Root, Root).
1 { literal_node(Root, node(0..Y-1, Mentioned)) : max_dupes(Mentioned, Y) } 1 :-
1 { condition_set(node(min_dupe_id, Root), node(0..Y-1, Mentioned)) : max_dupes(Mentioned, Y) } 1 :-
mentioned_in_literal(Root, Mentioned), Mentioned != Root,
internal_error("must have exactly one condition_set for literals").
1 { build_dependency_of_literal_node(LiteralNode, node(0..Y-1, BuildDependency)) : max_dupes(BuildDependency, Y) } 1 :-
literal_node(Root, LiteralNode),
build(LiteralNode),
not external(LiteralNode),
attr("build_requirement", LiteralNode, build_requirement("node", BuildDependency)).
condition_set(node(min_dupe_id, Root), LiteralNode) :- literal_node(Root, LiteralNode).
condition_set(LiteralNode, BuildNode) :- build_dependency_of_literal_node(LiteralNode, BuildNode).
:- build_dependency_of_literal_node(LiteralNode, BuildNode),
not attr("depends_on", LiteralNode, BuildNode, "build").
% Discriminate between "roots" that have been explicitly requested, and roots that are deduced from "virtual roots"
explicitly_requested_root(node(min_dupe_id, Package)) :-
solve_literal(TriggerID),
@@ -484,53 +472,10 @@ provider(ProviderNode, VirtualNode) :- attr("provider_set", ProviderNode, Virtua
imposed_constraint(ID, "depends_on", A1, A2, A3),
internal_error("Build deps must land in exactly one duplicate").
% If the parent is built, then we have a build_requirement on another node. For concrete nodes,
% or external nodes, we don't since we are trimming their build dependencies.
1 { attr("depends_on", node(X, Parent), node(0..Y-1, BuildDependency), "build") : max_dupes(BuildDependency, Y) } 1
1 { build_requirement(node(X, Parent), node(0..Y-1, BuildDependency)) : max_dupes(BuildDependency, Y) } 1
:- attr("build_requirement", node(X, Parent), build_requirement("node", BuildDependency)),
build(node(X, Parent)),
not external(node(X, Parent)).
% Concrete nodes
:- attr("build_requirement", ParentNode, build_requirement("node", BuildDependency)),
concrete(ParentNode),
not attr("concrete_build_dependency", ParentNode, BuildDependency, _).
:- attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, Constraint)),
attr("concrete_build_dependency", ParentNode, BuildDependency, BuildDependencyHash),
not 1 { pkg_fact(BuildDependency, version_satisfies(Constraint, Version)) : hash_attr(BuildDependencyHash, "version", BuildDependency, Version) } 1.
:- attr("build_requirement", ParentNode, build_requirement("provider_set", BuildDependency, Virtual)),
attr("concrete_build_dependency", ParentNode, BuildDependency, BuildDependencyHash),
attr("virtual_on_build_edge", ParentNode, BuildDependency, Virtual),
not 1 { pkg_fact(BuildDependency, version_satisfies(Constraint, Version)) : hash_attr(BuildDependencyHash, "version", BuildDependency, Version) } 1.
% External nodes
:- attr("build_requirement", ParentNode, build_requirement("node", BuildDependency)),
external(ParentNode),
not attr("external_build_requirement", ParentNode, build_requirement("node", BuildDependency)).
candidate_external_version(Constraint, BuildDependency, Version)
:- attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, Constraint)),
external(ParentNode),
pkg_fact(BuildDependency, version_satisfies(Constraint, Version)).
error(100, "External {0} cannot satisfy both {1} and {2}", BuildDependency, LiteralConstraint, ExternalConstraint)
:- attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, LiteralConstraint)),
external(ParentNode),
attr("external_build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, ExternalConstraint)),
not 1 { pkg_fact(BuildDependency, version_satisfies(ExternalConstraint, Version)) : candidate_external_version(LiteralConstraint, BuildDependency, Version) }.
% Asking for gcc@10 %gcc@9 shouldn't give us back an external gcc@10, just because of the hack
% we have on externals
:- attr("build_requirement", node(X, Parent), build_requirement("node", BuildDependency)),
Parent == BuildDependency,
external(node(X, Parent)).
build_requirement(node(X, Parent), node(Y, BuildDependency)) :-
attr("depends_on", node(X, Parent), node(Y, BuildDependency), "build"),
attr("build_requirement", node(X, Parent), build_requirement("node", BuildDependency)).
impose(ID, node(X, Parent)),
imposed_constraint(ID,"build_requirement",Parent,_).
1 { virtual_build_requirement(ParentNode, node(0..Y-1, Virtual)) : max_dupes(Virtual, Y) } 1
:- attr("dependency_holds", ParentNode, Virtual, "build"),
@@ -551,6 +496,7 @@ attr("node_version_satisfies", node(X, BuildDependency), Constraint) :-
attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, Constraint)),
build_requirement(ParentNode, node(X, BuildDependency)).
attr("depends_on", node(X, Parent), node(Y, BuildDependency), "build") :- build_requirement(node(X, Parent), node(Y, BuildDependency)).
1 { attr("provider_set", node(X, BuildDependency), node(0..Y-1, Virtual)) : max_dupes(Virtual, Y) } 1 :-
attr("build_requirement", ParentNode, build_requirement("provider_set", BuildDependency, Virtual)),
@@ -936,12 +882,6 @@ requirement_weight(node(ID, Package), Group, W) :-
requirement_policy(Package, Group, "one_of"),
requirement_group_satisfied(node(ID, Package), Group).
{ attr("build_requirement", node(ID, Package), BuildRequirement) : condition_requirement(TriggerID, "build_requirement", Package, BuildRequirement) } :-
pkg_fact(Package, condition_trigger(ConditionID, TriggerID)),
requirement_group_member(ConditionID, Package, Group),
activate_requirement(node(ID, Package), Group),
requirement_group(Package, Group).
requirement_group_satisfied(node(ID, Package), X) :-
1 { condition_holds(Y, node(ID, Package)) : requirement_group_member(Y, Package, X) } ,
requirement_policy(Package, X, "any_of"),

View File

@@ -18,6 +18,8 @@
import spack.store
from spack.error import SpackError
RUNTIME_TAG = "runtime"
class PossibleGraph(NamedTuple):
real_pkgs: Set[str]
@@ -48,8 +50,7 @@ def possible_dependencies(
) -> PossibleGraph:
"""Returns the set of possible dependencies, and the set of possible virtuals.
Runtime packages, which may be injected by compilers, needs to be added to specs if
the dependency is not explicit in the package.py recipe.
Both sets always include runtime packages, which may be injected by compilers.
Args:
transitive: return transitive dependencies if True, only direct dependencies if False
@@ -69,9 +70,14 @@ class NoStaticAnalysis(PossibleDependencyGraph):
def __init__(self, *, configuration: spack.config.Configuration, repo: spack.repo.RepoPath):
self.configuration = configuration
self.repo = repo
self.runtime_pkgs = set(self.repo.packages_with_tags(RUNTIME_TAG))
self.runtime_virtuals = set()
self._platform_condition = spack.spec.Spec(
f"platform={spack.platforms.host()} target={archspec.cpu.host().family}:"
)
for x in self.runtime_pkgs:
pkg_class = self.repo.get_pkg_class(x)
self.runtime_virtuals.update(pkg_class.provided_virtual_names())
try:
self.libc_pkgs = [x.name for x in self.providers_for("libc")]
@@ -85,10 +91,8 @@ def is_virtual(self, name: str) -> bool:
def is_allowed_on_this_platform(self, *, pkg_name: str) -> bool:
"""Returns true if a package is allowed on the current host"""
pkg_cls = self.repo.get_pkg_class(pkg_name)
no_condition = spack.spec.Spec()
for when_spec, conditions in pkg_cls.requirements.items():
# Restrict analysis to unconditional requirements
if when_spec != no_condition:
if not when_spec.intersects(self._platform_condition):
continue
for requirements, _, _ in conditions:
if not any(x.intersects(self._platform_condition) for x in requirements):
@@ -210,6 +214,8 @@ def possible_dependencies(
for root, children in edges.items():
real_packages.update(x for x in children if self._is_possible(pkg_name=x))
virtuals.update(self.runtime_virtuals)
real_packages = real_packages | self.runtime_pkgs
return PossibleGraph(real_pkgs=real_packages, virtuals=virtuals, edges=edges)
def _package_list(self, specs: Tuple[Union[spack.spec.Spec, str], ...]) -> List[str]:
@@ -464,7 +470,7 @@ def possible_packages_facts(self, gen, fn):
gen.fact(fn.max_dupes(package_name, 1))
gen.newline()
gen.h2("Packages with multiple possible nodes (build-tools)")
gen.h2("Packages with at multiple possible nodes (build-tools)")
default = spack.config.CONFIG.get("concretizer:duplicates:max_dupes:default", 2)
for package_name in sorted(self.possible_dependencies() & build_tools):
max_dupes = spack.config.CONFIG.get(

View File

@@ -111,14 +111,22 @@
__all__ = [
"CompilerSpec",
"Spec",
"SpecParseError",
"UnsupportedPropagationError",
"DuplicateDependencyError",
"DuplicateCompilerSpecError",
"UnsupportedCompilerError",
"DuplicateArchitectureError",
"InconsistentSpecError",
"InvalidDependencyError",
"NoProviderError",
"MultipleProviderError",
"UnsatisfiableSpecNameError",
"UnsatisfiableVersionSpecError",
"UnsatisfiableCompilerSpecError",
"UnsatisfiableCompilerFlagSpecError",
"UnsatisfiableArchitectureSpecError",
"UnsatisfiableProviderSpecError",
"UnsatisfiableDependencySpecError",
"AmbiguousHashError",
"InvalidHashError",
@@ -198,7 +206,7 @@ class InstallStatus(enum.Enum):
installed = "@g{[+]} "
upstream = "@g{[^]} "
external = "@M{[e]} "
external = "@g{[e]} "
absent = "@K{ - } "
missing = "@r{[-]} "
@@ -746,17 +754,11 @@ def update_deptypes(self, depflag: dt.DepFlag) -> bool:
self.depflag = new
return True
def update_virtuals(self, virtuals: Union[str, Iterable[str]]) -> bool:
def update_virtuals(self, virtuals: Iterable[str]) -> bool:
"""Update the list of provided virtuals"""
old = self.virtuals
if isinstance(virtuals, str):
union = {virtuals, *self.virtuals}
else:
union = {*virtuals, *self.virtuals}
if len(union) == len(old):
return False
self.virtuals = tuple(sorted(union))
return True
self.virtuals = tuple(sorted(set(virtuals).union(self.virtuals)))
return old != self.virtuals
def copy(self) -> "DependencySpec":
"""Return a copy of this edge"""
@@ -1020,7 +1022,7 @@ def select(
parent: Optional[str] = None,
child: Optional[str] = None,
depflag: dt.DepFlag = dt.ALL,
virtuals: Optional[Union[str, Sequence[str]]] = None,
virtuals: Optional[Sequence[str]] = None,
) -> List[DependencySpec]:
"""Selects a list of edges and returns them.
@@ -1039,7 +1041,7 @@ def select(
parent: name of the parent package
child: name of the child package
depflag: allowed dependency types in flag form
virtuals: list of virtuals or specific virtual on the edge
virtuals: list of virtuals on the edge
"""
if not depflag:
return []
@@ -1060,10 +1062,7 @@ def select(
# Filter by virtuals
if virtuals is not None:
if isinstance(virtuals, str):
selected = (dep for dep in selected if virtuals in dep.virtuals)
else:
selected = (dep for dep in selected if any(v in dep.virtuals for v in virtuals))
selected = (dep for dep in selected if any(v in dep.virtuals for v in virtuals))
return list(selected)
@@ -1071,123 +1070,26 @@ def clear(self):
self.edges.clear()
def _headers_default_handler(spec: "Spec"):
"""Default handler when looking for the 'headers' attribute.
Tries to search for ``*.h`` files recursively starting from
``spec.package.home.include``.
Parameters:
spec: spec that is being queried
Returns:
HeaderList: The headers in ``prefix.include``
Raises:
NoHeadersError: If no headers are found
"""
home = getattr(spec.package, "home")
headers = fs.find_headers("*", root=home.include, recursive=True)
if headers:
return headers
raise spack.error.NoHeadersError(f"Unable to locate {spec.name} headers in {home}")
def _libs_default_handler(spec: "Spec"):
"""Default handler when looking for the 'libs' attribute.
Tries to search for ``lib{spec.name}`` recursively starting from
``spec.package.home``. If ``spec.name`` starts with ``lib``, searches for
``{spec.name}`` instead.
Parameters:
spec: spec that is being queried
Returns:
LibraryList: The libraries found
Raises:
NoLibrariesError: If no libraries are found
"""
# Variable 'name' is passed to function 'find_libraries', which supports
# glob characters. For example, we have a package with a name 'abc-abc'.
# Now, we don't know if the original name of the package is 'abc_abc'
# (and it generates a library 'libabc_abc.so') or 'abc-abc' (and it
# generates a library 'libabc-abc.so'). So, we tell the function
# 'find_libraries' to give us anything that matches 'libabc?abc' and it
# gives us either 'libabc-abc.so' or 'libabc_abc.so' (or an error)
# depending on which one exists (there is a possibility, of course, to
# get something like 'libabcXabc.so, but for now we consider this
# unlikely).
name = spec.name.replace("-", "?")
home = getattr(spec.package, "home")
# Avoid double 'lib' for packages whose names already start with lib
if not name.startswith("lib") and not spec.satisfies("platform=windows"):
name = "lib" + name
# If '+shared' search only for shared library; if '~shared' search only for
# static library; otherwise, first search for shared and then for static.
search_shared = (
[True] if ("+shared" in spec) else ([False] if ("~shared" in spec) else [True, False])
)
for shared in search_shared:
# Since we are searching for link libraries, on Windows search only for
# ".Lib" extensions by default as those represent import libraries for implicit links.
libs = fs.find_libraries(name, home, shared=shared, recursive=True, runtime=False)
if libs:
return libs
raise spack.error.NoLibrariesError(
f"Unable to recursively locate {spec.name} libraries in {home}"
)
class ForwardQueryToPackage:
"""Descriptor used to forward queries from Spec to Package"""
def __init__(
self,
attribute_name: str,
default_handler: Optional[Callable[["Spec"], Any]] = None,
_indirect: bool = False,
) -> None:
def __init__(self, attribute_name: str, _indirect: bool = False) -> None:
"""Create a new descriptor.
Parameters:
attribute_name: name of the attribute to be searched for in the Package instance
default_handler: default function to be called if the attribute was not found in the
Package instance
_indirect: temporarily added to redirect a query to another package.
"""
self.attribute_name = attribute_name
self.default = default_handler
self.indirect = _indirect
def __get__(self, instance: "SpecBuildInterface", cls):
"""Retrieves the property from Package using a well defined chain
of responsibility.
"""Retrieves the property from Package using a well defined chain of responsibility.
The order of call is:
The call order is:
1. if the query was through the name of a virtual package try to
search for the attribute `{virtual_name}_{attribute_name}`
in Package
2. try to search for attribute `{attribute_name}` in Package
3. try to call the default handler
The first call that produces a value will stop the chain.
If no call can handle the request then AttributeError is raised with a
message indicating that no relevant attribute exists.
If a call returns None, an AttributeError is raised with a message
indicating a query failure, e.g. that library files were not found in a
'libs' query.
1. `pkg.{virtual_name}_{attribute_name}` if the query is for a virtual package
2. `pkg.{attribute_name}` otherwise
"""
# TODO: this indirection exist solely for `spec["python"].command` to actually return
# spec["python-venv"].command. It should be removed when `python` is a virtual.
@@ -1203,61 +1105,36 @@ def __get__(self, instance: "SpecBuildInterface", cls):
_ = instance.wrapped_obj[instance.wrapped_obj.name] # NOQA: ignore=F841
query = instance.last_query
callbacks_chain = []
# First in the chain : specialized attribute for virtual packages
# First try the deprecated attributes (e.g. `<virtual>_libs` and `libs`)
if query.isvirtual:
specialized_name = "{0}_{1}".format(query.name, self.attribute_name)
callbacks_chain.append(lambda: getattr(pkg, specialized_name))
# Try to get the generic method from Package
callbacks_chain.append(lambda: getattr(pkg, self.attribute_name))
# Final resort : default callback
if self.default is not None:
_default = self.default # make mypy happy
callbacks_chain.append(lambda: _default(instance.wrapped_obj))
deprecated_attrs = [f"{query.name}_{self.attribute_name}", self.attribute_name]
else:
deprecated_attrs = [self.attribute_name]
# Trigger the callbacks in order, the first one producing a
# value wins
value = None
message = None
for f in callbacks_chain:
try:
value = f()
# A callback can return None to trigger an error indicating
# that the query failed.
if value is None:
msg = "Query of package '{name}' for '{attrib}' failed\n"
msg += "\tprefix : {spec.prefix}\n"
msg += "\tspec : {spec}\n"
msg += "\tqueried as : {query.name}\n"
msg += "\textra parameters : {query.extra_parameters}"
message = msg.format(
name=pkg.name,
attrib=self.attribute_name,
spec=instance,
query=instance.last_query,
)
else:
return value
break
except AttributeError:
pass
# value is 'None'
if message is not None:
# Here we can use another type of exception. If we do that, the
# unit test 'test_getitem_exceptional_paths' in the file
# lib/spack/spack/test/spec_dag.py will need to be updated to match
# the type.
raise AttributeError(message)
# 'None' value at this point means that there are no appropriate
# properties defined and no default handler, or that all callbacks
# raised AttributeError. In this case, we raise AttributeError with an
# appropriate message.
fmt = "'{name}' package has no relevant attribute '{query}'\n"
fmt += "\tspec : '{spec}'\n"
fmt += "\tqueried as : '{spec.last_query.name}'\n"
fmt += "\textra parameters : '{spec.last_query.extra_parameters}'\n"
message = fmt.format(name=pkg.name, query=self.attribute_name, spec=instance)
raise AttributeError(message)
for attr in deprecated_attrs:
if not hasattr(pkg, attr):
continue
value = getattr(pkg, attr)
# Deprecated properties can return None to indicate the query failed.
if value is None:
raise AttributeError(
f"Query of package '{pkg.name}' for '{self.attribute_name}' failed\n"
f"\tprefix : {instance.prefix}\n" # type: ignore[attr-defined]
f"\tspec : {instance}\n"
f"\tqueried as : {query.name}\n"
f"\textra parameters : {query.extra_parameters}"
)
return value
# Then try the new functions (e.g. `find_libs`).
features = query.extra_parameters
virtual = query.name if query.isvirtual else None
if self.attribute_name == "libs":
return pkg.find_libs(features=features, virtual=virtual)
elif self.attribute_name == "headers":
return pkg.find_headers(features=features, virtual=virtual)
raise AttributeError(f"Package {pkg.name} has no attribute {self.attribute_name}")
def __set__(self, instance, value):
cls_name = type(instance).__name__
@@ -1271,10 +1148,10 @@ def __set__(self, instance, value):
class SpecBuildInterface(lang.ObjectWrapper):
# home is available in the base Package so no default is needed
home = ForwardQueryToPackage("home", default_handler=None)
headers = ForwardQueryToPackage("headers", default_handler=_headers_default_handler)
libs = ForwardQueryToPackage("libs", default_handler=_libs_default_handler)
command = ForwardQueryToPackage("command", default_handler=None, _indirect=True)
home = ForwardQueryToPackage("home")
headers = ForwardQueryToPackage("headers")
libs = ForwardQueryToPackage("libs")
command = ForwardQueryToPackage("command", _indirect=True)
def __init__(
self,
@@ -1429,7 +1306,7 @@ def with_compiler(self, compiler: "Spec") -> "SpecAnnotations":
def __repr__(self) -> str:
result = f"SpecAnnotations().with_spec_format({self.original_spec_format})"
if self.compiler_node_attribute:
result += f".with_compiler({str(self.compiler_node_attribute)})"
result += f"with_compiler({str(self.compiler_node_attribute)})"
return result
@@ -1588,11 +1465,7 @@ def _get_dependency(self, name):
return deps[0]
def edges_from_dependents(
self,
name=None,
depflag: dt.DepFlag = dt.ALL,
*,
virtuals: Optional[Union[str, Sequence[str]]] = None,
self, name=None, depflag: dt.DepFlag = dt.ALL, *, virtuals: Optional[List[str]] = None
) -> List[DependencySpec]:
"""Return a list of edges connecting this node in the DAG
to parents.
@@ -1607,11 +1480,7 @@ def edges_from_dependents(
]
def edges_to_dependencies(
self,
name=None,
depflag: dt.DepFlag = dt.ALL,
*,
virtuals: Optional[Union[str, Sequence[str]]] = None,
self, name=None, depflag: dt.DepFlag = dt.ALL, *, virtuals: Optional[Sequence[str]] = None
) -> List[DependencySpec]:
"""Returns a list of edges connecting this node in the DAG to children.
@@ -1653,7 +1522,7 @@ def dependencies(
name=None,
deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL,
*,
virtuals: Optional[Union[str, Sequence[str]]] = None,
virtuals: Optional[Sequence[str]] = None,
) -> List["Spec"]:
"""Returns a list of direct dependencies (nodes in the DAG)
@@ -1698,19 +1567,18 @@ def _dependencies_dict(self, depflag: dt.DepFlag = dt.ALL):
result[key] = list(group)
return result
def _add_flag(
self, name: str, value: Union[str, bool], propagate: bool, concrete: bool
) -> None:
"""Called by the parser to add a known flag"""
def _add_flag(self, name, value, propagate):
"""Called by the parser to add a known flag.
Known flags currently include "arch"
"""
if propagate and name in vt.RESERVED_NAMES:
if propagate and name in vt.reserved_names:
raise UnsupportedPropagationError(
f"Propagation with '==' is not supported for '{name}'."
)
valid_flags = FlagMap.valid_compiler_flags()
if name == "arch" or name == "architecture":
assert type(value) is str, "architecture have a string value"
parts = tuple(value.split("-"))
plat, os, tgt = parts if len(parts) == 3 else (None, None, value)
self._set_architecture(platform=plat, os=os, target=tgt)
@@ -1724,15 +1592,19 @@ def _add_flag(
self.namespace = value
elif name in valid_flags:
assert self.compiler_flags is not None
assert type(value) is str, f"{name} must have a string value"
flags_and_propagation = spack.compilers.flags.tokenize_flags(value, propagate)
flag_group = " ".join(x for (x, y) in flags_and_propagation)
for flag, propagation in flags_and_propagation:
self.compiler_flags.add_flag(name, flag, propagation, flag_group)
else:
self.variants[name] = vt.VariantValue.from_string_or_bool(
name, value, propagate=propagate, concrete=concrete
)
# FIXME:
# All other flags represent variants. 'foo=true' and 'foo=false'
# map to '+foo' and '~foo' respectively. As such they need a
# BoolValuedVariant instance.
if str(value).upper() == "TRUE" or str(value).upper() == "FALSE":
self.variants[name] = vt.BoolValuedVariant(name, value, propagate)
else:
self.variants[name] = vt.AbstractVariant(name, value, propagate)
def _set_architecture(self, **kwargs):
"""Called by the parser to set the architecture."""
@@ -2340,7 +2212,6 @@ def to_node_dict(self, hash=ht.dag_hash):
[v.name for v in self.variants.values() if v.propagate], flag_names
)
)
d["abstract"] = sorted(v.name for v in self.variants.values() if not v.concrete)
if self.external:
d["external"] = {
@@ -3014,8 +2885,9 @@ def ensure_valid_variants(spec):
# but are not necessarily recorded by the package's class
propagate_variants = [name for name, variant in spec.variants.items() if variant.propagate]
not_existing = set(spec.variants)
not_existing.difference_update(pkg_variants, vt.RESERVED_NAMES, propagate_variants)
not_existing = set(spec.variants) - (
set(pkg_variants) | set(vt.reserved_names) | set(propagate_variants)
)
if not_existing:
raise vt.UnknownVariantError(
@@ -3067,7 +2939,7 @@ def constrain(self, other, deps=True):
raise UnsatisfiableVersionSpecError(self.versions, other.versions)
for v in [x for x in other.variants if x in self.variants]:
if not self.variants[v].intersects(other.variants[v]):
if not self.variants[v].compatible(other.variants[v]):
raise vt.UnsatisfiableVariantSpecError(self.variants[v], other.variants[v])
sarch, oarch = self.architecture, other.architecture
@@ -3394,7 +3266,7 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
return True
# If we have no dependencies, we can't satisfy any constraints.
if not self._dependencies and self.original_spec_format() >= 5 and not self.external:
if not self._dependencies:
return False
# If we arrived here, the lhs root node satisfies the rhs root node. Now we need to check
@@ -3405,7 +3277,6 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
# verify the edge properties, cause everything is encoded in the hash of the nodes that
# will be verified later.
lhs_edges: Dict[str, Set[DependencySpec]] = collections.defaultdict(set)
mock_nodes_from_old_specfiles = set()
for rhs_edge in other.traverse_edges(root=False, cover="edges"):
# If we are checking for ^mpi we need to verify if there is any edge
if spack.repo.PATH.is_virtual(rhs_edge.spec.name):
@@ -3427,27 +3298,13 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
except KeyError:
return False
if current_node.original_spec_format() < 5 or (
current_node.original_spec_format() >= 5 and current_node.external
):
compiler_spec = current_node.annotations.compiler_node_attribute
if compiler_spec is None:
return False
mock_nodes_from_old_specfiles.add(compiler_spec)
# This checks that the single node compiler spec satisfies the request
# of a direct dependency. The check is not perfect, but based on heuristic.
if not compiler_spec.satisfies(rhs_edge.spec):
return False
else:
candidates = current_node.dependencies(
name=rhs_edge.spec.name,
deptype=rhs_edge.depflag,
virtuals=rhs_edge.virtuals or None,
)
if not candidates or not any(x.satisfies(rhs_edge.spec) for x in candidates):
return False
candidates = current_node.dependencies(
name=rhs_edge.spec.name,
deptype=rhs_edge.depflag,
virtuals=rhs_edge.virtuals or None,
)
if not candidates or not any(x.satisfies(rhs_edge.spec) for x in candidates):
return False
continue
@@ -3487,9 +3344,8 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
return False
# Edges have been checked above already, hence deps=False
lhs_nodes = [x for x in self.traverse(root=False)] + sorted(mock_nodes_from_old_specfiles)
return all(
any(lhs.satisfies(rhs, deps=False) for lhs in lhs_nodes)
any(lhs.satisfies(rhs, deps=False) for lhs in self.traverse(root=False))
for rhs in other.traverse(root=False)
)
@@ -3664,6 +3520,21 @@ def version(self):
raise spack.error.SpecError("Spec version is not concrete: " + str(self))
return self.versions[0]
def _get_dependency_by_name(self, name: str) -> Tuple["Spec", bool]:
"""Get a dependency by package name or virtual. Returns a tuple with the matching spec
and a boolean indicating if the spec is a virtual dependency. Raises a KeyError if the
dependency is not found."""
# Consider all direct dependencies and transitive runtime dependencies
order = itertools.chain(
self.edges_to_dependencies(depflag=dt.BUILD | dt.TEST),
self.traverse_edges(deptype=dt.LINK | dt.RUN, order="breadth", cover="edges"),
)
edge = next((e for e in order if e.spec.name == name or name in e.virtuals), None)
if edge is None:
raise KeyError(f"No spec with name {name} in {self}")
return edge.spec, name in edge.virtuals
def __getitem__(self, name: str):
"""Get a dependency from the spec by its name. This call implicitly
sets a query state in the package being retrieved. The behavior of
@@ -3684,23 +3555,14 @@ def __getitem__(self, name: str):
csv = query_parameters.pop().strip()
query_parameters = re.split(r"\s*,\s*", csv)
# Consider all direct dependencies and transitive runtime dependencies
order = itertools.chain(
self.edges_to_dependencies(depflag=dt.BUILD | dt.TEST),
self.traverse_edges(deptype=dt.LINK | dt.RUN, order="breadth", cover="edges"),
)
try:
edge = next((e for e in order if e.spec.name == name or name in e.virtuals))
except StopIteration as e:
raise KeyError(f"No spec with name {name} in {self}") from e
spec, is_virtual = self._get_dependency_by_name(name)
if self._concrete:
return SpecBuildInterface(
edge.spec, name, query_parameters, _parent=self, is_virtual=name in edge.virtuals
spec, name, query_parameters, _parent=self, is_virtual=is_virtual
)
return edge.spec
return spec
def __contains__(self, spec):
"""True if this spec or some dependency satisfies the spec.
@@ -3963,8 +3825,6 @@ def format_attribute(match_object: Match) -> str:
except AttributeError:
if part == "compiler":
return "none"
elif part == "specfile_version":
return f"v{current.original_spec_format()}"
raise SpecFormatStringError(
f"Attempted to format attribute {attribute}. "
@@ -4500,7 +4360,7 @@ def __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.VariantValue):
if not isinstance(vspec, vt.AbstractVariant):
raise TypeError(
"VariantMap accepts only values of variant types "
f"[got {type(vspec).__name__} instead]"
@@ -4610,7 +4470,8 @@ def constrain(self, other: "VariantMap") -> bool:
changed = False
for k in other:
if k in self:
if not self[k].intersects(other[k]):
# 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])
@@ -4640,7 +4501,7 @@ def __str__(self):
bool_keys = []
kv_keys = []
for key in sorted_keys:
if self[key].type == vt.VariantType.BOOL:
if isinstance(self[key].value, bool):
bool_keys.append(key)
else:
kv_keys.append(key)
@@ -4673,10 +4534,9 @@ def substitute_abstract_variants(spec: Spec):
unknown = []
for name, v in spec.variants.items():
if name == "dev_path":
v.type = vt.VariantType.SINGLE
v.concrete = True
spec.variants.substitute(vt.SingleValuedVariant(name, v._original_value))
continue
elif name in vt.RESERVED_NAMES:
elif name in vt.reserved_names:
continue
variant_defs = spack.repo.PATH.get_pkg_class(spec.fullname).variant_definitions(name)
@@ -4697,7 +4557,7 @@ def substitute_abstract_variants(spec: Spec):
if rest:
continue
new_variant = pkg_variant.make_variant(*v.values)
new_variant = pkg_variant.make_variant(v._original_value)
pkg_variant.validate_or_raise(new_variant, spec.name)
spec.variants.substitute(new_variant)
@@ -4815,7 +4675,6 @@ def from_node_dict(cls, node):
spec.architecture = ArchSpec.from_dict(node)
propagated_names = node.get("propagate", [])
abstract_variants = set(node.get("abstract", ()))
for name, values in node.get("parameters", {}).items():
propagate = name in propagated_names
if name in _valid_compiler_flags:
@@ -4823,8 +4682,8 @@ def from_node_dict(cls, node):
for val in values:
spec.compiler_flags.add_flag(name, val, propagate)
else:
spec.variants[name] = vt.VariantValue.from_node_dict(
name, values, propagate=propagate, abstract=name in abstract_variants
spec.variants[name] = vt.MultiValuedVariant.from_node_dict(
name, values, propagate=propagate
)
spec.external_path = None
@@ -4849,7 +4708,7 @@ def from_node_dict(cls, node):
patches = node["patches"]
if len(patches) > 0:
mvar = spec.variants.setdefault("patches", vt.MultiValuedVariant("patches", ()))
mvar.set(*patches)
mvar.value = patches
# FIXME: Monkey patches mvar to store patches order
mvar._patches_in_order_of_appearance = patches
@@ -5174,6 +5033,25 @@ def eval_conditional(string):
return eval(string, valid_variables)
class SpecParseError(spack.error.SpecError):
"""Wrapper for ParseError for when we're parsing specs."""
def __init__(self, parse_error):
super().__init__(parse_error.message)
self.string = parse_error.string
self.pos = parse_error.pos
@property
def long_message(self):
return "\n".join(
[
" Encountered when parsing spec:",
" %s" % self.string,
" %s^" % (" " * self.pos),
]
)
class InvalidVariantForSpecError(spack.error.SpecError):
"""Raised when an invalid conditional variant is specified."""
@@ -5191,6 +5069,14 @@ class DuplicateDependencyError(spack.error.SpecError):
"""Raised when the same dependency occurs in a spec twice."""
class MultipleVersionError(spack.error.SpecError):
"""Raised when version constraints occur in a spec twice."""
class DuplicateCompilerSpecError(spack.error.SpecError):
"""Raised when the same compiler occurs in a spec twice."""
class UnsupportedCompilerError(spack.error.SpecError):
"""Raised when the user asks for a compiler spack doesn't know about."""
@@ -5199,6 +5085,11 @@ class DuplicateArchitectureError(spack.error.SpecError):
"""Raised when the same architecture occurs in a spec twice."""
class InconsistentSpecError(spack.error.SpecError):
"""Raised when two nodes in the same spec DAG have inconsistent
constraints."""
class InvalidDependencyError(spack.error.SpecError):
"""Raised when a dependency in a spec is not actually a dependency
of the package."""
@@ -5210,6 +5101,30 @@ def __init__(self, pkg, deps):
)
class NoProviderError(spack.error.SpecError):
"""Raised when there is no package that provides a particular
virtual dependency.
"""
def __init__(self, vpkg):
super().__init__("No providers found for virtual package: '%s'" % vpkg)
self.vpkg = vpkg
class MultipleProviderError(spack.error.SpecError):
"""Raised when there is no package that provides a particular
virtual dependency.
"""
def __init__(self, vpkg, providers):
"""Takes the name of the vpkg"""
super().__init__(
"Multiple providers found for '%s': %s" % (vpkg, [str(s) for s in providers])
)
self.vpkg = vpkg
self.providers = providers
class UnsatisfiableSpecNameError(spack.error.UnsatisfiableSpecError):
"""Raised when two specs aren't even for the same package."""
@@ -5224,6 +5139,20 @@ def __init__(self, provided, required):
super().__init__(provided, required, "version")
class UnsatisfiableCompilerSpecError(spack.error.UnsatisfiableSpecError):
"""Raised when a spec compiler conflicts with package constraints."""
def __init__(self, provided, required):
super().__init__(provided, required, "compiler")
class UnsatisfiableCompilerFlagSpecError(spack.error.UnsatisfiableSpecError):
"""Raised when a spec variant conflicts with package constraints."""
def __init__(self, provided, required):
super().__init__(provided, required, "compiler_flags")
class UnsatisfiableArchitectureSpecError(spack.error.UnsatisfiableSpecError):
"""Raised when a spec architecture conflicts with package constraints."""
@@ -5231,6 +5160,14 @@ def __init__(self, provided, required):
super().__init__(provided, required, "architecture")
class UnsatisfiableProviderSpecError(spack.error.UnsatisfiableSpecError):
"""Raised when a provider is supplied but constraints don't match
a vpkg requirement"""
def __init__(self, provided, required):
super().__init__(provided, required, "provider")
# TODO: get rid of this and be more specific about particular incompatible
# dep constraints
class UnsatisfiableDependencySpecError(spack.error.UnsatisfiableSpecError):

View File

@@ -2,24 +2,36 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import itertools
from typing import Any, Dict, List, NamedTuple, Optional, Union
from typing import List
import spack.spec
import spack.util.spack_yaml
import spack.variant
from spack.error import SpackError
from spack.spec import Spec
class SpecList:
def __init__(self, *, name: str = "specs", yaml_list=None, expanded_list=None):
def __init__(self, name="specs", yaml_list=None, reference=None):
# Normalize input arguments
yaml_list = yaml_list or []
reference = reference or {}
self.name = name
self.yaml_list = yaml_list[:] if yaml_list is not None else []
self._reference = reference # TODO: Do we need defensive copy here?
# Validate yaml_list before assigning
if not all(isinstance(s, str) or isinstance(s, (list, dict)) for s in yaml_list):
raise ValueError(
"yaml_list can contain only valid YAML types! Found:\n %s"
% [type(s) for s in yaml_list]
)
self.yaml_list = yaml_list[:]
# Expansions can be expensive to compute and difficult to keep updated
# We cache results and invalidate when self.yaml_list changes
self.specs_as_yaml_list = expanded_list or []
self._expanded_list = None
self._constraints = None
self._specs: Optional[List[Spec]] = None
self._specs = None
@property
def is_matrix(self):
@@ -28,6 +40,12 @@ def is_matrix(self):
return True
return False
@property
def specs_as_yaml_list(self):
if self._expanded_list is None:
self._expanded_list = self._expand_references(self.yaml_list)
return self._expanded_list
@property
def specs_as_constraints(self):
if self._constraints is None:
@@ -44,7 +62,7 @@ def specs_as_constraints(self):
@property
def specs(self) -> List[Spec]:
if self._specs is None:
specs: List[Spec] = []
specs = []
# This could be slightly faster done directly from yaml_list,
# but this way is easier to maintain.
for constraint_list in self.specs_as_constraints:
@@ -56,13 +74,12 @@ def specs(self) -> List[Spec]:
return self._specs
def add(self, spec: Spec):
spec_str = str(spec)
self.yaml_list.append(spec_str)
def add(self, spec):
self.yaml_list.append(str(spec))
# expanded list can be updated without invalidation
if self.specs_as_yaml_list is not None:
self.specs_as_yaml_list.append(spec_str)
if self._expanded_list is not None:
self._expanded_list.append(str(spec))
# Invalidate cache variables when we change the list
self._constraints = None
@@ -84,18 +101,83 @@ def remove(self, spec):
# Remove may contain more than one string representation of the same spec
for item in remove:
self.yaml_list.remove(item)
self.specs_as_yaml_list.remove(item)
# invalidate cache variables when we change the list
self._expanded_list = None
self._constraints = None
self._specs = None
def extend(self, other: "SpecList", copy_reference=True) -> None:
self.yaml_list.extend(other.yaml_list)
self.specs_as_yaml_list.extend(other.specs_as_yaml_list)
def replace(self, idx: int, spec: str):
"""Replace the existing spec at the index with the new one.
Args:
idx: index of the spec to replace in the speclist
spec: new spec
"""
self.yaml_list[idx] = spec
# invalidate cache variables when we change the list
self._expanded_list = None
self._constraints = None
self._specs = None
def extend(self, other, copy_reference=True):
self.yaml_list.extend(other.yaml_list)
self._expanded_list = None
self._constraints = None
self._specs = None
if copy_reference:
self._reference = other._reference
def update_reference(self, reference):
self._reference = reference
self._expanded_list = None
self._constraints = None
self._specs = None
def _parse_reference(self, name):
sigil = ""
name = name[1:]
# Parse specs as constraints
if name.startswith("^") or name.startswith("%"):
sigil = name[0]
name = name[1:]
# Make sure the reference is valid
if name not in self._reference:
msg = f"SpecList '{self.name}' refers to named list '{name}'"
msg += " which does not appear in its reference dict."
raise UndefinedReferenceError(msg)
return (name, sigil)
def _expand_references(self, yaml):
if isinstance(yaml, list):
ret = []
for item in yaml:
# if it's a reference, expand it
if isinstance(item, str) and item.startswith("$"):
# replace the reference and apply the sigil if needed
name, sigil = self._parse_reference(item)
referent = [
_sigilify(item, sigil) for item in self._reference[name].specs_as_yaml_list
]
ret.extend(referent)
else:
# else just recurse
ret.append(self._expand_references(item))
return ret
elif isinstance(yaml, dict):
# There can't be expansions in dicts
return dict((name, self._expand_references(val)) for (name, val) in yaml.items())
else:
# Strings are just returned
return yaml
def __len__(self):
return len(self.specs)
@@ -169,111 +251,6 @@ def _sigilify(item, sigil):
return sigil + item
class Definition(NamedTuple):
name: str
yaml_list: List[Union[str, Dict]]
when: Optional[str]
class SpecListParser:
"""Parse definitions and user specs from data in environments"""
def __init__(self):
self.definitions: Dict[str, SpecList] = {}
def parse_definitions(self, *, data: List[Dict[str, Any]]) -> Dict[str, SpecList]:
definitions_from_yaml: Dict[str, List[Definition]] = {}
for item in data:
value = self._parse_yaml_definition(item)
definitions_from_yaml.setdefault(value.name, []).append(value)
self.definitions = {}
self._build_definitions(definitions_from_yaml)
return self.definitions
def parse_user_specs(self, *, name, yaml_list) -> SpecList:
definition = Definition(name=name, yaml_list=yaml_list, when=None)
return self._speclist_from_definitions(name, [definition])
def _parse_yaml_definition(self, yaml_entry) -> Definition:
when_string = yaml_entry.get("when")
if (when_string and len(yaml_entry) > 2) or (not when_string and len(yaml_entry) > 1):
mark = spack.util.spack_yaml.get_mark_from_yaml_data(yaml_entry)
attributes = ", ".join(x for x in yaml_entry if x != "when")
error_msg = f"definition must have a single attribute, got many: {attributes}"
raise SpecListError(f"{mark.name}:{mark.line + 1}: {error_msg}")
for name, yaml_list in yaml_entry.items():
if name == "when":
continue
return Definition(name=name, yaml_list=yaml_list, when=when_string)
# If we are here, it means only "when" is in the entry
mark = spack.util.spack_yaml.get_mark_from_yaml_data(yaml_entry)
error_msg = "definition must have a single attribute, got none"
raise SpecListError(f"{mark.name}:{mark.line + 1}: {error_msg}")
def _build_definitions(self, definitions_from_yaml: Dict[str, List[Definition]]):
for name, definitions in definitions_from_yaml.items():
self.definitions[name] = self._speclist_from_definitions(name, definitions)
def _speclist_from_definitions(self, name, definitions) -> SpecList:
combined_yaml_list = []
for def_part in definitions:
if def_part.when is not None and not spack.spec.eval_conditional(def_part.when):
continue
combined_yaml_list.extend(def_part.yaml_list)
expanded_list = self._expand_yaml_list(combined_yaml_list)
return SpecList(name=name, yaml_list=combined_yaml_list, expanded_list=expanded_list)
def _expand_yaml_list(self, raw_yaml_list):
result = []
for item in raw_yaml_list:
if isinstance(item, str) and item.startswith("$"):
result.extend(self._expand_reference(item))
continue
value = item
if isinstance(item, dict):
value = self._expand_yaml_matrix(item)
result.append(value)
return result
def _expand_reference(self, item: str):
sigil, name = "", item[1:]
if name.startswith("^") or name.startswith("%"):
sigil, name = name[0], name[1:]
if name not in self.definitions:
mark = spack.util.spack_yaml.get_mark_from_yaml_data(item)
error_msg = f"trying to expand the name '{name}', which is not defined yet"
raise UndefinedReferenceError(f"{mark.name}:{mark.line + 1}: {error_msg}")
value = self.definitions[name].specs_as_yaml_list
if not sigil:
return value
return [_sigilify(x, sigil) for x in value]
def _expand_yaml_matrix(self, matrix_yaml):
extra_attributes = set(matrix_yaml) - {"matrix", "exclude"}
if extra_attributes:
mark = spack.util.spack_yaml.get_mark_from_yaml_data(matrix_yaml)
error_msg = f"extra attributes in spec matrix: {','.join(sorted(extra_attributes))}"
raise SpecListError(f"{mark.name}:{mark.line + 1}: {error_msg}")
if "matrix" not in matrix_yaml:
mark = spack.util.spack_yaml.get_mark_from_yaml_data(matrix_yaml)
error_msg = "matrix is missing the 'matrix' attribute"
raise SpecListError(f"{mark.name}:{mark.line + 1}: {error_msg}")
# Assume data has been validated against the YAML schema
result = {"matrix": [self._expand_yaml_list(row) for row in matrix_yaml["matrix"]]}
if "exclude" in matrix_yaml:
result["exclude"] = matrix_yaml["exclude"]
return result
class SpecListError(SpackError):
"""Error class for all errors related to SpecList objects."""

View File

@@ -62,7 +62,7 @@
import sys
import traceback
import warnings
from typing import Iterator, List, Optional, Tuple, Union
from typing import Iterator, List, Optional, Tuple
from llnl.util.tty import color
@@ -99,7 +99,8 @@
VERSION_RANGE = rf"(?:(?:{VERSION})?:(?:{VERSION}(?!\s*=))?)"
VERSION_LIST = rf"(?:{VERSION_RANGE}|{VERSION})(?:\s*,\s*(?:{VERSION_RANGE}|{VERSION}))*"
SPLIT_KVP = re.compile(rf"^({NAME})(:?==?)(.*)$")
#: Regex with groups to use for splitting (optionally propagated) key-value pairs
SPLIT_KVP = re.compile(rf"^({NAME})(==?)(.*)$")
#: Regex with groups to use for splitting %[virtuals=...] tokens
SPLIT_COMPILER_TOKEN = re.compile(rf"^%\[virtuals=({VALUE}|{QUOTED_VALUE})]\s*(.*)$")
@@ -134,8 +135,8 @@ class SpecTokens(TokenBase):
# Variants
PROPAGATED_BOOL_VARIANT = rf"(?:(?:\+\+|~~|--)\s*{NAME})"
BOOL_VARIANT = rf"(?:[~+-]\s*{NAME})"
PROPAGATED_KEY_VALUE_PAIR = rf"(?:{NAME}:?==(?:{VALUE}|{QUOTED_VALUE}))"
KEY_VALUE_PAIR = rf"(?:{NAME}:?=(?:{VALUE}|{QUOTED_VALUE}))"
PROPAGATED_KEY_VALUE_PAIR = rf"(?:{NAME}==(?:{VALUE}|{QUOTED_VALUE}))"
KEY_VALUE_PAIR = rf"(?:{NAME}=(?:{VALUE}|{QUOTED_VALUE}))"
# Compilers
COMPILER_AND_VERSION = rf"(?:%\s*(?:{NAME})(?:[\s]*)@\s*(?:{VERSION_LIST}))"
COMPILER = rf"(?:%\s*(?:{NAME}))"
@@ -369,10 +370,10 @@ def raise_parsing_error(string: str, cause: Optional[Exception] = None):
"""Raise a spec parsing error with token context."""
raise SpecParsingError(string, self.ctx.current_token, self.literal_str) from cause
def add_flag(name: str, value: Union[str, bool], propagate: bool, concrete: bool):
def add_flag(name: str, value: str, propagate: bool):
"""Wrapper around ``Spec._add_flag()`` that adds parser context to errors raised."""
try:
initial_spec._add_flag(name, value, propagate, concrete)
initial_spec._add_flag(name, value, propagate)
except Exception as e:
raise_parsing_error(str(e), e)
@@ -427,34 +428,29 @@ def warn_if_after_compiler(token: str):
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.BOOL_VARIANT):
name = self.ctx.current_token.value[1:].strip()
variant_value = self.ctx.current_token.value[0] == "+"
add_flag(name, variant_value, propagate=False, concrete=True)
add_flag(self.ctx.current_token.value[1:].strip(), variant_value, propagate=False)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.PROPAGATED_BOOL_VARIANT):
name = self.ctx.current_token.value[2:].strip()
variant_value = self.ctx.current_token.value[0:2] == "++"
add_flag(name, variant_value, propagate=True, concrete=True)
add_flag(self.ctx.current_token.value[2:].strip(), variant_value, propagate=True)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.KEY_VALUE_PAIR):
name, value = self.ctx.current_token.value.split("=", maxsplit=1)
concrete = name.endswith(":")
if concrete:
name = name[:-1]
match = SPLIT_KVP.match(self.ctx.current_token.value)
assert match, "SPLIT_KVP and KEY_VALUE_PAIR do not agree."
add_flag(
name, strip_quotes_and_unescape(value), propagate=False, concrete=concrete
)
name, _, value = match.groups()
add_flag(name, strip_quotes_and_unescape(value), propagate=False)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.PROPAGATED_KEY_VALUE_PAIR):
name, value = self.ctx.current_token.value.split("==", maxsplit=1)
concrete = name.endswith(":")
if concrete:
name = name[:-1]
add_flag(name, strip_quotes_and_unescape(value), propagate=True, concrete=concrete)
match = SPLIT_KVP.match(self.ctx.current_token.value)
assert match, "SPLIT_KVP and PROPAGATED_KEY_VALUE_PAIR do not agree."
name, _, value = match.groups()
add_flag(name, strip_quotes_and_unescape(value), propagate=True)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.expect(SpecTokens.DAG_HASH):
@@ -513,8 +509,7 @@ def parse(self):
while True:
if self.ctx.accept(SpecTokens.KEY_VALUE_PAIR):
name, value = self.ctx.current_token.value.split("=", maxsplit=1)
if name.endswith(":"):
name = name[:-1]
name = name.strip("'\" ")
value = value.strip("'\" ").split(",")
attributes[name] = value
if name not in ("deptypes", "virtuals"):

View File

@@ -1,12 +1,9 @@
# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import collections
import multiprocessing
import os
import posixpath
import sys
from typing import Dict, Optional, Tuple
import pytest
@@ -780,139 +777,3 @@ def test_optimization_flags_are_using_node_target(default_mock_concretization, m
assert len(actions) == 1 and isinstance(actions[0], spack.util.environment.SetEnv)
assert actions[0].value == "-march=x86-64 -mtune=generic"
@pytest.mark.regression("49827")
@pytest.mark.parametrize(
"gcc_config,expected_rpaths",
[
(
"""\
gcc:
externals:
- spec: gcc@14.2.0 languages=c
prefix: /fake/path1
extra_attributes:
compilers:
c: /fake/path1
extra_rpaths:
- /extra/rpaths1
- /extra/rpaths2
""",
"/extra/rpaths1:/extra/rpaths2",
),
(
"""\
gcc:
externals:
- spec: gcc@14.2.0 languages=c
prefix: /fake/path1
extra_attributes:
compilers:
c: /fake/path1
""",
None,
),
],
)
@pytest.mark.not_on_windows("Windows doesn't use the compiler-wrapper")
def test_extra_rpaths_is_set(
working_env, mutable_config, mock_packages, gcc_config, expected_rpaths
):
"""Tests that using a compiler with an 'extra_rpaths' section will set the corresponding
SPACK_COMPILER_EXTRA_RPATHS variable for the wrapper.
"""
cfg_data = syaml.load_config(gcc_config)
spack.config.set("packages", cfg_data)
mpich = spack.concretize.concretize_one("mpich %gcc@14")
spack.build_environment.setup_package(mpich.package, dirty=False)
if expected_rpaths is not None:
assert os.environ["SPACK_COMPILER_EXTRA_RPATHS"] == expected_rpaths
else:
assert "SPACK_COMPILER_EXTRA_RPATHS" not in os.environ
class _TestProcess:
calls: Dict[str, int] = collections.defaultdict(int)
terminated = False
runtime = 0
def __init__(self, *, target, args):
self.alive = None
self.exitcode = 0
self._reset()
def start(self):
self.calls["start"] += 1
self.alive = True
def is_alive(self):
self.calls["is_alive"] += 1
return self.alive
def join(self, timeout: Optional[int] = None):
self.calls["join"] += 1
if timeout is not None and timeout > self.runtime:
self.alive = False
def terminate(self):
self.calls["terminate"] += 1
self._set_terminated()
self.alive = False
@classmethod
def _set_terminated(cls):
cls.terminated = True
@classmethod
def _reset(cls):
cls.calls.clear()
cls.terminated = False
class _TestPipe:
def close(self):
pass
def recv(self):
if _TestProcess.terminated is True:
return 1
return 0
def _pipe_fn(*, duplex: bool = False) -> Tuple[_TestPipe, _TestPipe]:
return _TestPipe(), _TestPipe()
@pytest.fixture()
def mock_build_process(monkeypatch):
monkeypatch.setattr(spack.build_environment, "BuildProcess", _TestProcess)
monkeypatch.setattr(multiprocessing, "Pipe", _pipe_fn)
def _factory(*, runtime: int):
_TestProcess.runtime = runtime
return _factory
@pytest.mark.parametrize(
"runtime,timeout,expected_result,expected_calls",
[
# execution time < timeout
(2, 5, 0, {"start": 1, "join": 1, "is_alive": 1}),
# execution time > timeout
(5, 2, 1, {"start": 1, "join": 2, "is_alive": 1, "terminate": 1}),
],
)
def test_build_process_timeout(
mock_build_process, runtime, timeout, expected_result, expected_calls
):
"""Tests that we make the correct function calls in different timeout scenarios."""
mock_build_process(runtime=runtime)
result = spack.build_environment.start_build_process(
pkg=None, function=None, kwargs={}, timeout=timeout
)
assert result == expected_result
assert _TestProcess.calls == expected_calls

View File

@@ -873,6 +873,10 @@ def test_push_to_build_cache(
ci.copy_stage_logs_to_artifacts(concrete_spec, str(logs_dir))
assert "spack-build-out.txt.gz" in os.listdir(logs_dir)
dl_dir = scratch / "download_dir"
buildcache_cmd("download", "--spec-file", json_path, "--path", str(dl_dir))
assert len(os.listdir(dl_dir)) == 2
def test_push_to_build_cache_exceptions(monkeypatch, tmp_path, capsys):
def push_or_raise(*args, **kwargs):

View File

@@ -62,7 +62,7 @@
(
["-t", "intel", "/test-intel"],
"test-intel",
[r"TestIntel(IntelOneApiPackage)", r"setup_environment"],
[r"TestIntel(IntelPackage)", r"setup_environment"],
),
(
["-t", "makefile", "/test-makefile"],

View File

@@ -38,9 +38,10 @@
(["--transitive", "--deptype=link", "dtbuild1"], {"dtlink2"}),
],
)
def test_direct_dependencies(cli_args, expected, mock_packages):
def test_direct_dependencies(cli_args, expected, mock_runtimes):
out = dependencies(*cli_args)
result = set(re.split(r"\s+", out.strip()))
expected.update(mock_runtimes)
assert expected == result

View File

@@ -24,6 +24,8 @@ def test_it_just_runs(pkg):
(
("mpi",),
[
"intel-mpi",
"intel-parallel-studio",
"mpich",
"mpilander",
"mvapich2",

View File

@@ -83,12 +83,3 @@ def tests_compiler_conversion_modules(mock_compiler):
compiler_spec = CompilerFactory.from_legacy_yaml(mock_compiler)[0]
assert compiler_spec.external
assert compiler_spec.external_modules == modules
@pytest.mark.regression("49717")
def tests_compiler_conversion_corrupted_paths(mock_compiler):
"""Tests that compiler entries with corrupted path do not raise"""
mock_compiler["paths"] = {"cc": "gcc", "cxx": "g++", "fc": "gfortran", "f77": "gfortran"}
# Test this call doesn't raise
compiler_spec = CompilerFactory.from_legacy_yaml(mock_compiler)
assert compiler_spec == []

View File

@@ -1831,7 +1831,10 @@ def test_solve_in_rounds_all_unsolved(self, monkeypatch, mock_packages):
monkeypatch.setattr(spack.solver.asp.Result, "unsolved_specs", simulate_unsolved_property)
monkeypatch.setattr(spack.solver.asp.Result, "specs", list())
with pytest.raises(spack.solver.asp.OutputDoesNotSatisfyInputError):
with pytest.raises(
spack.solver.asp.InternalConcretizerError,
match="a subset of input specs could not be solved for",
):
list(solver.solve_in_rounds(specs))
def test_coconcretize_reuse_and_virtuals(self):
@@ -3333,110 +3336,3 @@ def test_specifying_compilers_with_virtuals_syntax(default_mock_concretization):
assert mpich["fortran"].satisfies("gcc")
assert mpich["c"].satisfies("llvm")
assert mpich["cxx"].satisfies("llvm")
@pytest.mark.regression("49847")
@pytest.mark.xfail(sys.platform == "win32", reason="issues with install mockery")
def test_reuse_when_input_specifies_build_dep(install_mockery, do_not_check_runtimes_on_reuse):
"""Test that we can reuse a spec when specifying build dependencies in the input"""
pkgb_old = spack.concretize.concretize_one(spack.spec.Spec("pkg-b@0.9 %gcc@9"))
PackageInstaller([pkgb_old.package], fake=True, explicit=True).install()
with spack.config.override("concretizer:reuse", True):
result = spack.concretize.concretize_one("pkg-b %gcc")
assert pkgb_old.dag_hash() == result.dag_hash()
result = spack.concretize.concretize_one("pkg-a ^pkg-b %gcc@9")
assert pkgb_old.dag_hash() == result["pkg-b"].dag_hash()
assert result.satisfies("%gcc@9")
result = spack.concretize.concretize_one("pkg-a %gcc@10 ^pkg-b %gcc@9")
assert pkgb_old.dag_hash() == result["pkg-b"].dag_hash()
@pytest.mark.regression("49847")
def test_reuse_when_requiring_build_dep(
install_mockery, do_not_check_runtimes_on_reuse, mutable_config
):
"""Test that we can reuse a spec when specifying build dependencies in requirements"""
mutable_config.set("packages:all:require", "%gcc")
pkgb_old = spack.concretize.concretize_one(spack.spec.Spec("pkg-b@0.9"))
PackageInstaller([pkgb_old.package], fake=True, explicit=True).install()
with spack.config.override("concretizer:reuse", True):
result = spack.concretize.concretize_one("pkg-b")
assert pkgb_old.dag_hash() == result.dag_hash(), result.tree()
@pytest.mark.regression("50167")
def test_input_analysis_and_conditional_requirements(default_mock_concretization):
"""Tests that input analysis doesn't account for conditional requirement
to discard possible dependencies.
If the requirement is conditional, and impossible to achieve on the current
platform, the valid search space is still the complement of the condition that
activates the requirement.
"""
libceed = default_mock_concretization("libceed")
assert libceed["libxsmm"].satisfies("@main")
assert libceed["libxsmm"].satisfies("platform=test")
@pytest.mark.parametrize(
"compiler_str,expected,not_expected",
[
# Compiler queries are as specific as the constraint on the external
("gcc@10", ["%gcc", "%gcc@10"], ["%clang", "%gcc@9"]),
("gcc", ["%gcc"], ["%clang", "%gcc@9", "%gcc@10"]),
],
)
@pytest.mark.regression("49841")
def test_installing_external_with_compilers_directly(
compiler_str, expected, not_expected, mutable_config, mock_packages, tmp_path
):
"""Tests that version constraints are taken into account for compiler annotations
on externals
"""
spec_str = f"libelf@0.8.12 %{compiler_str}"
packages_yaml = syaml.load_config(
f"""
packages:
libelf:
buildable: false
externals:
- spec: {spec_str}
prefix: {tmp_path / 'libelf'}
"""
)
mutable_config.set("packages", packages_yaml["packages"])
s = spack.concretize.concretize_one(spec_str)
assert s.external
assert all(s.satisfies(c) for c in expected)
assert all(not s.satisfies(c) for c in not_expected)
@pytest.mark.regression("49841")
def test_using_externals_with_compilers(mutable_config, mock_packages, tmp_path):
"""Tests that version constraints are taken into account for compiler annotations
on externals, even imposed as transitive deps.
"""
packages_yaml = syaml.load_config(
f"""
packages:
libelf:
buildable: false
externals:
- spec: libelf@0.8.12 %gcc@10
prefix: {tmp_path / 'libelf'}
"""
)
mutable_config.set("packages", packages_yaml["packages"])
with pytest.raises(spack.error.SpackError):
spack.concretize.concretize_one("dyninst%gcc@10.2.1 ^libelf@0.8.12 %gcc@:9")
s = spack.concretize.concretize_one("dyninst%gcc@10.2.1 ^libelf@0.8.12 %gcc@10:")
libelf = s["libelf"]
assert libelf.external and libelf.satisfies("%gcc")

View File

@@ -2,15 +2,11 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from io import StringIO
import pytest
import spack.concretize
import spack.config
import spack.main
import spack.solver.asp
import spack.spec
version_error_messages = [
"Cannot satisfy",
@@ -64,31 +60,3 @@ def test_error_messages(error_messages, config_set, spec, mock_packages, mutable
for em in error_messages:
assert em in str(e.value)
def test_internal_error_handling_formatting(tmp_path):
log = StringIO()
input_to_output = [
(spack.spec.Spec("foo+x"), spack.spec.Spec("foo@=1.0~x")),
(spack.spec.Spec("bar+y"), spack.spec.Spec("x@=1.0~y")),
(spack.spec.Spec("baz+z"), None),
]
spack.main._handle_solver_bug(
spack.solver.asp.OutputDoesNotSatisfyInputError(input_to_output), root=tmp_path, out=log
)
output = log.getvalue()
assert "the following specs were not solved:\n - baz+z\n" in output
assert (
"the following specs were concretized, but do not satisfy the input:\n"
" - foo+x\n"
" - bar+y\n"
) in output
files = {f.name: str(f) for f in tmp_path.glob("spack-asp-*/*.json")}
assert {"input-1.json", "input-2.json", "output-1.json", "output-2.json"} == set(files.keys())
assert spack.spec.Spec.from_specfile(files["input-1.json"]) == spack.spec.Spec("foo+x")
assert spack.spec.Spec.from_specfile(files["input-2.json"]) == spack.spec.Spec("bar+y")
assert spack.spec.Spec.from_specfile(files["output-1.json"]) == spack.spec.Spec("foo@=1.0~x")
assert spack.spec.Spec.from_specfile(files["output-2.json"]) == spack.spec.Spec("x@=1.0~y")

View File

@@ -1239,68 +1239,3 @@ def test_virtual_requirement_respects_any_of(concretize_scope, mock_packages):
with pytest.raises(spack.error.SpackError):
spack.concretize.concretize_one("mpileaks ^[virtuals=mpi] zmpi")
@pytest.mark.parametrize(
"packages_yaml,expected_reuse,expected_contraints",
[
(
"""
packages:
all:
require:
- "%gcc"
""",
True,
# To minimize installed specs we reuse pkg-b compiler, since the requirement allows it
["%gcc@9"],
),
(
"""
packages:
all:
require:
- "%gcc@10"
""",
False,
["%gcc@10"],
),
(
"""
packages:
all:
require:
- "%gcc"
pkg-a:
require:
- "%gcc@10"
""",
True,
["%gcc@10"],
),
],
)
@pytest.mark.regression("49847")
def test_requirements_on_compilers_and_reuse(
concretize_scope, mock_packages, packages_yaml, expected_reuse, expected_contraints
):
"""Tests that we can require compilers with `%` in configuration files, and still get reuse
of specs (even though reused specs have no build dependency in the ASP encoding).
"""
input_spec = "pkg-a"
reused_spec = spack.concretize.concretize_one("pkg-b@0.9 %gcc@9")
reused_nodes = list(reused_spec.traverse())
update_packages_config(packages_yaml)
root_specs = [Spec(input_spec)]
with spack.config.override("concretizer:reuse", True):
solver = spack.solver.asp.Solver()
setup = spack.solver.asp.SpackSolverSetup()
result, _, _ = solver.driver.solve(setup, root_specs, reuse=reused_nodes)
pkga = result.specs[0]
is_pkgb_reused = pkga["pkg-b"].dag_hash() == reused_spec.dag_hash()
assert is_pkgb_reused == expected_reuse
for c in expected_contraints:
assert pkga.satisfies(c), print(pkga.tree())

View File

@@ -47,10 +47,10 @@ class Grads(AutotoolsPackage):
depends_on('readline')
depends_on('pkgconfig', type='build')
def setup_build_environment(self, env: EnvironmentModifications) -> None:
def setup_build_environment(self, env):
env.set('SUPPLIBS', '/')
def setup_run_environment(self, env: EnvironmentModifications) -> None:
def setup_run_environment(self, env):
env.set('GADDIR', self.prefix.data)
@run_after('install')

View File

@@ -517,7 +517,7 @@ class Llvm(CMakePackage, CudaPackage):
return (None, flags, None)
return (flags, None, None)
def setup_build_environment(self, env: EnvironmentModifications) -> None:
def setup_build_environment(self, env):
"""When using %clang, add only its ld.lld-$ver and/or ld.lld to our PATH"""
if self.compiler.name in ["clang", "apple-clang"]:
for lld in "ld.lld-{0}".format(self.compiler.version.version[0]), "ld.lld":
@@ -528,7 +528,7 @@ class Llvm(CMakePackage, CudaPackage):
os.symlink(bin, sym)
env.prepend_path("PATH", self.stage.path)
def setup_run_environment(self, env: EnvironmentModifications) -> None:
def setup_run_environment(self, env):
if "+clang" in self.spec:
env.set("CC", join_path(self.spec.prefix.bin, "clang"))
env.set("CXX", join_path(self.spec.prefix.bin, "clang++"))

View File

@@ -318,7 +318,7 @@ class Mfem(Package, CudaPackage, ROCmPackage):
patch('mfem-4.0.0-makefile-syntax-fix.patch', when='@4.0.0')
phases = ['configure', 'build', 'install']
def setup_build_environment(self, env: EnvironmentModifications) -> None:
def setup_build_environment(self, env):
env.unset('MFEM_DIR')
env.unset('MFEM_BUILD_DIR')

View File

@@ -281,7 +281,7 @@ class PyTorch(PythonPackage, CudaPackage):
"caffe2/CMakeLists.txt",
)
def setup_build_environment(self, env: EnvironmentModifications) -> None:
def setup_build_environment(self, env):
"""Set environment variables used to control the build.
PyTorch's ``setup.py`` is a thin wrapper around ``cmake``.

View File

@@ -440,7 +440,7 @@ class Trilinos(CMakePackage, CudaPackage):
url = "https://github.com/trilinos/Trilinos/archive/trilinos-release-{0}.tar.gz"
return url.format(version.dashed)
def setup_dependent_run_environment(self, env: EnvironmentModifications, dependent_spec: Spec) -> None:
def setup_dependent_run_environment(self, env, dependent_spec):
if "+cuda" in self.spec:
# currently Trilinos doesn't perform the memory fence so
# it relies on blocking CUDA kernel launch. This is needed
@@ -453,7 +453,7 @@ class Trilinos(CMakePackage, CudaPackage):
else:
self.spec.kokkos_cxx = spack_cxx
def setup_build_environment(self, env: EnvironmentModifications) -> None:
def setup_build_environment(self, env):
spec = self.spec
if "+cuda" in spec and "+wrapper" in spec:
if "+mpi" in spec:
@@ -847,7 +847,7 @@ class Trilinos(CMakePackage, CudaPackage):
)
filter_file(r"-lpytrilinos", "", "%s/Makefile.export.Trilinos" % self.prefix.include)
def setup_run_environment(self, env: EnvironmentModifications) -> None:
def setup_run_environment(self, env):
if "+exodus" in self.spec:
env.prepend_path("PYTHONPATH", self.prefix.lib)

View File

@@ -5,7 +5,6 @@
import contextlib
import datetime
import functools
import gzip
import json
import os
import pathlib
@@ -33,7 +32,6 @@
import spack.database
import spack.deptypes as dt
import spack.package_base
import spack.paths
import spack.repo
import spack.spec
import spack.store
@@ -1245,26 +1243,3 @@ def test_query_with_predicate_fn(database):
specs = database.query(predicate_fn=lambda x: not spack.repo.PATH.exists(x.spec.name))
assert not specs
@pytest.mark.regression("49964")
def test_querying_reindexed_database_specfilev5(tmp_path):
"""Tests that we can query a reindexed database from before compilers as dependencies,
and get appropriate results for %<compiler> and similar selections.
"""
test_path = pathlib.Path(spack.paths.test_path)
zipfile = test_path / "data" / "database" / "index.json.v7_v8.json.gz"
with gzip.open(str(zipfile), "rt", encoding="utf-8") as f:
data = json.load(f)
index_json = tmp_path / spack.database._DB_DIRNAME / spack.database.INDEX_JSON_FILE
index_json.parent.mkdir(parents=True)
index_json.write_text(json.dumps(data))
db = spack.database.Database(str(tmp_path))
specs = db.query("%gcc")
assert len(specs) == 8
assert len([x for x in specs if x.external]) == 2
assert len([x for x in specs if x.original_spec_format() < 5]) == 8

View File

@@ -20,7 +20,7 @@
SpackEnvironmentViewError,
_error_on_nonempty_view_dir,
)
from spack.environment.list import UndefinedReferenceError
from spack.spec_list import UndefinedReferenceError
pytestmark = pytest.mark.not_on_windows("Envs are not supported on windows")
@@ -107,8 +107,7 @@ def test_env_change_spec_in_definition(tmp_path, mock_packages, mutable_mock_env
assert any(x.intersects("mpileaks@2.1%gcc") for x in e.user_specs)
with e:
e.change_existing_spec(spack.spec.Spec("mpileaks@2.2"), list_name="desired_specs")
e.change_existing_spec(spack.spec.Spec("mpileaks@2.2"), list_name="desired_specs")
e.write()
# Ensure changed specs are in memory
@@ -777,8 +776,10 @@ def test_env_with_include_def_missing(mutable_mock_env_path, mock_packages):
"""
)
with pytest.raises(UndefinedReferenceError, match=r"which is not defined"):
_ = ev.Environment(env_path)
e = ev.Environment(env_path)
with e:
with pytest.raises(UndefinedReferenceError, match=r"which does not appear"):
e.concretize()
@pytest.mark.regression("41292")

Some files were not shown because too many files have changed in this diff Show More