Compare commits
85 Commits
features/t
...
v0.22.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
594a376c52 | ||
![]() |
1538c48616 | ||
![]() |
683e50b8d9 | ||
![]() |
9b32fb0beb | ||
![]() |
2c6df0d491 | ||
![]() |
ce7218acae | ||
![]() |
246eeb2b69 | ||
![]() |
cc47ee3984 | ||
![]() |
7b644719c1 | ||
![]() |
d8a6aa551e | ||
![]() |
ac7b18483a | ||
![]() |
39f37de4ce | ||
![]() |
703e153404 | ||
![]() |
aa013611bc | ||
![]() |
6a7ccd4e46 | ||
![]() |
1c6c4b4690 | ||
![]() |
68558b3dd0 | ||
![]() |
5440fe09cd | ||
![]() |
03c22f403f | ||
![]() |
f339225d22 | ||
![]() |
22c815f3d4 | ||
![]() |
4354288e44 | ||
![]() |
ea2d43b4a6 | ||
![]() |
85e67d60a0 | ||
![]() |
bf6a9ff5ed | ||
![]() |
1bdc30979d | ||
![]() |
ef1eabe5b3 | ||
![]() |
43d673f915 | ||
![]() |
8a9c501030 | ||
![]() |
9f035ca030 | ||
![]() |
d66dce2d66 | ||
![]() |
ef2aa2f5f5 | ||
![]() |
41f5f6eaab | ||
![]() |
cba347e0b7 | ||
![]() |
a3cef0f02e | ||
![]() |
45fca040c3 | ||
![]() |
eb2b5739b2 | ||
![]() |
d299e17d43 | ||
![]() |
d883883be0 | ||
![]() |
249dcb49e2 | ||
![]() |
8628add66b | ||
![]() |
aeccba8bc0 | ||
![]() |
d94e8ab36f | ||
![]() |
e66c26871f | ||
![]() |
2db4ff7061 | ||
![]() |
c248932a94 | ||
![]() |
f15d302fc7 | ||
![]() |
74ef630241 | ||
![]() |
a70ea11e69 | ||
![]() |
a79b1bd9af | ||
![]() |
ac5d5485b9 | ||
![]() |
04258f9cce | ||
![]() |
1b14170bd1 | ||
![]() |
a3bc9dbfe8 | ||
![]() |
e7c86259bd | ||
![]() |
2605aeb072 | ||
![]() |
94536d2b66 | ||
![]() |
5e580fc82e | ||
![]() |
195bad8675 | ||
![]() |
bd9f3f100a | ||
![]() |
b5962613a0 | ||
![]() |
cbcfc7e10a | ||
![]() |
579fadacd0 | ||
![]() |
b86d08b022 | ||
![]() |
02d62cf40f | ||
![]() |
97369776f0 | ||
![]() |
47af0159dc | ||
![]() |
db6ead6fc1 | ||
![]() |
b4aa2c3cab | ||
![]() |
4108de1ce4 | ||
![]() |
5fe93fee1e | ||
![]() |
8207f11333 | ||
![]() |
5bb5d2696f | ||
![]() |
55f37dffe5 | ||
![]() |
252a5bd71b | ||
![]() |
f55224f161 | ||
![]() |
189ae4b06e | ||
![]() |
5e9c702fa7 | ||
![]() |
965bb4d3c0 | ||
![]() |
354f98c94a | ||
![]() |
5dce480154 | ||
![]() |
f634d48b7c | ||
![]() |
4daee565ae | ||
![]() |
8e4dbdc2d7 | ||
![]() |
4f6adc03cd |
7
.github/workflows/ci.yaml
vendored
7
.github/workflows/ci.yaml
vendored
@@ -77,13 +77,8 @@ jobs:
|
||||
needs: [ prechecks, changes ]
|
||||
uses: ./.github/workflows/unit_tests.yaml
|
||||
secrets: inherit
|
||||
windows:
|
||||
if: ${{ github.repository == 'spack/spack' && needs.changes.outputs.core == 'true' }}
|
||||
needs: [ prechecks ]
|
||||
uses: ./.github/workflows/windows_python.yml
|
||||
secrets: inherit
|
||||
all:
|
||||
needs: [ windows, unit-tests, bootstrap ]
|
||||
needs: [ unit-tests, bootstrap ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Success
|
||||
|
33
.github/workflows/unit_tests.yaml
vendored
33
.github/workflows/unit_tests.yaml
vendored
@@ -195,7 +195,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, macos-14]
|
||||
os: [macos-13, macos-14]
|
||||
python-version: ["3.11"]
|
||||
steps:
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b
|
||||
@@ -228,3 +228,34 @@ jobs:
|
||||
flags: unittests,macos
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
# Run unit tests on Windows
|
||||
windows:
|
||||
defaults:
|
||||
run:
|
||||
shell:
|
||||
powershell Invoke-Expression -Command "./share/spack/qa/windows_test_setup.ps1"; {0}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip pywin32 setuptools pytest-cov clingo
|
||||
- name: Create local develop
|
||||
run: |
|
||||
./.github/workflows/setup_git.ps1
|
||||
- name: Unit Test
|
||||
run: |
|
||||
spack unit-test -x --verbose --cov --cov-config=pyproject.toml
|
||||
./share/spack/qa/validate_last_exit.ps1
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c
|
||||
with:
|
||||
flags: unittests,windows
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
83
.github/workflows/windows_python.yml
vendored
83
.github/workflows/windows_python.yml
vendored
@@ -1,83 +0,0 @@
|
||||
name: windows
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
concurrency:
|
||||
group: windows-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell:
|
||||
powershell Invoke-Expression -Command "./share/spack/qa/windows_test_setup.ps1"; {0}
|
||||
jobs:
|
||||
unit-tests:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip pywin32 setuptools pytest-cov clingo
|
||||
- name: Create local develop
|
||||
run: |
|
||||
./.github/workflows/setup_git.ps1
|
||||
- name: Unit Test
|
||||
run: |
|
||||
spack unit-test -x --verbose --cov --cov-config=pyproject.toml --ignore=lib/spack/spack/test/cmd
|
||||
./share/spack/qa/validate_last_exit.ps1
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be
|
||||
with:
|
||||
flags: unittests,windows
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
unit-tests-cmd:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip pywin32 setuptools coverage pytest-cov clingo
|
||||
- name: Create local develop
|
||||
run: |
|
||||
./.github/workflows/setup_git.ps1
|
||||
- name: Command Unit Test
|
||||
run: |
|
||||
spack unit-test -x --verbose --cov --cov-config=pyproject.toml lib/spack/spack/test/cmd
|
||||
./share/spack/qa/validate_last_exit.ps1
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be
|
||||
with:
|
||||
flags: unittests,windows
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
build-abseil:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip pywin32 setuptools coverage
|
||||
- name: Build Test
|
||||
run: |
|
||||
spack compiler find
|
||||
spack -d external find cmake ninja
|
||||
spack -d install abseil-cpp
|
429
CHANGELOG.md
429
CHANGELOG.md
@@ -1,3 +1,432 @@
|
||||
# v0.22.2 (2024-09-21)
|
||||
|
||||
## Bugfixes
|
||||
- Forward compatibility with Spack 0.23 packages with language dependencies (#45205, #45191)
|
||||
- Forward compatibility with `urllib` from Python 3.12.6+ (#46453, #46483)
|
||||
- Bump vendored `archspec` for better aarch64 support (#45721, #46445)
|
||||
- Support macOS Sequoia (#45018, #45127)
|
||||
- Fix regression in `{variants.X}` and `{variants.X.value}` format strings (#46206)
|
||||
- Ensure shell escaping of environment variable values in load and activate commands (#42780)
|
||||
- Fix an issue where `spec[pkg]` considers specs outside the current DAG (#45090)
|
||||
- Do not halt concretization on unknown variants in externals (#45326)
|
||||
- Improve validation of `develop` config section (#46485)
|
||||
- Explicitly disable `ccache` if turned off in config, to avoid cache pollution (#45275)
|
||||
- Improve backwards compatibility in `include_concrete` (#45766)
|
||||
- Fix issue where package tags were sometimes repeated (#45160)
|
||||
- Make `setup-env.sh` "sourced only" by dropping execution bits (#45641)
|
||||
- Make certain source/binary fetch errors recoverable instead of a hard error (#45683)
|
||||
- Remove debug statements in package hash computation (#45235)
|
||||
- Remove redundant clingo warnings (#45269)
|
||||
- Remove hard-coded layout version (#45645)
|
||||
- Do not initialize previous store state in `use_store` (#45268)
|
||||
- Docs improvements (#46475)
|
||||
|
||||
## Package updates
|
||||
- `chapel` major update (#42197, #44931, #45304)
|
||||
|
||||
# v0.22.1 (2024-07-04)
|
||||
|
||||
## Bugfixes
|
||||
- Fix reuse of externals on Linux (#44316)
|
||||
- Ensure parent gcc-runtime version >= child (#44834, #44870)
|
||||
- Ensure the latest gcc-runtime is rpath'ed when multiple exist among link deps (#44219)
|
||||
- Improve version detection of glibc (#44154)
|
||||
- Improve heuristics for solver (#44893, #44976, #45023)
|
||||
- Make strong preferences override reuse (#44373)
|
||||
- Reduce verbosity when C compiler is missing (#44182)
|
||||
- Make missing ccache executable an error when required (#44740)
|
||||
- Make every environment view containing `python` a `venv` (#44382)
|
||||
- Fix external detection for compilers with os but no target (#44156)
|
||||
- Fix version optimization for roots (#44272)
|
||||
- Handle common implementations of pagination of tags in OCI build caches (#43136)
|
||||
- Apply fetched patches to develop specs (#44950)
|
||||
- Avoid Windows wrappers for filesystem utilities on non-Windows (#44126)
|
||||
- Fix issue with long filenames in build caches on Windows (#43851)
|
||||
- Fix formatting issue in `spack audit` (#45045)
|
||||
- CI fixes (#44582, #43965, #43967, #44279, #44213)
|
||||
|
||||
## Package updates
|
||||
- protobuf: fix 3.4:3.21 patch checksum (#44443)
|
||||
- protobuf: update hash for patch needed when="@3.4:3.21" (#44210)
|
||||
- git: bump v2.39 to 2.45; deprecate unsafe versions (#44248)
|
||||
- gcc: use -rpath {rpath_dir} not -rpath={rpath dir} (#44315)
|
||||
- Remove mesa18 and libosmesa (#44264)
|
||||
- Enforce consistency of `gl` providers (#44307)
|
||||
- Require libiconv for iconv (#44335, #45026).
|
||||
Notice that glibc/musl also provide iconv, but are not guaranteed to be
|
||||
complete. Set `packages:iconv:require:[glibc]` to restore the old behavior.
|
||||
- py-matplotlib: qualify when to do a post install (#44191)
|
||||
- rust: fix v1.78.0 instructions (#44127)
|
||||
- suite-sparse: improve setting of the `libs` property (#44214)
|
||||
- netlib-lapack: provide blas and lapack together (#44981)
|
||||
|
||||
|
||||
# v0.22.0 (2024-05-12)
|
||||
|
||||
`v0.22.0` is a major feature release.
|
||||
|
||||
## Features in this release
|
||||
|
||||
1. **Compiler dependencies**
|
||||
|
||||
We are in the process of making compilers proper dependencies in Spack, and a number
|
||||
of changes in `v0.22` support that effort. You may notice nodes in your dependency
|
||||
graphs for compiler runtime libraries like `gcc-runtime` or `libgfortran`, and you
|
||||
may notice that Spack graphs now include `libc`. We've also begun moving compiler
|
||||
configuration from `compilers.yaml` to `packages.yaml` to make it consistent with
|
||||
other externals. We are trying to do this with the least disruption possible, so
|
||||
your existing `compilers.yaml` files should still work. We expect to be done with
|
||||
this transition by the `v0.23` release in November.
|
||||
|
||||
* #41104: Packages compiled with `%gcc` on Linux, macOS and FreeBSD now depend on a
|
||||
new package `gcc-runtime`, which contains a copy of the shared compiler runtime
|
||||
libraries. This enables gcc runtime libraries to be installed and relocated when
|
||||
using a build cache. When building minimal Spack-generated container images it is
|
||||
no longer necessary to install libgfortran, libgomp etc. using the system package
|
||||
manager.
|
||||
|
||||
* #42062: Packages compiled with `%oneapi` now depend on a new package
|
||||
`intel-oneapi-runtime`. This is similar to `gcc-runtime`, and the runtimes can
|
||||
provide virtuals and compilers can inject dependencies on virtuals into compiled
|
||||
packages. This allows us to model library soname compatibility and allows
|
||||
compilers like `%oneapi` to provide virtuals like `sycl` (which can also be
|
||||
provided by standalone libraries). Note that until we have an agreement in place
|
||||
with intel, Intel packages are marked `redistribute(source=False, binary=False)`
|
||||
and must be downloaded outside of Spack.
|
||||
|
||||
* #43272: changes to the optimization criteria of the solver improve the hit-rate of
|
||||
buildcaches by a fair amount. The solver more relaxed compatibility rules and will
|
||||
not try to strictly match compilers or targets of reused specs. Users can still
|
||||
enforce the previous strict behavior with `require:` sections in `packages.yaml`.
|
||||
Note that to enforce correct linking, Spack will *not* reuse old `%gcc` and
|
||||
`%oneapi` specs that do not have the runtime libraries as a dependency.
|
||||
|
||||
* #43539: Spack will reuse specs built with compilers that are *not* explicitly
|
||||
configured in `compilers.yaml`. Because we can now keep runtime libraries in build
|
||||
cache, we do not require you to also have a local configured compiler to *use* the
|
||||
runtime libraries. This improves reuse in buildcaches and avoids conflicts with OS
|
||||
updates that happen underneath Spack.
|
||||
|
||||
* #43190: binary compatibility on `linux` is now based on the `libc` version,
|
||||
instead of on the `os` tag. Spack builds now detect the host `libc` (`glibc` or
|
||||
`musl`) and add it as an implicit external node in the dependency graph. Binaries
|
||||
with a `libc` with the same name and a version less than or equal to that of the
|
||||
detected `libc` can be reused. This is only on `linux`, not `macos` or `Windows`.
|
||||
|
||||
* #43464: each package that can provide a compiler is now detectable using `spack
|
||||
external find`. External packages defining compiler paths are effectively used as
|
||||
compilers, and `spack external find -t compiler` can be used as a substitute for
|
||||
`spack compiler find`. More details on this transition are in
|
||||
[the docs](https://spack.readthedocs.io/en/latest/getting_started.html#manual-compiler-configuration)
|
||||
|
||||
2. **Improved `spack find` UI for Environments**
|
||||
|
||||
If you're working in an enviroment, you likely care about:
|
||||
|
||||
* What are the roots
|
||||
* Which ones are installed / not installed
|
||||
* What's been added that still needs to be concretized
|
||||
|
||||
We've tweaked `spack find` in environments to show this information much more
|
||||
clearly. Installation status is shown next to each root, so you can see what is
|
||||
installed. Roots are also shown in bold in the list of installed packages. There is
|
||||
also a new option for `spack find -r` / `--only-roots` that will only show env
|
||||
roots, if you don't want to look at all the installed specs.
|
||||
|
||||
More details in #42334.
|
||||
|
||||
3. **Improved command-line string quoting**
|
||||
|
||||
We are making some breaking changes to how Spack parses specs on the CLI in order to
|
||||
respect shell quoting instead of trying to fight it. If you (sadly) had to write
|
||||
something like this on the command line:
|
||||
|
||||
```
|
||||
spack install zlib cflags=\"-O2 -g\"
|
||||
```
|
||||
|
||||
That will now result in an error, but you can now write what you probably expected
|
||||
to work in the first place:
|
||||
|
||||
```
|
||||
spack install zlib cflags="-O2 -g"
|
||||
```
|
||||
|
||||
Quoted can also now include special characters, so you can supply flags like:
|
||||
|
||||
```
|
||||
spack intall zlib ldflags='-Wl,-rpath=$ORIGIN/_libs'
|
||||
```
|
||||
|
||||
To reduce ambiguity in parsing, we now require that you *not* put spaces around `=`
|
||||
and `==` when for flags or variants. This would not have broken before but will now
|
||||
result in an error:
|
||||
|
||||
```
|
||||
spack install zlib cflags = "-O2 -g"
|
||||
```
|
||||
|
||||
More details and discussion in #30634.
|
||||
|
||||
4. **Revert default `spack install` behavior to `--reuse`**
|
||||
|
||||
We changed the default concretizer behavior from `--reuse` to `--reuse-deps` in
|
||||
#30990 (in `v0.20`), which meant that *every* `spack install` invocation would
|
||||
attempt to build a new version of the requested package / any environment roots.
|
||||
While this is a common ask for *upgrading* and for *developer* workflows, we don't
|
||||
think it should be the default for a package manager.
|
||||
|
||||
We are going to try to stick to this policy:
|
||||
1. Prioritize reuse and build as little as possible by default.
|
||||
2. Only upgrade or install duplicates if they are explicitly asked for, or if there
|
||||
is a known security issue that necessitates an upgrade.
|
||||
|
||||
With the install command you now have three options:
|
||||
|
||||
* `--reuse` (default): reuse as many existing installations as possible.
|
||||
* `--reuse-deps` / `--fresh-roots`: upgrade (freshen) roots but reuse dependencies if possible.
|
||||
* `--fresh`: install fresh versions of requested packages (roots) and their dependencies.
|
||||
|
||||
We've also introduced `--fresh-roots` as an alias for `--reuse-deps` to make it more clear
|
||||
that it may give you fresh versions. More details in #41302 and #43988.
|
||||
|
||||
5. **More control over reused specs**
|
||||
|
||||
You can now control which packages to reuse and how. There is a new
|
||||
`concretizer:reuse` config option, which accepts the following properties:
|
||||
|
||||
- `roots`: `true` to reuse roots, `false` to reuse just dependencies
|
||||
- `exclude`: list of constraints used to select which specs *not* to reuse
|
||||
- `include`: list of constraints used to select which specs *to* reuse
|
||||
- `from`: list of sources for reused specs (some combination of `local`,
|
||||
`buildcache`, or `external`)
|
||||
|
||||
For example, to reuse only specs compiled with GCC, you could write:
|
||||
|
||||
```yaml
|
||||
concretizer:
|
||||
reuse:
|
||||
roots: true
|
||||
include:
|
||||
- "%gcc"
|
||||
```
|
||||
|
||||
Or, if `openmpi` must be used from externals, and it must be the only external used:
|
||||
|
||||
```yaml
|
||||
concretizer:
|
||||
reuse:
|
||||
roots: true
|
||||
from:
|
||||
- type: local
|
||||
exclude: ["openmpi"]
|
||||
- type: buildcache
|
||||
exclude: ["openmpi"]
|
||||
- type: external
|
||||
include: ["openmpi"]
|
||||
```
|
||||
|
||||
6. **New `redistribute()` directive**
|
||||
|
||||
Some packages can't be redistributed in source or binary form. We need an explicit
|
||||
way to say that in a package.
|
||||
|
||||
Now there is a `redistribute()` directive so that package authors can write:
|
||||
|
||||
```python
|
||||
class MyPackage(Package):
|
||||
redistribute(source=False, binary=False)
|
||||
```
|
||||
|
||||
Like other directives, this works with `when=`:
|
||||
|
||||
```python
|
||||
class MyPackage(Package):
|
||||
# 12.0 and higher are proprietary
|
||||
redistribute(source=False, binary=False, when="@12.0:")
|
||||
|
||||
# can't redistribute when we depend on some proprietary dependency
|
||||
redistribute(source=False, binary=False, when="^proprietary-dependency")
|
||||
```
|
||||
|
||||
More in #20185.
|
||||
|
||||
7. **New `conflict:` and `prefer:` syntax for package preferences**
|
||||
|
||||
Previously, you could express conflicts and preferences in `packages.yaml` through
|
||||
some contortions with `require:`:
|
||||
|
||||
```yaml
|
||||
packages:
|
||||
zlib-ng:
|
||||
require:
|
||||
- one_of: ["%clang", "@:"] # conflict on %clang
|
||||
- any_of: ["+shared", "@:"] # strong preference for +shared
|
||||
```
|
||||
|
||||
You can now use `require:` and `prefer:` for a much more readable configuration:
|
||||
|
||||
```yaml
|
||||
packages:
|
||||
zlib-ng:
|
||||
conflict:
|
||||
- "%clang"
|
||||
prefer:
|
||||
- "+shared"
|
||||
```
|
||||
|
||||
See [the documentation](https://spack.readthedocs.io/en/latest/packages_yaml.html#conflicts-and-strong-preferences)
|
||||
and #41832 for more details.
|
||||
|
||||
8. **`include_concrete` in environments**
|
||||
|
||||
You may want to build on the *concrete* contents of another environment without
|
||||
changing that environment. You can now include the concrete specs from another
|
||||
environment's `spack.lock` with `include_concrete`:
|
||||
|
||||
```yaml
|
||||
spack:
|
||||
specs: []
|
||||
concretizer:
|
||||
unify: true
|
||||
include_concrete:
|
||||
- /path/to/environment1
|
||||
- /path/to/environment2
|
||||
```
|
||||
|
||||
Now, when *this* environment is concretized, it will bring in the already concrete
|
||||
specs from `environment1` and `environment2`, and build on top of them without
|
||||
changing them. This is useful if you have phased deployments, where old deployments
|
||||
should not be modified but you want to use as many of them as possible. More details
|
||||
in #33768.
|
||||
|
||||
9. **`python-venv` isolation**
|
||||
|
||||
Spack has unique requirements for Python because it:
|
||||
1. installs every package in its own independent directory, and
|
||||
2. allows users to register *external* python installations.
|
||||
|
||||
External installations may contain their own installed packages that can interfere
|
||||
with Spack installations, and some distributions (Debian and Ubuntu) even change the
|
||||
`sysconfig` in ways that alter the installation layout of installed Python packages
|
||||
(e.g., with the addition of a `/local` prefix on Debian or Ubuntu). To isolate Spack
|
||||
from these and other issues, we now insert a small `python-venv` package in between
|
||||
`python` and packages that need to install Python code. This isolates Spack's build
|
||||
environment, isolates Spack from any issues with an external python, and resolves a
|
||||
large number of issues we've had with Python installations.
|
||||
|
||||
See #40773 for further details.
|
||||
|
||||
## New commands, options, and directives
|
||||
|
||||
* Allow packages to be pushed to build cache after install from source (#42423)
|
||||
* `spack develop`: stage build artifacts in same root as non-dev builds #41373
|
||||
* Don't delete `spack develop` build artifacts after install (#43424)
|
||||
* `spack find`: add options for local/upstream only (#42999)
|
||||
* `spack logs`: print log files for packages (either partially built or installed) (#42202)
|
||||
* `patch`: support reversing patches (#43040)
|
||||
* `develop`: Add -b/--build-directory option to set build_directory package attribute (#39606)
|
||||
* `spack list`: add `--namesapce` / `--repo` option (#41948)
|
||||
* directives: add `checked_by` field to `license()`, add some license checks
|
||||
* `spack gc`: add options for environments and build dependencies (#41731)
|
||||
* Add `--create` to `spack env activate` (#40896)
|
||||
|
||||
## Performance improvements
|
||||
|
||||
* environment.py: fix excessive re-reads (#43746)
|
||||
* ruamel yaml: fix quadratic complexity bug (#43745)
|
||||
* Refactor to improve `spec format` speed (#43712)
|
||||
* Do not acquire a write lock on the env post install if no views (#43505)
|
||||
* asp.py: fewer calls to `spec.copy()` (#43715)
|
||||
* spec.py: early return in `__str__`
|
||||
* avoid `jinja2` import at startup unless needed (#43237)
|
||||
|
||||
## Other new features of note
|
||||
|
||||
* `archspec`: update to `v0.2.4`: support for Windows, bugfixes for `neoverse-v1` and
|
||||
`neoverse-v2` detection.
|
||||
* `spack config get`/`blame`: with no args, show entire config
|
||||
* `spack env create <env>`: dir if dir-like (#44024)
|
||||
* ASP-based solver: update os compatibility for macOS (#43862)
|
||||
* Add handling of custom ssl certs in urllib ops (#42953)
|
||||
* Add ability to rename environments (#43296)
|
||||
* Add config option and compiler support to reuse across OS's (#42693)
|
||||
* Support for prereleases (#43140)
|
||||
* Only reuse externals when configured (#41707)
|
||||
* Environments: Add support for including views (#42250)
|
||||
|
||||
## Binary caches
|
||||
* Build cache: make signed/unsigned a mirror property (#41507)
|
||||
* tools stack
|
||||
|
||||
## Removals, deprecations, and syntax changes
|
||||
* remove `dpcpp` compiler and package (#43418)
|
||||
* spack load: remove --only argument (#42120)
|
||||
|
||||
## Notable Bugfixes
|
||||
* repo.py: drop deleted packages from provider cache (#43779)
|
||||
* Allow `+` in module file names (#41999)
|
||||
* `cmd/python`: use runpy to allow multiprocessing in scripts (#41789)
|
||||
* Show extension commands with spack -h (#41726)
|
||||
* Support environment variable expansion inside module projections (#42917)
|
||||
* Alert user to failed concretizations (#42655)
|
||||
* shell: fix zsh color formatting for PS1 in environments (#39497)
|
||||
* spack mirror create --all: include patches (#41579)
|
||||
|
||||
## Spack community stats
|
||||
|
||||
* 7,994 total packages; 525 since `v0.21.0`
|
||||
* 178 new Python packages, 5 new R packages
|
||||
* 358 people contributed to this release
|
||||
* 344 committers to packages
|
||||
* 45 committers to core
|
||||
|
||||
|
||||
# v0.21.2 (2024-03-01)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Containerize: accommodate nested or pre-existing spack-env paths (#41558)
|
||||
- Fix setup-env script, when going back and forth between instances (#40924)
|
||||
- Fix using fully-qualified namespaces from root specs (#41957)
|
||||
- Fix a bug when a required provider is requested for multiple virtuals (#42088)
|
||||
- OCI buildcaches:
|
||||
- only push in parallel when forking (#42143)
|
||||
- use pickleable errors (#42160)
|
||||
- Fix using sticky variants in externals (#42253)
|
||||
- Fix a rare issue with conditional requirements and multi-valued variants (#42566)
|
||||
|
||||
## Package updates
|
||||
- rust: add v1.75, rework a few variants (#41161,#41903)
|
||||
- py-transformers: add v4.35.2 (#41266)
|
||||
- mgard: fix OpenMP on AppleClang (#42933)
|
||||
|
||||
# v0.21.1 (2024-01-11)
|
||||
|
||||
## New features
|
||||
- Add support for reading buildcaches created by Spack v0.22 (#41773)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- spack graph: fix coloring with environments (#41240)
|
||||
- spack info: sort variants in --variants-by-name (#41389)
|
||||
- Spec.format: error on old style format strings (#41934)
|
||||
- ASP-based solver:
|
||||
- fix infinite recursion when computing concretization errors (#41061)
|
||||
- don't error for type mismatch on preferences (#41138)
|
||||
- don't emit spurious debug output (#41218)
|
||||
- Improve the error message for deprecated preferences (#41075)
|
||||
- Fix MSVC preview version breaking clingo build on Windows (#41185)
|
||||
- Fix multi-word aliases (#41126)
|
||||
- Add a warning for unconfigured compiler (#41213)
|
||||
- environment: fix an issue with deconcretization/reconcretization of specs (#41294)
|
||||
- buildcache: don't error if a patch is missing, when installing from binaries (#41986)
|
||||
- Multiple improvements to unit-tests (#41215,#41369,#41495,#41359,#41361,#41345,#41342,#41308,#41226)
|
||||
|
||||
## Package updates
|
||||
- root: add a webgui patch to address security issue (#41404)
|
||||
- BerkeleyGW: update source urls (#38218)
|
||||
|
||||
# v0.21.0 (2023-11-11)
|
||||
|
||||
`v0.21.0` is a major feature release.
|
||||
|
@@ -144,3 +144,5 @@ switch($SpackSubCommand)
|
||||
"unload" {Invoke-SpackLoad}
|
||||
default {python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs}
|
||||
}
|
||||
|
||||
exit $LASTEXITCODE
|
||||
|
@@ -1,19 +0,0 @@
|
||||
# -------------------------------------------------------------------------
|
||||
# This file controls default concretization preferences for Spack.
|
||||
#
|
||||
# Settings here are versioned with Spack and are intended to provide
|
||||
# sensible defaults out of the box. Spack maintainers should edit this
|
||||
# file to keep it current.
|
||||
#
|
||||
# Users can override these settings by editing the following files.
|
||||
#
|
||||
# Per-spack-instance settings (overrides defaults):
|
||||
# $SPACK_ROOT/etc/spack/packages.yaml
|
||||
#
|
||||
# Per-user settings (overrides default and site settings):
|
||||
# ~/.spack/packages.yaml
|
||||
# -------------------------------------------------------------------------
|
||||
packages:
|
||||
all:
|
||||
providers:
|
||||
iconv: [glibc, musl, libiconv]
|
@@ -1,19 +1,3 @@
|
||||
# -------------------------------------------------------------------------
|
||||
# This file controls default concretization preferences for Spack.
|
||||
#
|
||||
# Settings here are versioned with Spack and are intended to provide
|
||||
# sensible defaults out of the box. Spack maintainers should edit this
|
||||
# file to keep it current.
|
||||
#
|
||||
# Users can override these settings by editing the following files.
|
||||
#
|
||||
# Per-spack-instance settings (overrides defaults):
|
||||
# $SPACK_ROOT/etc/spack/packages.yaml
|
||||
#
|
||||
# Per-user settings (overrides default and site settings):
|
||||
# ~/.spack/packages.yaml
|
||||
# -------------------------------------------------------------------------
|
||||
packages:
|
||||
all:
|
||||
providers:
|
||||
iconv: [glibc, musl, libiconv]
|
||||
iconv:
|
||||
require: [libiconv]
|
||||
|
@@ -38,10 +38,9 @@ packages:
|
||||
lapack: [openblas, amdlibflame]
|
||||
libc: [glibc, musl]
|
||||
libgfortran: [ gcc-runtime ]
|
||||
libglx: [mesa+glx, mesa18+glx]
|
||||
libglx: [mesa+glx]
|
||||
libifcore: [ intel-oneapi-runtime ]
|
||||
libllvm: [llvm]
|
||||
libosmesa: [mesa+osmesa, mesa18+osmesa]
|
||||
lua-lang: [lua, lua-luajit-openresty, lua-luajit]
|
||||
luajit: [lua-luajit-openresty, lua-luajit]
|
||||
mariadb-client: [mariadb-c-client, mariadb]
|
||||
|
@@ -5,9 +5,9 @@
|
||||
|
||||
.. chain:
|
||||
|
||||
============================
|
||||
Chaining Spack Installations
|
||||
============================
|
||||
=============================================
|
||||
Chaining Spack Installations (upstreams.yaml)
|
||||
=============================================
|
||||
|
||||
You can point your Spack installation to another installation to use any
|
||||
packages that are installed there. To register the other Spack instance,
|
||||
|
@@ -460,6 +460,125 @@ Sourcing that file in Bash will make the environment available to the
|
||||
user; and can be included in ``.bashrc`` files, etc. The ``loads``
|
||||
file may also be copied out of the environment, renamed, etc.
|
||||
|
||||
|
||||
.. _environment_include_concrete:
|
||||
|
||||
------------------------------
|
||||
Included Concrete Environments
|
||||
------------------------------
|
||||
|
||||
Spack environments can create an environment based off of information in already
|
||||
established environments. You can think of it as a combination of existing
|
||||
environments. It will gather information from the existing environment's
|
||||
``spack.lock`` and use that during the creation of this included concrete
|
||||
environment. When an included concrete environment is created it will generate
|
||||
a ``spack.lock`` file for the newly created environment.
|
||||
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Creating included environments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
To create a combined concrete environment, you must have at least one existing
|
||||
concrete environment. You will use the command ``spack env create`` with the
|
||||
argument ``--include-concrete`` followed by the name or path of the environment
|
||||
you'd like to include. Here is an example of how to create a combined environment
|
||||
from the command line.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack env create myenv
|
||||
$ spack -e myenv add python
|
||||
$ spack -e myenv concretize
|
||||
$ spack env create --include-concrete myenv included_env
|
||||
|
||||
|
||||
You can also include an environment directly in the ``spack.yaml`` file. It
|
||||
involves adding the ``include_concrete`` heading in the yaml followed by the
|
||||
absolute path to the independent environments.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
specs: []
|
||||
concretizer:
|
||||
unify: true
|
||||
include_concrete:
|
||||
- /absolute/path/to/environment1
|
||||
- /absolute/path/to/environment2
|
||||
|
||||
|
||||
Once the ``spack.yaml`` has been updated you must concretize the environment to
|
||||
get the concrete specs from the included environments.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Updating an included environment
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
If changes were made to the base environment and you want that reflected in the
|
||||
included environment you will need to reconcretize both the base environment and the
|
||||
included environment for the change to be implemented. For example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack env create myenv
|
||||
$ spack -e myenv add python
|
||||
$ spack -e myenv concretize
|
||||
$ spack env create --include-concrete myenv included_env
|
||||
|
||||
|
||||
$ spack -e myenv find
|
||||
==> In environment myenv
|
||||
==> Root specs
|
||||
python
|
||||
|
||||
==> 0 installed packages
|
||||
|
||||
|
||||
$ spack -e included_env find
|
||||
==> In environment included_env
|
||||
==> No root specs
|
||||
==> Included specs
|
||||
python
|
||||
|
||||
==> 0 installed packages
|
||||
|
||||
Here we see that ``included_env`` has access to the python package through
|
||||
the ``myenv`` environment. But if we were to add another spec to ``myenv``,
|
||||
``included_env`` will not be able to access the new information.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack -e myenv add perl
|
||||
$ spack -e myenv concretize
|
||||
$ spack -e myenv find
|
||||
==> In environment myenv
|
||||
==> Root specs
|
||||
perl python
|
||||
|
||||
==> 0 installed packages
|
||||
|
||||
|
||||
$ spack -e included_env find
|
||||
==> In environment included_env
|
||||
==> No root specs
|
||||
==> Included specs
|
||||
python
|
||||
|
||||
==> 0 installed packages
|
||||
|
||||
It isn't until you run the ``spack concretize`` command that the combined
|
||||
environment will get the updated information from the reconcretized base environmennt.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack -e included_env concretize
|
||||
$ spack -e included_env find
|
||||
==> In environment included_env
|
||||
==> No root specs
|
||||
==> Included specs
|
||||
perl python
|
||||
|
||||
==> 0 installed packages
|
||||
|
||||
.. _environment-configuration:
|
||||
|
||||
------------------------
|
||||
@@ -811,6 +930,7 @@ For example, the following environment has three root packages:
|
||||
This allows for a much-needed reduction in redundancy between packages
|
||||
and constraints.
|
||||
|
||||
|
||||
----------------
|
||||
Filesystem Views
|
||||
----------------
|
||||
@@ -1044,7 +1164,7 @@ other targets to depend on the environment installation.
|
||||
|
||||
A typical workflow is as follows:
|
||||
|
||||
.. code:: console
|
||||
.. code-block:: console
|
||||
|
||||
spack env create -d .
|
||||
spack -e . add perl
|
||||
@@ -1137,7 +1257,7 @@ its dependencies. This can be useful when certain flags should only apply to
|
||||
dependencies. Below we show a use case where a spec is installed with verbose
|
||||
output (``spack install --verbose``) while its dependencies are installed silently:
|
||||
|
||||
.. code:: console
|
||||
.. code-block:: console
|
||||
|
||||
$ spack env depfile -o Makefile
|
||||
|
||||
@@ -1159,7 +1279,7 @@ This can be accomplished through the generated ``[<prefix>/]SPACK_PACKAGE_IDS``
|
||||
variable. Assuming we have an active and concrete environment, we generate the
|
||||
associated ``Makefile`` with a prefix ``example``:
|
||||
|
||||
.. code:: console
|
||||
.. code-block:: console
|
||||
|
||||
$ spack env depfile -o env.mk --make-prefix example
|
||||
|
||||
|
2
lib/spack/external/__init__.py
vendored
2
lib/spack/external/__init__.py
vendored
@@ -18,7 +18,7 @@
|
||||
|
||||
* Homepage: https://pypi.python.org/pypi/archspec
|
||||
* Usage: Labeling, comparison and detection of microarchitectures
|
||||
* Version: 0.2.4 (commit 48b92512b9ce203ded0ebd1ac41b42593e931f7c)
|
||||
* Version: 0.2.5-dev (commit cbb1fd5eb397a70d466e5160b393b87b0dbcc78f)
|
||||
|
||||
astunparse
|
||||
----------------
|
||||
|
12
lib/spack/external/archspec/cpu/detect.py
vendored
12
lib/spack/external/archspec/cpu/detect.py
vendored
@@ -47,7 +47,11 @@ def decorator(factory):
|
||||
|
||||
|
||||
def partial_uarch(
|
||||
name: str = "", vendor: str = "", features: Optional[Set[str]] = None, generation: int = 0
|
||||
name: str = "",
|
||||
vendor: str = "",
|
||||
features: Optional[Set[str]] = None,
|
||||
generation: int = 0,
|
||||
cpu_part: str = "",
|
||||
) -> Microarchitecture:
|
||||
"""Construct a partial microarchitecture, from information gathered during system scan."""
|
||||
return Microarchitecture(
|
||||
@@ -57,6 +61,7 @@ def partial_uarch(
|
||||
features=features or set(),
|
||||
compilers={},
|
||||
generation=generation,
|
||||
cpu_part=cpu_part,
|
||||
)
|
||||
|
||||
|
||||
@@ -90,6 +95,7 @@ def proc_cpuinfo() -> Microarchitecture:
|
||||
return partial_uarch(
|
||||
vendor=_canonicalize_aarch64_vendor(data),
|
||||
features=_feature_set(data, key="Features"),
|
||||
cpu_part=data.get("CPU part", ""),
|
||||
)
|
||||
|
||||
if architecture in (PPC64LE, PPC64):
|
||||
@@ -345,6 +351,10 @@ def sorting_fn(item):
|
||||
generic_candidates = [c for c in candidates if c.vendor == "generic"]
|
||||
best_generic = max(generic_candidates, key=sorting_fn)
|
||||
|
||||
# Relevant for AArch64. Filter on "cpu_part" if we have any match
|
||||
if info.cpu_part != "" and any(c for c in candidates if info.cpu_part == c.cpu_part):
|
||||
candidates = [c for c in candidates if info.cpu_part == c.cpu_part]
|
||||
|
||||
# Filter the candidates to be descendant of the best generic candidate.
|
||||
# This is to avoid that the lack of a niche feature that can be disabled
|
||||
# from e.g. BIOS prevents detection of a reasonably performant architecture
|
||||
|
@@ -2,9 +2,7 @@
|
||||
# Archspec Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
"""Types and functions to manage information
|
||||
on CPU microarchitectures.
|
||||
"""
|
||||
"""Types and functions to manage information on CPU microarchitectures."""
|
||||
import functools
|
||||
import platform
|
||||
import re
|
||||
@@ -65,21 +63,24 @@ class Microarchitecture:
|
||||
passed in as argument above.
|
||||
* versions: versions that support this micro-architecture.
|
||||
|
||||
generation (int): generation of the micro-architecture, if
|
||||
relevant.
|
||||
generation (int): generation of the micro-architecture, if relevant.
|
||||
cpu_part (str): cpu part of the architecture, if relevant.
|
||||
"""
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-arguments,too-many-instance-attributes
|
||||
#: Aliases for micro-architecture's features
|
||||
feature_aliases = FEATURE_ALIASES
|
||||
|
||||
def __init__(self, name, parents, vendor, features, compilers, generation=0):
|
||||
def __init__(self, name, parents, vendor, features, compilers, generation=0, cpu_part=""):
|
||||
self.name = name
|
||||
self.parents = parents
|
||||
self.vendor = vendor
|
||||
self.features = features
|
||||
self.compilers = compilers
|
||||
# Only relevant for PowerPC
|
||||
self.generation = generation
|
||||
# Only relevant for AArch64
|
||||
self.cpu_part = cpu_part
|
||||
# Cache the ancestor computation
|
||||
self._ancestors = None
|
||||
|
||||
@@ -111,6 +112,7 @@ def __eq__(self, other):
|
||||
and self.parents == other.parents # avoid ancestors here
|
||||
and self.compilers == other.compilers
|
||||
and self.generation == other.generation
|
||||
and self.cpu_part == other.cpu_part
|
||||
)
|
||||
|
||||
@coerce_target_names
|
||||
@@ -143,7 +145,8 @@ def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
fmt = (
|
||||
cls_name + "({0.name!r}, {0.parents!r}, {0.vendor!r}, "
|
||||
"{0.features!r}, {0.compilers!r}, {0.generation!r})"
|
||||
"{0.features!r}, {0.compilers!r}, generation={0.generation!r}, "
|
||||
"cpu_part={0.cpu_part!r})"
|
||||
)
|
||||
return fmt.format(self)
|
||||
|
||||
@@ -190,6 +193,7 @@ def to_dict(self):
|
||||
"generation": self.generation,
|
||||
"parents": [str(x) for x in self.parents],
|
||||
"compilers": self.compilers,
|
||||
"cpupart": self.cpu_part,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -202,6 +206,7 @@ def from_dict(data) -> "Microarchitecture":
|
||||
features=set(data["features"]),
|
||||
compilers=data.get("compilers", {}),
|
||||
generation=data.get("generation", 0),
|
||||
cpu_part=data.get("cpupart", ""),
|
||||
)
|
||||
|
||||
def optimization_flags(self, compiler, version):
|
||||
@@ -360,8 +365,11 @@ def fill_target_from_dict(name, data, targets):
|
||||
features = set(values["features"])
|
||||
compilers = values.get("compilers", {})
|
||||
generation = values.get("generation", 0)
|
||||
cpu_part = values.get("cpupart", "")
|
||||
|
||||
targets[name] = Microarchitecture(name, parents, vendor, features, compilers, generation)
|
||||
targets[name] = Microarchitecture(
|
||||
name, parents, vendor, features, compilers, generation=generation, cpu_part=cpu_part
|
||||
)
|
||||
|
||||
known_targets = {}
|
||||
data = archspec.cpu.schema.TARGETS_JSON["microarchitectures"]
|
||||
|
@@ -2225,10 +2225,14 @@
|
||||
],
|
||||
"nvhpc": [
|
||||
{
|
||||
"versions": "21.11:",
|
||||
"versions": "21.11:23.8",
|
||||
"name": "zen3",
|
||||
"flags": "-tp {name}",
|
||||
"warnings": "zen4 is not fully supported by nvhpc yet, falling back to zen3"
|
||||
"warnings": "zen4 is not fully supported by nvhpc versions < 23.9, falling back to zen3"
|
||||
},
|
||||
{
|
||||
"versions": "23.9:",
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2711,7 +2715,8 @@
|
||||
"flags": "-mcpu=thunderx2t99"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x0af"
|
||||
},
|
||||
"a64fx": {
|
||||
"from": ["armv8.2a"],
|
||||
@@ -2779,7 +2784,8 @@
|
||||
"flags": "-march=armv8.2-a+crc+crypto+fp16+sve"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x001"
|
||||
},
|
||||
"cortex_a72": {
|
||||
"from": ["aarch64"],
|
||||
@@ -2816,7 +2822,8 @@
|
||||
"flags" : "-mcpu=cortex-a72"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd08"
|
||||
},
|
||||
"neoverse_n1": {
|
||||
"from": ["cortex_a72", "armv8.2a"],
|
||||
@@ -2837,8 +2844,7 @@
|
||||
"asimdrdm",
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"asimddp",
|
||||
"ssbs"
|
||||
"asimddp"
|
||||
],
|
||||
"compilers" : {
|
||||
"gcc": [
|
||||
@@ -2902,7 +2908,8 @@
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd0c"
|
||||
},
|
||||
"neoverse_v1": {
|
||||
"from": ["neoverse_n1", "armv8.4a"],
|
||||
@@ -2926,8 +2933,6 @@
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"sm3",
|
||||
"sm4",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"sve",
|
||||
@@ -2936,7 +2941,6 @@
|
||||
"uscat",
|
||||
"ilrcpc",
|
||||
"flagm",
|
||||
"ssbs",
|
||||
"dcpodp",
|
||||
"svei8mm",
|
||||
"svebf16",
|
||||
@@ -3004,7 +3008,7 @@
|
||||
},
|
||||
{
|
||||
"versions": "11:",
|
||||
"flags" : "-march=armv8.4-a+sve+ssbs+fp16+bf16+crypto+i8mm+rng"
|
||||
"flags" : "-march=armv8.4-a+sve+fp16+bf16+crypto+i8mm+rng"
|
||||
},
|
||||
{
|
||||
"versions": "12:",
|
||||
@@ -3028,7 +3032,8 @@
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd40"
|
||||
},
|
||||
"neoverse_v2": {
|
||||
"from": ["neoverse_n1", "armv9.0a"],
|
||||
@@ -3052,32 +3057,22 @@
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"sm3",
|
||||
"sm4",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"sve",
|
||||
"asimdfhm",
|
||||
"dit",
|
||||
"uscat",
|
||||
"ilrcpc",
|
||||
"flagm",
|
||||
"ssbs",
|
||||
"sb",
|
||||
"dcpodp",
|
||||
"sve2",
|
||||
"sveaes",
|
||||
"svepmull",
|
||||
"svebitperm",
|
||||
"svesha3",
|
||||
"svesm4",
|
||||
"flagm2",
|
||||
"frint",
|
||||
"svei8mm",
|
||||
"svebf16",
|
||||
"i8mm",
|
||||
"bf16",
|
||||
"dgh"
|
||||
"bf16"
|
||||
],
|
||||
"compilers" : {
|
||||
"gcc": [
|
||||
@@ -3102,15 +3097,19 @@
|
||||
"flags" : "-march=armv8.5-a+sve -mtune=cortex-a76"
|
||||
},
|
||||
{
|
||||
"versions": "10.0:11.99",
|
||||
"versions": "10.0:11.3.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16 -mtune=cortex-a77"
|
||||
},
|
||||
{
|
||||
"versions": "11.4:11.99",
|
||||
"flags" : "-mcpu=neoverse-v2"
|
||||
},
|
||||
{
|
||||
"versions": "12.0:12.99",
|
||||
"versions": "12.0:12.2.99",
|
||||
"flags" : "-march=armv9-a+i8mm+bf16 -mtune=cortex-a710"
|
||||
},
|
||||
{
|
||||
"versions": "13.0:",
|
||||
"versions": "12.3:",
|
||||
"flags" : "-mcpu=neoverse-v2"
|
||||
}
|
||||
],
|
||||
@@ -3145,7 +3144,112 @@
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0xd4f"
|
||||
},
|
||||
"neoverse_n2": {
|
||||
"from": ["neoverse_n1", "armv9.0a"],
|
||||
"vendor": "ARM",
|
||||
"features": [
|
||||
"fp",
|
||||
"asimd",
|
||||
"evtstrm",
|
||||
"aes",
|
||||
"pmull",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"crc32",
|
||||
"atomics",
|
||||
"fphp",
|
||||
"asimdhp",
|
||||
"cpuid",
|
||||
"asimdrdm",
|
||||
"jscvt",
|
||||
"fcma",
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"sve",
|
||||
"asimdfhm",
|
||||
"uscat",
|
||||
"ilrcpc",
|
||||
"flagm",
|
||||
"sb",
|
||||
"dcpodp",
|
||||
"sve2",
|
||||
"flagm2",
|
||||
"frint",
|
||||
"svei8mm",
|
||||
"svebf16",
|
||||
"i8mm",
|
||||
"bf16"
|
||||
],
|
||||
"compilers" : {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "4.8:5.99",
|
||||
"flags": "-march=armv8-a"
|
||||
},
|
||||
{
|
||||
"versions": "6:6.99",
|
||||
"flags" : "-march=armv8.1-a"
|
||||
},
|
||||
{
|
||||
"versions": "7.0:7.99",
|
||||
"flags" : "-march=armv8.2-a -mtune=cortex-a72"
|
||||
},
|
||||
{
|
||||
"versions": "8.0:8.99",
|
||||
"flags" : "-march=armv8.4-a+sve -mtune=cortex-a72"
|
||||
},
|
||||
{
|
||||
"versions": "9.0:9.99",
|
||||
"flags" : "-march=armv8.5-a+sve -mtune=cortex-a76"
|
||||
},
|
||||
{
|
||||
"versions": "10.0:10.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16 -mtune=cortex-a77"
|
||||
},
|
||||
{
|
||||
"versions": "11.0:",
|
||||
"flags" : "-mcpu=neoverse-n2"
|
||||
}
|
||||
],
|
||||
"clang" : [
|
||||
{
|
||||
"versions": "9.0:10.99",
|
||||
"flags" : "-march=armv8.5-a+sve"
|
||||
},
|
||||
{
|
||||
"versions": "11.0:13.99",
|
||||
"flags" : "-march=armv8.5-a+sve+sve2+i8mm+bf16"
|
||||
},
|
||||
{
|
||||
"versions": "14.0:15.99",
|
||||
"flags" : "-march=armv9-a+i8mm+bf16"
|
||||
},
|
||||
{
|
||||
"versions": "16.0:",
|
||||
"flags" : "-mcpu=neoverse-n2"
|
||||
}
|
||||
],
|
||||
"arm" : [
|
||||
{
|
||||
"versions": "23.04.0:",
|
||||
"flags" : "-mcpu=neoverse-n2"
|
||||
}
|
||||
],
|
||||
"nvhpc" : [
|
||||
{
|
||||
"versions": "23.3:",
|
||||
"name": "neoverse-n1",
|
||||
"flags": "-tp {name}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cpupart": "0xd49"
|
||||
},
|
||||
"m1": {
|
||||
"from": ["armv8.4a"],
|
||||
@@ -3211,7 +3315,8 @@
|
||||
"flags" : "-mcpu=apple-m1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x022"
|
||||
},
|
||||
"m2": {
|
||||
"from": ["m1", "armv8.5a"],
|
||||
@@ -3289,7 +3394,8 @@
|
||||
"flags" : "-mcpu=apple-m2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cpupart": "0x032"
|
||||
},
|
||||
"arm": {
|
||||
"from": [],
|
||||
|
@@ -52,6 +52,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cpupart": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -107,4 +110,4 @@
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -98,3 +98,10 @@ def path_filter_caller(*args, **kwargs):
|
||||
if _func:
|
||||
return holder_func(_func)
|
||||
return holder_func
|
||||
|
||||
|
||||
def sanitize_win_longpath(path: str) -> str:
|
||||
"""Strip Windows extended path prefix from strings
|
||||
Returns sanitized string.
|
||||
no-op if extended path prefix is not present"""
|
||||
return path.lstrip("\\\\?\\")
|
||||
|
@@ -187,12 +187,18 @@ def polite_filename(filename: str) -> str:
|
||||
return _polite_antipattern().sub("_", filename)
|
||||
|
||||
|
||||
def getuid():
|
||||
def getuid() -> Union[str, int]:
|
||||
"""Returns os getuid on non Windows
|
||||
On Windows returns 0 for admin users, login string otherwise
|
||||
This is in line with behavior from get_owner_uid which
|
||||
always returns the login string on Windows
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
import ctypes
|
||||
|
||||
# If not admin, use the string name of the login as a unique ID
|
||||
if ctypes.windll.shell32.IsUserAnAdmin() == 0:
|
||||
return 1
|
||||
return os.getlogin()
|
||||
return 0
|
||||
else:
|
||||
return os.getuid()
|
||||
@@ -213,6 +219,15 @@ def _win_rename(src, dst):
|
||||
os.replace(src, dst)
|
||||
|
||||
|
||||
@system_path_filter
|
||||
def msdos_escape_parens(path):
|
||||
"""MS-DOS interprets parens as grouping parameters even in a quoted string"""
|
||||
if sys.platform == "win32":
|
||||
return path.replace("(", "^(").replace(")", "^)")
|
||||
else:
|
||||
return path
|
||||
|
||||
|
||||
@system_path_filter
|
||||
def rename(src, dst):
|
||||
# On Windows, os.rename will fail if the destination file already exists
|
||||
@@ -553,7 +568,13 @@ def exploding_archive_handler(tarball_container, stage):
|
||||
|
||||
|
||||
@system_path_filter(arg_slice=slice(1))
|
||||
def get_owner_uid(path, err_msg=None):
|
||||
def get_owner_uid(path, err_msg=None) -> Union[str, int]:
|
||||
"""Returns owner UID of path destination
|
||||
On non Windows this is the value of st_uid
|
||||
On Windows this is the login string associated with the
|
||||
owning user.
|
||||
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
mkdirp(path, mode=stat.S_IRWXU)
|
||||
|
||||
@@ -822,7 +843,7 @@ def copy_tree(
|
||||
if islink(s):
|
||||
link_target = resolve_link_target_relative_to_the_link(s)
|
||||
if symlinks:
|
||||
target = os.readlink(s)
|
||||
target = readlink(s)
|
||||
if os.path.isabs(target):
|
||||
|
||||
def escaped_path(path):
|
||||
@@ -2429,9 +2450,10 @@ def add_library_dependent(self, *dest):
|
||||
"""
|
||||
for pth in dest:
|
||||
if os.path.isfile(pth):
|
||||
self._additional_library_dependents.add(pathlib.Path(pth).parent)
|
||||
new_pth = pathlib.Path(pth).parent
|
||||
else:
|
||||
self._additional_library_dependents.add(pathlib.Path(pth))
|
||||
new_pth = pathlib.Path(pth)
|
||||
self._additional_library_dependents.add(new_pth)
|
||||
|
||||
@property
|
||||
def rpaths(self):
|
||||
@@ -2509,8 +2531,14 @@ def establish_link(self):
|
||||
|
||||
# for each binary install dir in self.pkg (i.e. pkg.prefix.bin, pkg.prefix.lib)
|
||||
# install a symlink to each dependent library
|
||||
for library, lib_dir in itertools.product(self.rpaths, self.library_dependents):
|
||||
self._link(library, lib_dir)
|
||||
|
||||
# do not rpath for system libraries included in the dag
|
||||
# we should not be modifying libraries managed by the Windows system
|
||||
# as this will negatively impact linker behavior and can result in permission
|
||||
# errors if those system libs are not modifiable by Spack
|
||||
if "windows-system" not in getattr(self.pkg, "tags", []):
|
||||
for library, lib_dir in itertools.product(self.rpaths, self.library_dependents):
|
||||
self._link(library, lib_dir)
|
||||
|
||||
|
||||
@system_path_filter
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
from llnl.util import lang, tty
|
||||
|
||||
from ..path import system_path_filter
|
||||
from ..path import sanitize_win_longpath, system_path_filter
|
||||
|
||||
if sys.platform == "win32":
|
||||
from win32file import CreateHardLink
|
||||
@@ -247,9 +247,9 @@ def _windows_create_junction(source: str, link: str):
|
||||
out, err = proc.communicate()
|
||||
tty.debug(out.decode())
|
||||
if proc.returncode != 0:
|
||||
err = err.decode()
|
||||
tty.error(err)
|
||||
raise SymlinkError("Make junction command returned a non-zero return code.", err)
|
||||
err_str = err.decode()
|
||||
tty.error(err_str)
|
||||
raise SymlinkError("Make junction command returned a non-zero return code.", err_str)
|
||||
|
||||
|
||||
def _windows_create_hard_link(path: str, link: str):
|
||||
@@ -269,14 +269,14 @@ def _windows_create_hard_link(path: str, link: str):
|
||||
CreateHardLink(link, path)
|
||||
|
||||
|
||||
def readlink(path: str):
|
||||
def readlink(path: str, *, dir_fd=None):
|
||||
"""Spack utility to override of os.readlink method to work cross platform"""
|
||||
if _windows_is_hardlink(path):
|
||||
return _windows_read_hard_link(path)
|
||||
elif _windows_is_junction(path):
|
||||
return _windows_read_junction(path)
|
||||
else:
|
||||
return os.readlink(path)
|
||||
return sanitize_win_longpath(os.readlink(path, dir_fd=dir_fd))
|
||||
|
||||
|
||||
def _windows_read_hard_link(link: str) -> str:
|
||||
|
@@ -4,7 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
#: PEP440 canonical <major>.<minor>.<micro>.<devN> string
|
||||
__version__ = "0.22.0.dev0"
|
||||
__version__ = "0.22.2"
|
||||
spack_version = __version__
|
||||
|
||||
|
||||
|
@@ -254,8 +254,8 @@ def _search_duplicate_specs_in_externals(error_cls):
|
||||
|
||||
@config_packages
|
||||
def _deprecated_preferences(error_cls):
|
||||
"""Search package preferences deprecated in v0.21 (and slated for removal in v0.22)"""
|
||||
# TODO (v0.22): remove this audit as the attributes will not be allowed in config
|
||||
"""Search package preferences deprecated in v0.21 (and slated for removal in v0.23)"""
|
||||
# TODO (v0.23): remove this audit as the attributes will not be allowed in config
|
||||
errors = []
|
||||
packages_yaml = spack.config.CONFIG.get_config("packages")
|
||||
|
||||
@@ -779,7 +779,7 @@ def check_virtual_with_variants(spec, msg):
|
||||
return
|
||||
error = error_cls(
|
||||
f"{pkg_name}: {msg}",
|
||||
f"remove variants from '{spec}' in depends_on directive in {filename}",
|
||||
[f"remove variants from '{spec}' in depends_on directive in {filename}"],
|
||||
)
|
||||
errors.append(error)
|
||||
|
||||
|
@@ -23,12 +23,12 @@
|
||||
import warnings
|
||||
from contextlib import closing
|
||||
from typing import Dict, Iterable, List, NamedTuple, Optional, Set, Tuple
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
import llnl.util.filesystem as fsys
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import BaseDirectoryVisitor, mkdirp, visit_directory_tree
|
||||
from llnl.util.symlink import readlink
|
||||
|
||||
import spack.caches
|
||||
import spack.cmd
|
||||
@@ -658,7 +658,7 @@ def get_buildfile_manifest(spec):
|
||||
# 2. paths are used as strings.
|
||||
for rel_path in visitor.symlinks:
|
||||
abs_path = os.path.join(root, rel_path)
|
||||
link = os.readlink(abs_path)
|
||||
link = readlink(abs_path)
|
||||
if os.path.isabs(link) and link.startswith(spack.store.STORE.layout.root):
|
||||
data["link_to_relocate"].append(rel_path)
|
||||
|
||||
@@ -898,9 +898,8 @@ def url_read_method(url):
|
||||
try:
|
||||
_, _, spec_file = web_util.read_from_url(url)
|
||||
contents = codecs.getreader("utf-8")(spec_file).read()
|
||||
except (URLError, web_util.SpackWebError) as url_err:
|
||||
tty.error("Error reading specfile: {0}".format(url))
|
||||
tty.error(url_err)
|
||||
except web_util.SpackWebError as e:
|
||||
tty.error(f"Error reading specfile: {url}: {e}")
|
||||
return contents
|
||||
|
||||
try:
|
||||
@@ -2001,6 +2000,7 @@ def install_root_node(spec, unsigned=False, force=False, sha256=None):
|
||||
with spack.util.path.filter_padding():
|
||||
tty.msg('Installing "{0}" from a buildcache'.format(spec.format()))
|
||||
extract_tarball(spec, download_result, force)
|
||||
spec.package.windows_establish_runtime_linkage()
|
||||
spack.hooks.post_install(spec, False)
|
||||
spack.store.STORE.db.add(spec, spack.store.STORE.layout)
|
||||
|
||||
@@ -2039,21 +2039,17 @@ def try_direct_fetch(spec, mirrors=None):
|
||||
try:
|
||||
_, _, fs = web_util.read_from_url(buildcache_fetch_url_signed_json)
|
||||
specfile_is_signed = True
|
||||
except (URLError, web_util.SpackWebError, HTTPError) as url_err:
|
||||
except web_util.SpackWebError as e1:
|
||||
try:
|
||||
_, _, fs = web_util.read_from_url(buildcache_fetch_url_json)
|
||||
except (URLError, web_util.SpackWebError, HTTPError) as url_err_x:
|
||||
except web_util.SpackWebError as e2:
|
||||
tty.debug(
|
||||
"Did not find {0} on {1}".format(
|
||||
specfile_name, buildcache_fetch_url_signed_json
|
||||
),
|
||||
url_err,
|
||||
f"Did not find {specfile_name} on {buildcache_fetch_url_signed_json}",
|
||||
e1,
|
||||
level=2,
|
||||
)
|
||||
tty.debug(
|
||||
"Did not find {0} on {1}".format(specfile_name, buildcache_fetch_url_json),
|
||||
url_err_x,
|
||||
level=2,
|
||||
f"Did not find {specfile_name} on {buildcache_fetch_url_json}", e2, level=2
|
||||
)
|
||||
continue
|
||||
specfile_contents = codecs.getreader("utf-8")(fs).read()
|
||||
@@ -2138,6 +2134,9 @@ def get_keys(install=False, trust=False, force=False, mirrors=None):
|
||||
|
||||
for mirror in mirror_collection.values():
|
||||
fetch_url = mirror.fetch_url
|
||||
# TODO: oci:// does not support signing.
|
||||
if fetch_url.startswith("oci://"):
|
||||
continue
|
||||
keys_url = url_util.join(
|
||||
fetch_url, BUILD_CACHE_RELATIVE_PATH, BUILD_CACHE_KEYS_RELATIVE_PATH
|
||||
)
|
||||
@@ -2148,19 +2147,12 @@ def get_keys(install=False, trust=False, force=False, mirrors=None):
|
||||
try:
|
||||
_, _, json_file = web_util.read_from_url(keys_index)
|
||||
json_index = sjson.load(codecs.getreader("utf-8")(json_file))
|
||||
except (URLError, web_util.SpackWebError) as url_err:
|
||||
except web_util.SpackWebError as url_err:
|
||||
if web_util.url_exists(keys_index):
|
||||
err_msg = [
|
||||
"Unable to find public keys in {0},",
|
||||
" caught exception attempting to read from {1}.",
|
||||
]
|
||||
|
||||
tty.error(
|
||||
"".join(err_msg).format(
|
||||
url_util.format(fetch_url), url_util.format(keys_index)
|
||||
)
|
||||
f"Unable to find public keys in {url_util.format(fetch_url)},"
|
||||
f" caught exception attempting to read from {url_util.format(keys_index)}."
|
||||
)
|
||||
|
||||
tty.debug(url_err)
|
||||
|
||||
continue
|
||||
@@ -2440,7 +2432,7 @@ def get_remote_hash(self):
|
||||
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:
|
||||
except (TimeoutError, urllib.error.URLError):
|
||||
return None
|
||||
|
||||
# Validate the hash
|
||||
@@ -2462,7 +2454,7 @@ def conditional_fetch(self) -> FetchIndexResult:
|
||||
|
||||
try:
|
||||
response = self.urlopen(urllib.request.Request(url_index, headers=self.headers))
|
||||
except urllib.error.URLError as e:
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
raise FetchIndexError("Could not fetch index from {}".format(url_index), e) from e
|
||||
|
||||
try:
|
||||
@@ -2503,10 +2495,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")
|
||||
headers = {
|
||||
"User-Agent": web_util.SPACK_USER_AGENT,
|
||||
"If-None-Match": '"{}"'.format(self.etag),
|
||||
}
|
||||
headers = {"User-Agent": web_util.SPACK_USER_AGENT, "If-None-Match": f'"{self.etag}"'}
|
||||
|
||||
try:
|
||||
response = self.urlopen(urllib.request.Request(url, headers=headers))
|
||||
@@ -2514,14 +2503,14 @@ def conditional_fetch(self) -> FetchIndexResult:
|
||||
if e.getcode() == 304:
|
||||
# Not modified; that means fresh.
|
||||
return FetchIndexResult(etag=None, hash=None, data=None, fresh=True)
|
||||
raise FetchIndexError("Could not fetch index {}".format(url), e) from e
|
||||
except urllib.error.URLError as e:
|
||||
raise FetchIndexError("Could not fetch index {}".format(url), e) from e
|
||||
raise FetchIndexError(f"Could not fetch index {url}", e) from e
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
raise FetchIndexError(f"Could not fetch index {url}", e) from e
|
||||
|
||||
try:
|
||||
result = codecs.getreader("utf-8")(response).read()
|
||||
except ValueError as e:
|
||||
raise FetchIndexError("Remote index {} is invalid".format(url), e) from e
|
||||
raise FetchIndexError(f"Remote index {url} is invalid", e) from e
|
||||
|
||||
headers = response.headers
|
||||
etag_header_value = headers.get("Etag", None) or headers.get("etag", None)
|
||||
@@ -2552,21 +2541,19 @@ def conditional_fetch(self) -> FetchIndexResult:
|
||||
headers={"Accept": "application/vnd.oci.image.manifest.v1+json"},
|
||||
)
|
||||
)
|
||||
except urllib.error.URLError as e:
|
||||
raise FetchIndexError(
|
||||
"Could not fetch manifest from {}".format(url_manifest), e
|
||||
) from e
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
raise FetchIndexError(f"Could not fetch manifest from {url_manifest}", e) from e
|
||||
|
||||
try:
|
||||
manifest = json.loads(response.read())
|
||||
except Exception as e:
|
||||
raise FetchIndexError("Remote index {} is invalid".format(url_manifest), e) from e
|
||||
raise FetchIndexError(f"Remote index {url_manifest} is invalid", e) from e
|
||||
|
||||
# Get first blob hash, which should be the index.json
|
||||
try:
|
||||
index_digest = spack.oci.image.Digest.from_string(manifest["layers"][0]["digest"])
|
||||
except Exception as e:
|
||||
raise FetchIndexError("Remote index {} is invalid".format(url_manifest), e) from e
|
||||
raise FetchIndexError(f"Remote index {url_manifest} is invalid", e) from e
|
||||
|
||||
# Fresh?
|
||||
if index_digest.digest == self.local_hash:
|
||||
|
@@ -43,7 +43,7 @@
|
||||
from collections import defaultdict
|
||||
from enum import Flag, auto
|
||||
from itertools import chain
|
||||
from typing import List, Set, Tuple
|
||||
from typing import Dict, List, Set, Tuple
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.string import plural
|
||||
@@ -72,6 +72,7 @@
|
||||
import spack.store
|
||||
import spack.subprocess_context
|
||||
import spack.user_environment
|
||||
import spack.util.executable
|
||||
import spack.util.path
|
||||
import spack.util.pattern
|
||||
from spack import traverse
|
||||
@@ -479,12 +480,12 @@ def set_wrapper_variables(pkg, env):
|
||||
env.set(SPACK_DEBUG_LOG_ID, pkg.spec.format("{name}-{hash:7}"))
|
||||
env.set(SPACK_DEBUG_LOG_DIR, spack.main.spack_working_dir)
|
||||
|
||||
# Find ccache binary and hand it to build environment
|
||||
if spack.config.get("config:ccache"):
|
||||
ccache = Executable("ccache")
|
||||
if not ccache:
|
||||
raise RuntimeError("No ccache binary found in PATH")
|
||||
env.set(SPACK_CCACHE_BINARY, ccache)
|
||||
# Enable ccache in the compiler wrapper
|
||||
env.set(SPACK_CCACHE_BINARY, spack.util.executable.which_string("ccache", required=True))
|
||||
else:
|
||||
# Avoid cache pollution if a build system forces `ccache <compiler wrapper invocation>`.
|
||||
env.set("CCACHE_DISABLE", "1")
|
||||
|
||||
# Gather information about various types of dependencies
|
||||
link_deps = set(pkg.spec.traverse(root=False, deptype=("link")))
|
||||
@@ -730,12 +731,28 @@ def _static_to_shared_library(arch, compiler, static_lib, shared_lib=None, **kwa
|
||||
return compiler(*compiler_args, output=compiler_output)
|
||||
|
||||
|
||||
def get_rpath_deps(pkg):
|
||||
"""Return immediate or transitive RPATHs depending on the package."""
|
||||
if pkg.transitive_rpaths:
|
||||
return [d for d in pkg.spec.traverse(root=False, deptype=("link"))]
|
||||
else:
|
||||
return pkg.spec.dependencies(deptype="link")
|
||||
def _get_rpath_deps_from_spec(
|
||||
spec: spack.spec.Spec, transitive_rpaths: bool
|
||||
) -> List[spack.spec.Spec]:
|
||||
if not transitive_rpaths:
|
||||
return spec.dependencies(deptype=dt.LINK)
|
||||
|
||||
by_name: Dict[str, spack.spec.Spec] = {}
|
||||
|
||||
for dep in spec.traverse(root=False, deptype=dt.LINK):
|
||||
lookup = by_name.get(dep.name)
|
||||
if lookup is None:
|
||||
by_name[dep.name] = dep
|
||||
elif lookup.version < dep.version:
|
||||
by_name[dep.name] = dep
|
||||
|
||||
return list(by_name.values())
|
||||
|
||||
|
||||
def get_rpath_deps(pkg: spack.package_base.PackageBase) -> List[spack.spec.Spec]:
|
||||
"""Return immediate or transitive dependencies (depending on the package) that need to be
|
||||
rpath'ed. If a package occurs multiple times, the newest version is kept."""
|
||||
return _get_rpath_deps_from_spec(pkg.spec, pkg.transitive_rpaths)
|
||||
|
||||
|
||||
def get_rpaths(pkg):
|
||||
|
@@ -145,7 +145,7 @@ def install(self, pkg, spec, prefix):
|
||||
opts += self.nmake_install_args()
|
||||
if self.makefile_name:
|
||||
opts.append("/F{}".format(self.makefile_name))
|
||||
opts.append(self.define("PREFIX", prefix))
|
||||
opts.append(self.define("PREFIX", fs.windows_sfn(prefix)))
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).nmake(
|
||||
*opts, *self.install_targets, ignore_quotes=self.ignore_quotes
|
||||
|
@@ -120,12 +120,6 @@ def skip_modules(self) -> Iterable[str]:
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def python_spec(self):
|
||||
"""Get python-venv if it exists or python otherwise."""
|
||||
python, *_ = self.spec.dependencies("python-venv") or self.spec.dependencies("python")
|
||||
return python
|
||||
|
||||
def view_file_conflicts(self, view, merge_map):
|
||||
"""Report all file conflicts, excepting special cases for python.
|
||||
Specifically, this does not report errors for duplicate
|
||||
@@ -146,8 +140,12 @@ def view_file_conflicts(self, view, merge_map):
|
||||
def add_files_to_view(self, view, merge_map, skip_if_exists=True):
|
||||
# Patch up shebangs if the package extends Python and we put a Python interpreter in the
|
||||
# view.
|
||||
python = self.python_spec
|
||||
if not self.extendee_spec or python.external:
|
||||
if not self.extendee_spec:
|
||||
return super().add_files_to_view(view, merge_map, skip_if_exists)
|
||||
|
||||
python, *_ = self.spec.dependencies("python-venv") or self.spec.dependencies("python")
|
||||
|
||||
if python.external:
|
||||
return super().add_files_to_view(view, merge_map, skip_if_exists)
|
||||
|
||||
# We only patch shebangs in the bin directory.
|
||||
@@ -368,6 +366,12 @@ def list_url(cls) -> Optional[str]: # type: ignore[override]
|
||||
return f"https://pypi.org/simple/{name}/"
|
||||
return None
|
||||
|
||||
@property
|
||||
def python_spec(self):
|
||||
"""Get python-venv if it exists or python otherwise."""
|
||||
python, *_ = self.spec.dependencies("python-venv") or self.spec.dependencies("python")
|
||||
return python
|
||||
|
||||
@property
|
||||
def headers(self) -> HeaderList:
|
||||
"""Discover header files in platlib."""
|
||||
|
@@ -44,6 +44,7 @@
|
||||
from spack import traverse
|
||||
from spack.error import SpackError
|
||||
from spack.reporters import CDash, CDashConfiguration
|
||||
from spack.reporters.cdash import SPACK_CDASH_TIMEOUT
|
||||
from spack.reporters.cdash import build_stamp as cdash_build_stamp
|
||||
|
||||
# See https://docs.gitlab.com/ee/ci/yaml/#retry for descriptions of conditions
|
||||
@@ -683,6 +684,22 @@ def generate_gitlab_ci_yaml(
|
||||
"instead.",
|
||||
)
|
||||
|
||||
def ensure_expected_target_path(path):
|
||||
"""Returns passed paths with all Windows path separators exchanged
|
||||
for posix separators only if copy_only_pipeline is enabled
|
||||
|
||||
This is required as copy_only_pipelines are a unique scenario where
|
||||
the generate job and child pipelines are run on different platforms.
|
||||
To make this compatible w/ Windows, we cannot write Windows style path separators
|
||||
that will be consumed on by the Posix copy job runner.
|
||||
|
||||
TODO (johnwparent): Refactor config + cli read/write to deal only in posix
|
||||
style paths
|
||||
"""
|
||||
if copy_only_pipeline and path:
|
||||
path = path.replace("\\", "/")
|
||||
return path
|
||||
|
||||
pipeline_mirrors = spack.mirror.MirrorCollection(binary=True)
|
||||
deprecated_mirror_config = False
|
||||
buildcache_destination = None
|
||||
@@ -806,7 +823,7 @@ def generate_gitlab_ci_yaml(
|
||||
if scope not in include_scopes and scope not in env_includes:
|
||||
include_scopes.insert(0, scope)
|
||||
env_includes.extend(include_scopes)
|
||||
env_yaml_root["spack"]["include"] = env_includes
|
||||
env_yaml_root["spack"]["include"] = [ensure_expected_target_path(i) for i in env_includes]
|
||||
|
||||
if "gitlab-ci" in env_yaml_root["spack"] and "ci" not in env_yaml_root["spack"]:
|
||||
env_yaml_root["spack"]["ci"] = env_yaml_root["spack"].pop("gitlab-ci")
|
||||
@@ -1094,7 +1111,7 @@ def main_script_replacements(cmd):
|
||||
if cdash_handler and cdash_handler.auth_token:
|
||||
try:
|
||||
cdash_handler.populate_buildgroup(all_job_names)
|
||||
except (SpackError, HTTPError, URLError) as err:
|
||||
except (SpackError, HTTPError, URLError, TimeoutError) as err:
|
||||
tty.warn(f"Problem populating buildgroup: {err}")
|
||||
else:
|
||||
tty.warn("Unable to populate buildgroup without CDash credentials")
|
||||
@@ -1227,6 +1244,9 @@ def main_script_replacements(cmd):
|
||||
"SPACK_REBUILD_EVERYTHING": str(rebuild_everything),
|
||||
"SPACK_REQUIRE_SIGNING": os.environ.get("SPACK_REQUIRE_SIGNING", "False"),
|
||||
}
|
||||
output_vars = output_object["variables"]
|
||||
for item, val in output_vars.items():
|
||||
output_vars[item] = ensure_expected_target_path(val)
|
||||
|
||||
# TODO: Remove this block in Spack 0.23
|
||||
if deprecated_mirror_config and remote_mirror_override:
|
||||
@@ -1283,7 +1303,6 @@ def main_script_replacements(cmd):
|
||||
sorted_output = {}
|
||||
for output_key, output_value in sorted(output_object.items()):
|
||||
sorted_output[output_key] = output_value
|
||||
|
||||
if known_broken_specs_encountered:
|
||||
tty.error("This pipeline generated hashes known to be broken on develop:")
|
||||
display_broken_spec_messages(broken_specs_url, known_broken_specs_encountered)
|
||||
@@ -1478,6 +1497,12 @@ def copy_test_logs_to_artifacts(test_stage, job_test_dir):
|
||||
copy_files_to_artifacts(os.path.join(test_stage, "*", "*.txt"), job_test_dir)
|
||||
|
||||
|
||||
def win_quote(quote_str: str) -> str:
|
||||
if IS_WINDOWS:
|
||||
quote_str = f'"{quote_str}"'
|
||||
return quote_str
|
||||
|
||||
|
||||
def download_and_extract_artifacts(url, work_dir):
|
||||
"""Look for gitlab artifacts.zip at the given url, and attempt to download
|
||||
and extract the contents into the given work_dir
|
||||
@@ -1500,7 +1525,7 @@ def download_and_extract_artifacts(url, work_dir):
|
||||
request = Request(url, headers=headers)
|
||||
request.get_method = lambda: "GET"
|
||||
|
||||
response = opener.open(request)
|
||||
response = opener.open(request, timeout=SPACK_CDASH_TIMEOUT)
|
||||
response_code = response.getcode()
|
||||
|
||||
if response_code != 200:
|
||||
@@ -1942,9 +1967,9 @@ def compose_command_err_handling(args):
|
||||
# but we need to handle EXEs (git, etc) ourselves
|
||||
catch_exe_failure = (
|
||||
"""
|
||||
if ($LASTEXITCODE -ne 0){
|
||||
throw "Command {} has failed"
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0){{
|
||||
throw 'Command {} has failed'
|
||||
}}
|
||||
"""
|
||||
if IS_WINDOWS
|
||||
else ""
|
||||
@@ -2070,7 +2095,7 @@ def read_broken_spec(broken_spec_url):
|
||||
"""
|
||||
try:
|
||||
_, _, fs = web_util.read_from_url(broken_spec_url)
|
||||
except (URLError, web_util.SpackWebError, HTTPError):
|
||||
except web_util.SpackWebError:
|
||||
tty.warn(f"Unable to read broken spec from {broken_spec_url}")
|
||||
return None
|
||||
|
||||
@@ -2176,13 +2201,13 @@ def __init__(self, ci_cdash):
|
||||
def args(self):
|
||||
return [
|
||||
"--cdash-upload-url",
|
||||
self.upload_url,
|
||||
win_quote(self.upload_url),
|
||||
"--cdash-build",
|
||||
self.build_name,
|
||||
win_quote(self.build_name),
|
||||
"--cdash-site",
|
||||
self.site,
|
||||
win_quote(self.site),
|
||||
"--cdash-buildstamp",
|
||||
self.build_stamp,
|
||||
win_quote(self.build_stamp),
|
||||
]
|
||||
|
||||
@property # type: ignore
|
||||
@@ -2248,7 +2273,7 @@ def create_buildgroup(self, opener, headers, url, group_name, group_type):
|
||||
|
||||
request = Request(url, data=enc_data, headers=headers)
|
||||
|
||||
response = opener.open(request)
|
||||
response = opener.open(request, timeout=SPACK_CDASH_TIMEOUT)
|
||||
response_code = response.getcode()
|
||||
|
||||
if response_code not in [200, 201]:
|
||||
@@ -2294,7 +2319,7 @@ def populate_buildgroup(self, job_names):
|
||||
request = Request(url, data=enc_data, headers=headers)
|
||||
request.get_method = lambda: "PUT"
|
||||
|
||||
response = opener.open(request)
|
||||
response = opener.open(request, timeout=SPACK_CDASH_TIMEOUT)
|
||||
response_code = response.getcode()
|
||||
|
||||
if response_code != 200:
|
||||
|
@@ -13,7 +13,6 @@
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
import llnl.util.tty as tty
|
||||
@@ -54,6 +53,7 @@
|
||||
from spack.oci.oci import (
|
||||
copy_missing_layers_with_retry,
|
||||
get_manifest_and_config_with_retry,
|
||||
list_tags,
|
||||
upload_blob_with_retry,
|
||||
upload_manifest_with_retry,
|
||||
)
|
||||
@@ -813,7 +813,7 @@ def _push_oci(
|
||||
|
||||
def extra_config(spec: Spec):
|
||||
spec_dict = spec.to_dict(hash=ht.dag_hash)
|
||||
spec_dict["buildcache_layout_version"] = 1
|
||||
spec_dict["buildcache_layout_version"] = bindist.CURRENT_BUILD_CACHE_LAYOUT_VERSION
|
||||
spec_dict["binary_cache_checksum"] = {
|
||||
"hash_algorithm": "sha256",
|
||||
"hash": checksums[spec.dag_hash()].compressed_digest.digest,
|
||||
@@ -856,10 +856,7 @@ def _config_from_tag(image_ref: ImageReference, tag: str) -> Optional[dict]:
|
||||
|
||||
|
||||
def _update_index_oci(image_ref: ImageReference, tmpdir: str, pool: MaybePool) -> None:
|
||||
request = urllib.request.Request(url=image_ref.tags_url())
|
||||
response = spack.oci.opener.urlopen(request)
|
||||
spack.oci.opener.ensure_status(request, response, 200)
|
||||
tags = json.load(response)["tags"]
|
||||
tags = list_tags(image_ref)
|
||||
|
||||
# Fetch all image config files in parallel
|
||||
spec_dicts = pool.starmap(
|
||||
|
@@ -31,7 +31,6 @@
|
||||
level = "long"
|
||||
|
||||
SPACK_COMMAND = "spack"
|
||||
MAKE_COMMAND = "make"
|
||||
INSTALL_FAIL_CODE = 1
|
||||
FAILED_CREATE_BUILDCACHE_CODE = 100
|
||||
|
||||
@@ -40,6 +39,12 @@ def deindent(desc):
|
||||
return desc.replace(" ", "")
|
||||
|
||||
|
||||
def unicode_escape(path: str) -> str:
|
||||
"""Returns transformed path with any unicode
|
||||
characters replaced with their corresponding escapes"""
|
||||
return path.encode("unicode-escape").decode("utf-8")
|
||||
|
||||
|
||||
def setup_parser(subparser):
|
||||
setup_parser.parser = subparser
|
||||
subparsers = subparser.add_subparsers(help="CI sub-commands")
|
||||
@@ -551,75 +556,35 @@ def ci_rebuild(args):
|
||||
# No hash match anywhere means we need to rebuild spec
|
||||
|
||||
# Start with spack arguments
|
||||
spack_cmd = [SPACK_COMMAND, "--color=always", "--backtrace", "--verbose"]
|
||||
spack_cmd = [SPACK_COMMAND, "--color=always", "--backtrace", "--verbose", "install"]
|
||||
|
||||
config = cfg.get("config")
|
||||
if not config["verify_ssl"]:
|
||||
spack_cmd.append("-k")
|
||||
|
||||
install_args = []
|
||||
install_args = [f'--use-buildcache={spack_ci.win_quote("package:never,dependencies:only")}']
|
||||
|
||||
can_verify = spack_ci.can_verify_binaries()
|
||||
verify_binaries = can_verify and spack_is_pr_pipeline is False
|
||||
if not verify_binaries:
|
||||
install_args.append("--no-check-signature")
|
||||
|
||||
slash_hash = "/{}".format(job_spec.dag_hash())
|
||||
|
||||
# Arguments when installing dependencies from cache
|
||||
deps_install_args = install_args
|
||||
slash_hash = spack_ci.win_quote("/" + job_spec.dag_hash())
|
||||
|
||||
# Arguments when installing the root from sources
|
||||
root_install_args = install_args + [
|
||||
"--keep-stage",
|
||||
"--only=package",
|
||||
"--use-buildcache=package:never,dependencies:only",
|
||||
]
|
||||
deps_install_args = install_args + ["--only=dependencies"]
|
||||
root_install_args = install_args + ["--keep-stage", "--only=package"]
|
||||
|
||||
if cdash_handler:
|
||||
# Add additional arguments to `spack install` for CDash reporting.
|
||||
root_install_args.extend(cdash_handler.args())
|
||||
root_install_args.append(slash_hash)
|
||||
|
||||
# ["x", "y"] -> "'x' 'y'"
|
||||
args_to_string = lambda args: " ".join("'{}'".format(arg) for arg in args)
|
||||
|
||||
commands = [
|
||||
# apparently there's a race when spack bootstraps? do it up front once
|
||||
[SPACK_COMMAND, "-e", env.path, "bootstrap", "now"],
|
||||
[
|
||||
SPACK_COMMAND,
|
||||
"-e",
|
||||
env.path,
|
||||
"env",
|
||||
"depfile",
|
||||
"-o",
|
||||
"Makefile",
|
||||
"--use-buildcache=package:never,dependencies:only",
|
||||
slash_hash, # limit to spec we're building
|
||||
],
|
||||
[
|
||||
# --output-sync requires GNU make 4.x.
|
||||
# Old make errors when you pass it a flag it doesn't recognize,
|
||||
# but it doesn't error or warn when you set unrecognized flags in
|
||||
# this variable.
|
||||
"export",
|
||||
"GNUMAKEFLAGS=--output-sync=recurse",
|
||||
],
|
||||
[
|
||||
MAKE_COMMAND,
|
||||
"SPACK={}".format(args_to_string(spack_cmd)),
|
||||
"SPACK_COLOR=always",
|
||||
"SPACK_INSTALL_FLAGS={}".format(args_to_string(deps_install_args)),
|
||||
"-j$(nproc)",
|
||||
"install-deps/{}".format(
|
||||
spack.environment.depfile.MakefileSpec(job_spec).safe_format(
|
||||
"{name}-{version}-{hash}"
|
||||
)
|
||||
),
|
||||
],
|
||||
spack_cmd + ["install"] + root_install_args,
|
||||
[SPACK_COMMAND, "-e", unicode_escape(env.path), "bootstrap", "now"],
|
||||
spack_cmd + deps_install_args + [slash_hash],
|
||||
spack_cmd + root_install_args + [slash_hash],
|
||||
]
|
||||
|
||||
tty.debug("Installing {0} from source".format(job_spec.name))
|
||||
install_exit_code = spack_ci.process_command("install", commands, repro_dir)
|
||||
|
||||
|
@@ -11,7 +11,6 @@
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from typing import IO, Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.argparsewriter import ArgparseRstWriter, ArgparseWriter, Command
|
||||
from llnl.util.tty.colify import colify
|
||||
@@ -867,9 +866,6 @@ def _commands(parser: ArgumentParser, args: Namespace) -> None:
|
||||
prepend_header(args, f)
|
||||
formatter(args, f)
|
||||
|
||||
if args.update_completion:
|
||||
fs.set_executable(args.update)
|
||||
|
||||
else:
|
||||
prepend_header(args, sys.stdout)
|
||||
formatter(args, sys.stdout)
|
||||
|
@@ -10,7 +10,7 @@
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import llnl.string as string
|
||||
import llnl.util.filesystem as fs
|
||||
@@ -87,6 +87,9 @@ def env_create_setup_parser(subparser):
|
||||
default=None,
|
||||
help="either a lockfile (must end with '.json' or '.lock') or a manifest file",
|
||||
)
|
||||
subparser.add_argument(
|
||||
"--include-concrete", action="append", help="name of old environment to copy specs from"
|
||||
)
|
||||
|
||||
|
||||
def env_create(args):
|
||||
@@ -104,12 +107,17 @@ def env_create(args):
|
||||
# the environment should not include a view.
|
||||
with_view = None
|
||||
|
||||
include_concrete = None
|
||||
if hasattr(args, "include_concrete"):
|
||||
include_concrete = args.include_concrete
|
||||
|
||||
env = _env_create(
|
||||
args.env_name,
|
||||
init_file=args.envfile,
|
||||
dir=args.dir or os.path.sep in args.env_name or args.env_name in (".", ".."),
|
||||
with_view=with_view,
|
||||
keep_relative=args.keep_relative,
|
||||
include_concrete=include_concrete,
|
||||
)
|
||||
|
||||
# Generate views, only really useful for environments created from spack.lock files.
|
||||
@@ -123,31 +131,43 @@ def _env_create(
|
||||
dir: bool = False,
|
||||
with_view: Optional[str] = None,
|
||||
keep_relative: bool = False,
|
||||
include_concrete: Optional[List[str]] = None,
|
||||
):
|
||||
"""Create a new environment, with an optional yaml description.
|
||||
|
||||
Arguments:
|
||||
name_or_path: name of the environment to create, or path to it
|
||||
init_file: optional initialization file -- can be a JSON lockfile (*.lock, *.json) or YAML
|
||||
manifest file
|
||||
dir: if True, create an environment in a directory instead of a managed environment
|
||||
keep_relative: if True, develop paths are copied verbatim into the new environment file,
|
||||
otherwise they may be made absolute if the new environment is in a different location
|
||||
name_or_path (str): name of the environment to create, or path to it
|
||||
init_file (str or file): optional initialization file -- can be
|
||||
a JSON lockfile (*.lock, *.json) or YAML manifest file
|
||||
dir (bool): if True, create an environment in a directory instead
|
||||
of a named environment
|
||||
keep_relative (bool): if True, develop paths are copied verbatim into
|
||||
the new environment file, otherwise they may be made absolute if the
|
||||
new environment is in a different location
|
||||
include_concrete (list): list of the included concrete environments
|
||||
"""
|
||||
if not dir:
|
||||
env = ev.create(
|
||||
name_or_path, init_file=init_file, with_view=with_view, keep_relative=keep_relative
|
||||
name_or_path,
|
||||
init_file=init_file,
|
||||
with_view=with_view,
|
||||
keep_relative=keep_relative,
|
||||
include_concrete=include_concrete,
|
||||
)
|
||||
tty.msg(
|
||||
colorize(
|
||||
f"Created environment @c{{{cescape(env.name)}}} in: @c{{{cescape(env.path)}}}"
|
||||
f"Created environment @c{{{cescape(name_or_path)}}} in: @c{{{cescape(env.path)}}}"
|
||||
)
|
||||
)
|
||||
else:
|
||||
env = ev.create_in_dir(
|
||||
name_or_path, init_file=init_file, with_view=with_view, keep_relative=keep_relative
|
||||
name_or_path,
|
||||
init_file=init_file,
|
||||
with_view=with_view,
|
||||
keep_relative=keep_relative,
|
||||
include_concrete=include_concrete,
|
||||
)
|
||||
tty.msg(colorize(f"Created anonymous environment in: @c{{{cescape(env.path)}}}"))
|
||||
tty.msg(colorize(f"Created independent environment in: @c{{{cescape(env.path)}}}"))
|
||||
tty.msg(f"Activate with: {colorize(f'@c{{spack env activate {cescape(name_or_path)}}}')}")
|
||||
return env
|
||||
|
||||
@@ -434,6 +454,12 @@ def env_remove_setup_parser(subparser):
|
||||
"""remove an existing environment"""
|
||||
subparser.add_argument("rm_env", metavar="env", nargs="+", help="environment(s) to remove")
|
||||
arguments.add_common_arguments(subparser, ["yes_to_all"])
|
||||
subparser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="remove the environment even if it is included in another environment",
|
||||
)
|
||||
|
||||
|
||||
def env_remove(args):
|
||||
@@ -442,14 +468,34 @@ def env_remove(args):
|
||||
This removes an environment managed by Spack. Directory environments
|
||||
and manifests embedded in repositories should be removed manually.
|
||||
"""
|
||||
read_envs = []
|
||||
remove_envs = []
|
||||
valid_envs = []
|
||||
bad_envs = []
|
||||
for env_name in args.rm_env:
|
||||
|
||||
for env_name in ev.all_environment_names():
|
||||
try:
|
||||
env = ev.read(env_name)
|
||||
read_envs.append(env)
|
||||
valid_envs.append(env)
|
||||
|
||||
if env_name in args.rm_env:
|
||||
remove_envs.append(env)
|
||||
except (spack.config.ConfigFormatError, ev.SpackEnvironmentConfigError):
|
||||
bad_envs.append(env_name)
|
||||
if env_name in args.rm_env:
|
||||
bad_envs.append(env_name)
|
||||
|
||||
# Check if remove_env is included from another env before trying to remove
|
||||
for env in valid_envs:
|
||||
for remove_env in remove_envs:
|
||||
# don't check if environment is included to itself
|
||||
if env.name == remove_env.name:
|
||||
continue
|
||||
|
||||
if remove_env.path in env.included_concrete_envs:
|
||||
msg = f'Environment "{remove_env.name}" is being used by environment "{env.name}"'
|
||||
if args.force:
|
||||
tty.warn(msg)
|
||||
else:
|
||||
tty.die(msg)
|
||||
|
||||
if not args.yes_to_all:
|
||||
environments = string.plural(len(args.rm_env), "environment", show_n=False)
|
||||
@@ -458,7 +504,7 @@ def env_remove(args):
|
||||
if not answer:
|
||||
tty.die("Will not remove any environments")
|
||||
|
||||
for env in read_envs:
|
||||
for env in remove_envs:
|
||||
name = env.name
|
||||
if env.active:
|
||||
tty.die(f"Environment {name} can't be removed while activated.")
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import copy
|
||||
import sys
|
||||
|
||||
import llnl.util.lang
|
||||
@@ -271,6 +272,27 @@ def root_decorator(spec, string):
|
||||
|
||||
print()
|
||||
|
||||
if env.included_concrete_envs:
|
||||
tty.msg("Included specs")
|
||||
|
||||
# Root specs cannot be displayed with prefixes, since those are not
|
||||
# set for abstract specs. Same for hashes
|
||||
root_args = copy.copy(args)
|
||||
root_args.paths = False
|
||||
|
||||
# Roots are displayed with variants, etc. so that we can see
|
||||
# specifically what the user asked for.
|
||||
cmd.display_specs(
|
||||
env.included_user_specs,
|
||||
root_args,
|
||||
decorator=lambda s, f: color.colorize("@*{%s}" % f),
|
||||
namespace=True,
|
||||
show_flags=True,
|
||||
show_full_compiler=True,
|
||||
variants=True,
|
||||
)
|
||||
print()
|
||||
|
||||
if args.show_concretized:
|
||||
tty.msg("Concretized roots")
|
||||
cmd.display_specs(env.specs_by_hash.values(), args, decorator=decorator)
|
||||
|
@@ -23,7 +23,7 @@
|
||||
|
||||
|
||||
# tutorial configuration parameters
|
||||
tutorial_branch = "releases/v0.21"
|
||||
tutorial_branch = "releases/v0.22"
|
||||
tutorial_mirror = "file:///mirror"
|
||||
tutorial_key = os.path.join(spack.paths.share_path, "keys", "tutorial.pub")
|
||||
|
||||
|
@@ -38,10 +38,10 @@
|
||||
|
||||
import spack.cmd
|
||||
import spack.environment as ev
|
||||
import spack.filesystem_view as fsv
|
||||
import spack.schema.projections
|
||||
import spack.store
|
||||
from spack.config import validate
|
||||
from spack.filesystem_view import YamlFilesystemView, view_func_parser
|
||||
from spack.util import spack_yaml as s_yaml
|
||||
|
||||
description = "project packages to a compact naming scheme on the filesystem"
|
||||
@@ -193,17 +193,13 @@ def view(parser, args):
|
||||
ordered_projections = {}
|
||||
|
||||
# What method are we using for this view
|
||||
if args.action in actions_link:
|
||||
link_fn = view_func_parser(args.action)
|
||||
else:
|
||||
link_fn = view_func_parser("symlink")
|
||||
|
||||
view = YamlFilesystemView(
|
||||
link_type = args.action if args.action in actions_link else "symlink"
|
||||
view = fsv.YamlFilesystemView(
|
||||
path,
|
||||
spack.store.STORE.layout,
|
||||
projections=ordered_projections,
|
||||
ignore_conflicts=getattr(args, "ignore_conflicts", False),
|
||||
link=link_fn,
|
||||
link_type=link_type,
|
||||
verbose=args.verbose,
|
||||
)
|
||||
|
||||
|
@@ -220,10 +220,10 @@ def _compiler_config_from_external(config):
|
||||
operating_system = host_platform.operating_system("default_os")
|
||||
target = host_platform.target("default_target").microarchitecture
|
||||
else:
|
||||
target = spec.target
|
||||
target = spec.architecture.target
|
||||
if not target:
|
||||
host_platform = spack.platforms.host()
|
||||
target = host_platform.target("default_target").microarchitecture
|
||||
target = spack.platforms.host().target("default_target")
|
||||
target = target.microarchitecture
|
||||
|
||||
operating_system = spec.os
|
||||
if not operating_system:
|
||||
|
@@ -97,7 +97,7 @@ class OpenMpi(Package):
|
||||
PatchesType = Optional[Union[Patcher, str, List[Union[Patcher, str]]]]
|
||||
|
||||
|
||||
SUPPORTED_LANGUAGES = ("fortran", "cxx")
|
||||
SUPPORTED_LANGUAGES = ("fortran", "cxx", "c")
|
||||
|
||||
|
||||
def _make_when_spec(value: WhenType) -> Optional["spack.spec.Spec"]:
|
||||
|
@@ -15,6 +15,7 @@
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.symlink import readlink
|
||||
|
||||
import spack.config
|
||||
import spack.hash_types as ht
|
||||
@@ -181,7 +182,7 @@ def deprecated_file_path(self, deprecated_spec, deprecator_spec=None):
|
||||
base_dir = (
|
||||
self.path_for_spec(deprecator_spec)
|
||||
if deprecator_spec
|
||||
else os.readlink(deprecated_spec.prefix)
|
||||
else readlink(deprecated_spec.prefix)
|
||||
)
|
||||
|
||||
yaml_path = os.path.join(
|
||||
|
@@ -34,6 +34,9 @@
|
||||
* ``spec``: a string representation of the abstract spec that was concretized
|
||||
|
||||
4. ``concrete_specs``: a dictionary containing the specs in the environment.
|
||||
5. ``include_concrete`` (dictionary): an optional dictionary that includes the roots
|
||||
and concrete specs from the included environments, keyed by the path to that
|
||||
environment
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
@@ -50,26 +53,37 @@
|
||||
- ``v2``
|
||||
- ``v3``
|
||||
- ``v4``
|
||||
- ``v5``
|
||||
* - ``v0.12:0.14``
|
||||
- ✅
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
* - ``v0.15:0.16``
|
||||
- ✅
|
||||
- ✅
|
||||
-
|
||||
-
|
||||
-
|
||||
* - ``v0.17``
|
||||
- ✅
|
||||
- ✅
|
||||
- ✅
|
||||
-
|
||||
-
|
||||
* - ``v0.18:``
|
||||
- ✅
|
||||
- ✅
|
||||
- ✅
|
||||
- ✅
|
||||
-
|
||||
* - ``v0.22:``
|
||||
- ✅
|
||||
- ✅
|
||||
- ✅
|
||||
- ✅
|
||||
- ✅
|
||||
|
||||
Version 1
|
||||
---------
|
||||
@@ -334,6 +348,118 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Version 5
|
||||
---------
|
||||
|
||||
Version 5 doesn't change the top-level lockfile format, but an optional dictionary is
|
||||
added. The dictionary has the ``root`` and ``concrete_specs`` of the included
|
||||
environments, which are keyed by the path to that environment. Since this is optional
|
||||
if the environment does not have any included environments ``include_concrete`` will
|
||||
not be a part of the lockfile.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"_meta": {
|
||||
"file-type": "spack-lockfile",
|
||||
"lockfile-version": 5,
|
||||
"specfile-version": 3
|
||||
},
|
||||
"roots": [
|
||||
{
|
||||
"hash": "<dag_hash 1>",
|
||||
"spec": "<abstract spec 1>"
|
||||
},
|
||||
{
|
||||
"hash": "<dag_hash 2>",
|
||||
"spec": "<abstract spec 2>"
|
||||
}
|
||||
],
|
||||
"concrete_specs": {
|
||||
"<dag_hash 1>": {
|
||||
"... <spec dict attributes> ...": { },
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "depname_1",
|
||||
"hash": "<dag_hash for depname_1>",
|
||||
"type": ["build", "link"]
|
||||
},
|
||||
{
|
||||
"name": "depname_2",
|
||||
"hash": "<dag_hash for depname_2>",
|
||||
"type": ["build", "link"]
|
||||
}
|
||||
],
|
||||
"hash": "<dag_hash 1>",
|
||||
},
|
||||
"<daghash 2>": {
|
||||
"... <spec dict attributes> ...": { },
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "depname_3",
|
||||
"hash": "<dag_hash for depname_3>",
|
||||
"type": ["build", "link"]
|
||||
},
|
||||
{
|
||||
"name": "depname_4",
|
||||
"hash": "<dag_hash for depname_4>",
|
||||
"type": ["build", "link"]
|
||||
}
|
||||
],
|
||||
"hash": "<dag_hash 2>"
|
||||
}
|
||||
}
|
||||
"include_concrete": {
|
||||
"<path to environment>": {
|
||||
"roots": [
|
||||
{
|
||||
"hash": "<dag_hash 1>",
|
||||
"spec": "<abstract spec 1>"
|
||||
},
|
||||
{
|
||||
"hash": "<dag_hash 2>",
|
||||
"spec": "<abstract spec 2>"
|
||||
}
|
||||
],
|
||||
"concrete_specs": {
|
||||
"<dag_hash 1>": {
|
||||
"... <spec dict attributes> ...": { },
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "depname_1",
|
||||
"hash": "<dag_hash for depname_1>",
|
||||
"type": ["build", "link"]
|
||||
},
|
||||
{
|
||||
"name": "depname_2",
|
||||
"hash": "<dag_hash for depname_2>",
|
||||
"type": ["build", "link"]
|
||||
}
|
||||
],
|
||||
"hash": "<dag_hash 1>",
|
||||
},
|
||||
"<daghash 2>": {
|
||||
"... <spec dict attributes> ...": { },
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "depname_3",
|
||||
"hash": "<dag_hash for depname_3>",
|
||||
"type": ["build", "link"]
|
||||
},
|
||||
{
|
||||
"name": "depname_4",
|
||||
"hash": "<dag_hash for depname_4>",
|
||||
"type": ["build", "link"]
|
||||
}
|
||||
],
|
||||
"hash": "<dag_hash 2>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
from .environment import (
|
||||
|
@@ -16,13 +16,13 @@
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import warnings
|
||||
from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
|
||||
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
import llnl.util.tty.color as clr
|
||||
from llnl.util.link_tree import ConflictingSpecsError
|
||||
from llnl.util.symlink import symlink
|
||||
from llnl.util.symlink import readlink, symlink
|
||||
|
||||
import spack.compilers
|
||||
import spack.concretize
|
||||
@@ -30,6 +30,7 @@
|
||||
import spack.deptypes as dt
|
||||
import spack.error
|
||||
import spack.fetch_strategy
|
||||
import spack.filesystem_view as fsv
|
||||
import spack.hash_types as ht
|
||||
import spack.hooks
|
||||
import spack.main
|
||||
@@ -52,7 +53,6 @@
|
||||
import spack.util.url
|
||||
import spack.version
|
||||
from spack import traverse
|
||||
from spack.filesystem_view import SimpleFilesystemView, inverse_view_func_parser, view_func_parser
|
||||
from spack.installer import PackageInstaller
|
||||
from spack.schema.env import TOP_LEVEL_KEY
|
||||
from spack.spec import Spec
|
||||
@@ -159,6 +159,8 @@ def default_manifest_yaml():
|
||||
default_view_name = "default"
|
||||
# Default behavior to link all packages into views (vs. only root packages)
|
||||
default_view_link = "all"
|
||||
# The name for any included concrete specs
|
||||
included_concrete_name = "include_concrete"
|
||||
|
||||
|
||||
def installed_specs():
|
||||
@@ -293,6 +295,7 @@ def create(
|
||||
init_file: Optional[Union[str, pathlib.Path]] = None,
|
||||
with_view: Optional[Union[str, pathlib.Path, bool]] = None,
|
||||
keep_relative: bool = False,
|
||||
include_concrete: Optional[List[str]] = None,
|
||||
) -> "Environment":
|
||||
"""Create a managed environment in Spack and returns it.
|
||||
|
||||
@@ -309,10 +312,15 @@ def create(
|
||||
string, it specifies the path to the view
|
||||
keep_relative: if True, develop paths are copied verbatim into the new environment file,
|
||||
otherwise they are made absolute
|
||||
include_concrete: list of concrete environment names/paths to be included
|
||||
"""
|
||||
environment_dir = environment_dir_from_name(name, exists_ok=False)
|
||||
return create_in_dir(
|
||||
environment_dir, init_file=init_file, with_view=with_view, keep_relative=keep_relative
|
||||
environment_dir,
|
||||
init_file=init_file,
|
||||
with_view=with_view,
|
||||
keep_relative=keep_relative,
|
||||
include_concrete=include_concrete,
|
||||
)
|
||||
|
||||
|
||||
@@ -321,6 +329,7 @@ def create_in_dir(
|
||||
init_file: Optional[Union[str, pathlib.Path]] = None,
|
||||
with_view: Optional[Union[str, pathlib.Path, bool]] = None,
|
||||
keep_relative: bool = False,
|
||||
include_concrete: Optional[List[str]] = None,
|
||||
) -> "Environment":
|
||||
"""Create an environment in the directory passed as input and returns it.
|
||||
|
||||
@@ -334,6 +343,7 @@ def create_in_dir(
|
||||
string, it specifies the path to the view
|
||||
keep_relative: if True, develop paths are copied verbatim into the new environment file,
|
||||
otherwise they are made absolute
|
||||
include_concrete: concrete environment names/paths to be included
|
||||
"""
|
||||
initialize_environment_dir(root, envfile=init_file)
|
||||
|
||||
@@ -346,6 +356,12 @@ def create_in_dir(
|
||||
if with_view is not None:
|
||||
manifest.set_default_view(with_view)
|
||||
|
||||
if include_concrete is not None:
|
||||
set_included_envs_to_env_paths(include_concrete)
|
||||
validate_included_envs_exists(include_concrete)
|
||||
validate_included_envs_concrete(include_concrete)
|
||||
manifest.set_include_concrete(include_concrete)
|
||||
|
||||
manifest.flush()
|
||||
|
||||
except (spack.config.ConfigFormatError, SpackEnvironmentConfigError) as e:
|
||||
@@ -419,6 +435,67 @@ def ensure_env_root_path_exists():
|
||||
fs.mkdirp(env_root_path())
|
||||
|
||||
|
||||
def set_included_envs_to_env_paths(include_concrete: List[str]) -> None:
|
||||
"""If the included environment(s) is the environment name
|
||||
it is replaced by the path to the environment
|
||||
|
||||
Args:
|
||||
include_concrete: list of env name or path to env"""
|
||||
|
||||
for i, env_name in enumerate(include_concrete):
|
||||
if is_env_dir(env_name):
|
||||
include_concrete[i] = env_name
|
||||
elif exists(env_name):
|
||||
include_concrete[i] = root(env_name)
|
||||
|
||||
|
||||
def validate_included_envs_exists(include_concrete: List[str]) -> None:
|
||||
"""Checks that all of the included environments exist
|
||||
|
||||
Args:
|
||||
include_concrete: list of already existing concrete environments to include
|
||||
|
||||
Raises:
|
||||
SpackEnvironmentError: if any of the included environments do not exist
|
||||
"""
|
||||
|
||||
missing_envs = set()
|
||||
|
||||
for i, env_name in enumerate(include_concrete):
|
||||
if not is_env_dir(env_name):
|
||||
missing_envs.add(env_name)
|
||||
|
||||
if missing_envs:
|
||||
msg = "The following environment(s) are missing: {0}".format(", ".join(missing_envs))
|
||||
raise SpackEnvironmentError(msg)
|
||||
|
||||
|
||||
def validate_included_envs_concrete(include_concrete: List[str]) -> None:
|
||||
"""Checks that all of the included environments are concrete
|
||||
|
||||
Args:
|
||||
include_concrete: list of already existing concrete environments to include
|
||||
|
||||
Raises:
|
||||
SpackEnvironmentError: if any of the included environments are not concrete
|
||||
"""
|
||||
|
||||
non_concrete_envs = set()
|
||||
|
||||
for env_path in include_concrete:
|
||||
if not os.path.exists(Environment(env_path).lock_path):
|
||||
non_concrete_envs.add(Environment(env_path).name)
|
||||
|
||||
if non_concrete_envs:
|
||||
msg = "The following environment(s) are not concrete: {0}\n" "Please run:".format(
|
||||
", ".join(non_concrete_envs)
|
||||
)
|
||||
for env in non_concrete_envs:
|
||||
msg += f"\n\t`spack -e {env} concretize`"
|
||||
|
||||
raise SpackEnvironmentError(msg)
|
||||
|
||||
|
||||
def all_environment_names():
|
||||
"""List the names of environments that currently exist."""
|
||||
# just return empty if the env path does not exist. A read-only
|
||||
@@ -529,7 +606,7 @@ def __init__(
|
||||
self.projections = projections
|
||||
self.select = select
|
||||
self.exclude = exclude
|
||||
self.link_type = view_func_parser(link_type)
|
||||
self.link_type = fsv.canonicalize_link_type(link_type)
|
||||
self.link = link
|
||||
|
||||
def select_fn(self, spec):
|
||||
@@ -563,7 +640,7 @@ def to_dict(self):
|
||||
if self.exclude:
|
||||
ret["exclude"] = self.exclude
|
||||
if self.link_type:
|
||||
ret["link_type"] = inverse_view_func_parser(self.link_type)
|
||||
ret["link_type"] = self.link_type
|
||||
if self.link != default_view_link:
|
||||
ret["link"] = self.link
|
||||
return ret
|
||||
@@ -585,7 +662,7 @@ def _current_root(self):
|
||||
if not os.path.islink(self.root):
|
||||
return None
|
||||
|
||||
root = os.readlink(self.root)
|
||||
root = readlink(self.root)
|
||||
if os.path.isabs(root):
|
||||
return root
|
||||
|
||||
@@ -613,7 +690,7 @@ def get_projection_for_spec(self, spec):
|
||||
to exist on the filesystem."""
|
||||
return self._view(self.root).get_projection_for_spec(spec)
|
||||
|
||||
def view(self, new: Optional[str] = None) -> SimpleFilesystemView:
|
||||
def view(self, new: Optional[str] = None) -> fsv.SimpleFilesystemView:
|
||||
"""
|
||||
Returns a view object for the *underlying* view directory. This means that the
|
||||
self.root symlink is followed, and that the view has to exist on the filesystem
|
||||
@@ -633,14 +710,14 @@ def view(self, new: Optional[str] = None) -> SimpleFilesystemView:
|
||||
)
|
||||
return self._view(path)
|
||||
|
||||
def _view(self, root: str) -> SimpleFilesystemView:
|
||||
def _view(self, root: str) -> fsv.SimpleFilesystemView:
|
||||
"""Returns a view object for a given root dir."""
|
||||
return SimpleFilesystemView(
|
||||
return fsv.SimpleFilesystemView(
|
||||
root,
|
||||
spack.store.STORE.layout,
|
||||
ignore_conflicts=True,
|
||||
projections=self.projections,
|
||||
link=self.link_type,
|
||||
link_type=self.link_type,
|
||||
)
|
||||
|
||||
def __contains__(self, spec):
|
||||
@@ -821,6 +898,18 @@ def __init__(self, manifest_dir: Union[str, pathlib.Path]) -> None:
|
||||
self.specs_by_hash: Dict[str, Spec] = {}
|
||||
#: Repository for this environment (memoized)
|
||||
self._repo = None
|
||||
|
||||
#: Environment paths for concrete (lockfile) included environments
|
||||
self.included_concrete_envs: List[str] = []
|
||||
#: First-level included concretized spec data from/to the lockfile.
|
||||
self.included_concrete_spec_data: Dict[str, Dict[str, List[str]]] = {}
|
||||
#: User specs from included environments from the last concretization
|
||||
self.included_concretized_user_specs: Dict[str, List[Spec]] = {}
|
||||
#: Roots from included environments with the last concretization, in order
|
||||
self.included_concretized_order: Dict[str, List[str]] = {}
|
||||
#: Concretized specs by hash from the included environments
|
||||
self.included_specs_by_hash: Dict[str, Dict[str, Spec]] = {}
|
||||
|
||||
#: Previously active environment
|
||||
self._previous_active = None
|
||||
self._dev_specs = None
|
||||
@@ -858,7 +947,7 @@ def _read(self):
|
||||
|
||||
if os.path.exists(self.lock_path):
|
||||
with open(self.lock_path) as f:
|
||||
read_lock_version = self._read_lockfile(f)
|
||||
read_lock_version = self._read_lockfile(f)["_meta"]["lockfile-version"]
|
||||
|
||||
if read_lock_version == 1:
|
||||
tty.debug(f"Storing backup of {self.lock_path} at {self._lock_backup_v1_path}")
|
||||
@@ -926,6 +1015,20 @@ def add_view(name, values):
|
||||
if self.views == dict():
|
||||
self.views[default_view_name] = ViewDescriptor(self.path, self.view_path_default)
|
||||
|
||||
def _process_concrete_includes(self):
|
||||
"""Extract and load into memory included concrete spec data."""
|
||||
self.included_concrete_envs = self.manifest[TOP_LEVEL_KEY].get(included_concrete_name, [])
|
||||
|
||||
if self.included_concrete_envs:
|
||||
if os.path.exists(self.lock_path):
|
||||
with open(self.lock_path) as f:
|
||||
data = self._read_lockfile(f)
|
||||
|
||||
if included_concrete_name in data:
|
||||
self.included_concrete_spec_data = data[included_concrete_name]
|
||||
else:
|
||||
self.include_concrete_envs()
|
||||
|
||||
def _construct_state_from_manifest(self):
|
||||
"""Set up user specs and views from the manifest file."""
|
||||
self.spec_lists = collections.OrderedDict()
|
||||
@@ -942,6 +1045,31 @@ def _construct_state_from_manifest(self):
|
||||
self.spec_lists[user_speclist_name] = user_specs
|
||||
|
||||
self._process_view(spack.config.get("view", True))
|
||||
self._process_concrete_includes()
|
||||
|
||||
def all_concretized_user_specs(self) -> List[Spec]:
|
||||
"""Returns all of the concretized user specs of the environment and
|
||||
its included environment(s)."""
|
||||
concretized_user_specs = self.concretized_user_specs[:]
|
||||
for included_specs in self.included_concretized_user_specs.values():
|
||||
for included in included_specs:
|
||||
# Don't duplicate included spec(s)
|
||||
if included not in concretized_user_specs:
|
||||
concretized_user_specs.append(included)
|
||||
|
||||
return concretized_user_specs
|
||||
|
||||
def all_concretized_orders(self) -> List[str]:
|
||||
"""Returns all of the concretized order of the environment and
|
||||
its included environment(s)."""
|
||||
concretized_order = self.concretized_order[:]
|
||||
for included_concretized_order in self.included_concretized_order.values():
|
||||
for included in included_concretized_order:
|
||||
# Don't duplicate included spec(s)
|
||||
if included not in concretized_order:
|
||||
concretized_order.append(included)
|
||||
|
||||
return concretized_order
|
||||
|
||||
@property
|
||||
def user_specs(self):
|
||||
@@ -966,6 +1094,26 @@ def _read_dev_specs(self):
|
||||
dev_specs[name] = local_entry
|
||||
return dev_specs
|
||||
|
||||
@property
|
||||
def included_user_specs(self) -> SpecList:
|
||||
"""Included concrete user (or root) specs from last concretization."""
|
||||
spec_list = SpecList()
|
||||
|
||||
if not self.included_concrete_envs:
|
||||
return spec_list
|
||||
|
||||
def add_root_specs(included_concrete_specs):
|
||||
# add specs from the include *and* any nested includes it may have
|
||||
for env, info in included_concrete_specs.items():
|
||||
for root_list in info["roots"]:
|
||||
spec_list.add(root_list["spec"])
|
||||
|
||||
if "include_concrete" in info:
|
||||
add_root_specs(info["include_concrete"])
|
||||
|
||||
add_root_specs(self.included_concrete_spec_data)
|
||||
return spec_list
|
||||
|
||||
def clear(self, re_read=False):
|
||||
"""Clear the contents of the environment
|
||||
|
||||
@@ -977,9 +1125,15 @@ def clear(self, re_read=False):
|
||||
self.spec_lists[user_speclist_name] = SpecList()
|
||||
|
||||
self._dev_specs = {}
|
||||
self.concretized_user_specs = [] # user specs from last concretize
|
||||
self.concretized_order = [] # roots of last concretize, in order
|
||||
self.concretized_user_specs = [] # user specs from last concretize
|
||||
self.specs_by_hash = {} # concretized specs by hash
|
||||
|
||||
self.included_concrete_spec_data = {} # concretized specs from lockfile of included envs
|
||||
self.included_concretized_order = {} # root specs of the included envs, keyed by env path
|
||||
self.included_concretized_user_specs = {} # user specs from last concretize's included env
|
||||
self.included_specs_by_hash = {} # concretized specs by hash from the included envs
|
||||
|
||||
self.invalidate_repository_cache()
|
||||
self._previous_active = None # previously active environment
|
||||
if not re_read:
|
||||
@@ -1033,6 +1187,43 @@ def scope_name(self):
|
||||
"""Name of the config scope of this environment's manifest file."""
|
||||
return self.manifest.scope_name
|
||||
|
||||
def include_concrete_envs(self):
|
||||
"""Copy and save the included envs' specs internally"""
|
||||
|
||||
root_hash_seen = set()
|
||||
concrete_hash_seen = set()
|
||||
self.included_concrete_spec_data = {}
|
||||
|
||||
for env_path in self.included_concrete_envs:
|
||||
# Check that environment exists
|
||||
if not is_env_dir(env_path):
|
||||
raise SpackEnvironmentError(f"Unable to find env at {env_path}")
|
||||
|
||||
env = Environment(env_path)
|
||||
self.included_concrete_spec_data[env_path] = {"roots": [], "concrete_specs": {}}
|
||||
|
||||
# Copy unique root specs from env
|
||||
for root_dict in env._concrete_roots_dict():
|
||||
if root_dict["hash"] not in root_hash_seen:
|
||||
self.included_concrete_spec_data[env_path]["roots"].append(root_dict)
|
||||
root_hash_seen.add(root_dict["hash"])
|
||||
|
||||
# Copy unique concrete specs from env
|
||||
for dag_hash, spec_details in env._concrete_specs_dict().items():
|
||||
if dag_hash not in concrete_hash_seen:
|
||||
self.included_concrete_spec_data[env_path]["concrete_specs"].update(
|
||||
{dag_hash: spec_details}
|
||||
)
|
||||
concrete_hash_seen.add(dag_hash)
|
||||
|
||||
# Copy transitive include data
|
||||
transitive = env.included_concrete_spec_data
|
||||
if transitive:
|
||||
self.included_concrete_spec_data[env_path]["include_concrete"] = transitive
|
||||
|
||||
self._read_lockfile_dict(self._to_lockfile_dict())
|
||||
self.write()
|
||||
|
||||
def destroy(self):
|
||||
"""Remove this environment from Spack entirely."""
|
||||
shutil.rmtree(self.path)
|
||||
@@ -1232,6 +1423,10 @@ def concretize(self, force=False, tests=False):
|
||||
for spec in set(self.concretized_user_specs) - set(self.user_specs):
|
||||
self.deconcretize(spec, concrete=False)
|
||||
|
||||
# If a combined env, check updated spec is in the linked envs
|
||||
if self.included_concrete_envs:
|
||||
self.include_concrete_envs()
|
||||
|
||||
# Pick the right concretization strategy
|
||||
if self.unify == "when_possible":
|
||||
return self._concretize_together_where_possible(tests=tests)
|
||||
@@ -1704,8 +1899,14 @@ def _partition_roots_by_install_status(self):
|
||||
of per spec."""
|
||||
installed, uninstalled = [], []
|
||||
with spack.store.STORE.db.read_transaction():
|
||||
for concretized_hash in self.concretized_order:
|
||||
spec = self.specs_by_hash[concretized_hash]
|
||||
for concretized_hash in self.all_concretized_orders():
|
||||
if concretized_hash in self.specs_by_hash:
|
||||
spec = self.specs_by_hash[concretized_hash]
|
||||
else:
|
||||
for env_path in self.included_specs_by_hash.keys():
|
||||
if concretized_hash in self.included_specs_by_hash[env_path]:
|
||||
spec = self.included_specs_by_hash[env_path][concretized_hash]
|
||||
break
|
||||
if not spec.installed or (
|
||||
spec.satisfies("dev_path=*") or spec.satisfies("^dev_path=*")
|
||||
):
|
||||
@@ -1785,8 +1986,14 @@ def added_specs(self):
|
||||
|
||||
def concretized_specs(self):
|
||||
"""Tuples of (user spec, concrete spec) for all concrete specs."""
|
||||
for s, h in zip(self.concretized_user_specs, self.concretized_order):
|
||||
yield (s, self.specs_by_hash[h])
|
||||
for s, h in zip(self.all_concretized_user_specs(), self.all_concretized_orders()):
|
||||
if h in self.specs_by_hash:
|
||||
yield (s, self.specs_by_hash[h])
|
||||
else:
|
||||
for env_path in self.included_specs_by_hash.keys():
|
||||
if h in self.included_specs_by_hash[env_path]:
|
||||
yield (s, self.included_specs_by_hash[env_path][h])
|
||||
break
|
||||
|
||||
def concrete_roots(self):
|
||||
"""Same as concretized_specs, except it returns the list of concrete
|
||||
@@ -1915,8 +2122,7 @@ def _get_environment_specs(self, recurse_dependencies=True):
|
||||
If these specs appear under different user_specs, only one copy
|
||||
is added to the list returned.
|
||||
"""
|
||||
specs = [self.specs_by_hash[h] for h in self.concretized_order]
|
||||
|
||||
specs = [self.specs_by_hash[h] for h in self.all_concretized_orders()]
|
||||
if recurse_dependencies:
|
||||
specs.extend(
|
||||
traverse.traverse_nodes(
|
||||
@@ -1926,16 +2132,23 @@ def _get_environment_specs(self, recurse_dependencies=True):
|
||||
|
||||
return specs
|
||||
|
||||
def _to_lockfile_dict(self):
|
||||
"""Create a dictionary to store a lockfile for this environment."""
|
||||
def _concrete_specs_dict(self):
|
||||
concrete_specs = {}
|
||||
for s in traverse.traverse_nodes(self.specs_by_hash.values(), key=traverse.by_dag_hash):
|
||||
spec_dict = s.node_dict_with_hashes(hash=ht.dag_hash)
|
||||
# Assumes no legacy formats, since this was just created.
|
||||
spec_dict[ht.dag_hash.name] = s.dag_hash()
|
||||
concrete_specs[s.dag_hash()] = spec_dict
|
||||
return concrete_specs
|
||||
|
||||
def _concrete_roots_dict(self):
|
||||
hash_spec_list = zip(self.concretized_order, self.concretized_user_specs)
|
||||
return [{"hash": h, "spec": str(s)} for h, s in hash_spec_list]
|
||||
|
||||
def _to_lockfile_dict(self):
|
||||
"""Create a dictionary to store a lockfile for this environment."""
|
||||
concrete_specs = self._concrete_specs_dict()
|
||||
root_specs = self._concrete_roots_dict()
|
||||
|
||||
spack_dict = {"version": spack.spack_version}
|
||||
spack_commit = spack.main.get_spack_commit()
|
||||
@@ -1956,36 +2169,81 @@ def _to_lockfile_dict(self):
|
||||
# spack version information
|
||||
"spack": spack_dict,
|
||||
# users specs + hashes are the 'roots' of the environment
|
||||
"roots": [{"hash": h, "spec": str(s)} for h, s in hash_spec_list],
|
||||
"roots": root_specs,
|
||||
# Concrete specs by hash, including dependencies
|
||||
"concrete_specs": concrete_specs,
|
||||
}
|
||||
|
||||
if self.included_concrete_envs:
|
||||
data[included_concrete_name] = self.included_concrete_spec_data
|
||||
|
||||
return data
|
||||
|
||||
def _read_lockfile(self, file_or_json):
|
||||
"""Read a lockfile from a file or from a raw string."""
|
||||
lockfile_dict = sjson.load(file_or_json)
|
||||
self._read_lockfile_dict(lockfile_dict)
|
||||
return lockfile_dict["_meta"]["lockfile-version"]
|
||||
return lockfile_dict
|
||||
|
||||
def set_included_concretized_user_specs(
|
||||
self,
|
||||
env_name: str,
|
||||
env_info: Dict[str, Dict[str, Any]],
|
||||
included_json_specs_by_hash: Dict[str, Dict[str, Any]],
|
||||
) -> Dict[str, Dict[str, Any]]:
|
||||
"""Sets all of the concretized user specs from included environments
|
||||
to include those from nested included environments.
|
||||
|
||||
Args:
|
||||
env_name: the name (technically the path) of the included environment
|
||||
env_info: included concrete environment data
|
||||
included_json_specs_by_hash: concrete spec data keyed by hash
|
||||
|
||||
Returns: updated specs_by_hash
|
||||
"""
|
||||
self.included_concretized_order[env_name] = []
|
||||
self.included_concretized_user_specs[env_name] = []
|
||||
|
||||
def add_specs(name, info, specs_by_hash):
|
||||
# Add specs from the environment as well as any of its nested
|
||||
# environments.
|
||||
for root_info in info["roots"]:
|
||||
self.included_concretized_order[name].append(root_info["hash"])
|
||||
self.included_concretized_user_specs[name].append(Spec(root_info["spec"]))
|
||||
if "concrete_specs" in info:
|
||||
specs_by_hash.update(info["concrete_specs"])
|
||||
|
||||
if included_concrete_name in info:
|
||||
for included_name, included_info in info[included_concrete_name].items():
|
||||
if included_name not in self.included_concretized_order:
|
||||
self.included_concretized_order[included_name] = []
|
||||
self.included_concretized_user_specs[included_name] = []
|
||||
add_specs(included_name, included_info, specs_by_hash)
|
||||
|
||||
add_specs(env_name, env_info, included_json_specs_by_hash)
|
||||
return included_json_specs_by_hash
|
||||
|
||||
def _read_lockfile_dict(self, d):
|
||||
"""Read a lockfile dictionary into this environment."""
|
||||
self.specs_by_hash = {}
|
||||
self.included_specs_by_hash = {}
|
||||
self.included_concretized_user_specs = {}
|
||||
self.included_concretized_order = {}
|
||||
|
||||
roots = d["roots"]
|
||||
self.concretized_user_specs = [Spec(r["spec"]) for r in roots]
|
||||
self.concretized_order = [r["hash"] for r in roots]
|
||||
json_specs_by_hash = d["concrete_specs"]
|
||||
included_json_specs_by_hash = {}
|
||||
|
||||
# Track specs by their lockfile key. Currently spack uses the finest
|
||||
# grained hash as the lockfile key, while older formats used the build
|
||||
# hash or a previous incarnation of the DAG hash (one that did not
|
||||
# include build deps or package hash).
|
||||
specs_by_hash = {}
|
||||
if included_concrete_name in d:
|
||||
for env_name, env_info in d[included_concrete_name].items():
|
||||
included_json_specs_by_hash.update(
|
||||
self.set_included_concretized_user_specs(
|
||||
env_name, env_info, included_json_specs_by_hash
|
||||
)
|
||||
)
|
||||
|
||||
# Track specs by their DAG hash, allows handling DAG hash collisions
|
||||
first_seen = {}
|
||||
current_lockfile_format = d["_meta"]["lockfile-version"]
|
||||
try:
|
||||
reader = READER_CLS[current_lockfile_format]
|
||||
@@ -1998,6 +2256,39 @@ def _read_lockfile_dict(self, d):
|
||||
msg += " You need to use a newer Spack version."
|
||||
raise SpackEnvironmentError(msg)
|
||||
|
||||
first_seen, self.concretized_order = self.filter_specs(
|
||||
reader, json_specs_by_hash, self.concretized_order
|
||||
)
|
||||
|
||||
for spec_dag_hash in self.concretized_order:
|
||||
self.specs_by_hash[spec_dag_hash] = first_seen[spec_dag_hash]
|
||||
|
||||
if any(self.included_concretized_order.values()):
|
||||
first_seen = {}
|
||||
|
||||
for env_name, concretized_order in self.included_concretized_order.items():
|
||||
filtered_spec, self.included_concretized_order[env_name] = self.filter_specs(
|
||||
reader, included_json_specs_by_hash, concretized_order
|
||||
)
|
||||
first_seen.update(filtered_spec)
|
||||
|
||||
for env_path, spec_hashes in self.included_concretized_order.items():
|
||||
self.included_specs_by_hash[env_path] = {}
|
||||
for spec_dag_hash in spec_hashes:
|
||||
self.included_specs_by_hash[env_path].update(
|
||||
{spec_dag_hash: first_seen[spec_dag_hash]}
|
||||
)
|
||||
|
||||
def filter_specs(self, reader, json_specs_by_hash, order_concretized):
|
||||
# Track specs by their lockfile key. Currently spack uses the finest
|
||||
# grained hash as the lockfile key, while older formats used the build
|
||||
# hash or a previous incarnation of the DAG hash (one that did not
|
||||
# include build deps or package hash).
|
||||
specs_by_hash = {}
|
||||
|
||||
# Track specs by their DAG hash, allows handling DAG hash collisions
|
||||
first_seen = {}
|
||||
|
||||
# First pass: Put each spec in the map ignoring dependencies
|
||||
for lockfile_key, node_dict in json_specs_by_hash.items():
|
||||
spec = reader.from_node_dict(node_dict)
|
||||
@@ -2020,7 +2311,8 @@ def _read_lockfile_dict(self, d):
|
||||
# keep. This is only required as long as we support older lockfile
|
||||
# formats where the mapping from DAG hash to lockfile key is possibly
|
||||
# one-to-many.
|
||||
for lockfile_key in self.concretized_order:
|
||||
|
||||
for lockfile_key in order_concretized:
|
||||
for s in specs_by_hash[lockfile_key].traverse():
|
||||
if s.dag_hash() not in first_seen:
|
||||
first_seen[s.dag_hash()] = s
|
||||
@@ -2028,12 +2320,10 @@ def _read_lockfile_dict(self, d):
|
||||
# Now make sure concretized_order and our internal specs dict
|
||||
# contains the keys used by modern spack (i.e. the dag_hash
|
||||
# that includes build deps and package hash).
|
||||
self.concretized_order = [
|
||||
specs_by_hash[h_key].dag_hash() for h_key in self.concretized_order
|
||||
]
|
||||
|
||||
for spec_dag_hash in self.concretized_order:
|
||||
self.specs_by_hash[spec_dag_hash] = first_seen[spec_dag_hash]
|
||||
order_concretized = [specs_by_hash[h_key].dag_hash() for h_key in order_concretized]
|
||||
|
||||
return first_seen, order_concretized
|
||||
|
||||
def write(self, regenerate: bool = True) -> None:
|
||||
"""Writes an in-memory environment to its location on disk.
|
||||
@@ -2046,7 +2336,7 @@ def write(self, regenerate: bool = True) -> None:
|
||||
regenerate: regenerate views and run post-write hooks as well as writing if True.
|
||||
"""
|
||||
self.manifest_uptodate_or_warn()
|
||||
if self.specs_by_hash:
|
||||
if self.specs_by_hash or self.included_concrete_envs:
|
||||
self.ensure_env_directory_exists(dot_env=True)
|
||||
self.update_environment_repository()
|
||||
self.manifest.flush()
|
||||
@@ -2545,6 +2835,19 @@ def override_user_spec(self, user_spec: str, idx: int) -> None:
|
||||
raise SpackEnvironmentError(msg) from e
|
||||
self.changed = True
|
||||
|
||||
def set_include_concrete(self, include_concrete: List[str]) -> None:
|
||||
"""Sets the included concrete environments in the manifest to the value(s) passed as input.
|
||||
|
||||
Args:
|
||||
include_concrete: list of already existing concrete environments to include
|
||||
"""
|
||||
self.pristine_configuration[included_concrete_name] = []
|
||||
|
||||
for env_path in include_concrete:
|
||||
self.pristine_configuration[included_concrete_name].append(env_path)
|
||||
|
||||
self.changed = True
|
||||
|
||||
def add_definition(self, user_spec: str, list_name: str) -> None:
|
||||
"""Appends a user spec to the first active definition matching the name passed as argument.
|
||||
|
||||
@@ -2728,54 +3031,56 @@ def included_config_scopes(self) -> List[spack.config.ConfigScope]:
|
||||
for i, config_path in enumerate(reversed(includes)):
|
||||
# allow paths to contain spack config/environment variables, etc.
|
||||
config_path = substitute_path_variables(config_path)
|
||||
|
||||
include_url = urllib.parse.urlparse(config_path)
|
||||
|
||||
# Transform file:// URLs to direct includes.
|
||||
if include_url.scheme == "file":
|
||||
config_path = urllib.request.url2pathname(include_url.path)
|
||||
# If scheme is not valid, config_path is not a url
|
||||
# of a type Spack is generally aware
|
||||
if spack.util.url.validate_scheme(include_url.scheme):
|
||||
# Transform file:// URLs to direct includes.
|
||||
if include_url.scheme == "file":
|
||||
config_path = urllib.request.url2pathname(include_url.path)
|
||||
|
||||
# Any other URL should be fetched.
|
||||
elif include_url.scheme in ("http", "https", "ftp"):
|
||||
# Stage any remote configuration file(s)
|
||||
staged_configs = (
|
||||
os.listdir(self.config_stage_dir)
|
||||
if os.path.exists(self.config_stage_dir)
|
||||
else []
|
||||
)
|
||||
remote_path = urllib.request.url2pathname(include_url.path)
|
||||
basename = os.path.basename(remote_path)
|
||||
if basename in staged_configs:
|
||||
# Do NOT re-stage configuration files over existing
|
||||
# ones with the same name since there is a risk of
|
||||
# losing changes (e.g., from 'spack config update').
|
||||
tty.warn(
|
||||
"Will not re-stage configuration from {0} to avoid "
|
||||
"losing changes to the already staged file of the "
|
||||
"same name.".format(remote_path)
|
||||
# Any other URL should be fetched.
|
||||
elif include_url.scheme in ("http", "https", "ftp"):
|
||||
# Stage any remote configuration file(s)
|
||||
staged_configs = (
|
||||
os.listdir(self.config_stage_dir)
|
||||
if os.path.exists(self.config_stage_dir)
|
||||
else []
|
||||
)
|
||||
|
||||
# Recognize the configuration stage directory
|
||||
# is flattened to ensure a single copy of each
|
||||
# configuration file.
|
||||
config_path = self.config_stage_dir
|
||||
if basename.endswith(".yaml"):
|
||||
config_path = os.path.join(config_path, basename)
|
||||
else:
|
||||
staged_path = spack.config.fetch_remote_configs(
|
||||
config_path, str(self.config_stage_dir), skip_existing=True
|
||||
)
|
||||
if not staged_path:
|
||||
raise SpackEnvironmentError(
|
||||
"Unable to fetch remote configuration {0}".format(config_path)
|
||||
remote_path = urllib.request.url2pathname(include_url.path)
|
||||
basename = os.path.basename(remote_path)
|
||||
if basename in staged_configs:
|
||||
# Do NOT re-stage configuration files over existing
|
||||
# ones with the same name since there is a risk of
|
||||
# losing changes (e.g., from 'spack config update').
|
||||
tty.warn(
|
||||
"Will not re-stage configuration from {0} to avoid "
|
||||
"losing changes to the already staged file of the "
|
||||
"same name.".format(remote_path)
|
||||
)
|
||||
config_path = staged_path
|
||||
|
||||
elif include_url.scheme:
|
||||
raise ValueError(
|
||||
f"Unsupported URL scheme ({include_url.scheme}) for "
|
||||
f"environment include: {config_path}"
|
||||
)
|
||||
# Recognize the configuration stage directory
|
||||
# is flattened to ensure a single copy of each
|
||||
# configuration file.
|
||||
config_path = self.config_stage_dir
|
||||
if basename.endswith(".yaml"):
|
||||
config_path = os.path.join(config_path, basename)
|
||||
else:
|
||||
staged_path = spack.config.fetch_remote_configs(
|
||||
config_path, str(self.config_stage_dir), skip_existing=True
|
||||
)
|
||||
if not staged_path:
|
||||
raise SpackEnvironmentError(
|
||||
"Unable to fetch remote configuration {0}".format(config_path)
|
||||
)
|
||||
config_path = staged_path
|
||||
|
||||
elif include_url.scheme:
|
||||
raise ValueError(
|
||||
f"Unsupported URL scheme ({include_url.scheme}) for "
|
||||
f"environment include: {config_path}"
|
||||
)
|
||||
|
||||
# treat relative paths as relative to the environment
|
||||
if not os.path.isabs(config_path):
|
||||
|
@@ -554,7 +554,7 @@ def fetch(self):
|
||||
|
||||
try:
|
||||
response = self._urlopen(self.url)
|
||||
except urllib.error.URLError as e:
|
||||
except (TimeoutError, urllib.error.URLError) as e:
|
||||
# clean up archive on failure.
|
||||
if self.archive_file:
|
||||
os.remove(self.archive_file)
|
||||
|
@@ -10,8 +10,9 @@
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
from typing import Optional
|
||||
from typing import Callable, Dict, Optional
|
||||
|
||||
from llnl.string import comma_or
|
||||
from llnl.util import tty
|
||||
from llnl.util.filesystem import (
|
||||
mkdirp,
|
||||
@@ -49,19 +50,20 @@
|
||||
_projections_path = ".spack/projections.yaml"
|
||||
|
||||
|
||||
def view_symlink(src, dst, **kwargs):
|
||||
# keyword arguments are irrelevant
|
||||
# here to fit required call signature
|
||||
LinkCallbackType = Callable[[str, str, "FilesystemView", Optional["spack.spec.Spec"]], None]
|
||||
|
||||
|
||||
def view_symlink(src: str, dst: str, *args, **kwargs) -> None:
|
||||
symlink(src, dst)
|
||||
|
||||
|
||||
def view_hardlink(src, dst, **kwargs):
|
||||
# keyword arguments are irrelevant
|
||||
# here to fit required call signature
|
||||
def view_hardlink(src: str, dst: str, *args, **kwargs) -> None:
|
||||
os.link(src, dst)
|
||||
|
||||
|
||||
def view_copy(src: str, dst: str, view, spec: Optional[spack.spec.Spec] = None):
|
||||
def view_copy(
|
||||
src: str, dst: str, view: "FilesystemView", spec: Optional["spack.spec.Spec"] = None
|
||||
) -> None:
|
||||
"""
|
||||
Copy a file from src to dst.
|
||||
|
||||
@@ -104,27 +106,40 @@ def view_copy(src: str, dst: str, view, spec: Optional[spack.spec.Spec] = None):
|
||||
tty.debug(f"Can't change the permissions for {dst}")
|
||||
|
||||
|
||||
def view_func_parser(parsed_name):
|
||||
# What method are we using for this view
|
||||
if parsed_name in ("hardlink", "hard"):
|
||||
#: supported string values for `link_type` in an env, mapped to canonical values
|
||||
_LINK_TYPES = {
|
||||
"hardlink": "hardlink",
|
||||
"hard": "hardlink",
|
||||
"copy": "copy",
|
||||
"relocate": "copy",
|
||||
"add": "symlink",
|
||||
"symlink": "symlink",
|
||||
"soft": "symlink",
|
||||
}
|
||||
|
||||
_VALID_LINK_TYPES = sorted(set(_LINK_TYPES.values()))
|
||||
|
||||
|
||||
def canonicalize_link_type(link_type: str) -> str:
|
||||
"""Return canonical"""
|
||||
canonical = _LINK_TYPES.get(link_type)
|
||||
if not canonical:
|
||||
raise ValueError(
|
||||
f"Invalid link type: '{link_type}. Must be one of {comma_or(_VALID_LINK_TYPES)}'"
|
||||
)
|
||||
return canonical
|
||||
|
||||
|
||||
def function_for_link_type(link_type: str) -> LinkCallbackType:
|
||||
link_type = canonicalize_link_type(link_type)
|
||||
if link_type == "hardlink":
|
||||
return view_hardlink
|
||||
elif parsed_name in ("copy", "relocate"):
|
||||
return view_copy
|
||||
elif parsed_name in ("add", "symlink", "soft"):
|
||||
elif link_type == "symlink":
|
||||
return view_symlink
|
||||
else:
|
||||
raise ValueError(f"invalid link type for view: '{parsed_name}'")
|
||||
elif link_type == "copy":
|
||||
return view_copy
|
||||
|
||||
|
||||
def inverse_view_func_parser(view_type):
|
||||
# get string based on view type
|
||||
if view_type is view_hardlink:
|
||||
link_name = "hardlink"
|
||||
elif view_type is view_copy:
|
||||
link_name = "copy"
|
||||
else:
|
||||
link_name = "symlink"
|
||||
return link_name
|
||||
assert False, "invalid link type" # need mypy Literal values
|
||||
|
||||
|
||||
class FilesystemView:
|
||||
@@ -140,7 +155,16 @@ class FilesystemView:
|
||||
directory structure.
|
||||
"""
|
||||
|
||||
def __init__(self, root, layout, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
layout: "spack.directory_layout.DirectoryLayout",
|
||||
*,
|
||||
projections: Optional[Dict] = None,
|
||||
ignore_conflicts: bool = False,
|
||||
verbose: bool = False,
|
||||
link_type: str = "symlink",
|
||||
):
|
||||
"""
|
||||
Initialize a filesystem view under the given `root` directory with
|
||||
corresponding directory `layout`.
|
||||
@@ -149,15 +173,14 @@ def __init__(self, root, layout, **kwargs):
|
||||
"""
|
||||
self._root = root
|
||||
self.layout = layout
|
||||
self.projections = {} if projections is None else projections
|
||||
|
||||
self.projections = kwargs.get("projections", {})
|
||||
|
||||
self.ignore_conflicts = kwargs.get("ignore_conflicts", False)
|
||||
self.verbose = kwargs.get("verbose", False)
|
||||
self.ignore_conflicts = ignore_conflicts
|
||||
self.verbose = verbose
|
||||
|
||||
# Setup link function to include view
|
||||
link_func = kwargs.get("link", view_symlink)
|
||||
self.link = ft.partial(link_func, view=self)
|
||||
self.link_type = link_type
|
||||
self.link = ft.partial(function_for_link_type(link_type), view=self)
|
||||
|
||||
def add_specs(self, *specs, **kwargs):
|
||||
"""
|
||||
@@ -255,8 +278,24 @@ class YamlFilesystemView(FilesystemView):
|
||||
Filesystem view to work with a yaml based directory layout.
|
||||
"""
|
||||
|
||||
def __init__(self, root, layout, **kwargs):
|
||||
super().__init__(root, layout, **kwargs)
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
layout: "spack.directory_layout.DirectoryLayout",
|
||||
*,
|
||||
projections: Optional[Dict] = None,
|
||||
ignore_conflicts: bool = False,
|
||||
verbose: bool = False,
|
||||
link_type: str = "symlink",
|
||||
):
|
||||
super().__init__(
|
||||
root,
|
||||
layout,
|
||||
projections=projections,
|
||||
ignore_conflicts=ignore_conflicts,
|
||||
verbose=verbose,
|
||||
link_type=link_type,
|
||||
)
|
||||
|
||||
# Super class gets projections from the kwargs
|
||||
# YAML specific to get projections from YAML file
|
||||
@@ -638,9 +677,6 @@ class SimpleFilesystemView(FilesystemView):
|
||||
"""A simple and partial implementation of FilesystemView focused on performance and immutable
|
||||
views, where specs cannot be removed after they were added."""
|
||||
|
||||
def __init__(self, root, layout, **kwargs):
|
||||
super().__init__(root, layout, **kwargs)
|
||||
|
||||
def _sanity_check_view_projection(self, specs):
|
||||
"""A very common issue is that we end up with two specs of the same package, that project
|
||||
to the same prefix. We want to catch that as early as possible and give a sensible error to
|
||||
|
@@ -488,6 +488,7 @@ def _process_binary_cache_tarball(
|
||||
|
||||
with timer.measure("install"), spack.util.path.filter_padding():
|
||||
binary_distribution.extract_tarball(pkg.spec, download_result, force=False, timer=timer)
|
||||
pkg.windows_establish_runtime_linkage()
|
||||
|
||||
if hasattr(pkg, "_post_buildcache_install_hook"):
|
||||
pkg._post_buildcache_install_hook()
|
||||
|
@@ -11,7 +11,7 @@
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from http.client import HTTPResponse
|
||||
from typing import NamedTuple, Tuple
|
||||
from typing import List, NamedTuple, Tuple
|
||||
from urllib.request import Request
|
||||
|
||||
import llnl.util.tty as tty
|
||||
@@ -27,6 +27,7 @@
|
||||
import spack.stage
|
||||
import spack.traverse
|
||||
import spack.util.crypto
|
||||
import spack.util.url
|
||||
|
||||
from .image import Digest, ImageReference
|
||||
|
||||
@@ -69,6 +70,42 @@ def with_query_param(url: str, param: str, value: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def list_tags(ref: ImageReference, _urlopen: spack.oci.opener.MaybeOpen = None) -> List[str]:
|
||||
"""Retrieves the list of tags associated with an image, handling pagination."""
|
||||
_urlopen = _urlopen or spack.oci.opener.urlopen
|
||||
tags = set()
|
||||
fetch_url = ref.tags_url()
|
||||
|
||||
while True:
|
||||
# Fetch tags
|
||||
request = Request(url=fetch_url)
|
||||
response = _urlopen(request)
|
||||
spack.oci.opener.ensure_status(request, response, 200)
|
||||
tags.update(json.load(response)["tags"])
|
||||
|
||||
# Check for pagination
|
||||
link_header = response.headers["Link"]
|
||||
|
||||
if link_header is None:
|
||||
break
|
||||
|
||||
tty.debug(f"OCI tag pagination: {link_header}")
|
||||
|
||||
rel_next_value = spack.util.url.parse_link_rel_next(link_header)
|
||||
|
||||
if rel_next_value is None:
|
||||
break
|
||||
|
||||
rel_next = urllib.parse.urlparse(rel_next_value)
|
||||
|
||||
if rel_next.scheme not in ("https", ""):
|
||||
break
|
||||
|
||||
fetch_url = ref.endpoint(rel_next_value)
|
||||
|
||||
return sorted(tags)
|
||||
|
||||
|
||||
def upload_blob(
|
||||
ref: ImageReference,
|
||||
file: str,
|
||||
|
@@ -418,18 +418,27 @@ def ensure_status(request: urllib.request.Request, response: HTTPResponse, statu
|
||||
)
|
||||
|
||||
|
||||
def default_retry(f, retries: int = 3, sleep=None):
|
||||
def default_retry(f, retries: int = 5, sleep=None):
|
||||
sleep = sleep or time.sleep
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
for i in range(retries):
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except urllib.error.HTTPError as e:
|
||||
except (urllib.error.URLError, TimeoutError) as e:
|
||||
# Retry on internal server errors, and rate limit errors
|
||||
# Potentially this could take into account the Retry-After header
|
||||
# if registries support it
|
||||
if i + 1 != retries and (500 <= e.code < 600 or e.code == 429):
|
||||
if i + 1 != retries and (
|
||||
(
|
||||
isinstance(e, urllib.error.HTTPError)
|
||||
and (500 <= e.code < 600 or e.code == 429)
|
||||
)
|
||||
or (
|
||||
isinstance(e, urllib.error.URLError) and isinstance(e.reason, TimeoutError)
|
||||
)
|
||||
or isinstance(e, TimeoutError)
|
||||
):
|
||||
# Exponential backoff
|
||||
sleep(2**i)
|
||||
continue
|
||||
|
@@ -143,6 +143,7 @@ def __init__(self):
|
||||
"12": "monterey",
|
||||
"13": "ventura",
|
||||
"14": "sonoma",
|
||||
"15": "sequoia",
|
||||
}
|
||||
|
||||
version = macos_version()
|
||||
|
@@ -161,7 +161,11 @@ def windows_establish_runtime_linkage(self):
|
||||
|
||||
Performs symlinking to incorporate rpath dependencies to Windows runtime search paths
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
# If spec is an external, we should not be modifying its bin directory, as we would
|
||||
# be doing in this method
|
||||
# Spack should in general not modify things it has not installed
|
||||
# we can reasonably expect externals to have their link interface properly established
|
||||
if sys.platform == "win32" and not self.spec.external:
|
||||
self.win_rpath.add_library_dependent(*self.win_add_library_dependent())
|
||||
self.win_rpath.add_rpath(*self.win_add_rpath())
|
||||
self.win_rpath.establish_link()
|
||||
@@ -195,10 +199,10 @@ def __init__(cls, name, bases, attr_dict):
|
||||
# assumed to be detectable
|
||||
if hasattr(cls, "executables") or hasattr(cls, "libraries"):
|
||||
# Append a tag to each detectable package, so that finding them is faster
|
||||
if hasattr(cls, "tags"):
|
||||
getattr(cls, "tags").append(DetectablePackageMeta.TAG)
|
||||
else:
|
||||
if not hasattr(cls, "tags"):
|
||||
setattr(cls, "tags", [DetectablePackageMeta.TAG])
|
||||
elif DetectablePackageMeta.TAG not in cls.tags:
|
||||
cls.tags.append(DetectablePackageMeta.TAG)
|
||||
|
||||
@classmethod
|
||||
def platform_executables(cls):
|
||||
@@ -1115,10 +1119,9 @@ def _make_stage(self):
|
||||
if not link_format:
|
||||
link_format = "build-{arch}-{hash:7}"
|
||||
stage_link = self.spec.format_path(link_format)
|
||||
return DevelopStage(compute_stage_name(self.spec), dev_path, stage_link)
|
||||
|
||||
# To fetch the current version
|
||||
source_stage = self._make_root_stage(self.fetcher)
|
||||
source_stage = DevelopStage(compute_stage_name(self.spec), dev_path, stage_link)
|
||||
else:
|
||||
source_stage = self._make_root_stage(self.fetcher)
|
||||
|
||||
# all_stages is source + resources + patches
|
||||
all_stages = StageComposite()
|
||||
@@ -1240,7 +1243,7 @@ def install_test_root(self):
|
||||
"""Return the install test root directory."""
|
||||
tty.warn(
|
||||
"The 'pkg.install_test_root' property is deprecated with removal "
|
||||
"expected v0.22. Use 'install_test_root(pkg)' instead."
|
||||
"expected v0.23. Use 'install_test_root(pkg)' instead."
|
||||
)
|
||||
return install_test_root(self)
|
||||
|
||||
@@ -1447,10 +1450,8 @@ def do_fetch(self, mirror_only=False):
|
||||
return
|
||||
|
||||
checksum = spack.config.get("config:checksum")
|
||||
fetch = self.stage.needs_fetching
|
||||
if (
|
||||
checksum
|
||||
and fetch
|
||||
and (self.version not in self.versions)
|
||||
and (not isinstance(self.version, GitVersion))
|
||||
):
|
||||
@@ -1557,13 +1558,11 @@ def do_patch(self):
|
||||
tty.debug("Patching failed last time. Restaging.")
|
||||
self.stage.restage()
|
||||
else:
|
||||
# develop specs/ DIYStages may have patch failures but
|
||||
# should never be restaged
|
||||
msg = (
|
||||
"A patch failure was detected in %s." % self.name
|
||||
+ " Build errors may occur due to this."
|
||||
# develop specs may have patch failures but should never be restaged
|
||||
tty.warn(
|
||||
f"A patch failure was detected in {self.name}."
|
||||
" Build errors may occur due to this."
|
||||
)
|
||||
tty.warn(msg)
|
||||
return
|
||||
|
||||
# If this file exists, then we already applied all the patches.
|
||||
@@ -1898,7 +1897,7 @@ def cache_extra_test_sources(self, srcs):
|
||||
"""
|
||||
msg = (
|
||||
"'pkg.cache_extra_test_sources(srcs) is deprecated with removal "
|
||||
"expected in v0.22. Use 'cache_extra_test_sources(pkg, srcs)' "
|
||||
"expected in v0.23. Use 'cache_extra_test_sources(pkg, srcs)' "
|
||||
"instead."
|
||||
)
|
||||
warnings.warn(msg)
|
||||
@@ -2446,9 +2445,18 @@ def rpath(self):
|
||||
|
||||
# on Windows, libraries of runtime interest are typically
|
||||
# stored in the bin directory
|
||||
# Do not include Windows system libraries in the rpath interface
|
||||
# these libraries are handled automatically by VS/VCVARS and adding
|
||||
# Spack derived system libs into the link path or address space of a program
|
||||
# can result in conflicting versions, which makes Spack packages less useable
|
||||
if sys.platform == "win32":
|
||||
rpaths = [self.prefix.bin]
|
||||
rpaths.extend(d.prefix.bin for d in deps if os.path.isdir(d.prefix.bin))
|
||||
rpaths.extend(
|
||||
d.prefix.bin
|
||||
for d in deps
|
||||
if os.path.isdir(d.prefix.bin)
|
||||
and "windows-system" not in getattr(d.package, "tags", [])
|
||||
)
|
||||
else:
|
||||
rpaths = [self.prefix.lib, self.prefix.lib64]
|
||||
rpaths.extend(d.prefix.lib for d in deps if os.path.isdir(d.prefix.lib))
|
||||
|
@@ -10,6 +10,7 @@
|
||||
import archspec.cpu
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.symlink import readlink
|
||||
|
||||
import spack.target
|
||||
import spack.version
|
||||
@@ -133,7 +134,7 @@ def craype_type_and_version(cls):
|
||||
# Take the default version from known symlink path
|
||||
default_path = os.path.join(craype_dir, "default")
|
||||
if os.path.islink(default_path):
|
||||
version = spack.version.Version(os.readlink(default_path))
|
||||
version = spack.version.Version(readlink(default_path))
|
||||
return (craype_type, version)
|
||||
|
||||
# If no default version, sort available versions and return latest
|
||||
|
@@ -16,7 +16,7 @@
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.lang import memoized
|
||||
from llnl.util.symlink import symlink
|
||||
from llnl.util.symlink import readlink, symlink
|
||||
|
||||
import spack.paths
|
||||
import spack.platforms
|
||||
@@ -25,6 +25,7 @@
|
||||
import spack.store
|
||||
import spack.util.elf as elf
|
||||
import spack.util.executable as executable
|
||||
import spack.util.path
|
||||
|
||||
from .relocate_text import BinaryFilePrefixReplacer, TextFilePrefixReplacer
|
||||
|
||||
@@ -565,7 +566,7 @@ def make_link_relative(new_links, orig_links):
|
||||
orig_links (list): original links
|
||||
"""
|
||||
for new_link, orig_link in zip(new_links, orig_links):
|
||||
target = os.readlink(orig_link)
|
||||
target = readlink(orig_link)
|
||||
relative_target = os.path.relpath(target, os.path.dirname(orig_link))
|
||||
os.unlink(new_link)
|
||||
symlink(relative_target, new_link)
|
||||
@@ -613,7 +614,7 @@ def relocate_links(links, prefix_to_prefix):
|
||||
"""Relocate links to a new install prefix."""
|
||||
regex = re.compile("|".join(re.escape(p) for p in prefix_to_prefix.keys()))
|
||||
for link in links:
|
||||
old_target = os.readlink(link)
|
||||
old_target = readlink(link)
|
||||
match = regex.match(old_target)
|
||||
|
||||
# No match.
|
||||
|
@@ -241,7 +241,7 @@ def get_all_package_diffs(type, rev1="HEAD^1", rev2="HEAD"):
|
||||
|
||||
Arguments:
|
||||
|
||||
type (str): String containing one or more of 'A', 'B', 'C'
|
||||
type (str): String containing one or more of 'A', 'R', 'C'
|
||||
rev1 (str): Revision to compare against, default is 'HEAD^'
|
||||
rev2 (str): Revision to compare to rev1, default is 'HEAD'
|
||||
|
||||
@@ -264,7 +264,7 @@ def get_all_package_diffs(type, rev1="HEAD^1", rev2="HEAD"):
|
||||
lines = [] if not out else re.split(r"\s+", out)
|
||||
changed = set()
|
||||
for path in lines:
|
||||
pkg_name, _, _ = path.partition(os.sep)
|
||||
pkg_name, _, _ = path.partition("/")
|
||||
if pkg_name not in added and pkg_name not in removed:
|
||||
changed.add(pkg_name)
|
||||
|
||||
|
@@ -58,7 +58,8 @@
|
||||
# Initialize data structures common to each phase's report.
|
||||
CDASH_PHASES = set(MAP_PHASES_TO_CDASH.values())
|
||||
CDASH_PHASES.add("update")
|
||||
|
||||
# CDash request timeout in seconds
|
||||
SPACK_CDASH_TIMEOUT = 45
|
||||
|
||||
CDashConfiguration = collections.namedtuple(
|
||||
"CDashConfiguration", ["upload_url", "packages", "build", "site", "buildstamp", "track"]
|
||||
@@ -447,7 +448,7 @@ def upload(self, filename):
|
||||
# By default, urllib2 only support GET and POST.
|
||||
# CDash expects this file to be uploaded via PUT.
|
||||
request.get_method = lambda: "PUT"
|
||||
response = opener.open(request)
|
||||
response = opener.open(request, timeout=SPACK_CDASH_TIMEOUT)
|
||||
if self.current_package_name not in self.buildIds:
|
||||
resp_value = response.read()
|
||||
if isinstance(resp_value, bytes):
|
||||
|
@@ -9,7 +9,7 @@
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
|
||||
from llnl.util.symlink import symlink
|
||||
from llnl.util.symlink import readlink, symlink
|
||||
|
||||
import spack.binary_distribution as bindist
|
||||
import spack.error
|
||||
@@ -26,7 +26,7 @@ def _relocate_spliced_links(links, orig_prefix, new_prefix):
|
||||
in our case. This still needs to be called after the copy to destination
|
||||
because it expects the new directory structure to be in place."""
|
||||
for link in links:
|
||||
link_target = os.readlink(os.path.join(orig_prefix, link))
|
||||
link_target = readlink(os.path.join(orig_prefix, link))
|
||||
link_target = re.sub("^" + orig_prefix, new_prefix, link_target)
|
||||
new_link_path = os.path.join(new_prefix, link)
|
||||
os.unlink(new_link_path)
|
||||
|
@@ -13,6 +13,7 @@
|
||||
r"\w[\w-]*": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["spec"],
|
||||
"properties": {"spec": {"type": "string"}, "path": {"type": "string"}},
|
||||
}
|
||||
},
|
||||
|
@@ -35,6 +35,7 @@
|
||||
{
|
||||
"include": {"type": "array", "default": [], "items": {"type": "string"}},
|
||||
"specs": spec_list_schema,
|
||||
"include_concrete": {"type": "array", "default": [], "items": {"type": "string"}},
|
||||
},
|
||||
),
|
||||
}
|
||||
|
@@ -141,7 +141,7 @@
|
||||
"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 "
|
||||
"is deprecated and will be removed in v0.23\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,
|
||||
@@ -197,7 +197,7 @@
|
||||
"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, and "
|
||||
"removed in v0.23.\n\n\tThese preferences will be ignored by Spack, and "
|
||||
"can be set only in the 'all' section of the same file. "
|
||||
"You can run:\n\n\t\t$ spack audit configs\n\n\tto get better diagnostics, "
|
||||
"including files:lines where the deprecated attributes are used.\n\n"
|
||||
|
@@ -314,6 +314,10 @@ def using_libc_compatibility() -> bool:
|
||||
return spack.platforms.host().name == "linux"
|
||||
|
||||
|
||||
def c_compiler_runs(compiler: spack.compiler.Compiler) -> bool:
|
||||
return compiler.compiler_verbose_output is not None
|
||||
|
||||
|
||||
def extend_flag_list(flag_list, new_flags):
|
||||
"""Extend a list of flags, preserving order and precedence.
|
||||
|
||||
@@ -840,8 +844,6 @@ def solve(self, setup, specs, reuse=None, output=None, control=None, allow_depre
|
||||
parent_dir = os.path.dirname(__file__)
|
||||
self.control.load(os.path.join(parent_dir, "concretize.lp"))
|
||||
self.control.load(os.path.join(parent_dir, "heuristic.lp"))
|
||||
if spack.config.CONFIG.get("concretizer:duplicates:strategy", "none") != "none":
|
||||
self.control.load(os.path.join(parent_dir, "heuristic_separate.lp"))
|
||||
self.control.load(os.path.join(parent_dir, "display.lp"))
|
||||
if not setup.concretize_everything:
|
||||
self.control.load(os.path.join(parent_dir, "when_possible.lp"))
|
||||
@@ -1431,16 +1433,14 @@ def condition(
|
||||
# caller, we won't emit partial facts.
|
||||
|
||||
condition_id = next(self._id_counter)
|
||||
self.gen.fact(fn.pkg_fact(required_spec.name, fn.condition(condition_id)))
|
||||
self.gen.fact(fn.condition_reason(condition_id, msg))
|
||||
|
||||
trigger_id = self._get_condition_id(
|
||||
required_spec, cache=self._trigger_cache, body=True, transform=transform_required
|
||||
)
|
||||
self.gen.fact(fn.pkg_fact(required_spec.name, fn.condition(condition_id)))
|
||||
self.gen.fact(fn.condition_reason(condition_id, msg))
|
||||
self.gen.fact(
|
||||
fn.pkg_fact(required_spec.name, fn.condition_trigger(condition_id, trigger_id))
|
||||
)
|
||||
|
||||
if not imposed_spec:
|
||||
return condition_id
|
||||
|
||||
@@ -1649,11 +1649,15 @@ def external_packages(self):
|
||||
if isinstance(reuse_yaml, typing.Mapping):
|
||||
default_include = reuse_yaml.get("include", [])
|
||||
default_exclude = reuse_yaml.get("exclude", [])
|
||||
libc_externals = list(all_libcs())
|
||||
for source in reuse_yaml.get("from", []):
|
||||
if source["type"] != "external":
|
||||
continue
|
||||
|
||||
include = source.get("include", default_include)
|
||||
if include:
|
||||
# Since libcs are implicit externals, we need to implicitly include them
|
||||
include = include + libc_externals
|
||||
exclude = source.get("exclude", default_exclude)
|
||||
spec_filters.append(
|
||||
SpecFilter(
|
||||
@@ -1685,19 +1689,43 @@ def external_packages(self):
|
||||
spack.spec.parse_with_version_concrete(x["spec"]) for x in externals
|
||||
]
|
||||
|
||||
external_specs = []
|
||||
selected_externals = set()
|
||||
if spec_filters:
|
||||
for current_filter in spec_filters:
|
||||
current_filter.factory = lambda: candidate_specs
|
||||
external_specs.extend(current_filter.selected_specs())
|
||||
else:
|
||||
external_specs.extend(candidate_specs)
|
||||
selected_externals.update(current_filter.selected_specs())
|
||||
|
||||
# Emit facts for externals specs. Note that "local_idx" is the index of the spec
|
||||
# in packages:<pkg_name>:externals. This means:
|
||||
#
|
||||
# packages:<pkg_name>:externals[local_idx].spec == spec
|
||||
external_versions = []
|
||||
for local_idx, spec in enumerate(candidate_specs):
|
||||
msg = f"{spec.name} available as external when satisfying {spec}"
|
||||
|
||||
if spec_filters and spec not in selected_externals:
|
||||
continue
|
||||
|
||||
if not spec.versions.concrete:
|
||||
warnings.warn(f"cannot use the external spec {spec}: needs a concrete version")
|
||||
continue
|
||||
|
||||
def external_imposition(input_spec, requirements):
|
||||
return requirements + [
|
||||
fn.attr("external_conditions_hold", input_spec.name, local_idx)
|
||||
]
|
||||
|
||||
try:
|
||||
self.condition(spec, spec, msg=msg, transform_imposed=external_imposition)
|
||||
except (spack.error.SpecError, RuntimeError) as e:
|
||||
warnings.warn(f"while setting up external spec {spec}: {e}")
|
||||
continue
|
||||
external_versions.append((spec.version, local_idx))
|
||||
self.possible_versions[spec.name].add(spec.version)
|
||||
self.gen.newline()
|
||||
|
||||
# Order the external versions to prefer more recent versions
|
||||
# even if specs in packages.yaml are not ordered that way
|
||||
external_versions = [
|
||||
(x.version, external_id) for external_id, x in enumerate(external_specs)
|
||||
]
|
||||
external_versions = [
|
||||
(v, idx, external_id)
|
||||
for idx, (v, external_id) in enumerate(sorted(external_versions, reverse=True))
|
||||
@@ -1707,19 +1735,6 @@ def external_packages(self):
|
||||
DeclaredVersion(version=version, idx=idx, origin=Provenance.EXTERNAL)
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
||||
def external_imposition(input_spec, requirements):
|
||||
return requirements + [
|
||||
fn.attr("external_conditions_hold", input_spec.name, local_idx)
|
||||
]
|
||||
|
||||
self.condition(spec, spec, msg=msg, transform_imposed=external_imposition)
|
||||
self.possible_versions[spec.name].add(spec.version)
|
||||
self.gen.newline()
|
||||
|
||||
self.trigger_rules()
|
||||
self.effect_rules()
|
||||
|
||||
@@ -1872,11 +1887,8 @@ def _spec_clauses(
|
||||
)
|
||||
|
||||
clauses.append(f.variant_value(spec.name, vname, value))
|
||||
|
||||
if variant.propagate:
|
||||
clauses.append(
|
||||
f.variant_propagation_candidate(spec.name, vname, value, spec.name)
|
||||
)
|
||||
clauses.append(f.propagate(spec.name, fn.variant_value(vname, value)))
|
||||
|
||||
# Tell the concretizer that this is a possible value for the
|
||||
# variant, to account for things like int/str values where we
|
||||
@@ -1931,6 +1943,11 @@ def _spec_clauses(
|
||||
for virtual in virtuals:
|
||||
clauses.append(fn.attr("virtual_on_incoming_edges", spec.name, virtual))
|
||||
|
||||
# If the spec is external and concrete, we allow all the libcs on the system
|
||||
if spec.external and spec.concrete and using_libc_compatibility():
|
||||
for libc in self.libcs:
|
||||
clauses.append(fn.attr("compatible_libc", spec.name, libc.name, libc.version))
|
||||
|
||||
# add all clauses from dependencies
|
||||
if transitive:
|
||||
# TODO: Eventually distinguish 2 deps on the same pkg (build and link)
|
||||
@@ -2726,7 +2743,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_propagation_candidate = fn.attr("variant_propagation_candidate")
|
||||
propagate = fn.attr("propagate")
|
||||
|
||||
|
||||
class _Body:
|
||||
@@ -2743,7 +2760,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_propagation_candidate = fn.attr("variant_propagation_candidate")
|
||||
propagate = fn.attr("propagate")
|
||||
|
||||
|
||||
class ProblemInstanceBuilder:
|
||||
@@ -2971,6 +2988,13 @@ class CompilerParser:
|
||||
def __init__(self, configuration) -> None:
|
||||
self.compilers: Set[KnownCompiler] = set()
|
||||
for c in all_compilers_in_config(configuration):
|
||||
if using_libc_compatibility() and not c_compiler_runs(c):
|
||||
tty.debug(
|
||||
f"the C compiler {c.cc} does not exist, or does not run correctly."
|
||||
f" The compiler {c.spec} will not be used during concretization."
|
||||
)
|
||||
continue
|
||||
|
||||
if using_libc_compatibility() and not c.default_libc:
|
||||
warnings.warn(
|
||||
f"cannot detect libc from {c.spec}. The compiler will not be used "
|
||||
@@ -3210,6 +3234,39 @@ def requires(self, impose: str, *, when: str):
|
||||
self.runtime_conditions.add((imposed_spec, when_spec))
|
||||
self.reset()
|
||||
|
||||
def propagate(self, constraint_str: str, *, when: str):
|
||||
msg = "the 'propagate' method can be called only with pkg('*')"
|
||||
assert self.current_package == "*", msg
|
||||
|
||||
when_spec = spack.spec.Spec(when)
|
||||
assert when_spec.name is None, "only anonymous when specs are accepted"
|
||||
|
||||
placeholder = "XXX"
|
||||
node_variable = "node(ID, Package)"
|
||||
when_spec.name = placeholder
|
||||
|
||||
body_clauses = self._setup.spec_clauses(when_spec, body=True)
|
||||
body_str = (
|
||||
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
|
||||
f" not external({node_variable}),\n"
|
||||
f" not runtime(Package)"
|
||||
).replace(f'"{placeholder}"', f"{node_variable}")
|
||||
|
||||
constraint_spec = spack.spec.Spec(constraint_str)
|
||||
assert constraint_spec.name is None, "only anonymous constraint specs are accepted"
|
||||
|
||||
constraint_spec.name = placeholder
|
||||
constraint_clauses = self._setup.spec_clauses(constraint_spec, body=False)
|
||||
for clause in constraint_clauses:
|
||||
if clause.args[0] == "node_compiler_version_satisfies":
|
||||
self._setup.compiler_version_constraints.add(constraint_spec.compiler)
|
||||
args = f'"{constraint_spec.compiler.name}", "{constraint_spec.compiler.versions}"'
|
||||
head_str = f"propagate({node_variable}, node_compiler_version_satisfies({args}))"
|
||||
rule = f"{head_str} :-\n{body_str}.\n\n"
|
||||
self.rules.append(rule)
|
||||
|
||||
self.reset()
|
||||
|
||||
def consume_facts(self):
|
||||
"""Consume the facts collected by this object, and emits rules and
|
||||
facts for the runtimes.
|
||||
|
@@ -811,37 +811,6 @@ node_has_variant(node(ID, Package), Variant) :-
|
||||
pkg_fact(Package, variant(Variant)),
|
||||
attr("node", node(ID, Package)).
|
||||
|
||||
% Variant propagation is forwarded to dependencies
|
||||
attr("variant_propagation_candidate", PackageNode, Variant, Value, Source) :-
|
||||
attr("node", PackageNode),
|
||||
depends_on(ParentNode, PackageNode),
|
||||
attr("variant_value", node(_, Source), Variant, Value),
|
||||
attr("variant_propagation_candidate", ParentNode, Variant, _, Source).
|
||||
|
||||
% 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),
|
||||
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, 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)
|
||||
:- attr("variant_set", node(X, Package), Variant),
|
||||
@@ -919,7 +888,7 @@ variant_not_default(node(ID, Package), Variant, Value)
|
||||
% variants set explicitly on the CLI don't count as non-default
|
||||
not attr("variant_set", node(ID, Package), Variant, Value),
|
||||
% variant values forced by propagation don't count as non-default
|
||||
not attr("variant_propagate", node(ID, Package), Variant, Value, _),
|
||||
not propagate(node(ID, Package), variant_value(Variant, Value)),
|
||||
% variants set on externals that we could use don't count as non-default
|
||||
% this makes spack prefer to use an external over rebuilding with the
|
||||
% default configuration
|
||||
@@ -932,7 +901,7 @@ variant_default_not_used(node(ID, Package), Variant, Value)
|
||||
:- variant_default_value(Package, Variant, Value),
|
||||
node_has_variant(node(ID, Package), Variant),
|
||||
not attr("variant_value", node(ID, Package), Variant, Value),
|
||||
not attr("variant_propagate", node(ID, Package), Variant, _, _),
|
||||
not propagate(node(ID, Package), variant_value(Variant, _)),
|
||||
attr("node", node(ID, Package)).
|
||||
|
||||
% The variant is set in an external spec
|
||||
@@ -989,6 +958,67 @@ pkg_fact(Package, variant_single_value("dev_path"))
|
||||
#defined variant_default_value/3.
|
||||
#defined variant_default_value_from_packages_yaml/3.
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Propagation semantics
|
||||
%-----------------------------------------------------------------------------
|
||||
|
||||
% Propagation roots have a corresponding attr("propagate", ...)
|
||||
propagate(RootNode, PropagatedAttribute) :- attr("propagate", RootNode, PropagatedAttribute).
|
||||
|
||||
% Propagate an attribute along edges to child nodes
|
||||
propagate(ChildNode, PropagatedAttribute) :-
|
||||
propagate(ParentNode, PropagatedAttribute),
|
||||
depends_on(ParentNode, ChildNode).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Activation of propagated values
|
||||
%-----------------------------------------------------------------------------
|
||||
|
||||
%----
|
||||
% Variants
|
||||
%----
|
||||
|
||||
% If a variant is propagated, and can be accepted, set its value
|
||||
attr("variant_value", node(ID, Package), Variant, Value) :-
|
||||
propagate(node(ID, Package), variant_value(Variant, Value)),
|
||||
node_has_variant(node(ID, Package), Variant),
|
||||
pkg_fact(Package, variant_possible_value(Variant, Value)),
|
||||
not attr("variant_set", node(ID, Package), Variant).
|
||||
|
||||
% If a variant is propagated, we cannot have extraneous values
|
||||
variant_is_propagated(PackageNode, Variant) :-
|
||||
attr("variant_value", PackageNode, Variant, Value),
|
||||
propagate(PackageNode, variant_value(Variant, Value)),
|
||||
not attr("variant_set", PackageNode, Variant).
|
||||
|
||||
:- variant_is_propagated(PackageNode, Variant),
|
||||
attr("variant_value", PackageNode, Variant, Value),
|
||||
not propagate(PackageNode, variant_value(Variant, Value)).
|
||||
|
||||
%----
|
||||
% Compiler constraints
|
||||
%----
|
||||
|
||||
attr("node_compiler_version_satisfies", node(ID, Package), Compiler, Version) :-
|
||||
propagate(node(ID, Package), node_compiler_version_satisfies(Compiler, Version)),
|
||||
node_compiler(node(ID, Package), CompilerID),
|
||||
compiler_name(CompilerID, Compiler),
|
||||
not runtime(Package),
|
||||
not external(Package).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Runtimes
|
||||
%-----------------------------------------------------------------------------
|
||||
|
||||
% Check whether the DAG has any built package
|
||||
has_built_packages() :- build(X), not external(X).
|
||||
|
||||
% If we build packages, the runtime nodes must use an available compiler
|
||||
1 { node_compiler(PackageNode, CompilerID) : build(PackageNode), not external(PackageNode) } :-
|
||||
has_built_packages(),
|
||||
runtime(RuntimePackage),
|
||||
node_compiler(node(_, RuntimePackage), CompilerID).
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% Platform semantics
|
||||
%-----------------------------------------------------------------------------
|
||||
@@ -1090,10 +1120,18 @@ attr("node_target", PackageNode, Target)
|
||||
:- attr("node", PackageNode), attr("node_target_set", PackageNode, Target).
|
||||
|
||||
% each node has the weight of its assigned target
|
||||
node_target_weight(node(ID, Package), Weight)
|
||||
:- attr("node", node(ID, Package)),
|
||||
attr("node_target", node(ID, Package), Target),
|
||||
target_weight(Target, Weight).
|
||||
target_weight(Target, 0)
|
||||
:- attr("node", PackageNode),
|
||||
attr("node_target", PackageNode, Target),
|
||||
attr("node_target_set", PackageNode, Target).
|
||||
|
||||
node_target_weight(PackageNode, MinWeight)
|
||||
:- attr("node", PackageNode),
|
||||
attr("node_target", PackageNode, Target),
|
||||
target(Target),
|
||||
MinWeight = #min { Weight : target_weight(Target, Weight) }.
|
||||
|
||||
:- attr("node_target", PackageNode, Target), not node_target_weight(PackageNode, _).
|
||||
|
||||
% compatibility rules for targets among nodes
|
||||
node_target_match(ParentNode, DependencyNode)
|
||||
@@ -1155,12 +1193,12 @@ error(10, "No valid compiler for {0} satisfies '%{1}'", Package, Compiler)
|
||||
|
||||
% If the compiler of a node must satisfy a constraint, then its version
|
||||
% must be chosen among the ones that satisfy said constraint
|
||||
error(100, "No valid version for '{0}' compiler '{1}' satisfies '@{2}'", Package, Compiler, Constraint)
|
||||
error(100, "Package {0} cannot satisfy '%{1}@{2}'", Package, Compiler, Constraint)
|
||||
:- attr("node", node(X, Package)),
|
||||
attr("node_compiler_version_satisfies", node(X, Package), Compiler, Constraint),
|
||||
not compiler_version_satisfies(Compiler, Constraint, _).
|
||||
not compiler_version_satisfies(Compiler, Constraint, _).
|
||||
|
||||
error(100, "No valid version for '{0}' compiler '{1}' satisfies '@{2}'", Package, Compiler, Constraint)
|
||||
error(100, "Package {0} cannot satisfy '%{1}@{2}'", Package, Compiler, Constraint)
|
||||
:- attr("node", node(X, Package)),
|
||||
attr("node_compiler_version_satisfies", node(X, Package), Compiler, Constraint),
|
||||
not compiler_version_satisfies(Compiler, Constraint, ID),
|
||||
@@ -1345,8 +1383,10 @@ build(PackageNode) :- not attr("hash", PackageNode, _), attr("node", PackageNode
|
||||
% topmost-priority criterion to reuse what is installed.
|
||||
%
|
||||
% The priority ranges are:
|
||||
% 200+ Shifted priorities for build nodes; correspond to priorities 0 - 99.
|
||||
% 100 - 199 Unshifted priorities. Currently only includes minimizing #builds.
|
||||
% 1000+ Optimizations for concretization errors
|
||||
% 300 - 1000 Highest priority optimizations for valid solutions
|
||||
% 200 - 299 Shifted priorities for build nodes; correspond to priorities 0 - 99.
|
||||
% 100 - 199 Unshifted priorities. Currently only includes minimizing #builds and minimizing dupes.
|
||||
% 0 - 99 Priorities for non-built nodes.
|
||||
build_priority(PackageNode, 200) :- build(PackageNode), attr("node", PackageNode).
|
||||
build_priority(PackageNode, 0) :- not build(PackageNode), attr("node", PackageNode).
|
||||
@@ -1394,6 +1434,16 @@ build_priority(PackageNode, 0) :- not build(PackageNode), attr("node", Package
|
||||
% 2. a `#minimize{ 0@2 : #true }.` statement that ensures the criterion
|
||||
% is displayed (clingo doesn't display sums over empty sets by default)
|
||||
|
||||
% A condition group specifies one or more specs that must be satisfied.
|
||||
% Specs declared first are preferred, so we assign increasing weights and
|
||||
% minimize the weights.
|
||||
opt_criterion(310, "requirement weight").
|
||||
#minimize{ 0@310: #true }.
|
||||
#minimize {
|
||||
Weight@310,PackageNode,Group
|
||||
: requirement_weight(PackageNode, Group, Weight)
|
||||
}.
|
||||
|
||||
% Try hard to reuse installed packages (i.e., minimize the number built)
|
||||
opt_criterion(110, "number of packages to build (vs. reuse)").
|
||||
#minimize { 0@110: #true }.
|
||||
@@ -1405,18 +1455,6 @@ opt_criterion(100, "number of nodes from the same package").
|
||||
#minimize { ID@100,Package : attr("virtual_node", node(ID, Package)) }.
|
||||
#defined optimize_for_reuse/0.
|
||||
|
||||
% A condition group specifies one or more specs that must be satisfied.
|
||||
% Specs declared first are preferred, so we assign increasing weights and
|
||||
% minimize the weights.
|
||||
opt_criterion(75, "requirement weight").
|
||||
#minimize{ 0@275: #true }.
|
||||
#minimize{ 0@75: #true }.
|
||||
#minimize {
|
||||
Weight@75+Priority,PackageNode,Group
|
||||
: requirement_weight(PackageNode, Group, Weight),
|
||||
build_priority(PackageNode, Priority)
|
||||
}.
|
||||
|
||||
% Minimize the number of deprecated versions being used
|
||||
opt_criterion(73, "deprecated versions used").
|
||||
#minimize{ 0@273: #true }.
|
||||
@@ -1424,6 +1462,7 @@ opt_criterion(73, "deprecated versions used").
|
||||
#minimize{
|
||||
1@73+Priority,PackageNode
|
||||
: attr("deprecated", PackageNode, _),
|
||||
not external(PackageNode),
|
||||
build_priority(PackageNode, Priority)
|
||||
}.
|
||||
|
||||
@@ -1431,11 +1470,11 @@ opt_criterion(73, "deprecated versions used").
|
||||
% 1. Version weight
|
||||
% 2. Number of variants with a non default value, if not set
|
||||
% for the root package.
|
||||
opt_criterion(70, "version weight").
|
||||
opt_criterion(70, "version badness (roots)").
|
||||
#minimize{ 0@270: #true }.
|
||||
#minimize{ 0@70: #true }.
|
||||
#minimize {
|
||||
Weight@70+Priority
|
||||
Weight@70+Priority,PackageNode
|
||||
: attr("root", PackageNode),
|
||||
version_weight(PackageNode, Weight),
|
||||
build_priority(PackageNode, Priority)
|
||||
@@ -1495,7 +1534,7 @@ opt_criterion(45, "preferred providers (non-roots)").
|
||||
}.
|
||||
|
||||
% Try to minimize the number of compiler mismatches in the DAG.
|
||||
opt_criterion(40, "compiler mismatches that are not from CLI").
|
||||
opt_criterion(40, "compiler mismatches that are not required").
|
||||
#minimize{ 0@240: #true }.
|
||||
#minimize{ 0@40: #true }.
|
||||
#minimize{
|
||||
@@ -1505,7 +1544,7 @@ opt_criterion(40, "compiler mismatches that are not from CLI").
|
||||
not runtime(Dependency)
|
||||
}.
|
||||
|
||||
opt_criterion(39, "compiler mismatches that are not from CLI").
|
||||
opt_criterion(39, "compiler mismatches that are required").
|
||||
#minimize{ 0@239: #true }.
|
||||
#minimize{ 0@39: #true }.
|
||||
#minimize{
|
||||
@@ -1525,13 +1564,14 @@ opt_criterion(30, "non-preferred OS's").
|
||||
}.
|
||||
|
||||
% Choose more recent versions for nodes
|
||||
opt_criterion(25, "version badness").
|
||||
opt_criterion(25, "version badness (non roots)").
|
||||
#minimize{ 0@225: #true }.
|
||||
#minimize{ 0@25: #true }.
|
||||
#minimize{
|
||||
Weight@25+Priority,node(X, Package)
|
||||
: version_weight(node(X, Package), Weight),
|
||||
build_priority(node(X, Package), Priority),
|
||||
not attr("root", node(X, Package)),
|
||||
not runtime(Package)
|
||||
}.
|
||||
|
||||
|
@@ -4,21 +4,35 @@
|
||||
% SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
%=============================================================================
|
||||
% Heuristic to speed-up solves (node with ID 0)
|
||||
% Heuristic to speed-up solves
|
||||
%=============================================================================
|
||||
|
||||
% No duplicates by default (most of them will be true)
|
||||
#heuristic attr("node", node(PackageID, Package)). [100, init]
|
||||
#heuristic attr("node", node(PackageID, Package)). [ 2, factor]
|
||||
#heuristic attr("virtual_node", node(VirtualID, Virtual)). [100, init]
|
||||
#heuristic attr("node", node(1..X-1, Package)) : max_dupes(Package, X), not virtual(Package), X > 1. [-1, sign]
|
||||
#heuristic attr("virtual_node", node(1..X-1, Package)) : max_dupes(Package, X), virtual(Package) , X > 1. [-1, sign]
|
||||
|
||||
%-----------------
|
||||
% Domain heuristic
|
||||
%-----------------
|
||||
% Pick preferred version
|
||||
#heuristic attr("version", node(PackageID, Package), Version) : pkg_fact(Package, version_declared(Version, Weight)), attr("node", node(PackageID, Package)). [40, init]
|
||||
#heuristic version_weight(node(PackageID, Package), 0) : pkg_fact(Package, version_declared(Version, 0 )), attr("node", node(PackageID, Package)). [ 1, sign]
|
||||
#heuristic attr("version", node(PackageID, Package), Version) : pkg_fact(Package, version_declared(Version, 0 )), attr("node", node(PackageID, Package)). [ 1, sign]
|
||||
#heuristic attr("version", node(PackageID, Package), Version) : pkg_fact(Package, version_declared(Version, Weight)), attr("node", node(PackageID, Package)), Weight > 0. [-1, sign]
|
||||
|
||||
% 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) : 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) : compiler_weight(ID, 0), compiler_id(ID), attr("root", node(0, Package)). [35, true]
|
||||
% Use default variants
|
||||
#heuristic attr("variant_value", node(PackageID, Package), Variant, Value) : variant_default_value(Package, Variant, Value), attr("node", node(PackageID, Package)). [40, true]
|
||||
#heuristic attr("variant_value", node(PackageID, Package), Variant, Value) : not variant_default_value(Package, Variant, Value), attr("node", node(PackageID, Package)). [40, false]
|
||||
|
||||
% Providers
|
||||
#heuristic attr("node", node(0, Package)) : default_provider_preference(Virtual, Package, 0), possible_in_link_run(Package). [30, true]
|
||||
% Use default operating system and platform
|
||||
#heuristic attr("node_os", node(PackageID, Package), OS) : os(OS, 0), attr("root", node(PackageID, Package)). [40, true]
|
||||
#heuristic attr("node_platform", node(PackageID, Package), Platform) : allowed_platform(Platform), attr("root", node(PackageID, Package)). [40, true]
|
||||
|
||||
% Use default targets
|
||||
#heuristic attr("node_target", node(PackageID, Package), Target) : target_weight(Target, Weight), attr("node", node(PackageID, Package)). [30, init]
|
||||
#heuristic attr("node_target", node(PackageID, Package), Target) : target_weight(Target, Weight), attr("node", node(PackageID, Package)). [ 2, factor]
|
||||
#heuristic attr("node_target", node(PackageID, Package), Target) : target_weight(Target, 0), attr("node", node(PackageID, Package)). [ 1, sign]
|
||||
#heuristic attr("node_target", node(PackageID, Package), Target) : target_weight(Target, Weight), attr("node", node(PackageID, Package)), Weight > 0. [-1, sign]
|
||||
|
||||
% Use the default compilers
|
||||
#heuristic node_compiler(node(PackageID, Package), ID) : compiler_weight(ID, 0), compiler_id(ID), attr("node", node(PackageID, Package)). [30, init]
|
||||
|
@@ -1,24 +0,0 @@
|
||||
% Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||
% Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
%
|
||||
% SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
%=============================================================================
|
||||
% Heuristic to speed-up solves (node with ID > 0)
|
||||
%=============================================================================
|
||||
|
||||
% node(ID, _)
|
||||
#heuristic attr("version", node(ID, Package), Version) : pkg_fact(Package, version_declared(Version, 0)), attr("node", node(ID, Package)), ID > 0. [25-5*ID, true]
|
||||
#heuristic version_weight(node(ID, Package), 0) : pkg_fact(Package, version_declared(Version, 0)), attr("node", node(ID, Package)), ID > 0. [25-5*ID, true]
|
||||
#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) : 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]
|
||||
#heuristic version_weight(node(ID, Package), 0) : pkg_fact(Package, version_declared(Version, 0)), attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
||||
#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) : compiler_weight(CompilerID, 0), compiler_id(CompilerID), attr("node", node(ID, Package)), multiple_unification_sets(Package), ID > 0. [25, true]
|
@@ -10,15 +10,13 @@
|
||||
%=============================================================================
|
||||
|
||||
% A package cannot be reused if the libc is not compatible with it
|
||||
:- provider(node(X, LibcPackage), node(0, "libc")),
|
||||
attr("version", node(X, LibcPackage), LibcVersion),
|
||||
attr("hash", node(R, ReusedPackage), Hash),
|
||||
% Libc packages can be reused without the "compatible_libc" attribute
|
||||
ReusedPackage != LibcPackage,
|
||||
not attr("compatible_libc", node(R, ReusedPackage), LibcPackage, LibcVersion).
|
||||
|
||||
% Check whether the DAG has any built package
|
||||
has_built_packages() :- build(X), not external(X).
|
||||
error(100, "Cannot reuse {0} since we cannot determine libc compatibility", ReusedPackage)
|
||||
:- provider(node(X, LibcPackage), node(0, "libc")),
|
||||
attr("version", node(X, LibcPackage), LibcVersion),
|
||||
attr("hash", node(R, ReusedPackage), Hash),
|
||||
% Libc packages can be reused without the "compatible_libc" attribute
|
||||
ReusedPackage != LibcPackage,
|
||||
not attr("compatible_libc", node(R, ReusedPackage), LibcPackage, LibcVersion).
|
||||
|
||||
% A libc is needed in the DAG
|
||||
:- has_built_packages(), not provider(_, node(0, "libc")).
|
||||
|
@@ -12,6 +12,7 @@
|
||||
%=============================================================================
|
||||
|
||||
% macOS
|
||||
os_compatible("sequoia", "sonoma").
|
||||
os_compatible("sonoma", "ventura").
|
||||
os_compatible("ventura", "monterey").
|
||||
os_compatible("monterey", "bigsur").
|
||||
|
@@ -4164,29 +4164,21 @@ def __getitem__(self, name: str):
|
||||
csv = query_parameters.pop().strip()
|
||||
query_parameters = re.split(r"\s*,\s*", csv)
|
||||
|
||||
# In some cases a package appears multiple times in the same DAG for *distinct*
|
||||
# specs. For example, a build-type dependency may itself depend on a package
|
||||
# the current spec depends on, but their specs may differ. Therefore we iterate
|
||||
# in an order here that prioritizes the build, test and runtime dependencies;
|
||||
# only when we don't find the package do we consider the full DAG.
|
||||
order = lambda: itertools.chain(
|
||||
self.traverse(deptype="link"),
|
||||
self.dependencies(deptype=dt.BUILD | dt.RUN | dt.TEST),
|
||||
self.traverse(), # fall back to a full search
|
||||
self.traverse_edges(deptype=dt.LINK, order="breadth", cover="edges"),
|
||||
self.edges_to_dependencies(depflag=dt.BUILD | dt.RUN | dt.TEST),
|
||||
self.traverse_edges(deptype=dt.ALL, order="breadth", cover="edges"),
|
||||
)
|
||||
|
||||
# Consider runtime dependencies and direct build/test deps before transitive dependencies,
|
||||
# and prefer matches closest to the root.
|
||||
try:
|
||||
child: Spec = next(
|
||||
itertools.chain(
|
||||
# Regular specs
|
||||
(x for x in order() if x.name == name),
|
||||
(
|
||||
x
|
||||
for x in order()
|
||||
if (not x.virtual)
|
||||
and any(name in edge.virtuals for edge in x.edges_from_dependents())
|
||||
),
|
||||
(x for x in order() if (not x.virtual) and x.package.provides(name)),
|
||||
e.spec
|
||||
for e in itertools.chain(
|
||||
(e for e in order() if e.spec.name == name or name in e.virtuals),
|
||||
# for historical reasons
|
||||
(e for e in order() if e.spec.concrete and e.spec.package.provides(name)),
|
||||
)
|
||||
)
|
||||
except StopIteration:
|
||||
@@ -4428,9 +4420,12 @@ def format_attribute(match_object: Match) -> str:
|
||||
if part.startswith("_"):
|
||||
raise SpecFormatStringError("Attempted to format private attribute")
|
||||
else:
|
||||
if part == "variants" and isinstance(current, vt.VariantMap):
|
||||
if isinstance(current, vt.VariantMap):
|
||||
# subscript instead of getattr for variant names
|
||||
current = current[part]
|
||||
try:
|
||||
current = current[part]
|
||||
except KeyError:
|
||||
raise SpecFormatStringError(f"Variant '{part}' does not exist")
|
||||
else:
|
||||
# aliases
|
||||
if part == "arch":
|
||||
|
@@ -346,8 +346,6 @@ class Stage(LockableStagingDir):
|
||||
similar, and are intended to persist for only one run of spack.
|
||||
"""
|
||||
|
||||
#: Most staging is managed by Spack. DIYStage is one exception.
|
||||
needs_fetching = True
|
||||
requires_patch_success = True
|
||||
|
||||
def __init__(
|
||||
@@ -772,8 +770,6 @@ def __init__(self):
|
||||
"cache_mirror",
|
||||
"steal_source",
|
||||
"disable_mirrors",
|
||||
"needs_fetching",
|
||||
"requires_patch_success",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -812,6 +808,10 @@ def path(self):
|
||||
def archive_file(self):
|
||||
return self[0].archive_file
|
||||
|
||||
@property
|
||||
def requires_patch_success(self):
|
||||
return self[0].requires_patch_success
|
||||
|
||||
@property
|
||||
def keep(self):
|
||||
return self[0].keep
|
||||
@@ -822,64 +822,7 @@ def keep(self, value):
|
||||
item.keep = value
|
||||
|
||||
|
||||
class DIYStage:
|
||||
"""
|
||||
Simple class that allows any directory to be a spack stage. Consequently,
|
||||
it does not expect or require that the source path adhere to the standard
|
||||
directory naming convention.
|
||||
"""
|
||||
|
||||
needs_fetching = False
|
||||
requires_patch_success = False
|
||||
|
||||
def __init__(self, path):
|
||||
if path is None:
|
||||
raise ValueError("Cannot construct DIYStage without a path.")
|
||||
elif not os.path.isdir(path):
|
||||
raise StagePathError("The stage path directory does not exist:", path)
|
||||
|
||||
self.archive_file = None
|
||||
self.path = path
|
||||
self.source_path = path
|
||||
self.created = True
|
||||
|
||||
# DIY stages do nothing as context managers.
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
def fetch(self, *args, **kwargs):
|
||||
tty.debug("No need to fetch for DIY.")
|
||||
|
||||
def check(self):
|
||||
tty.debug("No checksum needed for DIY.")
|
||||
|
||||
def expand_archive(self):
|
||||
tty.debug("Using source directory: {0}".format(self.source_path))
|
||||
|
||||
@property
|
||||
def expanded(self):
|
||||
"""Returns True since the source_path must exist."""
|
||||
return True
|
||||
|
||||
def restage(self):
|
||||
raise RestageError("Cannot restage a DIY stage.")
|
||||
|
||||
def create(self):
|
||||
self.created = True
|
||||
|
||||
def destroy(self):
|
||||
# No need to destroy DIY stage.
|
||||
pass
|
||||
|
||||
def cache_local(self):
|
||||
tty.debug("Sources for DIY stages are not cached")
|
||||
|
||||
|
||||
class DevelopStage(LockableStagingDir):
|
||||
needs_fetching = False
|
||||
requires_patch_success = False
|
||||
|
||||
def __init__(self, name, dev_path, reference_link):
|
||||
|
@@ -371,7 +371,6 @@ def use_store(
|
||||
data.update(extra_data)
|
||||
|
||||
# Swap the store with the one just constructed and return it
|
||||
ensure_singleton_created()
|
||||
spack.config.CONFIG.push_scope(
|
||||
spack.config.InternalConfigScope(name=scope_name, data={"config": {"install_tree": data}})
|
||||
)
|
||||
|
@@ -218,10 +218,12 @@ def test_satisfy_strict_constraint_when_not_concrete(architecture_tuple, constra
|
||||
str(archspec.cpu.host().family) != "x86_64", reason="tests are for x86_64 uarch ranges"
|
||||
)
|
||||
def test_concretize_target_ranges(root_target_range, dep_target_range, result, monkeypatch):
|
||||
spec = Spec(f"a %gcc@10 foobar=bar target={root_target_range} ^b target={dep_target_range}")
|
||||
spec = Spec(
|
||||
f"pkg-a %gcc@10 foobar=bar target={root_target_range} ^pkg-b target={dep_target_range}"
|
||||
)
|
||||
with spack.concretize.disable_compiler_existence_check():
|
||||
spec.concretize()
|
||||
assert spec.target == spec["b"].target == result
|
||||
assert spec.target == spec["pkg-b"].target == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@@ -22,6 +22,7 @@
|
||||
import archspec.cpu
|
||||
|
||||
from llnl.util.filesystem import join_path, visit_directory_tree
|
||||
from llnl.util.symlink import readlink
|
||||
|
||||
import spack.binary_distribution as bindist
|
||||
import spack.caches
|
||||
@@ -1062,10 +1063,10 @@ def test_tarball_common_prefix(dummy_prefix, tmpdir):
|
||||
assert set(os.listdir(os.path.join("prefix2", "share"))) == {"file"}
|
||||
|
||||
# Relative symlink should still be correct
|
||||
assert os.readlink(os.path.join("prefix2", "bin", "relative_app_link")) == "app"
|
||||
assert readlink(os.path.join("prefix2", "bin", "relative_app_link")) == "app"
|
||||
|
||||
# Absolute symlink should remain absolute -- this is for relocation to fix up.
|
||||
assert os.readlink(os.path.join("prefix2", "bin", "absolute_app_link")) == os.path.join(
|
||||
assert readlink(os.path.join("prefix2", "bin", "absolute_app_link")) == os.path.join(
|
||||
dummy_prefix, "bin", "app"
|
||||
)
|
||||
|
||||
|
@@ -228,3 +228,25 @@ def test_source_is_disabled(mutable_config):
|
||||
spack.config.add("bootstrap:trusted:{0}:{1}".format(conf["name"], False))
|
||||
with pytest.raises(ValueError):
|
||||
spack.bootstrap.core.source_is_enabled_or_raise(conf)
|
||||
|
||||
|
||||
@pytest.mark.regression("45247")
|
||||
def test_use_store_does_not_try_writing_outside_root(tmp_path, monkeypatch, mutable_config):
|
||||
"""Tests that when we use the 'use_store' context manager, there is no attempt at creating
|
||||
a Store outside the given root.
|
||||
"""
|
||||
initial_store = mutable_config.get("config:install_tree:root")
|
||||
user_store = tmp_path / "store"
|
||||
|
||||
fn = spack.store.Store.__init__
|
||||
|
||||
def _checked_init(self, root, *args, **kwargs):
|
||||
fn(self, root, *args, **kwargs)
|
||||
assert self.root == str(user_store)
|
||||
|
||||
monkeypatch.setattr(spack.store.Store, "__init__", _checked_init)
|
||||
|
||||
spack.store.reinitialize()
|
||||
with spack.store.use_store(user_store):
|
||||
assert spack.config.CONFIG.get("config:install_tree:root") == str(user_store)
|
||||
assert spack.config.CONFIG.get("config:install_tree:root") == initial_store
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
import spack.build_environment
|
||||
import spack.config
|
||||
import spack.deptypes as dt
|
||||
import spack.package_base
|
||||
import spack.spec
|
||||
import spack.util.spack_yaml as syaml
|
||||
@@ -456,14 +457,14 @@ def test_parallel_false_is_not_propagating(default_mock_concretization):
|
||||
# a foobar=bar (parallel = False)
|
||||
# |
|
||||
# b (parallel =True)
|
||||
s = default_mock_concretization("a foobar=bar")
|
||||
s = default_mock_concretization("pkg-a foobar=bar")
|
||||
|
||||
spack.build_environment.set_package_py_globals(s.package, context=Context.BUILD)
|
||||
assert s["a"].package.module.make_jobs == 1
|
||||
assert s["pkg-a"].package.module.make_jobs == 1
|
||||
|
||||
spack.build_environment.set_package_py_globals(s["b"].package, context=Context.BUILD)
|
||||
assert s["b"].package.module.make_jobs == spack.build_environment.determine_number_of_jobs(
|
||||
parallel=s["b"].package.parallel
|
||||
spack.build_environment.set_package_py_globals(s["pkg-b"].package, context=Context.BUILD)
|
||||
assert s["pkg-b"].package.module.make_jobs == spack.build_environment.determine_number_of_jobs(
|
||||
parallel=s["pkg-b"].package.parallel
|
||||
)
|
||||
|
||||
|
||||
@@ -559,7 +560,7 @@ def test_dirty_disable_module_unload(config, mock_packages, working_env, mock_mo
|
||||
"""Test that on CRAY platform 'module unload' is not called if the 'dirty'
|
||||
option is on.
|
||||
"""
|
||||
s = spack.spec.Spec("a").concretized()
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
|
||||
# If called with "dirty" we don't unload modules, so no calls to the
|
||||
# `module` function on Cray
|
||||
@@ -716,3 +717,21 @@ def test_build_system_globals_only_set_on_root_during_build(default_mock_concret
|
||||
for depth, spec in root.traverse(depth=True, root=True):
|
||||
for variable in build_variables:
|
||||
assert hasattr(spec.package.module, variable) == should_be_set(depth)
|
||||
|
||||
|
||||
def test_rpath_with_duplicate_link_deps():
|
||||
"""If we have two instances of one package in the same link sub-dag, only the newest version is
|
||||
rpath'ed. This is for runtime support without splicing."""
|
||||
runtime_1 = spack.spec.Spec("runtime@=1.0")
|
||||
runtime_2 = spack.spec.Spec("runtime@=2.0")
|
||||
child = spack.spec.Spec("child@=1.0")
|
||||
root = spack.spec.Spec("root@=1.0")
|
||||
|
||||
root.add_dependency_edge(child, depflag=dt.LINK, virtuals=())
|
||||
root.add_dependency_edge(runtime_2, depflag=dt.LINK, virtuals=())
|
||||
child.add_dependency_edge(runtime_1, depflag=dt.LINK, virtuals=())
|
||||
|
||||
rpath_deps = spack.build_environment._get_rpath_deps_from_spec(root, transitive_rpaths=True)
|
||||
assert child in rpath_deps
|
||||
assert runtime_2 in rpath_deps
|
||||
assert runtime_1 not in rpath_deps
|
||||
|
@@ -97,7 +97,7 @@ def test_negative_ninja_check(self, input_dir, test_dir, concretize_and_setup):
|
||||
@pytest.mark.usefixtures("config", "mock_packages")
|
||||
class TestAutotoolsPackage:
|
||||
def test_with_or_without(self, default_mock_concretization):
|
||||
s = default_mock_concretization("a")
|
||||
s = default_mock_concretization("pkg-a")
|
||||
options = s.package.with_or_without("foo")
|
||||
|
||||
# Ensure that values that are not representing a feature
|
||||
@@ -129,7 +129,7 @@ def activate(value):
|
||||
assert "--without-lorem-ipsum" in options
|
||||
|
||||
def test_none_is_allowed(self, default_mock_concretization):
|
||||
s = default_mock_concretization("a foo=none")
|
||||
s = default_mock_concretization("pkg-a foo=none")
|
||||
options = s.package.with_or_without("foo")
|
||||
|
||||
# Ensure that values that are not representing a feature
|
||||
|
@@ -51,7 +51,7 @@ def __init__(self, response_code=200, content_to_read=[]):
|
||||
self._content = content_to_read
|
||||
self._read = [False for c in content_to_read]
|
||||
|
||||
def open(self, request):
|
||||
def open(self, request, data=None, timeout=object()):
|
||||
return self
|
||||
|
||||
def getcode(self):
|
||||
|
@@ -106,24 +106,24 @@ def test_specs_staging(config, tmpdir):
|
||||
|
||||
"""
|
||||
builder = repo.MockRepositoryBuilder(tmpdir)
|
||||
builder.add_package("g")
|
||||
builder.add_package("f")
|
||||
builder.add_package("e")
|
||||
builder.add_package("d", dependencies=[("f", None, None), ("g", None, None)])
|
||||
builder.add_package("c")
|
||||
builder.add_package("b", dependencies=[("d", None, None), ("e", None, None)])
|
||||
builder.add_package("a", dependencies=[("b", None, None), ("c", None, None)])
|
||||
builder.add_package("pkg-g")
|
||||
builder.add_package("pkg-f")
|
||||
builder.add_package("pkg-e")
|
||||
builder.add_package("pkg-d", dependencies=[("pkg-f", None, None), ("pkg-g", None, None)])
|
||||
builder.add_package("pkg-c")
|
||||
builder.add_package("pkg-b", dependencies=[("pkg-d", None, None), ("pkg-e", None, None)])
|
||||
builder.add_package("pkg-a", dependencies=[("pkg-b", None, None), ("pkg-c", None, None)])
|
||||
|
||||
with repo.use_repositories(builder.root):
|
||||
spec_a = Spec("a").concretized()
|
||||
spec_a = Spec("pkg-a").concretized()
|
||||
|
||||
spec_a_label = ci._spec_ci_label(spec_a)
|
||||
spec_b_label = ci._spec_ci_label(spec_a["b"])
|
||||
spec_c_label = ci._spec_ci_label(spec_a["c"])
|
||||
spec_d_label = ci._spec_ci_label(spec_a["d"])
|
||||
spec_e_label = ci._spec_ci_label(spec_a["e"])
|
||||
spec_f_label = ci._spec_ci_label(spec_a["f"])
|
||||
spec_g_label = ci._spec_ci_label(spec_a["g"])
|
||||
spec_b_label = ci._spec_ci_label(spec_a["pkg-b"])
|
||||
spec_c_label = ci._spec_ci_label(spec_a["pkg-c"])
|
||||
spec_d_label = ci._spec_ci_label(spec_a["pkg-d"])
|
||||
spec_e_label = ci._spec_ci_label(spec_a["pkg-e"])
|
||||
spec_f_label = ci._spec_ci_label(spec_a["pkg-f"])
|
||||
spec_g_label = ci._spec_ci_label(spec_a["pkg-g"])
|
||||
|
||||
spec_labels, dependencies, stages = ci.stage_spec_jobs([spec_a])
|
||||
|
||||
@@ -760,7 +760,6 @@ def test_ci_rebuild_mock_success(
|
||||
rebuild_env = create_rebuild_env(tmpdir, pkg_name, broken_tests)
|
||||
|
||||
monkeypatch.setattr(spack.cmd.ci, "SPACK_COMMAND", "echo")
|
||||
monkeypatch.setattr(spack.cmd.ci, "MAKE_COMMAND", "echo")
|
||||
|
||||
with rebuild_env.env_dir.as_cwd():
|
||||
activate_rebuild_env(tmpdir, pkg_name, rebuild_env)
|
||||
@@ -843,7 +842,6 @@ def test_ci_rebuild(
|
||||
ci_cmd("rebuild", "--tests", fail_on_error=False)
|
||||
|
||||
monkeypatch.setattr(spack.cmd.ci, "SPACK_COMMAND", "notcommand")
|
||||
monkeypatch.setattr(spack.cmd.ci, "MAKE_COMMAND", "notcommand")
|
||||
monkeypatch.setattr(spack.cmd.ci, "INSTALL_FAIL_CODE", 127)
|
||||
|
||||
with rebuild_env.env_dir.as_cwd():
|
||||
@@ -1292,7 +1290,7 @@ def test_ci_generate_override_runner_attrs(
|
||||
spack:
|
||||
specs:
|
||||
- flatten-deps
|
||||
- a
|
||||
- pkg-a
|
||||
mirrors:
|
||||
some-mirror: https://my.fake.mirror
|
||||
ci:
|
||||
@@ -1309,12 +1307,12 @@ def test_ci_generate_override_runner_attrs(
|
||||
- match:
|
||||
- dependency-install
|
||||
- match:
|
||||
- a
|
||||
- pkg-a
|
||||
build-job:
|
||||
tags:
|
||||
- specific-a-2
|
||||
- match:
|
||||
- a
|
||||
- pkg-a
|
||||
build-job-remove:
|
||||
tags:
|
||||
- toplevel2
|
||||
@@ -1374,8 +1372,8 @@ def test_ci_generate_override_runner_attrs(
|
||||
assert global_vars["SPACK_CHECKOUT_VERSION"] == git_version or "v0.20.0.test0"
|
||||
|
||||
for ci_key in yaml_contents.keys():
|
||||
if ci_key.startswith("a"):
|
||||
# Make sure a's attributes override variables, and all the
|
||||
if ci_key.startswith("pkg-a"):
|
||||
# Make sure pkg-a's attributes override variables, and all the
|
||||
# scripts. Also, make sure the 'toplevel' tag doesn't
|
||||
# appear twice, but that a's specific extra tag does appear
|
||||
the_elt = yaml_contents[ci_key]
|
||||
@@ -1832,7 +1830,7 @@ def test_ci_generate_read_broken_specs_url(
|
||||
tmpdir, mutable_mock_env_path, install_mockery, mock_packages, monkeypatch, ci_base_environment
|
||||
):
|
||||
"""Verify that `broken-specs-url` works as intended"""
|
||||
spec_a = Spec("a")
|
||||
spec_a = Spec("pkg-a")
|
||||
spec_a.concretize()
|
||||
a_dag_hash = spec_a.dag_hash()
|
||||
|
||||
@@ -1858,7 +1856,7 @@ def test_ci_generate_read_broken_specs_url(
|
||||
spack:
|
||||
specs:
|
||||
- flatten-deps
|
||||
- a
|
||||
- pkg-a
|
||||
mirrors:
|
||||
some-mirror: https://my.fake.mirror
|
||||
ci:
|
||||
@@ -1866,9 +1864,9 @@ def test_ci_generate_read_broken_specs_url(
|
||||
pipeline-gen:
|
||||
- submapping:
|
||||
- match:
|
||||
- a
|
||||
- pkg-a
|
||||
- flatten-deps
|
||||
- b
|
||||
- pkg-b
|
||||
- dependency-install
|
||||
build-job:
|
||||
tags:
|
||||
|
@@ -81,14 +81,14 @@ def test_match_spec_env(mock_packages, mutable_mock_env_path):
|
||||
"""
|
||||
# Initial sanity check: we are planning on choosing a non-default
|
||||
# value, so make sure that is in fact not the default.
|
||||
check_defaults = spack.cmd.parse_specs(["a"], concretize=True)[0]
|
||||
check_defaults = spack.cmd.parse_specs(["pkg-a"], concretize=True)[0]
|
||||
assert not check_defaults.satisfies("foobar=baz")
|
||||
|
||||
e = ev.create("test")
|
||||
e.add("a foobar=baz")
|
||||
e.add("pkg-a foobar=baz")
|
||||
e.concretize()
|
||||
with e:
|
||||
env_spec = spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["a"])[0])
|
||||
env_spec = spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["pkg-a"])[0])
|
||||
assert env_spec.satisfies("foobar=baz")
|
||||
assert env_spec.concrete
|
||||
|
||||
@@ -96,12 +96,12 @@ def test_match_spec_env(mock_packages, mutable_mock_env_path):
|
||||
@pytest.mark.usefixtures("config")
|
||||
def test_multiple_env_match_raises_error(mock_packages, mutable_mock_env_path):
|
||||
e = ev.create("test")
|
||||
e.add("a foobar=baz")
|
||||
e.add("a foobar=fee")
|
||||
e.add("pkg-a foobar=baz")
|
||||
e.add("pkg-a foobar=fee")
|
||||
e.concretize()
|
||||
with e:
|
||||
with pytest.raises(ev.SpackEnvironmentError) as exc_info:
|
||||
spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["a"])[0])
|
||||
spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["pkg-a"])[0])
|
||||
|
||||
assert "matches multiple specs" in exc_info.value.message
|
||||
|
||||
@@ -109,16 +109,16 @@ def test_multiple_env_match_raises_error(mock_packages, mutable_mock_env_path):
|
||||
@pytest.mark.usefixtures("config")
|
||||
def test_root_and_dep_match_returns_root(mock_packages, mutable_mock_env_path):
|
||||
e = ev.create("test")
|
||||
e.add("b@0.9")
|
||||
e.add("a foobar=bar") # Depends on b, should choose b@1.0
|
||||
e.add("pkg-b@0.9")
|
||||
e.add("pkg-a foobar=bar") # Depends on b, should choose b@1.0
|
||||
e.concretize()
|
||||
with e:
|
||||
# This query matches the root b and b as a dependency of a. In that
|
||||
# case the root instance should be preferred.
|
||||
env_spec1 = spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["b"])[0])
|
||||
env_spec1 = spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["pkg-b"])[0])
|
||||
assert env_spec1.satisfies("@0.9")
|
||||
|
||||
env_spec2 = spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["b@1.0"])[0])
|
||||
env_spec2 = spack.cmd.matching_spec_from_env(spack.cmd.parse_specs(["pkg-b@1.0"])[0])
|
||||
assert env_spec2
|
||||
|
||||
|
||||
|
@@ -51,8 +51,8 @@ def test_concretize_root_test_dependencies_are_concretized(unify, mutable_mock_e
|
||||
|
||||
with ev.read("test") as e:
|
||||
e.unify = unify
|
||||
add("a")
|
||||
add("b")
|
||||
add("pkg-a")
|
||||
add("pkg-b")
|
||||
concretize("--test", "root")
|
||||
assert e.matching_spec("test-dependency")
|
||||
|
||||
|
@@ -15,26 +15,26 @@
|
||||
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.add("pkg-a@2.0 foobar=bar ^pkg-b@1.0")
|
||||
e.add("pkg-a@1.0 foobar=bar ^pkg-b@0.9")
|
||||
e.concretize()
|
||||
e.write()
|
||||
|
||||
|
||||
def test_deconcretize_dep(test_env):
|
||||
with ev.read("test") as e:
|
||||
deconcretize("-y", "b@1.0")
|
||||
deconcretize("-y", "pkg-b@1.0")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 1
|
||||
assert specs[0].satisfies("a@1.0")
|
||||
assert specs[0].satisfies("pkg-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")
|
||||
deconcretize("-y", "pkg-b")
|
||||
deconcretize("-y", "--all", "pkg-b")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 0
|
||||
@@ -42,27 +42,27 @@ def test_deconcretize_all_dep(test_env):
|
||||
|
||||
def test_deconcretize_root(test_env):
|
||||
with ev.read("test") as e:
|
||||
output = deconcretize("-y", "--root", "b@1.0")
|
||||
output = deconcretize("-y", "--root", "pkg-b@1.0")
|
||||
assert "No matching specs to deconcretize" in output
|
||||
assert len(e.concretized_order) == 2
|
||||
|
||||
deconcretize("-y", "--root", "a@2.0")
|
||||
deconcretize("-y", "--root", "pkg-a@2.0")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 1
|
||||
assert specs[0].satisfies("a@1.0")
|
||||
assert specs[0].satisfies("pkg-a@1.0")
|
||||
|
||||
|
||||
def test_deconcretize_all_root(test_env):
|
||||
with ev.read("test") as e:
|
||||
with pytest.raises(SpackCommandError):
|
||||
deconcretize("-y", "--root", "a")
|
||||
deconcretize("-y", "--root", "pkg-a")
|
||||
|
||||
output = deconcretize("-y", "--root", "--all", "b")
|
||||
output = deconcretize("-y", "--root", "--all", "pkg-b")
|
||||
assert "No matching specs to deconcretize" in output
|
||||
assert len(e.concretized_order) == 2
|
||||
|
||||
deconcretize("-y", "--root", "--all", "a")
|
||||
deconcretize("-y", "--root", "--all", "pkg-a")
|
||||
specs = [s for s, _ in e.concretized_specs()]
|
||||
|
||||
assert len(specs) == 0
|
||||
|
@@ -15,6 +15,7 @@
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.link_tree
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.symlink import readlink
|
||||
|
||||
import spack.cmd.env
|
||||
import spack.config
|
||||
@@ -27,7 +28,9 @@
|
||||
import spack.package_base
|
||||
import spack.paths
|
||||
import spack.repo
|
||||
import spack.store
|
||||
import spack.util.spack_json as sjson
|
||||
import spack.util.spack_yaml
|
||||
from spack.cmd.env import _env_create
|
||||
from spack.main import SpackCommand, SpackCommandError
|
||||
from spack.spec import Spec
|
||||
@@ -60,6 +63,27 @@
|
||||
sep = os.sep
|
||||
|
||||
|
||||
def setup_combined_multiple_env():
|
||||
env("create", "test1")
|
||||
test1 = ev.read("test1")
|
||||
with test1:
|
||||
add("zlib")
|
||||
test1.concretize()
|
||||
test1.write()
|
||||
|
||||
env("create", "test2")
|
||||
test2 = ev.read("test2")
|
||||
with test2:
|
||||
add("libelf")
|
||||
test2.concretize()
|
||||
test2.write()
|
||||
|
||||
env("create", "--include-concrete", "test1", "--include-concrete", "test2", "combined_env")
|
||||
combined = ev.read("combined_env")
|
||||
|
||||
return test1, test2, combined
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def environment_from_manifest(tmp_path):
|
||||
"""Returns a new environment named 'test' from the content of a manifest file."""
|
||||
@@ -369,6 +393,29 @@ def test_env_install_single_spec(install_mockery, mock_fetch):
|
||||
assert e.specs_by_hash[e.concretized_order[0]].name == "cmake-client"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unify", [True, False, "when_possible"])
|
||||
def test_env_install_include_concrete_env(unify, install_mockery, mock_fetch):
|
||||
test1, test2, combined = setup_combined_multiple_env()
|
||||
|
||||
combined.concretize()
|
||||
combined.write()
|
||||
|
||||
combined.unify = unify
|
||||
|
||||
with combined:
|
||||
install()
|
||||
|
||||
test1_roots = test1.concretized_order
|
||||
test2_roots = test2.concretized_order
|
||||
combined_included_roots = combined.included_concretized_order
|
||||
|
||||
for spec in combined.all_specs():
|
||||
assert spec.installed
|
||||
|
||||
assert test1_roots == combined_included_roots[test1.path]
|
||||
assert test2_roots == combined_included_roots[test2.path]
|
||||
|
||||
|
||||
def test_env_roots_marked_explicit(install_mockery, mock_fetch):
|
||||
install = SpackCommand("install")
|
||||
install("dependent-install")
|
||||
@@ -456,7 +503,7 @@ def test_env_install_two_specs_same_dep(install_mockery, mock_fetch, tmpdir, cap
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- a
|
||||
- pkg-a
|
||||
- depb
|
||||
"""
|
||||
)
|
||||
@@ -475,8 +522,8 @@ def test_env_install_two_specs_same_dep(install_mockery, mock_fetch, tmpdir, cap
|
||||
depb = spack.store.STORE.db.query_one("depb", installed=True)
|
||||
assert depb, "Expected depb to be installed"
|
||||
|
||||
a = spack.store.STORE.db.query_one("a", installed=True)
|
||||
assert a, "Expected a to be installed"
|
||||
a = spack.store.STORE.db.query_one("pkg-a", installed=True)
|
||||
assert a, "Expected pkg-a to be installed"
|
||||
|
||||
|
||||
def test_remove_after_concretize():
|
||||
@@ -557,6 +604,41 @@ def test_remove_command():
|
||||
assert "mpileaks@" not in find("--show-concretized")
|
||||
|
||||
|
||||
def test_bad_remove_included_env():
|
||||
env("create", "test")
|
||||
test = ev.read("test")
|
||||
|
||||
with test:
|
||||
add("mpileaks")
|
||||
|
||||
test.concretize()
|
||||
test.write()
|
||||
|
||||
env("create", "--include-concrete", "test", "combined_env")
|
||||
|
||||
with pytest.raises(SpackCommandError):
|
||||
env("remove", "test")
|
||||
|
||||
|
||||
def test_force_remove_included_env():
|
||||
env("create", "test")
|
||||
test = ev.read("test")
|
||||
|
||||
with test:
|
||||
add("mpileaks")
|
||||
|
||||
test.concretize()
|
||||
test.write()
|
||||
|
||||
env("create", "--include-concrete", "test", "combined_env")
|
||||
|
||||
rm_output = env("remove", "-f", "-y", "test")
|
||||
list_output = env("list")
|
||||
|
||||
assert '"test" is being used by environment "combined_env"' in rm_output
|
||||
assert "test" not in list_output
|
||||
|
||||
|
||||
def test_environment_status(capsys, tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
with capsys.disabled():
|
||||
@@ -745,7 +827,7 @@ def test_env_view_external_prefix(tmp_path, mutable_database, mock_packages):
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- a
|
||||
- pkg-a
|
||||
view: true
|
||||
"""
|
||||
)
|
||||
@@ -753,9 +835,9 @@ def test_env_view_external_prefix(tmp_path, mutable_database, mock_packages):
|
||||
external_config = io.StringIO(
|
||||
"""\
|
||||
packages:
|
||||
a:
|
||||
pkg-a:
|
||||
externals:
|
||||
- spec: a@2.0
|
||||
- spec: pkg-a@2.0
|
||||
prefix: {a_prefix}
|
||||
buildable: false
|
||||
""".format(
|
||||
@@ -1636,6 +1718,286 @@ def test_env_without_view_install(tmpdir, mock_stage, mock_fetch, install_mocker
|
||||
check_mpileaks_and_deps_in_view(view_dir)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("env_name", [True, False])
|
||||
def test_env_include_concrete_env_yaml(env_name):
|
||||
env("create", "test")
|
||||
test = ev.read("test")
|
||||
|
||||
with test:
|
||||
add("mpileaks")
|
||||
test.concretize()
|
||||
test.write()
|
||||
|
||||
environ = "test" if env_name else test.path
|
||||
|
||||
env("create", "--include-concrete", environ, "combined_env")
|
||||
|
||||
combined = ev.read("combined_env")
|
||||
combined_yaml = combined.manifest["spack"]
|
||||
|
||||
assert "include_concrete" in combined_yaml
|
||||
assert test.path in combined_yaml["include_concrete"]
|
||||
|
||||
|
||||
@pytest.mark.regression("45766")
|
||||
@pytest.mark.parametrize("format", ["v1", "v2", "v3"])
|
||||
def test_env_include_concrete_old_env(format, tmpdir):
|
||||
lockfile = os.path.join(spack.paths.test_path, "data", "legacy_env", f"{format}.lock")
|
||||
# create an env from old .lock file -- this does not update the format
|
||||
env("create", "old-env", lockfile)
|
||||
env("create", "--include-concrete", "old-env", "test")
|
||||
|
||||
assert ev.read("old-env").all_specs() == ev.read("test").all_specs()
|
||||
|
||||
|
||||
def test_env_bad_include_concrete_env():
|
||||
with pytest.raises(ev.SpackEnvironmentError):
|
||||
env("create", "--include-concrete", "nonexistant_env", "combined_env")
|
||||
|
||||
|
||||
def test_env_not_concrete_include_concrete_env():
|
||||
env("create", "test")
|
||||
test = ev.read("test")
|
||||
|
||||
with test:
|
||||
add("mpileaks")
|
||||
|
||||
with pytest.raises(ev.SpackEnvironmentError):
|
||||
env("create", "--include-concrete", "test", "combined_env")
|
||||
|
||||
|
||||
def test_env_multiple_include_concrete_envs():
|
||||
test1, test2, combined = setup_combined_multiple_env()
|
||||
|
||||
combined_yaml = combined.manifest["spack"]
|
||||
|
||||
assert test1.path in combined_yaml["include_concrete"][0]
|
||||
assert test2.path in combined_yaml["include_concrete"][1]
|
||||
|
||||
# No local specs in the combined env
|
||||
assert not combined_yaml["specs"]
|
||||
|
||||
|
||||
def test_env_include_concrete_envs_lockfile():
|
||||
test1, test2, combined = setup_combined_multiple_env()
|
||||
|
||||
combined_yaml = combined.manifest["spack"]
|
||||
|
||||
assert "include_concrete" in combined_yaml
|
||||
assert test1.path in combined_yaml["include_concrete"]
|
||||
|
||||
with open(combined.lock_path) as f:
|
||||
lockfile_as_dict = combined._read_lockfile(f)
|
||||
|
||||
assert set(
|
||||
entry["hash"] for entry in lockfile_as_dict["include_concrete"][test1.path]["roots"]
|
||||
) == set(test1.specs_by_hash)
|
||||
assert set(
|
||||
entry["hash"] for entry in lockfile_as_dict["include_concrete"][test2.path]["roots"]
|
||||
) == set(test2.specs_by_hash)
|
||||
|
||||
|
||||
def test_env_include_concrete_add_env():
|
||||
test1, test2, combined = setup_combined_multiple_env()
|
||||
|
||||
# crete new env & crecretize
|
||||
env("create", "new")
|
||||
new_env = ev.read("new")
|
||||
with new_env:
|
||||
add("mpileaks")
|
||||
|
||||
new_env.concretize()
|
||||
new_env.write()
|
||||
|
||||
# add new env to combined
|
||||
combined.included_concrete_envs.append(new_env.path)
|
||||
|
||||
# assert thing haven't changed yet
|
||||
with open(combined.lock_path) as f:
|
||||
lockfile_as_dict = combined._read_lockfile(f)
|
||||
|
||||
assert new_env.path not in lockfile_as_dict["include_concrete"].keys()
|
||||
|
||||
# concretize combined env with new env
|
||||
combined.concretize()
|
||||
combined.write()
|
||||
|
||||
# assert changes
|
||||
with open(combined.lock_path) as f:
|
||||
lockfile_as_dict = combined._read_lockfile(f)
|
||||
|
||||
assert new_env.path in lockfile_as_dict["include_concrete"].keys()
|
||||
|
||||
|
||||
def test_env_include_concrete_remove_env():
|
||||
test1, test2, combined = setup_combined_multiple_env()
|
||||
|
||||
# remove test2 from combined
|
||||
combined.included_concrete_envs = [test1.path]
|
||||
|
||||
# assert test2 is still in combined's lockfile
|
||||
with open(combined.lock_path) as f:
|
||||
lockfile_as_dict = combined._read_lockfile(f)
|
||||
|
||||
assert test2.path in lockfile_as_dict["include_concrete"].keys()
|
||||
|
||||
# reconcretize combined
|
||||
combined.concretize()
|
||||
combined.write()
|
||||
|
||||
# assert test2 is not in combined's lockfile
|
||||
with open(combined.lock_path) as f:
|
||||
lockfile_as_dict = combined._read_lockfile(f)
|
||||
|
||||
assert test2.path not in lockfile_as_dict["include_concrete"].keys()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unify", [True, False, "when_possible"])
|
||||
def test_env_include_concrete_env_reconcretized(unify):
|
||||
"""Double check to make sure that concrete_specs for the local specs is empty
|
||||
after recocnretizing.
|
||||
"""
|
||||
_, _, combined = setup_combined_multiple_env()
|
||||
|
||||
combined.unify = unify
|
||||
|
||||
with open(combined.lock_path) as f:
|
||||
lockfile_as_dict = combined._read_lockfile(f)
|
||||
|
||||
assert not lockfile_as_dict["roots"]
|
||||
assert not lockfile_as_dict["concrete_specs"]
|
||||
|
||||
combined.concretize()
|
||||
combined.write()
|
||||
|
||||
with open(combined.lock_path) as f:
|
||||
lockfile_as_dict = combined._read_lockfile(f)
|
||||
|
||||
assert not lockfile_as_dict["roots"]
|
||||
assert not lockfile_as_dict["concrete_specs"]
|
||||
|
||||
|
||||
def test_concretize_include_concrete_env():
|
||||
test1, _, combined = setup_combined_multiple_env()
|
||||
|
||||
with test1:
|
||||
add("mpileaks")
|
||||
test1.concretize()
|
||||
test1.write()
|
||||
|
||||
assert Spec("mpileaks") in test1.concretized_user_specs
|
||||
assert Spec("mpileaks") not in combined.included_concretized_user_specs[test1.path]
|
||||
|
||||
combined.concretize()
|
||||
combined.write()
|
||||
|
||||
assert Spec("mpileaks") in combined.included_concretized_user_specs[test1.path]
|
||||
|
||||
|
||||
def test_concretize_nested_include_concrete_envs():
|
||||
env("create", "test1")
|
||||
test1 = ev.read("test1")
|
||||
with test1:
|
||||
add("zlib")
|
||||
test1.concretize()
|
||||
test1.write()
|
||||
|
||||
env("create", "--include-concrete", "test1", "test2")
|
||||
test2 = ev.read("test2")
|
||||
with test2:
|
||||
add("libelf")
|
||||
test2.concretize()
|
||||
test2.write()
|
||||
|
||||
env("create", "--include-concrete", "test2", "test3")
|
||||
test3 = ev.read("test3")
|
||||
|
||||
with open(test3.lock_path) as f:
|
||||
lockfile_as_dict = test3._read_lockfile(f)
|
||||
|
||||
assert test2.path in lockfile_as_dict["include_concrete"]
|
||||
assert test1.path in lockfile_as_dict["include_concrete"][test2.path]["include_concrete"]
|
||||
|
||||
assert Spec("zlib") in test3.included_concretized_user_specs[test1.path]
|
||||
|
||||
|
||||
def test_concretize_nested_included_concrete():
|
||||
"""Confirm that nested included environments use specs concretized at
|
||||
environment creation time and change with reconcretization."""
|
||||
env("create", "test1")
|
||||
test1 = ev.read("test1")
|
||||
with test1:
|
||||
add("zlib")
|
||||
test1.concretize()
|
||||
test1.write()
|
||||
|
||||
# test2 should include test1 with zlib
|
||||
env("create", "--include-concrete", "test1", "test2")
|
||||
test2 = ev.read("test2")
|
||||
with test2:
|
||||
add("libelf")
|
||||
test2.concretize()
|
||||
test2.write()
|
||||
|
||||
assert Spec("zlib") in test2.included_concretized_user_specs[test1.path]
|
||||
|
||||
# Modify/re-concretize test1 to replace zlib with mpileaks
|
||||
with test1:
|
||||
remove("zlib")
|
||||
add("mpileaks")
|
||||
test1.concretize()
|
||||
test1.write()
|
||||
|
||||
# test3 should include the latest concretization of test1
|
||||
env("create", "--include-concrete", "test1", "test3")
|
||||
test3 = ev.read("test3")
|
||||
with test3:
|
||||
add("callpath")
|
||||
test3.concretize()
|
||||
test3.write()
|
||||
|
||||
included_specs = test3.included_concretized_user_specs[test1.path]
|
||||
assert len(included_specs) == 1
|
||||
assert Spec("mpileaks") in included_specs
|
||||
|
||||
# The last concretization of test4's included environments should have test2
|
||||
# with the original concretized test1 spec and test3 with the re-concretized
|
||||
# test1 spec.
|
||||
env("create", "--include-concrete", "test2", "--include-concrete", "test3", "test4")
|
||||
test4 = ev.read("test4")
|
||||
|
||||
def included_included_spec(path1, path2):
|
||||
included_path1 = test4.included_concrete_spec_data[path1]
|
||||
included_path2 = included_path1["include_concrete"][path2]
|
||||
return included_path2["roots"][0]["spec"]
|
||||
|
||||
included_test2_test1 = included_included_spec(test2.path, test1.path)
|
||||
assert "zlib" in included_test2_test1
|
||||
|
||||
included_test3_test1 = included_included_spec(test3.path, test1.path)
|
||||
assert "mpileaks" in included_test3_test1
|
||||
|
||||
# test4's concretized specs should reflect the original concretization.
|
||||
concrete_specs = [s for s, _ in test4.concretized_specs()]
|
||||
expected = [Spec(s) for s in ["libelf", "zlib", "mpileaks", "callpath"]]
|
||||
assert all(s in concrete_specs for s in expected)
|
||||
|
||||
# Re-concretize test2 to reflect the new concretization of included test1
|
||||
# to remove zlib and write it out so it can be picked up by test4.
|
||||
# Re-concretize test4 to reflect the re-concretization of included test2
|
||||
# and ensure that its included specs are up-to-date
|
||||
test2.concretize()
|
||||
test2.write()
|
||||
test4.concretize()
|
||||
|
||||
concrete_specs = [s for s, _ in test4.concretized_specs()]
|
||||
assert Spec("zlib") not in concrete_specs
|
||||
|
||||
# Expecting mpileaks to appear only once
|
||||
expected = [Spec(s) for s in ["libelf", "mpileaks", "callpath"]]
|
||||
assert len(concrete_specs) == 3 and all(s in concrete_specs for s in expected)
|
||||
|
||||
|
||||
def test_env_config_view_default(
|
||||
environment_from_manifest, mock_stage, mock_fetch, install_mockery
|
||||
):
|
||||
@@ -4066,8 +4428,8 @@ def test_env_view_resolves_identical_file_conflicts(tmp_path, install_mockery, m
|
||||
# view-file/bin/
|
||||
# x # expect this x to be linked
|
||||
|
||||
assert os.readlink(tmp_path / "view" / "bin" / "x") == bottom.bin.x
|
||||
assert os.readlink(tmp_path / "view" / "bin" / "y") == top.bin.y
|
||||
assert readlink(tmp_path / "view" / "bin" / "x") == bottom.bin.x
|
||||
assert readlink(tmp_path / "view" / "bin" / "y") == top.bin.y
|
||||
|
||||
|
||||
def test_env_view_ignores_different_file_conflicts(tmp_path, install_mockery, mock_fetch):
|
||||
@@ -4078,4 +4440,4 @@ def test_env_view_ignores_different_file_conflicts(tmp_path, install_mockery, mo
|
||||
install()
|
||||
prefix_dependent = e.matching_spec("view-ignore-conflict").prefix
|
||||
# The dependent's file is linked into the view
|
||||
assert os.readlink(tmp_path / "view" / "bin" / "x") == prefix_dependent.bin.x
|
||||
assert readlink(tmp_path / "view" / "bin" / "x") == prefix_dependent.bin.x
|
||||
|
@@ -349,6 +349,87 @@ def test_find_prefix_in_env(
|
||||
# Would throw error on regression
|
||||
|
||||
|
||||
def test_find_specs_include_concrete_env(mutable_mock_env_path, config, mutable_mock_repo, tmpdir):
|
||||
path = tmpdir.join("spack.yaml")
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
with open(str(path), "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
env("create", "test1", "spack.yaml")
|
||||
|
||||
test1 = ev.read("test1")
|
||||
test1.concretize()
|
||||
test1.write()
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
with open(str(path), "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- libelf
|
||||
"""
|
||||
)
|
||||
env("create", "test2", "spack.yaml")
|
||||
|
||||
test2 = ev.read("test2")
|
||||
test2.concretize()
|
||||
test2.write()
|
||||
|
||||
env("create", "--include-concrete", "test1", "--include-concrete", "test2", "combined_env")
|
||||
|
||||
with ev.read("combined_env"):
|
||||
output = find()
|
||||
|
||||
assert "No root specs" in output
|
||||
assert "Included specs" in output
|
||||
assert "mpileaks" in output
|
||||
assert "libelf" in output
|
||||
|
||||
|
||||
def test_find_specs_nested_include_concrete_env(
|
||||
mutable_mock_env_path, config, mutable_mock_repo, tmpdir
|
||||
):
|
||||
path = tmpdir.join("spack.yaml")
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
with open(str(path), "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- mpileaks
|
||||
"""
|
||||
)
|
||||
env("create", "test1", "spack.yaml")
|
||||
|
||||
test1 = ev.read("test1")
|
||||
test1.concretize()
|
||||
test1.write()
|
||||
|
||||
env("create", "--include-concrete", "test1", "test2")
|
||||
test2 = ev.read("test2")
|
||||
test2.add("libelf")
|
||||
test2.concretize()
|
||||
test2.write()
|
||||
|
||||
env("create", "--include-concrete", "test2", "test3")
|
||||
|
||||
with ev.read("test3"):
|
||||
output = find()
|
||||
|
||||
assert "No root specs" in output
|
||||
assert "Included specs" in output
|
||||
assert "mpileaks" in output
|
||||
assert "libelf" in output
|
||||
|
||||
|
||||
def test_find_loaded(database, working_env):
|
||||
output = find("--loaded", "--group")
|
||||
assert output == ""
|
||||
|
@@ -89,7 +89,7 @@ def check(pkg):
|
||||
assert pkg.run_tests
|
||||
|
||||
monkeypatch.setattr(spack.package_base.PackageBase, "unit_test_check", check)
|
||||
install("--test=all", "a")
|
||||
install("--test=all", "pkg-a")
|
||||
|
||||
|
||||
def test_install_package_already_installed(
|
||||
@@ -570,61 +570,58 @@ def test_cdash_upload_build_error(tmpdir, mock_fetch, install_mockery, capfd):
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_cdash_upload_clean_build(tmpdir, mock_fetch, install_mockery, capfd):
|
||||
# capfd interferes with Spack's capturing of e.g., Build.xml output
|
||||
with capfd.disabled():
|
||||
with tmpdir.as_cwd():
|
||||
install("--log-file=cdash_reports", "--log-format=cdash", "a")
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("a_Build.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert "</Build>" in content
|
||||
assert "<Text>" not in content
|
||||
with capfd.disabled(), tmpdir.as_cwd():
|
||||
install("--log-file=cdash_reports", "--log-format=cdash", "pkg-a")
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("pkg-a_Build.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert "</Build>" in content
|
||||
assert "<Text>" not in content
|
||||
|
||||
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_cdash_upload_extra_params(tmpdir, mock_fetch, install_mockery, capfd):
|
||||
# capfd interferes with Spack's capture of e.g., Build.xml output
|
||||
with capfd.disabled():
|
||||
with tmpdir.as_cwd():
|
||||
install(
|
||||
"--log-file=cdash_reports",
|
||||
"--log-format=cdash",
|
||||
"--cdash-build=my_custom_build",
|
||||
"--cdash-site=my_custom_site",
|
||||
"--cdash-track=my_custom_track",
|
||||
"a",
|
||||
)
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("a_Build.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert 'Site BuildName="my_custom_build - a"' in content
|
||||
assert 'Name="my_custom_site"' in content
|
||||
assert "-my_custom_track" in content
|
||||
with capfd.disabled(), tmpdir.as_cwd():
|
||||
install(
|
||||
"--log-file=cdash_reports",
|
||||
"--log-format=cdash",
|
||||
"--cdash-build=my_custom_build",
|
||||
"--cdash-site=my_custom_site",
|
||||
"--cdash-track=my_custom_track",
|
||||
"pkg-a",
|
||||
)
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("pkg-a_Build.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert 'Site BuildName="my_custom_build - pkg-a"' in content
|
||||
assert 'Name="my_custom_site"' in content
|
||||
assert "-my_custom_track" in content
|
||||
|
||||
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_cdash_buildstamp_param(tmpdir, mock_fetch, install_mockery, capfd):
|
||||
# capfd interferes with Spack's capture of e.g., Build.xml output
|
||||
with capfd.disabled():
|
||||
with tmpdir.as_cwd():
|
||||
cdash_track = "some_mocked_track"
|
||||
buildstamp_format = "%Y%m%d-%H%M-{0}".format(cdash_track)
|
||||
buildstamp = time.strftime(buildstamp_format, time.localtime(int(time.time())))
|
||||
install(
|
||||
"--log-file=cdash_reports",
|
||||
"--log-format=cdash",
|
||||
"--cdash-buildstamp={0}".format(buildstamp),
|
||||
"a",
|
||||
)
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("a_Build.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert buildstamp in content
|
||||
with capfd.disabled(), tmpdir.as_cwd():
|
||||
cdash_track = "some_mocked_track"
|
||||
buildstamp_format = "%Y%m%d-%H%M-{0}".format(cdash_track)
|
||||
buildstamp = time.strftime(buildstamp_format, time.localtime(int(time.time())))
|
||||
install(
|
||||
"--log-file=cdash_reports",
|
||||
"--log-format=cdash",
|
||||
"--cdash-buildstamp={0}".format(buildstamp),
|
||||
"pkg-a",
|
||||
)
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("pkg-a_Build.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert buildstamp in content
|
||||
|
||||
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
@@ -632,38 +629,37 @@ def test_cdash_install_from_spec_json(
|
||||
tmpdir, mock_fetch, install_mockery, capfd, mock_packages, mock_archive, config
|
||||
):
|
||||
# capfd interferes with Spack's capturing
|
||||
with capfd.disabled():
|
||||
with tmpdir.as_cwd():
|
||||
spec_json_path = str(tmpdir.join("spec.json"))
|
||||
with capfd.disabled(), tmpdir.as_cwd():
|
||||
spec_json_path = str(tmpdir.join("spec.json"))
|
||||
|
||||
pkg_spec = Spec("a")
|
||||
pkg_spec.concretize()
|
||||
pkg_spec = Spec("pkg-a")
|
||||
pkg_spec.concretize()
|
||||
|
||||
with open(spec_json_path, "w") as fd:
|
||||
fd.write(pkg_spec.to_json(hash=ht.dag_hash))
|
||||
with open(spec_json_path, "w") as fd:
|
||||
fd.write(pkg_spec.to_json(hash=ht.dag_hash))
|
||||
|
||||
install(
|
||||
"--log-format=cdash",
|
||||
"--log-file=cdash_reports",
|
||||
"--cdash-build=my_custom_build",
|
||||
"--cdash-site=my_custom_site",
|
||||
"--cdash-track=my_custom_track",
|
||||
"-f",
|
||||
spec_json_path,
|
||||
)
|
||||
install(
|
||||
"--log-format=cdash",
|
||||
"--log-file=cdash_reports",
|
||||
"--cdash-build=my_custom_build",
|
||||
"--cdash-site=my_custom_site",
|
||||
"--cdash-track=my_custom_track",
|
||||
"-f",
|
||||
spec_json_path,
|
||||
)
|
||||
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("a_Configure.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
install_command_regex = re.compile(
|
||||
r"<ConfigureCommand>(.+)</ConfigureCommand>", re.MULTILINE | re.DOTALL
|
||||
)
|
||||
m = install_command_regex.search(content)
|
||||
assert m
|
||||
install_command = m.group(1)
|
||||
assert "a@" in install_command
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("pkg-a_Configure.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
install_command_regex = re.compile(
|
||||
r"<ConfigureCommand>(.+)</ConfigureCommand>", re.MULTILINE | re.DOTALL
|
||||
)
|
||||
m = install_command_regex.search(content)
|
||||
assert m
|
||||
install_command = m.group(1)
|
||||
assert "pkg-a@" in install_command
|
||||
|
||||
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
@@ -795,15 +791,15 @@ def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock
|
||||
# ^libdwarf
|
||||
# ^mpich
|
||||
# libelf@0.8.10
|
||||
# a~bvv
|
||||
# ^b
|
||||
# a
|
||||
# ^b
|
||||
# pkg-a~bvv
|
||||
# ^pkg-b
|
||||
# pkg-a
|
||||
# ^pkg-b
|
||||
e = ev.create("test", with_view=False)
|
||||
e.add("mpileaks")
|
||||
e.add("libelf@0.8.10") # so env has both root and dep libelf specs
|
||||
e.add("a")
|
||||
e.add("a ~bvv")
|
||||
e.add("pkg-a")
|
||||
e.add("pkg-a ~bvv")
|
||||
e.concretize()
|
||||
e.write()
|
||||
env_specs = e.all_specs()
|
||||
@@ -814,9 +810,9 @@ def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock
|
||||
|
||||
# First find and remember some target concrete specs in the environment
|
||||
for e_spec in env_specs:
|
||||
if e_spec.satisfies(Spec("a ~bvv")):
|
||||
if e_spec.satisfies(Spec("pkg-a ~bvv")):
|
||||
a_spec = e_spec
|
||||
elif e_spec.name == "b":
|
||||
elif e_spec.name == "pkg-b":
|
||||
b_spec = e_spec
|
||||
elif e_spec.satisfies(Spec("mpi")):
|
||||
mpi_spec = e_spec
|
||||
@@ -839,8 +835,8 @@ def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock
|
||||
assert "You can add specs to the environment with 'spack add " in inst_out
|
||||
|
||||
# Without --add, ensure that two packages "a" get installed
|
||||
inst_out = install("a", output=str)
|
||||
assert len([x for x in e.all_specs() if x.installed and x.name == "a"]) == 2
|
||||
inst_out = install("pkg-a", output=str)
|
||||
assert len([x for x in e.all_specs() if x.installed and x.name == "pkg-a"]) == 2
|
||||
|
||||
# Install an unambiguous dependency spec (that already exists as a dep
|
||||
# in the environment) and make sure it gets installed (w/ deps),
|
||||
@@ -873,7 +869,7 @@ def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock
|
||||
# root of the environment as well as installed.
|
||||
assert b_spec not in e.roots()
|
||||
|
||||
install("--add", "b")
|
||||
install("--add", "pkg-b")
|
||||
|
||||
assert b_spec in e.roots()
|
||||
assert b_spec not in e.uninstalled_specs()
|
||||
@@ -908,7 +904,7 @@ def test_cdash_auth_token(tmpdir, mock_fetch, install_mockery, monkeypatch, capf
|
||||
# capfd interferes with Spack's capturing
|
||||
with tmpdir.as_cwd(), capfd.disabled():
|
||||
monkeypatch.setenv("SPACK_CDASH_AUTH_TOKEN", "asdf")
|
||||
out = install("-v", "--log-file=cdash_reports", "--log-format=cdash", "a")
|
||||
out = install("-v", "--log-file=cdash_reports", "--log-format=cdash", "pkg-a")
|
||||
assert "Using CDash auth token from environment" in out
|
||||
|
||||
|
||||
@@ -916,26 +912,25 @@ def test_cdash_auth_token(tmpdir, mock_fetch, install_mockery, monkeypatch, capf
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_cdash_configure_warning(tmpdir, mock_fetch, install_mockery, capfd):
|
||||
# capfd interferes with Spack's capturing of e.g., Build.xml output
|
||||
with capfd.disabled():
|
||||
with tmpdir.as_cwd():
|
||||
# Test would fail if install raised an error.
|
||||
with capfd.disabled(), tmpdir.as_cwd():
|
||||
# Test would fail if install raised an error.
|
||||
|
||||
# Ensure that even on non-x86_64 architectures, there are no
|
||||
# dependencies installed
|
||||
spec = spack.spec.Spec("configure-warning").concretized()
|
||||
spec.clear_dependencies()
|
||||
specfile = "./spec.json"
|
||||
with open(specfile, "w") as f:
|
||||
f.write(spec.to_json())
|
||||
# Ensure that even on non-x86_64 architectures, there are no
|
||||
# dependencies installed
|
||||
spec = Spec("configure-warning").concretized()
|
||||
spec.clear_dependencies()
|
||||
specfile = "./spec.json"
|
||||
with open(specfile, "w") as f:
|
||||
f.write(spec.to_json())
|
||||
|
||||
install("--log-file=cdash_reports", "--log-format=cdash", specfile)
|
||||
# Verify Configure.xml exists with expected contents.
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("Configure.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert "foo: No such file or directory" in content
|
||||
install("--log-file=cdash_reports", "--log-format=cdash", specfile)
|
||||
# Verify Configure.xml exists with expected contents.
|
||||
report_dir = tmpdir.join("cdash_reports")
|
||||
assert report_dir in tmpdir.listdir()
|
||||
report_file = report_dir.join("Configure.xml")
|
||||
assert report_file in report_dir.listdir()
|
||||
content = report_file.open().read()
|
||||
assert "foo: No such file or directory" in content
|
||||
|
||||
|
||||
@pytest.mark.not_on_windows("ArchSpec gives test platform debian rather than windows")
|
||||
@@ -952,7 +947,7 @@ def test_compiler_bootstrap(
|
||||
assert CompilerSpec("gcc@=12.0") not in compilers.all_compiler_specs()
|
||||
|
||||
# Test succeeds if it does not raise an error
|
||||
install("a%gcc@=12.0")
|
||||
install("pkg-a%gcc@=12.0")
|
||||
|
||||
|
||||
@pytest.mark.not_on_windows("Binary mirrors not supported on windows")
|
||||
@@ -992,8 +987,8 @@ def test_compiler_bootstrap_from_binary_mirror(
|
||||
# Now make sure that when the compiler is installed from binary mirror,
|
||||
# it also gets configured as a compiler. Test succeeds if it does not
|
||||
# raise an error
|
||||
install("--no-check-signature", "--cache-only", "--only", "dependencies", "b%gcc@=10.2.0")
|
||||
install("--no-cache", "--only", "package", "b%gcc@10.2.0")
|
||||
install("--no-check-signature", "--cache-only", "--only", "dependencies", "pkg-b%gcc@=10.2.0")
|
||||
install("--no-cache", "--only", "package", "pkg-b%gcc@10.2.0")
|
||||
|
||||
|
||||
@pytest.mark.not_on_windows("ArchSpec gives test platform debian rather than windows")
|
||||
@@ -1013,7 +1008,7 @@ def test_compiler_bootstrap_already_installed(
|
||||
|
||||
# Test succeeds if it does not raise an error
|
||||
install("gcc@=12.0")
|
||||
install("a%gcc@=12.0")
|
||||
install("pkg-a%gcc@=12.0")
|
||||
|
||||
|
||||
def test_install_fails_no_args(tmpdir):
|
||||
@@ -1195,7 +1190,7 @@ def test_report_filename_for_cdash(install_mockery_mutable_config, mock_fetch):
|
||||
parser = argparse.ArgumentParser()
|
||||
spack.cmd.install.setup_parser(parser)
|
||||
args = parser.parse_args(
|
||||
["--cdash-upload-url", "https://blahblah/submit.php?project=debugging", "a"]
|
||||
["--cdash-upload-url", "https://blahblah/submit.php?project=debugging", "pkg-a"]
|
||||
)
|
||||
specs = spack.cmd.install.concrete_specs_from_cli(args, {})
|
||||
filename = spack.cmd.install.report_filename(args, specs)
|
||||
|
@@ -121,7 +121,7 @@ def test_maintainers_list_packages(mock_packages, capfd):
|
||||
|
||||
|
||||
def test_maintainers_list_fails(mock_packages, capfd):
|
||||
out = maintainers("a", fail_on_error=False)
|
||||
out = maintainers("pkg-a", fail_on_error=False)
|
||||
assert not out
|
||||
assert maintainers.returncode == 1
|
||||
|
||||
|
@@ -11,6 +11,7 @@
|
||||
import spack.config
|
||||
import spack.main
|
||||
import spack.modules
|
||||
import spack.spec
|
||||
import spack.store
|
||||
|
||||
module = spack.main.SpackCommand("module")
|
||||
@@ -178,8 +179,8 @@ def test_setdefault_command(mutable_database, mutable_config):
|
||||
}
|
||||
}
|
||||
spack.config.set("modules", data)
|
||||
# Install two different versions of a package
|
||||
other_spec, preferred = "a@1.0", "a@2.0"
|
||||
# Install two different versions of pkg-a
|
||||
other_spec, preferred = "pkg-a@1.0", "pkg-a@2.0"
|
||||
|
||||
spack.spec.Spec(other_spec).concretized().package.do_install(fake=True)
|
||||
spack.spec.Spec(preferred).concretized().package.do_install(fake=True)
|
||||
|
@@ -28,8 +28,8 @@ def install(self, spec, prefix):
|
||||
pass
|
||||
"""
|
||||
|
||||
abc = set(("pkg-a", "pkg-b", "pkg-c"))
|
||||
abd = set(("pkg-a", "pkg-b", "pkg-d"))
|
||||
abc = {"mockpkg-a", "mockpkg-b", "mockpkg-c"}
|
||||
abd = {"mockpkg-a", "mockpkg-b", "mockpkg-d"}
|
||||
|
||||
|
||||
# Force all tests to use a git repository *in* the mock packages repo.
|
||||
@@ -53,27 +53,33 @@ def mock_pkg_git_repo(git, tmpdir_factory):
|
||||
git("config", "user.name", "Spack Testing")
|
||||
git("-c", "commit.gpgsign=false", "commit", "-m", "initial mock repo commit")
|
||||
|
||||
# add commit with pkg-a, pkg-b, pkg-c packages
|
||||
mkdirp("pkg-a", "pkg-b", "pkg-c")
|
||||
with open("pkg-a/package.py", "w") as f:
|
||||
# add commit with mockpkg-a, mockpkg-b, mockpkg-c packages
|
||||
mkdirp("mockpkg-a", "mockpkg-b", "mockpkg-c")
|
||||
with open("mockpkg-a/package.py", "w") as f:
|
||||
f.write(pkg_template.format(name="PkgA"))
|
||||
with open("pkg-b/package.py", "w") as f:
|
||||
with open("mockpkg-b/package.py", "w") as f:
|
||||
f.write(pkg_template.format(name="PkgB"))
|
||||
with open("pkg-c/package.py", "w") as f:
|
||||
with open("mockpkg-c/package.py", "w") as f:
|
||||
f.write(pkg_template.format(name="PkgC"))
|
||||
git("add", "pkg-a", "pkg-b", "pkg-c")
|
||||
git("-c", "commit.gpgsign=false", "commit", "-m", "add pkg-a, pkg-b, pkg-c")
|
||||
git("add", "mockpkg-a", "mockpkg-b", "mockpkg-c")
|
||||
git("-c", "commit.gpgsign=false", "commit", "-m", "add mockpkg-a, mockpkg-b, mockpkg-c")
|
||||
|
||||
# remove pkg-c, add pkg-d
|
||||
with open("pkg-b/package.py", "a") as f:
|
||||
f.write("\n# change pkg-b")
|
||||
git("add", "pkg-b")
|
||||
mkdirp("pkg-d")
|
||||
with open("pkg-d/package.py", "w") as f:
|
||||
# remove mockpkg-c, add mockpkg-d
|
||||
with open("mockpkg-b/package.py", "a") as f:
|
||||
f.write("\n# change mockpkg-b")
|
||||
git("add", "mockpkg-b")
|
||||
mkdirp("mockpkg-d")
|
||||
with open("mockpkg-d/package.py", "w") as f:
|
||||
f.write(pkg_template.format(name="PkgD"))
|
||||
git("add", "pkg-d")
|
||||
git("rm", "-rf", "pkg-c")
|
||||
git("-c", "commit.gpgsign=false", "commit", "-m", "change pkg-b, remove pkg-c, add pkg-d")
|
||||
git("add", "mockpkg-d")
|
||||
git("rm", "-rf", "mockpkg-c")
|
||||
git(
|
||||
"-c",
|
||||
"commit.gpgsign=false",
|
||||
"commit",
|
||||
"-m",
|
||||
"change mockpkg-b, remove mockpkg-c, add mockpkg-d",
|
||||
)
|
||||
|
||||
with spack.repo.use_repositories(str(repo_path)):
|
||||
yield mock_repo_packages
|
||||
@@ -86,12 +92,11 @@ def mock_pkg_names():
|
||||
# Be sure to include virtual packages since packages with stand-alone
|
||||
# tests may inherit additional tests from the virtuals they provide,
|
||||
# such as packages that implement `mpi`.
|
||||
names = set(
|
||||
return {
|
||||
name
|
||||
for name in repo.all_package_names(include_virtuals=True)
|
||||
if not name.startswith("pkg-")
|
||||
)
|
||||
return names
|
||||
if not name.startswith("mockpkg-")
|
||||
}
|
||||
|
||||
|
||||
def split(output):
|
||||
@@ -113,17 +118,17 @@ def test_mock_packages_path(mock_packages):
|
||||
|
||||
def test_pkg_add(git, mock_pkg_git_repo):
|
||||
with working_dir(mock_pkg_git_repo):
|
||||
mkdirp("pkg-e")
|
||||
with open("pkg-e/package.py", "w") as f:
|
||||
mkdirp("mockpkg-e")
|
||||
with open("mockpkg-e/package.py", "w") as f:
|
||||
f.write(pkg_template.format(name="PkgE"))
|
||||
|
||||
pkg("add", "pkg-e")
|
||||
pkg("add", "mockpkg-e")
|
||||
|
||||
with working_dir(mock_pkg_git_repo):
|
||||
try:
|
||||
assert "A pkg-e/package.py" in git("status", "--short", output=str)
|
||||
assert "A mockpkg-e/package.py" in git("status", "--short", output=str)
|
||||
finally:
|
||||
shutil.rmtree("pkg-e")
|
||||
shutil.rmtree("mockpkg-e")
|
||||
# Removing a package mid-run disrupts Spack's caching
|
||||
if spack.repo.PATH.repos[0]._fast_package_checker:
|
||||
spack.repo.PATH.repos[0]._fast_package_checker.invalidate()
|
||||
@@ -138,10 +143,10 @@ def test_pkg_list(mock_pkg_git_repo, mock_pkg_names):
|
||||
assert sorted(mock_pkg_names) == sorted(out)
|
||||
|
||||
out = split(pkg("list", "HEAD^"))
|
||||
assert sorted(mock_pkg_names.union(["pkg-a", "pkg-b", "pkg-c"])) == sorted(out)
|
||||
assert sorted(mock_pkg_names.union(["mockpkg-a", "mockpkg-b", "mockpkg-c"])) == sorted(out)
|
||||
|
||||
out = split(pkg("list", "HEAD"))
|
||||
assert sorted(mock_pkg_names.union(["pkg-a", "pkg-b", "pkg-d"])) == sorted(out)
|
||||
assert sorted(mock_pkg_names.union(["mockpkg-a", "mockpkg-b", "mockpkg-d"])) == sorted(out)
|
||||
|
||||
# test with three dots to make sure pkg calls `git merge-base`
|
||||
out = split(pkg("list", "HEAD^^..."))
|
||||
@@ -151,25 +156,25 @@ def test_pkg_list(mock_pkg_git_repo, mock_pkg_names):
|
||||
@pytest.mark.not_on_windows("stdout format conflict")
|
||||
def test_pkg_diff(mock_pkg_git_repo, mock_pkg_names):
|
||||
out = split(pkg("diff", "HEAD^^", "HEAD^"))
|
||||
assert out == ["HEAD^:", "pkg-a", "pkg-b", "pkg-c"]
|
||||
assert out == ["HEAD^:", "mockpkg-a", "mockpkg-b", "mockpkg-c"]
|
||||
|
||||
out = split(pkg("diff", "HEAD^^", "HEAD"))
|
||||
assert out == ["HEAD:", "pkg-a", "pkg-b", "pkg-d"]
|
||||
assert out == ["HEAD:", "mockpkg-a", "mockpkg-b", "mockpkg-d"]
|
||||
|
||||
out = split(pkg("diff", "HEAD^", "HEAD"))
|
||||
assert out == ["HEAD^:", "pkg-c", "HEAD:", "pkg-d"]
|
||||
assert out == ["HEAD^:", "mockpkg-c", "HEAD:", "mockpkg-d"]
|
||||
|
||||
|
||||
@pytest.mark.not_on_windows("stdout format conflict")
|
||||
def test_pkg_added(mock_pkg_git_repo):
|
||||
out = split(pkg("added", "HEAD^^", "HEAD^"))
|
||||
assert ["pkg-a", "pkg-b", "pkg-c"] == out
|
||||
assert ["mockpkg-a", "mockpkg-b", "mockpkg-c"] == out
|
||||
|
||||
out = split(pkg("added", "HEAD^^", "HEAD"))
|
||||
assert ["pkg-a", "pkg-b", "pkg-d"] == out
|
||||
assert ["mockpkg-a", "mockpkg-b", "mockpkg-d"] == out
|
||||
|
||||
out = split(pkg("added", "HEAD^", "HEAD"))
|
||||
assert ["pkg-d"] == out
|
||||
assert ["mockpkg-d"] == out
|
||||
|
||||
out = split(pkg("added", "HEAD", "HEAD"))
|
||||
assert out == []
|
||||
@@ -184,7 +189,7 @@ def test_pkg_removed(mock_pkg_git_repo):
|
||||
assert out == []
|
||||
|
||||
out = split(pkg("removed", "HEAD^", "HEAD"))
|
||||
assert out == ["pkg-c"]
|
||||
assert out == ["mockpkg-c"]
|
||||
|
||||
|
||||
@pytest.mark.not_on_windows("stdout format conflict")
|
||||
@@ -196,34 +201,34 @@ def test_pkg_changed(mock_pkg_git_repo):
|
||||
assert out == []
|
||||
|
||||
out = split(pkg("changed", "--type", "a", "HEAD^^", "HEAD^"))
|
||||
assert out == ["pkg-a", "pkg-b", "pkg-c"]
|
||||
assert out == ["mockpkg-a", "mockpkg-b", "mockpkg-c"]
|
||||
|
||||
out = split(pkg("changed", "--type", "r", "HEAD^^", "HEAD^"))
|
||||
assert out == []
|
||||
|
||||
out = split(pkg("changed", "--type", "ar", "HEAD^^", "HEAD^"))
|
||||
assert out == ["pkg-a", "pkg-b", "pkg-c"]
|
||||
assert out == ["mockpkg-a", "mockpkg-b", "mockpkg-c"]
|
||||
|
||||
out = split(pkg("changed", "--type", "arc", "HEAD^^", "HEAD^"))
|
||||
assert out == ["pkg-a", "pkg-b", "pkg-c"]
|
||||
assert out == ["mockpkg-a", "mockpkg-b", "mockpkg-c"]
|
||||
|
||||
out = split(pkg("changed", "HEAD^", "HEAD"))
|
||||
assert out == ["pkg-b"]
|
||||
assert out == ["mockpkg-b"]
|
||||
|
||||
out = split(pkg("changed", "--type", "c", "HEAD^", "HEAD"))
|
||||
assert out == ["pkg-b"]
|
||||
assert out == ["mockpkg-b"]
|
||||
|
||||
out = split(pkg("changed", "--type", "a", "HEAD^", "HEAD"))
|
||||
assert out == ["pkg-d"]
|
||||
assert out == ["mockpkg-d"]
|
||||
|
||||
out = split(pkg("changed", "--type", "r", "HEAD^", "HEAD"))
|
||||
assert out == ["pkg-c"]
|
||||
assert out == ["mockpkg-c"]
|
||||
|
||||
out = split(pkg("changed", "--type", "ar", "HEAD^", "HEAD"))
|
||||
assert out == ["pkg-c", "pkg-d"]
|
||||
assert out == ["mockpkg-c", "mockpkg-d"]
|
||||
|
||||
out = split(pkg("changed", "--type", "arc", "HEAD^", "HEAD"))
|
||||
assert out == ["pkg-b", "pkg-c", "pkg-d"]
|
||||
assert out == ["mockpkg-b", "mockpkg-c", "mockpkg-d"]
|
||||
|
||||
# invalid type argument
|
||||
with pytest.raises(spack.main.SpackCommandError):
|
||||
@@ -289,7 +294,7 @@ def test_pkg_canonical_source(mock_packages):
|
||||
|
||||
|
||||
def test_pkg_hash(mock_packages):
|
||||
output = pkg("hash", "a", "b").strip().split()
|
||||
output = pkg("hash", "pkg-a", "pkg-b").strip().split()
|
||||
assert len(output) == 2 and all(len(elt) == 32 for elt in output)
|
||||
|
||||
output = pkg("hash", "multimethod").strip().split()
|
||||
|
@@ -58,7 +58,7 @@ def test_spec_concretizer_args(mutable_config, mutable_database, do_not_check_ru
|
||||
def test_spec_parse_dependency_variant_value():
|
||||
"""Verify that we can provide multiple key=value variants to multiple separate
|
||||
packages within a spec string."""
|
||||
output = spec("multivalue-variant fee=barbaz ^ a foobar=baz")
|
||||
output = spec("multivalue-variant fee=barbaz ^ pkg-a foobar=baz")
|
||||
|
||||
assert "fee=barbaz" in output
|
||||
assert "foobar=baz" in output
|
||||
|
@@ -10,10 +10,14 @@
|
||||
|
||||
from llnl.util.filesystem import copy_tree
|
||||
|
||||
import spack.cmd.common.arguments
|
||||
import spack.cmd.install
|
||||
import spack.cmd.test
|
||||
import spack.config
|
||||
import spack.install_test
|
||||
import spack.package_base
|
||||
import spack.paths
|
||||
import spack.spec
|
||||
import spack.store
|
||||
from spack.install_test import TestStatus
|
||||
from spack.main import SpackCommand
|
||||
|
@@ -24,6 +24,8 @@
|
||||
import spack.platforms
|
||||
import spack.repo
|
||||
import spack.solver.asp
|
||||
import spack.store
|
||||
import spack.util.file_cache
|
||||
import spack.util.libc
|
||||
import spack.variant as vt
|
||||
from spack.concretize import find_spec
|
||||
@@ -404,7 +406,7 @@ def test_compiler_flags_from_compiler_and_dependent(self):
|
||||
def test_compiler_flags_differ_identical_compilers(self, mutable_config, clang12_with_flags):
|
||||
mutable_config.set("compilers", [clang12_with_flags])
|
||||
# Correct arch to use test compiler that has flags
|
||||
spec = Spec("a %clang@12.2.0 platform=test os=fe target=fe")
|
||||
spec = Spec("pkg-a %clang@12.2.0 platform=test os=fe target=fe")
|
||||
|
||||
# Get the compiler that matches the spec (
|
||||
compiler = spack.compilers.compiler_for_spec("clang@=12.2.0", spec.architecture)
|
||||
@@ -473,7 +475,7 @@ def test_architecture_deep_inheritance(self, mock_targets, compiler_factory):
|
||||
assert s.architecture.target == spec.architecture.target
|
||||
|
||||
def test_compiler_flags_from_user_are_grouped(self):
|
||||
spec = Spec('a%gcc cflags="-O -foo-flag foo-val" platform=test')
|
||||
spec = Spec('pkg-a%gcc cflags="-O -foo-flag foo-val" platform=test')
|
||||
spec.concretize()
|
||||
cflags = spec.compiler_flags["cflags"]
|
||||
assert any(x == "-foo-flag foo-val" for x in cflags)
|
||||
@@ -581,20 +583,20 @@ def test_concretize_propagate_multivalue_variant(self):
|
||||
spec = Spec("multivalue-variant foo==baz,fee")
|
||||
spec.concretize()
|
||||
|
||||
assert spec.satisfies("^a foo=baz,fee")
|
||||
assert spec.satisfies("^b foo=baz,fee")
|
||||
assert not spec.satisfies("^a foo=bar")
|
||||
assert not spec.satisfies("^b foo=bar")
|
||||
assert spec.satisfies("^pkg-a foo=baz,fee")
|
||||
assert spec.satisfies("^pkg-b foo=baz,fee")
|
||||
assert not spec.satisfies("^pkg-a foo=bar")
|
||||
assert not spec.satisfies("^pkg-b foo=bar")
|
||||
|
||||
def test_no_matching_compiler_specs(self, mock_low_high_config):
|
||||
# only relevant when not building compilers as needed
|
||||
with spack.concretize.enable_compiler_existence_check():
|
||||
s = Spec("a %gcc@=0.0.0")
|
||||
s = Spec("pkg-a %gcc@=0.0.0")
|
||||
with pytest.raises(spack.concretize.UnavailableCompilerVersionError):
|
||||
s.concretize()
|
||||
|
||||
def test_no_compilers_for_arch(self):
|
||||
s = Spec("a arch=linux-rhel0-x86_64")
|
||||
s = Spec("pkg-a arch=linux-rhel0-x86_64")
|
||||
with pytest.raises(spack.error.SpackError):
|
||||
s.concretize()
|
||||
|
||||
@@ -803,7 +805,7 @@ def test_regression_issue_7941(self):
|
||||
# The string representation of a spec containing
|
||||
# an explicit multi-valued variant and a dependency
|
||||
# might be parsed differently than the originating spec
|
||||
s = Spec("a foobar=bar ^b")
|
||||
s = Spec("pkg-a foobar=bar ^pkg-b")
|
||||
t = Spec(str(s))
|
||||
|
||||
s.concretize()
|
||||
@@ -1183,14 +1185,14 @@ def test_conditional_provides_or_depends_on(self):
|
||||
[
|
||||
# Check that True is treated correctly and attaches test deps
|
||||
# to all nodes in the DAG
|
||||
("a", True, ["a"], []),
|
||||
("a foobar=bar", True, ["a", "b"], []),
|
||||
("pkg-a", True, ["pkg-a"], []),
|
||||
("pkg-a foobar=bar", True, ["pkg-a", "pkg-b"], []),
|
||||
# Check that a list of names activates the dependency only for
|
||||
# packages in that list
|
||||
("a foobar=bar", ["a"], ["a"], ["b"]),
|
||||
("a foobar=bar", ["b"], ["b"], ["a"]),
|
||||
("pkg-a foobar=bar", ["pkg-a"], ["pkg-a"], ["pkg-b"]),
|
||||
("pkg-a foobar=bar", ["pkg-b"], ["pkg-b"], ["pkg-a"]),
|
||||
# Check that False disregard test dependencies
|
||||
("a foobar=bar", False, [], ["a", "b"]),
|
||||
("pkg-a foobar=bar", False, [], ["pkg-a", "pkg-b"]),
|
||||
],
|
||||
)
|
||||
def test_activating_test_dependencies(self, spec_str, tests_arg, with_dep, without_dep):
|
||||
@@ -1249,7 +1251,7 @@ def test_custom_compiler_version(self, mutable_config, compiler_factory, monkeyp
|
||||
"compilers", [compiler_factory(spec="gcc@10foo", operating_system="redhat6")]
|
||||
)
|
||||
monkeypatch.setattr(spack.compiler.Compiler, "real_version", "10.2.1")
|
||||
s = Spec("a %gcc@10foo os=redhat6").concretized()
|
||||
s = Spec("pkg-a %gcc@10foo os=redhat6").concretized()
|
||||
assert "%gcc@10foo" in s
|
||||
|
||||
def test_all_patches_applied(self):
|
||||
@@ -1393,10 +1395,10 @@ def test_no_reuse_when_variant_condition_does_not_hold(self, mutable_database, m
|
||||
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
|
||||
def test_reuse_with_flags(self, mutable_database, mutable_config):
|
||||
spack.config.set("concretizer:reuse", True)
|
||||
spec = Spec("a cflags=-g cxxflags=-g").concretized()
|
||||
spec = Spec("pkg-a cflags=-g cxxflags=-g").concretized()
|
||||
spack.store.STORE.db.add(spec, None)
|
||||
|
||||
testspec = Spec("a cflags=-g")
|
||||
testspec = Spec("pkg-a cflags=-g")
|
||||
testspec.concretize()
|
||||
assert testspec == spec
|
||||
|
||||
@@ -1739,49 +1741,49 @@ def test_reuse_with_unknown_namespace_dont_raise(
|
||||
self, temporary_store, mock_custom_repository
|
||||
):
|
||||
with spack.repo.use_repositories(mock_custom_repository, override=False):
|
||||
s = Spec("c").concretized()
|
||||
s = Spec("pkg-c").concretized()
|
||||
assert s.namespace != "builtin.mock"
|
||||
s.package.do_install(fake=True, explicit=True)
|
||||
|
||||
with spack.config.override("concretizer:reuse", True):
|
||||
s = Spec("c").concretized()
|
||||
s = Spec("pkg-c").concretized()
|
||||
assert s.namespace == "builtin.mock"
|
||||
|
||||
@pytest.mark.regression("28259")
|
||||
def test_reuse_with_unknown_package_dont_raise(self, tmpdir, temporary_store, monkeypatch):
|
||||
builder = spack.repo.MockRepositoryBuilder(tmpdir.mkdir("mock.repo"), namespace="myrepo")
|
||||
builder.add_package("c")
|
||||
builder.add_package("pkg-c")
|
||||
with spack.repo.use_repositories(builder.root, override=False):
|
||||
s = Spec("c").concretized()
|
||||
s = Spec("pkg-c").concretized()
|
||||
assert s.namespace == "myrepo"
|
||||
s.package.do_install(fake=True, explicit=True)
|
||||
|
||||
del sys.modules["spack.pkg.myrepo.c"]
|
||||
del sys.modules["spack.pkg.myrepo.pkg-c"]
|
||||
del sys.modules["spack.pkg.myrepo"]
|
||||
builder.remove("c")
|
||||
builder.remove("pkg-c")
|
||||
with spack.repo.use_repositories(builder.root, override=False) as repos:
|
||||
# TODO (INJECT CONFIGURATION): unclear why the cache needs to be invalidated explicitly
|
||||
repos.repos[0]._pkg_checker.invalidate()
|
||||
with spack.config.override("concretizer:reuse", True):
|
||||
s = Spec("c").concretized()
|
||||
s = Spec("pkg-c").concretized()
|
||||
assert s.namespace == "builtin.mock"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"specs,expected",
|
||||
"specs,expected,libc_offset",
|
||||
[
|
||||
(["libelf", "libelf@0.8.10"], 1),
|
||||
(["libdwarf%gcc", "libelf%clang"], 2),
|
||||
(["libdwarf%gcc", "libdwarf%clang"], 3),
|
||||
(["libdwarf^libelf@0.8.12", "libdwarf^libelf@0.8.13"], 4),
|
||||
(["hdf5", "zmpi"], 3),
|
||||
(["hdf5", "mpich"], 2),
|
||||
(["hdf5^zmpi", "mpich"], 4),
|
||||
(["mpi", "zmpi"], 2),
|
||||
(["mpi", "mpich"], 1),
|
||||
(["libelf", "libelf@0.8.10"], 1, 1),
|
||||
(["libdwarf%gcc", "libelf%clang"], 2, 1),
|
||||
(["libdwarf%gcc", "libdwarf%clang"], 3, 2),
|
||||
(["libdwarf^libelf@0.8.12", "libdwarf^libelf@0.8.13"], 4, 1),
|
||||
(["hdf5", "zmpi"], 3, 1),
|
||||
(["hdf5", "mpich"], 2, 1),
|
||||
(["hdf5^zmpi", "mpich"], 4, 1),
|
||||
(["mpi", "zmpi"], 2, 1),
|
||||
(["mpi", "mpich"], 1, 1),
|
||||
],
|
||||
)
|
||||
@pytest.mark.only_clingo("Original concretizer cannot concretize in rounds")
|
||||
def test_best_effort_coconcretize(self, specs, expected):
|
||||
def test_best_effort_coconcretize(self, specs, expected, libc_offset):
|
||||
specs = [Spec(s) for s in specs]
|
||||
solver = spack.solver.asp.Solver()
|
||||
solver.reuse = False
|
||||
@@ -1790,7 +1792,9 @@ def test_best_effort_coconcretize(self, specs, expected):
|
||||
for s in result.specs:
|
||||
concrete_specs.update(s.traverse())
|
||||
|
||||
libc_offset = 1 if spack.solver.asp.using_libc_compatibility() else 0
|
||||
if not spack.solver.asp.using_libc_compatibility():
|
||||
libc_offset = 0
|
||||
|
||||
assert len(concrete_specs) == expected + libc_offset
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -1892,20 +1896,20 @@ def test_misleading_error_message_on_version(self, mutable_database):
|
||||
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
|
||||
def test_version_weight_and_provenance(self):
|
||||
"""Test package preferences during coconcretization."""
|
||||
reusable_specs = [Spec(spec_str).concretized() for spec_str in ("b@0.9", "b@1.0")]
|
||||
root_spec = Spec("a foobar=bar")
|
||||
reusable_specs = [Spec(spec_str).concretized() for spec_str in ("pkg-b@0.9", "pkg-b@1.0")]
|
||||
root_spec = Spec("pkg-a foobar=bar")
|
||||
|
||||
with spack.config.override("concretizer:reuse", True):
|
||||
solver = spack.solver.asp.Solver()
|
||||
setup = spack.solver.asp.SpackSolverSetup()
|
||||
result, _, _ = solver.driver.solve(setup, [root_spec], reuse=reusable_specs)
|
||||
# The result here should have a single spec to build ('a')
|
||||
# and it should be using b@1.0 with a version badness of 2
|
||||
# The result here should have a single spec to build ('pkg-a')
|
||||
# and it should be using pkg-b@1.0 with a version badness of 2
|
||||
# The provenance is:
|
||||
# version_declared("b","1.0",0,"package_py").
|
||||
# version_declared("b","0.9",1,"package_py").
|
||||
# version_declared("b","1.0",2,"installed").
|
||||
# version_declared("b","0.9",3,"installed").
|
||||
# version_declared("pkg-b","1.0",0,"package_py").
|
||||
# version_declared("pkg-b","0.9",1,"package_py").
|
||||
# version_declared("pkg-b","1.0",2,"installed").
|
||||
# version_declared("pkg-b","0.9",3,"installed").
|
||||
#
|
||||
# Depending on the target, it may also use gnuconfig
|
||||
result_spec = result.specs[0]
|
||||
@@ -1914,16 +1918,16 @@ def test_version_weight_and_provenance(self):
|
||||
libc_offset = 1 if spack.solver.asp.using_libc_compatibility() else 0
|
||||
criteria = [
|
||||
(num_specs - 1 - libc_offset, None, "number of packages to build (vs. reuse)"),
|
||||
(2, 0, "version badness"),
|
||||
(2, 0, "version badness (non roots)"),
|
||||
]
|
||||
|
||||
for criterion in criteria:
|
||||
assert criterion in result.criteria, result_spec
|
||||
assert result_spec.satisfies("^b@1.0")
|
||||
assert criterion in result.criteria, criterion
|
||||
assert result_spec.satisfies("^pkg-b@1.0")
|
||||
|
||||
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
|
||||
def test_reuse_succeeds_with_config_compatible_os(self):
|
||||
root_spec = Spec("b")
|
||||
root_spec = Spec("pkg-b")
|
||||
s = root_spec.concretized()
|
||||
other_os = s.copy()
|
||||
mock_os = "ubuntu2204"
|
||||
@@ -2187,7 +2191,7 @@ def test_external_python_extension_find_unified_python(self):
|
||||
"specs",
|
||||
[
|
||||
["mpileaks^ callpath ^dyninst@8.1.1:8 ^mpich2@1.3:1"],
|
||||
["multivalue-variant ^a@2:2"],
|
||||
["multivalue-variant ^pkg-a@2:2"],
|
||||
["v1-consumer ^conditional-provider@1:1 +disable-v1"],
|
||||
],
|
||||
)
|
||||
@@ -2226,9 +2230,9 @@ def test_unsolved_specs_raises_error(self, monkeypatch, mock_packages, config):
|
||||
def test_clear_error_when_unknown_compiler_requested(self, mock_packages, config):
|
||||
"""Tests that the solver can report a case where the compiler cannot be set"""
|
||||
with pytest.raises(
|
||||
spack.error.UnsatisfiableSpecError, match="Cannot set the required compiler: a%foo"
|
||||
spack.error.UnsatisfiableSpecError, match="Cannot set the required compiler: pkg-a%foo"
|
||||
):
|
||||
Spec("a %foo").concretized()
|
||||
Spec("pkg-a %foo").concretized()
|
||||
|
||||
@pytest.mark.regression("36339")
|
||||
def test_compiler_match_constraints_when_selected(self):
|
||||
@@ -2264,7 +2268,7 @@ def test_compiler_match_constraints_when_selected(self):
|
||||
},
|
||||
]
|
||||
spack.config.set("compilers", compiler_configuration)
|
||||
s = Spec("a %gcc@:11").concretized()
|
||||
s = Spec("pkg-a %gcc@:11").concretized()
|
||||
assert s.compiler.version == ver("=11.1.0"), s
|
||||
|
||||
@pytest.mark.regression("36339")
|
||||
@@ -2285,7 +2289,7 @@ def test_compiler_with_custom_non_numeric_version(self, mock_executable):
|
||||
}
|
||||
]
|
||||
spack.config.set("compilers", compiler_configuration)
|
||||
s = Spec("a %gcc@foo").concretized()
|
||||
s = Spec("pkg-a %gcc@foo").concretized()
|
||||
assert s.compiler.version == ver("=foo")
|
||||
|
||||
@pytest.mark.regression("36628")
|
||||
@@ -2311,7 +2315,7 @@ def test_concretization_with_compilers_supporting_target_any(self):
|
||||
]
|
||||
|
||||
with spack.config.override("compilers", compiler_configuration):
|
||||
s = spack.spec.Spec("a").concretized()
|
||||
s = Spec("pkg-a").concretized()
|
||||
assert s.satisfies("%gcc@12.1.0")
|
||||
|
||||
@pytest.mark.parametrize("spec_str", ["mpileaks", "mpileaks ^mpich"])
|
||||
@@ -2346,7 +2350,7 @@ def test_dont_define_new_version_from_input_if_checksum_required(self, working_e
|
||||
with pytest.raises(spack.error.UnsatisfiableSpecError):
|
||||
# normally spack concretizes to @=3.0 if it's not defined in package.py, except
|
||||
# when checksums are required
|
||||
Spec("a@=3.0").concretized()
|
||||
Spec("pkg-a@=3.0").concretized()
|
||||
|
||||
@pytest.mark.regression("39570")
|
||||
@pytest.mark.db
|
||||
@@ -2446,7 +2450,7 @@ def _default_libc(self):
|
||||
spack.util.libc, "libc_from_current_python_process", lambda: Spec("glibc@=2.28")
|
||||
)
|
||||
mutable_config.set("config:install_missing_compilers", True)
|
||||
s = Spec("a %gcc@=13.2.0").concretized()
|
||||
s = Spec("pkg-a %gcc@=13.2.0").concretized()
|
||||
assert s.satisfies("%gcc@13.2.0")
|
||||
|
||||
@pytest.mark.regression("43267")
|
||||
@@ -2464,6 +2468,7 @@ def test_spec_with_build_dep_from_json(self, tmp_path):
|
||||
assert s["dttop"].dag_hash() == build_dep.dag_hash()
|
||||
|
||||
@pytest.mark.regression("44040")
|
||||
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
|
||||
def test_exclude_specs_from_reuse(self, monkeypatch):
|
||||
"""Tests that we can exclude a spec from reuse when concretizing, and that the spec
|
||||
is not added back to the solve as a dependency of another reusable spec.
|
||||
@@ -2503,6 +2508,121 @@ def test_exclude_specs_from_reuse(self, monkeypatch):
|
||||
for dep in result["dyninst"].traverse(root=False):
|
||||
assert dep.dag_hash() == reused[dep.name].dag_hash()
|
||||
|
||||
@pytest.mark.regression("44091")
|
||||
@pytest.mark.parametrize(
|
||||
"included_externals",
|
||||
[
|
||||
["deprecated-versions"],
|
||||
# Try the empty list, to ensure that in that case everything will be included
|
||||
# since filtering should happen only when the list is non-empty
|
||||
[],
|
||||
],
|
||||
)
|
||||
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
|
||||
def test_include_specs_from_externals_and_libcs(
|
||||
self, included_externals, mutable_config, tmp_path
|
||||
):
|
||||
"""Tests that when we include specs from externals, we always include libcs."""
|
||||
mutable_config.set(
|
||||
"packages",
|
||||
{
|
||||
"deprecated-versions": {
|
||||
"externals": [{"spec": "deprecated-versions@1.1.0", "prefix": str(tmp_path)}]
|
||||
}
|
||||
},
|
||||
)
|
||||
request_str = "deprecated-client"
|
||||
|
||||
# When using the external the version is selected even if deprecated
|
||||
with spack.config.override(
|
||||
"concretizer:reuse", {"from": [{"type": "external", "include": included_externals}]}
|
||||
):
|
||||
result = Spec(request_str).concretized()
|
||||
|
||||
assert result["deprecated-versions"].satisfies("@1.1.0")
|
||||
|
||||
# When excluding it, we pick the non-deprecated version
|
||||
with spack.config.override(
|
||||
"concretizer:reuse",
|
||||
{"from": [{"type": "external", "exclude": ["deprecated-versions"]}]},
|
||||
):
|
||||
result = Spec(request_str).concretized()
|
||||
|
||||
assert result["deprecated-versions"].satisfies("@1.0.0")
|
||||
|
||||
@pytest.mark.regression("44085")
|
||||
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
|
||||
def test_can_reuse_concrete_externals_for_dependents(self, mutable_config, tmp_path):
|
||||
"""Test that external specs that are in the DB can be reused. This means they are
|
||||
preferred to concretizing another external from packages.yaml
|
||||
"""
|
||||
packages_yaml = {
|
||||
"externaltool": {"externals": [{"spec": "externaltool@2.0", "prefix": "/fake/path"}]}
|
||||
}
|
||||
mutable_config.set("packages", packages_yaml)
|
||||
# Concretize with gcc@9 to get a suboptimal spec, since we have gcc@10 available
|
||||
external_spec = Spec("externaltool@2 %gcc@9").concretized()
|
||||
assert external_spec.external
|
||||
|
||||
root_specs = [Spec("sombrero")]
|
||||
with spack.config.override("concretizer:reuse", True):
|
||||
solver = spack.solver.asp.Solver()
|
||||
setup = spack.solver.asp.SpackSolverSetup()
|
||||
result, _, _ = solver.driver.solve(setup, root_specs, reuse=[external_spec])
|
||||
|
||||
assert len(result.specs) == 1
|
||||
sombrero = result.specs[0]
|
||||
assert sombrero["externaltool"].dag_hash() == external_spec.dag_hash()
|
||||
|
||||
@pytest.mark.regression("45321")
|
||||
@pytest.mark.parametrize(
|
||||
"corrupted_str",
|
||||
[
|
||||
"cmake@3.4.3 foo=bar", # cmake has no variant "foo"
|
||||
"mvdefaults@1.0 foo=a,d", # variant "foo" has no value "d"
|
||||
"cmake %gcc", # spec has no version
|
||||
],
|
||||
)
|
||||
def test_corrupted_external_does_not_halt_concretization(self, corrupted_str, mutable_config):
|
||||
"""Tests that having a wrong variant in an external spec doesn't stop concretization"""
|
||||
corrupted_spec = Spec(corrupted_str)
|
||||
packages_yaml = {
|
||||
f"{corrupted_spec.name}": {
|
||||
"externals": [{"spec": corrupted_str, "prefix": "/dev/null"}]
|
||||
}
|
||||
}
|
||||
mutable_config.set("packages", packages_yaml)
|
||||
# Assert we don't raise due to the corrupted external entry above
|
||||
s = Spec("pkg-a").concretized()
|
||||
assert s.concrete
|
||||
|
||||
@pytest.mark.regression("44828")
|
||||
@pytest.mark.not_on_windows("Tests use linux paths")
|
||||
def test_correct_external_is_selected_from_packages_yaml(self, mutable_config):
|
||||
"""Tests that when filtering external specs, the correct external is selected to
|
||||
reconstruct the prefix, and other external attributes.
|
||||
"""
|
||||
packages_yaml = {
|
||||
"cmake": {
|
||||
"externals": [
|
||||
{"spec": "cmake@3.23.1 %gcc", "prefix": "/tmp/prefix1"},
|
||||
{"spec": "cmake@3.23.1 %clang", "prefix": "/tmp/prefix2"},
|
||||
]
|
||||
}
|
||||
}
|
||||
concretizer_yaml = {
|
||||
"reuse": {"roots": True, "from": [{"type": "external", "exclude": ["%gcc"]}]}
|
||||
}
|
||||
mutable_config.set("packages", packages_yaml)
|
||||
mutable_config.set("concretizer", concretizer_yaml)
|
||||
|
||||
s = Spec("cmake").concretized()
|
||||
|
||||
# Check that we got the properties from the right external
|
||||
assert s.external
|
||||
assert s.satisfies("%clang")
|
||||
assert s.prefix == "/tmp/prefix2"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def duplicates_test_repository():
|
||||
@@ -2704,7 +2824,9 @@ def test_drop_moving_targets(v_str, v_opts, checksummed):
|
||||
class TestConcreteSpecsByHash:
|
||||
"""Tests the container of concrete specs"""
|
||||
|
||||
@pytest.mark.parametrize("input_specs", [["a"], ["a foobar=bar", "b"], ["a foobar=baz", "b"]])
|
||||
@pytest.mark.parametrize(
|
||||
"input_specs", [["pkg-a"], ["pkg-a foobar=bar", "pkg-b"], ["pkg-a foobar=baz", "pkg-b"]]
|
||||
)
|
||||
def test_adding_specs(self, input_specs, default_mock_concretization):
|
||||
"""Tests that concrete specs in the container are equivalent, but stored as different
|
||||
objects in memory.
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
import archspec.cpu
|
||||
|
||||
import spack.config
|
||||
import spack.paths
|
||||
import spack.repo
|
||||
import spack.solver.asp
|
||||
@@ -47,8 +48,8 @@ def enable_runtimes():
|
||||
|
||||
|
||||
def test_correct_gcc_runtime_is_injected_as_dependency(runtime_repo):
|
||||
s = spack.spec.Spec("a%gcc@10.2.1 ^b%gcc@9.4.0").concretized()
|
||||
a, b = s["a"], s["b"]
|
||||
s = spack.spec.Spec("pkg-a%gcc@10.2.1 ^pkg-b%gcc@9.4.0").concretized()
|
||||
a, b = s["pkg-a"], s["pkg-b"]
|
||||
|
||||
# Both a and b should depend on the same gcc-runtime directly
|
||||
assert a.dependencies("gcc-runtime") == b.dependencies("gcc-runtime")
|
||||
@@ -61,16 +62,16 @@ def test_correct_gcc_runtime_is_injected_as_dependency(runtime_repo):
|
||||
def test_external_nodes_do_not_have_runtimes(runtime_repo, mutable_config, tmp_path):
|
||||
"""Tests that external nodes don't have runtime dependencies."""
|
||||
|
||||
packages_yaml = {"b": {"externals": [{"spec": "b@1.0", "prefix": f"{str(tmp_path)}"}]}}
|
||||
packages_yaml = {"pkg-b": {"externals": [{"spec": "pkg-b@1.0", "prefix": f"{str(tmp_path)}"}]}}
|
||||
spack.config.set("packages", packages_yaml)
|
||||
|
||||
s = spack.spec.Spec("a%gcc@10.2.1").concretized()
|
||||
s = spack.spec.Spec("pkg-a%gcc@10.2.1").concretized()
|
||||
|
||||
a, b = s["a"], s["b"]
|
||||
a, b = s["pkg-a"], s["pkg-b"]
|
||||
|
||||
# Since b is an external, it doesn't depend on gcc-runtime
|
||||
assert a.dependencies("gcc-runtime")
|
||||
assert a.dependencies("b")
|
||||
assert a.dependencies("pkg-b")
|
||||
assert not b.dependencies("gcc-runtime")
|
||||
|
||||
|
||||
@@ -78,23 +79,36 @@ def test_external_nodes_do_not_have_runtimes(runtime_repo, mutable_config, tmp_p
|
||||
"root_str,reused_str,expected,nruntime",
|
||||
[
|
||||
# The reused runtime is older than we need, thus we'll add a more recent one for a
|
||||
("a%gcc@10.2.1", "b%gcc@9.4.0", {"a": "gcc-runtime@10.2.1", "b": "gcc-runtime@9.4.0"}, 2),
|
||||
# The root is compiled with an older compiler, thus we'll reuse the runtime from b
|
||||
("a%gcc@9.4.0", "b%gcc@10.2.1", {"a": "gcc-runtime@10.2.1", "b": "gcc-runtime@10.2.1"}, 1),
|
||||
(
|
||||
"pkg-a%gcc@10.2.1",
|
||||
"pkg-b%gcc@9.4.0",
|
||||
{"pkg-a": "gcc-runtime@10.2.1", "pkg-b": "gcc-runtime@9.4.0"},
|
||||
2,
|
||||
),
|
||||
# The root is compiled with an older compiler, thus we'll NOT reuse the runtime from b
|
||||
(
|
||||
"pkg-a%gcc@9.4.0",
|
||||
"pkg-b%gcc@10.2.1",
|
||||
{"pkg-a": "gcc-runtime@9.4.0", "pkg-b": "gcc-runtime@9.4.0"},
|
||||
1,
|
||||
),
|
||||
# Same as before, but tests that we can reuse from a more generic target
|
||||
pytest.param(
|
||||
"a%gcc@9.4.0",
|
||||
"b%gcc@10.2.1 target=x86_64",
|
||||
{"a": "gcc-runtime@10.2.1 target=x86_64", "b": "gcc-runtime@10.2.1 target=x86_64"},
|
||||
"pkg-a%gcc@9.4.0",
|
||||
"pkg-b%gcc@10.2.1 target=x86_64",
|
||||
{"pkg-a": "gcc-runtime@9.4.0", "pkg-b": "gcc-runtime@9.4.0"},
|
||||
1,
|
||||
marks=pytest.mark.skipif(
|
||||
str(archspec.cpu.host().family) != "x86_64", reason="test data is x86_64 specific"
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"a%gcc@10.2.1",
|
||||
"b%gcc@9.4.0 target=x86_64",
|
||||
{"a": "gcc-runtime@10.2.1 target=x86_64", "b": "gcc-runtime@9.4.0 target=x86_64"},
|
||||
"pkg-a%gcc@10.2.1",
|
||||
"pkg-b%gcc@9.4.0 target=x86_64",
|
||||
{
|
||||
"pkg-a": "gcc-runtime@10.2.1 target=x86_64",
|
||||
"pkg-b": "gcc-runtime@9.4.0 target=x86_64",
|
||||
},
|
||||
2,
|
||||
marks=pytest.mark.skipif(
|
||||
str(archspec.cpu.host().family) != "x86_64", reason="test data is x86_64 specific"
|
||||
@@ -102,17 +116,19 @@ def test_external_nodes_do_not_have_runtimes(runtime_repo, mutable_config, tmp_p
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.regression("44444")
|
||||
def test_reusing_specs_with_gcc_runtime(root_str, reused_str, expected, nruntime, runtime_repo):
|
||||
"""Tests that we can reuse specs with a "gcc-runtime" leaf node. In particular, checks
|
||||
that the semantic for gcc-runtimes versions accounts for reused packages too.
|
||||
|
||||
Reusable runtime versions should be lower, or equal, to that of parent nodes.
|
||||
"""
|
||||
root, reused_spec = _concretize_with_reuse(root_str=root_str, reused_str=reused_str)
|
||||
|
||||
assert f"{expected['b']}" in reused_spec
|
||||
runtime_a = root.dependencies("gcc-runtime")[0]
|
||||
assert runtime_a.satisfies(expected["a"])
|
||||
runtime_b = root["b"].dependencies("gcc-runtime")[0]
|
||||
assert runtime_b.satisfies(expected["b"])
|
||||
assert runtime_a.satisfies(expected["pkg-a"])
|
||||
runtime_b = root["pkg-b"].dependencies("gcc-runtime")[0]
|
||||
assert runtime_b.satisfies(expected["pkg-b"])
|
||||
|
||||
runtimes = [x for x in root.traverse() if x.name == "gcc-runtime"]
|
||||
assert len(runtimes) == nruntime
|
||||
@@ -123,8 +139,7 @@ def test_reusing_specs_with_gcc_runtime(root_str, reused_str, expected, nruntime
|
||||
[
|
||||
# Ensure that, whether we have multiple runtimes in the DAG or not,
|
||||
# we always link only the latest version
|
||||
("a%gcc@10.2.1", "b%gcc@9.4.0", ["gcc-runtime@10.2.1"], ["gcc-runtime@9.4.0"]),
|
||||
("a%gcc@9.4.0", "b%gcc@10.2.1", ["gcc-runtime@10.2.1"], ["gcc-runtime@9.4.0"]),
|
||||
("pkg-a%gcc@10.2.1", "pkg-b%gcc@9.4.0", ["gcc-runtime@10.2.1"], ["gcc-runtime@9.4.0"])
|
||||
],
|
||||
)
|
||||
def test_views_can_handle_duplicate_runtime_nodes(
|
||||
|
@@ -512,5 +512,5 @@ def test_default_preference_variant_different_type_does_not_error(self):
|
||||
packages.yaml doesn't fail with an error.
|
||||
"""
|
||||
with spack.config.override("packages:all", {"variants": "+foo"}):
|
||||
s = Spec("a").concretized()
|
||||
s = Spec("pkg-a").concretized()
|
||||
assert s.satisfies("foo=bar")
|
||||
|
@@ -103,23 +103,6 @@ def test_repo(_create_test_repo, monkeypatch, mock_stage):
|
||||
yield mock_repo_path
|
||||
|
||||
|
||||
class MakeStage:
|
||||
def __init__(self, stage):
|
||||
self.stage = stage
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.stage
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_installs(monkeypatch, tmpdir):
|
||||
stage_path = str(tmpdir.ensure("fake-stage", dir=True))
|
||||
universal_unused_stage = spack.stage.DIYStage(stage_path)
|
||||
monkeypatch.setattr(
|
||||
spack.build_systems.generic.Package, "_make_stage", MakeStage(universal_unused_stage)
|
||||
)
|
||||
|
||||
|
||||
def test_one_package_multiple_reqs(concretize_scope, test_repo):
|
||||
conf_str = """\
|
||||
packages:
|
||||
@@ -514,7 +497,7 @@ def test_oneof_ordering(concretize_scope, test_repo):
|
||||
assert s2.satisfies("@2.5")
|
||||
|
||||
|
||||
def test_reuse_oneof(concretize_scope, _create_test_repo, mutable_database, fake_installs):
|
||||
def test_reuse_oneof(concretize_scope, _create_test_repo, mutable_database, mock_fetch):
|
||||
conf_str = """\
|
||||
packages:
|
||||
y:
|
||||
@@ -944,9 +927,9 @@ def test_default_requirements_semantic(packages_yaml, concretize_scope, mock_pac
|
||||
Spec("zlib ~shared").concretized()
|
||||
|
||||
# A spec without the shared variant still concretize
|
||||
s = Spec("a").concretized()
|
||||
assert not s.satisfies("a +shared")
|
||||
assert not s.satisfies("a ~shared")
|
||||
s = Spec("pkg-a").concretized()
|
||||
assert not s.satisfies("pkg-a +shared")
|
||||
assert not s.satisfies("pkg-a ~shared")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -1176,3 +1159,46 @@ def test_forward_multi_valued_variant_using_requires(
|
||||
|
||||
for constraint in not_expected:
|
||||
assert not s.satisfies(constraint)
|
||||
|
||||
|
||||
def test_strong_preferences_higher_priority_than_reuse(concretize_scope, mock_packages):
|
||||
"""Tests that strong preferences have a higher priority than reusing specs."""
|
||||
reused_spec = Spec("adios2~bzip2").concretized()
|
||||
reuse_nodes = list(reused_spec.traverse())
|
||||
root_specs = [Spec("ascent+adios2")]
|
||||
|
||||
# Check that without further configuration adios2 is reused
|
||||
with spack.config.override("concretizer:reuse", True):
|
||||
solver = spack.solver.asp.Solver()
|
||||
setup = spack.solver.asp.SpackSolverSetup()
|
||||
result, _, _ = solver.driver.solve(setup, root_specs, reuse=reuse_nodes)
|
||||
ascent = result.specs[0]
|
||||
assert ascent["adios2"].dag_hash() == reused_spec.dag_hash(), ascent
|
||||
|
||||
# If we stick a preference, adios2 is not reused
|
||||
update_packages_config(
|
||||
"""
|
||||
packages:
|
||||
adios2:
|
||||
prefer:
|
||||
- "+bzip2"
|
||||
"""
|
||||
)
|
||||
with spack.config.override("concretizer:reuse", True):
|
||||
solver = spack.solver.asp.Solver()
|
||||
setup = spack.solver.asp.SpackSolverSetup()
|
||||
result, _, _ = solver.driver.solve(setup, root_specs, reuse=reuse_nodes)
|
||||
ascent = result.specs[0]
|
||||
|
||||
assert ascent["adios2"].dag_hash() != reused_spec.dag_hash()
|
||||
assert ascent["adios2"].satisfies("+bzip2")
|
||||
|
||||
# A preference is still preference, so we can override from input
|
||||
with spack.config.override("concretizer:reuse", True):
|
||||
solver = spack.solver.asp.Solver()
|
||||
setup = spack.solver.asp.SpackSolverSetup()
|
||||
result, _, _ = solver.driver.solve(
|
||||
setup, [Spec("ascent+adios2 ^adios2~bzip2")], reuse=reuse_nodes
|
||||
)
|
||||
ascent = result.specs[0]
|
||||
assert ascent["adios2"].dag_hash() == reused_spec.dag_hash(), ascent
|
||||
|
@@ -1211,13 +1211,13 @@ def test_license_dir_config(mutable_config, mock_packages):
|
||||
expected_dir = spack.paths.default_license_dir
|
||||
assert spack.config.get("config:license_dir") == expected_dir
|
||||
assert spack.package_base.PackageBase.global_license_dir == expected_dir
|
||||
assert spack.repo.PATH.get_pkg_class("a").global_license_dir == expected_dir
|
||||
assert spack.repo.PATH.get_pkg_class("pkg-a").global_license_dir == expected_dir
|
||||
|
||||
rel_path = os.path.join(os.path.sep, "foo", "bar", "baz")
|
||||
spack.config.set("config:license_dir", rel_path)
|
||||
assert spack.config.get("config:license_dir") == rel_path
|
||||
assert spack.package_base.PackageBase.global_license_dir == rel_path
|
||||
assert spack.repo.PATH.get_pkg_class("a").global_license_dir == rel_path
|
||||
assert spack.repo.PATH.get_pkg_class("pkg-a").global_license_dir == rel_path
|
||||
|
||||
|
||||
@pytest.mark.regression("22547")
|
||||
|
@@ -595,7 +595,7 @@ def mutable_mock_repo(mock_repo_path, request):
|
||||
def mock_custom_repository(tmpdir, mutable_mock_repo):
|
||||
"""Create a custom repository with a single package "c" and return its path."""
|
||||
builder = spack.repo.MockRepositoryBuilder(tmpdir.mkdir("myrepo"))
|
||||
builder.add_package("c")
|
||||
builder.add_package("pkg-c")
|
||||
return builder.root
|
||||
|
||||
|
||||
@@ -2053,3 +2053,11 @@ def _true(x):
|
||||
@pytest.fixture()
|
||||
def do_not_check_runtimes_on_reuse(monkeypatch):
|
||||
monkeypatch.setattr(spack.solver.asp, "_has_runtime_dependencies", _true)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def _c_compiler_always_exists():
|
||||
fn = spack.solver.asp.c_compiler_runs
|
||||
spack.solver.asp.c_compiler_runs = _true
|
||||
yield
|
||||
spack.solver.asp.c_compiler_runs = fn
|
||||
|
@@ -58,16 +58,22 @@ def upstream_and_downstream_db(tmpdir, gen_mock_layout):
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"install_tree,result",
|
||||
[("all", ["b", "c"]), ("upstream", ["c"]), ("local", ["b"]), ("{u}", ["c"]), ("{d}", ["b"])],
|
||||
[
|
||||
("all", ["pkg-b", "pkg-c"]),
|
||||
("upstream", ["pkg-c"]),
|
||||
("local", ["pkg-b"]),
|
||||
("{u}", ["pkg-c"]),
|
||||
("{d}", ["pkg-b"]),
|
||||
],
|
||||
)
|
||||
def test_query_by_install_tree(
|
||||
install_tree, result, upstream_and_downstream_db, mock_packages, monkeypatch, config
|
||||
):
|
||||
up_write_db, up_db, up_layout, down_db, down_layout = upstream_and_downstream_db
|
||||
|
||||
# Set the upstream DB to contain "c" and downstream to contain "b")
|
||||
b = spack.spec.Spec("b").concretized()
|
||||
c = spack.spec.Spec("c").concretized()
|
||||
# Set the upstream DB to contain "pkg-c" and downstream to contain "pkg-b")
|
||||
b = spack.spec.Spec("pkg-b").concretized()
|
||||
c = spack.spec.Spec("pkg-c").concretized()
|
||||
up_write_db.add(c, up_layout)
|
||||
up_db._read()
|
||||
down_db.add(b, down_layout)
|
||||
@@ -86,7 +92,7 @@ def test_spec_installed_upstream(
|
||||
|
||||
# a known installed spec should say that it's installed
|
||||
with spack.repo.use_repositories(mock_custom_repository):
|
||||
spec = spack.spec.Spec("c").concretized()
|
||||
spec = spack.spec.Spec("pkg-c").concretized()
|
||||
assert not spec.installed
|
||||
assert not spec.installed_upstream
|
||||
|
||||
@@ -848,7 +854,7 @@ def test_query_virtual_spec(database):
|
||||
|
||||
def test_failed_spec_path_error(database):
|
||||
"""Ensure spec not concrete check is covered."""
|
||||
s = spack.spec.Spec("a")
|
||||
s = spack.spec.Spec("pkg-a")
|
||||
with pytest.raises(AssertionError, match="concrete spec required"):
|
||||
spack.store.STORE.failure_tracker.mark(s)
|
||||
|
||||
@@ -863,7 +869,7 @@ def _is(self, spec):
|
||||
# Pretend the spec has been failure locked
|
||||
monkeypatch.setattr(spack.database.FailureTracker, "lock_taken", _is)
|
||||
|
||||
s = spack.spec.Spec("a").concretized()
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
spack.store.STORE.failure_tracker.clear(s)
|
||||
out = capfd.readouterr()[0]
|
||||
assert "Retaining failure marking" in out
|
||||
@@ -881,7 +887,7 @@ def _is(self, spec):
|
||||
# Ensure raise OSError when try to remove the non-existent marking
|
||||
monkeypatch.setattr(spack.database.FailureTracker, "persistent_mark", _is)
|
||||
|
||||
s = default_mock_concretization("a")
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
spack.store.STORE.failure_tracker.clear(s, force=True)
|
||||
out = capfd.readouterr()[1]
|
||||
assert "Removing failure marking despite lock" in out
|
||||
@@ -895,15 +901,16 @@ def test_mark_failed(default_mock_concretization, mutable_database, monkeypatch,
|
||||
def _raise_exc(lock):
|
||||
raise lk.LockTimeoutError("write", "/mock-lock", 1.234, 10)
|
||||
|
||||
# Ensure attempt to acquire write lock on the mark raises the exception
|
||||
monkeypatch.setattr(lk.Lock, "acquire_write", _raise_exc)
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
s = default_mock_concretization("a")
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
|
||||
# Ensure attempt to acquire write lock on the mark raises the exception
|
||||
monkeypatch.setattr(lk.Lock, "acquire_write", _raise_exc)
|
||||
|
||||
spack.store.STORE.failure_tracker.mark(s)
|
||||
|
||||
out = str(capsys.readouterr()[1])
|
||||
assert "Unable to mark a as failed" in out
|
||||
assert "Unable to mark pkg-a as failed" in out
|
||||
|
||||
spack.store.STORE.failure_tracker.clear_all()
|
||||
|
||||
@@ -912,7 +919,7 @@ def _raise_exc(lock):
|
||||
def test_prefix_failed(default_mock_concretization, mutable_database, monkeypatch):
|
||||
"""Add coverage to failed operation."""
|
||||
|
||||
s = default_mock_concretization("a")
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
|
||||
# Confirm the spec is not already marked as failed
|
||||
assert not spack.store.STORE.failure_tracker.has_failed(s)
|
||||
@@ -936,7 +943,7 @@ def test_prefix_write_lock_error(default_mock_concretization, mutable_database,
|
||||
def _raise(db, spec):
|
||||
raise lk.LockError("Mock lock error")
|
||||
|
||||
s = default_mock_concretization("a")
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
|
||||
# Ensure subsequent lock operations fail
|
||||
monkeypatch.setattr(lk.Lock, "acquire_write", _raise)
|
||||
@@ -1112,7 +1119,7 @@ def test_database_read_works_with_trailing_data(tmp_path, default_mock_concretiz
|
||||
# Populate a database
|
||||
root = str(tmp_path)
|
||||
db = spack.database.Database(root)
|
||||
spec = default_mock_concretization("a")
|
||||
spec = default_mock_concretization("pkg-a")
|
||||
db.add(spec, directory_layout=None)
|
||||
specs_in_db = db.query_local()
|
||||
assert spec in specs_in_db
|
||||
|
@@ -31,7 +31,7 @@ def test_true_directives_exist(mock_packages):
|
||||
|
||||
assert cls.dependencies
|
||||
assert "extendee" in cls.dependencies[spack.spec.Spec()]
|
||||
assert "b" in cls.dependencies[spack.spec.Spec()]
|
||||
assert "pkg-b" in cls.dependencies[spack.spec.Spec()]
|
||||
|
||||
assert cls.resources
|
||||
assert spack.spec.Spec() in cls.resources
|
||||
@@ -44,7 +44,7 @@ def test_constraints_from_context(mock_packages):
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class("with-constraint-met")
|
||||
|
||||
assert pkg_cls.dependencies
|
||||
assert "b" in pkg_cls.dependencies[spack.spec.Spec("@1.0")]
|
||||
assert "pkg-b" in pkg_cls.dependencies[spack.spec.Spec("@1.0")]
|
||||
|
||||
assert pkg_cls.conflicts
|
||||
assert (spack.spec.Spec("%gcc"), None) in pkg_cls.conflicts[spack.spec.Spec("+foo@1.0")]
|
||||
@@ -55,7 +55,7 @@ def test_constraints_from_context_are_merged(mock_packages):
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class("with-constraint-met")
|
||||
|
||||
assert pkg_cls.dependencies
|
||||
assert "c" in pkg_cls.dependencies[spack.spec.Spec("@0.14:15 ^b@3.8:4.0")]
|
||||
assert "pkg-c" in pkg_cls.dependencies[spack.spec.Spec("@0.14:15 ^pkg-b@3.8:4.0")]
|
||||
|
||||
|
||||
@pytest.mark.regression("27754")
|
||||
@@ -69,7 +69,7 @@ def test_extends_spec(config, mock_packages):
|
||||
|
||||
@pytest.mark.regression("34368")
|
||||
def test_error_on_anonymous_dependency(config, mock_packages):
|
||||
pkg = spack.repo.PATH.get_pkg_class("a")
|
||||
pkg = spack.repo.PATH.get_pkg_class("pkg-a")
|
||||
with pytest.raises(spack.directives.DependencyError):
|
||||
spack.directives._depends_on(pkg, "@4.5")
|
||||
|
||||
|
@@ -383,10 +383,10 @@ def test_can_add_specs_to_environment_without_specs_attribute(tmp_path, mock_pac
|
||||
"""
|
||||
)
|
||||
env = ev.Environment(tmp_path)
|
||||
env.add("a")
|
||||
env.add("pkg-a")
|
||||
|
||||
assert len(env.user_specs) == 1
|
||||
assert env.manifest.pristine_yaml_content["spack"]["specs"] == ["a"]
|
||||
assert env.manifest.pristine_yaml_content["spack"]["specs"] == ["pkg-a"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -584,7 +584,7 @@ def test_conflicts_with_packages_that_are_not_dependencies(
|
||||
spack:
|
||||
specs:
|
||||
- {spec_str}
|
||||
- b
|
||||
- pkg-b
|
||||
concretizer:
|
||||
unify: true
|
||||
"""
|
||||
@@ -712,7 +712,7 @@ def test_variant_propagation_with_unify_false(tmp_path, mock_packages, config):
|
||||
spack:
|
||||
specs:
|
||||
- parent-foo ++foo
|
||||
- c
|
||||
- pkg-c
|
||||
concretizer:
|
||||
unify: false
|
||||
"""
|
||||
@@ -797,10 +797,10 @@ def test_deconcretize_then_concretize_does_not_error(mutable_mock_env_path, mock
|
||||
"""spack:
|
||||
specs:
|
||||
# These two specs concretize to the same hash
|
||||
- c
|
||||
- c@1.0
|
||||
- pkg-c
|
||||
- pkg-c@1.0
|
||||
# Spec used to trigger the bug
|
||||
- a
|
||||
- pkg-a
|
||||
concretizer:
|
||||
unify: true
|
||||
"""
|
||||
@@ -808,8 +808,38 @@ def test_deconcretize_then_concretize_does_not_error(mutable_mock_env_path, mock
|
||||
e = ev.Environment(mutable_mock_env_path)
|
||||
with e:
|
||||
e.concretize()
|
||||
e.deconcretize(spack.spec.Spec("a"), concrete=False)
|
||||
e.deconcretize(spack.spec.Spec("pkg-a"), concrete=False)
|
||||
e.concretize()
|
||||
assert len(e.concrete_roots()) == 3
|
||||
all_root_hashes = set(x.dag_hash() for x in e.concrete_roots())
|
||||
all_root_hashes = {x.dag_hash() for x in e.concrete_roots()}
|
||||
assert len(all_root_hashes) == 2
|
||||
|
||||
|
||||
@pytest.mark.regression("44216")
|
||||
@pytest.mark.only_clingo()
|
||||
def test_root_version_weights_for_old_versions(mutable_mock_env_path, mock_packages):
|
||||
"""Tests that, when we select two old versions of root specs that have the same version
|
||||
optimization penalty, both are considered.
|
||||
"""
|
||||
mutable_mock_env_path.mkdir()
|
||||
spack_yaml = mutable_mock_env_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
"""spack:
|
||||
specs:
|
||||
# allow any version, but the most recent
|
||||
- bowtie@:1.3
|
||||
# allows only the third most recent, so penalty is 2
|
||||
- gcc@1
|
||||
concretizer:
|
||||
unify: true
|
||||
"""
|
||||
)
|
||||
e = ev.Environment(mutable_mock_env_path)
|
||||
with e:
|
||||
e.concretize()
|
||||
|
||||
bowtie = [x for x in e.concrete_roots() if x.name == "bowtie"][0]
|
||||
gcc = [x for x in e.concrete_roots() if x.name == "gcc"][0]
|
||||
|
||||
assert bowtie.satisfies("@=1.3.0")
|
||||
assert gcc.satisfies("@=1.0")
|
||||
|
@@ -132,7 +132,7 @@ def test_hms(sec, result):
|
||||
|
||||
def test_get_dependent_ids(install_mockery, mock_packages):
|
||||
# Concretize the parent package, which handle dependency too
|
||||
spec = spack.spec.Spec("a")
|
||||
spec = spack.spec.Spec("pkg-a")
|
||||
spec.concretize()
|
||||
assert spec.concrete
|
||||
|
||||
@@ -223,11 +223,11 @@ def _spec(spec, unsigned=False, mirrors_for_spec=None):
|
||||
# Skip database updates
|
||||
monkeypatch.setattr(spack.database.Database, "add", _noop)
|
||||
|
||||
spec = spack.spec.Spec("a").concretized()
|
||||
spec = spack.spec.Spec("pkg-a").concretized()
|
||||
assert inst._process_binary_cache_tarball(spec.package, explicit=False, unsigned=False)
|
||||
|
||||
out = capfd.readouterr()[0]
|
||||
assert "Extracting a" in out
|
||||
assert "Extracting pkg-a" in out
|
||||
assert "from binary cache" in out
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ def test_installer_prune_built_build_deps(install_mockery, monkeypatch, tmpdir):
|
||||
|
||||
@property
|
||||
def _mock_installed(self):
|
||||
return self.name in ["c"]
|
||||
return self.name == "pkg-c"
|
||||
|
||||
# Mock the installed property to say that (b) is installed
|
||||
monkeypatch.setattr(spack.spec.Spec, "installed", _mock_installed)
|
||||
@@ -286,24 +286,25 @@ def _mock_installed(self):
|
||||
# Create mock repository with packages (a), (b), (c), (d), and (e)
|
||||
builder = spack.repo.MockRepositoryBuilder(tmpdir.mkdir("mock-repo"))
|
||||
|
||||
builder.add_package("a", dependencies=[("b", "build", None), ("c", "build", None)])
|
||||
builder.add_package("b", dependencies=[("d", "build", None)])
|
||||
builder.add_package("pkg-a", dependencies=[("pkg-b", "build", None), ("pkg-c", "build", None)])
|
||||
builder.add_package("pkg-b", dependencies=[("pkg-d", "build", None)])
|
||||
builder.add_package(
|
||||
"c", dependencies=[("d", "build", None), ("e", "all", None), ("f", "build", None)]
|
||||
"pkg-c",
|
||||
dependencies=[("pkg-d", "build", None), ("pkg-e", "all", None), ("pkg-f", "build", None)],
|
||||
)
|
||||
builder.add_package("d")
|
||||
builder.add_package("e")
|
||||
builder.add_package("f")
|
||||
builder.add_package("pkg-d")
|
||||
builder.add_package("pkg-e")
|
||||
builder.add_package("pkg-f")
|
||||
|
||||
with spack.repo.use_repositories(builder.root):
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
installer._init_queue()
|
||||
|
||||
# Assert that (c) is not in the build_pq
|
||||
result = set([task.pkg_id[0] for _, task in installer.build_pq])
|
||||
expected = set(["a", "b", "c", "d", "e"])
|
||||
result = {task.pkg_id[:5] for _, task in installer.build_pq}
|
||||
expected = {"pkg-a", "pkg-b", "pkg-c", "pkg-d", "pkg-e"}
|
||||
assert result == expected
|
||||
|
||||
|
||||
@@ -418,8 +419,7 @@ def test_ensure_locked_have(install_mockery, tmpdir, capsys):
|
||||
|
||||
@pytest.mark.parametrize("lock_type,reads,writes", [("read", 1, 0), ("write", 0, 1)])
|
||||
def test_ensure_locked_new_lock(install_mockery, tmpdir, lock_type, reads, writes):
|
||||
pkg_id = "a"
|
||||
const_arg = installer_args([pkg_id], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
spec = installer.build_requests[0].pkg.spec
|
||||
with tmpdir.as_cwd():
|
||||
@@ -438,8 +438,7 @@ def _pl(db, spec, timeout):
|
||||
lock.default_timeout = 1e-9 if timeout is None else None
|
||||
return lock
|
||||
|
||||
pkg_id = "a"
|
||||
const_arg = installer_args([pkg_id], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
spec = installer.build_requests[0].pkg.spec
|
||||
|
||||
@@ -494,7 +493,7 @@ def test_packages_needed_to_bootstrap_compiler_packages(install_mockery, monkeyp
|
||||
spec.concretize()
|
||||
|
||||
def _conc_spec(compiler):
|
||||
return spack.spec.Spec("a").concretized()
|
||||
return spack.spec.Spec("pkg-a").concretized()
|
||||
|
||||
# Ensure we can get past functions that are precluding obtaining
|
||||
# packages.
|
||||
@@ -602,7 +601,7 @@ def test_clear_failures_success(tmpdir):
|
||||
"""Test the clear_failures happy path."""
|
||||
failures = spack.database.FailureTracker(str(tmpdir), default_timeout=0.1)
|
||||
|
||||
spec = spack.spec.Spec("a")
|
||||
spec = spack.spec.Spec("pkg-a")
|
||||
spec._mark_concrete()
|
||||
|
||||
# Set up a test prefix failure lock
|
||||
@@ -628,7 +627,7 @@ def test_clear_failures_success(tmpdir):
|
||||
def test_clear_failures_errs(tmpdir, capsys):
|
||||
"""Test the clear_failures exception paths."""
|
||||
failures = spack.database.FailureTracker(str(tmpdir), default_timeout=0.1)
|
||||
spec = spack.spec.Spec("a")
|
||||
spec = spack.spec.Spec("pkg-a")
|
||||
spec._mark_concrete()
|
||||
failures.mark(spec)
|
||||
|
||||
@@ -690,11 +689,11 @@ def test_check_deps_status_install_failure(install_mockery):
|
||||
"""Tests that checking the dependency status on a request to install
|
||||
'a' fails, if we mark the dependency as failed.
|
||||
"""
|
||||
s = spack.spec.Spec("a").concretized()
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
for dep in s.traverse(root=False):
|
||||
spack.store.STORE.failure_tracker.mark(dep)
|
||||
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
request = installer.build_requests[0]
|
||||
|
||||
@@ -703,7 +702,7 @@ def test_check_deps_status_install_failure(install_mockery):
|
||||
|
||||
|
||||
def test_check_deps_status_write_locked(install_mockery, monkeypatch):
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
request = installer.build_requests[0]
|
||||
|
||||
@@ -715,7 +714,7 @@ def test_check_deps_status_write_locked(install_mockery, monkeypatch):
|
||||
|
||||
|
||||
def test_check_deps_status_external(install_mockery, monkeypatch):
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
request = installer.build_requests[0]
|
||||
|
||||
@@ -728,7 +727,7 @@ def test_check_deps_status_external(install_mockery, monkeypatch):
|
||||
|
||||
|
||||
def test_check_deps_status_upstream(install_mockery, monkeypatch):
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
request = installer.build_requests[0]
|
||||
|
||||
@@ -805,7 +804,7 @@ def test_install_task_add_compiler(install_mockery, monkeypatch, capfd):
|
||||
def _add(_compilers):
|
||||
tty.msg(config_msg)
|
||||
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
task = create_build_task(installer.build_requests[0].pkg)
|
||||
task.compiler = True
|
||||
@@ -843,7 +842,7 @@ def test_release_lock_write_n_exception(install_mockery, tmpdir, capsys):
|
||||
@pytest.mark.parametrize("installed", [True, False])
|
||||
def test_push_task_skip_processed(install_mockery, installed):
|
||||
"""Test to ensure skip re-queueing a processed package."""
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
assert len(list(installer.build_tasks)) == 0
|
||||
|
||||
@@ -861,7 +860,7 @@ def test_push_task_skip_processed(install_mockery, installed):
|
||||
|
||||
def test_requeue_task(install_mockery, capfd):
|
||||
"""Test to ensure cover _requeue_task."""
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
task = create_build_task(installer.build_requests[0].pkg)
|
||||
|
||||
@@ -879,7 +878,7 @@ def test_requeue_task(install_mockery, capfd):
|
||||
assert qtask.attempts == task.attempts + 1
|
||||
|
||||
out = capfd.readouterr()[1]
|
||||
assert "Installing a" in out
|
||||
assert "Installing pkg-a" in out
|
||||
assert " in progress by another process" in out
|
||||
|
||||
|
||||
@@ -892,17 +891,17 @@ def _mktask(pkg):
|
||||
def _rmtask(installer, pkg_id):
|
||||
raise RuntimeError("Raise an exception to test except path")
|
||||
|
||||
const_arg = installer_args(["a"], {})
|
||||
const_arg = installer_args(["pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
spec = installer.build_requests[0].pkg.spec
|
||||
|
||||
# Cover task removal happy path
|
||||
installer.build_tasks["a"] = _mktask(spec.package)
|
||||
installer.build_tasks["pkg-a"] = _mktask(spec.package)
|
||||
installer._cleanup_all_tasks()
|
||||
assert len(installer.build_tasks) == 0
|
||||
|
||||
# Cover task removal exception path
|
||||
installer.build_tasks["a"] = _mktask(spec.package)
|
||||
installer.build_tasks["pkg-a"] = _mktask(spec.package)
|
||||
monkeypatch.setattr(inst.PackageInstaller, "_remove_task", _rmtask)
|
||||
installer._cleanup_all_tasks()
|
||||
assert len(installer.build_tasks) == 1
|
||||
@@ -996,7 +995,7 @@ def test_install_uninstalled_deps(install_mockery, monkeypatch, capsys):
|
||||
|
||||
def test_install_failed(install_mockery, monkeypatch, capsys):
|
||||
"""Test install with failed install."""
|
||||
const_arg = installer_args(["b"], {})
|
||||
const_arg = installer_args(["pkg-b"], {})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
# Make sure the package is identified as failed
|
||||
@@ -1012,7 +1011,7 @@ def test_install_failed(install_mockery, monkeypatch, capsys):
|
||||
|
||||
def test_install_failed_not_fast(install_mockery, monkeypatch, capsys):
|
||||
"""Test install with failed install."""
|
||||
const_arg = installer_args(["a"], {"fail_fast": False})
|
||||
const_arg = installer_args(["pkg-a"], {"fail_fast": False})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
# Make sure the package is identified as failed
|
||||
@@ -1023,12 +1022,12 @@ def test_install_failed_not_fast(install_mockery, monkeypatch, capsys):
|
||||
|
||||
out = str(capsys.readouterr())
|
||||
assert "failed to install" in out
|
||||
assert "Skipping build of a" in out
|
||||
assert "Skipping build of pkg-a" in out
|
||||
|
||||
|
||||
def test_install_fail_on_interrupt(install_mockery, monkeypatch):
|
||||
"""Test ctrl-c interrupted install."""
|
||||
spec_name = "a"
|
||||
spec_name = "pkg-a"
|
||||
err_msg = "mock keyboard interrupt for {0}".format(spec_name)
|
||||
|
||||
def _interrupt(installer, task, install_status, **kwargs):
|
||||
@@ -1046,13 +1045,13 @@ def _interrupt(installer, task, install_status, **kwargs):
|
||||
with pytest.raises(KeyboardInterrupt, match=err_msg):
|
||||
installer.install()
|
||||
|
||||
assert "b" in installer.installed # ensure dependency of a is 'installed'
|
||||
assert "pkg-b" in installer.installed # ensure dependency of pkg-a is 'installed'
|
||||
assert spec_name not in installer.installed
|
||||
|
||||
|
||||
def test_install_fail_single(install_mockery, monkeypatch):
|
||||
"""Test expected results for failure of single package."""
|
||||
spec_name = "a"
|
||||
spec_name = "pkg-a"
|
||||
err_msg = "mock internal package build error for {0}".format(spec_name)
|
||||
|
||||
class MyBuildException(Exception):
|
||||
@@ -1073,13 +1072,13 @@ def _install(installer, task, install_status, **kwargs):
|
||||
with pytest.raises(MyBuildException, match=err_msg):
|
||||
installer.install()
|
||||
|
||||
assert "b" in installer.installed # ensure dependency of a is 'installed'
|
||||
assert "pkg-b" in installer.installed # ensure dependency of a is 'installed'
|
||||
assert spec_name not in installer.installed
|
||||
|
||||
|
||||
def test_install_fail_multi(install_mockery, monkeypatch):
|
||||
"""Test expected results for failure of multiple packages."""
|
||||
spec_name = "c"
|
||||
spec_name = "pkg-c"
|
||||
err_msg = "mock internal package build error"
|
||||
|
||||
class MyBuildException(Exception):
|
||||
@@ -1091,7 +1090,7 @@ def _install(installer, task, install_status, **kwargs):
|
||||
else:
|
||||
installer.installed.add(task.pkg.name)
|
||||
|
||||
const_arg = installer_args([spec_name, "a"], {})
|
||||
const_arg = installer_args([spec_name, "pkg-a"], {})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
# Raise a KeyboardInterrupt error to trigger early termination
|
||||
@@ -1100,14 +1099,14 @@ def _install(installer, task, install_status, **kwargs):
|
||||
with pytest.raises(inst.InstallError, match="Installation request failed"):
|
||||
installer.install()
|
||||
|
||||
assert "a" in installer.installed # ensure the the second spec installed
|
||||
assert "pkg-a" in installer.installed # ensure the the second spec installed
|
||||
assert spec_name not in installer.installed
|
||||
|
||||
|
||||
def test_install_fail_fast_on_detect(install_mockery, monkeypatch, capsys):
|
||||
"""Test fail_fast install when an install failure is detected."""
|
||||
const_arg = installer_args(["b"], {"fail_fast": False})
|
||||
const_arg.extend(installer_args(["c"], {"fail_fast": True}))
|
||||
const_arg = installer_args(["pkg-b"], {"fail_fast": False})
|
||||
const_arg.extend(installer_args(["pkg-c"], {"fail_fast": True}))
|
||||
installer = create_installer(const_arg)
|
||||
pkg_ids = [inst.package_id(spec) for spec, _ in const_arg]
|
||||
|
||||
@@ -1137,7 +1136,7 @@ def _test_install_fail_fast_on_except_patch(installer, **kwargs):
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_install_fail_fast_on_except(install_mockery, monkeypatch, capsys):
|
||||
"""Test fail_fast install when an install failure results from an error."""
|
||||
const_arg = installer_args(["a"], {"fail_fast": True})
|
||||
const_arg = installer_args(["pkg-a"], {"fail_fast": True})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
# Raise a non-KeyboardInterrupt exception to trigger fast failure.
|
||||
@@ -1152,7 +1151,7 @@ def test_install_fail_fast_on_except(install_mockery, monkeypatch, capsys):
|
||||
installer.install()
|
||||
|
||||
out = str(capsys.readouterr())
|
||||
assert "Skipping build of a" in out
|
||||
assert "Skipping build of pkg-a" in out
|
||||
|
||||
|
||||
def test_install_lock_failures(install_mockery, monkeypatch, capfd):
|
||||
@@ -1161,7 +1160,7 @@ def test_install_lock_failures(install_mockery, monkeypatch, capfd):
|
||||
def _requeued(installer, task, install_status):
|
||||
tty.msg("requeued {0}".format(task.pkg.spec.name))
|
||||
|
||||
const_arg = installer_args(["b"], {})
|
||||
const_arg = installer_args(["pkg-b"], {})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
# Ensure never acquire a lock
|
||||
@@ -1181,7 +1180,7 @@ def _requeued(installer, task, install_status):
|
||||
|
||||
def test_install_lock_installed_requeue(install_mockery, monkeypatch, capfd):
|
||||
"""Cover basic install handling for installed package."""
|
||||
const_arg = installer_args(["b"], {})
|
||||
const_arg = installer_args(["pkg-b"], {})
|
||||
b, _ = const_arg[0]
|
||||
installer = create_installer(const_arg)
|
||||
b_pkg_id = inst.package_id(b)
|
||||
@@ -1237,7 +1236,7 @@ def _requeued(installer, task, install_status):
|
||||
# Ensure don't continually requeue the task
|
||||
monkeypatch.setattr(inst.PackageInstaller, "_requeue_task", _requeued)
|
||||
|
||||
const_arg = installer_args(["b"], {})
|
||||
const_arg = installer_args(["pkg-b"], {})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
with pytest.raises(inst.InstallError, match="request failed"):
|
||||
@@ -1253,7 +1252,7 @@ def _requeued(installer, task, install_status):
|
||||
|
||||
def test_install_skip_patch(install_mockery, mock_fetch):
|
||||
"""Test the path skip_patch install path."""
|
||||
spec_name = "b"
|
||||
spec_name = "pkg-b"
|
||||
const_arg = installer_args([spec_name], {"fake": False, "skip_patch": True})
|
||||
installer = create_installer(const_arg)
|
||||
|
||||
@@ -1280,7 +1279,7 @@ def test_overwrite_install_backup_success(temporary_store, config, mock_packages
|
||||
of the original prefix, and leave the original spec marked installed.
|
||||
"""
|
||||
# Get a build task. TODO: refactor this to avoid calling internal methods
|
||||
const_arg = installer_args(["b"])
|
||||
const_arg = installer_args(["pkg-b"], {})
|
||||
installer = create_installer(const_arg)
|
||||
installer._init_queue()
|
||||
task = installer._pop_task()
|
||||
@@ -1341,7 +1340,7 @@ def remove(self, spec):
|
||||
self.called = True
|
||||
|
||||
# Get a build task. TODO: refactor this to avoid calling internal methods
|
||||
const_arg = installer_args(["b"])
|
||||
const_arg = installer_args(["pkg-b"], {})
|
||||
installer = create_installer(const_arg)
|
||||
installer._init_queue()
|
||||
task = installer._pop_task()
|
||||
@@ -1370,8 +1369,8 @@ def test_term_status_line():
|
||||
# accept that. `with log_output(buf)` doesn't really work because it trims output
|
||||
# and we actually want to test for escape sequences etc.
|
||||
x = inst.TermStatusLine(enabled=True)
|
||||
x.add("a")
|
||||
x.add("b")
|
||||
x.add("pkg-a")
|
||||
x.add("pkg-b")
|
||||
x.clear()
|
||||
|
||||
|
||||
|
@@ -14,7 +14,7 @@
|
||||
import pytest
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
from llnl.util.symlink import islink, symlink
|
||||
from llnl.util.symlink import islink, readlink, symlink
|
||||
|
||||
import spack.paths
|
||||
|
||||
@@ -181,7 +181,7 @@ def test_symlinks_true(self, stage):
|
||||
|
||||
assert os.path.exists("dest/a/b2")
|
||||
with fs.working_dir("dest/a"):
|
||||
assert os.path.exists(os.readlink("b2"))
|
||||
assert os.path.exists(readlink("b2"))
|
||||
|
||||
assert os.path.realpath("dest/f/2") == os.path.abspath("dest/a/b/2")
|
||||
assert os.path.realpath("dest/2") == os.path.abspath("dest/1")
|
||||
@@ -281,7 +281,7 @@ def test_allow_broken_symlinks(self, stage):
|
||||
symlink("nonexistant.txt", "source/broken", allow_broken_symlinks=True)
|
||||
fs.install_tree("source", "dest", symlinks=True, allow_broken_symlinks=True)
|
||||
assert os.path.islink("dest/broken")
|
||||
assert not os.path.exists(os.readlink("dest/broken"))
|
||||
assert not os.path.exists(readlink("dest/broken"))
|
||||
|
||||
def test_glob_src(self, stage):
|
||||
"""Test using a glob as the source."""
|
||||
|
@@ -289,8 +289,8 @@ def test_mirror_cache_symlinks(tmpdir):
|
||||
@pytest.mark.parametrize(
|
||||
"specs,expected_specs",
|
||||
[
|
||||
(["a"], ["a@=1.0", "a@=2.0"]),
|
||||
(["a", "brillig"], ["a@=1.0", "a@=2.0", "brillig@=1.0.0", "brillig@=2.0.0"]),
|
||||
(["pkg-a"], ["pkg-a@=1.0", "pkg-a@=2.0"]),
|
||||
(["pkg-a", "brillig"], ["pkg-a@=1.0", "pkg-a@=2.0", "brillig@=1.0.0", "brillig@=2.0.0"]),
|
||||
],
|
||||
)
|
||||
def test_get_all_versions(specs, expected_specs):
|
||||
|
@@ -7,6 +7,8 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from llnl.util.symlink import readlink
|
||||
|
||||
import spack.cmd.modules
|
||||
import spack.config
|
||||
import spack.error
|
||||
@@ -78,7 +80,7 @@ def test_modules_default_symlink(
|
||||
|
||||
link_path = os.path.join(os.path.dirname(mock_module_filename), "default")
|
||||
assert os.path.islink(link_path)
|
||||
assert os.readlink(link_path) == mock_module_filename
|
||||
assert readlink(link_path) == mock_module_filename
|
||||
|
||||
generator.remove()
|
||||
assert not os.path.lexists(link_path)
|
||||
|
@@ -151,7 +151,9 @@ class InMemoryOCIRegistry(DummyServer):
|
||||
A third option is to use the chunked upload, but this is not implemented here, because
|
||||
it's typically a major performance hit in upload speed, so we're not using it in Spack."""
|
||||
|
||||
def __init__(self, domain: str, allow_single_post: bool = True) -> None:
|
||||
def __init__(
|
||||
self, domain: str, allow_single_post: bool = True, tags_per_page: int = 100
|
||||
) -> None:
|
||||
super().__init__(domain)
|
||||
self.router.register("GET", r"/v2/", self.index)
|
||||
self.router.register("HEAD", r"/v2/(?P<name>.+)/blobs/(?P<digest>.+)", self.head_blob)
|
||||
@@ -165,6 +167,9 @@ def __init__(self, domain: str, allow_single_post: bool = True) -> None:
|
||||
# If True, allow single POST upload, not all registries support this
|
||||
self.allow_single_post = allow_single_post
|
||||
|
||||
# How many tags are returned in a single request
|
||||
self.tags_per_page = tags_per_page
|
||||
|
||||
# Used for POST + PUT upload. This is a map from session ID to image name
|
||||
self.sessions: Dict[str, str] = {}
|
||||
|
||||
@@ -280,10 +285,34 @@ def handle_upload(self, req: Request, name: str, digest: Digest):
|
||||
return MockHTTPResponse(201, "Created", headers={"Location": f"/v2/{name}/blobs/{digest}"})
|
||||
|
||||
def list_tags(self, req: Request, name: str):
|
||||
# Paginate using Link headers, this was added to the spec in the following commit:
|
||||
# https://github.com/opencontainers/distribution-spec/commit/2ed79d930ecec11dd755dc8190409a3b10f01ca9
|
||||
|
||||
# List all tags, exclude digests.
|
||||
tags = [_tag for _name, _tag in self.manifests.keys() if _name == name and ":" not in _tag]
|
||||
tags.sort()
|
||||
return MockHTTPResponse.with_json(200, "OK", body={"tags": tags})
|
||||
all_tags = sorted(
|
||||
_tag for _name, _tag in self.manifests.keys() if _name == name and ":" not in _tag
|
||||
)
|
||||
|
||||
query = urllib.parse.parse_qs(urllib.parse.urlparse(req.full_url).query)
|
||||
|
||||
n = int(query["n"][0]) if "n" in query else self.tags_per_page
|
||||
|
||||
if "last" in query:
|
||||
try:
|
||||
offset = all_tags.index(query["last"][0]) + 1
|
||||
except ValueError:
|
||||
return MockHTTPResponse(404, "Not found")
|
||||
else:
|
||||
offset = 0
|
||||
|
||||
tags = all_tags[offset : offset + n]
|
||||
|
||||
if offset + n < len(all_tags):
|
||||
headers = {"Link": f'</v2/{name}/tags/list?last={tags[-1]}&n={n}>; rel="next"'}
|
||||
else:
|
||||
headers = None
|
||||
|
||||
return MockHTTPResponse.with_json(200, "OK", headers=headers, body={"tags": tags})
|
||||
|
||||
|
||||
class DummyServerUrllibHandler(urllib.request.BaseHandler):
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import random
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
@@ -19,6 +20,7 @@
|
||||
copy_missing_layers,
|
||||
get_manifest_and_config,
|
||||
image_from_mirror,
|
||||
list_tags,
|
||||
upload_blob,
|
||||
upload_manifest,
|
||||
)
|
||||
@@ -670,3 +672,31 @@ def test_retry(url, max_retries, expect_failure, expect_requests):
|
||||
|
||||
assert len(server.requests) == expect_requests
|
||||
assert sleep_time == [2**i for i in range(expect_requests - 1)]
|
||||
|
||||
|
||||
def test_list_tags():
|
||||
# Follows a relatively new rewording of the OCI distribution spec, which is not yet tagged.
|
||||
# https://github.com/opencontainers/distribution-spec/commit/2ed79d930ecec11dd755dc8190409a3b10f01ca9
|
||||
N = 20
|
||||
urlopen = create_opener(InMemoryOCIRegistry("example.com", tags_per_page=5)).open
|
||||
image = ImageReference.from_string("example.com/image")
|
||||
to_tag = lambda i: f"tag-{i:02}"
|
||||
|
||||
# Create N tags in arbitrary order
|
||||
_tags_to_create = [to_tag(i) for i in range(N)]
|
||||
random.shuffle(_tags_to_create)
|
||||
for tag in _tags_to_create:
|
||||
upload_manifest(image.with_tag(tag), default_manifest(), tag=True, _urlopen=urlopen)
|
||||
|
||||
# list_tags should return all tags from all pages in order
|
||||
tags = list_tags(image, urlopen)
|
||||
assert len(tags) == N
|
||||
assert [to_tag(i) for i in range(N)] == tags
|
||||
|
||||
# Test a single request, which should give the first 5 tags
|
||||
assert json.loads(urlopen(image.tags_url()).read())["tags"] == [to_tag(i) for i in range(5)]
|
||||
|
||||
# Test response at an offset, which should exclude the `last` tag.
|
||||
assert json.loads(urlopen(image.tags_url() + f"?last={to_tag(N - 3)}").read())["tags"] == [
|
||||
to_tag(i) for i in range(N - 2, N)
|
||||
]
|
||||
|
@@ -13,28 +13,44 @@
|
||||
# Normalize simple conditionals
|
||||
("optional-dep-test", {"optional-dep-test": None}),
|
||||
("optional-dep-test~a", {"optional-dep-test~a": None}),
|
||||
("optional-dep-test+a", {"optional-dep-test+a": {"a": None}}),
|
||||
("optional-dep-test a=true", {"optional-dep-test a=true": {"a": None}}),
|
||||
("optional-dep-test a=true", {"optional-dep-test+a": {"a": None}}),
|
||||
("optional-dep-test@1.1", {"optional-dep-test@1.1": {"b": None}}),
|
||||
("optional-dep-test%intel", {"optional-dep-test%intel": {"c": None}}),
|
||||
("optional-dep-test%intel@64.1", {"optional-dep-test%intel@64.1": {"c": None, "d": None}}),
|
||||
("optional-dep-test+a", {"optional-dep-test+a": {"pkg-a": None}}),
|
||||
("optional-dep-test a=true", {"optional-dep-test a=true": {"pkg-a": None}}),
|
||||
("optional-dep-test a=true", {"optional-dep-test+a": {"pkg-a": None}}),
|
||||
("optional-dep-test@1.1", {"optional-dep-test@1.1": {"pkg-b": None}}),
|
||||
("optional-dep-test%intel", {"optional-dep-test%intel": {"pkg-c": None}}),
|
||||
(
|
||||
"optional-dep-test%intel@64.1",
|
||||
{"optional-dep-test%intel@64.1": {"pkg-c": None, "pkg-d": None}},
|
||||
),
|
||||
(
|
||||
"optional-dep-test%intel@64.1.2",
|
||||
{"optional-dep-test%intel@64.1.2": {"c": None, "d": None}},
|
||||
{"optional-dep-test%intel@64.1.2": {"pkg-c": None, "pkg-d": None}},
|
||||
),
|
||||
("optional-dep-test%clang@35", {"optional-dep-test%clang@35": {"e": None}}),
|
||||
("optional-dep-test%clang@35", {"optional-dep-test%clang@35": {"pkg-e": None}}),
|
||||
# Normalize multiple conditionals
|
||||
("optional-dep-test+a@1.1", {"optional-dep-test+a@1.1": {"a": None, "b": None}}),
|
||||
("optional-dep-test+a%intel", {"optional-dep-test+a%intel": {"a": None, "c": None}}),
|
||||
("optional-dep-test@1.1%intel", {"optional-dep-test@1.1%intel": {"b": None, "c": None}}),
|
||||
("optional-dep-test+a@1.1", {"optional-dep-test+a@1.1": {"pkg-a": None, "pkg-b": None}}),
|
||||
(
|
||||
"optional-dep-test+a%intel",
|
||||
{"optional-dep-test+a%intel": {"pkg-a": None, "pkg-c": None}},
|
||||
),
|
||||
(
|
||||
"optional-dep-test@1.1%intel",
|
||||
{"optional-dep-test@1.1%intel": {"pkg-b": None, "pkg-c": None}},
|
||||
),
|
||||
(
|
||||
"optional-dep-test@1.1%intel@64.1.2+a",
|
||||
{"optional-dep-test@1.1%intel@64.1.2+a": {"a": None, "b": None, "c": None, "d": None}},
|
||||
{
|
||||
"optional-dep-test@1.1%intel@64.1.2+a": {
|
||||
"pkg-a": None,
|
||||
"pkg-b": None,
|
||||
"pkg-c": None,
|
||||
"pkg-d": None,
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
"optional-dep-test@1.1%clang@36.5+a",
|
||||
{"optional-dep-test@1.1%clang@36.5+a": {"b": None, "a": None, "e": None}},
|
||||
{"optional-dep-test@1.1%clang@36.5+a": {"pkg-b": None, "pkg-a": None, "pkg-e": None}},
|
||||
),
|
||||
# Chained MPI
|
||||
(
|
||||
@@ -44,7 +60,10 @@
|
||||
# Each of these dependencies comes from a conditional
|
||||
# dependency on another. This requires iterating to evaluate
|
||||
# the whole chain.
|
||||
("optional-dep-test+f", {"optional-dep-test+f": {"f": None, "g": None, "mpi": None}}),
|
||||
(
|
||||
"optional-dep-test+f",
|
||||
{"optional-dep-test+f": {"pkg-f": None, "pkg-g": None, "mpi": None}},
|
||||
),
|
||||
]
|
||||
)
|
||||
def spec_and_expected(request):
|
||||
@@ -63,12 +82,12 @@ def test_normalize(spec_and_expected, config, mock_packages):
|
||||
def test_default_variant(config, mock_packages):
|
||||
spec = Spec("optional-dep-test-3")
|
||||
spec.concretize()
|
||||
assert "a" in spec
|
||||
assert "pkg-a" in spec
|
||||
|
||||
spec = Spec("optional-dep-test-3~var")
|
||||
spec.concretize()
|
||||
assert "a" in spec
|
||||
assert "pkg-a" in spec
|
||||
|
||||
spec = Spec("optional-dep-test-3+var")
|
||||
spec.concretize()
|
||||
assert "b" in spec
|
||||
assert "pkg-b" in spec
|
||||
|
@@ -21,6 +21,7 @@
|
||||
import spack.install_test
|
||||
import spack.package_base
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
from spack.build_systems.generic import Package
|
||||
from spack.installer import InstallError
|
||||
|
||||
@@ -142,19 +143,19 @@ def setup_install_test(source_paths, test_root):
|
||||
"spec,sources,extras,expect",
|
||||
[
|
||||
(
|
||||
"a",
|
||||
"pkg-a",
|
||||
["example/a.c"], # Source(s)
|
||||
["example/a.c"], # Extra test source
|
||||
["example/a.c"],
|
||||
), # Test install dir source(s)
|
||||
(
|
||||
"b",
|
||||
"pkg-b",
|
||||
["test/b.cpp", "test/b.hpp", "example/b.txt"], # Source(s)
|
||||
["test"], # Extra test source
|
||||
["test/b.cpp", "test/b.hpp"],
|
||||
), # Test install dir source
|
||||
(
|
||||
"c",
|
||||
"pkg-c",
|
||||
["examples/a.py", "examples/b.py", "examples/c.py", "tests/d.py"],
|
||||
["examples/b.py", "tests"],
|
||||
["examples/b.py", "tests/d.py"],
|
||||
@@ -202,7 +203,7 @@ def test_cache_extra_sources(install_mockery, spec, sources, extras, expect):
|
||||
|
||||
|
||||
def test_cache_extra_sources_fails(install_mockery):
|
||||
s = spack.spec.Spec("a").concretized()
|
||||
s = spack.spec.Spec("pkg-a").concretized()
|
||||
s.package.spec.concretize()
|
||||
|
||||
with pytest.raises(InstallError) as exc_info:
|
||||
@@ -226,7 +227,7 @@ class URLsPackage(spack.package.Package):
|
||||
url = "https://www.example.com/url-package-1.0.tgz"
|
||||
urls = ["https://www.example.com/archive"]
|
||||
|
||||
s = spack.spec.Spec("a")
|
||||
s = spack.spec.Spec("pkg-a")
|
||||
with pytest.raises(ValueError, match="defines both"):
|
||||
URLsPackage(s)
|
||||
|
||||
@@ -236,7 +237,7 @@ class LicensedPackage(spack.package.Package):
|
||||
extendees = None # currently a required attribute for is_extension()
|
||||
license_files = None
|
||||
|
||||
s = spack.spec.Spec("a")
|
||||
s = spack.spec.Spec("pkg-a")
|
||||
pkg = LicensedPackage(s)
|
||||
assert pkg.global_license_file is None
|
||||
|
||||
@@ -249,21 +250,21 @@ class BaseTestPackage(Package):
|
||||
|
||||
|
||||
def test_package_version_fails():
|
||||
s = spack.spec.Spec("a")
|
||||
s = spack.spec.Spec("pkg-a")
|
||||
pkg = BaseTestPackage(s)
|
||||
with pytest.raises(ValueError, match="does not have a concrete version"):
|
||||
pkg.version()
|
||||
|
||||
|
||||
def test_package_tester_fails():
|
||||
s = spack.spec.Spec("a")
|
||||
s = spack.spec.Spec("pkg-a")
|
||||
pkg = BaseTestPackage(s)
|
||||
with pytest.raises(ValueError, match="without concrete version"):
|
||||
pkg.tester()
|
||||
|
||||
|
||||
def test_package_fetcher_fails():
|
||||
s = spack.spec.Spec("a")
|
||||
s = spack.spec.Spec("pkg-a")
|
||||
pkg = BaseTestPackage(s)
|
||||
with pytest.raises(ValueError, match="without concrete version"):
|
||||
pkg.fetcher
|
||||
@@ -275,7 +276,7 @@ def compilers(compiler, arch_spec):
|
||||
|
||||
monkeypatch.setattr(spack.compilers, "compilers_for_spec", compilers)
|
||||
|
||||
s = spack.spec.Spec("a")
|
||||
s = spack.spec.Spec("pkg-a")
|
||||
pkg = BaseTestPackage(s)
|
||||
pkg.test_requires_compiler = True
|
||||
pkg.do_test()
|
||||
|
@@ -16,7 +16,7 @@
|
||||
import pytest
|
||||
|
||||
from llnl.util import filesystem as fs
|
||||
from llnl.util.symlink import symlink
|
||||
from llnl.util.symlink import readlink, symlink
|
||||
|
||||
import spack.binary_distribution as bindist
|
||||
import spack.cmd.buildcache as buildcache
|
||||
@@ -181,12 +181,12 @@ def test_relocate_links(tmpdir):
|
||||
relocate_links(["to_self", "to_dependency", "to_system"], prefix_to_prefix)
|
||||
|
||||
# These two are relocated
|
||||
assert os.readlink("to_self") == str(tmpdir.join("new_prefix_a", "file"))
|
||||
assert os.readlink("to_dependency") == str(tmpdir.join("new_prefix_b", "file"))
|
||||
assert readlink("to_self") == str(tmpdir.join("new_prefix_a", "file"))
|
||||
assert readlink("to_dependency") == str(tmpdir.join("new_prefix_b", "file"))
|
||||
|
||||
# These two are not.
|
||||
assert os.readlink("to_system") == system_path
|
||||
assert os.readlink("to_self_but_relative") == "relative"
|
||||
assert readlink("to_system") == system_path
|
||||
assert readlink("to_self_but_relative") == "relative"
|
||||
|
||||
|
||||
def test_needs_relocation():
|
||||
@@ -517,7 +517,7 @@ def test_manual_download(
|
||||
def _instr(pkg):
|
||||
return f"Download instructions for {pkg.spec.name}"
|
||||
|
||||
spec = default_mock_concretization("a")
|
||||
spec = default_mock_concretization("pkg-a")
|
||||
spec.package.manual_download = manual
|
||||
if instr:
|
||||
monkeypatch.setattr(spack.package_base.PackageBase, "download_instr", _instr)
|
||||
@@ -543,7 +543,7 @@ def test_fetch_without_code_is_noop(
|
||||
default_mock_concretization, install_mockery, fetching_not_allowed
|
||||
):
|
||||
"""do_fetch for packages without code should be a no-op"""
|
||||
pkg = default_mock_concretization("a").package
|
||||
pkg = default_mock_concretization("pkg-a").package
|
||||
pkg.has_code = False
|
||||
pkg.do_fetch()
|
||||
|
||||
@@ -552,7 +552,7 @@ def test_fetch_external_package_is_noop(
|
||||
default_mock_concretization, install_mockery, fetching_not_allowed
|
||||
):
|
||||
"""do_fetch for packages without code should be a no-op"""
|
||||
spec = default_mock_concretization("a")
|
||||
spec = default_mock_concretization("pkg-a")
|
||||
spec.external_path = "/some/where"
|
||||
assert spec.external
|
||||
spec.package.do_fetch()
|
||||
|
@@ -270,12 +270,9 @@ def trigger_bad_patch(pkg):
|
||||
def test_patch_failure_develop_spec_exits_gracefully(
|
||||
mock_packages, config, install_mockery, mock_fetch, tmpdir, mock_stage
|
||||
):
|
||||
"""
|
||||
ensure that a failing patch does not trigger exceptions
|
||||
for develop specs
|
||||
"""
|
||||
"""ensure that a failing patch does not trigger exceptions for develop specs"""
|
||||
|
||||
spec = Spec("patch-a-dependency " "^libelf dev_path=%s" % str(tmpdir))
|
||||
spec = Spec(f"patch-a-dependency ^libelf dev_path={tmpdir}")
|
||||
spec.concretize()
|
||||
libelf = spec["libelf"]
|
||||
assert "patches" in list(libelf.variants.keys())
|
||||
|
@@ -9,6 +9,8 @@
|
||||
import spack.package_base
|
||||
import spack.paths
|
||||
import spack.repo
|
||||
import spack.spec
|
||||
import spack.util.file_cache
|
||||
|
||||
|
||||
@pytest.fixture(params=["packages", "", "foo"])
|
||||
@@ -30,25 +32,25 @@ def extra_repo(tmpdir_factory, request):
|
||||
|
||||
|
||||
def test_repo_getpkg(mutable_mock_repo):
|
||||
mutable_mock_repo.get_pkg_class("a")
|
||||
mutable_mock_repo.get_pkg_class("builtin.mock.a")
|
||||
mutable_mock_repo.get_pkg_class("pkg-a")
|
||||
mutable_mock_repo.get_pkg_class("builtin.mock.pkg-a")
|
||||
|
||||
|
||||
def test_repo_multi_getpkg(mutable_mock_repo, extra_repo):
|
||||
mutable_mock_repo.put_first(extra_repo[0])
|
||||
mutable_mock_repo.get_pkg_class("a")
|
||||
mutable_mock_repo.get_pkg_class("builtin.mock.a")
|
||||
mutable_mock_repo.get_pkg_class("pkg-a")
|
||||
mutable_mock_repo.get_pkg_class("builtin.mock.pkg-a")
|
||||
|
||||
|
||||
def test_repo_multi_getpkgclass(mutable_mock_repo, extra_repo):
|
||||
mutable_mock_repo.put_first(extra_repo[0])
|
||||
mutable_mock_repo.get_pkg_class("a")
|
||||
mutable_mock_repo.get_pkg_class("builtin.mock.a")
|
||||
mutable_mock_repo.get_pkg_class("pkg-a")
|
||||
mutable_mock_repo.get_pkg_class("builtin.mock.pkg-a")
|
||||
|
||||
|
||||
def test_repo_pkg_with_unknown_namespace(mutable_mock_repo):
|
||||
with pytest.raises(spack.repo.UnknownNamespaceError):
|
||||
mutable_mock_repo.get_pkg_class("unknown.a")
|
||||
mutable_mock_repo.get_pkg_class("unknown.pkg-a")
|
||||
|
||||
|
||||
def test_repo_unknown_pkg(mutable_mock_repo):
|
||||
@@ -142,14 +144,14 @@ def test_get_all_mock_packages(mock_packages):
|
||||
|
||||
def test_repo_path_handles_package_removal(tmpdir, mock_packages):
|
||||
builder = spack.repo.MockRepositoryBuilder(tmpdir, namespace="removal")
|
||||
builder.add_package("c")
|
||||
builder.add_package("pkg-c")
|
||||
with spack.repo.use_repositories(builder.root, override=False) as repos:
|
||||
r = repos.repo_for_pkg("c")
|
||||
r = repos.repo_for_pkg("pkg-c")
|
||||
assert r.namespace == "removal"
|
||||
|
||||
builder.remove("c")
|
||||
builder.remove("pkg-c")
|
||||
with spack.repo.use_repositories(builder.root, override=False) as repos:
|
||||
r = repos.repo_for_pkg("c")
|
||||
r = repos.repo_for_pkg("pkg-c")
|
||||
assert r.namespace == "builtin.mock"
|
||||
|
||||
|
||||
|
@@ -138,19 +138,19 @@ def test_specify_preinstalled_dep(tmpdir, monkeypatch):
|
||||
transitive dependency that is only supplied by the preinstalled package.
|
||||
"""
|
||||
builder = spack.repo.MockRepositoryBuilder(tmpdir)
|
||||
builder.add_package("c")
|
||||
builder.add_package("b", dependencies=[("c", None, None)])
|
||||
builder.add_package("a", dependencies=[("b", None, None)])
|
||||
builder.add_package("pkg-c")
|
||||
builder.add_package("pkg-b", dependencies=[("pkg-c", None, None)])
|
||||
builder.add_package("pkg-a", dependencies=[("pkg-b", None, None)])
|
||||
|
||||
with spack.repo.use_repositories(builder.root):
|
||||
b_spec = Spec("b").concretized()
|
||||
monkeypatch.setattr(Spec, "installed", property(lambda x: x.name != "a"))
|
||||
b_spec = Spec("pkg-b").concretized()
|
||||
monkeypatch.setattr(Spec, "installed", property(lambda x: x.name != "pkg-a"))
|
||||
|
||||
a_spec = Spec("a")
|
||||
a_spec = Spec("pkg-a")
|
||||
a_spec._add_dependency(b_spec, depflag=dt.BUILD | dt.LINK, virtuals=())
|
||||
a_spec.concretize()
|
||||
|
||||
assert set(x.name for x in a_spec.traverse()) == set(["a", "b", "c"])
|
||||
assert {x.name for x in a_spec.traverse()} == {"pkg-a", "pkg-b", "pkg-c"}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("config")
|
||||
@@ -982,15 +982,15 @@ def test_synthetic_construction_of_split_dependencies_from_same_package(mock_pac
|
||||
# Construct in a synthetic way (i.e. without using the solver)
|
||||
# the following spec:
|
||||
#
|
||||
# b
|
||||
# pkg-b
|
||||
# build / \ link,run
|
||||
# c@2.0 c@1.0
|
||||
# pkg-c@2.0 pkg-c@1.0
|
||||
#
|
||||
# To demonstrate that a spec can now hold two direct
|
||||
# dependencies from the same package
|
||||
root = Spec("b").concretized()
|
||||
link_run_spec = Spec("c@=1.0").concretized()
|
||||
build_spec = Spec("c@=2.0").concretized()
|
||||
root = Spec("pkg-b").concretized()
|
||||
link_run_spec = Spec("pkg-c@=1.0").concretized()
|
||||
build_spec = Spec("pkg-c@=2.0").concretized()
|
||||
|
||||
root.add_dependency_edge(link_run_spec, depflag=dt.LINK, virtuals=())
|
||||
root.add_dependency_edge(link_run_spec, depflag=dt.RUN, virtuals=())
|
||||
@@ -998,10 +998,10 @@ def test_synthetic_construction_of_split_dependencies_from_same_package(mock_pac
|
||||
|
||||
# Check dependencies from the perspective of root
|
||||
assert len(root.dependencies()) == 2
|
||||
assert all(x.name == "c" for x in root.dependencies())
|
||||
assert all(x.name == "pkg-c" for x in root.dependencies())
|
||||
|
||||
assert "@2.0" in root.dependencies(name="c", deptype=dt.BUILD)[0]
|
||||
assert "@1.0" in root.dependencies(name="c", deptype=dt.LINK | dt.RUN)[0]
|
||||
assert "@2.0" in root.dependencies(name="pkg-c", deptype=dt.BUILD)[0]
|
||||
assert "@1.0" in root.dependencies(name="pkg-c", deptype=dt.LINK | dt.RUN)[0]
|
||||
|
||||
# Check parent from the perspective of the dependencies
|
||||
assert len(build_spec.dependents()) == 1
|
||||
@@ -1013,30 +1013,30 @@ def test_synthetic_construction_of_split_dependencies_from_same_package(mock_pac
|
||||
def test_synthetic_construction_bootstrapping(mock_packages, config):
|
||||
# Construct the following spec:
|
||||
#
|
||||
# b@2.0
|
||||
# pkg-b@2.0
|
||||
# | build
|
||||
# b@1.0
|
||||
# pkg-b@1.0
|
||||
#
|
||||
root = Spec("b@=2.0").concretized()
|
||||
bootstrap = Spec("b@=1.0").concretized()
|
||||
root = Spec("pkg-b@=2.0").concretized()
|
||||
bootstrap = Spec("pkg-b@=1.0").concretized()
|
||||
|
||||
root.add_dependency_edge(bootstrap, depflag=dt.BUILD, virtuals=())
|
||||
|
||||
assert len(root.dependencies()) == 1
|
||||
assert root.dependencies()[0].name == "b"
|
||||
assert root.name == "b"
|
||||
assert root.dependencies()[0].name == "pkg-b"
|
||||
assert root.name == "pkg-b"
|
||||
|
||||
|
||||
def test_addition_of_different_deptypes_in_multiple_calls(mock_packages, config):
|
||||
# Construct the following spec:
|
||||
#
|
||||
# b@2.0
|
||||
# pkg-b@2.0
|
||||
# | build,link,run
|
||||
# b@1.0
|
||||
# pkg-b@1.0
|
||||
#
|
||||
# with three calls and check we always have a single edge
|
||||
root = Spec("b@=2.0").concretized()
|
||||
bootstrap = Spec("b@=1.0").concretized()
|
||||
root = Spec("pkg-b@=2.0").concretized()
|
||||
bootstrap = Spec("pkg-b@=1.0").concretized()
|
||||
|
||||
for current_depflag in (dt.BUILD, dt.LINK, dt.RUN):
|
||||
root.add_dependency_edge(bootstrap, depflag=current_depflag, virtuals=())
|
||||
@@ -1063,9 +1063,9 @@ def test_addition_of_different_deptypes_in_multiple_calls(mock_packages, config)
|
||||
def test_adding_same_deptype_with_the_same_name_raises(
|
||||
mock_packages, config, c1_depflag, c2_depflag
|
||||
):
|
||||
p = Spec("b@=2.0").concretized()
|
||||
c1 = Spec("b@=1.0").concretized()
|
||||
c2 = Spec("b@=2.0").concretized()
|
||||
p = Spec("pkg-b@=2.0").concretized()
|
||||
c1 = Spec("pkg-b@=1.0").concretized()
|
||||
c2 = Spec("pkg-b@=2.0").concretized()
|
||||
|
||||
p.add_dependency_edge(c1, depflag=c1_depflag, virtuals=())
|
||||
with pytest.raises(spack.error.SpackError):
|
||||
@@ -1105,3 +1105,23 @@ def test_indexing_prefers_direct_or_transitive_link_deps():
|
||||
|
||||
# Ensure that the full DAG is still searched
|
||||
assert root["a2"]
|
||||
|
||||
|
||||
def test_getitem_sticks_to_subdag():
|
||||
"""Test that indexing on Spec by virtual does not traverse outside the dag, which happens in
|
||||
the unlikely case someone would rewrite __getitem__ in terms of edges_from_dependents instead
|
||||
of edges_to_dependencies."""
|
||||
x, y, z = Spec("x"), Spec("y"), Spec("z")
|
||||
x.add_dependency_edge(z, depflag=dt.LINK, virtuals=("virtual",))
|
||||
y.add_dependency_edge(z, depflag=dt.LINK, virtuals=())
|
||||
assert x["virtual"].name == "z"
|
||||
with pytest.raises(KeyError):
|
||||
y["virtual"]
|
||||
|
||||
|
||||
def test_getitem_finds_transitive_virtual():
|
||||
x, y, z = Spec("x"), Spec("y"), Spec("z")
|
||||
x.add_dependency_edge(z, depflag=dt.LINK, virtuals=())
|
||||
x.add_dependency_edge(y, depflag=dt.LINK, virtuals=())
|
||||
y.add_dependency_edge(z, depflag=dt.LINK, virtuals=("virtual",))
|
||||
assert x["virtual"].name == "z"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user