Compare commits
127 Commits
devtools-r
...
package/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08aaecd1e2 | ||
|
|
b237d303df | ||
|
|
c3015d2a1b | ||
|
|
ab60bfe36a | ||
|
|
8bcc3e2820 | ||
|
|
388f141a92 | ||
|
|
f74b083a15 | ||
|
|
5b9d260054 | ||
|
|
4bd47d89db | ||
|
|
96f3c76052 | ||
|
|
9c74eda61f | ||
|
|
d9de93a0fc | ||
|
|
3892fadbf6 | ||
|
|
62b32080a8 | ||
|
|
09d66168c4 | ||
|
|
d7869da36b | ||
|
|
b6864fb1c3 | ||
|
|
e6125061e1 | ||
|
|
491bd48897 | ||
|
|
ad4878f770 | ||
|
|
420eff11b7 | ||
|
|
15e7aaf94d | ||
|
|
bd6c5ec82d | ||
|
|
4e171453c0 | ||
|
|
420bce5cd2 | ||
|
|
b4f6c49bc0 | ||
|
|
da4f2776d2 | ||
|
|
e2f274a634 | ||
|
|
15dcd3c65c | ||
|
|
49c2894def | ||
|
|
1ae37f6720 | ||
|
|
15f6368c7f | ||
|
|
57b63228ce | ||
|
|
13abfb7013 | ||
|
|
b41fc1ec79 | ||
|
|
124e41da23 | ||
|
|
f6039d1d45 | ||
|
|
8871bd5ba5 | ||
|
|
efe85755d8 | ||
|
|
7aaa17856d | ||
|
|
fbf02b561a | ||
|
|
4027a2139b | ||
|
|
f0ced1af42 | ||
|
|
2e45edf4e3 | ||
|
|
4bcfb01566 | ||
|
|
b8bb8a70ce | ||
|
|
dd2b436b5a | ||
|
|
da2cc2351c | ||
|
|
383ec19a0c | ||
|
|
45f8a0e42c | ||
|
|
4636a7f14f | ||
|
|
38f3f57a54 | ||
|
|
b17d7cd0e6 | ||
|
|
b5e2f23b6c | ||
|
|
7a4df732e1 | ||
|
|
7e6aaf9458 | ||
|
|
2d35d29e0f | ||
|
|
1baed0d833 | ||
|
|
cadc2a1aa5 | ||
|
|
78449ba92b | ||
|
|
26d6bfbb7f | ||
|
|
3405fe60f1 | ||
|
|
53c266b161 | ||
|
|
ed8ecc469e | ||
|
|
b2840acd52 | ||
|
|
c35250b313 | ||
|
|
e114853115 | ||
|
|
89fc9a9d47 | ||
|
|
afc693645a | ||
|
|
4ac0e511ad | ||
|
|
b0355d6cc0 | ||
|
|
300d53d6f8 | ||
|
|
0b344e0fd3 | ||
|
|
15adb308bf | ||
|
|
050d565375 | ||
|
|
f6ef2c254e | ||
|
|
62c27b1924 | ||
|
|
2ff0766aa4 | ||
|
|
dc245e87f9 | ||
|
|
c1f134e2a0 | ||
|
|
391940d2eb | ||
|
|
8c061e51e3 | ||
|
|
5774df6b7a | ||
|
|
3a5c1eb5f3 | ||
|
|
3a2ec729f7 | ||
|
|
a093f4a8ce | ||
|
|
b8302a8277 | ||
|
|
32f319157d | ||
|
|
75dfad8788 | ||
|
|
f3ba20db26 | ||
|
|
6301edbd5d | ||
|
|
c232bf435a | ||
|
|
f3537bc66b | ||
|
|
4004f27bc0 | ||
|
|
910190f55b | ||
|
|
4ce80b95f3 | ||
|
|
8f1f9048ec | ||
|
|
e7372a54a1 | ||
|
|
5074b7e922 | ||
|
|
461eb944bd | ||
|
|
4700108b5b | ||
|
|
3384181868 | ||
|
|
f0f6e54b29 | ||
|
|
a2f00886e9 | ||
|
|
1235084c20 | ||
|
|
b5538960c3 | ||
|
|
d3d82e8d6b | ||
|
|
17a9198c78 | ||
|
|
c6c689be28 | ||
|
|
ab563c09d2 | ||
|
|
abdac36fd5 | ||
|
|
b8a18f0a78 | ||
|
|
17656b2ea0 | ||
|
|
3c641c8509 | ||
|
|
141c7de5d8 | ||
|
|
f6b23b4653 | ||
|
|
4755b28398 | ||
|
|
c9dfb9b0fd | ||
|
|
5a67c578b7 | ||
|
|
e47be18acb | ||
|
|
6593d22c4e | ||
|
|
f51dad976e | ||
|
|
ff8cd597e0 | ||
|
|
fd22d109a6 | ||
|
|
88ee3a0fba | ||
|
|
f50377de7f | ||
|
|
8e96d3a051 |
46
.github/workflows/build-containers.yml
vendored
46
.github/workflows/build-containers.yml
vendored
@@ -38,12 +38,11 @@ jobs:
|
||||
# Meaning of the various items in the matrix list
|
||||
# 0: Container name (e.g. ubuntu-bionic)
|
||||
# 1: Platforms to build for
|
||||
# 2: Base image (e.g. ubuntu:18.04)
|
||||
# 2: Base image (e.g. ubuntu:22.04)
|
||||
dockerfile: [[amazon-linux, 'linux/amd64,linux/arm64', 'amazonlinux:2'],
|
||||
[centos7, 'linux/amd64,linux/arm64,linux/ppc64le', 'centos:7'],
|
||||
[centos-stream, 'linux/amd64,linux/arm64,linux/ppc64le', 'centos:stream'],
|
||||
[leap15, 'linux/amd64,linux/arm64,linux/ppc64le', 'opensuse/leap:15'],
|
||||
[ubuntu-bionic, 'linux/amd64,linux/arm64,linux/ppc64le', 'ubuntu:18.04'],
|
||||
[ubuntu-focal, 'linux/amd64,linux/arm64,linux/ppc64le', 'ubuntu:20.04'],
|
||||
[ubuntu-jammy, 'linux/amd64,linux/arm64,linux/ppc64le', 'ubuntu:22.04'],
|
||||
[almalinux8, 'linux/amd64,linux/arm64,linux/ppc64le', 'almalinux:8'],
|
||||
@@ -58,18 +57,20 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
||||
|
||||
- name: Set Container Tag Normal (Nightly)
|
||||
run: |
|
||||
container="${{ matrix.dockerfile[0] }}:latest"
|
||||
echo "container=${container}" >> $GITHUB_ENV
|
||||
echo "versioned=${container}" >> $GITHUB_ENV
|
||||
|
||||
# On a new release create a container with the same tag as the release.
|
||||
- name: Set Container Tag on Release
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
versioned="${{matrix.dockerfile[0]}}:${GITHUB_REF##*/}"
|
||||
echo "versioned=${versioned}" >> $GITHUB_ENV
|
||||
- uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934
|
||||
id: docker_meta
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ github.repository_owner }}/${{ matrix.dockerfile[0] }}
|
||||
${{ github.repository_owner }}/${{ matrix.dockerfile[0] }}
|
||||
tags: |
|
||||
type=schedule,pattern=nightly
|
||||
type=schedule,pattern=develop
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
|
||||
- name: Generate the Dockerfile
|
||||
env:
|
||||
@@ -92,13 +93,13 @@ jobs:
|
||||
path: dockerfiles
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # @v1
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # @v1
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # @v1
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -106,21 +107,18 @@ jobs:
|
||||
|
||||
- name: Log in to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # @v1
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build & Deploy ${{ matrix.dockerfile[0] }}
|
||||
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # @v2
|
||||
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09
|
||||
with:
|
||||
context: dockerfiles/${{ matrix.dockerfile[0] }}
|
||||
platforms: ${{ matrix.dockerfile[1] }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: |
|
||||
spack/${{ env.container }}
|
||||
spack/${{ env.versioned }}
|
||||
ghcr.io/spack/${{ env.container }}
|
||||
ghcr.io/spack/${{ env.versioned }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
|
||||
2
.github/workflows/style/requirements.txt
vendored
2
.github/workflows/style/requirements.txt
vendored
@@ -1,4 +1,4 @@
|
||||
black==23.10.1
|
||||
black==23.11.0
|
||||
clingo==5.6.2
|
||||
flake8==6.1.0
|
||||
isort==5.12.0
|
||||
|
||||
@@ -229,3 +229,11 @@ config:
|
||||
flags:
|
||||
# Whether to keep -Werror flags active in package builds.
|
||||
keep_werror: 'none'
|
||||
|
||||
# A mapping of aliases that can be used to define new commands. For instance,
|
||||
# `sp: spec -I` will define a new command `sp` that will execute `spec` with
|
||||
# the `-I` argument. Aliases cannot override existing commands.
|
||||
aliases:
|
||||
concretise: concretize
|
||||
containerise: containerize
|
||||
rm: remove
|
||||
|
||||
@@ -155,6 +155,33 @@ List of popular build caches
|
||||
|
||||
* `Extreme-scale Scientific Software Stack (E4S) <https://e4s-project.github.io/>`_: `build cache <https://oaciss.uoregon.edu/e4s/inventory.html>`_
|
||||
|
||||
----------
|
||||
Relocation
|
||||
----------
|
||||
|
||||
When using buildcaches across different machines, it is likely that the install
|
||||
root will be different from the one used to build the binaries.
|
||||
|
||||
To address this issue, Spack automatically relocates all paths encoded in binaries
|
||||
and scripts to their new location upon install.
|
||||
|
||||
Note that there are some cases where this is not possible: if binaries are built in
|
||||
a relatively short path, and then installed to a longer path, there may not be enough
|
||||
space in the binary to encode the new path. In this case, Spack will fail to install
|
||||
the package from the build cache, and a source build is required.
|
||||
|
||||
To reduce the likelihood of this happening, it is highly recommended to add padding to
|
||||
the install root during the build, as specified in the :ref:`config <config-yaml>`
|
||||
section of the configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
config:
|
||||
install_tree:
|
||||
root: /opt/spack
|
||||
padded_length: 128
|
||||
|
||||
|
||||
|
||||
-----------------------------------------
|
||||
OCI / Docker V2 registries as build cache
|
||||
@@ -216,29 +243,34 @@ other system dependencies. However, they are still compatible with tools like
|
||||
are `alternative drivers <https://docs.docker.com/storage/storagedriver/>`_.
|
||||
|
||||
------------------------------------
|
||||
Using a buildcache in GitHub Actions
|
||||
Spack build cache for GitHub Actions
|
||||
------------------------------------
|
||||
|
||||
GitHub Actions is a popular CI/CD platform for building and testing software,
|
||||
but each CI job has limited resources, making from source builds too slow for
|
||||
many applications. Spack build caches can be used to share binaries between CI
|
||||
runs, speeding up CI significantly.
|
||||
To significantly speed up Spack in GitHub Actions, binaries can be cached in
|
||||
GitHub Packages. This service is an OCI registry that can be linked to a GitHub
|
||||
repository.
|
||||
|
||||
A typical workflow is to include a ``spack.yaml`` environment in your repository
|
||||
that specifies the packages to install:
|
||||
that specifies the packages to install, the target architecture, and the build
|
||||
cache to use under ``mirrors``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
specs: [pkg-x, pkg-y]
|
||||
packages:
|
||||
all:
|
||||
require: target=x86_64_v2
|
||||
mirrors:
|
||||
github_packages: oci://ghcr.io/<user>/<repo>
|
||||
specs:
|
||||
- python@3.11
|
||||
config:
|
||||
install_tree:
|
||||
root: /opt/spack
|
||||
padded_length: 128
|
||||
packages:
|
||||
all:
|
||||
require: target=x86_64_v2
|
||||
mirrors:
|
||||
local-buildcache: oci://ghcr.io/<organization>/<repository>
|
||||
|
||||
And a GitHub action that sets up Spack, installs packages from the build cache
|
||||
or from sources, and pushes newly built binaries to the build cache:
|
||||
A GitHub action can then be used to install the packages and push them to the
|
||||
build cache:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@@ -252,26 +284,35 @@ or from sources, and pushes newly built binaries to the build cache:
|
||||
jobs:
|
||||
example:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Spack
|
||||
run: |
|
||||
git clone --depth=1 https://github.com/spack/spack.git
|
||||
echo "$PWD/spack/bin/" >> "$GITHUB_PATH"
|
||||
- name: Checkout Spack
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: spack/spack
|
||||
path: spack
|
||||
|
||||
- name: Setup Spack
|
||||
run: echo "$PWD/spack/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Concretize
|
||||
run: spack -e . concretize
|
||||
|
||||
- name: Install
|
||||
run: spack -e . install --no-check-signature --fail-fast
|
||||
run: spack -e . install --no-check-signature
|
||||
|
||||
- name: Run tests
|
||||
run: ./my_view/bin/python3 -c 'print("hello world")'
|
||||
|
||||
- name: Push to buildcache
|
||||
run: |
|
||||
spack -e . mirror set --oci-username <user> --oci-password "${{ secrets.GITHUB_TOKEN }}" github_packages
|
||||
spack -e . buildcache push --base-image ubuntu:22.04 --unsigned --update-index github_packages
|
||||
if: always()
|
||||
spack -e . mirror set --oci-username ${{ github.actor }} --oci-password "${{ secrets.GITHUB_TOKEN }}" local-buildcache
|
||||
spack -e . buildcache push --base-image ubuntu:22.04 --unsigned --update-index local-buildcache
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
The first time this action runs, it will build the packages from source and
|
||||
push them to the build cache. Subsequent runs will pull the binaries from the
|
||||
@@ -281,15 +322,15 @@ over source builds.
|
||||
The build cache entries appear in the GitHub Packages section of your repository,
|
||||
and contain instructions for pulling and running them with ``docker`` or ``podman``.
|
||||
|
||||
----------
|
||||
Relocation
|
||||
----------
|
||||
|
||||
Initial build and later installation do not necessarily happen at the same
|
||||
location. Spack provides a relocation capability and corrects for RPATHs and
|
||||
non-relocatable scripts. However, many packages compile paths into binary
|
||||
artifacts directly. In such cases, the build instructions of this package would
|
||||
need to be adjusted for better re-locatability.
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Using Spack's public build cache for GitHub Actions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Spack offers a public build cache for GitHub Actions with a set of common packages,
|
||||
which lets you get started quickly. See the following resources for more information:
|
||||
|
||||
* `spack/github-actions-buildcache <https://github.com/spack/github-actions-buildcache>`_
|
||||
|
||||
.. _cmd-spack-buildcache:
|
||||
|
||||
|
||||
@@ -526,56 +526,52 @@ Package Preferences
|
||||
In some cases package requirements can be too strong, and package
|
||||
preferences are the better option. Package preferences do not impose
|
||||
constraints on packages for particular versions or variants values,
|
||||
they rather only set defaults -- the concretizer is free to change
|
||||
them if it must due to other constraints. Also note that package
|
||||
preferences are of lower priority than reuse of already installed
|
||||
packages.
|
||||
they rather only set defaults. The concretizer is free to change
|
||||
them if it must, due to other constraints, and also prefers reusing
|
||||
installed packages over building new ones that are a better match for
|
||||
preferences.
|
||||
|
||||
Here's an example ``packages.yaml`` file that sets preferred packages:
|
||||
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]
|
||||
|
||||
These preferences override Spack's default and effectively reorder priorities
|
||||
when looking for the best compiler, target or virtual package provider. Each
|
||||
preference takes an ordered list of spec constraints, with earlier entries in
|
||||
the list being preferred over later entries.
|
||||
|
||||
In the example above all packages prefer to be compiled with ``gcc@12.2.0``,
|
||||
to target the ``x86_64_v3`` microarchitecture and to use ``mvapich2`` if they
|
||||
depend on ``mpi``.
|
||||
|
||||
The ``variants`` and ``version`` preferences can be set under
|
||||
package specific sections of the ``packages.yaml`` file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
opencv:
|
||||
compiler: [gcc@4.9]
|
||||
variants: +debug
|
||||
gperftools:
|
||||
version: [2.2, 2.4, 2.3]
|
||||
all:
|
||||
compiler: [gcc@4.4.7, 'gcc@4.6:', intel, clang, pgi]
|
||||
target: [sandybridge]
|
||||
providers:
|
||||
mpi: [mvapich2, mpich, openmpi]
|
||||
|
||||
At a high level, this example is specifying how packages are preferably
|
||||
concretized. The opencv package should prefer using GCC 4.9 and
|
||||
be built with debug options. The gperftools package should prefer version
|
||||
2.2 over 2.4. Every package on the system should prefer mvapich2 for
|
||||
its MPI and GCC 4.4.7 (except for opencv, which overrides this by preferring GCC 4.9).
|
||||
These options are used to fill in implicit defaults. Any of them can be overwritten
|
||||
on the command line if explicitly requested.
|
||||
In this case, the preference for ``opencv`` is to build with debug options, while
|
||||
``gperftools`` prefers version 2.2 over 2.4.
|
||||
|
||||
Package preferences accept the follow keys or components under
|
||||
the specific package (or ``all``) section: ``compiler``, ``variants``,
|
||||
``version``, ``providers``, and ``target``. Each component has an
|
||||
ordered list of spec ``constraints``, with earlier entries in the
|
||||
list being preferred over later entries.
|
||||
Any preference can be overwritten on the command line if explicitly requested.
|
||||
|
||||
Sometimes a package installation may have constraints that forbid
|
||||
the first concretization rule, in which case Spack will use the first
|
||||
legal concretization rule. Going back to the example, if a user
|
||||
requests gperftools 2.3 or later, then Spack will install version 2.4
|
||||
as the 2.4 version of gperftools is preferred over 2.3.
|
||||
|
||||
An explicit concretization rule in the preferred section will always
|
||||
take preference over unlisted concretizations. In the above example,
|
||||
xlc isn't listed in the compiler list. Every listed compiler from
|
||||
gcc to pgi will thus be preferred over the xlc compiler.
|
||||
|
||||
The syntax for the ``provider`` section differs slightly from other
|
||||
concretization rules. A provider lists a value that packages may
|
||||
``depends_on`` (e.g, MPI) and a list of rules for fulfilling that
|
||||
dependency.
|
||||
Preferences cannot overcome explicit constraints, as they only set a preferred
|
||||
ordering among homogeneous attribute values. Going back to the example, if
|
||||
``gperftools@2.3:`` was requested, then Spack will install version 2.4
|
||||
since the most preferred version 2.2 is prohibited by the version constraint.
|
||||
|
||||
.. _package_permissions:
|
||||
|
||||
|
||||
@@ -304,3 +304,17 @@ To work properly, this requires your terminal to reset its title after
|
||||
Spack has finished its work, otherwise Spack's status information will
|
||||
remain in the terminal's title indefinitely. Most terminals should already
|
||||
be set up this way and clear Spack's status information.
|
||||
|
||||
-----------
|
||||
``aliases``
|
||||
-----------
|
||||
|
||||
Aliases can be used to define new Spack commands. They can be either shortcuts
|
||||
for longer commands or include specific arguments for convenience. For instance,
|
||||
if users want to use ``spack install``'s ``-v`` argument all the time, they can
|
||||
create a new alias called ``inst`` that will always call ``install -v``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
aliases:
|
||||
inst: install -v
|
||||
|
||||
@@ -243,9 +243,11 @@ lower-precedence settings. Completely ignoring higher-level configuration
|
||||
options is supported with the ``::`` notation for keys (see
|
||||
:ref:`config-overrides` below).
|
||||
|
||||
There are also special notations for string concatenation and precendense override.
|
||||
Using the ``+:`` notation can be used to force *prepending* strings or lists. For lists, this is identical
|
||||
to the default behavior. Using the ``-:`` works similarly, but for *appending* values.
|
||||
There are also special notations for string concatenation and precendense override:
|
||||
|
||||
* ``+:`` will force *prepending* strings or lists. For lists, this is the default behavior.
|
||||
* ``-:`` works similarly, but for *appending* values.
|
||||
|
||||
:ref:`config-prepend-append`
|
||||
|
||||
^^^^^^^^^^^
|
||||
|
||||
@@ -519,11 +519,11 @@ inspections and customize them per-module-set.
|
||||
|
||||
modules:
|
||||
prefix_inspections:
|
||||
bin:
|
||||
./bin:
|
||||
- PATH
|
||||
man:
|
||||
./man:
|
||||
- MANPATH
|
||||
'':
|
||||
./:
|
||||
- CMAKE_PREFIX_PATH
|
||||
|
||||
Prefix inspections are only applied if the relative path inside the
|
||||
@@ -579,7 +579,7 @@ the view.
|
||||
view_relative_modules:
|
||||
use_view: my_view
|
||||
prefix_inspections:
|
||||
bin:
|
||||
./bin:
|
||||
- PATH
|
||||
view:
|
||||
my_view:
|
||||
|
||||
@@ -3503,6 +3503,56 @@ is equivalent to:
|
||||
Constraints from nested context managers are also combined together, but they are rarely
|
||||
needed or recommended.
|
||||
|
||||
.. _default_args:
|
||||
|
||||
------------------------
|
||||
Common default arguments
|
||||
------------------------
|
||||
|
||||
Similarly, if directives have a common set of default arguments, you can
|
||||
group them together in a ``with default_args()`` block:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PyExample(PythonPackage):
|
||||
|
||||
with default_args(type=("build", "run")):
|
||||
depends_on("py-foo")
|
||||
depends_on("py-foo@2:", when="@2:")
|
||||
depends_on("py-bar")
|
||||
depends_on("py-bz")
|
||||
|
||||
The above is short for:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PyExample(PythonPackage):
|
||||
|
||||
depends_on("py-foo", type=("build", "run"))
|
||||
depends_on("py-foo@2:", when="@2:", type=("build", "run"))
|
||||
depends_on("py-bar", type=("build", "run"))
|
||||
depends_on("py-bz", type=("build", "run"))
|
||||
|
||||
.. note::
|
||||
|
||||
The ``with when()`` context manager is composable, while ``with default_args()``
|
||||
merely overrides the default. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with default_args(when="+feature"):
|
||||
depends_on("foo")
|
||||
depends_on("bar")
|
||||
depends_on("baz", when="+baz")
|
||||
|
||||
is equivalent to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
depends_on("foo", when="+feature")
|
||||
depends_on("bar", when="+feature")
|
||||
depends_on("baz", when="+baz") # Note: not when="+feature+baz"
|
||||
|
||||
.. _install-method:
|
||||
|
||||
------------------
|
||||
|
||||
@@ -8,6 +8,6 @@ pygments==2.16.1
|
||||
urllib3==2.0.7
|
||||
pytest==7.4.3
|
||||
isort==5.12.0
|
||||
black==23.10.1
|
||||
black==23.11.0
|
||||
flake8==6.1.0
|
||||
mypy==1.6.1
|
||||
mypy==1.7.0
|
||||
|
||||
2
lib/spack/external/__init__.py
vendored
2
lib/spack/external/__init__.py
vendored
@@ -18,7 +18,7 @@
|
||||
|
||||
* Homepage: https://pypi.python.org/pypi/archspec
|
||||
* Usage: Labeling, comparison and detection of microarchitectures
|
||||
* Version: 0.2.1 (commit df43a1834460bf94516136951c4729a3100603ec)
|
||||
* Version: 0.2.2 (commit 1dc58a5776dd77e6fc6e4ba5626af5b1fb24996e)
|
||||
|
||||
astunparse
|
||||
----------------
|
||||
|
||||
2
lib/spack/external/archspec/__init__.py
vendored
2
lib/spack/external/archspec/__init__.py
vendored
@@ -1,2 +1,2 @@
|
||||
"""Init file to avoid namespace packages"""
|
||||
__version__ = "0.2.1"
|
||||
__version__ = "0.2.2"
|
||||
|
||||
@@ -2318,6 +2318,26 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"power10": {
|
||||
"from": ["power9"],
|
||||
"vendor": "IBM",
|
||||
"generation": 10,
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "11.1:",
|
||||
"flags": "-mcpu={name} -mtune={name}"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": "11.0:",
|
||||
"flags": "-mcpu={name} -mtune={name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ppc64le": {
|
||||
"from": [],
|
||||
"vendor": "generic",
|
||||
@@ -2405,6 +2425,29 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"power10le": {
|
||||
"from": ["power9le"],
|
||||
"vendor": "IBM",
|
||||
"generation": 10,
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"name": "power10",
|
||||
"versions": "11.1:",
|
||||
"flags": "-mcpu={name} -mtune={name}"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": "11.0:",
|
||||
"family": "ppc64le",
|
||||
"name": "power10",
|
||||
"flags": "-mcpu={name} -mtune={name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"aarch64": {
|
||||
"from": [],
|
||||
"vendor": "generic",
|
||||
@@ -2592,6 +2635,37 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"armv9.0a": {
|
||||
"from": ["armv8.5a"],
|
||||
"vendor": "generic",
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "12:",
|
||||
"flags": "-march=armv9-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": "14:",
|
||||
"flags": "-march=armv9-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"apple-clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv9-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"arm": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv9-a -mtune=generic"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"thunderx2": {
|
||||
"from": ["armv8.1a"],
|
||||
"vendor": "Cavium",
|
||||
@@ -2813,8 +2887,12 @@
|
||||
],
|
||||
"arm" : [
|
||||
{
|
||||
"versions": "20:",
|
||||
"versions": "20:21.9",
|
||||
"flags" : "-march=armv8.2-a+fp16+rcpc+dotprod+crypto"
|
||||
},
|
||||
{
|
||||
"versions": "22:",
|
||||
"flags" : "-mcpu=neoverse-n1"
|
||||
}
|
||||
],
|
||||
"nvhpc" : [
|
||||
@@ -2942,7 +3020,7 @@
|
||||
},
|
||||
{
|
||||
"versions": "22:",
|
||||
"flags" : "-march=armv8.4-a+sve+ssbs+fp16+bf16+crypto+i8mm+rng"
|
||||
"flags" : "-mcpu=neoverse-v1"
|
||||
}
|
||||
],
|
||||
"nvhpc" : [
|
||||
@@ -2954,6 +3032,126 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"neoverse_v2": {
|
||||
"from": ["neoverse_n1", "armv9.0a"],
|
||||
"vendor": "ARM",
|
||||
"features": [
|
||||
"fp",
|
||||
"asimd",
|
||||
"evtstrm",
|
||||
"aes",
|
||||
"pmull",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"crc32",
|
||||
"atomics",
|
||||
"fphp",
|
||||
"asimdhp",
|
||||
"cpuid",
|
||||
"asimdrdm",
|
||||
"jscvt",
|
||||
"fcma",
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"sm3",
|
||||
"sm4",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"sve",
|
||||
"asimdfhm",
|
||||
"dit",
|
||||
"uscat",
|
||||
"ilrcpc",
|
||||
"flagm",
|
||||
"ssbs",
|
||||
"sb",
|
||||
"paca",
|
||||
"pacg",
|
||||
"dcpodp",
|
||||
"sve2",
|
||||
"sveaes",
|
||||
"svepmull",
|
||||
"svebitperm",
|
||||
"svesha3",
|
||||
"svesm4",
|
||||
"flagm2",
|
||||
"frint",
|
||||
"svei8mm",
|
||||
"svebf16",
|
||||
"i8mm",
|
||||
"bf16",
|
||||
"dgh",
|
||||
"bti"
|
||||
],
|
||||
"compilers" : {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "4.8:5.99",
|
||||
"flags": "-march=armv8-a"
|
||||
},
|
||||
{
|
||||
"versions": "6:6.99",
|
||||
"flags" : "-march=armv8.1-a"
|
||||
},
|
||||
{
|
||||
"versions": "7.0:7.99",
|
||||
"flags" : "-march=armv8.2-a -mtune=cortex-a72"
|
||||
},
|
||||
{
|
||||
"versions": "8.0:8.99",
|
||||
"flags" : "-march=armv8.4-a+sve -mtune=cortex-a72"
|
||||
},
|
||||
{
|
||||
"versions": "9.0:9.99",
|
||||
"flags" : "-march=armv8.5-a+sve -mtune=cortex-a76"
|
||||
},
|
||||
{
|
||||
"versions": "10.0:11.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16 -mtune=cortex-a77"
|
||||
},
|
||||
{
|
||||
"versions": "12.0:12.99",
|
||||
"flags" : "-march=armv9-a+i8mm+bf16 -mtune=cortex-a710"
|
||||
},
|
||||
{
|
||||
"versions": "13.0:",
|
||||
"flags" : "-mcpu=neoverse-v2"
|
||||
}
|
||||
],
|
||||
"clang" : [
|
||||
{
|
||||
"versions": "9.0:10.99",
|
||||
"flags" : "-march=armv8.5-a+sve"
|
||||
},
|
||||
{
|
||||
"versions": "11.0:13.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16"
|
||||
},
|
||||
{
|
||||
"versions": "14.0:15.99",
|
||||
"flags" : "-march=armv9-a+i8mm+bf16"
|
||||
},
|
||||
{
|
||||
"versions": "16.0:",
|
||||
"flags" : "-mcpu=neoverse-v2"
|
||||
}
|
||||
],
|
||||
"arm" : [
|
||||
{
|
||||
"versions": "23.04.0:",
|
||||
"flags" : "-mcpu=neoverse-v2"
|
||||
}
|
||||
],
|
||||
"nvhpc" : [
|
||||
{
|
||||
"versions": "23.3:",
|
||||
"name": "neoverse-v2",
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"m1": {
|
||||
"from": ["armv8.4a"],
|
||||
"vendor": "Apple",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
#: PEP440 canonical <major>.<minor>.<micro>.<devN> string
|
||||
__version__ = "0.21.0.dev0"
|
||||
__version__ = "0.22.0.dev0"
|
||||
spack_version = __version__
|
||||
|
||||
|
||||
|
||||
@@ -66,8 +66,9 @@
|
||||
from spack.stage import Stage
|
||||
from spack.util.executable import which
|
||||
|
||||
_build_cache_relative_path = "build_cache"
|
||||
_build_cache_keys_relative_path = "_pgp"
|
||||
BUILD_CACHE_RELATIVE_PATH = "build_cache"
|
||||
BUILD_CACHE_KEYS_RELATIVE_PATH = "_pgp"
|
||||
CURRENT_BUILD_CACHE_LAYOUT_VERSION = 1
|
||||
|
||||
|
||||
class BuildCacheDatabase(spack_db.Database):
|
||||
@@ -481,7 +482,7 @@ def _fetch_and_cache_index(self, mirror_url, cache_entry={}):
|
||||
scheme = urllib.parse.urlparse(mirror_url).scheme
|
||||
|
||||
if scheme != "oci" and not web_util.url_exists(
|
||||
url_util.join(mirror_url, _build_cache_relative_path, "index.json")
|
||||
url_util.join(mirror_url, BUILD_CACHE_RELATIVE_PATH, "index.json")
|
||||
):
|
||||
return False
|
||||
|
||||
@@ -600,6 +601,10 @@ def __init__(self, msg):
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class InvalidMetadataFile(spack.error.SpackError):
|
||||
pass
|
||||
|
||||
|
||||
class UnsignedPackageException(spack.error.SpackError):
|
||||
"""
|
||||
Raised if installation of unsigned package is attempted without
|
||||
@@ -614,11 +619,11 @@ def compute_hash(data):
|
||||
|
||||
|
||||
def build_cache_relative_path():
|
||||
return _build_cache_relative_path
|
||||
return BUILD_CACHE_RELATIVE_PATH
|
||||
|
||||
|
||||
def build_cache_keys_relative_path():
|
||||
return _build_cache_keys_relative_path
|
||||
return BUILD_CACHE_KEYS_RELATIVE_PATH
|
||||
|
||||
|
||||
def build_cache_prefix(prefix):
|
||||
@@ -1401,7 +1406,7 @@ def _build_tarball_in_stage_dir(spec: Spec, out_url: str, stage_dir: str, option
|
||||
spec_dict = sjson.load(content)
|
||||
else:
|
||||
raise ValueError("{0} not a valid spec file type".format(spec_file))
|
||||
spec_dict["buildcache_layout_version"] = 1
|
||||
spec_dict["buildcache_layout_version"] = CURRENT_BUILD_CACHE_LAYOUT_VERSION
|
||||
spec_dict["binary_cache_checksum"] = {"hash_algorithm": "sha256", "hash": checksum}
|
||||
|
||||
with open(specfile_path, "w") as outfile:
|
||||
@@ -1560,6 +1565,42 @@ def _delete_staged_downloads(download_result):
|
||||
download_result["specfile_stage"].destroy()
|
||||
|
||||
|
||||
def _get_valid_spec_file(path: str, max_supported_layout: int) -> Tuple[Dict, int]:
|
||||
"""Read and validate a spec file, returning the spec dict with its layout version, or raising
|
||||
InvalidMetadataFile if invalid."""
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
binary_content = f.read()
|
||||
except OSError:
|
||||
raise InvalidMetadataFile(f"No such file: {path}")
|
||||
|
||||
# In the future we may support transparently decompressing compressed spec files.
|
||||
if binary_content[:2] == b"\x1f\x8b":
|
||||
raise InvalidMetadataFile("Compressed spec files are not supported")
|
||||
|
||||
try:
|
||||
as_string = binary_content.decode("utf-8")
|
||||
if path.endswith(".json.sig"):
|
||||
spec_dict = Spec.extract_json_from_clearsig(as_string)
|
||||
else:
|
||||
spec_dict = json.loads(as_string)
|
||||
except Exception as e:
|
||||
raise InvalidMetadataFile(f"Could not parse {path} due to: {e}") from e
|
||||
|
||||
# Ensure this version is not too new.
|
||||
try:
|
||||
layout_version = int(spec_dict.get("buildcache_layout_version", 0))
|
||||
except ValueError as e:
|
||||
raise InvalidMetadataFile("Could not parse layout version") from e
|
||||
|
||||
if layout_version > max_supported_layout:
|
||||
raise InvalidMetadataFile(
|
||||
f"Layout version {layout_version} is too new for this version of Spack"
|
||||
)
|
||||
|
||||
return spec_dict, layout_version
|
||||
|
||||
|
||||
def download_tarball(spec, unsigned=False, mirrors_for_spec=None):
|
||||
"""
|
||||
Download binary tarball for given package into stage area, returning
|
||||
@@ -1652,6 +1693,18 @@ def download_tarball(spec, unsigned=False, mirrors_for_spec=None):
|
||||
try:
|
||||
local_specfile_stage.fetch()
|
||||
local_specfile_stage.check()
|
||||
try:
|
||||
_get_valid_spec_file(
|
||||
local_specfile_stage.save_filename,
|
||||
CURRENT_BUILD_CACHE_LAYOUT_VERSION,
|
||||
)
|
||||
except InvalidMetadataFile as e:
|
||||
tty.warn(
|
||||
f"Ignoring binary package for {spec.name}/{spec.dag_hash()[:7]} "
|
||||
f"from {mirror} due to invalid metadata file: {e}"
|
||||
)
|
||||
local_specfile_stage.destroy()
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
local_specfile_stage.cache_local()
|
||||
@@ -1674,14 +1727,26 @@ def download_tarball(spec, unsigned=False, mirrors_for_spec=None):
|
||||
|
||||
else:
|
||||
ext = "json.sig" if try_signed else "json"
|
||||
specfile_path = url_util.join(mirror, _build_cache_relative_path, specfile_prefix)
|
||||
specfile_path = url_util.join(mirror, BUILD_CACHE_RELATIVE_PATH, specfile_prefix)
|
||||
specfile_url = f"{specfile_path}.{ext}"
|
||||
spackfile_url = url_util.join(mirror, _build_cache_relative_path, tarball)
|
||||
spackfile_url = url_util.join(mirror, BUILD_CACHE_RELATIVE_PATH, tarball)
|
||||
local_specfile_stage = try_fetch(specfile_url)
|
||||
if local_specfile_stage:
|
||||
local_specfile_path = local_specfile_stage.save_filename
|
||||
signature_verified = False
|
||||
|
||||
try:
|
||||
_get_valid_spec_file(
|
||||
local_specfile_path, CURRENT_BUILD_CACHE_LAYOUT_VERSION
|
||||
)
|
||||
except InvalidMetadataFile as e:
|
||||
tty.warn(
|
||||
f"Ignoring binary package for {spec.name}/{spec.dag_hash()[:7]} "
|
||||
f"from {mirror} due to invalid metadata file: {e}"
|
||||
)
|
||||
local_specfile_stage.destroy()
|
||||
continue
|
||||
|
||||
if try_signed and not unsigned:
|
||||
# If we found a signed specfile at the root, try to verify
|
||||
# the signature immediately. We will not download the
|
||||
@@ -2001,24 +2066,16 @@ def extract_tarball(spec, download_result, unsigned=False, force=False, timer=ti
|
||||
)
|
||||
|
||||
specfile_path = download_result["specfile_stage"].save_filename
|
||||
|
||||
with open(specfile_path, "r") as inputfile:
|
||||
content = inputfile.read()
|
||||
if specfile_path.endswith(".json.sig"):
|
||||
spec_dict = Spec.extract_json_from_clearsig(content)
|
||||
else:
|
||||
spec_dict = sjson.load(content)
|
||||
|
||||
spec_dict, layout_version = _get_valid_spec_file(
|
||||
specfile_path, CURRENT_BUILD_CACHE_LAYOUT_VERSION
|
||||
)
|
||||
bchecksum = spec_dict["binary_cache_checksum"]
|
||||
|
||||
filename = download_result["tarball_stage"].save_filename
|
||||
signature_verified = download_result["signature_verified"]
|
||||
tmpdir = None
|
||||
|
||||
if (
|
||||
"buildcache_layout_version" not in spec_dict
|
||||
or int(spec_dict["buildcache_layout_version"]) < 1
|
||||
):
|
||||
if layout_version == 0:
|
||||
# Handle the older buildcache layout where the .spack file
|
||||
# contains a spec json, maybe an .asc file (signature),
|
||||
# and another tarball containing the actual install tree.
|
||||
@@ -2029,7 +2086,7 @@ def extract_tarball(spec, download_result, unsigned=False, force=False, timer=ti
|
||||
_delete_staged_downloads(download_result)
|
||||
shutil.rmtree(tmpdir)
|
||||
raise e
|
||||
else:
|
||||
elif layout_version == 1:
|
||||
# Newer buildcache layout: the .spack file contains just
|
||||
# in the install tree, the signature, if it exists, is
|
||||
# wrapped around the spec.json at the root. If sig verify
|
||||
@@ -2053,7 +2110,6 @@ def extract_tarball(spec, download_result, unsigned=False, force=False, timer=ti
|
||||
raise NoChecksumException(
|
||||
tarfile_path, size, contents, "sha256", expected, local_checksum
|
||||
)
|
||||
|
||||
try:
|
||||
with closing(tarfile.open(tarfile_path, "r")) as tar:
|
||||
# Remove install prefix from tarfil to extract directly into spec.prefix
|
||||
@@ -2184,10 +2240,10 @@ def try_direct_fetch(spec, mirrors=None):
|
||||
|
||||
for mirror in binary_mirrors:
|
||||
buildcache_fetch_url_json = url_util.join(
|
||||
mirror.fetch_url, _build_cache_relative_path, specfile_name
|
||||
mirror.fetch_url, BUILD_CACHE_RELATIVE_PATH, specfile_name
|
||||
)
|
||||
buildcache_fetch_url_signed_json = url_util.join(
|
||||
mirror.fetch_url, _build_cache_relative_path, signed_specfile_name
|
||||
mirror.fetch_url, BUILD_CACHE_RELATIVE_PATH, signed_specfile_name
|
||||
)
|
||||
try:
|
||||
_, _, fs = web_util.read_from_url(buildcache_fetch_url_signed_json)
|
||||
@@ -2292,7 +2348,7 @@ def get_keys(install=False, trust=False, force=False, mirrors=None):
|
||||
for mirror in mirror_collection.values():
|
||||
fetch_url = mirror.fetch_url
|
||||
keys_url = url_util.join(
|
||||
fetch_url, _build_cache_relative_path, _build_cache_keys_relative_path
|
||||
fetch_url, BUILD_CACHE_RELATIVE_PATH, BUILD_CACHE_KEYS_RELATIVE_PATH
|
||||
)
|
||||
keys_index = url_util.join(keys_url, "index.json")
|
||||
|
||||
@@ -2357,7 +2413,7 @@ def push_keys(*mirrors, **kwargs):
|
||||
for mirror in mirrors:
|
||||
push_url = getattr(mirror, "push_url", mirror)
|
||||
keys_url = url_util.join(
|
||||
push_url, _build_cache_relative_path, _build_cache_keys_relative_path
|
||||
push_url, BUILD_CACHE_RELATIVE_PATH, BUILD_CACHE_KEYS_RELATIVE_PATH
|
||||
)
|
||||
keys_local = url_util.local_file_path(keys_url)
|
||||
|
||||
@@ -2495,11 +2551,11 @@ def download_buildcache_entry(file_descriptions, mirror_url=None):
|
||||
)
|
||||
|
||||
if mirror_url:
|
||||
mirror_root = os.path.join(mirror_url, _build_cache_relative_path)
|
||||
mirror_root = os.path.join(mirror_url, BUILD_CACHE_RELATIVE_PATH)
|
||||
return _download_buildcache_entry(mirror_root, file_descriptions)
|
||||
|
||||
for mirror in spack.mirror.MirrorCollection(binary=True).values():
|
||||
mirror_root = os.path.join(mirror.fetch_url, _build_cache_relative_path)
|
||||
mirror_root = os.path.join(mirror.fetch_url, BUILD_CACHE_RELATIVE_PATH)
|
||||
|
||||
if _download_buildcache_entry(mirror_root, file_descriptions):
|
||||
return True
|
||||
@@ -2590,7 +2646,7 @@ def __init__(self, url, local_hash, urlopen=web_util.urlopen):
|
||||
|
||||
def get_remote_hash(self):
|
||||
# Failure to fetch index.json.hash is not fatal
|
||||
url_index_hash = url_util.join(self.url, _build_cache_relative_path, "index.json.hash")
|
||||
url_index_hash = url_util.join(self.url, BUILD_CACHE_RELATIVE_PATH, "index.json.hash")
|
||||
try:
|
||||
response = self.urlopen(urllib.request.Request(url_index_hash, headers=self.headers))
|
||||
except urllib.error.URLError:
|
||||
@@ -2611,7 +2667,7 @@ def conditional_fetch(self) -> FetchIndexResult:
|
||||
return FetchIndexResult(etag=None, hash=None, data=None, fresh=True)
|
||||
|
||||
# Otherwise, download index.json
|
||||
url_index = url_util.join(self.url, _build_cache_relative_path, "index.json")
|
||||
url_index = url_util.join(self.url, BUILD_CACHE_RELATIVE_PATH, "index.json")
|
||||
|
||||
try:
|
||||
response = self.urlopen(urllib.request.Request(url_index, headers=self.headers))
|
||||
@@ -2655,7 +2711,7 @@ def __init__(self, url, etag, urlopen=web_util.urlopen):
|
||||
|
||||
def conditional_fetch(self) -> FetchIndexResult:
|
||||
# Just do a conditional fetch immediately
|
||||
url = url_util.join(self.url, _build_cache_relative_path, "index.json")
|
||||
url = url_util.join(self.url, BUILD_CACHE_RELATIVE_PATH, "index.json")
|
||||
headers = {
|
||||
"User-Agent": web_util.SPACK_USER_AGENT,
|
||||
"If-None-Match": '"{}"'.format(self.etag),
|
||||
|
||||
@@ -143,7 +143,9 @@ def _bootstrap_config_scopes() -> Sequence["spack.config.ConfigScope"]:
|
||||
def _add_compilers_if_missing() -> None:
|
||||
arch = spack.spec.ArchSpec.frontend_arch()
|
||||
if not spack.compilers.compilers_for_arch(arch):
|
||||
new_compilers = spack.compilers.find_new_compilers()
|
||||
new_compilers = spack.compilers.find_new_compilers(
|
||||
mixed_toolchain=sys.platform == "darwin"
|
||||
)
|
||||
if new_compilers:
|
||||
spack.compilers.add_compilers_to_config(new_compilers, init_config=False)
|
||||
|
||||
|
||||
@@ -1016,10 +1016,17 @@ def get_env_modifications(self) -> EnvironmentModifications:
|
||||
self._make_runnable(dspec, env)
|
||||
|
||||
if self.should_setup_run_env & flag:
|
||||
run_env_mods = EnvironmentModifications()
|
||||
for spec in dspec.dependents(deptype=dt.LINK | dt.RUN):
|
||||
if id(spec) in self.nodes_in_subdag:
|
||||
pkg.setup_dependent_run_environment(env, spec)
|
||||
pkg.setup_run_environment(env)
|
||||
pkg.setup_dependent_run_environment(run_env_mods, spec)
|
||||
pkg.setup_run_environment(run_env_mods)
|
||||
if self.context == Context.BUILD:
|
||||
# Don't let the runtime environment of comiler like dependencies leak into the
|
||||
# build env
|
||||
run_env_mods.drop("CC", "CXX", "F77", "FC")
|
||||
env.extend(run_env_mods)
|
||||
|
||||
return env
|
||||
|
||||
def _make_buildtime_detectable(self, dep: spack.spec.Spec, env: EnvironmentModifications):
|
||||
|
||||
@@ -34,6 +34,11 @@ def cmake_cache_option(name, boolean_value, comment="", force=False):
|
||||
return 'set({0} {1} CACHE BOOL "{2}"{3})\n'.format(name, value, comment, force_str)
|
||||
|
||||
|
||||
def cmake_cache_filepath(name, value, comment=""):
|
||||
"""Generate a string for a cmake cache variable of type FILEPATH"""
|
||||
return 'set({0} "{1}" CACHE FILEPATH "{2}")\n'.format(name, value, comment)
|
||||
|
||||
|
||||
class CachedCMakeBuilder(CMakeBuilder):
|
||||
#: Phases of a Cached CMake package
|
||||
#: Note: the initconfig phase is used for developer builds as a final phase to stop on
|
||||
@@ -257,6 +262,15 @@ def initconfig_hardware_entries(self):
|
||||
entries.append(
|
||||
cmake_cache_path("HIP_CXX_COMPILER", "{0}".format(self.spec["hip"].hipcc))
|
||||
)
|
||||
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, "clang++"))
|
||||
)
|
||||
archs = self.spec.variants["amdgpu_target"].value
|
||||
if archs[0] != "none":
|
||||
arch_str = ";".join(archs)
|
||||
@@ -277,7 +291,7 @@ def std_initconfig_entries(self):
|
||||
"#------------------{0}".format("-" * 60),
|
||||
"# CMake executable path: {0}".format(self.pkg.spec["cmake"].command.path),
|
||||
"#------------------{0}\n".format("-" * 60),
|
||||
cmake_cache_path("CMAKE_PREFIX_PATH", cmake_prefix_path),
|
||||
cmake_cache_string("CMAKE_PREFIX_PATH", cmake_prefix_path),
|
||||
self.define_cmake_cache_from_variant("CMAKE_BUILD_TYPE", "build_type"),
|
||||
]
|
||||
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
import shutil
|
||||
from os.path import basename, dirname, isdir
|
||||
|
||||
from llnl.util.filesystem import find_headers, find_libraries, join_path
|
||||
from llnl.util.filesystem import find_headers, find_libraries, join_path, mkdirp
|
||||
from llnl.util.link_tree import LinkTree
|
||||
|
||||
from spack.directives import conflicts, variant
|
||||
from spack.package import mkdirp
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.util.executable import Executable
|
||||
|
||||
@@ -212,3 +211,7 @@ def link_flags(self):
|
||||
@property
|
||||
def ld_flags(self):
|
||||
return "{0} {1}".format(self.search_flags, self.link_flags)
|
||||
|
||||
|
||||
#: Tuple of Intel math libraries, exported to packages
|
||||
INTEL_MATH_LIBRARIES = ("intel-mkl", "intel-oneapi-mkl", "intel-parallel-studio")
|
||||
|
||||
@@ -46,7 +46,22 @@
|
||||
from spack.reporters import CDash, CDashConfiguration
|
||||
from spack.reporters.cdash import build_stamp as cdash_build_stamp
|
||||
|
||||
JOB_RETRY_CONDITIONS = ["always"]
|
||||
# See https://docs.gitlab.com/ee/ci/yaml/#retry for descriptions of conditions
|
||||
JOB_RETRY_CONDITIONS = [
|
||||
# "always",
|
||||
"unknown_failure",
|
||||
"script_failure",
|
||||
"api_failure",
|
||||
"stuck_or_timeout_failure",
|
||||
"runner_system_failure",
|
||||
"runner_unsupported",
|
||||
"stale_schedule",
|
||||
# "job_execution_timeout",
|
||||
"archived_failure",
|
||||
"unmet_prerequisites",
|
||||
"scheduler_failure",
|
||||
"data_integrity_failure",
|
||||
]
|
||||
|
||||
TEMP_STORAGE_MIRROR_NAME = "ci_temporary_mirror"
|
||||
SPACK_RESERVED_TAGS = ["public", "protected", "notary"]
|
||||
|
||||
@@ -796,7 +796,9 @@ def names(args: Namespace, out: IO) -> None:
|
||||
commands = copy.copy(spack.cmd.all_commands())
|
||||
|
||||
if args.aliases:
|
||||
commands.extend(spack.main.aliases.keys())
|
||||
aliases = spack.config.get("config:aliases")
|
||||
if aliases:
|
||||
commands.extend(aliases.keys())
|
||||
|
||||
colify(commands, output=out)
|
||||
|
||||
@@ -812,8 +814,10 @@ def bash(args: Namespace, out: IO) -> None:
|
||||
parser = spack.main.make_argument_parser()
|
||||
spack.main.add_all_commands(parser)
|
||||
|
||||
aliases = ";".join(f"{key}:{val}" for key, val in spack.main.aliases.items())
|
||||
out.write(f'SPACK_ALIASES="{aliases}"\n\n')
|
||||
aliases_config = spack.config.get("config:aliases")
|
||||
if aliases_config:
|
||||
aliases = ";".join(f"{key}:{val}" for key, val in aliases_config.items())
|
||||
out.write(f'SPACK_ALIASES="{aliases}"\n\n')
|
||||
|
||||
writer = BashCompletionWriter(parser.prog, out, args.aliases)
|
||||
writer.write(parser)
|
||||
|
||||
30
lib/spack/spack/cmd/common/confirmation.py
Normal file
30
lib/spack/spack/cmd/common/confirmation.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.cmd
|
||||
|
||||
display_args = {"long": True, "show_flags": False, "variants": False, "indent": 4}
|
||||
|
||||
|
||||
def confirm_action(specs: List[spack.spec.Spec], participle: str, noun: str):
|
||||
"""Display the list of specs to be acted on and ask for confirmation.
|
||||
|
||||
Args:
|
||||
specs: specs to be removed
|
||||
participle: action expressed as a participle, e.g. "uninstalled"
|
||||
noun: action expressed as a noun, e.g. "uninstallation"
|
||||
"""
|
||||
tty.msg(f"The following {len(specs)} packages will be {participle}:\n")
|
||||
spack.cmd.display_specs(specs, **display_args)
|
||||
print("")
|
||||
answer = tty.get_yes_or_no("Do you want to proceed?", default=False)
|
||||
if not answer:
|
||||
tty.msg(f"Aborting {noun}")
|
||||
sys.exit(0)
|
||||
@@ -31,6 +31,19 @@ def setup_parser(subparser):
|
||||
aliases=["add"],
|
||||
help="search the system for compilers to add to Spack configuration",
|
||||
)
|
||||
mixed_toolchain_group = find_parser.add_mutually_exclusive_group()
|
||||
mixed_toolchain_group.add_argument(
|
||||
"--mixed-toolchain",
|
||||
action="store_true",
|
||||
default=sys.platform == "darwin",
|
||||
help="Allow mixed toolchains (for example: clang, clang++, gfortran)",
|
||||
)
|
||||
mixed_toolchain_group.add_argument(
|
||||
"--no-mixed-toolchain",
|
||||
action="store_false",
|
||||
dest="mixed_toolchain",
|
||||
help="Do not allow mixed toolchains (for example: clang, clang++, gfortran)",
|
||||
)
|
||||
find_parser.add_argument("add_paths", nargs=argparse.REMAINDER)
|
||||
find_parser.add_argument(
|
||||
"--scope",
|
||||
@@ -86,7 +99,9 @@ def compiler_find(args):
|
||||
|
||||
# Below scope=None because we want new compilers that don't appear
|
||||
# in any other configuration.
|
||||
new_compilers = spack.compilers.find_new_compilers(paths, scope=None)
|
||||
new_compilers = spack.compilers.find_new_compilers(
|
||||
paths, scope=None, mixed_toolchain=args.mixed_toolchain
|
||||
)
|
||||
if new_compilers:
|
||||
spack.compilers.add_compilers_to_config(new_compilers, scope=args.scope, init_config=False)
|
||||
n = len(new_compilers)
|
||||
|
||||
@@ -407,7 +407,9 @@ def config_prefer_upstream(args):
|
||||
pkgs = {}
|
||||
for spec in pref_specs:
|
||||
# Collect all the upstream compilers and versions for this package.
|
||||
pkg = pkgs.get(spec.name, {"version": [], "compiler": []})
|
||||
pkg = pkgs.get(spec.name, {"version": []})
|
||||
all = pkgs.get("all", {"compiler": []})
|
||||
pkgs["all"] = all
|
||||
pkgs[spec.name] = pkg
|
||||
|
||||
# We have no existing variant if this is our first added version.
|
||||
@@ -418,8 +420,8 @@ def config_prefer_upstream(args):
|
||||
pkg["version"].append(version)
|
||||
|
||||
compiler = str(spec.compiler)
|
||||
if compiler not in pkg["compiler"]:
|
||||
pkg["compiler"].append(compiler)
|
||||
if compiler not in all["compiler"]:
|
||||
all["compiler"].append(compiler)
|
||||
|
||||
# Get and list all the variants that differ from the default.
|
||||
variants = []
|
||||
|
||||
103
lib/spack/spack/cmd/deconcretize.py
Normal file
103
lib/spack/spack/cmd/deconcretize.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.cmd
|
||||
import spack.cmd.common.arguments as arguments
|
||||
import spack.cmd.common.confirmation as confirmation
|
||||
import spack.environment as ev
|
||||
import spack.spec
|
||||
|
||||
description = "remove specs from the concretized lockfile of an environment"
|
||||
section = "environments"
|
||||
level = "long"
|
||||
|
||||
# Arguments for display_specs when we find ambiguity
|
||||
display_args = {"long": True, "show_flags": False, "variants": False, "indent": 4}
|
||||
|
||||
|
||||
def setup_parser(subparser):
|
||||
subparser.add_argument(
|
||||
"--root", action="store_true", help="deconcretize only specific environment roots"
|
||||
)
|
||||
arguments.add_common_arguments(subparser, ["yes_to_all", "specs"])
|
||||
subparser.add_argument(
|
||||
"-a",
|
||||
"--all",
|
||||
action="store_true",
|
||||
dest="all",
|
||||
help="deconcretize ALL specs that match each supplied spec",
|
||||
)
|
||||
|
||||
|
||||
def get_deconcretize_list(
|
||||
args: argparse.Namespace, specs: List[spack.spec.Spec], env: ev.Environment
|
||||
) -> List[spack.spec.Spec]:
|
||||
"""
|
||||
Get list of environment roots to deconcretize
|
||||
"""
|
||||
env_specs = [s for _, s in env.concretized_specs()]
|
||||
to_deconcretize = []
|
||||
errors = []
|
||||
|
||||
for s in specs:
|
||||
if args.root:
|
||||
# find all roots matching given spec
|
||||
to_deconc = [e for e in env_specs if e.satisfies(s)]
|
||||
else:
|
||||
# find all roots matching or depending on a matching spec
|
||||
to_deconc = [e for e in env_specs if any(d.satisfies(s) for d in e.traverse())]
|
||||
|
||||
if len(to_deconc) < 1:
|
||||
tty.warn(f"No matching specs to deconcretize for {s}")
|
||||
|
||||
elif len(to_deconc) > 1 and not args.all:
|
||||
errors.append((s, to_deconc))
|
||||
|
||||
to_deconcretize.extend(to_deconc)
|
||||
|
||||
if errors:
|
||||
for spec, matching in errors:
|
||||
tty.error(f"{spec} matches multiple concrete specs:")
|
||||
sys.stderr.write("\n")
|
||||
spack.cmd.display_specs(matching, output=sys.stderr, **display_args)
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.flush()
|
||||
tty.die("Use '--all' to deconcretize all matching specs, or be more specific")
|
||||
|
||||
return to_deconcretize
|
||||
|
||||
|
||||
def deconcretize_specs(args, specs):
|
||||
env = spack.cmd.require_active_env(cmd_name="deconcretize")
|
||||
|
||||
if args.specs:
|
||||
deconcretize_list = get_deconcretize_list(args, specs, env)
|
||||
else:
|
||||
deconcretize_list = [s for _, s in env.concretized_specs()]
|
||||
|
||||
if not args.yes_to_all:
|
||||
confirmation.confirm_action(deconcretize_list, "deconcretized", "deconcretization")
|
||||
|
||||
with env.write_transaction():
|
||||
for spec in deconcretize_list:
|
||||
env.deconcretize(spec)
|
||||
env.write()
|
||||
|
||||
|
||||
def deconcretize(parser, args):
|
||||
if not args.specs and not args.all:
|
||||
tty.die(
|
||||
"deconcretize requires at least one spec argument.",
|
||||
" Use `spack deconcretize --all` to deconcretize ALL specs.",
|
||||
)
|
||||
|
||||
specs = spack.cmd.parse_specs(args.specs) if args.specs else [any]
|
||||
deconcretize_specs(args, specs)
|
||||
@@ -99,10 +99,7 @@ def dev_build(self, args):
|
||||
|
||||
spec = specs[0]
|
||||
if not spack.repo.PATH.exists(spec.name):
|
||||
tty.die(
|
||||
"No package for '{0}' was found.".format(spec.name),
|
||||
" Use `spack create` to create a new package",
|
||||
)
|
||||
raise spack.repo.UnknownPackageError(spec.name)
|
||||
|
||||
if not spec.versions.concrete_range_as_version:
|
||||
tty.die(
|
||||
|
||||
@@ -200,6 +200,8 @@ def diff(parser, args):
|
||||
|
||||
specs = []
|
||||
for spec in spack.cmd.parse_specs(args.specs):
|
||||
# If the spec has a hash, check it before disambiguating
|
||||
spec.replace_hash()
|
||||
if spec.concrete:
|
||||
specs.append(spec)
|
||||
else:
|
||||
|
||||
@@ -43,10 +43,7 @@ def edit_package(name, repo_path, namespace):
|
||||
if not os.access(path, os.R_OK):
|
||||
tty.die("Insufficient permissions on '%s'!" % path)
|
||||
else:
|
||||
tty.die(
|
||||
"No package for '{0}' was found.".format(spec.name),
|
||||
" Use `spack create` to create a new package",
|
||||
)
|
||||
raise spack.repo.UnknownPackageError(spec.name)
|
||||
|
||||
editor(path)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -144,10 +145,13 @@ def create_temp_env_directory():
|
||||
return tempfile.mkdtemp(prefix="spack-")
|
||||
|
||||
|
||||
def env_activate(args):
|
||||
if not args.activate_env and not args.dir and not args.temp:
|
||||
tty.die("spack env activate requires an environment name, directory, or --temp")
|
||||
def _tty_info(msg):
|
||||
"""tty.info like function that prints the equivalent printf statement for eval."""
|
||||
decorated = f'{colorize("@*b{==>}")} {msg}\n'
|
||||
print(f"printf {shlex.quote(decorated)};")
|
||||
|
||||
|
||||
def env_activate(args):
|
||||
if not args.shell:
|
||||
spack.cmd.common.shell_init_instructions(
|
||||
"spack env activate", " eval `spack env activate {sh_arg} [...]`"
|
||||
@@ -160,12 +164,25 @@ def env_activate(args):
|
||||
|
||||
env_name_or_dir = args.activate_env or args.dir
|
||||
|
||||
# When executing `spack env activate` without further arguments, activate
|
||||
# the default environment. It's created when it doesn't exist yet.
|
||||
if not env_name_or_dir and not args.temp:
|
||||
short_name = "default"
|
||||
if not ev.exists(short_name):
|
||||
ev.create(short_name)
|
||||
action = "Created and activated"
|
||||
else:
|
||||
action = "Activated"
|
||||
env_path = ev.root(short_name)
|
||||
_tty_info(f"{action} default environment in {env_path}")
|
||||
|
||||
# Temporary environment
|
||||
if args.temp:
|
||||
elif args.temp:
|
||||
env = create_temp_env_directory()
|
||||
env_path = os.path.abspath(env)
|
||||
short_name = os.path.basename(env_path)
|
||||
ev.create_in_dir(env).write(regenerate=False)
|
||||
_tty_info(f"Created and activated temporary environment in {env_path}")
|
||||
|
||||
# Managed environment
|
||||
elif ev.exists(env_name_or_dir) and not args.dir:
|
||||
@@ -385,7 +402,7 @@ def env_remove(args):
|
||||
try:
|
||||
env = ev.read(env_name)
|
||||
read_envs.append(env)
|
||||
except spack.config.ConfigFormatError:
|
||||
except (spack.config.ConfigFormatError, ev.SpackEnvironmentConfigError):
|
||||
bad_envs.append(env_name)
|
||||
|
||||
if not args.yes_to_all:
|
||||
@@ -553,8 +570,8 @@ def env_update_setup_parser(subparser):
|
||||
def env_update(args):
|
||||
manifest_file = ev.manifest_file(args.update_env)
|
||||
backup_file = manifest_file + ".bkp"
|
||||
needs_update = not ev.is_latest_format(manifest_file)
|
||||
|
||||
needs_update = not ev.is_latest_format(manifest_file)
|
||||
if not needs_update:
|
||||
tty.msg('No update needed for the environment "{0}"'.format(args.update_env))
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.cmd.common.arguments
|
||||
import spack.cmd.common.confirmation
|
||||
import spack.cmd.uninstall
|
||||
import spack.environment as ev
|
||||
import spack.store
|
||||
@@ -41,6 +42,6 @@ def gc(parser, args):
|
||||
return
|
||||
|
||||
if not args.yes_to_all:
|
||||
spack.cmd.uninstall.confirm_removal(specs)
|
||||
spack.cmd.common.confirmation.confirm_action(specs, "uninstalled", "uninstallation")
|
||||
|
||||
spack.cmd.uninstall.do_uninstall(specs, force=False)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
from itertools import zip_longest
|
||||
|
||||
@@ -16,6 +17,7 @@
|
||||
import spack.install_test
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
import spack.version
|
||||
from spack.package_base import preferred_version
|
||||
|
||||
description = "get detailed information on a particular package"
|
||||
@@ -53,6 +55,7 @@ def setup_parser(subparser):
|
||||
("--tags", print_tags.__doc__),
|
||||
("--tests", print_tests.__doc__),
|
||||
("--virtuals", print_virtuals.__doc__),
|
||||
("--variants-by-name", "list variants in strict name order; don't group by condition"),
|
||||
]
|
||||
for opt, help_comment in options:
|
||||
subparser.add_argument(opt, action="store_true", help=help_comment)
|
||||
@@ -77,35 +80,10 @@ def license(s):
|
||||
|
||||
|
||||
class VariantFormatter:
|
||||
def __init__(self, variants):
|
||||
self.variants = variants
|
||||
def __init__(self, pkg):
|
||||
self.variants = pkg.variants
|
||||
self.headers = ("Name [Default]", "When", "Allowed values", "Description")
|
||||
|
||||
# Formats
|
||||
fmt_name = "{0} [{1}]"
|
||||
|
||||
# Initialize column widths with the length of the
|
||||
# corresponding headers, as they cannot be shorter
|
||||
# than that
|
||||
self.column_widths = [len(x) for x in self.headers]
|
||||
|
||||
# Expand columns based on max line lengths
|
||||
for k, e in variants.items():
|
||||
v, w = e
|
||||
candidate_max_widths = (
|
||||
len(fmt_name.format(k, self.default(v))), # Name [Default]
|
||||
len(str(w)),
|
||||
len(v.allowed_values), # Allowed values
|
||||
len(v.description), # Description
|
||||
)
|
||||
|
||||
self.column_widths = (
|
||||
max(self.column_widths[0], candidate_max_widths[0]),
|
||||
max(self.column_widths[1], candidate_max_widths[1]),
|
||||
max(self.column_widths[2], candidate_max_widths[2]),
|
||||
max(self.column_widths[3], candidate_max_widths[3]),
|
||||
)
|
||||
|
||||
# Don't let name or possible values be less than max widths
|
||||
_, cols = tty.terminal_size()
|
||||
max_name = min(self.column_widths[0], 30)
|
||||
@@ -137,6 +115,8 @@ def default(self, v):
|
||||
def lines(self):
|
||||
if not self.variants:
|
||||
yield " None"
|
||||
return
|
||||
|
||||
else:
|
||||
yield " " + self.fmt % self.headers
|
||||
underline = tuple([w * "=" for w in self.column_widths])
|
||||
@@ -159,7 +139,7 @@ def lines(self):
|
||||
yield " " + self.fmt % t
|
||||
|
||||
|
||||
def print_dependencies(pkg):
|
||||
def print_dependencies(pkg, args):
|
||||
"""output build, link, and run package dependencies"""
|
||||
|
||||
for deptype in ("build", "link", "run"):
|
||||
@@ -172,7 +152,7 @@ def print_dependencies(pkg):
|
||||
color.cprint(" None")
|
||||
|
||||
|
||||
def print_detectable(pkg):
|
||||
def print_detectable(pkg, args):
|
||||
"""output information on external detection"""
|
||||
|
||||
color.cprint("")
|
||||
@@ -200,7 +180,7 @@ def print_detectable(pkg):
|
||||
color.cprint(" False")
|
||||
|
||||
|
||||
def print_maintainers(pkg):
|
||||
def print_maintainers(pkg, args):
|
||||
"""output package maintainers"""
|
||||
|
||||
if len(pkg.maintainers) > 0:
|
||||
@@ -209,7 +189,7 @@ def print_maintainers(pkg):
|
||||
color.cprint(section_title("Maintainers: ") + mnt)
|
||||
|
||||
|
||||
def print_phases(pkg):
|
||||
def print_phases(pkg, args):
|
||||
"""output installation phases"""
|
||||
|
||||
if hasattr(pkg.builder, "phases") and pkg.builder.phases:
|
||||
@@ -221,7 +201,7 @@ def print_phases(pkg):
|
||||
color.cprint(phase_str)
|
||||
|
||||
|
||||
def print_tags(pkg):
|
||||
def print_tags(pkg, args):
|
||||
"""output package tags"""
|
||||
|
||||
color.cprint("")
|
||||
@@ -233,7 +213,7 @@ def print_tags(pkg):
|
||||
color.cprint(" None")
|
||||
|
||||
|
||||
def print_tests(pkg):
|
||||
def print_tests(pkg, args):
|
||||
"""output relevant build-time and stand-alone tests"""
|
||||
|
||||
# Some built-in base packages (e.g., Autotools) define callback (e.g.,
|
||||
@@ -271,18 +251,171 @@ def print_tests(pkg):
|
||||
color.cprint(" None")
|
||||
|
||||
|
||||
def print_variants(pkg):
|
||||
def _fmt_value(v):
|
||||
if v is None or isinstance(v, bool):
|
||||
return str(v).lower()
|
||||
else:
|
||||
return str(v)
|
||||
|
||||
|
||||
def _fmt_name_and_default(variant):
|
||||
"""Print colorized name [default] for a variant."""
|
||||
return color.colorize(f"@c{{{variant.name}}} @C{{[{_fmt_value(variant.default)}]}}")
|
||||
|
||||
|
||||
def _fmt_when(when, indent):
|
||||
return color.colorize(f"{indent * ' '}@B{{when}} {color.cescape(when)}")
|
||||
|
||||
|
||||
def _fmt_variant_description(variant, width, indent):
|
||||
"""Format a variant's description, preserving explicit line breaks."""
|
||||
return "\n".join(
|
||||
textwrap.fill(
|
||||
line, width=width, initial_indent=indent * " ", subsequent_indent=indent * " "
|
||||
)
|
||||
for line in variant.description.split("\n")
|
||||
)
|
||||
|
||||
|
||||
def _fmt_variant(variant, max_name_default_len, indent, when=None, out=None):
|
||||
out = out or sys.stdout
|
||||
|
||||
_, cols = tty.terminal_size()
|
||||
|
||||
name_and_default = _fmt_name_and_default(variant)
|
||||
name_default_len = color.clen(name_and_default)
|
||||
|
||||
values = variant.values
|
||||
if not isinstance(variant.values, (tuple, list, spack.variant.DisjointSetsOfValues)):
|
||||
values = [variant.values]
|
||||
|
||||
# put 'none' first, sort the rest by value
|
||||
sorted_values = sorted(values, key=lambda v: (v != "none", v))
|
||||
|
||||
pad = 4 # min padding between 'name [default]' and values
|
||||
value_indent = (indent + max_name_default_len + pad) * " " # left edge of values
|
||||
|
||||
# This preserves any formatting (i.e., newlines) from how the description was
|
||||
# written in package.py, but still wraps long lines for small terminals.
|
||||
# This allows some packages to provide detailed help on their variants (see, e.g., gasnet).
|
||||
formatted_values = "\n".join(
|
||||
textwrap.wrap(
|
||||
f"{', '.join(_fmt_value(v) for v in sorted_values)}",
|
||||
width=cols - 2,
|
||||
initial_indent=value_indent,
|
||||
subsequent_indent=value_indent,
|
||||
)
|
||||
)
|
||||
formatted_values = formatted_values[indent + name_default_len + pad :]
|
||||
|
||||
# name [default] value1, value2, value3, ...
|
||||
padding = pad * " "
|
||||
color.cprint(f"{indent * ' '}{name_and_default}{padding}@c{{{formatted_values}}}", stream=out)
|
||||
|
||||
# when <spec>
|
||||
description_indent = indent + 4
|
||||
if when is not None and when != spack.spec.Spec():
|
||||
out.write(_fmt_when(when, description_indent - 2))
|
||||
out.write("\n")
|
||||
|
||||
# description, preserving explicit line breaks from the way it's written in the package file
|
||||
out.write(_fmt_variant_description(variant, cols - 2, description_indent))
|
||||
out.write("\n")
|
||||
|
||||
|
||||
def _variants_by_name_when(pkg):
|
||||
"""Adaptor to get variants keyed by { name: { when: { [Variant...] } }."""
|
||||
# TODO: replace with pkg.variants_by_name(when=True) when unified directive dicts are merged.
|
||||
variants = {}
|
||||
for name, (variant, whens) in pkg.variants.items():
|
||||
for when in whens:
|
||||
variants.setdefault(name, {}).setdefault(when, []).append(variant)
|
||||
return variants
|
||||
|
||||
|
||||
def _variants_by_when_name(pkg):
|
||||
"""Adaptor to get variants keyed by { when: { name: Variant } }"""
|
||||
# TODO: replace with pkg.variants when unified directive dicts are merged.
|
||||
variants = {}
|
||||
for name, (variant, whens) in pkg.variants.items():
|
||||
for when in whens:
|
||||
variants.setdefault(when, {})[name] = variant
|
||||
return variants
|
||||
|
||||
|
||||
def _print_variants_header(pkg):
|
||||
"""output variants"""
|
||||
|
||||
if not pkg.variants:
|
||||
print(" None")
|
||||
return
|
||||
|
||||
color.cprint("")
|
||||
color.cprint(section_title("Variants:"))
|
||||
|
||||
formatter = VariantFormatter(pkg.variants)
|
||||
for line in formatter.lines:
|
||||
color.cprint(color.cescape(line))
|
||||
variants_by_name = _variants_by_name_when(pkg)
|
||||
|
||||
# Calculate the max length of the "name [default]" part of the variant display
|
||||
# This lets us know where to print variant values.
|
||||
max_name_default_len = max(
|
||||
color.clen(_fmt_name_and_default(variant))
|
||||
for name, when_variants in variants_by_name.items()
|
||||
for variants in when_variants.values()
|
||||
for variant in variants
|
||||
)
|
||||
|
||||
return max_name_default_len, variants_by_name
|
||||
|
||||
|
||||
def print_versions(pkg):
|
||||
def _unconstrained_ver_first(item):
|
||||
"""sort key that puts specs with open version ranges first"""
|
||||
spec, _ = item
|
||||
return (spack.version.any_version not in spec.versions, spec)
|
||||
|
||||
|
||||
def print_variants_grouped_by_when(pkg):
|
||||
max_name_default_len, _ = _print_variants_header(pkg)
|
||||
|
||||
indent = 4
|
||||
variants = _variants_by_when_name(pkg)
|
||||
for when, variants_by_name in sorted(variants.items(), key=_unconstrained_ver_first):
|
||||
padded_values = max_name_default_len + 4
|
||||
start_indent = indent
|
||||
|
||||
if when != spack.spec.Spec():
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.write(_fmt_when(when, indent))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
# indent names slightly inside 'when', but line up values
|
||||
padded_values -= 2
|
||||
start_indent += 2
|
||||
|
||||
for name, variant in sorted(variants_by_name.items()):
|
||||
_fmt_variant(variant, padded_values, start_indent, None, out=sys.stdout)
|
||||
|
||||
|
||||
def print_variants_by_name(pkg):
|
||||
max_name_default_len, variants_by_name = _print_variants_header(pkg)
|
||||
max_name_default_len += 4
|
||||
|
||||
indent = 4
|
||||
for name, when_variants in variants_by_name.items():
|
||||
for when, variants in sorted(when_variants.items(), key=_unconstrained_ver_first):
|
||||
for variant in variants:
|
||||
_fmt_variant(variant, max_name_default_len, indent, when, out=sys.stdout)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
def print_variants(pkg, args):
|
||||
"""output variants"""
|
||||
if args.variants_by_name:
|
||||
print_variants_by_name(pkg)
|
||||
else:
|
||||
print_variants_grouped_by_when(pkg)
|
||||
|
||||
|
||||
def print_versions(pkg, args):
|
||||
"""output versions"""
|
||||
|
||||
color.cprint("")
|
||||
@@ -300,18 +433,24 @@ def print_versions(pkg):
|
||||
pad = padder(pkg.versions, 4)
|
||||
|
||||
preferred = preferred_version(pkg)
|
||||
url = ""
|
||||
if pkg.has_code:
|
||||
url = fs.for_package_version(pkg, preferred)
|
||||
|
||||
def get_url(version):
|
||||
try:
|
||||
return fs.for_package_version(pkg, version)
|
||||
except spack.fetch_strategy.InvalidArgsError:
|
||||
return "No URL"
|
||||
|
||||
url = get_url(preferred) if pkg.has_code else ""
|
||||
line = version(" {0}".format(pad(preferred))) + color.cescape(url)
|
||||
color.cprint(line)
|
||||
color.cwrite(line)
|
||||
|
||||
print()
|
||||
|
||||
safe = []
|
||||
deprecated = []
|
||||
for v in reversed(sorted(pkg.versions)):
|
||||
if pkg.has_code:
|
||||
url = fs.for_package_version(pkg, v)
|
||||
url = get_url(v)
|
||||
if pkg.versions[v].get("deprecated", False):
|
||||
deprecated.append((v, url))
|
||||
else:
|
||||
@@ -329,7 +468,7 @@ def print_versions(pkg):
|
||||
color.cprint(line)
|
||||
|
||||
|
||||
def print_virtuals(pkg):
|
||||
def print_virtuals(pkg, args):
|
||||
"""output virtual packages"""
|
||||
|
||||
color.cprint("")
|
||||
@@ -352,7 +491,7 @@ def print_virtuals(pkg):
|
||||
color.cprint(" None")
|
||||
|
||||
|
||||
def print_licenses(pkg):
|
||||
def print_licenses(pkg, args):
|
||||
"""Output the licenses of the project."""
|
||||
|
||||
color.cprint("")
|
||||
@@ -384,7 +523,8 @@ def info(parser, args):
|
||||
else:
|
||||
color.cprint(" None")
|
||||
|
||||
color.cprint(section_title("Homepage: ") + pkg.homepage)
|
||||
if getattr(pkg, "homepage"):
|
||||
color.cprint(section_title("Homepage: ") + pkg.homepage)
|
||||
|
||||
# Now output optional information in expected order
|
||||
sections = [
|
||||
@@ -401,6 +541,6 @@ def info(parser, args):
|
||||
]
|
||||
for print_it, func in sections:
|
||||
if print_it:
|
||||
func(pkg)
|
||||
func(pkg, args)
|
||||
|
||||
color.cprint("")
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
|
||||
# tutorial configuration parameters
|
||||
tutorial_branch = "releases/v0.20"
|
||||
tutorial_branch = "releases/v0.21"
|
||||
tutorial_mirror = "file:///mirror"
|
||||
tutorial_key = os.path.join(spack.paths.share_path, "keys", "tutorial.pub")
|
||||
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
|
||||
import spack.cmd
|
||||
import spack.cmd.common.arguments as arguments
|
||||
import spack.cmd.common.confirmation as confirmation
|
||||
import spack.environment as ev
|
||||
import spack.error
|
||||
import spack.package_base
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
import spack.store
|
||||
import spack.traverse as traverse
|
||||
@@ -278,7 +277,7 @@ def uninstall_specs(args, specs):
|
||||
return
|
||||
|
||||
if not args.yes_to_all:
|
||||
confirm_removal(uninstall_list)
|
||||
confirmation.confirm_action(uninstall_list, "uninstalled", "uninstallation")
|
||||
|
||||
# Uninstall everything on the list
|
||||
do_uninstall(uninstall_list, args.force)
|
||||
@@ -292,21 +291,6 @@ def uninstall_specs(args, specs):
|
||||
env.regenerate_views()
|
||||
|
||||
|
||||
def confirm_removal(specs: List[spack.spec.Spec]):
|
||||
"""Display the list of specs to be removed and ask for confirmation.
|
||||
|
||||
Args:
|
||||
specs: specs to be removed
|
||||
"""
|
||||
tty.msg("The following {} packages will be uninstalled:\n".format(len(specs)))
|
||||
spack.cmd.display_specs(specs, **display_args)
|
||||
print("")
|
||||
answer = tty.get_yes_or_no("Do you want to proceed?", default=False)
|
||||
if not answer:
|
||||
tty.msg("Aborting uninstallation")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def uninstall(parser, args):
|
||||
if not args.specs and not args.all:
|
||||
tty.die(
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import itertools
|
||||
import multiprocessing.pool
|
||||
import os
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import spack.compiler
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.operating_systems
|
||||
import spack.paths
|
||||
import spack.platforms
|
||||
import spack.spec
|
||||
@@ -223,13 +224,16 @@ def all_compiler_specs(scope=None, init_config=True):
|
||||
]
|
||||
|
||||
|
||||
def find_compilers(path_hints=None):
|
||||
def find_compilers(
|
||||
path_hints: Optional[List[str]] = None, *, mixed_toolchain=False
|
||||
) -> List["spack.compiler.Compiler"]:
|
||||
"""Return the list of compilers found in the paths given as arguments.
|
||||
|
||||
Args:
|
||||
path_hints (list or None): list of path hints where to look for.
|
||||
A sensible default based on the ``PATH`` environment variable
|
||||
will be used if the value is None
|
||||
path_hints: list of path hints where to look for. A sensible default based on the ``PATH``
|
||||
environment variable will be used if the value is None
|
||||
mixed_toolchain: allow mixing compilers from different toolchains if otherwise missing for
|
||||
a certain language
|
||||
"""
|
||||
if path_hints is None:
|
||||
path_hints = get_path("PATH")
|
||||
@@ -250,7 +254,7 @@ def find_compilers(path_hints=None):
|
||||
finally:
|
||||
tp.close()
|
||||
|
||||
def valid_version(item):
|
||||
def valid_version(item: Tuple[Optional[DetectVersionArgs], Optional[str]]) -> bool:
|
||||
value, error = item
|
||||
if error is None:
|
||||
return True
|
||||
@@ -262,25 +266,37 @@ def valid_version(item):
|
||||
pass
|
||||
return False
|
||||
|
||||
def remove_errors(item):
|
||||
def remove_errors(
|
||||
item: Tuple[Optional[DetectVersionArgs], Optional[str]]
|
||||
) -> DetectVersionArgs:
|
||||
value, _ = item
|
||||
assert value is not None
|
||||
return value
|
||||
|
||||
return make_compiler_list(map(remove_errors, filter(valid_version, detected_versions)))
|
||||
return make_compiler_list(
|
||||
[remove_errors(detected) for detected in detected_versions if valid_version(detected)],
|
||||
mixed_toolchain=mixed_toolchain,
|
||||
)
|
||||
|
||||
|
||||
def find_new_compilers(path_hints=None, scope=None):
|
||||
def find_new_compilers(
|
||||
path_hints: Optional[List[str]] = None,
|
||||
scope: Optional[str] = None,
|
||||
*,
|
||||
mixed_toolchain: bool = False,
|
||||
):
|
||||
"""Same as ``find_compilers`` but return only the compilers that are not
|
||||
already in compilers.yaml.
|
||||
|
||||
Args:
|
||||
path_hints (list or None): list of path hints where to look for.
|
||||
A sensible default based on the ``PATH`` environment variable
|
||||
will be used if the value is None
|
||||
scope (str): scope to look for a compiler. If None consider the
|
||||
merged configuration.
|
||||
path_hints: list of path hints where to look for. A sensible default based on the ``PATH``
|
||||
environment variable will be used if the value is None
|
||||
scope: scope to look for a compiler. If None consider the merged configuration.
|
||||
mixed_toolchain: allow mixing compilers from different toolchains if otherwise missing for
|
||||
a certain language
|
||||
"""
|
||||
compilers = find_compilers(path_hints)
|
||||
compilers = find_compilers(path_hints, mixed_toolchain=mixed_toolchain)
|
||||
|
||||
return select_new_compilers(compilers, scope)
|
||||
|
||||
|
||||
@@ -638,7 +654,9 @@ def all_compiler_types():
|
||||
)
|
||||
|
||||
|
||||
def arguments_to_detect_version_fn(operating_system, paths):
|
||||
def arguments_to_detect_version_fn(
|
||||
operating_system: spack.operating_systems.OperatingSystem, paths: List[str]
|
||||
) -> List[DetectVersionArgs]:
|
||||
"""Returns a list of DetectVersionArgs tuples to be used in a
|
||||
corresponding function to detect compiler versions.
|
||||
|
||||
@@ -646,8 +664,7 @@ def arguments_to_detect_version_fn(operating_system, paths):
|
||||
function by providing a method called with the same name.
|
||||
|
||||
Args:
|
||||
operating_system (spack.operating_systems.OperatingSystem): the operating system
|
||||
on which we are looking for compilers
|
||||
operating_system: the operating system on which we are looking for compilers
|
||||
paths: paths to search for compilers
|
||||
|
||||
Returns:
|
||||
@@ -656,10 +673,10 @@ def arguments_to_detect_version_fn(operating_system, paths):
|
||||
compilers in this OS.
|
||||
"""
|
||||
|
||||
def _default(search_paths):
|
||||
command_arguments = []
|
||||
def _default(search_paths: List[str]) -> List[DetectVersionArgs]:
|
||||
command_arguments: List[DetectVersionArgs] = []
|
||||
files_to_be_tested = fs.files_in(*search_paths)
|
||||
for compiler_name in spack.compilers.supported_compilers_for_host_platform():
|
||||
for compiler_name in supported_compilers_for_host_platform():
|
||||
compiler_cls = class_for_compiler_name(compiler_name)
|
||||
|
||||
for language in ("cc", "cxx", "f77", "fc"):
|
||||
@@ -684,7 +701,9 @@ def _default(search_paths):
|
||||
return fn(paths)
|
||||
|
||||
|
||||
def detect_version(detect_version_args):
|
||||
def detect_version(
|
||||
detect_version_args: DetectVersionArgs,
|
||||
) -> Tuple[Optional[DetectVersionArgs], Optional[str]]:
|
||||
"""Computes the version of a compiler and adds it to the information
|
||||
passed as input.
|
||||
|
||||
@@ -693,8 +712,7 @@ def detect_version(detect_version_args):
|
||||
needs to be checked by the code dispatching the calls.
|
||||
|
||||
Args:
|
||||
detect_version_args (DetectVersionArgs): information on the
|
||||
compiler for which we should detect the version.
|
||||
detect_version_args: information on the compiler for which we should detect the version.
|
||||
|
||||
Returns:
|
||||
A ``(DetectVersionArgs, error)`` tuple. If ``error`` is ``None`` the
|
||||
@@ -710,7 +728,7 @@ def _default(fn_args):
|
||||
path = fn_args.path
|
||||
|
||||
# Get compiler names and the callback to detect their versions
|
||||
callback = getattr(compiler_cls, "{0}_version".format(language))
|
||||
callback = getattr(compiler_cls, f"{language}_version")
|
||||
|
||||
try:
|
||||
version = callback(path)
|
||||
@@ -736,13 +754,15 @@ def _default(fn_args):
|
||||
return fn(detect_version_args)
|
||||
|
||||
|
||||
def make_compiler_list(detected_versions):
|
||||
def make_compiler_list(
|
||||
detected_versions: List[DetectVersionArgs], mixed_toolchain: bool = False
|
||||
) -> List["spack.compiler.Compiler"]:
|
||||
"""Process a list of detected versions and turn them into a list of
|
||||
compiler specs.
|
||||
|
||||
Args:
|
||||
detected_versions (list): list of DetectVersionArgs containing a
|
||||
valid version
|
||||
detected_versions: list of DetectVersionArgs containing a valid version
|
||||
mixed_toolchain: allow mixing compilers from different toolchains if langauge is missing
|
||||
|
||||
Returns:
|
||||
list: list of Compiler objects
|
||||
@@ -751,7 +771,7 @@ def make_compiler_list(detected_versions):
|
||||
sorted_compilers = sorted(detected_versions, key=group_fn)
|
||||
|
||||
# Gather items in a dictionary by the id, name variation and language
|
||||
compilers_d = {}
|
||||
compilers_d: Dict[CompilerID, Dict[NameVariation, dict]] = {}
|
||||
for sort_key, group in itertools.groupby(sorted_compilers, key=group_fn):
|
||||
compiler_id, name_variation, language = sort_key
|
||||
by_compiler_id = compilers_d.setdefault(compiler_id, {})
|
||||
@@ -760,7 +780,7 @@ def make_compiler_list(detected_versions):
|
||||
|
||||
def _default_make_compilers(cmp_id, paths):
|
||||
operating_system, compiler_name, version = cmp_id
|
||||
compiler_cls = spack.compilers.class_for_compiler_name(compiler_name)
|
||||
compiler_cls = class_for_compiler_name(compiler_name)
|
||||
spec = spack.spec.CompilerSpec(compiler_cls.name, f"={version}")
|
||||
paths = [paths.get(x, None) for x in ("cc", "cxx", "f77", "fc")]
|
||||
# TODO: johnwparent - revist the following line as per discussion at:
|
||||
@@ -782,13 +802,14 @@ def _default_make_compilers(cmp_id, paths):
|
||||
getattr(variation, "suffix", None),
|
||||
)
|
||||
|
||||
compilers = []
|
||||
# Flatten to a list of compiler id, primary variation and compiler dictionary
|
||||
flat_compilers: List[Tuple[CompilerID, NameVariation, dict]] = []
|
||||
for compiler_id, by_compiler_id in compilers_d.items():
|
||||
ordered = sorted(by_compiler_id, key=sort_fn)
|
||||
selected_variation = ordered[0]
|
||||
selected = by_compiler_id[selected_variation]
|
||||
|
||||
# fill any missing parts from subsequent entries
|
||||
# Fill any missing parts from subsequent entries (without mixing toolchains)
|
||||
for lang in ["cxx", "f77", "fc"]:
|
||||
if lang not in selected:
|
||||
next_lang = next(
|
||||
@@ -797,14 +818,63 @@ def _default_make_compilers(cmp_id, paths):
|
||||
if next_lang:
|
||||
selected[lang] = next_lang
|
||||
|
||||
operating_system, _, _ = compiler_id
|
||||
make_compilers = getattr(operating_system, "make_compilers", _default_make_compilers)
|
||||
flat_compilers.append((compiler_id, selected_variation, selected))
|
||||
|
||||
compilers.extend(make_compilers(compiler_id, selected))
|
||||
# Next, fill out the blanks of missing compilers by creating a mixed toolchain (if requested)
|
||||
if mixed_toolchain:
|
||||
make_mixed_toolchain(flat_compilers)
|
||||
|
||||
# Finally, create the compiler list
|
||||
compilers = []
|
||||
for compiler_id, _, compiler in flat_compilers:
|
||||
make_compilers = getattr(compiler_id.os, "make_compilers", _default_make_compilers)
|
||||
compilers.extend(make_compilers(compiler_id, compiler))
|
||||
|
||||
return compilers
|
||||
|
||||
|
||||
def make_mixed_toolchain(compilers: List[Tuple[CompilerID, NameVariation, dict]]) -> None:
|
||||
"""Add missing compilers across toolchains when they are missing for a particular language.
|
||||
This currently only adds the most sensible gfortran to (apple)-clang if it doesn't have a
|
||||
fortran compiler (no flang)."""
|
||||
|
||||
# First collect the clangs that are missing a fortran compiler
|
||||
clangs_without_flang = [
|
||||
(id, variation, compiler)
|
||||
for id, variation, compiler in compilers
|
||||
if id.compiler_name in ("clang", "apple-clang")
|
||||
and "f77" not in compiler
|
||||
and "fc" not in compiler
|
||||
]
|
||||
if not clangs_without_flang:
|
||||
return
|
||||
|
||||
# Filter on GCCs with fortran compiler
|
||||
gccs_with_fortran = [
|
||||
(id, variation, compiler)
|
||||
for id, variation, compiler in compilers
|
||||
if id.compiler_name == "gcc" and "f77" in compiler and "fc" in compiler
|
||||
]
|
||||
|
||||
# Sort these GCCs by "best variation" (no prefix / suffix first)
|
||||
gccs_with_fortran.sort(
|
||||
key=lambda x: (getattr(x[1], "prefix", None), getattr(x[1], "suffix", None))
|
||||
)
|
||||
|
||||
# Attach the optimal GCC fortran compiler to the clangs that don't have one
|
||||
for clang_id, _, clang_compiler in clangs_without_flang:
|
||||
gcc_compiler = next(
|
||||
(gcc[2] for gcc in gccs_with_fortran if gcc[0].os == clang_id.os), None
|
||||
)
|
||||
|
||||
if not gcc_compiler:
|
||||
continue
|
||||
|
||||
# Update the fc / f77 entries
|
||||
clang_compiler["f77"] = gcc_compiler["f77"]
|
||||
clang_compiler["fc"] = gcc_compiler["fc"]
|
||||
|
||||
|
||||
def is_mixed_toolchain(compiler):
|
||||
"""Returns True if the current compiler is a mixed toolchain,
|
||||
False otherwise.
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
@@ -41,7 +40,6 @@ def debug_flags(self):
|
||||
"-gdwarf-5",
|
||||
"-gline-tables-only",
|
||||
"-gmodules",
|
||||
"-gz",
|
||||
"-g",
|
||||
]
|
||||
|
||||
@@ -114,17 +112,6 @@ def extract_version_from_output(cls, output):
|
||||
return ".".join(match.groups())
|
||||
return "unknown"
|
||||
|
||||
@classmethod
|
||||
def fc_version(cls, fortran_compiler):
|
||||
if sys.platform == "darwin":
|
||||
return cls.default_version("clang")
|
||||
|
||||
return cls.default_version(fortran_compiler)
|
||||
|
||||
@classmethod
|
||||
def f77_version(cls, f77):
|
||||
return cls.fc_version(f77)
|
||||
|
||||
@property
|
||||
def stdcxx_libs(self):
|
||||
return ("-lstdc++",)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
@@ -39,10 +38,10 @@ class Clang(Compiler):
|
||||
cxx_names = ["clang++"]
|
||||
|
||||
# Subclasses use possible names of Fortran 77 compiler
|
||||
f77_names = ["flang", "gfortran", "xlf_r"]
|
||||
f77_names = ["flang"]
|
||||
|
||||
# Subclasses use possible names of Fortran 90 compiler
|
||||
fc_names = ["flang", "gfortran", "xlf90_r"]
|
||||
fc_names = ["flang"]
|
||||
|
||||
version_argument = "--version"
|
||||
|
||||
@@ -56,7 +55,6 @@ def debug_flags(self):
|
||||
"-gdwarf-5",
|
||||
"-gline-tables-only",
|
||||
"-gmodules",
|
||||
"-gz",
|
||||
"-g",
|
||||
]
|
||||
|
||||
@@ -182,16 +180,3 @@ def extract_version_from_output(cls, output):
|
||||
if match:
|
||||
ver = match.group(match.lastindex)
|
||||
return ver
|
||||
|
||||
@classmethod
|
||||
def fc_version(cls, fc):
|
||||
# We could map from gcc/gfortran version to clang version, but on macOS
|
||||
# we normally mix any version of gfortran with any version of clang.
|
||||
if sys.platform == "darwin":
|
||||
return cls.default_version("clang")
|
||||
else:
|
||||
return cls.default_version(fc)
|
||||
|
||||
@classmethod
|
||||
def f77_version(cls, f77):
|
||||
return cls.fc_version(f77)
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
SECTION_SCHEMAS = {
|
||||
"compilers": spack.schema.compilers.schema,
|
||||
"concretizer": spack.schema.concretizer.schema,
|
||||
"definitions": spack.schema.definitions.schema,
|
||||
"mirrors": spack.schema.mirrors.schema,
|
||||
"repos": spack.schema.repos.schema,
|
||||
"packages": spack.schema.packages.schema,
|
||||
@@ -994,6 +995,7 @@ def read_config_file(filename, schema=None):
|
||||
key = next(iter(data))
|
||||
schema = _ALL_SCHEMAS[key]
|
||||
validate(data, schema)
|
||||
|
||||
return data
|
||||
|
||||
except StopIteration:
|
||||
|
||||
@@ -1522,14 +1522,18 @@ def _query(
|
||||
# TODO: like installed and known that can be queried? Or are
|
||||
# TODO: these really special cases that only belong here?
|
||||
|
||||
# Just look up concrete specs with hashes; no fancy search.
|
||||
if isinstance(query_spec, spack.spec.Spec) and query_spec.concrete:
|
||||
# TODO: handling of hashes restriction is not particularly elegant.
|
||||
hash_key = query_spec.dag_hash()
|
||||
if hash_key in self._data and (not hashes or hash_key in hashes):
|
||||
return [self._data[hash_key].spec]
|
||||
else:
|
||||
return []
|
||||
if query_spec is not any:
|
||||
if not isinstance(query_spec, spack.spec.Spec):
|
||||
query_spec = spack.spec.Spec(query_spec)
|
||||
|
||||
# Just look up concrete specs with hashes; no fancy search.
|
||||
if query_spec.concrete:
|
||||
# TODO: handling of hashes restriction is not particularly elegant.
|
||||
hash_key = query_spec.dag_hash()
|
||||
if hash_key in self._data and (not hashes or hash_key in hashes):
|
||||
return [self._data[hash_key].spec]
|
||||
else:
|
||||
return []
|
||||
|
||||
# Abstract specs require more work -- currently we test
|
||||
# against everything.
|
||||
@@ -1537,6 +1541,9 @@ def _query(
|
||||
start_date = start_date or datetime.datetime.min
|
||||
end_date = end_date or datetime.datetime.max
|
||||
|
||||
# save specs whose name doesn't match for last, to avoid a virtual check
|
||||
deferred = []
|
||||
|
||||
for key, rec in self._data.items():
|
||||
if hashes is not None and rec.spec.dag_hash() not in hashes:
|
||||
continue
|
||||
@@ -1561,8 +1568,26 @@ def _query(
|
||||
if not (start_date < inst_date < end_date):
|
||||
continue
|
||||
|
||||
if query_spec is any or rec.spec.satisfies(query_spec):
|
||||
if query_spec is any:
|
||||
results.append(rec.spec)
|
||||
continue
|
||||
|
||||
# check anon specs and exact name matches first
|
||||
if not query_spec.name or rec.spec.name == query_spec.name:
|
||||
if rec.spec.satisfies(query_spec):
|
||||
results.append(rec.spec)
|
||||
|
||||
# save potential virtual matches for later, but not if we already found a match
|
||||
elif not results:
|
||||
deferred.append(rec.spec)
|
||||
|
||||
# Checking for virtuals is expensive, so we save it for last and only if needed.
|
||||
# If we get here, we didn't find anything in the DB that matched by name.
|
||||
# If we did fine something, the query spec can't be virtual b/c we matched an actual
|
||||
# package installation, so skip the virtual check entirely. If we *didn't* find anything,
|
||||
# check all the deferred specs *if* the query is virtual.
|
||||
if not results and query_spec is not any and deferred and query_spec.virtual:
|
||||
results = [spec for spec in deferred if spec.satisfies(query_spec)]
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ class DirectiveMeta(type):
|
||||
_directive_dict_names: Set[str] = set()
|
||||
_directives_to_be_executed: List[str] = []
|
||||
_when_constraints_from_context: List[str] = []
|
||||
_default_args: List[dict] = []
|
||||
|
||||
def __new__(cls, name, bases, attr_dict):
|
||||
# Initialize the attribute containing the list of directives
|
||||
@@ -199,6 +200,16 @@ def pop_from_context():
|
||||
"""Pop the last constraint from the context"""
|
||||
return DirectiveMeta._when_constraints_from_context.pop()
|
||||
|
||||
@staticmethod
|
||||
def push_default_args(default_args):
|
||||
"""Push default arguments"""
|
||||
DirectiveMeta._default_args.append(default_args)
|
||||
|
||||
@staticmethod
|
||||
def pop_default_args():
|
||||
"""Pop default arguments"""
|
||||
return DirectiveMeta._default_args.pop()
|
||||
|
||||
@staticmethod
|
||||
def directive(dicts=None):
|
||||
"""Decorator for Spack directives.
|
||||
@@ -259,7 +270,13 @@ def _decorator(decorated_function):
|
||||
directive_names.append(decorated_function.__name__)
|
||||
|
||||
@functools.wraps(decorated_function)
|
||||
def _wrapper(*args, **kwargs):
|
||||
def _wrapper(*args, **_kwargs):
|
||||
# First merge default args with kwargs
|
||||
kwargs = dict()
|
||||
for default_args in DirectiveMeta._default_args:
|
||||
kwargs.update(default_args)
|
||||
kwargs.update(_kwargs)
|
||||
|
||||
# Inject when arguments from the context
|
||||
if DirectiveMeta._when_constraints_from_context:
|
||||
# Check that directives not yet supporting the when= argument
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
from .environment import (
|
||||
TOP_LEVEL_KEY,
|
||||
Environment,
|
||||
SpackEnvironmentConfigError,
|
||||
SpackEnvironmentError,
|
||||
SpackEnvironmentViewError,
|
||||
activate,
|
||||
@@ -372,6 +373,7 @@
|
||||
__all__ = [
|
||||
"TOP_LEVEL_KEY",
|
||||
"Environment",
|
||||
"SpackEnvironmentConfigError",
|
||||
"SpackEnvironmentError",
|
||||
"SpackEnvironmentViewError",
|
||||
"activate",
|
||||
|
||||
@@ -342,7 +342,7 @@ def create_in_dir(
|
||||
|
||||
manifest.flush()
|
||||
|
||||
except spack.config.ConfigFormatError as e:
|
||||
except (spack.config.ConfigFormatError, SpackEnvironmentConfigError) as e:
|
||||
shutil.rmtree(manifest_dir)
|
||||
raise e
|
||||
|
||||
@@ -396,7 +396,13 @@ def all_environments():
|
||||
|
||||
def _read_yaml(str_or_file):
|
||||
"""Read YAML from a file for round-trip parsing."""
|
||||
data = syaml.load_config(str_or_file)
|
||||
try:
|
||||
data = syaml.load_config(str_or_file)
|
||||
except syaml.SpackYAMLError as e:
|
||||
raise SpackEnvironmentConfigError(
|
||||
f"Invalid environment configuration detected: {e.message}"
|
||||
)
|
||||
|
||||
filename = getattr(str_or_file, "name", None)
|
||||
default_data = spack.config.validate(data, spack.schema.env.schema, filename)
|
||||
return data, default_data
|
||||
@@ -781,10 +787,18 @@ def _re_read(self):
|
||||
"""Reinitialize the environment object."""
|
||||
self.clear(re_read=True)
|
||||
self.manifest = EnvironmentManifestFile(self.path)
|
||||
self._read()
|
||||
self._read(re_read=True)
|
||||
|
||||
def _read(self):
|
||||
self._construct_state_from_manifest()
|
||||
def _read(self, re_read=False):
|
||||
# If the manifest has included files, then some of the information
|
||||
# (e.g., definitions) MAY be in those files. So we need to ensure
|
||||
# the config is populated with any associated spec lists in order
|
||||
# to fully construct the manifest state.
|
||||
includes = self.manifest[TOP_LEVEL_KEY].get("include", [])
|
||||
if includes and not re_read:
|
||||
prepare_config_scope(self)
|
||||
|
||||
self._construct_state_from_manifest(re_read)
|
||||
|
||||
if os.path.exists(self.lock_path):
|
||||
with open(self.lock_path) as f:
|
||||
@@ -798,21 +812,30 @@ 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 _construct_state_from_manifest(self):
|
||||
def _process_definition(self, item):
|
||||
"""Process a single spec definition item."""
|
||||
entry = copy.deepcopy(item)
|
||||
when = _eval_conditional(entry.pop("when", "True"))
|
||||
assert len(entry) == 1
|
||||
if when:
|
||||
name, spec_list = next(iter(entry.items()))
|
||||
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 _construct_state_from_manifest(self, re_read=False):
|
||||
"""Read manifest file and set up user specs."""
|
||||
self.spec_lists = collections.OrderedDict()
|
||||
|
||||
if not re_read:
|
||||
for item in spack.config.get("definitions", []):
|
||||
self._process_definition(item)
|
||||
|
||||
env_configuration = self.manifest[TOP_LEVEL_KEY]
|
||||
for item in env_configuration.get("definitions", []):
|
||||
entry = copy.deepcopy(item)
|
||||
when = _eval_conditional(entry.pop("when", "True"))
|
||||
assert len(entry) == 1
|
||||
if when:
|
||||
name, spec_list = next(iter(entry.items()))
|
||||
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
|
||||
self._process_definition(item)
|
||||
|
||||
spec_list = env_configuration.get(user_speclist_name, [])
|
||||
user_specs = SpecList(
|
||||
@@ -857,7 +880,9 @@ def clear(self, re_read=False):
|
||||
yaml, and need to be maintained when re-reading an existing
|
||||
environment.
|
||||
"""
|
||||
self.spec_lists = {user_speclist_name: SpecList()} # specs from yaml
|
||||
self.spec_lists = collections.OrderedDict()
|
||||
self.spec_lists[user_speclist_name] = SpecList()
|
||||
|
||||
self.dev_specs = {} # dev-build specs from yaml
|
||||
self.concretized_user_specs = [] # user specs from last concretize
|
||||
self.concretized_order = [] # roots of last concretize, in order
|
||||
@@ -1006,7 +1031,8 @@ def included_config_scopes(self):
|
||||
|
||||
elif include_url.scheme:
|
||||
raise ValueError(
|
||||
"Unsupported URL scheme for environment include: {}".format(config_path)
|
||||
f"Unsupported URL scheme ({include_url.scheme}) for "
|
||||
f"environment include: {config_path}"
|
||||
)
|
||||
|
||||
# treat relative paths as relative to the environment
|
||||
@@ -1068,8 +1094,10 @@ def update_stale_references(self, from_list=None):
|
||||
from_list = next(iter(self.spec_lists.keys()))
|
||||
index = list(self.spec_lists.keys()).index(from_list)
|
||||
|
||||
# spec_lists is an OrderedDict, all list entries after the modified
|
||||
# list may refer to the modified list. Update stale references
|
||||
# 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
|
||||
):
|
||||
@@ -1167,7 +1195,7 @@ def change_existing_spec(
|
||||
def remove(self, query_spec, list_name=user_speclist_name, force=False):
|
||||
"""Remove specs from an environment that match a query_spec"""
|
||||
err_msg_header = (
|
||||
f"cannot remove {query_spec} from '{list_name}' definition "
|
||||
f"Cannot remove '{query_spec}' from '{list_name}' definition "
|
||||
f"in {self.manifest.manifest_file}"
|
||||
)
|
||||
query_spec = Spec(query_spec)
|
||||
@@ -1198,11 +1226,10 @@ def remove(self, query_spec, list_name=user_speclist_name, force=False):
|
||||
list_to_change.remove(spec)
|
||||
self.update_stale_references(list_name)
|
||||
new_specs = set(self.user_specs)
|
||||
except spack.spec_list.SpecListError:
|
||||
except spack.spec_list.SpecListError as e:
|
||||
# define new specs list
|
||||
new_specs = set(self.user_specs)
|
||||
msg = f"Spec '{spec}' is part of a spec matrix and "
|
||||
msg += f"cannot be removed from list '{list_to_change}'."
|
||||
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
|
||||
@@ -1331,7 +1358,7 @@ def concretize(self, force=False, tests=False):
|
||||
|
||||
# Remove concrete specs that no longer correlate to a user spec
|
||||
for spec in set(self.concretized_user_specs) - set(self.user_specs):
|
||||
self.deconcretize(spec)
|
||||
self.deconcretize(spec, concrete=False)
|
||||
|
||||
# Pick the right concretization strategy
|
||||
if self.unify == "when_possible":
|
||||
@@ -1346,15 +1373,36 @@ def concretize(self, force=False, tests=False):
|
||||
msg = "concretization strategy not implemented [{0}]"
|
||||
raise SpackEnvironmentError(msg.format(self.unify))
|
||||
|
||||
def deconcretize(self, spec):
|
||||
def deconcretize(self, spec: spack.spec.Spec, concrete: bool = True):
|
||||
"""
|
||||
Remove specified spec from environment concretization
|
||||
|
||||
Arguments:
|
||||
spec: Spec to deconcretize. This must be a root of the environment
|
||||
concrete: If True, find all instances of spec as concrete in the environemnt.
|
||||
If False, find a single instance of the abstract spec as root of the environment.
|
||||
"""
|
||||
# spec has to be a root of the environment
|
||||
index = self.concretized_user_specs.index(spec)
|
||||
dag_hash = self.concretized_order.pop(index)
|
||||
del self.concretized_user_specs[index]
|
||||
if concrete:
|
||||
dag_hash = spec.dag_hash()
|
||||
|
||||
pairs = zip(self.concretized_user_specs, self.concretized_order)
|
||||
filtered = [(spec, h) for spec, h in pairs if h != dag_hash]
|
||||
# Cannot use zip and unpack two values; it fails if filtered is empty
|
||||
self.concretized_user_specs = [s for s, _ in filtered]
|
||||
self.concretized_order = [h for _, h in filtered]
|
||||
else:
|
||||
index = self.concretized_user_specs.index(spec)
|
||||
dag_hash = self.concretized_order.pop(index)
|
||||
|
||||
del self.concretized_user_specs[index]
|
||||
|
||||
# If this was the only user spec that concretized to this concrete spec, remove it
|
||||
if dag_hash not in self.concretized_order:
|
||||
del self.specs_by_hash[dag_hash]
|
||||
# if we deconcretized a dependency that doesn't correspond to a root, it
|
||||
# won't be here.
|
||||
if dag_hash in self.specs_by_hash:
|
||||
del self.specs_by_hash[dag_hash]
|
||||
|
||||
def _get_specs_to_concretize(
|
||||
self,
|
||||
@@ -1525,7 +1573,11 @@ def _concretize_separately(self, tests=False):
|
||||
batch = []
|
||||
for j, (i, concrete, duration) in enumerate(
|
||||
spack.util.parallel.imap_unordered(
|
||||
_concretize_task, args, processes=num_procs, debug=tty.is_debug()
|
||||
_concretize_task,
|
||||
args,
|
||||
processes=num_procs,
|
||||
debug=tty.is_debug(),
|
||||
maxtaskperchild=1,
|
||||
)
|
||||
):
|
||||
batch.append((i, concrete))
|
||||
@@ -1708,11 +1760,14 @@ def _env_modifications_for_view(
|
||||
self, view: ViewDescriptor, reverse: bool = False
|
||||
) -> spack.util.environment.EnvironmentModifications:
|
||||
try:
|
||||
mods = uenv.environment_modifications_for_specs(*self.concrete_roots(), view=view)
|
||||
with spack.store.STORE.db.read_transaction():
|
||||
installed_roots = [s for s in self.concrete_roots() if s.installed]
|
||||
mods = uenv.environment_modifications_for_specs(*installed_roots, view=view)
|
||||
except Exception as e:
|
||||
# Failing to setup spec-specific changes shouldn't be a hard error.
|
||||
tty.warn(
|
||||
"couldn't load runtime environment due to {}: {}".format(e.__class__.__name__, e)
|
||||
f"could not {'unload' if reverse else 'load'} runtime environment due "
|
||||
f"to {e.__class__.__name__}: {e}"
|
||||
)
|
||||
return spack.util.environment.EnvironmentModifications()
|
||||
return mods.reversed() if reverse else mods
|
||||
@@ -2063,7 +2118,7 @@ def matching_spec(self, spec):
|
||||
|
||||
def removed_specs(self):
|
||||
"""Tuples of (user spec, concrete spec) for all specs that will be
|
||||
removed on nexg concretize."""
|
||||
removed on next concretize."""
|
||||
needed = set()
|
||||
for s, c in self.concretized_specs():
|
||||
if s in self.user_specs:
|
||||
@@ -2722,7 +2777,7 @@ def override_user_spec(self, user_spec: str, idx: int) -> None:
|
||||
self.changed = True
|
||||
|
||||
def add_definition(self, user_spec: str, list_name: str) -> None:
|
||||
"""Appends a user spec to the first active definition mathing the name passed as argument.
|
||||
"""Appends a user spec to the first active definition matching the name passed as argument.
|
||||
|
||||
Args:
|
||||
user_spec: user spec to be appended
|
||||
@@ -2935,3 +2990,7 @@ class SpackEnvironmentError(spack.error.SpackError):
|
||||
|
||||
class SpackEnvironmentViewError(SpackEnvironmentError):
|
||||
"""Class for errors regarding view generation."""
|
||||
|
||||
|
||||
class SpackEnvironmentConfigError(SpackEnvironmentError):
|
||||
"""Class for Spack environment-specific configuration errors."""
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"""Service functions and classes to implement the hooks
|
||||
for Spack's command extensions.
|
||||
"""
|
||||
import difflib
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
@@ -176,10 +177,19 @@ class CommandNotFoundError(spack.error.SpackError):
|
||||
"""
|
||||
|
||||
def __init__(self, cmd_name):
|
||||
super().__init__(
|
||||
msg = (
|
||||
"{0} is not a recognized Spack command or extension command;"
|
||||
" check with `spack commands`.".format(cmd_name)
|
||||
)
|
||||
long_msg = None
|
||||
|
||||
similar = difflib.get_close_matches(cmd_name, spack.cmd.all_commands())
|
||||
|
||||
if 1 <= len(similar) <= 5:
|
||||
long_msg = "\nDid you mean one of the following commands?\n "
|
||||
long_msg += "\n ".join(similar)
|
||||
|
||||
super().__init__(msg, long_msg)
|
||||
|
||||
|
||||
class ExtensionNamingError(spack.error.SpackError):
|
||||
|
||||
@@ -3,17 +3,22 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
from typing import Optional, Set
|
||||
|
||||
from llnl.util import tty
|
||||
|
||||
import spack.config
|
||||
import spack.modules
|
||||
import spack.spec
|
||||
|
||||
|
||||
def _for_each_enabled(spec, method_name, explicit=None):
|
||||
def _for_each_enabled(
|
||||
spec: spack.spec.Spec, method_name: str, explicit: Optional[bool] = None
|
||||
) -> None:
|
||||
"""Calls a method for each enabled module"""
|
||||
set_names = set(spack.config.get("modules", {}).keys())
|
||||
set_names: Set[str] = set(spack.config.get("modules", {}).keys())
|
||||
for name in set_names:
|
||||
enabled = spack.config.get("modules:%s:enable" % name)
|
||||
enabled = spack.config.get(f"modules:{name}:enable")
|
||||
if not enabled:
|
||||
tty.debug("NO MODULE WRITTEN: list of enabled module files is empty")
|
||||
continue
|
||||
@@ -28,7 +33,7 @@ def _for_each_enabled(spec, method_name, explicit=None):
|
||||
tty.warn(msg.format(method_name, str(e)))
|
||||
|
||||
|
||||
def post_install(spec, explicit):
|
||||
def post_install(spec, explicit: bool):
|
||||
import spack.environment as ev # break import cycle
|
||||
|
||||
if ev.active_environment():
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
import os.path
|
||||
import pstats
|
||||
import re
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess as sp
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
from typing import List, Tuple
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
@@ -49,9 +51,6 @@
|
||||
#: names of profile statistics
|
||||
stat_names = pstats.Stats.sort_arg_dict_default
|
||||
|
||||
#: top-level aliases for Spack commands
|
||||
aliases = {"concretise": "concretize", "containerise": "containerize", "rm": "remove"}
|
||||
|
||||
#: help levels in order of detail (i.e., number of commands shown)
|
||||
levels = ["short", "long"]
|
||||
|
||||
@@ -359,7 +358,10 @@ def add_command(self, cmd_name):
|
||||
module = spack.cmd.get_module(cmd_name)
|
||||
|
||||
# build a list of aliases
|
||||
alias_list = [k for k, v in aliases.items() if v == cmd_name]
|
||||
alias_list = []
|
||||
aliases = spack.config.get("config:aliases")
|
||||
if aliases:
|
||||
alias_list = [k for k, v in aliases.items() if shlex.split(v)[0] == cmd_name]
|
||||
|
||||
subparser = self.subparsers.add_parser(
|
||||
cmd_name,
|
||||
@@ -670,7 +672,6 @@ def __init__(self, command_name, subprocess=False):
|
||||
Windows, where it is always False.
|
||||
"""
|
||||
self.parser = make_argument_parser()
|
||||
self.command = self.parser.add_command(command_name)
|
||||
self.command_name = command_name
|
||||
# TODO: figure out how to support this on windows
|
||||
self.subprocess = subprocess if sys.platform != "win32" else False
|
||||
@@ -702,13 +703,14 @@ def __call__(self, *argv, **kwargs):
|
||||
|
||||
if self.subprocess:
|
||||
p = sp.Popen(
|
||||
[spack.paths.spack_script, self.command_name] + prepend + list(argv),
|
||||
[spack.paths.spack_script] + prepend + [self.command_name] + list(argv),
|
||||
stdout=sp.PIPE,
|
||||
stderr=sp.STDOUT,
|
||||
)
|
||||
out, self.returncode = p.communicate()
|
||||
out = out.decode()
|
||||
else:
|
||||
command = self.parser.add_command(self.command_name)
|
||||
args, unknown = self.parser.parse_known_args(
|
||||
prepend + [self.command_name] + list(argv)
|
||||
)
|
||||
@@ -716,7 +718,7 @@ def __call__(self, *argv, **kwargs):
|
||||
out = io.StringIO()
|
||||
try:
|
||||
with log_output(out, echo=True):
|
||||
self.returncode = _invoke_command(self.command, self.parser, args, unknown)
|
||||
self.returncode = _invoke_command(command, self.parser, args, unknown)
|
||||
|
||||
except SystemExit as e:
|
||||
self.returncode = e.code
|
||||
@@ -870,6 +872,46 @@ def restore_macos_dyld_vars():
|
||||
os.environ[dyld_var] = os.environ[stored_var_name]
|
||||
|
||||
|
||||
def resolve_alias(cmd_name: str, cmd: List[str]) -> Tuple[str, List[str]]:
|
||||
"""Resolves aliases in the given command.
|
||||
|
||||
Args:
|
||||
cmd_name: command name.
|
||||
cmd: command line arguments.
|
||||
|
||||
Returns:
|
||||
new command name and arguments.
|
||||
"""
|
||||
all_commands = spack.cmd.all_commands()
|
||||
aliases = spack.config.get("config:aliases")
|
||||
|
||||
if aliases:
|
||||
for key, value in aliases.items():
|
||||
if " " in key:
|
||||
tty.warn(
|
||||
f"Alias '{key}' (mapping to '{value}') contains a space"
|
||||
", which is not supported."
|
||||
)
|
||||
if key in all_commands:
|
||||
tty.warn(
|
||||
f"Alias '{key}' (mapping to '{value}') attempts to override"
|
||||
" built-in command."
|
||||
)
|
||||
|
||||
if cmd_name not in all_commands:
|
||||
alias = None
|
||||
|
||||
if aliases:
|
||||
alias = aliases.get(cmd_name)
|
||||
|
||||
if alias is not None:
|
||||
alias_parts = shlex.split(alias)
|
||||
cmd_name = alias_parts[0]
|
||||
cmd = alias_parts + cmd[1:]
|
||||
|
||||
return cmd_name, cmd
|
||||
|
||||
|
||||
def _main(argv=None):
|
||||
"""Logic for the main entry point for the Spack command.
|
||||
|
||||
@@ -962,7 +1004,7 @@ def _main(argv=None):
|
||||
|
||||
# Try to load the particular command the caller asked for.
|
||||
cmd_name = args.command[0]
|
||||
cmd_name = aliases.get(cmd_name, cmd_name)
|
||||
cmd_name, args.command = resolve_alias(cmd_name, args.command)
|
||||
|
||||
# set up a bootstrap context, if asked.
|
||||
# bootstrap context needs to include parsing the command, b/c things
|
||||
@@ -974,10 +1016,10 @@ def _main(argv=None):
|
||||
bootstrap_context = bootstrap.ensure_bootstrap_configuration()
|
||||
|
||||
with bootstrap_context:
|
||||
return finish_parse_and_run(parser, cmd_name, env_format_error)
|
||||
return finish_parse_and_run(parser, cmd_name, args.command, env_format_error)
|
||||
|
||||
|
||||
def finish_parse_and_run(parser, cmd_name, env_format_error):
|
||||
def finish_parse_and_run(parser, cmd_name, cmd, env_format_error):
|
||||
"""Finish parsing after we know the command to run."""
|
||||
# add the found command to the parser and re-run then re-parse
|
||||
command = parser.add_command(cmd_name)
|
||||
|
||||
@@ -7,10 +7,15 @@
|
||||
include Tcl non-hierarchical modules, Lua hierarchical modules, and others.
|
||||
"""
|
||||
|
||||
from .common import disable_modules
|
||||
from typing import Dict, Type
|
||||
|
||||
from .common import BaseModuleFileWriter, disable_modules
|
||||
from .lmod import LmodModulefileWriter
|
||||
from .tcl import TclModulefileWriter
|
||||
|
||||
__all__ = ["TclModulefileWriter", "LmodModulefileWriter", "disable_modules"]
|
||||
|
||||
module_types = {"tcl": TclModulefileWriter, "lmod": LmodModulefileWriter}
|
||||
module_types: Dict[str, Type[BaseModuleFileWriter]] = {
|
||||
"tcl": TclModulefileWriter,
|
||||
"lmod": LmodModulefileWriter,
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
import os.path
|
||||
import re
|
||||
import string
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import llnl.util.filesystem
|
||||
import llnl.util.tty as tty
|
||||
@@ -50,6 +50,7 @@
|
||||
import spack.projections as proj
|
||||
import spack.repo
|
||||
import spack.schema.environment
|
||||
import spack.spec
|
||||
import spack.store
|
||||
import spack.tengine as tengine
|
||||
import spack.util.environment
|
||||
@@ -395,16 +396,14 @@ class BaseConfiguration:
|
||||
|
||||
default_projections = {"all": "{name}/{version}-{compiler.name}-{compiler.version}"}
|
||||
|
||||
def __init__(self, spec, module_set_name, explicit=None):
|
||||
def __init__(self, spec: spack.spec.Spec, module_set_name: str, explicit: bool) -> None:
|
||||
# Module where type(self) is defined
|
||||
self.module = inspect.getmodule(self)
|
||||
m = inspect.getmodule(self)
|
||||
assert m is not None # make mypy happy
|
||||
self.module = m
|
||||
# Spec for which we want to generate a module file
|
||||
self.spec = spec
|
||||
self.name = module_set_name
|
||||
# Software installation has been explicitly asked (get this information from
|
||||
# db when querying an existing module, like during a refresh or rm operations)
|
||||
if explicit is None:
|
||||
explicit = spec._installed_explicitly()
|
||||
self.explicit = explicit
|
||||
# Dictionary of configuration options that should be applied
|
||||
# to the spec
|
||||
@@ -458,7 +457,11 @@ def suffixes(self):
|
||||
if constraint in self.spec:
|
||||
suffixes.append(suffix)
|
||||
suffixes = list(dedupe(suffixes))
|
||||
if self.hash:
|
||||
# For hidden modules we can always add a fixed length hash as suffix, since it guards
|
||||
# against file name clashes, and the module is not exposed to the user anyways.
|
||||
if self.hidden:
|
||||
suffixes.append(self.spec.dag_hash(length=7))
|
||||
elif self.hash:
|
||||
suffixes.append(self.hash)
|
||||
return suffixes
|
||||
|
||||
@@ -483,43 +486,35 @@ def excluded(self):
|
||||
spec = self.spec
|
||||
conf = self.module.configuration(self.name)
|
||||
|
||||
# Compute the list of include rules that match
|
||||
include_rules = conf.get("include", [])
|
||||
include_matches = [x for x in include_rules if spec.satisfies(x)]
|
||||
|
||||
# Compute the list of exclude rules that match
|
||||
exclude_rules = conf.get("exclude", [])
|
||||
exclude_matches = [x for x in exclude_rules if spec.satisfies(x)]
|
||||
# Compute the list of matching include / exclude rules, and whether excluded as implicit
|
||||
include_matches = [x for x in conf.get("include", []) if spec.satisfies(x)]
|
||||
exclude_matches = [x for x in conf.get("exclude", []) if spec.satisfies(x)]
|
||||
excluded_as_implicit = not self.explicit and conf.get("exclude_implicits", False)
|
||||
|
||||
def debug_info(line_header, match_list):
|
||||
if match_list:
|
||||
msg = "\t{0} : {1}".format(line_header, spec.cshort_spec)
|
||||
tty.debug(msg)
|
||||
tty.debug(f"\t{line_header} : {spec.cshort_spec}")
|
||||
for rule in match_list:
|
||||
tty.debug("\t\tmatches rule: {0}".format(rule))
|
||||
tty.debug(f"\t\tmatches rule: {rule}")
|
||||
|
||||
debug_info("INCLUDE", include_matches)
|
||||
debug_info("EXCLUDE", exclude_matches)
|
||||
|
||||
if not include_matches and exclude_matches:
|
||||
return True
|
||||
if excluded_as_implicit:
|
||||
tty.debug(f"\tEXCLUDED_AS_IMPLICIT : {spec.cshort_spec}")
|
||||
|
||||
return False
|
||||
return not include_matches and (exclude_matches or excluded_as_implicit)
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""Returns True if the module has been hidden, False otherwise."""
|
||||
|
||||
# A few variables for convenience of writing the method
|
||||
spec = self.spec
|
||||
conf = self.module.configuration(self.name)
|
||||
|
||||
hidden_as_implicit = not self.explicit and conf.get(
|
||||
"hide_implicits", conf.get("exclude_implicits", False)
|
||||
)
|
||||
hidden_as_implicit = not self.explicit and conf.get("hide_implicits", False)
|
||||
|
||||
if hidden_as_implicit:
|
||||
tty.debug(f"\tHIDDEN_AS_IMPLICIT : {spec.cshort_spec}")
|
||||
tty.debug(f"\tHIDDEN_AS_IMPLICIT : {self.spec.cshort_spec}")
|
||||
|
||||
return hidden_as_implicit
|
||||
|
||||
@@ -551,8 +546,7 @@ def exclude_env_vars(self):
|
||||
def _create_list_for(self, what):
|
||||
include = []
|
||||
for item in self.conf[what]:
|
||||
conf = type(self)(item, self.name)
|
||||
if not conf.excluded:
|
||||
if not self.module.make_configuration(item, self.name).excluded:
|
||||
include.append(item)
|
||||
return include
|
||||
|
||||
@@ -731,7 +725,9 @@ def environment_modifications(self):
|
||||
# for that to work, globals have to be set on the package modules, and the
|
||||
# whole chain of setup_dependent_package has to be followed from leaf to spec.
|
||||
# So: just run it here, but don't collect env mods.
|
||||
spack.build_environment.SetupContext(context=Context.RUN).set_all_package_py_globals()
|
||||
spack.build_environment.SetupContext(
|
||||
spec, context=Context.RUN
|
||||
).set_all_package_py_globals()
|
||||
|
||||
# Then run setup_dependent_run_environment before setup_run_environment.
|
||||
for dep in spec.dependencies(deptype=("link", "run")):
|
||||
@@ -824,8 +820,7 @@ def autoload(self):
|
||||
def _create_module_list_of(self, what):
|
||||
m = self.conf.module
|
||||
name = self.conf.name
|
||||
explicit = self.conf.explicit
|
||||
return [m.make_layout(x, name, explicit).use_name for x in getattr(self.conf, what)]
|
||||
return [m.make_layout(x, name).use_name for x in getattr(self.conf, what)]
|
||||
|
||||
@tengine.context_property
|
||||
def verbose(self):
|
||||
@@ -834,12 +829,19 @@ def verbose(self):
|
||||
|
||||
|
||||
class BaseModuleFileWriter:
|
||||
def __init__(self, spec, module_set_name, explicit=None):
|
||||
default_template: str
|
||||
hide_cmd_format: str
|
||||
modulerc_header: List[str]
|
||||
|
||||
def __init__(
|
||||
self, spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
|
||||
) -> None:
|
||||
self.spec = spec
|
||||
|
||||
# This class is meant to be derived. Get the module of the
|
||||
# actual writer.
|
||||
self.module = inspect.getmodule(self)
|
||||
assert self.module is not None # make mypy happy
|
||||
m = self.module
|
||||
|
||||
# Create the triplet of configuration/layout/context
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
import collections
|
||||
import itertools
|
||||
import os.path
|
||||
import posixpath
|
||||
from typing import Any, Dict, List
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang as lang
|
||||
@@ -24,18 +23,19 @@
|
||||
|
||||
|
||||
#: lmod specific part of the configuration
|
||||
def configuration(module_set_name):
|
||||
config_path = "modules:%s:lmod" % module_set_name
|
||||
config = spack.config.get(config_path, {})
|
||||
return config
|
||||
def configuration(module_set_name: str) -> dict:
|
||||
return spack.config.get(f"modules:{module_set_name}:lmod", {})
|
||||
|
||||
|
||||
# Caches the configuration {spec_hash: configuration}
|
||||
configuration_registry: Dict[str, Any] = {}
|
||||
configuration_registry: Dict[Tuple[str, str, bool], BaseConfiguration] = {}
|
||||
|
||||
|
||||
def make_configuration(spec, module_set_name, explicit):
|
||||
def make_configuration(
|
||||
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
|
||||
) -> BaseConfiguration:
|
||||
"""Returns the lmod configuration for spec"""
|
||||
explicit = bool(spec._installed_explicitly()) if explicit is None else explicit
|
||||
key = (spec.dag_hash(), module_set_name, explicit)
|
||||
try:
|
||||
return configuration_registry[key]
|
||||
@@ -45,16 +45,18 @@ def make_configuration(spec, module_set_name, explicit):
|
||||
)
|
||||
|
||||
|
||||
def make_layout(spec, module_set_name, explicit):
|
||||
def make_layout(
|
||||
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
|
||||
) -> BaseFileLayout:
|
||||
"""Returns the layout information for spec"""
|
||||
conf = make_configuration(spec, module_set_name, explicit)
|
||||
return LmodFileLayout(conf)
|
||||
return LmodFileLayout(make_configuration(spec, module_set_name, explicit))
|
||||
|
||||
|
||||
def make_context(spec, module_set_name, explicit):
|
||||
def make_context(
|
||||
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
|
||||
) -> BaseContext:
|
||||
"""Returns the context information for spec"""
|
||||
conf = make_configuration(spec, module_set_name, explicit)
|
||||
return LmodContext(conf)
|
||||
return LmodContext(make_configuration(spec, module_set_name, explicit))
|
||||
|
||||
|
||||
def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]:
|
||||
@@ -97,10 +99,7 @@ def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]:
|
||||
class LmodConfiguration(BaseConfiguration):
|
||||
"""Configuration class for lmod module files."""
|
||||
|
||||
# Note: Posixpath is used here as well as below as opposed to
|
||||
# os.path.join due to spack.spec.Spec.format
|
||||
# requiring forward slash path seperators at this stage
|
||||
default_projections = {"all": posixpath.join("{name}", "{version}")}
|
||||
default_projections = {"all": "{name}/{version}"}
|
||||
|
||||
@property
|
||||
def core_compilers(self) -> List[spack.spec.CompilerSpec]:
|
||||
@@ -274,19 +273,16 @@ def filename(self):
|
||||
hierarchy_name = os.path.join(*parts)
|
||||
|
||||
# Compute the absolute path
|
||||
fullname = os.path.join(
|
||||
return os.path.join(
|
||||
self.arch_dirname, # root for lmod files on this architecture
|
||||
hierarchy_name, # relative path
|
||||
".".join([self.use_name, self.extension]), # file name
|
||||
f"{self.use_name}.{self.extension}", # file name
|
||||
)
|
||||
return fullname
|
||||
|
||||
@property
|
||||
def modulerc(self):
|
||||
"""Returns the modulerc file associated with current module file"""
|
||||
return os.path.join(
|
||||
os.path.dirname(self.filename), ".".join([".modulerc", self.extension])
|
||||
)
|
||||
return os.path.join(os.path.dirname(self.filename), f".modulerc.{self.extension}")
|
||||
|
||||
def token_to_path(self, name, value):
|
||||
"""Transforms a hierarchy token into the corresponding path part.
|
||||
@@ -319,9 +315,7 @@ def path_part_fmt(token):
|
||||
# we need to append a hash to the version to distinguish
|
||||
# among flavors of the same library (e.g. openblas~openmp vs.
|
||||
# openblas+openmp)
|
||||
path = path_part_fmt(token=value)
|
||||
path = "-".join([path, value.dag_hash(length=7)])
|
||||
return path
|
||||
return f"{path_part_fmt(token=value)}-{value.dag_hash(length=7)}"
|
||||
|
||||
@property
|
||||
def available_path_parts(self):
|
||||
@@ -333,8 +327,7 @@ def available_path_parts(self):
|
||||
# List of services that are part of the hierarchy
|
||||
hierarchy = self.conf.hierarchy_tokens
|
||||
# Tokenize each part that is both in the hierarchy and available
|
||||
parts = [self.token_to_path(x, available[x]) for x in hierarchy if x in available]
|
||||
return parts
|
||||
return [self.token_to_path(x, available[x]) for x in hierarchy if x in available]
|
||||
|
||||
@property
|
||||
@lang.memoized
|
||||
@@ -452,7 +445,7 @@ def missing(self):
|
||||
@lang.memoized
|
||||
def unlocked_paths(self):
|
||||
"""Returns the list of paths that are unlocked unconditionally."""
|
||||
layout = make_layout(self.spec, self.conf.name, self.conf.explicit)
|
||||
layout = make_layout(self.spec, self.conf.name)
|
||||
return [os.path.join(*parts) for parts in layout.unlocked_paths[None]]
|
||||
|
||||
@tengine.context_property
|
||||
@@ -460,7 +453,7 @@ def conditionally_unlocked_paths(self):
|
||||
"""Returns the list of paths that are unlocked conditionally.
|
||||
Each item in the list is a tuple with the structure (condition, path).
|
||||
"""
|
||||
layout = make_layout(self.spec, self.conf.name, self.conf.explicit)
|
||||
layout = make_layout(self.spec, self.conf.name)
|
||||
value = []
|
||||
conditional_paths = layout.unlocked_paths
|
||||
conditional_paths.pop(None)
|
||||
@@ -482,9 +475,9 @@ def manipulate_path(token):
|
||||
class LmodModulefileWriter(BaseModuleFileWriter):
|
||||
"""Writer class for lmod module files."""
|
||||
|
||||
default_template = posixpath.join("modules", "modulefile.lua")
|
||||
default_template = "modules/modulefile.lua"
|
||||
|
||||
modulerc_header: list = []
|
||||
modulerc_header = []
|
||||
|
||||
hide_cmd_format = 'hide_version("%s")'
|
||||
|
||||
|
||||
@@ -7,28 +7,29 @@
|
||||
non-hierarchical modules.
|
||||
"""
|
||||
import os.path
|
||||
import posixpath
|
||||
from typing import Any, Dict
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
import spack.config
|
||||
import spack.spec
|
||||
import spack.tengine as tengine
|
||||
|
||||
from .common import BaseConfiguration, BaseContext, BaseFileLayout, BaseModuleFileWriter
|
||||
|
||||
|
||||
#: Tcl specific part of the configuration
|
||||
def configuration(module_set_name):
|
||||
config_path = "modules:%s:tcl" % module_set_name
|
||||
config = spack.config.get(config_path, {})
|
||||
return config
|
||||
def configuration(module_set_name: str) -> dict:
|
||||
return spack.config.get(f"modules:{module_set_name}:tcl", {})
|
||||
|
||||
|
||||
# Caches the configuration {spec_hash: configuration}
|
||||
configuration_registry: Dict[str, Any] = {}
|
||||
configuration_registry: Dict[Tuple[str, str, bool], BaseConfiguration] = {}
|
||||
|
||||
|
||||
def make_configuration(spec, module_set_name, explicit):
|
||||
def make_configuration(
|
||||
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
|
||||
) -> BaseConfiguration:
|
||||
"""Returns the tcl configuration for spec"""
|
||||
explicit = bool(spec._installed_explicitly()) if explicit is None else explicit
|
||||
key = (spec.dag_hash(), module_set_name, explicit)
|
||||
try:
|
||||
return configuration_registry[key]
|
||||
@@ -38,16 +39,18 @@ def make_configuration(spec, module_set_name, explicit):
|
||||
)
|
||||
|
||||
|
||||
def make_layout(spec, module_set_name, explicit):
|
||||
def make_layout(
|
||||
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
|
||||
) -> BaseFileLayout:
|
||||
"""Returns the layout information for spec"""
|
||||
conf = make_configuration(spec, module_set_name, explicit)
|
||||
return TclFileLayout(conf)
|
||||
return TclFileLayout(make_configuration(spec, module_set_name, explicit))
|
||||
|
||||
|
||||
def make_context(spec, module_set_name, explicit):
|
||||
def make_context(
|
||||
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
|
||||
) -> BaseContext:
|
||||
"""Returns the context information for spec"""
|
||||
conf = make_configuration(spec, module_set_name, explicit)
|
||||
return TclContext(conf)
|
||||
return TclContext(make_configuration(spec, module_set_name, explicit))
|
||||
|
||||
|
||||
class TclConfiguration(BaseConfiguration):
|
||||
@@ -75,10 +78,7 @@ def prerequisites(self):
|
||||
class TclModulefileWriter(BaseModuleFileWriter):
|
||||
"""Writer class for tcl module files."""
|
||||
|
||||
# Note: Posixpath is used here as opposed to
|
||||
# os.path.join due to spack.spec.Spec.format
|
||||
# requiring forward slash path seperators at this stage
|
||||
default_template = posixpath.join("modules", "modulefile.tcl")
|
||||
default_template = "modules/modulefile.tcl"
|
||||
|
||||
modulerc_header = ["#%Module4.7"]
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"""
|
||||
import functools
|
||||
import inspect
|
||||
from contextlib import contextmanager
|
||||
|
||||
from llnl.util.lang import caller_locals
|
||||
|
||||
@@ -271,6 +272,13 @@ def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
spack.directives.DirectiveMeta.pop_from_context()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def default_args(**kwargs):
|
||||
spack.directives.DirectiveMeta.push_default_args(kwargs)
|
||||
yield
|
||||
spack.directives.DirectiveMeta.pop_default_args()
|
||||
|
||||
|
||||
class MultiMethodError(spack.error.SpackError):
|
||||
"""Superclass for multimethod dispatch errors"""
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
from spack.build_systems.bundle import BundlePackage
|
||||
from spack.build_systems.cached_cmake import (
|
||||
CachedCMakePackage,
|
||||
cmake_cache_filepath,
|
||||
cmake_cache_option,
|
||||
cmake_cache_path,
|
||||
cmake_cache_string,
|
||||
@@ -49,6 +50,7 @@
|
||||
from spack.build_systems.nmake import NMakePackage
|
||||
from spack.build_systems.octave import OctavePackage
|
||||
from spack.build_systems.oneapi import (
|
||||
INTEL_MATH_LIBRARIES,
|
||||
IntelOneApiLibraryPackage,
|
||||
IntelOneApiPackage,
|
||||
IntelOneApiStaticLibraryList,
|
||||
@@ -85,7 +87,7 @@
|
||||
UpstreamPackageError,
|
||||
)
|
||||
from spack.mixins import filter_compiler_wrappers
|
||||
from spack.multimethod import when
|
||||
from spack.multimethod import default_args, when
|
||||
from spack.package_base import (
|
||||
DependencyConflictError,
|
||||
build_system_flags,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import abc
|
||||
import collections.abc
|
||||
import contextlib
|
||||
import difflib
|
||||
import errno
|
||||
import functools
|
||||
import importlib
|
||||
@@ -1516,7 +1517,18 @@ def __init__(self, name, repo=None):
|
||||
long_msg = "Did you mean to specify a filename with './{0}'?"
|
||||
long_msg = long_msg.format(name)
|
||||
else:
|
||||
long_msg = "You may need to run 'spack clean -m'."
|
||||
long_msg = "Use 'spack create' to create a new package."
|
||||
|
||||
if not repo:
|
||||
repo = spack.repo.PATH
|
||||
|
||||
# We need to compare the base package name
|
||||
pkg_name = name.rsplit(".", 1)[-1]
|
||||
similar = difflib.get_close_matches(pkg_name, repo.all_package_names())
|
||||
|
||||
if 1 <= len(similar) <= 5:
|
||||
long_msg += "\n\nDid you mean one of the following packages?\n "
|
||||
long_msg += "\n ".join(similar)
|
||||
|
||||
super().__init__(msg, long_msg)
|
||||
self.name = name
|
||||
|
||||
@@ -62,3 +62,25 @@ def _deprecated_properties(validator, deprecated, instance, schema):
|
||||
|
||||
|
||||
Validator = llnl.util.lang.Singleton(_make_validator)
|
||||
|
||||
spec_list_schema = {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"matrix": {
|
||||
"type": "array",
|
||||
"items": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"exclude": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
{"type": "string"},
|
||||
{"type": "null"},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
"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"}}},
|
||||
},
|
||||
"deprecatedProperties": {
|
||||
"properties": ["terminal_title"],
|
||||
|
||||
34
lib/spack/spack/schema/definitions.py
Normal file
34
lib/spack/spack/schema/definitions.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Schema for definitions
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/definitions.py
|
||||
:lines: 13-
|
||||
"""
|
||||
|
||||
import spack.schema
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
"definitions": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {"when": {"type": "string"}},
|
||||
"patternProperties": {r"^(?!when$)\w*": spack.schema.spec_list_schema},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#: Full schema with metadata
|
||||
schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Spack definitions configuration file schema",
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": properties,
|
||||
}
|
||||
@@ -12,34 +12,11 @@
|
||||
|
||||
import spack.schema.gitlab_ci # DEPRECATED
|
||||
import spack.schema.merged
|
||||
import spack.schema.packages
|
||||
import spack.schema.projections
|
||||
|
||||
#: Top level key in a manifest file
|
||||
TOP_LEVEL_KEY = "spack"
|
||||
|
||||
spec_list_schema = {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"matrix": {
|
||||
"type": "array",
|
||||
"items": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"exclude": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
{"type": "string"},
|
||||
{"type": "null"},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
projections_scheme = spack.schema.projections.properties["projections"]
|
||||
|
||||
schema = {
|
||||
@@ -75,16 +52,7 @@
|
||||
}
|
||||
},
|
||||
},
|
||||
"definitions": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {"when": {"type": "string"}},
|
||||
"patternProperties": {r"^(?!when$)\w*": spec_list_schema},
|
||||
},
|
||||
},
|
||||
"specs": spec_list_schema,
|
||||
"specs": spack.schema.spec_list_schema,
|
||||
"view": {
|
||||
"anyOf": [
|
||||
{"type": "boolean"},
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import spack.schema.concretizer
|
||||
import spack.schema.config
|
||||
import spack.schema.container
|
||||
import spack.schema.definitions
|
||||
import spack.schema.mirrors
|
||||
import spack.schema.modules
|
||||
import spack.schema.packages
|
||||
@@ -32,6 +33,7 @@
|
||||
spack.schema.config.properties,
|
||||
spack.schema.container.properties,
|
||||
spack.schema.ci.properties,
|
||||
spack.schema.definitions.properties,
|
||||
spack.schema.mirrors.properties,
|
||||
spack.schema.modules.properties,
|
||||
spack.schema.packages.properties,
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
#: IS ADDED IMMEDIATELY BELOW THE MODULE TYPE ATTRIBUTE
|
||||
spec_regex = (
|
||||
r"(?!hierarchy|core_specs|verbose|hash_length|defaults|filter_hierarchy_specs|hide|"
|
||||
r"whitelist|blacklist|" # DEPRECATED: remove in 0.20.
|
||||
r"include|exclude|" # use these more inclusive/consistent options
|
||||
r"projections|naming_scheme|core_compilers|all)(^\w[\w-]*)"
|
||||
r"include|exclude|projections|naming_scheme|core_compilers|all)(^\w[\w-]*)"
|
||||
)
|
||||
|
||||
#: Matches a valid name for a module set
|
||||
@@ -46,14 +44,7 @@
|
||||
"default": {},
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
# DEPRECATED: remove in 0.20.
|
||||
"environment_blacklist": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
# use exclude_env_vars instead
|
||||
"exclude_env_vars": {"type": "array", "default": [], "items": {"type": "string"}},
|
||||
"exclude_env_vars": {"type": "array", "default": [], "items": {"type": "string"}}
|
||||
},
|
||||
},
|
||||
"template": {"type": "string"},
|
||||
@@ -80,11 +71,6 @@
|
||||
"properties": {
|
||||
"verbose": {"type": "boolean", "default": False},
|
||||
"hash_length": {"type": "integer", "minimum": 0, "default": 7},
|
||||
# DEPRECATED: remove in 0.20.
|
||||
"whitelist": array_of_strings,
|
||||
"blacklist": array_of_strings,
|
||||
"blacklist_implicits": {"type": "boolean", "default": False},
|
||||
# whitelist/blacklist have been replaced with include/exclude
|
||||
"include": array_of_strings,
|
||||
"exclude": array_of_strings,
|
||||
"exclude_implicits": {"type": "boolean", "default": False},
|
||||
@@ -188,52 +174,3 @@
|
||||
"additionalProperties": False,
|
||||
"properties": properties,
|
||||
}
|
||||
|
||||
|
||||
# deprecated keys and their replacements
|
||||
old_to_new_key = {"exclude_implicits": "hide_implicits"}
|
||||
|
||||
|
||||
def update_keys(data, key_translations):
|
||||
"""Change blacklist/whitelist to exclude/include.
|
||||
|
||||
Arguments:
|
||||
data (dict): data from a valid modules configuration.
|
||||
key_translations (dict): A dictionary of keys to translate to
|
||||
their respective values.
|
||||
|
||||
Return:
|
||||
(bool) whether anything was changed in data
|
||||
"""
|
||||
changed = False
|
||||
|
||||
if isinstance(data, dict):
|
||||
keys = list(data.keys())
|
||||
for key in keys:
|
||||
value = data[key]
|
||||
|
||||
translation = key_translations.get(key)
|
||||
if translation:
|
||||
data[translation] = data.pop(key)
|
||||
changed = True
|
||||
|
||||
changed |= update_keys(value, key_translations)
|
||||
|
||||
elif isinstance(data, list):
|
||||
for elt in data:
|
||||
changed |= update_keys(elt, key_translations)
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def update(data):
|
||||
"""Update the data in place to remove deprecated properties.
|
||||
|
||||
Args:
|
||||
data (dict): dictionary to be updated
|
||||
|
||||
Returns:
|
||||
True if data was changed, False otherwise
|
||||
"""
|
||||
# translate blacklist/whitelist to exclude/include
|
||||
return update_keys(data, old_to_new_key)
|
||||
|
||||
@@ -8,6 +8,66 @@
|
||||
:lines: 13-
|
||||
"""
|
||||
|
||||
permissions = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"read": {"type": "string", "enum": ["user", "group", "world"]},
|
||||
"write": {"type": "string", "enum": ["user", "group", "world"]},
|
||||
"group": {"type": "string"},
|
||||
},
|
||||
}
|
||||
|
||||
variants = {"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]}
|
||||
|
||||
requirements = {
|
||||
"oneOf": [
|
||||
# 'require' can be a list of requirement_groups.
|
||||
# each requirement group is a list of one or more
|
||||
# specs. Either at least one or exactly one spec
|
||||
# in the group must be satisfied (depending on
|
||||
# whether you use "any_of" or "one_of",
|
||||
# repectively)
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"one_of": {"type": "array", "items": {"type": "string"}},
|
||||
"any_of": {"type": "array", "items": {"type": "string"}},
|
||||
"spec": {"type": "string"},
|
||||
"message": {"type": "string"},
|
||||
"when": {"type": "string"},
|
||||
},
|
||||
},
|
||||
{"type": "string"},
|
||||
]
|
||||
},
|
||||
},
|
||||
# Shorthand for a single requirement group with
|
||||
# one member
|
||||
{"type": "string"},
|
||||
]
|
||||
}
|
||||
|
||||
permissions = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"read": {"type": "string", "enum": ["user", "group", "world"]},
|
||||
"write": {"type": "string", "enum": ["user", "group", "world"]},
|
||||
"group": {"type": "string"},
|
||||
},
|
||||
}
|
||||
|
||||
package_attributes = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"patternProperties": {r"\w+": {}},
|
||||
}
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
@@ -15,57 +75,14 @@
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"additionalProperties": False,
|
||||
"patternProperties": {
|
||||
r"\w[\w-]*": { # package name
|
||||
"properties": {
|
||||
"all": { # package name
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"require": {
|
||||
"oneOf": [
|
||||
# 'require' can be a list of requirement_groups.
|
||||
# each requirement group is a list of one or more
|
||||
# specs. Either at least one or exactly one spec
|
||||
# in the group must be satisfied (depending on
|
||||
# whether you use "any_of" or "one_of",
|
||||
# repectively)
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"one_of": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"any_of": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"spec": {"type": "string"},
|
||||
"message": {"type": "string"},
|
||||
"when": {"type": "string"},
|
||||
},
|
||||
},
|
||||
{"type": "string"},
|
||||
]
|
||||
},
|
||||
},
|
||||
# Shorthand for a single requirement group with
|
||||
# one member
|
||||
{"type": "string"},
|
||||
]
|
||||
},
|
||||
"version": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
# version strings (type should be string, number is still possible
|
||||
# but deprecated. this is to avoid issues with e.g. 3.10 -> 3.1)
|
||||
"items": {"anyOf": [{"type": "string"}, {"type": "number"}]},
|
||||
},
|
||||
"require": requirements,
|
||||
"version": {}, # Here only to warn users on ignored properties
|
||||
"target": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
@@ -78,22 +95,10 @@
|
||||
"items": {"type": "string"},
|
||||
}, # compiler specs
|
||||
"buildable": {"type": "boolean", "default": True},
|
||||
"permissions": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"read": {"type": "string", "enum": ["user", "group", "world"]},
|
||||
"write": {"type": "string", "enum": ["user", "group", "world"]},
|
||||
"group": {"type": "string"},
|
||||
},
|
||||
},
|
||||
"permissions": permissions,
|
||||
# If 'get_full_repo' is promoted to a Package-level
|
||||
# attribute, it could be useful to set it here
|
||||
"package_attributes": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"patternProperties": {r"\w+": {}},
|
||||
},
|
||||
"package_attributes": package_attributes,
|
||||
"providers": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
@@ -106,12 +111,40 @@
|
||||
}
|
||||
},
|
||||
},
|
||||
"variants": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
]
|
||||
"variants": variants,
|
||||
},
|
||||
"deprecatedProperties": {
|
||||
"properties": ["version"],
|
||||
"message": "setting version preferences in the 'all' section of packages.yaml "
|
||||
"is deprecated and will be removed in v0.22\n\n\tThese preferences "
|
||||
"will be ignored by Spack. You can set them only in package specific sections "
|
||||
"of the same file.\n",
|
||||
"error": False,
|
||||
},
|
||||
}
|
||||
},
|
||||
"patternProperties": {
|
||||
r"(?!^all$)(^\w[\w-]*)": { # package name
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"require": requirements,
|
||||
"version": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
# version strings
|
||||
"items": {"anyOf": [{"type": "string"}, {"type": "number"}]},
|
||||
},
|
||||
"target": {}, # Here only to warn users on ignored properties
|
||||
"compiler": {}, # Here only to warn users on ignored properties
|
||||
"buildable": {"type": "boolean", "default": True},
|
||||
"permissions": permissions,
|
||||
# If 'get_full_repo' is promoted to a Package-level
|
||||
# attribute, it could be useful to set it here
|
||||
"package_attributes": package_attributes,
|
||||
"providers": {}, # Here only to warn users on ignored properties
|
||||
"variants": variants,
|
||||
"externals": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -127,6 +160,14 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
"deprecatedProperties": {
|
||||
"properties": ["target", "compiler", "providers"],
|
||||
"message": "setting compiler, target or provider preferences in a package "
|
||||
"specific section of packages.yaml is deprecated, and will be removed in "
|
||||
"v0.22.\n\n\tThese preferences will be ignored by Spack. You "
|
||||
"can set them only in the 'all' section of the same file.\n",
|
||||
"error": False,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
import enum
|
||||
import itertools
|
||||
import os
|
||||
import pathlib
|
||||
import pprint
|
||||
import re
|
||||
import types
|
||||
import warnings
|
||||
from typing import Dict, List, NamedTuple, Optional, Sequence, Tuple, Union
|
||||
from typing import Callable, Dict, List, NamedTuple, Optional, Sequence, Set, Tuple, Union
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
@@ -337,6 +338,13 @@ def __getattr__(self, name):
|
||||
|
||||
fn = AspFunctionBuilder()
|
||||
|
||||
TransformFunction = Callable[[spack.spec.Spec, List[AspFunction]], List[AspFunction]]
|
||||
|
||||
|
||||
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 _create_counter(specs, tests):
|
||||
strategy = spack.config.CONFIG.get("concretizer:duplicates:strategy", "none")
|
||||
@@ -684,7 +692,7 @@ def extract_args(model, predicate_name):
|
||||
class ErrorHandler:
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.error_args = extract_args(model, "error")
|
||||
self.full_model = None
|
||||
|
||||
def multiple_values_error(self, attribute, pkg):
|
||||
return f'Cannot select a single "{attribute}" for package "{pkg}"'
|
||||
@@ -692,6 +700,48 @@ def multiple_values_error(self, attribute, pkg):
|
||||
def no_value_error(self, attribute, pkg):
|
||||
return f'Cannot select a single "{attribute}" for package "{pkg}"'
|
||||
|
||||
def _get_cause_tree(
|
||||
self,
|
||||
cause: Tuple[str, str],
|
||||
conditions: Dict[str, str],
|
||||
condition_causes: List[Tuple[Tuple[str, str], Tuple[str, str]]],
|
||||
seen: Set,
|
||||
indent: str = " ",
|
||||
) -> List[str]:
|
||||
"""
|
||||
Implementation of recursion for self.get_cause_tree. Much of this operates on tuples
|
||||
(condition_id, set_id) in which the latter idea means that the condition represented by
|
||||
the former held in the condition set represented by the latter.
|
||||
"""
|
||||
seen = set(seen) | set(cause)
|
||||
parents = [c for e, c in condition_causes if e == cause and c not in seen]
|
||||
local = "required because %s " % conditions[cause[0]]
|
||||
|
||||
return [indent + local] + [
|
||||
c
|
||||
for parent in parents
|
||||
for c in self._get_cause_tree(
|
||||
parent, conditions, condition_causes, seen, indent=indent + " "
|
||||
)
|
||||
]
|
||||
|
||||
def get_cause_tree(self, cause: Tuple[str, str]) -> List[str]:
|
||||
"""
|
||||
Get the cause tree associated with the given cause.
|
||||
|
||||
Arguments:
|
||||
cause: The root cause of the tree (final condition)
|
||||
|
||||
Returns:
|
||||
A list of strings describing the causes, formatted to display tree structure.
|
||||
"""
|
||||
conditions: Dict[str, str] = dict(extract_args(self.full_model, "condition_reason"))
|
||||
condition_causes: List[Tuple[Tuple[str, str], Tuple[str, str]]] = list(
|
||||
((Effect, EID), (Cause, CID))
|
||||
for Effect, EID, Cause, CID in extract_args(self.full_model, "condition_cause")
|
||||
)
|
||||
return self._get_cause_tree(cause, conditions, condition_causes, set())
|
||||
|
||||
def handle_error(self, msg, *args):
|
||||
"""Handle an error state derived by the solver."""
|
||||
if msg == "multiple_values_error":
|
||||
@@ -700,14 +750,31 @@ def handle_error(self, msg, *args):
|
||||
if msg == "no_value_error":
|
||||
return self.no_value_error(*args)
|
||||
|
||||
try:
|
||||
idx = args.index("startcauses")
|
||||
except ValueError:
|
||||
msg_args = args
|
||||
causes = []
|
||||
else:
|
||||
msg_args = args[:idx]
|
||||
cause_args = args[idx + 1 :]
|
||||
cause_args_conditions = cause_args[::2]
|
||||
cause_args_ids = cause_args[1::2]
|
||||
causes = list(zip(cause_args_conditions, cause_args_ids))
|
||||
|
||||
msg = msg.format(*msg_args)
|
||||
|
||||
# For variant formatting, we sometimes have to construct specs
|
||||
# to format values properly. Find/replace all occurances of
|
||||
# Spec(...) with the string representation of the spec mentioned
|
||||
msg = msg.format(*args)
|
||||
specs_to_construct = re.findall(r"Spec\(([^)]*)\)", msg)
|
||||
for spec_str in specs_to_construct:
|
||||
msg = msg.replace("Spec(%s)" % spec_str, str(spack.spec.Spec(spec_str)))
|
||||
|
||||
for cause in set(causes):
|
||||
for c in self.get_cause_tree(cause):
|
||||
msg += f"\n{c}"
|
||||
|
||||
return msg
|
||||
|
||||
def message(self, errors) -> str:
|
||||
@@ -719,11 +786,31 @@ def message(self, errors) -> str:
|
||||
return "\n".join([header] + messages)
|
||||
|
||||
def raise_if_errors(self):
|
||||
if not self.error_args:
|
||||
initial_error_args = extract_args(self.model, "error")
|
||||
if not initial_error_args:
|
||||
return
|
||||
|
||||
error_causation = clingo.Control()
|
||||
|
||||
parent_dir = pathlib.Path(__file__).parent
|
||||
errors_lp = parent_dir / "error_messages.lp"
|
||||
|
||||
def on_model(model):
|
||||
self.full_model = model.symbols(shown=True, terms=True)
|
||||
|
||||
with error_causation.backend() as backend:
|
||||
for atom in self.model:
|
||||
atom_id = backend.add_atom(atom)
|
||||
backend.add_rule([atom_id], [], choice=False)
|
||||
|
||||
error_causation.load(str(errors_lp))
|
||||
error_causation.ground([("base", []), ("error_messages", [])])
|
||||
_ = error_causation.solve(on_model=on_model)
|
||||
|
||||
# No choices so there will be only one model
|
||||
error_args = extract_args(self.full_model, "error")
|
||||
errors = sorted(
|
||||
[(int(priority), msg, args) for priority, msg, *args in self.error_args], reverse=True
|
||||
[(int(priority), msg, args) for priority, msg, *args in error_args], reverse=True
|
||||
)
|
||||
msg = self.message(errors)
|
||||
raise UnsatisfiableSpecError(msg)
|
||||
@@ -924,7 +1011,7 @@ def on_model(model):
|
||||
if sym.name not in ("attr", "error", "opt_criterion"):
|
||||
tty.debug(
|
||||
"UNKNOWN SYMBOL: %s(%s)"
|
||||
% (sym.name, ", ".join(intermediate_repr(sym.arguments)))
|
||||
% (sym.name, ", ".join([str(s) for s in intermediate_repr(sym.arguments)]))
|
||||
)
|
||||
|
||||
elif cores:
|
||||
@@ -1116,7 +1203,7 @@ def conflict_rules(self, pkg):
|
||||
default_msg = "{0}: '{1}' conflicts with '{2}'"
|
||||
no_constraint_msg = "{0}: conflicts with '{1}'"
|
||||
for trigger, constraints in pkg.conflicts.items():
|
||||
trigger_msg = "conflict trigger %s" % str(trigger)
|
||||
trigger_msg = f"conflict is triggered when {str(trigger)}"
|
||||
trigger_spec = spack.spec.Spec(trigger)
|
||||
trigger_id = self.condition(
|
||||
trigger_spec, name=trigger_spec.name or pkg.name, msg=trigger_msg
|
||||
@@ -1128,7 +1215,11 @@ def conflict_rules(self, pkg):
|
||||
conflict_msg = no_constraint_msg.format(pkg.name, trigger)
|
||||
else:
|
||||
conflict_msg = default_msg.format(pkg.name, trigger, constraint)
|
||||
constraint_msg = "conflict constraint %s" % str(constraint)
|
||||
|
||||
spec_for_msg = (
|
||||
spack.spec.Spec(pkg.name) if constraint == spack.spec.Spec() else constraint
|
||||
)
|
||||
constraint_msg = f"conflict applies to spec {str(spec_for_msg)}"
|
||||
constraint_id = self.condition(constraint, name=pkg.name, msg=constraint_msg)
|
||||
self.gen.fact(
|
||||
fn.pkg_fact(pkg.name, fn.conflict(trigger_id, constraint_id, conflict_msg))
|
||||
@@ -1167,32 +1258,9 @@ def compiler_facts(self):
|
||||
matches = sorted(indexed_possible_compilers, key=lambda x: ppk(x[1].spec))
|
||||
|
||||
for weight, (compiler_id, cspec) in enumerate(matches):
|
||||
f = fn.default_compiler_preference(compiler_id, weight)
|
||||
f = fn.compiler_weight(compiler_id, weight)
|
||||
self.gen.fact(f)
|
||||
|
||||
def package_compiler_defaults(self, pkg):
|
||||
"""Facts about packages' compiler prefs."""
|
||||
|
||||
packages = spack.config.get("packages")
|
||||
pkg_prefs = packages.get(pkg.name)
|
||||
if not pkg_prefs or "compiler" not in pkg_prefs:
|
||||
return
|
||||
|
||||
compiler_list = self.possible_compilers
|
||||
compiler_list = sorted(compiler_list, key=lambda x: (x.name, x.version), reverse=True)
|
||||
ppk = spack.package_prefs.PackagePrefs(pkg.name, "compiler", all=False)
|
||||
matches = sorted(compiler_list, key=lambda x: ppk(x.spec))
|
||||
|
||||
for i, compiler in enumerate(reversed(matches)):
|
||||
self.gen.fact(
|
||||
fn.pkg_fact(
|
||||
pkg.name,
|
||||
fn.node_compiler_preference(
|
||||
compiler.spec.name, compiler.spec.version, -i * 100
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
def package_requirement_rules(self, pkg):
|
||||
rules = self.requirement_rules_from_package_py(pkg)
|
||||
rules.extend(self.requirement_rules_from_packages_yaml(pkg))
|
||||
@@ -1284,9 +1352,6 @@ def pkg_rules(self, pkg, tests):
|
||||
# conflicts
|
||||
self.conflict_rules(pkg)
|
||||
|
||||
# default compilers for this package
|
||||
self.package_compiler_defaults(pkg)
|
||||
|
||||
# virtuals
|
||||
self.package_provider_rules(pkg)
|
||||
|
||||
@@ -1310,7 +1375,7 @@ def trigger_rules(self):
|
||||
self.gen.h2("Trigger conditions")
|
||||
for name in self._trigger_cache:
|
||||
cache = self._trigger_cache[name]
|
||||
for spec_str, (trigger_id, requirements) in cache.items():
|
||||
for (spec_str, _), (trigger_id, requirements) in cache.items():
|
||||
self.gen.fact(fn.pkg_fact(name, fn.trigger_id(trigger_id)))
|
||||
self.gen.fact(fn.pkg_fact(name, fn.trigger_msg(spec_str)))
|
||||
for predicate in requirements:
|
||||
@@ -1323,7 +1388,7 @@ def effect_rules(self):
|
||||
self.gen.h2("Imposed requirements")
|
||||
for name in self._effect_cache:
|
||||
cache = self._effect_cache[name]
|
||||
for spec_str, (effect_id, requirements) in cache.items():
|
||||
for (spec_str, _), (effect_id, requirements) in cache.items():
|
||||
self.gen.fact(fn.pkg_fact(name, fn.effect_id(effect_id)))
|
||||
self.gen.fact(fn.pkg_fact(name, fn.effect_msg(spec_str)))
|
||||
for predicate in requirements:
|
||||
@@ -1422,18 +1487,26 @@ def variant_rules(self, pkg):
|
||||
|
||||
self.gen.newline()
|
||||
|
||||
def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=False):
|
||||
def condition(
|
||||
self,
|
||||
required_spec: spack.spec.Spec,
|
||||
imposed_spec: Optional[spack.spec.Spec] = None,
|
||||
name: Optional[str] = None,
|
||||
msg: Optional[str] = None,
|
||||
transform_required: Optional[TransformFunction] = None,
|
||||
transform_imposed: Optional[TransformFunction] = remove_node,
|
||||
):
|
||||
"""Generate facts for a dependency or virtual provider condition.
|
||||
|
||||
Arguments:
|
||||
required_spec (spack.spec.Spec): the spec that triggers this condition
|
||||
imposed_spec (spack.spec.Spec or None): the spec with constraints that
|
||||
are imposed when this condition is triggered
|
||||
name (str or None): name for `required_spec` (required if
|
||||
required_spec is anonymous, ignored if not)
|
||||
msg (str or None): description of the condition
|
||||
node (bool): if False does not emit "node" or "virtual_node" requirements
|
||||
from the imposed spec
|
||||
required_spec: the constraints that triggers this condition
|
||||
imposed_spec: the constraints that are imposed when this condition is triggered
|
||||
name: name for `required_spec` (required if required_spec is anonymous, ignored if not)
|
||||
msg: description of the condition
|
||||
transform_required: transformation applied to facts from the required spec. Defaults
|
||||
to leave facts as they are.
|
||||
transform_imposed: transformation applied to facts from the imposed spec. Defaults
|
||||
to removing "node" and "virtual_node" facts.
|
||||
Returns:
|
||||
int: id of the condition created by this function
|
||||
"""
|
||||
@@ -1451,10 +1524,14 @@ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=
|
||||
|
||||
cache = self._trigger_cache[named_cond.name]
|
||||
|
||||
named_cond_key = str(named_cond)
|
||||
named_cond_key = (str(named_cond), transform_required)
|
||||
if named_cond_key not in cache:
|
||||
trigger_id = next(self._trigger_id_counter)
|
||||
requirements = self.spec_clauses(named_cond, body=True, required_from=name)
|
||||
|
||||
if transform_required:
|
||||
requirements = transform_required(named_cond, requirements)
|
||||
|
||||
cache[named_cond_key] = (trigger_id, requirements)
|
||||
trigger_id, requirements = cache[named_cond_key]
|
||||
self.gen.fact(fn.pkg_fact(named_cond.name, fn.condition_trigger(condition_id, trigger_id)))
|
||||
@@ -1463,14 +1540,14 @@ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=
|
||||
return condition_id
|
||||
|
||||
cache = self._effect_cache[named_cond.name]
|
||||
imposed_spec_key = str(imposed_spec)
|
||||
imposed_spec_key = (str(imposed_spec), transform_imposed)
|
||||
if imposed_spec_key not in cache:
|
||||
effect_id = next(self._effect_id_counter)
|
||||
requirements = self.spec_clauses(imposed_spec, body=False, required_from=name)
|
||||
if not node:
|
||||
requirements = list(
|
||||
filter(lambda x: x.args[0] not in ("node", "virtual_node"), requirements)
|
||||
)
|
||||
|
||||
if transform_imposed:
|
||||
requirements = transform_imposed(imposed_spec, requirements)
|
||||
|
||||
cache[imposed_spec_key] = (effect_id, requirements)
|
||||
effect_id, requirements = cache[imposed_spec_key]
|
||||
self.gen.fact(fn.pkg_fact(named_cond.name, fn.condition_effect(condition_id, effect_id)))
|
||||
@@ -1530,21 +1607,32 @@ def package_dependencies_rules(self, pkg):
|
||||
if not depflag:
|
||||
continue
|
||||
|
||||
msg = "%s depends on %s" % (pkg.name, dep.spec.name)
|
||||
msg = f"{pkg.name} depends on {dep.spec}"
|
||||
if cond != spack.spec.Spec():
|
||||
msg += " when %s" % cond
|
||||
msg += f" when {cond}"
|
||||
else:
|
||||
pass
|
||||
|
||||
condition_id = self.condition(cond, dep.spec, pkg.name, msg)
|
||||
self.gen.fact(
|
||||
fn.pkg_fact(pkg.name, fn.dependency_condition(condition_id, dep.spec.name))
|
||||
)
|
||||
def track_dependencies(input_spec, requirements):
|
||||
return requirements + [fn.attr("track_dependencies", input_spec.name)]
|
||||
|
||||
for t in dt.ALL_FLAGS:
|
||||
if t & depflag:
|
||||
# there is a declared dependency of type t
|
||||
self.gen.fact(fn.dependency_type(condition_id, dt.flag_to_string(t)))
|
||||
def dependency_holds(input_spec, requirements):
|
||||
return remove_node(input_spec, requirements) + [
|
||||
fn.attr(
|
||||
"dependency_holds", pkg.name, input_spec.name, dt.flag_to_string(t)
|
||||
)
|
||||
for t in dt.ALL_FLAGS
|
||||
if t & depflag
|
||||
]
|
||||
|
||||
self.condition(
|
||||
cond,
|
||||
dep.spec,
|
||||
name=pkg.name,
|
||||
msg=msg,
|
||||
transform_required=track_dependencies,
|
||||
transform_imposed=dependency_holds,
|
||||
)
|
||||
|
||||
self.gen.newline()
|
||||
|
||||
@@ -1559,6 +1647,7 @@ def virtual_preferences(self, pkg_name, func):
|
||||
for i, provider in enumerate(providers):
|
||||
provider_name = spack.spec.Spec(provider).name
|
||||
func(vspec, provider_name, i)
|
||||
self.gen.newline()
|
||||
|
||||
def provider_defaults(self):
|
||||
self.gen.h2("Default virtual providers")
|
||||
@@ -1639,8 +1728,17 @@ def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
|
||||
when_spec = spack.spec.Spec(pkg_name)
|
||||
|
||||
try:
|
||||
# With virtual we want to emit "node" and "virtual_node" in imposed specs
|
||||
transform: Optional[TransformFunction] = remove_node
|
||||
if virtual:
|
||||
transform = None
|
||||
|
||||
member_id = self.condition(
|
||||
required_spec=when_spec, imposed_spec=spec, name=pkg_name, node=virtual
|
||||
required_spec=when_spec,
|
||||
imposed_spec=spec,
|
||||
name=pkg_name,
|
||||
transform_imposed=transform,
|
||||
msg=f"{spec_str} is a requirement for package {pkg_name}",
|
||||
)
|
||||
except Exception as e:
|
||||
# Do not raise if the rule comes from the 'all' subsection, since usability
|
||||
@@ -1703,8 +1801,16 @@ def external_packages(self):
|
||||
# Declare external conditions with a local index into packages.yaml
|
||||
for local_idx, spec in enumerate(external_specs):
|
||||
msg = "%s available as external when satisfying %s" % (spec.name, spec)
|
||||
condition_id = self.condition(spec, msg=msg)
|
||||
self.gen.fact(fn.pkg_fact(pkg_name, fn.possible_external(condition_id, local_idx)))
|
||||
|
||||
def external_imposition(input_spec, _):
|
||||
return [fn.attr("external_conditions_hold", input_spec.name, local_idx)]
|
||||
|
||||
self.condition(
|
||||
spec,
|
||||
spack.spec.Spec(spec.name),
|
||||
msg=msg,
|
||||
transform_imposed=external_imposition,
|
||||
)
|
||||
self.possible_versions[spec.name].add(spec.version)
|
||||
self.gen.newline()
|
||||
|
||||
@@ -1734,8 +1840,8 @@ def preferred_variants(self, pkg_name):
|
||||
fn.variant_default_value_from_packages_yaml(pkg_name, variant.name, value)
|
||||
)
|
||||
|
||||
def target_preferences(self, pkg_name):
|
||||
key_fn = spack.package_prefs.PackagePrefs(pkg_name, "target")
|
||||
def target_preferences(self):
|
||||
key_fn = spack.package_prefs.PackagePrefs("all", "target")
|
||||
|
||||
if not self.target_specs_cache:
|
||||
self.target_specs_cache = [
|
||||
@@ -1745,17 +1851,25 @@ def target_preferences(self, pkg_name):
|
||||
|
||||
package_targets = self.target_specs_cache[:]
|
||||
package_targets.sort(key=key_fn)
|
||||
|
||||
offset = 0
|
||||
best_default = self.default_targets[0][1]
|
||||
for i, preferred in enumerate(package_targets):
|
||||
if str(preferred.architecture.target) == best_default and i != 0:
|
||||
offset = 100
|
||||
self.gen.fact(
|
||||
fn.pkg_fact(
|
||||
pkg_name, fn.target_weight(str(preferred.architecture.target), i + offset)
|
||||
)
|
||||
)
|
||||
self.gen.fact(fn.target_weight(str(preferred.architecture.target), i))
|
||||
|
||||
def flag_defaults(self):
|
||||
self.gen.h2("Compiler flag defaults")
|
||||
|
||||
# types of flags that can be on specs
|
||||
for flag in spack.spec.FlagMap.valid_compiler_flags():
|
||||
self.gen.fact(fn.flag_type(flag))
|
||||
self.gen.newline()
|
||||
|
||||
# flags from compilers.yaml
|
||||
compilers = all_compilers_in_config()
|
||||
for compiler in compilers:
|
||||
for name, flags in compiler.flags.items():
|
||||
for flag in flags:
|
||||
self.gen.fact(
|
||||
fn.compiler_version_flag(compiler.name, compiler.version, name, flag)
|
||||
)
|
||||
|
||||
def spec_clauses(self, *args, **kwargs):
|
||||
"""Wrap a call to `_spec_clauses()` into a try/except block that
|
||||
@@ -1808,7 +1922,7 @@ class Head:
|
||||
node_flag = fn.attr("node_flag_set")
|
||||
node_flag_source = fn.attr("node_flag_source")
|
||||
node_flag_propagate = fn.attr("node_flag_propagate")
|
||||
variant_propagate = fn.attr("variant_propagate")
|
||||
variant_propagation_candidate = fn.attr("variant_propagation_candidate")
|
||||
|
||||
class Body:
|
||||
node = fn.attr("node")
|
||||
@@ -1822,7 +1936,7 @@ class Body:
|
||||
node_flag = fn.attr("node_flag")
|
||||
node_flag_source = fn.attr("node_flag_source")
|
||||
node_flag_propagate = fn.attr("node_flag_propagate")
|
||||
variant_propagate = fn.attr("variant_propagate")
|
||||
variant_propagation_candidate = fn.attr("variant_propagation_candidate")
|
||||
|
||||
f = Body if body else Head
|
||||
|
||||
@@ -1871,7 +1985,9 @@ class Body:
|
||||
clauses.append(f.variant_value(spec.name, vname, value))
|
||||
|
||||
if variant.propagate:
|
||||
clauses.append(f.variant_propagate(spec.name, vname, value, spec.name))
|
||||
clauses.append(
|
||||
f.variant_propagation_candidate(spec.name, vname, value, spec.name)
|
||||
)
|
||||
|
||||
# Tell the concretizer that this is a possible value for the
|
||||
# variant, to account for things like int/str values where we
|
||||
@@ -1918,6 +2034,7 @@ class Body:
|
||||
if not body:
|
||||
for virtual in virtuals:
|
||||
clauses.append(fn.attr("provider_set", spec.name, virtual))
|
||||
clauses.append(fn.attr("virtual_node", virtual))
|
||||
else:
|
||||
for virtual in virtuals:
|
||||
clauses.append(fn.attr("virtual_on_incoming_edges", spec.name, virtual))
|
||||
@@ -2208,6 +2325,8 @@ def target_defaults(self, specs):
|
||||
|
||||
self.default_targets = list(sorted(set(self.default_targets)))
|
||||
|
||||
self.target_preferences()
|
||||
|
||||
def virtual_providers(self):
|
||||
self.gen.h2("Virtual providers")
|
||||
msg = (
|
||||
@@ -2529,7 +2648,6 @@ def setup(
|
||||
self.pkg_rules(pkg, tests=self.tests)
|
||||
self.gen.h2("Package preferences: %s" % pkg)
|
||||
self.preferred_variants(pkg)
|
||||
self.target_preferences(pkg)
|
||||
|
||||
self.gen.h1("Develop specs")
|
||||
# Inject dev_path from environment
|
||||
@@ -2555,20 +2673,45 @@ def setup(
|
||||
self.define_target_constraints()
|
||||
|
||||
def literal_specs(self, specs):
|
||||
for idx, spec in enumerate(specs):
|
||||
for spec in specs:
|
||||
self.gen.h2("Spec: %s" % str(spec))
|
||||
self.gen.fact(fn.literal(idx))
|
||||
condition_id = next(self._condition_id_counter)
|
||||
trigger_id = next(self._trigger_id_counter)
|
||||
|
||||
self.gen.fact(fn.literal(idx, "virtual_root" if spec.virtual else "root", spec.name))
|
||||
for clause in self.spec_clauses(spec):
|
||||
self.gen.fact(fn.literal(idx, *clause.args))
|
||||
if clause.args[0] == "variant_set":
|
||||
self.gen.fact(
|
||||
fn.literal(idx, "variant_default_value_from_cli", *clause.args[1:])
|
||||
# Special condition triggered by "literal_solved"
|
||||
self.gen.fact(fn.literal(trigger_id))
|
||||
self.gen.fact(fn.pkg_fact(spec.name, fn.condition_trigger(condition_id, trigger_id)))
|
||||
self.gen.fact(fn.condition_reason(condition_id, f"{spec} requested from CLI"))
|
||||
|
||||
# Effect imposes the spec
|
||||
imposed_spec_key = str(spec), None
|
||||
cache = self._effect_cache[spec.name]
|
||||
msg = (
|
||||
"literal specs have different requirements. clear cache before computing literals"
|
||||
)
|
||||
assert imposed_spec_key not in cache, msg
|
||||
effect_id = next(self._effect_id_counter)
|
||||
requirements = self.spec_clauses(spec)
|
||||
root_name = spec.name
|
||||
for clause in requirements:
|
||||
clause_name = clause.args[0]
|
||||
if clause_name == "variant_set":
|
||||
requirements.append(
|
||||
fn.attr("variant_default_value_from_cli", *clause.args[1:])
|
||||
)
|
||||
elif clause_name in ("node", "virtual_node", "hash"):
|
||||
# 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))
|
||||
|
||||
requirements.append(fn.attr("virtual_root" if spec.virtual else "root", spec.name))
|
||||
cache[imposed_spec_key] = (effect_id, requirements)
|
||||
self.gen.fact(fn.pkg_fact(spec.name, fn.condition_effect(condition_id, effect_id)))
|
||||
|
||||
if self.concretize_everything:
|
||||
self.gen.fact(fn.solve_literal(idx))
|
||||
self.gen.fact(fn.solve_literal(trigger_id))
|
||||
|
||||
self.effect_rules()
|
||||
|
||||
def validate_and_define_versions_from_requirements(
|
||||
self, *, allow_deprecated: bool, require_checksum: bool
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
% ID of the nodes in the "root" link-run sub-DAG
|
||||
#const min_dupe_id = 0.
|
||||
|
||||
#const link_run = 0.
|
||||
#const direct_link_run =1.
|
||||
#const direct_build = 2.
|
||||
#const direct_link_run = 0.
|
||||
#const direct_build = 1.
|
||||
|
||||
% Allow clingo to create nodes
|
||||
{ attr("node", node(0..X-1, Package)) } :- max_dupes(Package, X), not virtual(Package).
|
||||
@@ -30,23 +29,21 @@
|
||||
:- attr("variant_value", PackageNode, _, _), not attr("node", PackageNode).
|
||||
:- attr("node_flag_compiler_default", PackageNode), not attr("node", PackageNode).
|
||||
:- attr("node_flag", PackageNode, _, _), not attr("node", PackageNode).
|
||||
:- attr("node_flag_source", PackageNode, _, _), not attr("node", PackageNode).
|
||||
:- attr("no_flags", PackageNode, _), not attr("node", PackageNode).
|
||||
:- attr("external_spec_selected", PackageNode, _), not attr("node", PackageNode).
|
||||
:- attr("depends_on", ParentNode, _, _), not attr("node", ParentNode).
|
||||
:- attr("depends_on", _, ChildNode, _), not attr("node", ChildNode).
|
||||
:- attr("node_flag_source", ParentNode, _, _), not attr("node", ParentNode).
|
||||
:- attr("node_flag_source", _, _, ChildNode), not attr("node", ChildNode).
|
||||
:- attr("virtual_node", VirtualNode), not provider(_, VirtualNode), internal_error("virtual node with no provider").
|
||||
:- provider(_, VirtualNode), not attr("virtual_node", VirtualNode), internal_error("provider with no virtual node").
|
||||
:- provider(PackageNode, _), not attr("node", PackageNode), internal_error("provider with no real node").
|
||||
|
||||
:- attr("virtual_node", VirtualNode), not provider(_, VirtualNode).
|
||||
:- provider(_, VirtualNode), not attr("virtual_node", VirtualNode).
|
||||
:- provider(PackageNode, _), not attr("node", PackageNode).
|
||||
|
||||
:- attr("root", node(ID, PackageNode)), ID > min_dupe_id.
|
||||
:- attr("root", node(ID, PackageNode)), ID > min_dupe_id, internal_error("root with a non-minimal duplicate ID").
|
||||
|
||||
% Nodes in the "root" unification set cannot depend on non-root nodes if the dependency is "link" or "run"
|
||||
:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "link"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)).
|
||||
:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "run"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)).
|
||||
:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "link"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("link dependency out of the root unification set").
|
||||
:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "run"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("run dependency out of the root unification set").
|
||||
|
||||
% Rules on "unification sets", i.e. on sets of nodes allowing a single configuration of any given package
|
||||
unify(SetID, PackageName) :- unification_set(SetID, node(_, PackageName)).
|
||||
@@ -86,22 +83,24 @@ unification_set(SetID, VirtualNode)
|
||||
%----
|
||||
|
||||
% In the "root" unification set only ID = 0 are allowed
|
||||
:- unification_set("root", node(ID, _)), ID != 0.
|
||||
:- unification_set("root", node(ID, _)), ID != 0, internal_error("root unification set has node with non-zero unification set ID").
|
||||
|
||||
% In the "root" unification set we allow only packages from the link-run possible subDAG
|
||||
:- unification_set("root", node(_, Package)), not possible_in_link_run(Package), not virtual(Package).
|
||||
:- unification_set("root", node(_, Package)), not possible_in_link_run(Package), not virtual(Package), internal_error("package outside possible link/run graph in root unification set").
|
||||
|
||||
% Each node must belong to at least one unification set
|
||||
:- attr("node", PackageNode), not unification_set(_, PackageNode).
|
||||
:- attr("node", PackageNode), not unification_set(_, PackageNode), internal_error("node belongs to no unification set").
|
||||
|
||||
% Cannot have a node with an ID, if lower ID of the same package are not used
|
||||
:- attr("node", node(ID1, Package)),
|
||||
not attr("node", node(ID2, Package)),
|
||||
max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1.
|
||||
max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1,
|
||||
internal_error("node skipped id number").
|
||||
|
||||
:- attr("virtual_node", node(ID1, Package)),
|
||||
not attr("virtual_node", node(ID2, Package)),
|
||||
max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1.
|
||||
max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1,
|
||||
internal_error("virtual node skipped id number").
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Map literal input specs to facts that drive the solve
|
||||
@@ -115,29 +114,28 @@ multiple_nodes_attribute("depends_on").
|
||||
multiple_nodes_attribute("virtual_on_edge").
|
||||
multiple_nodes_attribute("provider_set").
|
||||
|
||||
% Map constraint on the literal ID to facts on the node
|
||||
attr(Name, node(min_dupe_id, A1)) :- literal(LiteralID, Name, A1), solve_literal(LiteralID).
|
||||
attr(Name, node(min_dupe_id, A1), A2) :- literal(LiteralID, Name, A1, A2), solve_literal(LiteralID), not multiple_nodes_attribute(Name).
|
||||
attr(Name, node(min_dupe_id, A1), A2, A3) :- literal(LiteralID, Name, A1, A2, A3), solve_literal(LiteralID), not multiple_nodes_attribute(Name).
|
||||
attr(Name, node(min_dupe_id, A1), A2, A3, A4) :- literal(LiteralID, Name, A1, A2, A3, A4), solve_literal(LiteralID).
|
||||
trigger_condition_holds(TriggerID, node(min_dupe_id, Package)) :-
|
||||
solve_literal(TriggerID),
|
||||
pkg_fact(Package, condition_trigger(_, TriggerID)),
|
||||
literal(TriggerID).
|
||||
|
||||
% Special cases where nodes occur in arguments other than A1
|
||||
attr("node_flag_source", node(min_dupe_id, A1), A2, node(min_dupe_id, A3)) :- literal(LiteralID, "node_flag_source", A1, A2, A3), solve_literal(LiteralID).
|
||||
attr("depends_on", node(min_dupe_id, A1), node(min_dupe_id, A2), A3) :- literal(LiteralID, "depends_on", A1, A2, A3), solve_literal(LiteralID).
|
||||
trigger_node(TriggerID, Node, Node) :-
|
||||
trigger_condition_holds(TriggerID, Node),
|
||||
literal(TriggerID).
|
||||
|
||||
attr("virtual_node", node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", _, Virtual), solve_literal(LiteralID).
|
||||
attr("provider_set", node(min_dupe_id, Provider), node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", Provider, Virtual), solve_literal(LiteralID).
|
||||
provider(node(min_dupe_id, Provider), node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", Provider, Virtual), solve_literal(LiteralID).
|
||||
% Since we trigger the existence of literal nodes from a condition, we need to construct
|
||||
% the condition_set/2 manually below
|
||||
mentioned_in_literal(Root, Mentioned) :- mentioned_in_literal(TriggerID, Root, Mentioned), solve_literal(TriggerID).
|
||||
condition_set(node(min_dupe_id, Root), node(min_dupe_id, Mentioned)) :- mentioned_in_literal(Root, Mentioned).
|
||||
|
||||
% Discriminate between "roots" that have been explicitly requested, and roots that are deduced from "virtual roots"
|
||||
explicitly_requested_root(node(min_dupe_id, A1)) :- literal(LiteralID, "root", A1), solve_literal(LiteralID).
|
||||
explicitly_requested_root(node(min_dupe_id, Package)) :-
|
||||
solve_literal(TriggerID),
|
||||
trigger_and_effect(Package, TriggerID, EffectID),
|
||||
imposed_constraint(EffectID, "root", Package).
|
||||
|
||||
#defined concretize_everything/0.
|
||||
#defined literal/1.
|
||||
#defined literal/3.
|
||||
#defined literal/4.
|
||||
#defined literal/5.
|
||||
#defined literal/6.
|
||||
|
||||
% Attributes for node packages which must have a single value
|
||||
attr_single_value("version").
|
||||
@@ -235,7 +233,8 @@ possible_version_weight(node(ID, Package), Weight)
|
||||
|
||||
1 { version_weight(node(ID, Package), Weight) : pkg_fact(Package, version_declared(Version, Weight)) } 1
|
||||
:- attr("version", node(ID, Package), Version),
|
||||
attr("node", node(ID, Package)).
|
||||
attr("node", node(ID, Package)),
|
||||
internal_error("version weights must exist and be unique").
|
||||
|
||||
% node_version_satisfies implies that exactly one of the satisfying versions
|
||||
% is the package's version, and vice versa.
|
||||
@@ -249,7 +248,8 @@ possible_version_weight(node(ID, Package), Weight)
|
||||
% bound on the choice rule to avoid false positives with the error below
|
||||
1 { attr("version", node(ID, Package), Version) : pkg_fact(Package, version_satisfies(Constraint, Version)) }
|
||||
:- attr("node_version_satisfies", node(ID, Package), Constraint),
|
||||
pkg_fact(Package, version_satisfies(Constraint, _)).
|
||||
pkg_fact(Package, version_satisfies(Constraint, _)),
|
||||
internal_error("must choose a single version to satisfy version constraints").
|
||||
|
||||
% More specific error message if the version cannot satisfy some constraint
|
||||
% Otherwise covered by `no_version_error` and `versions_conflict_error`.
|
||||
@@ -362,7 +362,7 @@ imposed_nodes(ConditionID, PackageNode, node(X, A1))
|
||||
|
||||
% Conditions that hold impose may impose constraints on other specs
|
||||
attr(Name, node(X, A1)) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1), imposed_nodes(ID, PackageNode, node(X, A1)).
|
||||
attr(Name, node(X, A1), A2) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2), imposed_nodes(ID, PackageNode, node(X, A1)).
|
||||
attr(Name, node(X, A1), A2) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2), imposed_nodes(ID, PackageNode, node(X, A1)), not multiple_nodes_attribute(Name).
|
||||
attr(Name, node(X, A1), A2, A3) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2, A3), imposed_nodes(ID, PackageNode, node(X, A1)), not multiple_nodes_attribute(Name).
|
||||
attr(Name, node(X, A1), A2, A3, A4) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2, A3, A4), imposed_nodes(ID, PackageNode, node(X, A1)).
|
||||
|
||||
@@ -373,6 +373,16 @@ attr("node_flag_source", node(X, A1), A2, node(Y, A3))
|
||||
imposed_constraint(ID, "node_flag_source", A1, A2, A3),
|
||||
condition_set(node(Y, A3), node(X, A1)).
|
||||
|
||||
% Provider set is relevant only for literals, since it's the only place where `^[virtuals=foo] bar`
|
||||
% might appear in the HEAD of a rule
|
||||
attr("provider_set", node(min_dupe_id, Provider), node(min_dupe_id, Virtual))
|
||||
:- solve_literal(TriggerID),
|
||||
trigger_and_effect(_, TriggerID, EffectID),
|
||||
impose(EffectID, _),
|
||||
imposed_constraint(EffectID, "provider_set", Provider, Virtual).
|
||||
|
||||
provider(ProviderNode, VirtualNode) :- attr("provider_set", ProviderNode, VirtualNode).
|
||||
|
||||
% Here we can't use the condition set because it's a recursive definition, that doesn't define the
|
||||
% node index, and leads to unsatisfiability. Hence we say that one and only one node index must
|
||||
% satisfy the dependency.
|
||||
@@ -432,24 +442,11 @@ depends_on(PackageNode, DependencyNode) :- attr("depends_on", PackageNode, Depen
|
||||
% concrete. We chop off dependencies for externals, and dependencies of
|
||||
% concrete specs don't need to be resolved -- they arise from the concrete
|
||||
% specs themselves.
|
||||
dependency_holds(node(NodeID, Package), Dependency, Type) :-
|
||||
pkg_fact(Package, dependency_condition(ID, Dependency)),
|
||||
dependency_type(ID, Type),
|
||||
build(node(NodeID, Package)),
|
||||
not external(node(NodeID, Package)),
|
||||
condition_holds(ID, node(NodeID, Package)).
|
||||
|
||||
% We cut off dependencies of externals (as we don't really know them).
|
||||
% Don't impose constraints on dependencies that don't exist.
|
||||
do_not_impose(EffectID, node(NodeID, Package)) :-
|
||||
not dependency_holds(node(NodeID, Package), Dependency, _),
|
||||
attr("node", node(NodeID, Package)),
|
||||
pkg_fact(Package, dependency_condition(ID, Dependency)),
|
||||
pkg_fact(Package, condition_effect(ID, EffectID)).
|
||||
attr("track_dependencies", Node) :- build(Node), not external(Node).
|
||||
|
||||
% If a dependency holds on a package node, there must be one and only one dependency node satisfying it
|
||||
1 { attr("depends_on", PackageNode, node(0..Y-1, Dependency), Type) : max_dupes(Dependency, Y) } 1
|
||||
:- dependency_holds(PackageNode, Dependency, Type),
|
||||
:- attr("dependency_holds", PackageNode, Dependency, Type),
|
||||
not virtual(Dependency).
|
||||
|
||||
% all nodes in the graph must be reachable from some root
|
||||
@@ -499,7 +496,7 @@ error(100, "Package '{0}' needs to provide both '{1}' and '{2}' together, but pr
|
||||
% if a package depends on a virtual, it's not external and we have a
|
||||
% provider for that virtual then it depends on the provider
|
||||
node_depends_on_virtual(PackageNode, Virtual, Type)
|
||||
:- dependency_holds(PackageNode, Virtual, Type),
|
||||
:- attr("dependency_holds", PackageNode, Virtual, Type),
|
||||
virtual(Virtual),
|
||||
not external(PackageNode).
|
||||
|
||||
@@ -509,7 +506,7 @@ node_depends_on_virtual(PackageNode, Virtual) :- node_depends_on_virtual(Package
|
||||
:- node_depends_on_virtual(PackageNode, Virtual, Type).
|
||||
|
||||
attr("virtual_on_edge", PackageNode, ProviderNode, Virtual)
|
||||
:- dependency_holds(PackageNode, Virtual, Type),
|
||||
:- attr("dependency_holds", PackageNode, Virtual, Type),
|
||||
attr("depends_on", PackageNode, ProviderNode, Type),
|
||||
provider(ProviderNode, node(_, Virtual)),
|
||||
not external(PackageNode).
|
||||
@@ -592,21 +589,15 @@ possible_provider_weight(DependencyNode, VirtualNode, 0, "external")
|
||||
:- provider(DependencyNode, VirtualNode),
|
||||
external(DependencyNode).
|
||||
|
||||
% A provider mentioned in packages.yaml can use a weight
|
||||
% according to its priority in the list of providers
|
||||
possible_provider_weight(node(DependencyID, Dependency), node(VirtualID, Virtual), Weight, "packages_yaml")
|
||||
:- provider(node(DependencyID, Dependency), node(VirtualID, Virtual)),
|
||||
depends_on(node(ID, Package), node(DependencyID, Dependency)),
|
||||
pkg_fact(Package, provider_preference(Virtual, Dependency, Weight)).
|
||||
|
||||
% A provider mentioned in the default configuration can use a weight
|
||||
% according to its priority in the list of providers
|
||||
possible_provider_weight(node(DependencyID, Dependency), node(VirtualID, Virtual), Weight, "default")
|
||||
:- provider(node(DependencyID, Dependency), node(VirtualID, Virtual)),
|
||||
default_provider_preference(Virtual, Dependency, Weight).
|
||||
possible_provider_weight(node(ProviderID, Provider), node(VirtualID, Virtual), Weight, "default")
|
||||
:- provider(node(ProviderID, Provider), node(VirtualID, Virtual)),
|
||||
default_provider_preference(Virtual, Provider, Weight).
|
||||
|
||||
% Any provider can use 100 as a weight, which is very high and discourage its use
|
||||
possible_provider_weight(node(DependencyID, Dependency), VirtualNode, 100, "fallback") :- provider(node(DependencyID, Dependency), VirtualNode).
|
||||
possible_provider_weight(node(ProviderID, Provider), VirtualNode, 100, "fallback")
|
||||
:- provider(node(ProviderID, Provider), VirtualNode).
|
||||
|
||||
% do not warn if generated program contains none of these.
|
||||
#defined virtual/1.
|
||||
@@ -624,11 +615,11 @@ possible_provider_weight(node(DependencyID, Dependency), VirtualNode, 100, "fall
|
||||
pkg_fact(Package, version_declared(Version, Weight, "external")) }
|
||||
:- external(node(ID, Package)).
|
||||
|
||||
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
|
||||
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec version", Package)
|
||||
:- external(node(ID, Package)),
|
||||
not external_version(node(ID, Package), _, _).
|
||||
|
||||
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
|
||||
error(100, "Attempted to use external for '{0}' which does not satisfy a unique configured external spec version", Package)
|
||||
:- external(node(ID, Package)),
|
||||
2 { external_version(node(ID, Package), Version, Weight) }.
|
||||
|
||||
@@ -657,18 +648,15 @@ external(PackageNode) :- attr("external_spec_selected", PackageNode, _).
|
||||
|
||||
% determine if an external spec has been selected
|
||||
attr("external_spec_selected", node(ID, Package), LocalIndex) :-
|
||||
external_conditions_hold(node(ID, Package), LocalIndex),
|
||||
attr("external_conditions_hold", node(ID, Package), LocalIndex),
|
||||
attr("node", node(ID, Package)),
|
||||
not attr("hash", node(ID, Package), _).
|
||||
|
||||
external_conditions_hold(node(PackageID, Package), LocalIndex) :-
|
||||
pkg_fact(Package, possible_external(ID, LocalIndex)), condition_holds(ID, node(PackageID, Package)).
|
||||
|
||||
% it cannot happen that a spec is external, but none of the external specs
|
||||
% conditions hold.
|
||||
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
|
||||
:- external(node(ID, Package)),
|
||||
not external_conditions_hold(node(ID, Package), _).
|
||||
not attr("external_conditions_hold", node(ID, Package), _).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Config required semantics
|
||||
@@ -769,23 +757,36 @@ node_has_variant(node(ID, Package), Variant) :-
|
||||
pkg_fact(Package, variant(Variant)),
|
||||
attr("node", node(ID, Package)).
|
||||
|
||||
attr("variant_propagate", PackageNode, Variant, Value, Source) :-
|
||||
% Variant propagation is forwarded to dependencies
|
||||
attr("variant_propagation_candidate", PackageNode, Variant, Value, Source) :-
|
||||
attr("node", PackageNode),
|
||||
depends_on(ParentNode, PackageNode),
|
||||
attr("variant_propagate", ParentNode, Variant, Value, Source),
|
||||
not attr("variant_set", PackageNode, Variant).
|
||||
attr("variant_value", node(_, Source), Variant, Value),
|
||||
attr("variant_propagation_candidate", ParentNode, Variant, _, Source).
|
||||
|
||||
attr("variant_value", node(ID, Package), Variant, Value) :-
|
||||
attr("node", node(ID, Package)),
|
||||
% If the node is a candidate, and it has the variant and value,
|
||||
% then those variant and value should be propagated
|
||||
attr("variant_propagate", node(ID, Package), Variant, Value, Source) :-
|
||||
attr("variant_propagation_candidate", node(ID, Package), Variant, Value, Source),
|
||||
node_has_variant(node(ID, Package), Variant),
|
||||
attr("variant_propagate", node(ID, Package), Variant, Value, _),
|
||||
pkg_fact(Package, variant_possible_value(Variant, Value)).
|
||||
pkg_fact(Package, variant_possible_value(Variant, Value)),
|
||||
not attr("variant_set", node(ID, Package), Variant).
|
||||
|
||||
% Propagate the value, if there is the corresponding attribute
|
||||
attr("variant_value", PackageNode, Variant, Value) :- attr("variant_propagate", PackageNode, Variant, Value, _).
|
||||
|
||||
% If a variant is propagated, we cannot have extraneous values (this is for multi valued variants)
|
||||
variant_is_propagated(PackageNode, Variant) :- attr("variant_propagate", PackageNode, Variant, _, _).
|
||||
:- variant_is_propagated(PackageNode, Variant),
|
||||
attr("variant_value", PackageNode, Variant, Value),
|
||||
not attr("variant_propagate", PackageNode, Variant, Value, _).
|
||||
|
||||
% Cannot receive different values from different sources on the same variant
|
||||
error(100, "{0} and {1} cannot both propagate variant '{2}' to package {3} with values '{4}' and '{5}'", Source1, Source2, Variant, Package, Value1, Value2) :-
|
||||
attr("variant_propagate", node(X, Package), Variant, Value1, Source1),
|
||||
attr("variant_propagate", node(X, Package), Variant, Value2, Source2),
|
||||
node_has_variant(node(X, Package), Variant),
|
||||
Value1 < Value2.
|
||||
Value1 < Value2, Source1 < Source2.
|
||||
|
||||
% a variant cannot be set if it is not a variant on the package
|
||||
error(100, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package)
|
||||
@@ -887,8 +888,9 @@ variant_default_not_used(node(ID, Package), Variant, Value)
|
||||
% The variant is set in an external spec
|
||||
external_with_variant_set(node(NodeID, Package), Variant, Value)
|
||||
:- attr("variant_value", node(NodeID, Package), Variant, Value),
|
||||
condition_requirement(ID, "variant_value", Package, Variant, Value),
|
||||
pkg_fact(Package, possible_external(ID, _)),
|
||||
condition_requirement(TriggerID, "variant_value", Package, Variant, Value),
|
||||
trigger_and_effect(Package, TriggerID, EffectID),
|
||||
imposed_constraint(EffectID, "external_conditions_hold", Package, _),
|
||||
external(node(NodeID, Package)),
|
||||
attr("node", node(NodeID, Package)).
|
||||
|
||||
@@ -1064,7 +1066,7 @@ attr("node_target", PackageNode, Target)
|
||||
node_target_weight(node(ID, Package), Weight)
|
||||
:- attr("node", node(ID, Package)),
|
||||
attr("node_target", node(ID, Package), Target),
|
||||
pkg_fact(Package, target_weight(Target, Weight)).
|
||||
target_weight(Target, Weight).
|
||||
|
||||
% compatibility rules for targets among nodes
|
||||
node_target_match(ParentNode, DependencyNode)
|
||||
@@ -1186,23 +1188,17 @@ compiler_mismatch_required(PackageNode, DependencyNode)
|
||||
#defined allow_compiler/2.
|
||||
|
||||
% compilers weighted by preference according to packages.yaml
|
||||
compiler_weight(node(ID, Package), Weight)
|
||||
node_compiler_weight(node(ID, Package), Weight)
|
||||
:- node_compiler(node(ID, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version(CompilerID, V),
|
||||
pkg_fact(Package, node_compiler_preference(Compiler, V, Weight)).
|
||||
compiler_weight(node(ID, Package), Weight)
|
||||
compiler_weight(CompilerID, Weight).
|
||||
|
||||
node_compiler_weight(node(ID, Package), 100)
|
||||
:- node_compiler(node(ID, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version(CompilerID, V),
|
||||
not pkg_fact(Package, node_compiler_preference(Compiler, V, _)),
|
||||
default_compiler_preference(CompilerID, Weight).
|
||||
compiler_weight(node(ID, Package), 100)
|
||||
:- node_compiler(node(ID, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
compiler_version(CompilerID, V),
|
||||
not pkg_fact(Package, node_compiler_preference(Compiler, V, _)),
|
||||
not default_compiler_preference(CompilerID, _).
|
||||
not compiler_weight(CompilerID, _).
|
||||
|
||||
% For the time being, be strict and reuse only if the compiler match one we have on the system
|
||||
error(100, "Compiler {1}@{2} requested for {0} cannot be found. Set install_missing_compilers:true if intended.", Package, Compiler, Version)
|
||||
@@ -1210,7 +1206,7 @@ error(100, "Compiler {1}@{2} requested for {0} cannot be found. Set install_miss
|
||||
not node_compiler(node(ID, Package), _).
|
||||
|
||||
#defined node_compiler_preference/4.
|
||||
#defined default_compiler_preference/3.
|
||||
#defined compiler_weight/3.
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Compiler flags
|
||||
@@ -1534,7 +1530,7 @@ opt_criterion(15, "non-preferred compilers").
|
||||
#minimize{ 0@15: #true }.
|
||||
#minimize{
|
||||
Weight@15+Priority,PackageNode
|
||||
: compiler_weight(PackageNode, Weight),
|
||||
: node_compiler_weight(PackageNode, Weight),
|
||||
build_priority(PackageNode, Priority)
|
||||
}.
|
||||
|
||||
|
||||
@@ -24,4 +24,29 @@
|
||||
#show error/5.
|
||||
#show error/6.
|
||||
|
||||
% for error causation
|
||||
#show condition_reason/2.
|
||||
|
||||
% For error messages to use later
|
||||
#show pkg_fact/2.
|
||||
#show condition_holds/2.
|
||||
#show imposed_constraint/3.
|
||||
#show imposed_constraint/4.
|
||||
#show imposed_constraint/5.
|
||||
#show imposed_constraint/6.
|
||||
#show condition_requirement/3.
|
||||
#show condition_requirement/4.
|
||||
#show condition_requirement/5.
|
||||
#show condition_requirement/6.
|
||||
#show node_has_variant/2.
|
||||
#show build/1.
|
||||
#show external/1.
|
||||
#show external_version/3.
|
||||
#show trigger_and_effect/3.
|
||||
#show unification_set/2.
|
||||
#show provider/2.
|
||||
#show condition_nodes/3.
|
||||
#show trigger_node/3.
|
||||
#show imposed_nodes/3.
|
||||
|
||||
% debug
|
||||
|
||||
239
lib/spack/spack/solver/error_messages.lp
Normal file
239
lib/spack/spack/solver/error_messages.lp
Normal file
@@ -0,0 +1,239 @@
|
||||
% Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||
% Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
%
|
||||
% SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
%=============================================================================
|
||||
% This logic program adds detailed error messages to Spack's concretizer
|
||||
%=============================================================================
|
||||
|
||||
#program error_messages.
|
||||
|
||||
% Create a causal tree between trigger conditions by locating the effect conditions
|
||||
% that are triggers for another condition. Condition2 is caused by Condition1
|
||||
condition_cause(Condition2, ID2, Condition1, ID1) :-
|
||||
condition_holds(Condition2, node(ID2, Package2)),
|
||||
pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
|
||||
condition_requirement(Trigger, Name, Package),
|
||||
condition_nodes(Trigger, TriggerNode, node(ID, Package)),
|
||||
trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
|
||||
attr(Name, node(ID, Package)),
|
||||
condition_holds(Condition1, node(ID1, Package1)),
|
||||
pkg_fact(Package1, condition_effect(Condition1, Effect)),
|
||||
imposed_constraint(Effect, Name, Package),
|
||||
imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
|
||||
|
||||
condition_cause(Condition2, ID2, Condition1, ID1) :-
|
||||
condition_holds(Condition2, node(ID2, Package2)),
|
||||
pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
|
||||
condition_requirement(Trigger, Name, Package, A1),
|
||||
condition_nodes(Trigger, TriggerNode, node(ID, Package)),
|
||||
trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
|
||||
attr(Name, node(ID, Package), A1),
|
||||
condition_holds(Condition1, node(ID1, Package1)),
|
||||
pkg_fact(Package1, condition_effect(Condition1, Effect)),
|
||||
imposed_constraint(Effect, Name, Package, A1),
|
||||
imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
|
||||
|
||||
condition_cause(Condition2, ID2, Condition1, ID1) :-
|
||||
condition_holds(Condition2, node(ID2, Package2)),
|
||||
pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
|
||||
condition_requirement(Trigger, Name, Package, A1, A2),
|
||||
condition_nodes(Trigger, TriggerNode, node(ID, Package)),
|
||||
trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
|
||||
attr(Name, node(ID, Package), A1, A2),
|
||||
condition_holds(Condition1, node(ID1, Package1)),
|
||||
pkg_fact(Package1, condition_effect(Condition1, Effect)),
|
||||
imposed_constraint(Effect, Name, Package, A1, A2),
|
||||
imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
|
||||
|
||||
condition_cause(Condition2, ID2, Condition1, ID1) :-
|
||||
condition_holds(Condition2, node(ID2, Package2)),
|
||||
pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
|
||||
condition_requirement(Trigger, Name, Package, A1, A2, A3),
|
||||
condition_nodes(Trigger, TriggerNode, node(ID, Package)),
|
||||
trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
|
||||
attr(Name, node(ID, Package), A1, A2, A3),
|
||||
condition_holds(Condition1, node(ID1, Package1)),
|
||||
pkg_fact(Package1, condition_effect(Condition1, Effect)),
|
||||
imposed_constraint(Effect, Name, Package, A1, A2, A3),
|
||||
imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
|
||||
|
||||
% special condition cause for dependency conditions
|
||||
% we can't simply impose the existence of the node for dependency conditions
|
||||
% because we need to allow for the choice of which dupe ID the node gets
|
||||
condition_cause(Condition2, ID2, Condition1, ID1) :-
|
||||
condition_holds(Condition2, node(ID2, Package2)),
|
||||
pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
|
||||
condition_requirement(Trigger, "node", Package),
|
||||
condition_nodes(Trigger, TriggerNode, node(ID, Package)),
|
||||
trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
|
||||
attr("node", node(ID, Package)),
|
||||
condition_holds(Condition1, node(ID1, Package1)),
|
||||
pkg_fact(Package1, condition_effect(Condition1, Effect)),
|
||||
imposed_constraint(Effect, "dependency_holds", Parent, Package, Type),
|
||||
imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)),
|
||||
attr("depends_on", node(X, Parent), node(ID, Package), Type).
|
||||
|
||||
% The literal startcauses is used to separate the variables that are part of the error from the
|
||||
% ones describing the causal tree of the error. After startcauses, each successive pair must be
|
||||
% a condition and a condition_set id for which it holds.
|
||||
|
||||
% More specific error message if the version cannot satisfy some constraint
|
||||
% Otherwise covered by `no_version_error` and `versions_conflict_error`.
|
||||
error(1, "Cannot satisfy '{0}@{1}'", Package, Constraint, startcauses, ConstraintCause, CauseID)
|
||||
:- attr("node_version_satisfies", node(ID, Package), Constraint),
|
||||
pkg_fact(TriggerPkg, condition_effect(ConstraintCause, EffectID)),
|
||||
imposed_constraint(EffectID, "node_version_satisfies", Package, Constraint),
|
||||
condition_holds(ConstraintCause, node(CauseID, TriggerPkg)),
|
||||
attr("version", node(ID, Package), Version),
|
||||
not pkg_fact(Package, version_satisfies(Constraint, Version)).
|
||||
|
||||
error(0, "Cannot satisfy '{0}@{1}' and '{0}@{2}", Package, Constraint1, Constraint2, startcauses, Cause1, C1ID, Cause2, C2ID)
|
||||
:- attr("node_version_satisfies", node(ID, Package), Constraint1),
|
||||
pkg_fact(TriggerPkg1, condition_effect(Cause1, EffectID1)),
|
||||
imposed_constraint(EffectID1, "node_version_satisfies", Package, Constraint1),
|
||||
condition_holds(Cause1, node(C1ID, TriggerPkg1)),
|
||||
% two constraints
|
||||
attr("node_version_satisfies", node(ID, Package), Constraint2),
|
||||
pkg_fact(TriggerPkg2, condition_effect(Cause2, EffectID2)),
|
||||
imposed_constraint(EffectID2, "node_version_satisfies", Package, Constraint2),
|
||||
condition_holds(Cause2, node(C2ID, TriggerPkg2)),
|
||||
% version chosen
|
||||
attr("version", node(ID, Package), Version),
|
||||
% version satisfies one but not the other
|
||||
pkg_fact(Package, version_satisfies(Constraint1, Version)),
|
||||
not pkg_fact(Package, version_satisfies(Constraint2, Version)).
|
||||
|
||||
% causation tracking error for no or multiple virtual providers
|
||||
error(0, "Cannot find a valid provider for virtual {0}", Virtual, startcauses, Cause, CID)
|
||||
:- attr("virtual_node", node(X, Virtual)),
|
||||
not provider(_, node(X, Virtual)),
|
||||
imposed_constraint(EID, "dependency_holds", Parent, Virtual, Type),
|
||||
pkg_fact(TriggerPkg, condition_effect(Cause, EID)),
|
||||
condition_holds(Cause, node(CID, TriggerPkg)).
|
||||
|
||||
|
||||
% At most one variant value for single-valued variants
|
||||
error(0, "'{0}' required multiple values for single-valued variant '{1}'\n Requested 'Spec({1}={2})' and 'Spec({1}={3})'", Package, Variant, Value1, Value2, startcauses, Cause1, X, Cause2, X)
|
||||
:- attr("node", node(X, Package)),
|
||||
node_has_variant(node(X, Package), Variant),
|
||||
pkg_fact(Package, variant_single_value(Variant)),
|
||||
build(node(X, Package)),
|
||||
attr("variant_value", node(X, Package), Variant, Value1),
|
||||
imposed_constraint(EID1, "variant_set", Package, Variant, Value1),
|
||||
pkg_fact(TriggerPkg1, condition_effect(Cause1, EID1)),
|
||||
condition_holds(Cause1, node(X, TriggerPkg1)),
|
||||
attr("variant_value", node(X, Package), Variant, Value2),
|
||||
imposed_constraint(EID2, "variant_set", Package, Variant, Value2),
|
||||
pkg_fact(TriggerPkg2, condition_effect(Cause2, EID2)),
|
||||
condition_holds(Cause2, node(X, TriggerPkg2)),
|
||||
Value1 < Value2. % see[1] in concretize.lp
|
||||
|
||||
% Externals have to specify external conditions
|
||||
error(0, "Attempted to use external for {0} which does not satisfy any configured external spec version", Package, startcauses, ExternalCause, CID)
|
||||
:- external(node(ID, Package)),
|
||||
attr("external_spec_selected", node(ID, Package), Index),
|
||||
imposed_constraint(EID, "external_conditions_hold", Package, Index),
|
||||
pkg_fact(TriggerPkg, condition_effect(ExternalCause, EID)),
|
||||
condition_holds(ExternalCause, node(CID, TriggerPkg)),
|
||||
not external_version(node(ID, Package), _, _).
|
||||
|
||||
error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}') is an external constraint for {0} which was not satisfied", Package, Name, A1)
|
||||
:- external(node(ID, Package)),
|
||||
not attr("external_conditions_hold", node(ID, Package), _),
|
||||
imposed_constraint(EID, "external_conditions_hold", Package, _),
|
||||
trigger_and_effect(Package, TID, EID),
|
||||
condition_requirement(TID, Name, A1),
|
||||
not attr(Name, node(_, A1)).
|
||||
|
||||
error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}', '{3}') is an external constraint for {0} which was not satisfied", Package, Name, A1, A2)
|
||||
:- external(node(ID, Package)),
|
||||
not attr("external_conditions_hold", node(ID, Package), _),
|
||||
imposed_constraint(EID, "external_conditions_hold", Package, _),
|
||||
trigger_and_effect(Package, TID, EID),
|
||||
condition_requirement(TID, Name, A1, A2),
|
||||
not attr(Name, node(_, A1), A2).
|
||||
|
||||
error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}', '{3}', '{4}') is an external constraint for {0} which was not satisfied", Package, Name, A1, A2, A3)
|
||||
:- external(node(ID, Package)),
|
||||
not attr("external_conditions_hold", node(ID, Package), _),
|
||||
imposed_constraint(EID, "external_conditions_hold", Package, _),
|
||||
trigger_and_effect(Package, TID, EID),
|
||||
condition_requirement(TID, Name, A1, A2, A3),
|
||||
not attr(Name, node(_, A1), A2, A3).
|
||||
|
||||
error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n 'Spec({0} {1}={2})' is an external constraint for {0} which was not satisfied\n 'Spec({0} {1}={3})' required", Package, Variant, Value, OtherValue, startcauses, OtherValueCause, CID)
|
||||
:- external(node(ID, Package)),
|
||||
not attr("external_conditions_hold", node(ID, Package), _),
|
||||
imposed_constraint(EID, "external_conditions_hold", Package, _),
|
||||
trigger_and_effect(Package, TID, EID),
|
||||
condition_requirement(TID, "variant_value", Package, Variant, Value),
|
||||
not attr("variant_value", node(ID, Package), Variant, Value),
|
||||
attr("variant_value", node(ID, Package), Variant, OtherValue),
|
||||
imposed_constraint(EID2, "variant_set", Package, Variant, OtherValue),
|
||||
pkg_fact(TriggerPkg, condition_effect(OtherValueCause, EID2)),
|
||||
condition_holds(OtherValueCause, node(CID, TriggerPkg)).
|
||||
|
||||
error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}', '{3}', '{4}', '{5}') is an external constraint for {0} which was not satisfied", Package, Name, A1, A2, A3, A4)
|
||||
:- external(node(ID, Package)),
|
||||
not attr("external_conditions_hold", node(ID, Package), _),
|
||||
imposed_constraint(EID, "external_conditions_hold", Package, _),
|
||||
trigger_and_effect(Package, TID, EID),
|
||||
condition_requirement(TID, Name, A1, A2, A3, A4),
|
||||
not attr(Name, node(_, A1), A2, A3, A4).
|
||||
|
||||
% error message with causes for conflicts
|
||||
error(0, Msg, startcauses, TriggerID, ID1, ConstraintID, ID2)
|
||||
:- attr("node", node(ID, Package)),
|
||||
pkg_fact(Package, conflict(TriggerID, ConstraintID, Msg)),
|
||||
% node(ID1, TriggerPackage) is node(ID2, Package) in most, but not all, cases
|
||||
condition_holds(TriggerID, node(ID1, TriggerPackage)),
|
||||
condition_holds(ConstraintID, node(ID2, Package)),
|
||||
unification_set(X, node(ID2, Package)),
|
||||
unification_set(X, node(ID1, TriggerPackage)),
|
||||
not external(node(ID, Package)), % ignore conflicts for externals
|
||||
not attr("hash", node(ID, Package), _). % ignore conflicts for installed packages
|
||||
|
||||
% variables to show
|
||||
#show error/2.
|
||||
#show error/3.
|
||||
#show error/4.
|
||||
#show error/5.
|
||||
#show error/6.
|
||||
#show error/7.
|
||||
#show error/8.
|
||||
#show error/9.
|
||||
#show error/10.
|
||||
#show error/11.
|
||||
|
||||
#show condition_cause/4.
|
||||
#show condition_reason/2.
|
||||
|
||||
% Define all variables used to avoid warnings at runtime when the model doesn't happen to have one
|
||||
#defined error/2.
|
||||
#defined error/3.
|
||||
#defined error/4.
|
||||
#defined error/5.
|
||||
#defined error/6.
|
||||
#defined attr/2.
|
||||
#defined attr/3.
|
||||
#defined attr/4.
|
||||
#defined attr/5.
|
||||
#defined pkg_fact/2.
|
||||
#defined imposed_constraint/3.
|
||||
#defined imposed_constraint/4.
|
||||
#defined imposed_constraint/5.
|
||||
#defined imposed_constraint/6.
|
||||
#defined condition_requirement/3.
|
||||
#defined condition_requirement/4.
|
||||
#defined condition_requirement/5.
|
||||
#defined condition_requirement/6.
|
||||
#defined condition_holds/2.
|
||||
#defined unification_set/2.
|
||||
#defined external/1.
|
||||
#defined trigger_and_effect/3.
|
||||
#defined build/1.
|
||||
#defined node_has_variant/2.
|
||||
#defined provider/2.
|
||||
#defined external_version/3.
|
||||
@@ -11,19 +11,14 @@
|
||||
%-----------------
|
||||
% Domain heuristic
|
||||
%-----------------
|
||||
#heuristic attr("hash", node(0, Package), Hash) : literal(_, "root", Package). [45, init]
|
||||
#heuristic attr("root", node(0, Package)) : literal(_, "root", Package). [45, true]
|
||||
#heuristic attr("node", node(0, Package)) : literal(_, "root", Package). [45, true]
|
||||
#heuristic attr("node", node(0, Package)) : literal(_, "node", Package). [45, true]
|
||||
|
||||
% Root node
|
||||
#heuristic attr("version", node(0, Package), Version) : pkg_fact(Package, version_declared(Version, 0)), attr("root", node(0, Package)). [35, true]
|
||||
#heuristic version_weight(node(0, Package), 0) : pkg_fact(Package, version_declared(Version, 0)), attr("root", node(0, Package)). [35, true]
|
||||
#heuristic attr("variant_value", node(0, Package), Variant, Value) : variant_default_value(Package, Variant, Value), attr("root", node(0, Package)). [35, true]
|
||||
#heuristic attr("node_target", node(0, Package), Target) : pkg_fact(Package, target_weight(Target, 0)), attr("root", node(0, Package)). [35, true]
|
||||
#heuristic attr("node_target", node(0, Package), Target) : target_weight(Target, 0), attr("root", node(0, Package)). [35, true]
|
||||
#heuristic node_target_weight(node(0, Package), 0) : attr("root", node(0, Package)). [35, true]
|
||||
#heuristic node_compiler(node(0, Package), CompilerID) : default_compiler_preference(ID, 0), compiler_id(ID), attr("root", node(0, Package)). [35, true]
|
||||
#heuristic node_compiler(node(0, Package), CompilerID) : compiler_weight(ID, 0), compiler_id(ID), attr("root", node(0, Package)). [35, true]
|
||||
|
||||
% Providers
|
||||
#heuristic attr("node", node(0, Package)) : default_provider_preference(Virtual, Package, 0), possible_in_link_run(Package). [30, true]
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#heuristic attr("variant_value", node(ID, Package), Variant, Value) : variant_default_value(Package, Variant, Value), attr("node", node(ID, Package)), ID > 0. [25-5*ID, true]
|
||||
#heuristic attr("node_target", node(ID, Package), Target) : pkg_fact(Package, target_weight(Target, 0)), attr("node", node(ID, Package)), ID > 0. [25-5*ID, true]
|
||||
#heuristic node_target_weight(node(ID, Package), 0) : attr("node", node(ID, Package)), ID > 0. [25-5*ID, true]
|
||||
#heuristic node_compiler(node(ID, Package), CompilerID) : default_compiler_preference(CompilerID, 0), compiler_id(CompilerID), attr("node", node(ID, Package)), ID > 0. [25-5*ID, true]
|
||||
#heuristic node_compiler(node(ID, Package), CompilerID) : compiler_weight(CompilerID, 0), compiler_id(CompilerID), attr("node", node(ID, Package)), ID > 0. [25-5*ID, true]
|
||||
|
||||
% node(ID, _), split build dependencies
|
||||
#heuristic attr("version", node(ID, Package), Version) : pkg_fact(Package, version_declared(Version, 0)), attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
||||
@@ -21,4 +21,4 @@
|
||||
#heuristic attr("variant_value", node(ID, Package), Variant, Value) : variant_default_value(Package, Variant, Value), attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
||||
#heuristic attr("node_target", node(ID, Package), Target) : pkg_fact(Package, target_weight(Target, 0)), attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
||||
#heuristic node_target_weight(node(ID, Package), 0) : attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
||||
#heuristic node_compiler(node(ID, Package), CompilerID) : default_compiler_preference(CompilerID, 0), compiler_id(CompilerID), attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
||||
#heuristic node_compiler(node(ID, Package), CompilerID) : compiler_weight(CompilerID, 0), compiler_id(CompilerID), attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
||||
|
||||
@@ -93,8 +93,8 @@ def remove(self, spec):
|
||||
if (isinstance(s, str) and not s.startswith("$")) and Spec(s) == Spec(spec)
|
||||
]
|
||||
if not remove:
|
||||
msg = "Cannot remove %s from SpecList %s\n" % (spec, self.name)
|
||||
msg += "Either %s is not in %s or %s is " % (spec, self.name, spec)
|
||||
msg = f"Cannot remove {spec} from SpecList {self.name}.\n"
|
||||
msg += f"Either {spec} is not in {self.name} or {spec} is "
|
||||
msg += "expanded from a matrix and cannot be removed directly."
|
||||
raise SpecListError(msg)
|
||||
|
||||
@@ -133,9 +133,8 @@ def _parse_reference(self, name):
|
||||
|
||||
# Make sure the reference is valid
|
||||
if name not in self._reference:
|
||||
msg = "SpecList %s refers to " % self.name
|
||||
msg += "named list %s " % name
|
||||
msg += "which does not appear in its reference dict"
|
||||
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)
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import filecmp
|
||||
import glob
|
||||
import gzip
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
@@ -1112,3 +1114,77 @@ def test_tarfile_of_spec_prefix(tmpdir):
|
||||
assert tar.getmember(f"{expected_prefix}/b_directory/file").isreg()
|
||||
assert tar.getmember(f"{expected_prefix}/c_directory").isdir()
|
||||
assert tar.getmember(f"{expected_prefix}/c_directory/file").isreg()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("layout,expect_success", [(None, True), (1, True), (2, False)])
|
||||
def test_get_valid_spec_file(tmp_path, layout, expect_success):
|
||||
# Test reading a spec.json file that does not specify a layout version.
|
||||
spec_dict = Spec("example").to_dict()
|
||||
path = tmp_path / "spec.json"
|
||||
effective_layout = layout or 0 # If not specified it should be 0
|
||||
|
||||
# Add a layout version
|
||||
if layout is not None:
|
||||
spec_dict["buildcache_layout_version"] = layout
|
||||
|
||||
# Save to file
|
||||
with open(path, "w") as f:
|
||||
json.dump(spec_dict, f)
|
||||
|
||||
try:
|
||||
spec_dict_disk, layout_disk = bindist._get_valid_spec_file(
|
||||
str(path), max_supported_layout=1
|
||||
)
|
||||
assert expect_success
|
||||
assert spec_dict_disk == spec_dict
|
||||
assert layout_disk == effective_layout
|
||||
except bindist.InvalidMetadataFile:
|
||||
assert not expect_success
|
||||
|
||||
|
||||
def test_get_valid_spec_file_doesnt_exist(tmp_path):
|
||||
with pytest.raises(bindist.InvalidMetadataFile, match="No such file"):
|
||||
bindist._get_valid_spec_file(str(tmp_path / "no-such-file"), max_supported_layout=1)
|
||||
|
||||
|
||||
def test_get_valid_spec_file_gzipped(tmp_path):
|
||||
# Create a gzipped file, contents don't matter
|
||||
path = tmp_path / "spec.json.gz"
|
||||
with gzip.open(path, "wb") as f:
|
||||
f.write(b"hello")
|
||||
with pytest.raises(
|
||||
bindist.InvalidMetadataFile, match="Compressed spec files are not supported"
|
||||
):
|
||||
bindist._get_valid_spec_file(str(path), max_supported_layout=1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename", ["spec.json", "spec.json.sig"])
|
||||
def test_get_valid_spec_file_no_json(tmp_path, filename):
|
||||
tmp_path.joinpath(filename).write_text("not json")
|
||||
with pytest.raises(bindist.InvalidMetadataFile):
|
||||
bindist._get_valid_spec_file(str(tmp_path / filename), max_supported_layout=1)
|
||||
|
||||
|
||||
def test_download_tarball_with_unsupported_layout_fails(tmp_path, mutable_config, capsys):
|
||||
layout_version = bindist.CURRENT_BUILD_CACHE_LAYOUT_VERSION + 1
|
||||
spec = Spec("gmake@4.4.1%gcc@13.1.0 arch=linux-ubuntu23.04-zen2")
|
||||
spec._mark_concrete()
|
||||
spec_dict = spec.to_dict()
|
||||
spec_dict["buildcache_layout_version"] = layout_version
|
||||
|
||||
# Setup a basic local build cache structure
|
||||
path = (
|
||||
tmp_path / bindist.build_cache_relative_path() / bindist.tarball_name(spec, ".spec.json")
|
||||
)
|
||||
path.parent.mkdir(parents=True)
|
||||
with open(path, "w") as f:
|
||||
json.dump(spec_dict, f)
|
||||
|
||||
# Configure as a mirror.
|
||||
mirror_cmd("add", "test-mirror", str(tmp_path))
|
||||
|
||||
# Shouldn't be able "download" this.
|
||||
assert bindist.download_tarball(spec, unsigned=True) is None
|
||||
|
||||
# And there should be a warning about an unsupported layout version.
|
||||
assert f"Layout version {layout_version} is too new" in capsys.readouterr().err
|
||||
|
||||
@@ -652,3 +652,18 @@ def test_monkey_patching_works_across_virtual(default_mock_concretization):
|
||||
s["mpich"].foo = "foo"
|
||||
assert s["mpich"].foo == "foo"
|
||||
assert s["mpi"].foo == "foo"
|
||||
|
||||
|
||||
def test_clear_compiler_related_runtime_variables_of_build_deps(default_mock_concretization):
|
||||
"""Verify that Spack drops CC, CXX, FC and F77 from the dependencies related build environment
|
||||
variable changes if they are set in setup_run_environment. Spack manages those variables
|
||||
elsewhere."""
|
||||
s = default_mock_concretization("build-env-compiler-var-a")
|
||||
ctx = spack.build_environment.SetupContext(s, context=Context.BUILD)
|
||||
result = {}
|
||||
ctx.get_env_modifications().apply_modifications(result)
|
||||
assert "CC" not in result
|
||||
assert "CXX" not in result
|
||||
assert "FC" not in result
|
||||
assert "F77" not in result
|
||||
assert result["ANOTHER_VAR"] == "this-should-be-present"
|
||||
|
||||
@@ -58,6 +58,24 @@ def test_subcommands():
|
||||
assert "spack compiler add" in out2
|
||||
|
||||
|
||||
@pytest.mark.not_on_windows("subprocess not supported on Windows")
|
||||
def test_override_alias():
|
||||
"""Test that spack commands cannot be overriden by aliases."""
|
||||
|
||||
install = spack.main.SpackCommand("install", subprocess=True)
|
||||
instal = spack.main.SpackCommand("instal", subprocess=True)
|
||||
|
||||
out = install(fail_on_error=False, global_args=["-c", "config:aliases:install:find"])
|
||||
assert "install requires a package argument or active environment" in out
|
||||
assert "Alias 'install' (mapping to 'find') attempts to override built-in command" in out
|
||||
|
||||
out = install(fail_on_error=False, global_args=["-c", "config:aliases:foo bar:find"])
|
||||
assert "Alias 'foo bar' (mapping to 'find') contains a space, which is not supported" in out
|
||||
|
||||
out = instal(fail_on_error=False, global_args=["-c", "config:aliases:instal:find"])
|
||||
assert "install requires a package argument or active environment" not in out
|
||||
|
||||
|
||||
def test_rst():
|
||||
"""Do some simple sanity checks of the rst writer."""
|
||||
out1 = commands("--format=rst")
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
import spack.cmd.compiler
|
||||
import spack.compilers
|
||||
import spack.main
|
||||
import spack.spec
|
||||
import spack.util.pattern
|
||||
import spack.version
|
||||
|
||||
compiler = spack.main.SpackCommand("compiler")
|
||||
@@ -146,7 +148,7 @@ def test_compiler_add(mutable_config, mock_packages, mock_executable):
|
||||
|
||||
compilers_before_find = set(spack.compilers.all_compiler_specs())
|
||||
args = spack.util.pattern.Bunch(
|
||||
all=None, compiler_spec=None, add_paths=[str(root_dir)], scope=None
|
||||
all=None, compiler_spec=None, add_paths=[str(root_dir)], scope=None, mixed_toolchain=False
|
||||
)
|
||||
spack.cmd.compiler.compiler_find(args)
|
||||
compilers_after_find = set(spack.compilers.all_compiler_specs())
|
||||
@@ -159,10 +161,15 @@ def test_compiler_add(mutable_config, mock_packages, mock_executable):
|
||||
|
||||
@pytest.mark.not_on_windows("Cannot execute bash script on Windows")
|
||||
@pytest.mark.regression("17590")
|
||||
def test_compiler_find_mixed_suffixes(no_compilers_yaml, working_env, compilers_dir):
|
||||
@pytest.mark.parametrize("mixed_toolchain", [True, False])
|
||||
def test_compiler_find_mixed_suffixes(
|
||||
mixed_toolchain, no_compilers_yaml, working_env, compilers_dir
|
||||
):
|
||||
"""Ensure that we'll mix compilers with different suffixes when necessary."""
|
||||
os.environ["PATH"] = str(compilers_dir)
|
||||
output = compiler("find", "--scope=site")
|
||||
output = compiler(
|
||||
"find", "--scope=site", "--mixed-toolchain" if mixed_toolchain else "--no-mixed-toolchain"
|
||||
)
|
||||
|
||||
assert "clang@11.0.0" in output
|
||||
assert "gcc@8.4.0" in output
|
||||
@@ -176,9 +183,8 @@ def test_compiler_find_mixed_suffixes(no_compilers_yaml, working_env, compilers_
|
||||
assert clang["paths"] == {
|
||||
"cc": str(compilers_dir / "clang"),
|
||||
"cxx": str(compilers_dir / "clang++"),
|
||||
# we only auto-detect mixed clang on macos
|
||||
"f77": gfortran_path if sys.platform == "darwin" else None,
|
||||
"fc": gfortran_path if sys.platform == "darwin" else None,
|
||||
"f77": gfortran_path if mixed_toolchain else None,
|
||||
"fc": gfortran_path if mixed_toolchain else None,
|
||||
}
|
||||
|
||||
assert gcc["paths"] == {
|
||||
|
||||
@@ -215,10 +215,10 @@ def test_config_add_override_leaf(mutable_empty_config):
|
||||
|
||||
|
||||
def test_config_add_update_dict(mutable_empty_config):
|
||||
config("add", "packages:all:version:[1.0.0]")
|
||||
config("add", "packages:hdf5:version:[1.0.0]")
|
||||
output = config("get", "packages")
|
||||
|
||||
expected = "packages:\n all:\n version: [1.0.0]\n"
|
||||
expected = "packages:\n hdf5:\n version: [1.0.0]\n"
|
||||
assert output == expected
|
||||
|
||||
|
||||
@@ -352,8 +352,7 @@ def test_config_add_update_dict_from_file(mutable_empty_config, tmpdir):
|
||||
contents = """spack:
|
||||
packages:
|
||||
all:
|
||||
version:
|
||||
- 1.0.0
|
||||
target: [x86_64]
|
||||
"""
|
||||
|
||||
# create temp file and add it to config
|
||||
@@ -368,8 +367,7 @@ def test_config_add_update_dict_from_file(mutable_empty_config, tmpdir):
|
||||
# added config comes before prior config
|
||||
expected = """packages:
|
||||
all:
|
||||
version:
|
||||
- 1.0.0
|
||||
target: [x86_64]
|
||||
compiler: [gcc]
|
||||
"""
|
||||
|
||||
@@ -381,7 +379,7 @@ def test_config_add_invalid_file_fails(tmpdir):
|
||||
# invalid because version requires a list
|
||||
contents = """spack:
|
||||
packages:
|
||||
all:
|
||||
hdf5:
|
||||
version: 1.0.0
|
||||
"""
|
||||
|
||||
@@ -631,14 +629,11 @@ def test_config_prefer_upstream(
|
||||
packages = syaml.load(open(cfg_file))["packages"]
|
||||
|
||||
# Make sure only the non-default variants are set.
|
||||
assert packages["boost"] == {
|
||||
"compiler": ["gcc@=10.2.1"],
|
||||
"variants": "+debug +graph",
|
||||
"version": ["1.63.0"],
|
||||
}
|
||||
assert packages["dependency-install"] == {"compiler": ["gcc@=10.2.1"], "version": ["2.0"]}
|
||||
assert packages["all"] == {"compiler": ["gcc@=10.2.1"]}
|
||||
assert packages["boost"] == {"variants": "+debug +graph", "version": ["1.63.0"]}
|
||||
assert packages["dependency-install"] == {"version": ["2.0"]}
|
||||
# Ensure that neither variant gets listed for hdf5, since they conflict
|
||||
assert packages["hdf5"] == {"compiler": ["gcc@=10.2.1"], "version": ["2.3"]}
|
||||
assert packages["hdf5"] == {"version": ["2.3"]}
|
||||
|
||||
# Make sure a message about the conflicting hdf5's was given.
|
||||
assert "- hdf5" in output
|
||||
|
||||
78
lib/spack/spack/test/cmd/deconcretize.py
Normal file
78
lib/spack/spack/test/cmd/deconcretize.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import pytest
|
||||
|
||||
import spack.environment as ev
|
||||
from spack.main import SpackCommand, SpackCommandError
|
||||
|
||||
deconcretize = SpackCommand("deconcretize")
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_env(mutable_mock_env_path, config, mock_packages):
|
||||
ev.create("test")
|
||||
with ev.read("test") as e:
|
||||
e.add("a@2.0 foobar=bar ^b@1.0")
|
||||
e.add("a@1.0 foobar=bar ^b@0.9")
|
||||
e.concretize()
|
||||
e.write()
|
||||
|
||||
|
||||
def test_deconcretize_dep(test_env):
|
||||
with ev.read("test") as e:
|
||||
deconcretize("-y", "b@1.0")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 1
|
||||
assert specs[0].satisfies("a@1.0")
|
||||
|
||||
|
||||
def test_deconcretize_all_dep(test_env):
|
||||
with ev.read("test") as e:
|
||||
with pytest.raises(SpackCommandError):
|
||||
deconcretize("-y", "b")
|
||||
deconcretize("-y", "--all", "b")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 0
|
||||
|
||||
|
||||
def test_deconcretize_root(test_env):
|
||||
with ev.read("test") as e:
|
||||
output = deconcretize("-y", "--root", "b@1.0")
|
||||
assert "No matching specs to deconcretize" in output
|
||||
assert len(e.concretized_order) == 2
|
||||
|
||||
deconcretize("-y", "--root", "a@2.0")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 1
|
||||
assert specs[0].satisfies("a@1.0")
|
||||
|
||||
|
||||
def test_deconcretize_all_root(test_env):
|
||||
with ev.read("test") as e:
|
||||
with pytest.raises(SpackCommandError):
|
||||
deconcretize("-y", "--root", "a")
|
||||
|
||||
output = deconcretize("-y", "--root", "--all", "b")
|
||||
assert "No matching specs to deconcretize" in output
|
||||
assert len(e.concretized_order) == 2
|
||||
|
||||
deconcretize("-y", "--root", "--all", "a")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 0
|
||||
|
||||
|
||||
def test_deconcretize_all(test_env):
|
||||
with ev.read("test") as e:
|
||||
with pytest.raises(SpackCommandError):
|
||||
deconcretize()
|
||||
deconcretize("-y", "--all")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 0
|
||||
@@ -163,8 +163,15 @@ def test_dev_build_fails_multiple_specs(mock_packages):
|
||||
|
||||
|
||||
def test_dev_build_fails_nonexistent_package_name(mock_packages):
|
||||
output = dev_build("no_such_package", fail_on_error=False)
|
||||
assert "No package for 'no_such_package' was found" in output
|
||||
output = ""
|
||||
|
||||
try:
|
||||
dev_build("no_such_package")
|
||||
assert False, "no exception was raised!"
|
||||
except spack.repo.UnknownPackageError as e:
|
||||
output = e.message
|
||||
|
||||
assert "Package 'no_such_package' not found" in output
|
||||
|
||||
|
||||
def test_dev_build_fails_no_version(mock_packages):
|
||||
|
||||
@@ -284,7 +284,7 @@ def setup_error(pkg, env):
|
||||
|
||||
_, err = capfd.readouterr()
|
||||
assert "cmake-client had issues!" in err
|
||||
assert "Warning: couldn't load runtime environment" in err
|
||||
assert "Warning: could not load runtime environment" in err
|
||||
|
||||
|
||||
def test_activate_adds_transitive_run_deps_to_path(install_mockery, mock_fetch, monkeypatch):
|
||||
@@ -502,12 +502,12 @@ def test_env_activate_broken_view(
|
||||
# test that Spack detects the missing package and fails gracefully
|
||||
with spack.repo.use_repositories(mock_custom_repository):
|
||||
wrong_repo = env("activate", "--sh", "test")
|
||||
assert "Warning: couldn't load runtime environment" in wrong_repo
|
||||
assert "Warning: could not load runtime environment" in wrong_repo
|
||||
assert "Unknown namespace: builtin.mock" in wrong_repo
|
||||
|
||||
# test replacing repo fixes it
|
||||
normal_repo = env("activate", "--sh", "test")
|
||||
assert "Warning: couldn't load runtime environment" not in normal_repo
|
||||
assert "Warning: could not load runtime environment" not in normal_repo
|
||||
assert "Unknown namespace: builtin.mock" not in normal_repo
|
||||
|
||||
|
||||
@@ -632,7 +632,7 @@ def test_env_view_external_prefix(tmp_path, mutable_database, mock_packages):
|
||||
manifest_dir.mkdir(parents=True, exist_ok=False)
|
||||
manifest_file = manifest_dir / ev.manifest_name
|
||||
manifest_file.write_text(
|
||||
"""
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- a
|
||||
@@ -720,38 +720,25 @@ def test_env_with_config(environment_from_manifest):
|
||||
|
||||
def test_with_config_bad_include(environment_from_manifest):
|
||||
"""Confirm missing include paths raise expected exception and error."""
|
||||
e = environment_from_manifest(
|
||||
"""
|
||||
with pytest.raises(spack.config.ConfigFileError, match="2 missing include path"):
|
||||
e = environment_from_manifest(
|
||||
"""
|
||||
spack:
|
||||
include:
|
||||
- /no/such/directory
|
||||
- no/such/file.yaml
|
||||
"""
|
||||
)
|
||||
with pytest.raises(spack.config.ConfigFileError, match="2 missing include path"):
|
||||
)
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
assert ev.active_environment() is None
|
||||
|
||||
|
||||
def test_env_with_include_config_files_same_basename(environment_from_manifest):
|
||||
e = environment_from_manifest(
|
||||
"""
|
||||
spack:
|
||||
include:
|
||||
- ./path/to/included-config.yaml
|
||||
- ./second/path/to/include-config.yaml
|
||||
specs:
|
||||
- libelf
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
|
||||
e = ev.read("test")
|
||||
|
||||
fs.mkdirp(os.path.join(e.path, "path", "to"))
|
||||
with open(os.path.join(e.path, "./path/to/included-config.yaml"), "w") as f:
|
||||
def test_env_with_include_config_files_same_basename(tmp_path, environment_from_manifest):
|
||||
file1 = fs.join_path(tmp_path, "path", "to", "included-config.yaml")
|
||||
fs.mkdirp(os.path.dirname(file1))
|
||||
with open(file1, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
packages:
|
||||
@@ -760,8 +747,9 @@ def test_env_with_include_config_files_same_basename(environment_from_manifest):
|
||||
"""
|
||||
)
|
||||
|
||||
fs.mkdirp(os.path.join(e.path, "second", "path", "to"))
|
||||
with open(os.path.join(e.path, "./second/path/to/include-config.yaml"), "w") as f:
|
||||
file2 = fs.join_path(tmp_path, "second", "path", "included-config.yaml")
|
||||
fs.mkdirp(os.path.dirname(file2))
|
||||
with open(file2, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
packages:
|
||||
@@ -770,6 +758,18 @@ def test_env_with_include_config_files_same_basename(environment_from_manifest):
|
||||
"""
|
||||
)
|
||||
|
||||
e = environment_from_manifest(
|
||||
f"""
|
||||
spack:
|
||||
include:
|
||||
- {file1}
|
||||
- {file2}
|
||||
specs:
|
||||
- libelf
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
@@ -806,12 +806,18 @@ def mpileaks_env_config(include_path):
|
||||
)
|
||||
|
||||
|
||||
def test_env_with_included_config_file(environment_from_manifest, packages_file):
|
||||
def test_env_with_included_config_file(mutable_mock_env_path, packages_file):
|
||||
"""Test inclusion of a relative packages configuration file added to an
|
||||
existing environment.
|
||||
"""
|
||||
env_root = mutable_mock_env_path
|
||||
fs.mkdirp(env_root)
|
||||
include_filename = "included-config.yaml"
|
||||
e = environment_from_manifest(
|
||||
included_path = env_root / include_filename
|
||||
shutil.move(packages_file.strpath, included_path)
|
||||
|
||||
spack_yaml = env_root / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
f"""\
|
||||
spack:
|
||||
include:
|
||||
@@ -821,9 +827,7 @@ def test_env_with_included_config_file(environment_from_manifest, packages_file)
|
||||
"""
|
||||
)
|
||||
|
||||
included_path = os.path.join(e.path, include_filename)
|
||||
shutil.move(packages_file.strpath, included_path)
|
||||
|
||||
e = ev.Environment(env_root)
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
@@ -856,68 +860,67 @@ def test_env_with_included_config_missing_file(tmpdir, mutable_empty_config):
|
||||
with spack_yaml.open("w") as f:
|
||||
f.write("spack:\n include:\n - {0}\n".format(missing_file.strpath))
|
||||
|
||||
env = ev.Environment(tmpdir.strpath)
|
||||
with pytest.raises(spack.config.ConfigError, match="missing include path"):
|
||||
ev.activate(env)
|
||||
ev.Environment(tmpdir.strpath)
|
||||
|
||||
|
||||
def test_env_with_included_config_scope(environment_from_manifest, packages_file):
|
||||
def test_env_with_included_config_scope(mutable_mock_env_path, packages_file):
|
||||
"""Test inclusion of a package file from the environment's configuration
|
||||
stage directory. This test is intended to represent a case where a remote
|
||||
file has already been staged."""
|
||||
config_scope_path = os.path.join(ev.root("test"), "config")
|
||||
|
||||
# Configure the environment to include file(s) from the environment's
|
||||
# remote configuration stage directory.
|
||||
e = environment_from_manifest(mpileaks_env_config(config_scope_path))
|
||||
env_root = mutable_mock_env_path
|
||||
config_scope_path = env_root / "config"
|
||||
|
||||
# Copy the packages.yaml file to the environment configuration
|
||||
# directory, so it is picked up during concretization. (Using
|
||||
# copy instead of rename in case the fixture scope changes.)
|
||||
fs.mkdirp(config_scope_path)
|
||||
include_filename = os.path.basename(packages_file.strpath)
|
||||
included_path = os.path.join(config_scope_path, include_filename)
|
||||
included_path = config_scope_path / include_filename
|
||||
fs.copy(packages_file.strpath, included_path)
|
||||
|
||||
# Configure the environment to include file(s) from the environment's
|
||||
# remote configuration stage directory.
|
||||
spack_yaml = env_root / ev.manifest_name
|
||||
spack_yaml.write_text(mpileaks_env_config(config_scope_path))
|
||||
|
||||
# Ensure the concretized environment reflects contents of the
|
||||
# packages.yaml file.
|
||||
e = ev.Environment(env_root)
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
|
||||
|
||||
|
||||
def test_env_with_included_config_var_path(environment_from_manifest, packages_file):
|
||||
def test_env_with_included_config_var_path(tmpdir, packages_file):
|
||||
"""Test inclusion of a package configuration file with path variables
|
||||
"staged" in the environment's configuration stage directory."""
|
||||
config_var_path = os.path.join("$tempdir", "included-config.yaml")
|
||||
e = environment_from_manifest(mpileaks_env_config(config_var_path))
|
||||
included_file = packages_file.strpath
|
||||
env_path = pathlib.PosixPath(tmpdir)
|
||||
config_var_path = os.path.join("$tempdir", "included-packages.yaml")
|
||||
|
||||
spack_yaml = env_path / ev.manifest_name
|
||||
spack_yaml.write_text(mpileaks_env_config(config_var_path))
|
||||
|
||||
config_real_path = substitute_path_variables(config_var_path)
|
||||
fs.mkdirp(os.path.dirname(config_real_path))
|
||||
shutil.move(packages_file.strpath, config_real_path)
|
||||
shutil.move(included_file, config_real_path)
|
||||
assert os.path.exists(config_real_path)
|
||||
|
||||
e = ev.Environment(env_path)
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
|
||||
|
||||
|
||||
def test_env_config_precedence(environment_from_manifest):
|
||||
e = environment_from_manifest(
|
||||
"""
|
||||
spack:
|
||||
packages:
|
||||
libelf:
|
||||
version: ["0.8.12"]
|
||||
include:
|
||||
- ./included-config.yaml
|
||||
specs:
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
with open(os.path.join(e.path, "included-config.yaml"), "w") as f:
|
||||
def test_env_with_included_config_precedence(tmp_path):
|
||||
"""Test included scope and manifest precedence when including a package
|
||||
configuration file."""
|
||||
|
||||
included_file = "included-packages.yaml"
|
||||
included_path = tmp_path / included_file
|
||||
with open(included_path, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
packages:
|
||||
@@ -928,29 +931,50 @@ def test_env_config_precedence(environment_from_manifest):
|
||||
"""
|
||||
)
|
||||
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
# ensure included scope took effect
|
||||
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
|
||||
|
||||
# ensure env file takes precedence
|
||||
assert any(x.satisfies("libelf@0.8.12") for x in e._get_environment_specs())
|
||||
|
||||
|
||||
def test_included_config_precedence(environment_from_manifest):
|
||||
e = environment_from_manifest(
|
||||
"""
|
||||
spack_yaml = tmp_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
f"""\
|
||||
spack:
|
||||
packages:
|
||||
libelf:
|
||||
version: ["0.8.12"]
|
||||
include:
|
||||
- ./high-config.yaml # this one should take precedence
|
||||
- ./low-config.yaml
|
||||
- {os.path.join(".", included_file)}
|
||||
specs:
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
|
||||
with open(os.path.join(e.path, "high-config.yaml"), "w") as f:
|
||||
e = ev.Environment(tmp_path)
|
||||
with e:
|
||||
e.concretize()
|
||||
specs = e._get_environment_specs()
|
||||
|
||||
# ensure included scope took effect
|
||||
assert any(x.satisfies("mpileaks@2.2") for x in specs)
|
||||
|
||||
# ensure env file takes precedence
|
||||
assert any(x.satisfies("libelf@0.8.12") for x in specs)
|
||||
|
||||
|
||||
def test_env_with_included_configs_precedence(tmp_path):
|
||||
"""Test precendence of multiple included configuration files."""
|
||||
file1 = "high-config.yaml"
|
||||
file2 = "low-config.yaml"
|
||||
|
||||
spack_yaml = tmp_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
f"""\
|
||||
spack:
|
||||
include:
|
||||
- {os.path.join(".", file1)} # this one should take precedence
|
||||
- {os.path.join(".", file2)}
|
||||
specs:
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
|
||||
with open(tmp_path / file1, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
packages:
|
||||
@@ -959,7 +983,7 @@ def test_included_config_precedence(environment_from_manifest):
|
||||
"""
|
||||
)
|
||||
|
||||
with open(os.path.join(e.path, "low-config.yaml"), "w") as f:
|
||||
with open(tmp_path / file2, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
packages:
|
||||
@@ -970,29 +994,19 @@ def test_included_config_precedence(environment_from_manifest):
|
||||
"""
|
||||
)
|
||||
|
||||
e = ev.Environment(tmp_path)
|
||||
with e:
|
||||
e.concretize()
|
||||
specs = e._get_environment_specs()
|
||||
|
||||
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
|
||||
# ensure included package spec took precedence over manifest spec
|
||||
assert any(x.satisfies("mpileaks@2.2") for x in specs)
|
||||
|
||||
assert any([x.satisfies("libelf@0.8.10") for x in e._get_environment_specs()])
|
||||
|
||||
|
||||
def test_bad_env_yaml_format(environment_from_manifest):
|
||||
with pytest.raises(spack.config.ConfigFormatError) as e:
|
||||
environment_from_manifest(
|
||||
"""\
|
||||
spack:
|
||||
spacks:
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
|
||||
assert "'spacks' was unexpected" in str(e)
|
||||
|
||||
assert "test" not in env("list")
|
||||
# ensure first included package spec took precedence over one from second
|
||||
assert any(x.satisfies("libelf@0.8.10") for x in specs)
|
||||
|
||||
|
||||
@pytest.mark.regression("39248")
|
||||
def test_bad_env_yaml_format_remove(mutable_mock_env_path):
|
||||
badenv = "badenv"
|
||||
env("create", badenv)
|
||||
@@ -1009,6 +1023,55 @@ def test_bad_env_yaml_format_remove(mutable_mock_env_path):
|
||||
assert badenv not in env("list")
|
||||
|
||||
|
||||
@pytest.mark.regression("39248")
|
||||
@pytest.mark.parametrize(
|
||||
"error,message,contents",
|
||||
[
|
||||
(
|
||||
spack.config.ConfigFormatError,
|
||||
"not of type",
|
||||
"""\
|
||||
spack:
|
||||
specs: mpi@2.0
|
||||
""",
|
||||
),
|
||||
(
|
||||
ev.SpackEnvironmentConfigError,
|
||||
"duplicate key",
|
||||
"""\
|
||||
spack:
|
||||
packages:
|
||||
all:
|
||||
providers:
|
||||
mpi: [mvapich2]
|
||||
mpi: [mpich]
|
||||
""",
|
||||
),
|
||||
(
|
||||
spack.config.ConfigFormatError,
|
||||
"'specks' was unexpected",
|
||||
"""\
|
||||
spack:
|
||||
specks:
|
||||
- libdwarf
|
||||
""",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_bad_env_yaml_create_fails(tmp_path, mutable_mock_env_path, error, message, contents):
|
||||
"""Ensure creation with invalid yaml does NOT create or leave the environment."""
|
||||
filename = tmp_path / ev.manifest_name
|
||||
filename.write_text(contents)
|
||||
env_name = "bad_env"
|
||||
with pytest.raises(error, match=message):
|
||||
env("create", env_name, str(filename))
|
||||
|
||||
assert env_name not in env("list")
|
||||
manifest = mutable_mock_env_path / env_name / ev.manifest_name
|
||||
assert not os.path.exists(str(manifest))
|
||||
|
||||
|
||||
@pytest.mark.regression("39248")
|
||||
@pytest.mark.parametrize("answer", ["-y", ""])
|
||||
def test_multi_env_remove(mutable_mock_env_path, monkeypatch, answer):
|
||||
"""Test removal (or not) of a valid and invalid environment"""
|
||||
@@ -1020,7 +1083,7 @@ def test_multi_env_remove(mutable_mock_env_path, monkeypatch, answer):
|
||||
env("create", e)
|
||||
|
||||
# Ensure the bad environment contains invalid yaml
|
||||
filename = mutable_mock_env_path / environments[1] / "spack.yaml"
|
||||
filename = mutable_mock_env_path / environments[1] / ev.manifest_name
|
||||
filename.write_text(
|
||||
"""\
|
||||
- libdwarf
|
||||
@@ -1036,7 +1099,7 @@ def test_multi_env_remove(mutable_mock_env_path, monkeypatch, answer):
|
||||
if remove_environment is True:
|
||||
# Successfully removed (and reported removal) of *both* environments
|
||||
assert not all(e in env("list") for e in environments)
|
||||
assert output.count("Successfully removed") == 2
|
||||
assert output.count("Successfully removed") == len(environments)
|
||||
else:
|
||||
# Not removing any of the environments
|
||||
assert all(e in env("list") for e in environments)
|
||||
@@ -1578,11 +1641,10 @@ def test_stack_yaml_remove_from_list(tmpdir):
|
||||
assert Spec("callpath") in test.user_specs
|
||||
|
||||
|
||||
def test_stack_yaml_remove_from_list_force(tmpdir):
|
||||
filename = str(tmpdir.join("spack.yaml"))
|
||||
with open(filename, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
def test_stack_yaml_remove_from_list_force(tmp_path):
|
||||
spack_yaml = tmp_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
"""\
|
||||
spack:
|
||||
definitions:
|
||||
- packages: [mpileaks, callpath]
|
||||
@@ -1591,20 +1653,20 @@ def test_stack_yaml_remove_from_list_force(tmpdir):
|
||||
- [$packages]
|
||||
- [^mpich, ^zmpi]
|
||||
"""
|
||||
)
|
||||
with tmpdir.as_cwd():
|
||||
env("create", "test", "./spack.yaml")
|
||||
with ev.read("test"):
|
||||
concretize()
|
||||
remove("-f", "-l", "packages", "mpileaks")
|
||||
find_output = find("-c")
|
||||
)
|
||||
|
||||
assert "mpileaks" not in find_output
|
||||
env("create", "test", str(spack_yaml))
|
||||
with ev.read("test"):
|
||||
concretize()
|
||||
remove("-f", "-l", "packages", "mpileaks")
|
||||
find_output = find("-c")
|
||||
|
||||
test = ev.read("test")
|
||||
assert len(test.user_specs) == 2
|
||||
assert Spec("callpath ^zmpi") in test.user_specs
|
||||
assert Spec("callpath ^mpich") in test.user_specs
|
||||
assert "mpileaks" not in find_output
|
||||
|
||||
test = ev.read("test")
|
||||
assert len(test.user_specs) == 2
|
||||
assert Spec("callpath ^zmpi") in test.user_specs
|
||||
assert Spec("callpath ^mpich") in test.user_specs
|
||||
|
||||
|
||||
def test_stack_yaml_remove_from_matrix_no_effect(tmpdir):
|
||||
@@ -1650,7 +1712,7 @@ def test_stack_yaml_force_remove_from_matrix(tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
env("create", "test", "./spack.yaml")
|
||||
with ev.read("test") as e:
|
||||
concretize()
|
||||
e.concretize()
|
||||
|
||||
before_user = e.user_specs.specs
|
||||
before_conc = e.concretized_user_specs
|
||||
@@ -2559,7 +2621,7 @@ def test_env_write_only_non_default_nested(tmpdir):
|
||||
- matrix:
|
||||
- [mpileaks]
|
||||
packages:
|
||||
mpileaks:
|
||||
all:
|
||||
compiler: [gcc]
|
||||
view: true
|
||||
"""
|
||||
@@ -2897,6 +2959,25 @@ def test_activate_temp(monkeypatch, tmpdir):
|
||||
assert ev.is_env_dir(str(tmpdir))
|
||||
|
||||
|
||||
def test_activate_default(monkeypatch):
|
||||
"""Tests whether `spack env activate` creates / activates the default
|
||||
environment"""
|
||||
assert not ev.exists("default")
|
||||
|
||||
# Activating it the first time should create it
|
||||
env("activate", "--sh")
|
||||
env("deactivate", "--sh")
|
||||
assert ev.exists("default")
|
||||
|
||||
# Activating it while it already exists should work
|
||||
env("activate", "--sh")
|
||||
env("deactivate", "--sh")
|
||||
assert ev.exists("default")
|
||||
|
||||
env("remove", "-y", "default")
|
||||
assert not ev.exists("default")
|
||||
|
||||
|
||||
def test_env_view_fail_if_symlink_points_elsewhere(tmpdir, install_mockery, mock_fetch):
|
||||
view = str(tmpdir.join("view"))
|
||||
# Put a symlink to an actual directory in view
|
||||
|
||||
@@ -25,7 +25,7 @@ def parser():
|
||||
def print_buffer(monkeypatch):
|
||||
buffer = []
|
||||
|
||||
def _print(*args):
|
||||
def _print(*args, **kwargs):
|
||||
buffer.extend(args)
|
||||
|
||||
monkeypatch.setattr(spack.cmd.info.color, "cprint", _print, raising=False)
|
||||
@@ -33,10 +33,11 @@ def _print(*args):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pkg", ["openmpi", "trilinos", "boost", "python", "dealii", "xsdk"] # a BundlePackage
|
||||
"pkg", ["openmpi", "trilinos", "boost", "python", "dealii", "xsdk", "gasnet", "warpx"]
|
||||
)
|
||||
def test_it_just_runs(pkg):
|
||||
info(pkg)
|
||||
@pytest.mark.parametrize("extra_args", [[], ["--variants-by-name"]])
|
||||
def test_it_just_runs(pkg, extra_args):
|
||||
info(pkg, *extra_args)
|
||||
|
||||
|
||||
def test_info_noversion(mock_packages, print_buffer):
|
||||
@@ -78,7 +79,8 @@ def test_is_externally_detectable(pkg_query, expected, parser, print_buffer):
|
||||
"gcc", # This should ensure --test's c_names processing loop covered
|
||||
],
|
||||
)
|
||||
def test_info_fields(pkg_query, parser, print_buffer):
|
||||
@pytest.mark.parametrize("extra_args", [[], ["--variants-by-name"]])
|
||||
def test_info_fields(pkg_query, extra_args, parser, print_buffer):
|
||||
expected_fields = (
|
||||
"Description:",
|
||||
"Homepage:",
|
||||
@@ -91,7 +93,7 @@ def test_info_fields(pkg_query, parser, print_buffer):
|
||||
"Licenses:",
|
||||
)
|
||||
|
||||
args = parser.parse_args(["--all", pkg_query])
|
||||
args = parser.parse_args(["--all", pkg_query] + extra_args)
|
||||
spack.cmd.info.info(parser, args)
|
||||
|
||||
for text in expected_fields:
|
||||
|
||||
@@ -422,7 +422,6 @@ def test_clang_flags():
|
||||
"-gdwarf-5",
|
||||
"-gline-tables-only",
|
||||
"-gmodules",
|
||||
"-gz",
|
||||
"-g",
|
||||
],
|
||||
"clang@3.3",
|
||||
@@ -445,7 +444,6 @@ def test_aocc_flags():
|
||||
"-gdwarf-5",
|
||||
"-gline-tables-only",
|
||||
"-gmodules",
|
||||
"-gz",
|
||||
"-g",
|
||||
],
|
||||
"aocc@2.2.0",
|
||||
|
||||
@@ -349,6 +349,9 @@ def test_compiler_flags_differ_identical_compilers(self):
|
||||
spec.concretize()
|
||||
assert spec.satisfies("cflags=-O2")
|
||||
|
||||
@pytest.mark.only_clingo(
|
||||
"Optional compiler propagation isn't deprecated for original concretizer"
|
||||
)
|
||||
def test_concretize_compiler_flag_propagate(self):
|
||||
spec = Spec("hypre cflags=='-g' ^openblas")
|
||||
spec.concretize()
|
||||
@@ -458,19 +461,54 @@ def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(self):
|
||||
@pytest.mark.only_clingo(
|
||||
"Optional compiler propagation isn't deprecated for original concretizer"
|
||||
)
|
||||
def test_concretize_propagate_disabled_variant(self):
|
||||
"""Test a package variant value was passed from its parent."""
|
||||
spec = Spec("hypre~~shared ^openblas")
|
||||
spec.concretize()
|
||||
|
||||
assert spec.satisfies("^openblas~shared")
|
||||
@pytest.mark.parametrize(
|
||||
"spec_str,expected_propagation",
|
||||
[
|
||||
("hypre~~shared ^openblas+shared", [("hypre", "~shared"), ("openblas", "+shared")]),
|
||||
# Propagates past a node that doesn't have the variant
|
||||
("hypre~~shared ^openblas", [("hypre", "~shared"), ("openblas", "~shared")]),
|
||||
(
|
||||
"ascent~~shared +adios2",
|
||||
[("ascent", "~shared"), ("adios2", "~shared"), ("bzip2", "~shared")],
|
||||
),
|
||||
# Propagates below a node that uses the other value explicitly
|
||||
(
|
||||
"ascent~~shared +adios2 ^adios2+shared",
|
||||
[("ascent", "~shared"), ("adios2", "+shared"), ("bzip2", "~shared")],
|
||||
),
|
||||
(
|
||||
"ascent++shared +adios2 ^adios2~shared",
|
||||
[("ascent", "+shared"), ("adios2", "~shared"), ("bzip2", "+shared")],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_concretize_propagate_disabled_variant(self, spec_str, expected_propagation):
|
||||
"""Tests various patterns of boolean variant propagation"""
|
||||
spec = Spec(spec_str).concretized()
|
||||
for key, expected_satisfies in expected_propagation:
|
||||
spec[key].satisfies(expected_satisfies)
|
||||
|
||||
@pytest.mark.only_clingo(
|
||||
"Optional compiler propagation isn't deprecated for original concretizer"
|
||||
)
|
||||
def test_concretize_propagated_variant_is_not_passed_to_dependent(self):
|
||||
"""Test a package variant value was passed from its parent."""
|
||||
spec = Spec("hypre~~shared ^openblas+shared")
|
||||
spec = Spec("ascent~~shared +adios2 ^adios2+shared")
|
||||
spec.concretize()
|
||||
|
||||
assert spec.satisfies("^openblas+shared")
|
||||
assert spec.satisfies("^adios2+shared")
|
||||
assert spec.satisfies("^bzip2~shared")
|
||||
|
||||
@pytest.mark.only_clingo(
|
||||
"Optional compiler propagation isn't deprecated for original concretizer"
|
||||
)
|
||||
def test_concretize_propagate_specified_variant(self):
|
||||
"""Test that only the specified variant is propagated to the dependencies"""
|
||||
spec = Spec("parent-foo-bar ~~foo")
|
||||
spec.concretize()
|
||||
|
||||
assert spec.satisfies("~foo") and spec.satisfies("^dependency-foo-bar~foo")
|
||||
assert spec.satisfies("+bar") and not spec.satisfies("^dependency-foo-bar+bar")
|
||||
|
||||
@pytest.mark.only_clingo("Original concretizer is allowed to forego variant propagation")
|
||||
def test_concretize_propagate_multivalue_variant(self):
|
||||
|
||||
68
lib/spack/spack/test/concretize_errors.py
Normal file
68
lib/spack/spack/test/concretize_errors.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import pytest
|
||||
|
||||
import spack.solver.asp
|
||||
import spack.spec
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.not_on_windows("Windows uses old concretizer"),
|
||||
pytest.mark.only_clingo("Original concretizer does not support configuration requirements"),
|
||||
]
|
||||
|
||||
version_error_messages = [
|
||||
"Cannot satisfy 'fftw@:1.0' and 'fftw@1.1:",
|
||||
" required because quantum-espresso depends on fftw@:1.0",
|
||||
" required because quantum-espresso ^fftw@1.1: requested from CLI",
|
||||
" required because quantum-espresso ^fftw@1.1: requested from CLI",
|
||||
]
|
||||
|
||||
external_error_messages = [
|
||||
(
|
||||
"Attempted to build package quantum-espresso which is not buildable and does not have"
|
||||
" a satisfying external"
|
||||
),
|
||||
(
|
||||
" 'quantum-espresso~veritas' is an external constraint for quantum-espresso"
|
||||
" which was not satisfied"
|
||||
),
|
||||
" 'quantum-espresso+veritas' required",
|
||||
" required because quantum-espresso+veritas requested from CLI",
|
||||
]
|
||||
|
||||
variant_error_messages = [
|
||||
"'fftw' required multiple values for single-valued variant 'mpi'",
|
||||
" Requested '~mpi' and '+mpi'",
|
||||
" required because quantum-espresso depends on fftw+mpi when +invino",
|
||||
" required because quantum-espresso+invino ^fftw~mpi requested from CLI",
|
||||
" required because quantum-espresso+invino ^fftw~mpi requested from CLI",
|
||||
]
|
||||
|
||||
external_config = {
|
||||
"packages:quantum-espresso": {
|
||||
"buildable": False,
|
||||
"externals": [{"spec": "quantum-espresso@1.0~veritas", "prefix": "/path/to/qe"}],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"error_messages,config_set,spec",
|
||||
[
|
||||
(version_error_messages, {}, "quantum-espresso^fftw@1.1:"),
|
||||
(external_error_messages, external_config, "quantum-espresso+veritas"),
|
||||
(variant_error_messages, {}, "quantum-espresso+invino^fftw~mpi"),
|
||||
],
|
||||
)
|
||||
def test_error_messages(error_messages, config_set, spec, mock_packages, mutable_config):
|
||||
for path, conf in config_set.items():
|
||||
spack.config.set(path, conf)
|
||||
|
||||
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError) as e:
|
||||
_ = spack.spec.Spec(spec).concretized()
|
||||
|
||||
for em in error_messages:
|
||||
assert em in str(e.value)
|
||||
@@ -105,17 +105,13 @@ def test_preferred_variants_from_wildcard(self):
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"compiler_str,spec_str",
|
||||
[("gcc@4.5.0", "mpileaks"), ("clang@12.0.0", "mpileaks"), ("gcc@4.5.0", "openmpi")],
|
||||
[("gcc@=4.5.0", "mpileaks"), ("clang@=12.0.0", "mpileaks"), ("gcc@=4.5.0", "openmpi")],
|
||||
)
|
||||
def test_preferred_compilers(self, compiler_str, spec_str):
|
||||
"""Test preferred compilers are applied correctly"""
|
||||
spec = Spec(spec_str)
|
||||
update_packages(spec.name, "compiler", [compiler_str])
|
||||
spec.concretize()
|
||||
# note: lhs has concrete compiler version, rhs still abstract.
|
||||
# Could be made more strict by checking for equality with `gcc@=4.5.0`
|
||||
# etc.
|
||||
assert spec.compiler.satisfies(CompilerSpec(compiler_str))
|
||||
update_packages("all", "compiler", [compiler_str])
|
||||
spec = spack.spec.Spec(spec_str).concretized()
|
||||
assert spec.compiler == CompilerSpec(compiler_str)
|
||||
|
||||
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
|
||||
def test_preferred_target(self, mutable_mock_repo):
|
||||
@@ -124,7 +120,7 @@ def test_preferred_target(self, mutable_mock_repo):
|
||||
default = str(spec.target)
|
||||
preferred = str(spec.target.family)
|
||||
|
||||
update_packages("mpich", "target", [preferred])
|
||||
update_packages("all", "target", [preferred])
|
||||
spec = concretize("mpich")
|
||||
assert str(spec.target) == preferred
|
||||
|
||||
@@ -132,7 +128,7 @@ def test_preferred_target(self, mutable_mock_repo):
|
||||
assert str(spec["mpileaks"].target) == preferred
|
||||
assert str(spec["mpich"].target) == preferred
|
||||
|
||||
update_packages("mpileaks", "target", [default])
|
||||
update_packages("all", "target", [default])
|
||||
spec = concretize("mpileaks")
|
||||
assert str(spec["mpileaks"].target) == default
|
||||
assert str(spec["mpich"].target) == default
|
||||
|
||||
@@ -78,7 +78,7 @@ def env_yaml(tmpdir):
|
||||
verify_ssl: False
|
||||
dirty: False
|
||||
packages:
|
||||
libelf:
|
||||
all:
|
||||
compiler: [ 'gcc@4.5.3' ]
|
||||
repos:
|
||||
- /x/y/z
|
||||
@@ -942,7 +942,7 @@ def test_single_file_scope(config, env_yaml):
|
||||
# from the single-file config
|
||||
assert spack.config.get("config:verify_ssl") is False
|
||||
assert spack.config.get("config:dirty") is False
|
||||
assert spack.config.get("packages:libelf:compiler") == ["gcc@4.5.3"]
|
||||
assert spack.config.get("packages:all:compiler") == ["gcc@4.5.3"]
|
||||
|
||||
# from the lower config scopes
|
||||
assert spack.config.get("config:checksum") is True
|
||||
@@ -965,7 +965,7 @@ def test_single_file_scope_section_override(tmpdir, config):
|
||||
config:
|
||||
verify_ssl: False
|
||||
packages::
|
||||
libelf:
|
||||
all:
|
||||
compiler: [ 'gcc@4.5.3' ]
|
||||
repos:
|
||||
- /x/y/z
|
||||
@@ -977,7 +977,7 @@ def test_single_file_scope_section_override(tmpdir, config):
|
||||
with spack.config.override(scope):
|
||||
# from the single-file config
|
||||
assert spack.config.get("config:verify_ssl") is False
|
||||
assert spack.config.get("packages:libelf:compiler") == ["gcc@4.5.3"]
|
||||
assert spack.config.get("packages:all:compiler") == ["gcc@4.5.3"]
|
||||
|
||||
# from the lower config scopes
|
||||
assert spack.config.get("config:checksum") is True
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# DEPRECATED: remove this in v0.20
|
||||
# See `exclude.yaml` for the new syntax
|
||||
enable:
|
||||
- lmod
|
||||
lmod:
|
||||
core_compilers:
|
||||
- 'clang@3.3'
|
||||
hierarchy:
|
||||
- mpi
|
||||
blacklist:
|
||||
- callpath
|
||||
|
||||
all:
|
||||
autoload: direct
|
||||
@@ -1,30 +0,0 @@
|
||||
# DEPRECATED: remove this in v0.20
|
||||
# See `alter_environment.yaml` for the new syntax
|
||||
enable:
|
||||
- lmod
|
||||
lmod:
|
||||
core_compilers:
|
||||
- 'clang@3.3'
|
||||
|
||||
hierarchy:
|
||||
- mpi
|
||||
|
||||
all:
|
||||
autoload: none
|
||||
filter:
|
||||
environment_blacklist:
|
||||
- CMAKE_PREFIX_PATH
|
||||
environment:
|
||||
set:
|
||||
'{name}_ROOT': '{prefix}'
|
||||
|
||||
'platform=test target=x86_64':
|
||||
environment:
|
||||
set:
|
||||
FOO: 'foo'
|
||||
unset:
|
||||
- BAR
|
||||
|
||||
'platform=test target=core2':
|
||||
load:
|
||||
- 'foo/bar'
|
||||
@@ -2,6 +2,7 @@ enable:
|
||||
- lmod
|
||||
lmod:
|
||||
hide_implicits: true
|
||||
hash_length: 0
|
||||
core_compilers:
|
||||
- 'clang@3.3'
|
||||
hierarchy:
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# DEPRECATED: remove this in v0.20
|
||||
# See `exclude.yaml` for the new syntax
|
||||
enable:
|
||||
- tcl
|
||||
tcl:
|
||||
whitelist:
|
||||
- zmpi
|
||||
blacklist:
|
||||
- callpath
|
||||
- mpi
|
||||
all:
|
||||
autoload: direct
|
||||
@@ -1,25 +0,0 @@
|
||||
# DEPRECATED: remove this in v0.20
|
||||
# See `alter_environment.yaml` for the new syntax
|
||||
enable:
|
||||
- tcl
|
||||
tcl:
|
||||
all:
|
||||
autoload: none
|
||||
filter:
|
||||
environment_blacklist:
|
||||
- CMAKE_PREFIX_PATH
|
||||
environment:
|
||||
set:
|
||||
'{name}_ROOT': '{prefix}'
|
||||
|
||||
'platform=test target=x86_64':
|
||||
environment:
|
||||
set:
|
||||
FOO: 'foo'
|
||||
OMPI_MCA_mpi_leave_pinned: '1'
|
||||
unset:
|
||||
- BAR
|
||||
|
||||
'platform=test target=core2':
|
||||
load:
|
||||
- 'foo/bar'
|
||||
@@ -1,8 +0,0 @@
|
||||
# DEPRECATED: remove this in v0.20
|
||||
# See `exclude_implicits.yaml` for the new syntax
|
||||
enable:
|
||||
- tcl
|
||||
tcl:
|
||||
blacklist_implicits: true
|
||||
all:
|
||||
autoload: direct
|
||||
@@ -4,5 +4,6 @@ enable:
|
||||
- tcl
|
||||
tcl:
|
||||
exclude_implicits: true
|
||||
hash_length: 0
|
||||
all:
|
||||
autoload: direct
|
||||
|
||||
@@ -2,5 +2,6 @@ enable:
|
||||
- tcl
|
||||
tcl:
|
||||
hide_implicits: true
|
||||
hash_length: 0
|
||||
all:
|
||||
autoload: direct
|
||||
|
||||
@@ -4,7 +4,7 @@ tcl:
|
||||
all:
|
||||
autoload: none
|
||||
filter:
|
||||
environment_blacklist:
|
||||
exclude_env_vars:
|
||||
- CMAKE_PREFIX_PATH
|
||||
environment:
|
||||
set:
|
||||
|
||||
@@ -803,6 +803,14 @@ def test_query_spec_with_non_conditional_virtual_dependency(database):
|
||||
assert len(results) == 1
|
||||
|
||||
|
||||
def test_query_virtual_spec(database):
|
||||
"""Make sure we can query for virtuals in the DB"""
|
||||
results = spack.store.STORE.db.query_local("mpi")
|
||||
assert len(results) == 3
|
||||
names = [s.name for s in results]
|
||||
assert all(name in names for name in ["mpich", "mpich2", "zmpi"])
|
||||
|
||||
|
||||
def test_failed_spec_path_error(database):
|
||||
"""Ensure spec not concrete check is covered."""
|
||||
s = spack.spec.Spec("a")
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
SpackEnvironmentViewError,
|
||||
_error_on_nonempty_view_dir,
|
||||
)
|
||||
from spack.spec_list import UndefinedReferenceError
|
||||
|
||||
pytestmark = pytest.mark.not_on_windows("Envs are not supported on windows")
|
||||
|
||||
@@ -716,3 +717,64 @@ def test_variant_propagation_with_unify_false(tmp_path, mock_packages):
|
||||
root = env.matching_spec("parent-foo")
|
||||
for node in root.traverse():
|
||||
assert node.satisfies("+foo")
|
||||
|
||||
|
||||
def test_env_with_include_defs(mutable_mock_env_path, mock_packages):
|
||||
"""Test environment with included definitions file."""
|
||||
env_path = mutable_mock_env_path
|
||||
env_path.mkdir()
|
||||
defs_file = env_path / "definitions.yaml"
|
||||
defs_file.write_text(
|
||||
"""definitions:
|
||||
- core_specs: [libdwarf, libelf]
|
||||
- compilers: ['%gcc']
|
||||
"""
|
||||
)
|
||||
|
||||
spack_yaml = env_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
f"""spack:
|
||||
include:
|
||||
- file://{defs_file}
|
||||
|
||||
definitions:
|
||||
- my_packages: [zlib]
|
||||
|
||||
specs:
|
||||
- matrix:
|
||||
- [$core_specs]
|
||||
- [$compilers]
|
||||
- $my_packages
|
||||
"""
|
||||
)
|
||||
|
||||
e = ev.Environment(env_path)
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
|
||||
def test_env_with_include_def_missing(mutable_mock_env_path, mock_packages):
|
||||
"""Test environment with included definitions file that is missing a definition."""
|
||||
env_path = mutable_mock_env_path
|
||||
env_path.mkdir()
|
||||
filename = "missing-def.yaml"
|
||||
defs_file = env_path / filename
|
||||
defs_file.write_text("definitions:\n- my_compilers: ['%gcc']\n")
|
||||
|
||||
spack_yaml = env_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
f"""spack:
|
||||
include:
|
||||
- file://{defs_file}
|
||||
|
||||
specs:
|
||||
- matrix:
|
||||
- [$core_specs]
|
||||
- [$my_compilers]
|
||||
"""
|
||||
)
|
||||
|
||||
e = ev.Environment(env_path)
|
||||
with e:
|
||||
with pytest.raises(UndefinedReferenceError, match=r"which does not appear"):
|
||||
e.concretize()
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
import spack.package_base
|
||||
import spack.schema.modules
|
||||
import spack.spec
|
||||
import spack.util.spack_yaml as syaml
|
||||
from spack.modules.common import UpstreamModuleIndex
|
||||
from spack.spec import Spec
|
||||
|
||||
@@ -191,26 +190,6 @@ def find_nothing(*args):
|
||||
spack.package_base.PackageBase.uninstall_by_spec(spec)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"module_type, old_config,new_config",
|
||||
[("tcl", "exclude_implicits.yaml", "hide_implicits.yaml")],
|
||||
)
|
||||
def test_exclude_include_update(module_type, old_config, new_config):
|
||||
module_test_data_root = os.path.join(spack.paths.test_path, "data", "modules", module_type)
|
||||
with open(os.path.join(module_test_data_root, old_config)) as f:
|
||||
old_yaml = syaml.load(f)
|
||||
with open(os.path.join(module_test_data_root, new_config)) as f:
|
||||
new_yaml = syaml.load(f)
|
||||
|
||||
# ensure file that needs updating is translated to the right thing.
|
||||
assert spack.schema.modules.update_keys(old_yaml, spack.schema.modules.old_to_new_key)
|
||||
assert new_yaml == old_yaml
|
||||
# ensure a file that doesn't need updates doesn't get updated
|
||||
original_new_yaml = new_yaml.copy()
|
||||
assert not spack.schema.modules.update_keys(new_yaml, spack.schema.modules.old_to_new_key)
|
||||
assert original_new_yaml == new_yaml
|
||||
|
||||
|
||||
@pytest.mark.regression("37649")
|
||||
def test_check_module_set_name(mutable_config):
|
||||
"""Tests that modules set name are validated correctly and an error is reported if the
|
||||
|
||||
@@ -435,7 +435,7 @@ def test_modules_no_arch(self, factory, module_configuration):
|
||||
|
||||
assert str(spec.os) not in path
|
||||
|
||||
def test_hide_implicits(self, module_configuration):
|
||||
def test_hide_implicits(self, module_configuration, temporary_store):
|
||||
"""Tests the addition and removal of hide command in modulerc."""
|
||||
module_configuration("hide_implicits")
|
||||
|
||||
@@ -446,29 +446,42 @@ def test_hide_implicits(self, module_configuration):
|
||||
writer.write()
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = f.readlines()
|
||||
content = "".join(content).split("\n")
|
||||
hide_cmd = 'hide_version("%s")' % writer.layout.use_name
|
||||
assert len([x for x in content if hide_cmd == x]) == 1
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
hide_implicit_mpileaks = f'hide_version("{writer.layout.use_name}")'
|
||||
assert len([x for x in content if hide_implicit_mpileaks == x]) == 1
|
||||
|
||||
# mpileaks becomes explicit, thus modulerc is removed
|
||||
writer = writer_cls(spec, "default", True)
|
||||
writer.write(overwrite=True)
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
# The direct dependencies are all implicitly installed, and they should all be hidden,
|
||||
# except for mpich, which is provider for mpi, which is in the hierarchy, and therefore
|
||||
# can't be hidden. All other hidden modules should have a 7 character hash (the config
|
||||
# hash_length = 0 only applies to exposed modules).
|
||||
with open(writer.layout.filename) as f:
|
||||
depends_statements = [line.strip() for line in f.readlines() if "depends_on" in line]
|
||||
for dep in spec.dependencies(deptype=("link", "run")):
|
||||
if dep.satisfies("mpi"):
|
||||
assert not any(dep.dag_hash(7) in line for line in depends_statements)
|
||||
else:
|
||||
assert any(dep.dag_hash(7) in line for line in depends_statements)
|
||||
|
||||
# mpileaks is defined as explicit, no modulerc file should exist
|
||||
# when mpileaks becomes explicit, its file name changes (hash_length = 0), meaning an
|
||||
# extra module file is created; the old one still exists and remains hidden.
|
||||
writer = writer_cls(spec, "default", True)
|
||||
writer.write()
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
assert hide_implicit_mpileaks in content # old, implicit mpileaks is still hidden
|
||||
assert f'hide_version("{writer.layout.use_name}")' not in content
|
||||
|
||||
# explicit module is removed
|
||||
writer.remove()
|
||||
# after removing both the implicit and explicit module, the modulerc file would be empty
|
||||
# and should be removed.
|
||||
writer_cls(spec, "default", False).remove()
|
||||
writer_cls(spec, "default", True).remove()
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
assert not os.path.exists(writer.layout.filename)
|
||||
|
||||
# implicit module is removed
|
||||
writer = writer_cls(spec, "default", False)
|
||||
writer.write(overwrite=True)
|
||||
writer.write()
|
||||
assert os.path.exists(writer.layout.filename)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
writer.remove()
|
||||
@@ -486,35 +499,19 @@ def test_hide_implicits(self, module_configuration):
|
||||
writer_alt2.write(overwrite=True)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = f.readlines()
|
||||
content = "".join(content).split("\n")
|
||||
hide_cmd = 'hide_version("%s")' % writer.layout.use_name
|
||||
hide_cmd_alt1 = 'hide_version("%s")' % writer_alt1.layout.use_name
|
||||
hide_cmd_alt2 = 'hide_version("%s")' % writer_alt2.layout.use_name
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
hide_cmd = f'hide_version("{writer.layout.use_name}")'
|
||||
hide_cmd_alt1 = f'hide_version("{writer_alt1.layout.use_name}")'
|
||||
hide_cmd_alt2 = f'hide_version("{writer_alt2.layout.use_name}")'
|
||||
assert len([x for x in content if hide_cmd == x]) == 1
|
||||
assert len([x for x in content if hide_cmd_alt1 == x]) == 1
|
||||
assert len([x for x in content if hide_cmd_alt2 == x]) == 1
|
||||
|
||||
# one version is removed, a second becomes explicit
|
||||
# one version is removed
|
||||
writer_alt1.remove()
|
||||
writer_alt2 = writer_cls(spec_alt2, "default", True)
|
||||
writer_alt2.write(overwrite=True)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = f.readlines()
|
||||
content = "".join(content).split("\n")
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
assert len([x for x in content if hide_cmd == x]) == 1
|
||||
assert len([x for x in content if hide_cmd_alt1 == x]) == 0
|
||||
assert len([x for x in content if hide_cmd_alt2 == x]) == 0
|
||||
|
||||
# disable hide_implicits configuration option
|
||||
module_configuration("autoload_direct")
|
||||
writer = writer_cls(spec, "default")
|
||||
writer.write(overwrite=True)
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
|
||||
# reenable hide_implicits configuration option
|
||||
module_configuration("hide_implicits")
|
||||
writer = writer_cls(spec, "default")
|
||||
writer.write(overwrite=True)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
assert len([x for x in content if hide_cmd_alt2 == x]) == 1
|
||||
|
||||
@@ -425,40 +425,38 @@ def test_extend_context(self, modulefile_content, module_configuration):
|
||||
|
||||
@pytest.mark.regression("4400")
|
||||
@pytest.mark.db
|
||||
@pytest.mark.parametrize("config_name", ["hide_implicits", "exclude_implicits"])
|
||||
def test_hide_implicits_no_arg(self, module_configuration, database, config_name):
|
||||
module_configuration(config_name)
|
||||
def test_hide_implicits_no_arg(self, module_configuration, database):
|
||||
module_configuration("exclude_implicits")
|
||||
|
||||
# mpileaks has been installed explicitly when setting up
|
||||
# the tests database
|
||||
mpileaks_specs = database.query("mpileaks")
|
||||
for item in mpileaks_specs:
|
||||
writer = writer_cls(item, "default")
|
||||
assert not writer.conf.hidden
|
||||
assert not writer.conf.excluded
|
||||
|
||||
# callpath is a dependency of mpileaks, and has been pulled
|
||||
# in implicitly
|
||||
callpath_specs = database.query("callpath")
|
||||
for item in callpath_specs:
|
||||
writer = writer_cls(item, "default")
|
||||
assert writer.conf.hidden
|
||||
assert writer.conf.excluded
|
||||
|
||||
@pytest.mark.regression("12105")
|
||||
@pytest.mark.parametrize("config_name", ["hide_implicits", "exclude_implicits"])
|
||||
def test_hide_implicits_with_arg(self, module_configuration, config_name):
|
||||
module_configuration(config_name)
|
||||
def test_hide_implicits_with_arg(self, module_configuration):
|
||||
module_configuration("exclude_implicits")
|
||||
|
||||
# mpileaks is defined as explicit with explicit argument set on writer
|
||||
mpileaks_spec = spack.spec.Spec("mpileaks")
|
||||
mpileaks_spec.concretize()
|
||||
writer = writer_cls(mpileaks_spec, "default", True)
|
||||
assert not writer.conf.hidden
|
||||
assert not writer.conf.excluded
|
||||
|
||||
# callpath is defined as implicit with explicit argument set on writer
|
||||
callpath_spec = spack.spec.Spec("callpath")
|
||||
callpath_spec.concretize()
|
||||
writer = writer_cls(callpath_spec, "default", False)
|
||||
assert writer.conf.hidden
|
||||
assert writer.conf.excluded
|
||||
|
||||
@pytest.mark.regression("9624")
|
||||
@pytest.mark.db
|
||||
@@ -488,7 +486,7 @@ def test_modules_no_arch(self, factory, module_configuration):
|
||||
|
||||
assert str(spec.os) not in path
|
||||
|
||||
def test_hide_implicits(self, module_configuration):
|
||||
def test_hide_implicits(self, module_configuration, temporary_store):
|
||||
"""Tests the addition and removal of hide command in modulerc."""
|
||||
module_configuration("hide_implicits")
|
||||
|
||||
@@ -499,29 +497,37 @@ def test_hide_implicits(self, module_configuration):
|
||||
writer.write()
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = f.readlines()
|
||||
content = "".join(content).split("\n")
|
||||
hide_cmd = "module-hide --soft --hidden-loaded %s" % writer.layout.use_name
|
||||
assert len([x for x in content if hide_cmd == x]) == 1
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
hide_implicit_mpileaks = f"module-hide --soft --hidden-loaded {writer.layout.use_name}"
|
||||
assert len([x for x in content if hide_implicit_mpileaks == x]) == 1
|
||||
|
||||
# mpileaks becomes explicit, thus modulerc is removed
|
||||
writer = writer_cls(spec, "default", True)
|
||||
writer.write(overwrite=True)
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
# The direct dependencies are all implicit, and they should have depends-on with fixed
|
||||
# 7 character hash, even though the config is set to hash_length = 0.
|
||||
with open(writer.layout.filename) as f:
|
||||
depends_statements = [line.strip() for line in f.readlines() if "depends-on" in line]
|
||||
for dep in spec.dependencies(deptype=("link", "run")):
|
||||
assert any(dep.dag_hash(7) in line for line in depends_statements)
|
||||
|
||||
# mpileaks is defined as explicit, no modulerc file should exist
|
||||
# when mpileaks becomes explicit, its file name changes (hash_length = 0), meaning an
|
||||
# extra module file is created; the old one still exists and remains hidden.
|
||||
writer = writer_cls(spec, "default", True)
|
||||
writer.write()
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
assert hide_implicit_mpileaks in content # old, implicit mpileaks is still hidden
|
||||
assert f"module-hide --soft --hidden-loaded {writer.layout.use_name}" not in content
|
||||
|
||||
# explicit module is removed
|
||||
writer.remove()
|
||||
# after removing both the implicit and explicit module, the modulerc file would be empty
|
||||
# and should be removed.
|
||||
writer_cls(spec, "default", False).remove()
|
||||
writer_cls(spec, "default", True).remove()
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
assert not os.path.exists(writer.layout.filename)
|
||||
|
||||
# implicit module is removed
|
||||
writer = writer_cls(spec, "default", False)
|
||||
writer.write(overwrite=True)
|
||||
writer.write()
|
||||
assert os.path.exists(writer.layout.filename)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
writer.remove()
|
||||
@@ -539,35 +545,19 @@ def test_hide_implicits(self, module_configuration):
|
||||
writer_alt2.write(overwrite=True)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = f.readlines()
|
||||
content = "".join(content).split("\n")
|
||||
hide_cmd = "module-hide --soft --hidden-loaded %s" % writer.layout.use_name
|
||||
hide_cmd_alt1 = "module-hide --soft --hidden-loaded %s" % writer_alt1.layout.use_name
|
||||
hide_cmd_alt2 = "module-hide --soft --hidden-loaded %s" % writer_alt2.layout.use_name
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
hide_cmd = f"module-hide --soft --hidden-loaded {writer.layout.use_name}"
|
||||
hide_cmd_alt1 = f"module-hide --soft --hidden-loaded {writer_alt1.layout.use_name}"
|
||||
hide_cmd_alt2 = f"module-hide --soft --hidden-loaded {writer_alt2.layout.use_name}"
|
||||
assert len([x for x in content if hide_cmd == x]) == 1
|
||||
assert len([x for x in content if hide_cmd_alt1 == x]) == 1
|
||||
assert len([x for x in content if hide_cmd_alt2 == x]) == 1
|
||||
|
||||
# one version is removed, a second becomes explicit
|
||||
# one version is removed
|
||||
writer_alt1.remove()
|
||||
writer_alt2 = writer_cls(spec_alt2, "default", True)
|
||||
writer_alt2.write(overwrite=True)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
with open(writer.layout.modulerc) as f:
|
||||
content = f.readlines()
|
||||
content = "".join(content).split("\n")
|
||||
content = [line.strip() for line in f.readlines()]
|
||||
assert len([x for x in content if hide_cmd == x]) == 1
|
||||
assert len([x for x in content if hide_cmd_alt1 == x]) == 0
|
||||
assert len([x for x in content if hide_cmd_alt2 == x]) == 0
|
||||
|
||||
# disable hide_implicits configuration option
|
||||
module_configuration("autoload_direct")
|
||||
writer = writer_cls(spec, "default")
|
||||
writer.write(overwrite=True)
|
||||
assert not os.path.exists(writer.layout.modulerc)
|
||||
|
||||
# reenable hide_implicits configuration option
|
||||
module_configuration("hide_implicits")
|
||||
writer = writer_cls(spec, "default")
|
||||
writer.write(overwrite=True)
|
||||
assert os.path.exists(writer.layout.modulerc)
|
||||
assert len([x for x in content if hide_cmd_alt2 == x]) == 1
|
||||
|
||||
@@ -80,7 +80,17 @@ def test_module_suffixes(module_suffixes_schema):
|
||||
@pytest.mark.regression("10246")
|
||||
@pytest.mark.parametrize(
|
||||
"config_name",
|
||||
["compilers", "config", "env", "merged", "mirrors", "modules", "packages", "repos"],
|
||||
[
|
||||
"compilers",
|
||||
"config",
|
||||
"definitions",
|
||||
"env",
|
||||
"merged",
|
||||
"mirrors",
|
||||
"modules",
|
||||
"packages",
|
||||
"repos",
|
||||
],
|
||||
)
|
||||
def test_schema_validation(meta_schema, config_name):
|
||||
import importlib
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
import spack.build_environment
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.spec
|
||||
import spack.util.environment as environment
|
||||
import spack.util.prefix as prefix
|
||||
|
||||
@@ -596,6 +596,14 @@ def group_by_name(self) -> Dict[str, ModificationList]:
|
||||
modifications[item.name].append(item)
|
||||
return modifications
|
||||
|
||||
def drop(self, *name) -> bool:
|
||||
"""Drop all modifications to the variable with the given name."""
|
||||
old_mods = self.env_modifications
|
||||
new_mods = [x for x in self.env_modifications if x.name not in name]
|
||||
self.env_modifications = new_mods
|
||||
|
||||
return len(old_mods) != len(new_mods)
|
||||
|
||||
def is_unset(self, variable_name: str) -> bool:
|
||||
"""Returns True if the last modification to a variable is to unset it, False otherwise."""
|
||||
modifications = self.group_by_name()
|
||||
|
||||
@@ -330,8 +330,11 @@ def add_extra_search_paths(paths):
|
||||
for candidate_item in candidate_items:
|
||||
for directory in search_paths:
|
||||
exe = directory / candidate_item
|
||||
if exe.is_file() and os.access(str(exe), os.X_OK):
|
||||
return str(exe)
|
||||
try:
|
||||
if exe.is_file() and os.access(str(exe), os.X_OK):
|
||||
return str(exe)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if required:
|
||||
raise CommandNotFoundError("spack requires '%s'. Make sure it is in your path." % args[0])
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ErrorFromWorker:
|
||||
@@ -53,7 +54,9 @@ def __call__(self, *args, **kwargs):
|
||||
return value
|
||||
|
||||
|
||||
def imap_unordered(f, list_of_args, *, processes: int, debug=False):
|
||||
def imap_unordered(
|
||||
f, list_of_args, *, processes: int, maxtaskperchild: Optional[int] = None, debug=False
|
||||
):
|
||||
"""Wrapper around multiprocessing.Pool.imap_unordered.
|
||||
|
||||
Args:
|
||||
@@ -62,6 +65,8 @@ def imap_unordered(f, list_of_args, *, processes: int, debug=False):
|
||||
processes: maximum number of processes allowed
|
||||
debug: if False, raise an exception containing just the error messages
|
||||
from workers, if True an exception with complete stacktraces
|
||||
maxtaskperchild: number of tasks to be executed by a child before being
|
||||
killed and substituted
|
||||
|
||||
Raises:
|
||||
RuntimeError: if any error occurred in the worker processes
|
||||
@@ -70,7 +75,7 @@ def imap_unordered(f, list_of_args, *, processes: int, debug=False):
|
||||
yield from map(f, list_of_args)
|
||||
return
|
||||
|
||||
with multiprocessing.Pool(processes) as p:
|
||||
with multiprocessing.Pool(processes, maxtasksperchild=maxtaskperchild) as p:
|
||||
for result in p.imap_unordered(Task(f), list_of_args):
|
||||
if isinstance(result, ErrorFromWorker):
|
||||
raise RuntimeError(result.stacktrace if debug else str(result))
|
||||
|
||||
@@ -370,7 +370,7 @@ _spack_compress_aliases() {
|
||||
# If there are zero or one completions, don't do anything
|
||||
# If this isn't the first argument, bail because aliases currently only apply
|
||||
# to top-level commands.
|
||||
if [ "${#COMPREPLY[@]}" -le "1" ] || [ "$COMP_CWORD" != "1" ]; then
|
||||
if [ "${#COMPREPLY[@]}" -le "1" ] || [ "$COMP_CWORD_NO_FLAGS" != "1" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
@@ -894,16 +894,16 @@ e4s-cray-rhel-build:
|
||||
variables:
|
||||
SPACK_CI_STACK_NAME: e4s-cray-sles
|
||||
|
||||
# e4s-cray-sles-generate:
|
||||
# extends: [ ".generate-cray-sles", ".e4s-cray-sles" ]
|
||||
e4s-cray-sles-generate:
|
||||
extends: [ ".generate-cray-sles", ".e4s-cray-sles" ]
|
||||
|
||||
# e4s-cray-sles-build:
|
||||
# extends: [ ".build", ".e4s-cray-sles" ]
|
||||
# trigger:
|
||||
# include:
|
||||
# - artifact: jobs_scratch_dir/cloud-ci-pipeline.yml
|
||||
# job: e4s-cray-sles-generate
|
||||
# strategy: depend
|
||||
# needs:
|
||||
# - artifacts: True
|
||||
# job: e4s-cray-sles-generate
|
||||
e4s-cray-sles-build:
|
||||
extends: [ ".build", ".e4s-cray-sles" ]
|
||||
trigger:
|
||||
include:
|
||||
- artifact: jobs_scratch_dir/cloud-ci-pipeline.yml
|
||||
job: e4s-cray-sles-generate
|
||||
strategy: depend
|
||||
needs:
|
||||
- artifacts: True
|
||||
job: e4s-cray-sles-generate
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user