Compare commits

..

118 Commits

Author SHA1 Message Date
Harmen Stoppels
25c74506a3 cmake.py: include transitive run deps in CMAKE_PREFIX_PATH
- Run deps often need to be located during the build
- In cases like python-venv and python, where there's a direct build dep
  on both, but python-venv has a run dep on python, meaning we'd rather
  see python-venv in path before python topologically.
2024-11-28 18:27:15 +01:00
Harmen Stoppels
e37e53cfe8 traverse: add MixedDepthVisitor, use in cmake (#47750)
This visitor accepts the sub-dag of all nodes and unique edges that have
deptype X directly from given roots, or deptype Y transitively for any
of the roots.
2024-11-28 17:48:48 +01:00
Andrey Perestoronin
cf31d20d4c add new packages (#47817) 2024-11-28 09:49:52 -05:00
Harmen Stoppels
b74db341c8 darwin: preserve hardlinks on codesign/install_name_tool (#47808) 2024-11-28 14:57:28 +01:00
Daryl W. Grunau
e88a3f6f85 eospac: version 6.5.12 (#47826)
Co-authored-by: Daryl W. Grunau <dwg@lanl.gov>
2024-11-28 12:32:35 +01:00
Massimiliano Culpo
9bd7483e73 Add further C and C++ dependencies to packages (#47821) 2024-11-28 10:50:35 +01:00
Harmen Stoppels
04c76fab63 hip: hints for find_package llvm/clang (#47788)
LLVM can be a transitive link dependency of hip through gl's dependency mesa, which uses it for software rendering.

In this case make sure llvm-amdgpu is found with find_package(LLVM) and
find_package(Clang) by setting LLVM_ROOT and Clang_ROOT.

That makes the patch of find_package's HINTS redundant, so remove that.
It did not work anyways, because CMAKE_PREFIX_PATH has higher precedence
than HINTS.
2024-11-28 10:23:09 +01:00
Adam J. Stewart
ecbf9fcacf py-scooby: add v0.10.0 (#47790) 2024-11-28 10:21:36 +01:00
Victor A. P. Magri
69fb594699 hypre: add a variant to allow using internal lapack functions (#47780) 2024-11-28 10:15:12 +01:00
Howard Pritchard
d28614151f nghttp2: add v1.64.0 (#47800)
Signed-off-by: Howard Pritchard <hppritcha@gmail.com>
2024-11-28 10:12:41 +01:00
etiennemlb
f1d6af6c94 netlib-scalapack: fix for some clang derivative (cce/rocmcc) (#45434) 2024-11-28 09:25:33 +01:00
Adam J. Stewart
192821f361 py-river: mark numpy 2 compatibility (#47813) 2024-11-28 09:24:21 +01:00
Adam J. Stewart
18790ca397 py-pyvista: VTK 9.4 not yet supported (#47815) 2024-11-28 09:23:41 +01:00
BOUDAOUD34
c22d77a38e dbcsr: patch for resolving .mod file conflicts in ROCm by implementing USE, INTRINSIC (#46181)
Co-authored-by: U-PALLAS\boudaoud <boudaoud@pc44.pallas.cines.fr>
2024-11-28 09:20:48 +01:00
Tom Payerle
d82bdb3bf7 seacas: update recipe to find faodel (#40239)
Explcitly sets the CMake variables  Faodel_INCLUDE_DIRS and Faodel_LIBRARY_DIRS when +faodel.
This seems to be needed for recent versions of seacas (seacas@2021-04-02:), but should be safe
to do for all versions.

For Faodel_INCLUDE_DIRS, it looks like Faodel has header files under $(Faodel_Prefix)/include/faodel,
but seacas is not including the "faodel" part in #includes.  So add both $(Faodel_Prefix)/include
and $(Foadel_Prefix)/include/faodel

Co-authored-by: payerle <payerle@users.noreply.github.com>
2024-11-28 09:17:44 +01:00
Matt Thompson
a042bdfe0b mapl: add hpcx-mpi (#47793) 2024-11-28 09:15:25 +01:00
Adam J. Stewart
60e3e645e8 py-joblib: add v1.4.2 (#47789) 2024-11-28 08:28:44 +01:00
Chris Marsh
51785437bc Patch to fix building gcc@14.2 on darwin. Fixes #45628 (#47830) 2024-11-27 20:58:18 -07:00
dependabot[bot]
2e8db0815d build(deps): bump docker/build-push-action from 6.9.0 to 6.10.0 (#47819)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](4f58ea7922...48aba3b46d)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-27 16:29:41 -07:00
George Malerbo
8a6428746f raylib: add v5.5 (#47708)
* Add version 5.5 in package.py

* Update package.py
2024-11-27 16:25:22 -07:00
Adam J. Stewart
6b9c099af8 py-keras: add v3.7.0 (#47816) 2024-11-27 16:12:47 -07:00
Derek Ryan Strong
30814fb4e0 Deprecate rsync releases before v3.2.5 (#47820) 2024-11-27 16:14:34 -06:00
Harmen Stoppels
3194be2e92 gcc-runtime: remove libz.so from libgfortran.so if present (#47812) 2024-11-27 22:32:37 +01:00
snehring
41be2f5899 ltr-retriever: changing directory layout (#38513) 2024-11-27 14:16:57 -07:00
kwryankrattiger
02af41ebb3 gdk-pixbuf: Point at gitlab instead of broken mirror (#47825) 2024-11-27 15:13:55 -06:00
snehring
9d33c89030 r-rsamtools: add -lz to Makevars (#38649) 2024-11-27 13:44:48 -07:00
Erik Heeren
51ab7bad3b julia: conflict for %gcc@12: support (#35931) 2024-11-27 04:31:44 -07:00
kwryankrattiger
0b094f2473 Docs: Reference 7z requirement on Windows (#35943) 2024-11-26 17:11:12 -05:00
Christoph Junghans
cd306d0bc6 all-libary: add voronoi support and git version (#47798)
* all-libary: add voronoi support and git version

---------

Co-authored-by: Wouter Deconinck <wdconinc@gmail.com>
2024-11-26 14:56:22 -07:00
Dom Heinzeller
fdb9cf2412 Intel/oneapi compilers: correct version ranges for diab-disable flag (#47428)
* c/c++ flags should have been modified for all 2023.x.y versions, but
  upper bound was too low
* Fortran flags should have been modified for all 2024.x.y versions, but
  likewise the upper bound was too low
2024-11-26 12:34:37 -07:00
etiennemlb
a546441d2e siesta: remove link args on a non-declared dependency (#46080) 2024-11-26 20:25:04 +01:00
IHuismann
141cdb6810 adol-c: fix libs property (#36614) 2024-11-26 17:01:18 +01:00
Brian Van Essen
f2ab74efe5 cray: add further versions of Cray packages. (#37733) 2024-11-26 16:59:53 +01:00
Martin Aumüller
38b838e405 openscenegraph: remove X11 dependencies for macos (#39037) 2024-11-26 16:59:10 +01:00
Mark Abraham
c037188b59 gromacs: announce deprecation policy and start to implement (#47804)
* gromacs: announce deprecation policy and start to implement

* Style it up

* [@spackbot] updating style on behalf of mabraham

* Bump versions used in CI

---------

Co-authored-by: mabraham <mabraham@users.noreply.github.com>
2024-11-26 05:54:07 -07:00
Mark Abraham
0835a3c5f2 gromacs: obtain SYCL from either ACpp or intel-oneapi-runtime (#47806) 2024-11-26 05:51:54 -07:00
Mark Abraham
38a2f9c2f2 gromacs: Improve HeFFTe dependency (#47805)
GROMACS supports HeFFTe with either SYCL or CUDA build and requires
a matching HeFFTe build
2024-11-26 05:50:41 -07:00
Massimiliano Culpo
eecd4afe58 gromacs: fix the value used for the ITT directory (#47795) 2024-11-26 08:14:45 +01:00
Seth R. Johnson
83624551e0 ROOT: default to +aqua~x on macOS (#47792) 2024-11-25 14:27:38 -06:00
Victor A. P. Magri
741652caa1 caliper: add "tools" variant (#47779) 2024-11-25 18:26:53 +01:00
Mark Abraham
8e914308f0 gromacs: add itt variant (#47764)
Permit configuring GROMACS with support for mdrun to trace its timing
regions by calling the ITT API. This permits tools like VTune and
unitrace to augment their analysis with GROMACS-specific annotation.
2024-11-25 16:12:55 +01:00
Mikael Simberg
3c220d0989 apex: add 2.7.0 (#47736) 2024-11-25 13:22:16 +01:00
Wouter Deconinck
8094fa1e2f py-gradio: add v5.1.0 (and add/update dependencies) (fix CVEs) (#47504)
* py-pdm-backend: add v2.4.3
* py-starlette: add v0.28.0, v0.32.0, v0.35.1, v0.36.3, v0.37.2, v0.41.2
* py-fastapi: add v0.110.2, v0.115.4
* py-pydantic-extra-types: add v2.10.0
* py-pydantic-settings: add v2.6.1
* py-python-multipart: add v0.0.17
* py-email-validator: add v2.2.0
2024-11-25 13:07:56 +01:00
Massimiliano Culpo
5c67051980 Add missing C/C++ dependencies (#47782) 2024-11-25 12:56:39 +01:00
John W. Parent
c01fb9a6d2 Add CMake 3.31 minor release (#47676) 2024-11-25 04:32:57 -07:00
Harmen Stoppels
bf12bb57e7 install_test: first look at builder, then package (#47735) 2024-11-25 11:53:28 +01:00
Wouter Deconinck
406c73ae11 py-boto*: add v1.34.162 (#47528)
Co-authored-by: wdconinc <wdconinc@users.noreply.github.com>
2024-11-25 11:39:09 +01:00
Wouter Deconinck
3f50ccfcdd py-azure-*: updated versions (#47525) 2024-11-25 11:38:49 +01:00
Wouter Deconinck
9883a2144d py-quart: add v0.19.8 (#47508)
Co-authored-by: wdconinc <wdconinc@users.noreply.github.com>
2024-11-25 11:38:22 +01:00
Wouter Deconinck
94815d2227 py-netifaces: add v0.10.9, v0.11.0 (#47451) 2024-11-25 11:37:41 +01:00
Wouter Deconinck
a15563f890 py-werkzeug: add v3.1.3 (and deprecate older versions) (#47507)
Co-authored-by: wdconinc <wdconinc@users.noreply.github.com>
2024-11-25 11:28:01 +01:00
Wouter Deconinck
ac2ede8d2f py-pyzmq: add v25.1.2, v26.0.3, v26.1.1, v26.2.0 (switch to py-scikit-build-core) (#44493) 2024-11-25 11:00:00 +01:00
david-edwards-linaro
b256a7c50d linaro-forge: remove v21.1.3 (#47688) 2024-11-25 10:53:27 +01:00
Szabolcs Horvát
21e10d6d98 igraph: add v0.10.15 (#47692) 2024-11-25 10:51:24 +01:00
afzpatel
ed39967848 rocm-tensile: add 6.2.1 (#47702) 2024-11-25 10:40:21 +01:00
Alex Richert
eda0c6888e ip: add cmake version requirement for @5.1: (#47754) 2024-11-25 02:38:08 -07:00
pauleonix
66055f903c cuda: Add v12.6.3 (#47721) 2024-11-25 10:36:11 +01:00
Dave Keeshan
a1c57d86c3 libusb: add v1.0.23:1.0.27 (#47727) 2024-11-25 10:33:08 +01:00
Dave Keeshan
9da8dcae97 verible: add v0.0.3841 (#47729) 2024-11-25 10:30:48 +01:00
jflrichard
c93f223a73 postgis: add version 3.1.2 (#47743) 2024-11-25 10:24:03 +01:00
Wouter Deconinck
f1faf31735 build-containers: determine latest release tag and push that as latest (#47742) 2024-11-25 10:20:58 +01:00
Stephen Herbener
8957ef0df5 Updated version specs for bufr-query package. (#47752) 2024-11-25 10:14:16 +01:00
Veselin Dobrev
347ec87fc5 mfem: add logic for the C++ standard level when using rocPRIM (#47751) 2024-11-25 10:13:22 +01:00
Adam J. Stewart
cd8c46e54e py-ruff: add v0.8.0 (#47758) 2024-11-25 10:02:31 +01:00
Adam J. Stewart
75b03bc12f glib: add v2.82.2 (#47766) 2024-11-24 20:55:18 +01:00
Adam J. Stewart
58511a3352 py-pandas: correct Python version constraint (#47770) 2024-11-24 17:48:16 +01:00
Adam J. Stewart
325873a4c7 py-fsspec: add v2024.10.0 (#47778) 2024-11-24 15:42:30 +01:00
Adam J. Stewart
9156e4be04 awscli-v2: add v2.22.4 (#47765) 2024-11-24 15:42:06 +01:00
Adam J. Stewart
12d3abc736 py-pytz: add v2024.2 (#47777) 2024-11-24 15:40:45 +01:00
Adam J. Stewart
4208aa6291 py-torchvision: add Python 3.13 support (#47776) 2024-11-24 15:40:11 +01:00
Adam J. Stewart
0bad754e23 py-scikit-learn: add Python 3.13 support (#47775) 2024-11-24 15:39:36 +01:00
Adam J. Stewart
cde2620f41 py-safetensors: add v0.4.5 (#47774) 2024-11-24 15:38:05 +01:00
Adam J. Stewart
a35aa038b0 py-pystac: add support for Python 3.12+ (#47772) 2024-11-24 15:37:43 +01:00
Adam J. Stewart
150416919e py-pydantic-core: add v2.27.1 (#47771) 2024-11-24 15:37:06 +01:00
Adam J. Stewart
281c274e0b py-jupyter-packaging: add Python 3.13 support (#47769) 2024-11-24 15:31:31 +01:00
Adam J. Stewart
16e130ece1 py-cryptography: mark Python 3.13 support (#47768) 2024-11-24 15:31:08 +01:00
Adam J. Stewart
7586303fba py-ruamel-yaml-clib: add Python compatibility bounds (#47773) 2024-11-24 15:28:45 +01:00
Harmen Stoppels
6501880fbf py-node-env: add v1.9.1 (#47762) 2024-11-24 15:27:16 +01:00
Harmen Stoppels
c76098038c py-ipykernel: split forward and backward compat bounds (#47763) 2024-11-24 15:26:15 +01:00
Harmen Stoppels
124b616b27 add a few forward compat bounds with python (#47761) 2024-11-24 15:23:11 +01:00
Adam J. Stewart
1148c8f195 gobject-introspection: Python 3.12 still not supported (#47767) 2024-11-24 03:53:32 -07:00
Adam J. Stewart
c57452dd08 py-cffi: support Python 3.12+ (#47713) 2024-11-24 08:41:29 +01:00
Harmen Stoppels
a7e57c9a14 py-opt-einsum: add v3.4.0 (#47759) 2024-11-24 08:40:29 +01:00
Teague Sterling
85d83f9c26 duckdb: add v1.1.3, deprecate <v1.1.0 (#47653)
* duckdb: add v1.0.0, v0.10.3

* Adding issue reference

* Adding issue reference

* duckdb: add v1.1.0

Signed-off-by: Teague Sterling <teaguesterling@gmail.com>

* Fixing styles

Signed-off-by: Teague Sterling <teaguesterling@gmail.com>

* duckdb: add v1.1.1

Signed-off-by: Teague Sterling <teaguesterling@gmail.com>

* duckdb: Fix missing depends_on(unixodbc, when=+odbc)

* Adding duckdb variants, removing old variants, removing deprecated versions

Signed-off-by: Teague Sterling <teaguesterling@gmail.com>

* duckdb+static_openssl: Add pkgconfig and zlib-api to link zlib when needed

* duckdb: add v1.1.3

Signed-off-by: Teague Sterling <teaguesterling@gmail.com>

* Update package.py for CVE-2024-41672 as suggested

* [@spackbot] updating style on behalf of teaguesterling

* duckdb: add CVE comment before deprecated versions

---------

Signed-off-by: Teague Sterling <teaguesterling@gmail.com>
Co-authored-by: Bernhard Kaindl <bernhardkaindl7@gmail.com>
Co-authored-by: Wouter Deconinck <wdconinc@gmail.com>
2024-11-23 13:13:40 -07:00
finkandreas
39a081d7fd Kokkos complex_align variant, Trilinos+PETSc enforcement for Kokkos~complex_align (#47686) 2024-11-23 07:45:22 -07:00
Harmen Stoppels
71b65bb424 py-opt-einsum: missing forward compat bound for python (#47757) 2024-11-23 10:48:07 +01:00
Adam J. Stewart
3dcbd118df py-cython: support Python 3.12+ (#47714)
and add various other compat bounds on dependents
2024-11-22 22:20:41 +01:00
Harmen Stoppels
5dacb774f6 itk: use vendored googletest (#47687)
external googletest breaks dependents because they end up with
ITK_LIBRARIES set to `GTest::GTest;GTest::Main`, which then end up
literally in a nonsensical link line `-lGTest::GtTest`.

the vendored googletest produces a cmake config file where
`ITKGoogleTest_LIBRARIES` is empty.
2024-11-22 18:41:23 +01:00
Harmen Stoppels
cb3d6549c9 traverse.py: ensure topo order is bfs for trees (#47720) 2024-11-22 15:04:19 +01:00
Mark Abraham
559c2f1eb9 gromacs: oneapi does not always require gcc (#47679)
* gromacs: oneapi does not always require gcc

* Support intel_provided_gcc only with %intel classic compiler

Require gcc only when needed with %intel

* New approach depending on gcc-runtime directly

* Update var/spack/repos/builtin/packages/gromacs/package.py

Co-authored-by: Christoph Junghans <christoph.junghans@gmail.com>

---------

Co-authored-by: Christoph Junghans <christoph.junghans@gmail.com>
2024-11-22 06:33:30 -07:00
Harmen Stoppels
ed1dbea77b eigen: self.builder.build_directory -> self.build_directory (#47728) 2024-11-22 07:20:38 +01:00
Seth R. Johnson
6ebafe4631 vecgeom: add v1.2.10 and delete unused, deprecated versions (#47725)
* vecgeom: add v1.2.10

* Remove unused+deprecated versions of vecgeom

* Deprecate older v1.2.x  versions

* [@spackbot] updating style on behalf of sethrj
2024-11-21 17:03:09 -07:00
Harmen Stoppels
7f0bb7147d README.md update old tutorial URL (#47718) 2024-11-21 16:46:46 +01:00
Satish Balay
f41b38e93d xsdk: add v1.1.0 (#47635)
xsdk: exclude pflotran, alquimia, exago

heffte: ~fftw when=+hip

dealii: ~sundials ~opencascade ~vtk ~taskflow
2024-11-21 08:08:27 -06:00
Massimiliano Culpo
5fd12b7bea Add further missing C, C++ dependencies to packages (#47662) 2024-11-21 14:49:12 +01:00
Mikael Simberg
fe746bdebb aws-ofi-nccl: Add 1.8.1 to 1.13.0 (#47717) 2024-11-21 05:37:57 -07:00
eugeneswalker
453af4b9f7 hdf5-vol-cache %cce: add -Wno-error=incompatible-function-pointer-types (#47698) 2024-11-20 14:56:19 -08:00
eugeneswalker
29cf1559cc netlib-scalapack %cce: add cflags -Wno-error=implicit-function-declaration (#47701) 2024-11-20 15:09:14 -07:00
eugeneswalker
a9b3e1670b mpifileutils%cce: append cflags -Wno-error=implicit-function-declaration (#47700) 2024-11-20 14:19:05 -07:00
kwryankrattiger
4f9aa6004b visit: add v3.4.0, v3.4.1 (#47161)
* Visit: Add new versions 3.4.0 and 3.4.1

* Adios2: Restrict python, 3.11 doesn't not work for older Adios2

* VisIt: Set the VTK_VERSION for @3.4:

Older versions of VTK used the VTK_{MAJOR, MINOR}_VERSION variables for
VTK detection. VisIt >= 3.4 uses the full string VTK_VERSION.

* CI: Don't build llvm-amdgpu for non-HIP stack

* VisIt: v3.4.1 handles newer Adios2 correctly

* Visit: Add missing links in HDF5, set correct VTK version configuration parameter

* VisIt: Add py-pip requirement and patch visit with configuration changes

* HDF5 symlinks move when inside of callback

* VisIt ninja install fails with python module. Using make does not

* VisIt 3.4 has a high minimum cmake requirement

* HDF5: Early return when not mpi for mpi symlinks

* HDF5: Use platform agnostic method for creating legacy compatible MPI symlinks

* Fix VISIT_VTK_VERSION handling for 8.2.1a hack
2024-11-20 18:37:56 +01:00
Harmen Stoppels
aa2c18e4df spack style: import-check -> import, fix bugs, exclude spack.pkg (#47690) 2024-11-20 16:15:28 +01:00
dependabot[bot]
0ff3e86315 build(deps): bump codecov/codecov-action from 5.0.2 to 5.0.3 (#47683)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.0.2 to 5.0.3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](5c47607acb...05f5a9cfad)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 20:40:01 -06:00
dependabot[bot]
df208c1095 build(deps): bump docker/metadata-action from 5.5.1 to 5.6.1 (#47682)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.5.1 to 5.6.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](8e5442c4ef...369eb591f4)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 20:39:45 -06:00
Chris Marsh
853f70edc8 cgal: update depends versions for 6.0.1 (#47516)
* This extends PR #47285 to properly include some of the required version constrains of cgal 6 incl C++ standard. It also adds the new no-gmp backend as a variant.

* fix style

* disable cgal@6 +demo variant as the demos require qt6 which is not in spack

* disable the gmp variant until clarity on how its supposed to work is provided. bound shared and header_only variants to relevant versions

* Fix missing msvc compiler limit, fix variant left in

* Add more comments. Better describe the gmp variant. Remove testing code

* fix style
2024-11-19 16:43:21 -07:00
Paul R. C. Kent
50970f866e Add llvm v19.1.4 (#47681) 2024-11-19 16:03:28 -07:00
Wouter Deconinck
8821300985 py-gevent: add v24.2.1, v24.10.3, v24.11.1 (#47646)
* py-gevent: add v24.2.1, v24.10.3
* py-gevent: add v24.11.1
2024-11-19 12:14:52 -08:00
AMD Toolchain Support
adc8e1d996 Restrict disable dynamic thread scaling only to 3.1 version (#47673)
Co-authored-by: vijay kallesh <Vijay-teekinavar.Kallesh@amd.com>
2024-11-19 12:12:21 -08:00
Andrey Perestoronin
1e0aac6ac3 Add new 2025.0.1 Oneapi patch packages (#47678) 2024-11-19 11:38:42 -07:00
Harmen Stoppels
99e2313d81 openturns: fix deps (#47669) 2024-11-19 18:13:47 +01:00
Mark Abraham
22690a7576 Make oneAPI library-with-sdk specialize library class (#47632) 2024-11-19 12:12:10 -05:00
Harmen Stoppels
5325cfe865 systemd: symlink the internal libraries so they are found in rpath (#47667) 2024-11-19 15:28:49 +01:00
Harmen Stoppels
5333925dd7 sensei: fix install rpath for python extension (#47670) 2024-11-19 15:23:54 +01:00
Massimiliano Culpo
2db99e1ff6 gmp: fix cxx dependency, remove dependency on fortran (#47671) 2024-11-19 15:19:08 +01:00
Massimiliano Culpo
68aa712a3e solver: add a timeout handle for users (#47661)
This PR adds a configuration setting to allow setting time limits for concretization.

For backward compatibility, the default is to set no time limit.
2024-11-19 15:00:26 +01:00
Mikael Simberg
2e71bc640c pika: Add 0.30.1 (#47666) 2024-11-19 05:44:41 -07:00
Dom Heinzeller
661f3621a7 netcdf-cxx: add a maintainer (#47665) 2024-11-19 05:28:38 -07:00
Massimiliano Culpo
f182032337 Restore message when concretizing in parallel (#47663)
It was lost in #44843
2024-11-19 12:28:14 +00:00
teddy
066666b7b1 py-non-regression-test-tools: add v1.1.6 & remove v1.1.2 (tag removed) (#47622)
* py-non-regression-test-tools: add v1.1.6  & remove v1.1.2 (tag removed)
* Update var/spack/repos/builtin/packages/py-non-regression-test-tools/package.py

Co-authored-by: Tamara Dahlgren <35777542+tldahlgren@users.noreply.github.com>

---------

Co-authored-by: t. chantrait <teddy.chantrait@cea.fr>
Co-authored-by: Tamara Dahlgren <35777542+tldahlgren@users.noreply.github.com>
2024-11-19 04:38:33 -07:00
434 changed files with 10259 additions and 6323 deletions

View File

@@ -57,7 +57,13 @@ jobs:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
- name: Determine latest release tag
id: latest
run: |
git fetch --quiet --tags
echo "tag=$(git tag --list --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" | tee -a $GITHUB_OUTPUT
- uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
id: docker_meta
with:
images: |
@@ -71,6 +77,7 @@ jobs:
type=semver,pattern={{major}}
type=ref,event=branch
type=ref,event=pr
type=raw,value=latest,enable=${{ github.ref == format('refs/tags/{0}', steps.latest.outputs.tag) }}
- name: Generate the Dockerfile
env:
@@ -113,7 +120,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build & Deploy ${{ matrix.dockerfile[0] }}
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355
with:
context: dockerfiles/${{ matrix.dockerfile[0] }}
platforms: ${{ matrix.dockerfile[1] }}

View File

@@ -29,6 +29,6 @@ jobs:
- run: coverage xml
- name: "Upload coverage report to CodeCov"
uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a
uses: codecov/codecov-action@05f5a9cfad807516dbbef9929c4a42df3eb78766
with:
verbose: true

View File

@@ -55,7 +55,7 @@ jobs:
cmake bison libbison-dev kcov
- name: Install Python packages
run: |
pip install --upgrade pip setuptools pytest pytest-xdist pytest-cov clingo
pip install --upgrade pip setuptools pytest pytest-xdist pytest-cov
pip install --upgrade flake8 "isort>=4.3.5" "mypy>=0.900" "click" "black"
- name: Setup git configuration
run: |
@@ -173,6 +173,7 @@ jobs:
spack bootstrap disable github-actions-v0.5
spack bootstrap disable github-actions-v0.6
spack bootstrap status
spack solve zlib
spack unit-test --verbose --cov --cov-config=pyproject.toml --cov-report=xml:coverage.xml lib/spack/spack/test/concretization/core.py
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
with:
@@ -210,7 +211,7 @@ jobs:
. share/spack/setup-env.sh
$(which spack) bootstrap disable spack-install
$(which spack) solve zlib
common_args=(--dist loadfile --tx '4*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python')
common_args=(--dist loadfile --tx '4*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python' -x)
$(which spack) unit-test --verbose --cov --cov-config=pyproject.toml --cov-report=xml:coverage.xml "${common_args[@]}"
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
with:
@@ -241,7 +242,7 @@ jobs:
env:
COVERAGE_FILE: coverage/.coverage-windows
run: |
spack unit-test --verbose --cov --cov-config=pyproject.toml
spack unit-test -x --verbose --cov --cov-config=pyproject.toml
./share/spack/qa/validate_last_exit.ps1
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
with:

View File

@@ -70,7 +70,7 @@ Tutorial
----------------
We maintain a
[**hands-on tutorial**](https://spack.readthedocs.io/en/latest/tutorial.html).
[**hands-on tutorial**](https://spack-tutorial.readthedocs.io/).
It covers basic to advanced usage, packaging, developer features, and large HPC
deployments. You can do all of the exercises on your own laptop using a
Docker container.

View File

@@ -55,3 +55,11 @@ concretizer:
splice:
explicit: []
automatic: false
# Maximum time, in seconds, allowed for the 'solve' phase. If set to 0, there is no time limit.
timeout: 0
# If set to true, exceeding the timeout will always result in a concretization error. If false,
# the best (suboptimal) model computed before the timeout is used.
#
# Setting this to false yields unreproducible results, so we advise to use that value only
# for debugging purposes (e.g. check which constraints can help Spack concretize faster).
error_on_timeout: true

View File

@@ -19,7 +19,7 @@ config:
install_tree:
root: $spack/opt/spack
projections:
all: "{architecture.platform}/{architecture.target}/{name}-{version}-{hash}"
all: "{architecture}/{compiler.name}-{compiler.version}/{name}-{version}-{hash}"
# install_tree can include an optional padded length (int or boolean)
# default is False (do not pad)
# if padded_length is True, Spack will pad as close to the system max path

View File

@@ -15,11 +15,12 @@
# -------------------------------------------------------------------------
packages:
all:
compiler:
- apple-clang
- clang
- gcc
providers:
c: [apple-clang, llvm, gcc]
cxx: [apple-clang, llvm, gcc]
elf: [libelf]
fortran: [gcc]
fuse: [macfuse]
gl: [apple-gl]
glu: [apple-glu]

View File

@@ -15,18 +15,19 @@
# -------------------------------------------------------------------------
packages:
all:
compiler: [gcc, clang, oneapi, xl, nag, fj, aocc]
providers:
awk: [gawk]
armci: [armcimpi]
blas: [openblas, amdblis]
c: [gcc, llvm, intel-oneapi-compilers, xl, aocc]
cxx: [gcc, llvm, intel-oneapi-compilers, xl, aocc]
c: [gcc]
cxx: [gcc]
D: [ldc]
daal: [intel-oneapi-daal]
elf: [elfutils]
fftw-api: [fftw, amdfftw]
flame: [libflame, amdlibflame]
fortran: [gcc, llvm]
fortran: [gcc]
fortran-rt: [gcc-runtime, intel-oneapi-runtime]
fuse: [libfuse]
gl: [glx, osmesa]

View File

@@ -15,8 +15,8 @@
# -------------------------------------------------------------------------
packages:
all:
compiler:
- msvc
providers:
c : [msvc]
cxx: [msvc]
mpi: [msmpi]
gl: [wgl]

View File

@@ -1326,6 +1326,7 @@ Required:
* Microsoft Visual Studio
* Python
* Git
* 7z
Optional:
* Intel Fortran (needed for some packages)
@@ -1391,6 +1392,13 @@ as the project providing Git support on Windows. This is additionally the recomm
for installing Git on Windows, a link to which can be found above. Spack requires the
utilities vendored by this project.
"""
7zip
"""
A tool for extracting ``.xz`` files is required for extracting source tarballs. The latest 7zip
can be located at https://sourceforge.net/projects/sevenzip/.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Step 2: Install and setup Spack
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

15
lib/spack/env/cc vendored
View File

@@ -41,6 +41,10 @@ SPACK_ENV_PATH
SPACK_DEBUG_LOG_DIR
SPACK_DEBUG_LOG_ID
SPACK_COMPILER_SPEC
SPACK_CC_RPATH_ARG
SPACK_CXX_RPATH_ARG
SPACK_F77_RPATH_ARG
SPACK_FC_RPATH_ARG
SPACK_LINKER_ARG
SPACK_SHORT_SPEC
SPACK_SYSTEM_DIRS
@@ -219,7 +223,6 @@ for param in $params; do
if eval "test -z \"\${${param}:-}\""; then
die "Spack compiler must be run from Spack! Input '$param' is missing."
fi
# FIXME (compiler as nodes) add checks on whether `SPACK_XX_RPATH` is set if `SPACK_XX` is set
done
# eval this because SPACK_MANAGED_DIRS and SPACK_SYSTEM_DIRS are inputs we don't wanna loop over.
@@ -400,7 +403,7 @@ dtags_to_strip="${SPACK_DTAGS_TO_STRIP}"
linker_arg="${SPACK_LINKER_ARG}"
# Set up rpath variable according to language.
rpath="ERROR: RPATH ARG WAS NOT SET, MAYBE THE PACKAGE DOES NOT DEPEND ON ${comp}?"
rpath="ERROR: RPATH ARG WAS NOT SET"
eval "rpath=\${SPACK_${comp}_RPATH_ARG:?${rpath}}"
# Dump the mode and exit if the command is dump-mode.
@@ -785,17 +788,15 @@ case "$mode" in
C)
extend spack_flags_list SPACK_ALWAYS_CFLAGS
extend spack_flags_list SPACK_CFLAGS
preextend flags_list SPACK_TARGET_ARGS_CC
;;
CXX)
extend spack_flags_list SPACK_ALWAYS_CXXFLAGS
extend spack_flags_list SPACK_CXXFLAGS
preextend flags_list SPACK_TARGET_ARGS_CXX
;;
F)
preextend flags_list SPACK_TARGET_ARGS_FORTRAN
;;
esac
# prepend target args
preextend flags_list SPACK_TARGET_ARGS
;;
esac

View File

@@ -24,6 +24,7 @@
Callable,
Deque,
Dict,
Generator,
Iterable,
List,
Match,
@@ -2838,6 +2839,25 @@ def temporary_dir(
remove_directory_contents(tmp_dir)
@contextmanager
def edit_in_place_through_temporary_file(file_path: str) -> Generator[str, None, None]:
"""Context manager for modifying ``file_path`` in place, preserving its inode and hardlinks,
for functions or external tools that do not support in-place editing. Notice that this function
is unsafe in that it works with paths instead of a file descriptors, but this is by design,
since we assume the call site will create a new inode at the same path."""
tmp_fd, tmp_path = tempfile.mkstemp(
dir=os.path.dirname(file_path), prefix=f"{os.path.basename(file_path)}."
)
# windows cannot replace a file with open fds, so close since the call site needs to replace.
os.close(tmp_fd)
try:
shutil.copyfile(file_path, tmp_path, follow_symlinks=True)
yield tmp_path
shutil.copyfile(tmp_path, file_path, follow_symlinks=True)
finally:
os.unlink(tmp_path)
def filesummary(path, print_bytes=16) -> Tuple[int, bytes]:
"""Create a small summary of the given file. Does not error
when file does not exist.

View File

@@ -73,7 +73,7 @@ def index_by(objects, *funcs):
if isinstance(f, str):
f = lambda x: getattr(x, funcs[0])
elif isinstance(f, tuple):
f = lambda x: tuple(getattr(x, p, None) for p in funcs[0])
f = lambda x: tuple(getattr(x, p) for p in funcs[0])
result = {}
for o in objects:
@@ -995,8 +995,11 @@ def _receive_forwarded(self, context: str, exc: Exception, tb: List[str]):
def grouped_message(self, with_tracebacks: bool = True) -> str:
"""Print out an error message coalescing all the forwarded errors."""
each_exception_message = [
"\n\t{0} raised {1}: {2}\n{3}".format(
context, exc.__class__.__name__, exc, f"\n{''.join(tb)}" if with_tracebacks else ""
"{0} raised {1}: {2}{3}".format(
context,
exc.__class__.__name__,
exc,
"\n{0}".format("".join(tb)) if with_tracebacks else "",
)
for context, exc, tb in self.exceptions
]

View File

@@ -11,7 +11,7 @@
import spack.util.git
#: PEP440 canonical <major>.<minor>.<micro>.<devN> string
__version__ = "1.0.0-alpha.1"
__version__ = "0.24.0.dev0"
spack_version = __version__

View File

@@ -55,7 +55,6 @@ def _search_duplicate_compilers(error_cls):
import spack.builder
import spack.config
import spack.deptypes
import spack.fetch_strategy
import spack.patch
import spack.repo
@@ -1015,14 +1014,7 @@ def _issues_in_depends_on_directive(pkgs, error_cls):
# depends_on('foo+bar ^fee+baz')
#
# but we'd like to have two dependencies listed instead.
nested_dependencies = dep.spec.edges_to_dependencies()
# Filter out pure build dependencies, like:
#
# depends_on('foo+bar%gcc')
#
nested_dependencies = [
x for x in nested_dependencies if x.depflag != spack.deptypes.BUILD
]
nested_dependencies = dep.spec.dependencies()
if nested_dependencies:
summary = f"{pkg_name}: nested dependency declaration '{dep.spec}'"
ndir = len(nested_dependencies) + 1

View File

@@ -765,14 +765,7 @@ def tarball_directory_name(spec):
Return name of the tarball directory according to the convention
<os>-<architecture>/<compiler>/<package>-<version>/
"""
if spec.original_spec_format() < 5:
compiler = spec.annotations.compiler_node_attribute
assert compiler is not None, "a compiler spec is expected"
return spec.format_path(
f"{spec.architecture}/{compiler.name}-{compiler.version}/{spec.name}-{spec.version}"
)
return spec.format_path(f"{spec.architecture.platform}/{spec.name}-{spec.version}")
return spec.format_path("{architecture}/{compiler.name}-{compiler.version}/{name}-{version}")
def tarball_name(spec, ext):
@@ -780,17 +773,9 @@ def tarball_name(spec, ext):
Return the name of the tarfile according to the convention
<os>-<architecture>-<package>-<dag_hash><ext>
"""
if spec.original_spec_format() < 5:
compiler = spec.annotations.compiler_node_attribute
assert compiler is not None, "a compiler spec is expected"
spec_formatted = (
f"{spec.architecture}-{compiler.name}-{compiler.version}-{spec.name}"
f"-{spec.version}-{spec.dag_hash()}"
)
else:
spec_formatted = (
f"{spec.architecture.platform}-{spec.name}-{spec.version}-{spec.dag_hash()}"
)
spec_formatted = spec.format_path(
"{architecture}-{compiler.name}-{compiler.version}-{name}-{version}-{hash}"
)
return f"{spec_formatted}{ext}"
@@ -2349,7 +2334,9 @@ def is_backup_file(file):
if not codesign:
return
for binary in changed_files:
codesign("-fs-", binary)
# preserve the original inode by running codesign on a copy
with fsys.edit_in_place_through_temporary_file(binary) as tmp_binary:
codesign("-fs-", tmp_binary)
# If we are installing back to the same location
# relocate the sbang location if the spack directory changed

View File

@@ -227,13 +227,12 @@ def _root_spec(spec_str: str) -> str:
# Add a compiler and platform requirement to the root spec.
platform = str(spack.platforms.host())
# FIXME (compiler as nodes): recover the compiler for source bootstrapping
# if platform == "darwin":
# spec_str += " %apple-clang"
if platform == "windows":
if platform == "darwin":
spec_str += " %apple-clang"
elif platform == "windows":
spec_str += " %msvc"
# elif platform == "linux":
# spec_str += " %gcc"
elif platform == "linux":
spec_str += " %gcc"
elif platform == "freebsd":
spec_str += " %clang"
spec_str += f" platform={platform}"

View File

@@ -16,9 +16,8 @@
import archspec.cpu
import spack.compilers.config
import spack.compilers.libraries
import spack.config
import spack.compiler
import spack.compilers
import spack.platforms
import spack.spec
import spack.traverse
@@ -40,7 +39,7 @@ def __init__(self, configuration):
self.external_cmake, self.external_bison = self._externals_from_yaml(configuration)
def _valid_compiler_or_raise(self):
def _valid_compiler_or_raise(self) -> "spack.compiler.Compiler":
if str(self.host_platform) == "linux":
compiler_name = "gcc"
elif str(self.host_platform) == "darwin":
@@ -48,30 +47,17 @@ def _valid_compiler_or_raise(self):
elif str(self.host_platform) == "windows":
compiler_name = "msvc"
elif str(self.host_platform) == "freebsd":
compiler_name = "llvm"
compiler_name = "clang"
else:
raise RuntimeError(f"Cannot bootstrap clingo from sources on {self.host_platform}")
candidates = [
x
for x in spack.compilers.config.CompilerFactory.from_packages_yaml(spack.config.CONFIG)
if x.name == compiler_name
]
candidates = spack.compilers.compilers_for_spec(
compiler_name, arch_spec=self.host_architecture
)
if not candidates:
raise RuntimeError(
f"Cannot find any version of {compiler_name} to bootstrap clingo from sources"
)
candidates.sort(key=lambda x: x.version, reverse=True)
best = candidates[0]
# Get compilers for bootstrapping from the 'builtin' repository
best.namespace = "builtin"
# If the compiler does not support C++ 14, fail with a legible error message
try:
_ = best.package.standard_flag(language="cxx", standard="14")
except RuntimeError as e:
raise RuntimeError(
"cannot find a compiler supporting C++ 14 [needed to bootstrap clingo]"
) from e
candidates.sort(key=lambda x: x.spec.version, reverse=True)
return candidates[0]
def _externals_from_yaml(
@@ -90,6 +76,9 @@ def _externals_from_yaml(
if not s.satisfies(requirements[pkg_name]):
continue
if not s.intersects(f"%{self.host_compiler.spec}"):
continue
if not s.intersects(f"arch={self.host_architecture}"):
continue
@@ -122,10 +111,11 @@ def concretize(self) -> "spack.spec.Spec":
# Tweak it to conform to the host architecture
for node in s.traverse():
node.architecture.os = str(self.host_os)
node.compiler = self.host_compiler.spec
node.architecture = self.host_architecture
if node.name == "gcc-runtime":
node.versions = self.host_compiler.versions
node.versions = self.host_compiler.spec.versions
for edge in spack.traverse.traverse_edges([s], cover="edges"):
if edge.spec.name == "python":
@@ -137,9 +127,6 @@ def concretize(self) -> "spack.spec.Spec":
if edge.spec.name == "cmake" and self.external_cmake:
edge.spec = self.external_cmake
if edge.spec.name == self.host_compiler.name:
edge.spec = self.host_compiler
if "libc" in edge.virtuals:
edge.spec = self.host_libc
@@ -155,12 +142,12 @@ def python_external_spec(self) -> "spack.spec.Spec":
return self._external_spec(result)
def libc_external_spec(self) -> "spack.spec.Spec":
detector = spack.compilers.libraries.CompilerPropertyDetector(self.host_compiler)
result = detector.default_libc()
result = self.host_compiler.default_libc
return self._external_spec(result)
def _external_spec(self, initial_spec) -> "spack.spec.Spec":
initial_spec.namespace = "builtin"
initial_spec.compiler = self.host_compiler.spec
initial_spec.architecture = self.host_architecture
for flag_type in spack.spec.FlagMap.valid_compiler_flags():
initial_spec.compiler_flags[flag_type] = []

View File

@@ -11,7 +11,7 @@
from llnl.util import tty
import spack.compilers.config
import spack.compilers
import spack.config
import spack.environment
import spack.modules
@@ -143,8 +143,8 @@ def _bootstrap_config_scopes() -> Sequence["spack.config.ConfigScope"]:
def _add_compilers_if_missing() -> None:
arch = spack.spec.ArchSpec.frontend_arch()
if not spack.compilers.config.compilers_for_arch(arch):
spack.compilers.config.find_compilers()
if not spack.compilers.compilers_for_arch(arch):
spack.compilers.find_compilers()
@contextlib.contextmanager

View File

@@ -281,12 +281,7 @@ def try_import(self, module: str, abstract_spec_str: str) -> bool:
# Install the spec that should make the module importable
with spack.config.override(self.mirror_scope):
PackageInstaller(
[concrete_spec.package],
fail_fast=True,
package_use_cache=False,
dependencies_use_cache=False,
).install()
PackageInstaller([concrete_spec.package], fail_fast=True).install()
if _try_import_from_store(module, query_spec=concrete_spec, query_info=info):
self.last_search = info
@@ -322,10 +317,11 @@ def create_bootstrapper(conf: ConfigDictionary):
return _bootstrap_methods[btype](conf)
def source_is_enabled(conf: ConfigDictionary):
def source_is_enabled_or_raise(conf: ConfigDictionary):
"""Raise ValueError if the source is not enabled for bootstrapping"""
trusted, name = spack.config.get("bootstrap:trusted"), conf["name"]
return trusted.get(name, False)
if not trusted.get(name, False):
raise ValueError("source is not trusted")
def ensure_module_importable_or_raise(module: str, abstract_spec: Optional[str] = None):
@@ -355,10 +351,8 @@ def ensure_module_importable_or_raise(module: str, abstract_spec: Optional[str]
exception_handler = GroupedExceptionHandler()
for current_config in bootstrapping_sources():
if not source_is_enabled(current_config):
continue
with exception_handler.forward(current_config["name"], Exception):
source_is_enabled_or_raise(current_config)
current_bootstrapper = create_bootstrapper(current_config)
if current_bootstrapper.try_import(module, abstract_spec):
return
@@ -370,7 +364,11 @@ def ensure_module_importable_or_raise(module: str, abstract_spec: Optional[str]
msg = f'cannot bootstrap the "{module}" Python module '
if abstract_spec:
msg += f'from spec "{abstract_spec}" '
msg += exception_handler.grouped_message(with_tracebacks=tty.is_debug())
if tty.is_debug():
msg += exception_handler.grouped_message(with_tracebacks=True)
else:
msg += exception_handler.grouped_message(with_tracebacks=False)
msg += "\nRun `spack --debug ...` for more detailed errors"
raise ImportError(msg)
@@ -408,9 +406,8 @@ def ensure_executables_in_path_or_raise(
exception_handler = GroupedExceptionHandler()
for current_config in bootstrapping_sources():
if not source_is_enabled(current_config):
continue
with exception_handler.forward(current_config["name"], Exception):
source_is_enabled_or_raise(current_config)
current_bootstrapper = create_bootstrapper(current_config)
if current_bootstrapper.try_search_path(executables, abstract_spec):
# Additional environment variables needed

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,7 @@
import multiprocessing
import os
import re
import stat
import sys
import traceback
import types
@@ -59,7 +60,7 @@
import spack.build_systems.meson
import spack.build_systems.python
import spack.builder
import spack.compilers.libraries
import spack.compilers
import spack.config
import spack.deptypes as dt
import spack.error
@@ -73,6 +74,7 @@
import spack.store
import spack.subprocess_context
import spack.util.executable
import spack.util.libc
from spack import traverse
from spack.context import Context
from spack.error import InstallError, NoHeadersError, NoLibrariesError
@@ -295,10 +297,62 @@ def _add_werror_handling(keep_werror, env):
env.set("SPACK_COMPILER_FLAGS_REPLACE", " ".join(["|".join(item) for item in replace_flags]))
def set_wrapper_environment_variables_for_flags(pkg, env):
def set_compiler_environment_variables(pkg, env):
assert pkg.spec.concrete
compiler = pkg.compiler
spec = pkg.spec
# Make sure the executables for this compiler exist
compiler.verify_executables()
# Set compiler variables used by CMake and autotools
assert all(key in compiler.link_paths for key in ("cc", "cxx", "f77", "fc"))
# Populate an object with the list of environment modifications
# and return it
# TODO : add additional kwargs for better diagnostics, like requestor,
# ttyout, ttyerr, etc.
link_dir = spack.paths.build_env_path
# Set SPACK compiler variables so that our wrapper knows what to
# call. If there is no compiler configured then use a default
# wrapper which will emit an error if it is used.
if compiler.cc:
env.set("SPACK_CC", compiler.cc)
env.set("CC", os.path.join(link_dir, compiler.link_paths["cc"]))
else:
env.set("CC", os.path.join(link_dir, "cc"))
if compiler.cxx:
env.set("SPACK_CXX", compiler.cxx)
env.set("CXX", os.path.join(link_dir, compiler.link_paths["cxx"]))
else:
env.set("CC", os.path.join(link_dir, "c++"))
if compiler.f77:
env.set("SPACK_F77", compiler.f77)
env.set("F77", os.path.join(link_dir, compiler.link_paths["f77"]))
else:
env.set("F77", os.path.join(link_dir, "f77"))
if compiler.fc:
env.set("SPACK_FC", compiler.fc)
env.set("FC", os.path.join(link_dir, compiler.link_paths["fc"]))
else:
env.set("FC", os.path.join(link_dir, "fc"))
# Set SPACK compiler rpath flags so that our wrapper knows what to use
env.set("SPACK_CC_RPATH_ARG", compiler.cc_rpath_arg)
env.set("SPACK_CXX_RPATH_ARG", compiler.cxx_rpath_arg)
env.set("SPACK_F77_RPATH_ARG", compiler.f77_rpath_arg)
env.set("SPACK_FC_RPATH_ARG", compiler.fc_rpath_arg)
env.set("SPACK_LINKER_ARG", compiler.linker_arg)
# Check whether we want to force RPATH or RUNPATH
if spack.config.get("config:shared_linking:type") == "rpath":
env.set("SPACK_DTAGS_TO_STRIP", compiler.enable_new_dtags)
env.set("SPACK_DTAGS_TO_ADD", compiler.disable_new_dtags)
else:
env.set("SPACK_DTAGS_TO_STRIP", compiler.disable_new_dtags)
env.set("SPACK_DTAGS_TO_ADD", compiler.enable_new_dtags)
if pkg.keep_werror is not None:
keep_werror = pkg.keep_werror
else:
@@ -306,6 +360,10 @@ def set_wrapper_environment_variables_for_flags(pkg, env):
_add_werror_handling(keep_werror, env)
# Set the target parameters that the compiler will add
isa_arg = optimization_flags(compiler, spec.target)
env.set("SPACK_TARGET_ARGS", isa_arg)
# Trap spack-tracked compiler flags as appropriate.
# env_flags are easy to accidentally override.
inject_flags = {}
@@ -339,27 +397,74 @@ def set_wrapper_environment_variables_for_flags(pkg, env):
env.set(flag.upper(), " ".join(f for f in env_flags[flag]))
pkg.flags_to_build_system_args(build_system_flags)
env.set("SPACK_COMPILER_SPEC", str(spec.compiler))
env.set("SPACK_SYSTEM_DIRS", SYSTEM_DIR_CASE_ENTRY)
# FIXME (compiler as nodes): recover this one in the correct packages
# compiler.setup_custom_environment(pkg, env)
compiler.setup_custom_environment(pkg, env)
return env
def optimization_flags(compiler, target):
if spack.compilers.is_mixed_toolchain(compiler):
msg = (
"microarchitecture specific optimizations are not "
"supported yet on mixed compiler toolchains [check"
f" {compiler.name}@{compiler.version} for further details]"
)
tty.debug(msg)
return ""
# Try to check if the current compiler comes with a version number or
# has an unexpected suffix. If so, treat it as a compiler with a
# custom spec.
version_number, _ = archspec.cpu.version_components(compiler.version.dotted_numeric_string)
compiler_version = compiler.version
version_number, suffix = archspec.cpu.version_components(compiler.version)
if not version_number or suffix:
try:
compiler_version = compiler.real_version
except spack.util.executable.ProcessError as e:
# log this and just return compiler.version instead
tty.debug(str(e))
try:
result = target.optimization_flags(compiler.name, version_number)
result = target.optimization_flags(compiler.name, compiler_version.dotted_numeric_string)
except (ValueError, archspec.cpu.UnsupportedMicroarchitecture):
result = ""
return result
class FilterDefaultDynamicLinkerSearchPaths:
"""Remove rpaths to directories that are default search paths of the dynamic linker."""
def __init__(self, dynamic_linker: Optional[str]) -> None:
# Identify directories by (inode, device) tuple, which handles symlinks too.
self.default_path_identifiers: Set[Tuple[int, int]] = set()
if not dynamic_linker:
return
for path in spack.util.libc.default_search_paths_from_dynamic_linker(dynamic_linker):
try:
s = os.stat(path)
if stat.S_ISDIR(s.st_mode):
self.default_path_identifiers.add((s.st_ino, s.st_dev))
except OSError:
continue
def is_dynamic_loader_default_path(self, p: str) -> bool:
try:
s = os.stat(p)
return (s.st_ino, s.st_dev) in self.default_path_identifiers
except OSError:
return False
def __call__(self, dirs: List[str]) -> List[str]:
if not self.default_path_identifiers:
return dirs
return [p for p in dirs if not self.is_dynamic_loader_default_path(p)]
def set_wrapper_variables(pkg, env):
"""Set environment variables used by the Spack compiler wrapper (which have the prefix
`SPACK_`) and also add the compiler wrappers to PATH.
@@ -368,8 +473,39 @@ def set_wrapper_variables(pkg, env):
this function computes these options in a manner that is intended to match the DAG traversal
order in `SetupContext`. TODO: this is not the case yet, we're using post order, SetupContext
is using topo order."""
# Set compiler flags injected from the spec
set_wrapper_environment_variables_for_flags(pkg, env)
# Set environment variables if specified for
# the given compiler
compiler = pkg.compiler
env.extend(spack.schema.environment.parse(compiler.environment))
if compiler.extra_rpaths:
extra_rpaths = ":".join(compiler.extra_rpaths)
env.set("SPACK_COMPILER_EXTRA_RPATHS", extra_rpaths)
# Add spack build environment path with compiler wrappers first in
# the path. We add the compiler wrapper path, which includes default
# wrappers (cc, c++, f77, f90), AND a subdirectory containing
# compiler-specific symlinks. The latter ensures that builds that
# are sensitive to the *name* of the compiler see the right name when
# we're building with the wrappers.
#
# Conflicts on case-insensitive systems (like "CC" and "cc") are
# handled by putting one in the <build_env_path>/case-insensitive
# directory. Add that to the path too.
env_paths = []
compiler_specific = os.path.join(
spack.paths.build_env_path, os.path.dirname(pkg.compiler.link_paths["cc"])
)
for item in [spack.paths.build_env_path, compiler_specific]:
env_paths.append(item)
ci = os.path.join(item, "case-insensitive")
if os.path.isdir(ci):
env_paths.append(ci)
tty.debug("Adding compiler bin/ paths: " + " ".join(env_paths))
for item in env_paths:
env.prepend_path("PATH", item)
env.set_path(SPACK_ENV_PATH, env_paths)
# Working directory for the spack command itself, for debug logs.
if spack.config.get("config:debug"):
@@ -435,15 +571,22 @@ def set_wrapper_variables(pkg, env):
lib_path = os.path.join(pkg.prefix, libdir)
rpath_dirs.insert(0, lib_path)
filter_default_dynamic_linker_search_paths = FilterDefaultDynamicLinkerSearchPaths(
pkg.compiler.default_dynamic_linker
)
# TODO: filter_system_paths is again wrong (and probably unnecessary due to the is_system_path
# branch above). link_dirs should be filtered with entries from _parse_link_paths.
link_dirs = list(dedupe(filter_system_paths(link_dirs)))
include_dirs = list(dedupe(filter_system_paths(include_dirs)))
rpath_dirs = list(dedupe(filter_system_paths(rpath_dirs)))
rpath_dirs = filter_default_dynamic_linker_search_paths(rpath_dirs)
default_dynamic_linker_filter = spack.compilers.libraries.dynamic_linker_filter_for(pkg.spec)
if default_dynamic_linker_filter:
rpath_dirs = default_dynamic_linker_filter(rpath_dirs)
# TODO: implicit_rpaths is prefiltered by is_system_path, that should be removed in favor of
# just this filter.
implicit_rpaths = filter_default_dynamic_linker_search_paths(pkg.compiler.implicit_rpaths())
if implicit_rpaths:
env.set("SPACK_COMPILER_IMPLICIT_RPATHS", ":".join(implicit_rpaths))
# Spack managed directories include the stage, store and upstream stores. We extend this with
# their real paths to make it more robust (e.g. /tmp vs /private/tmp on macOS).
@@ -499,19 +642,22 @@ def set_package_py_globals(pkg, context: Context = Context.BUILD):
# Put spack compiler paths in module scope. (Some packages use it
# in setup_run_environment etc, so don't put it context == build)
link_dir = spack.paths.build_env_path
pkg_compiler = None
try:
pkg_compiler = pkg.compiler
except spack.compilers.NoCompilerForSpecError as e:
tty.debug(f"cannot set 'spack_cc': {str(e)}")
# FIXME (compiler as nodes): make this more general, and not tied to three languages
# Maybe add a callback?
global_names = {
"c": ("spack_cc",),
"cxx": ("spack_cxx",),
"fortran": ("spack_fc", "spack_f77"),
}
for language in ("c", "cxx", "fortran"):
spec = pkg.spec.dependencies(virtuals=[language])
value = None if not spec else os.path.join(link_dir, spec[0].package.link_paths[language])
for name in global_names[language]:
setattr(module, name, value)
if pkg_compiler is not None:
module.spack_cc = os.path.join(link_dir, pkg_compiler.link_paths["cc"])
module.spack_cxx = os.path.join(link_dir, pkg_compiler.link_paths["cxx"])
module.spack_f77 = os.path.join(link_dir, pkg_compiler.link_paths["f77"])
module.spack_fc = os.path.join(link_dir, pkg_compiler.link_paths["fc"])
else:
module.spack_cc = None
module.spack_cxx = None
module.spack_f77 = None
module.spack_fc = None
# Useful directories within the prefix are encapsulated in
# a Prefix object.
@@ -678,6 +824,7 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD):
context == Context.TEST and pkg.test_requires_compiler
)
if need_compiler:
set_compiler_environment_variables(pkg, env_mods)
set_wrapper_variables(pkg, env_mods)
# Platform specific setup goes before package specific setup. This is for setting
@@ -702,6 +849,11 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD):
# Load modules on an already clean environment, just before applying Spack's
# own environment modifications. This ensures Spack controls CC/CXX/... variables.
if need_compiler:
tty.debug("setup_package: loading compiler modules")
for mod in pkg.compiler.modules:
load_module(mod)
load_external_modules(pkg)
# Make sure nothing's strange about the Spack environment.
@@ -730,6 +882,9 @@ def __init__(self, *roots: spack.spec.Spec, context: Context):
elif context == Context.RUN:
self.root_depflag = dt.RUN | dt.LINK
def accept(self, item):
return True
def neighbors(self, item):
spec = item.edge.spec
if spec.dag_hash() in self.root_hashes:
@@ -767,19 +922,19 @@ def effective_deptypes(
a flag specifying in what way they do so. The list is ordered topologically
from root to leaf, meaning that environment modifications should be applied
in reverse so that dependents override dependencies, not the other way around."""
visitor = traverse.TopoVisitor(
EnvironmentVisitor(*specs, context=context),
key=lambda x: x.dag_hash(),
topo_sorted_edges = traverse.traverse_topo_edges_generator(
traverse.with_artificial_edges(specs),
visitor=EnvironmentVisitor(*specs, context=context),
key=traverse.by_dag_hash,
root=True,
all_edges=True,
)
traverse.traverse_depth_first_with_visitor(traverse.with_artificial_edges(specs), visitor)
# Dictionary with "no mode" as default value, so it's easy to write modes[x] |= flag.
use_modes = defaultdict(lambda: UseMode(0))
nodes_with_type = []
for edge in visitor.edges:
for edge in topo_sorted_edges:
parent, child, depflag = edge.parent, edge.spec, edge.depflag
# Mark the starting point

View File

@@ -13,7 +13,6 @@
import spack.build_environment
import spack.builder
import spack.compilers.libraries
import spack.error
import spack.package_base
import spack.phase_callbacks
@@ -397,44 +396,33 @@ def _do_patch_libtool(self) -> None:
markers[tag] = "LIBTOOL TAG CONFIG: {0}".format(tag.upper())
# Replace empty linker flag prefixes:
if self.spec.satisfies("%nag"):
if self.pkg.compiler.name == "nag":
# Nag is mixed with gcc and g++, which are recognized correctly.
# Therefore, we change only Fortran values:
nag_pkg = self.spec["fortran"].package
for tag in ["fc", "f77"]:
marker = markers[tag]
x.filter(
regex='^wl=""$',
repl=f'wl="{nag_pkg.linker_arg}"',
start_at=f"# ### BEGIN {marker}",
stop_at=f"# ### END {marker}",
repl='wl="{0}"'.format(self.pkg.compiler.linker_arg),
start_at="# ### BEGIN {0}".format(marker),
stop_at="# ### END {0}".format(marker),
)
else:
compiler_spec = spack.compilers.libraries.compiler_spec(self.spec)
if compiler_spec:
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(compiler_spec.package.linker_arg))
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.pkg.compiler.linker_arg))
# Replace empty PIC flag values:
for compiler, marker in markers.items():
if compiler == "cc":
language = "c"
elif compiler == "cxx":
language = "cxx"
else:
language = "fortran"
if language not in self.spec:
continue
for cc, marker in markers.items():
x.filter(
regex='^pic_flag=""$',
repl=f'pic_flag="{self.spec[language].package.pic_flag}"',
start_at=f"# ### BEGIN {marker}",
stop_at=f"# ### END {marker}",
repl='pic_flag="{0}"'.format(
getattr(self.pkg.compiler, "{0}_pic_flag".format(cc))
),
start_at="# ### BEGIN {0}".format(marker),
stop_at="# ### END {0}".format(marker),
)
# Other compiler-specific patches:
if self.spec.satisfies("%fj"):
if self.pkg.compiler.name == "fj":
x.filter(regex="-nostdlib", repl="", string=True)
rehead = r"/\S*/"
for o in [
@@ -447,7 +435,7 @@ def _do_patch_libtool(self) -> None:
r"crtendS\.o",
]:
x.filter(regex=(rehead + o), repl="")
elif self.spec.satisfies("%nag"):
elif self.pkg.compiler.name == "nag":
for tag in ["fc", "f77"]:
marker = markers[tag]
start_at = "# ### BEGIN {0}".format(marker)

View File

@@ -11,7 +11,6 @@
import llnl.util.tty as tty
import spack.phase_callbacks
from spack.directives import depends_on
from .cmake import CMakeBuilder, CMakePackage
@@ -69,7 +68,12 @@ class CachedCMakeBuilder(CMakeBuilder):
@property
def cache_name(self):
return f"{self.pkg.name}-{self.spec.architecture.platform}-{self.spec.dag_hash()}.cmake"
return "{0}-{1}-{2}@{3}.cmake".format(
self.pkg.name,
self.pkg.spec.architecture,
self.pkg.spec.compiler.name,
self.pkg.spec.compiler.version,
)
@property
def cache_path(self):
@@ -112,9 +116,7 @@ def initconfig_compiler_entries(self):
# Fortran compiler is optional
if "FC" in os.environ:
spack_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", os.environ["FC"])
system_fc_entry = cmake_cache_path(
"CMAKE_Fortran_COMPILER", self.spec["fortran"].package.fortran
)
system_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", self.pkg.compiler.fc)
else:
spack_fc_entry = "# No Fortran compiler defined in spec"
system_fc_entry = "# No Fortran compiler defined in spec"
@@ -130,8 +132,8 @@ def initconfig_compiler_entries(self):
" " + cmake_cache_path("CMAKE_CXX_COMPILER", os.environ["CXX"]),
" " + spack_fc_entry,
"else()\n",
" " + cmake_cache_path("CMAKE_C_COMPILER", self.spec["c"].package.cc),
" " + cmake_cache_path("CMAKE_CXX_COMPILER", self.spec["cxx"].package.cxx),
" " + cmake_cache_path("CMAKE_C_COMPILER", self.pkg.compiler.cc),
" " + cmake_cache_path("CMAKE_CXX_COMPILER", self.pkg.compiler.cxx),
" " + system_fc_entry,
"endif()\n",
]
@@ -350,10 +352,6 @@ class CachedCMakePackage(CMakePackage):
CMakeBuilder = CachedCMakeBuilder
# These dependencies are assumed in the builder
depends_on("c", type="build")
depends_on("cxx", type="build")
def flag_handler(self, name, flags):
if name in ("cflags", "cxxflags", "cppflags", "fflags"):
return None, None, None # handled in the cmake cache

View File

@@ -9,7 +9,7 @@
import re
import sys
from itertools import chain
from typing import Any, List, Optional, Set, Tuple
from typing import Any, List, Optional, Tuple
import llnl.util.filesystem as fs
from llnl.util.lang import stable_partition
@@ -21,6 +21,7 @@
import spack.phase_callbacks
import spack.spec
import spack.util.prefix
from spack import traverse
from spack.directives import build_system, conflicts, depends_on, variant
from spack.multimethod import when
from spack.util.environment import filter_system_paths
@@ -166,15 +167,18 @@ def _values(x):
def get_cmake_prefix_path(pkg: spack.package_base.PackageBase) -> List[str]:
"""Obtain the CMAKE_PREFIX_PATH entries for a package, based on the cmake_prefix_path package
attribute of direct build/test and transitive link dependencies."""
# Add direct build/test deps
selected: Set[str] = {s.dag_hash() for s in pkg.spec.dependencies(deptype=dt.BUILD | dt.TEST)}
# Add transitive link deps
selected.update(s.dag_hash() for s in pkg.spec.traverse(root=False, deptype=dt.LINK))
# Separate out externals so they do not shadow Spack prefixes
externals, spack_built = stable_partition(
(s for s in pkg.spec.traverse(root=False, order="topo") if s.dag_hash() in selected),
lambda x: x.external,
edges = traverse.traverse_topo_edges_generator(
traverse.with_artificial_edges([pkg.spec]),
visitor=traverse.MixedDepthVisitor(
direct=dt.BUILD | dt.TEST, transitive=dt.LINK | dt.RUN, key=traverse.by_dag_hash
),
key=traverse.by_dag_hash,
root=False,
all_edges=False, # cover all nodes, not all edges
)
ordered_specs = [edge.spec for edge in edges]
# Separate out externals so they do not shadow Spack prefixes
externals, spack_built = stable_partition((s for s in ordered_specs), lambda x: x.external)
return filter_system_paths(
path for spec in chain(spack_built, externals) for path in spec.package.cmake_prefix_paths

View File

@@ -5,20 +5,15 @@
import itertools
import os
import pathlib
import platform
import re
import sys
from typing import Dict, List, Optional, Sequence, Tuple, Union
import archspec.cpu
from typing import Dict, List, Sequence, Tuple, Union
import llnl.util.tty as tty
from llnl.util.lang import classproperty, memoized
from llnl.util.lang import classproperty
import spack.compilers.libraries
import spack.config
import spack.compiler
import spack.package_base
import spack.paths
import spack.util.executable
# Local "type" for type hints
@@ -49,9 +44,6 @@ class CompilerPackage(spack.package_base.PackageBase):
#: Static definition of languages supported by this class
compiler_languages: Sequence[str] = ["c", "cxx", "fortran"]
#: Relative path to compiler wrappers
link_paths: Dict[str, str] = {}
def __init__(self, spec: "spack.spec.Spec"):
super().__init__(spec)
msg = f"Supported languages for {spec} are not a subset of possible supported languages"
@@ -86,14 +78,14 @@ def executables(cls) -> Sequence[str]:
]
@classmethod
def determine_version(cls, exe: Path) -> str:
def determine_version(cls, exe: Path):
version_argument = cls.compiler_version_argument
if isinstance(version_argument, str):
version_argument = (version_argument,)
for va in version_argument:
try:
output = compiler_output(exe, version_argument=va)
output = spack.compiler.get_compiler_version_output(exe, va)
match = re.search(cls.compiler_version_regex, output)
if match:
return ".".join(match.groups())
@@ -104,7 +96,6 @@ def determine_version(cls, exe: Path) -> str:
f"[{__file__}] Cannot detect a valid version for the executable "
f"{str(exe)}, for package '{cls.name}': {e}"
)
return ""
@classmethod
def compiler_bindir(cls, prefix: Path) -> Path:
@@ -152,183 +143,3 @@ def determine_compiler_paths(cls, exes: Sequence[Path]) -> Dict[str, Path]:
def determine_variants(cls, exes: Sequence[Path], version_str: str) -> Tuple:
# path determination is separated so it can be reused in subclasses
return "", {"compilers": cls.determine_compiler_paths(exes=exes)}
#: Returns the argument needed to set the RPATH, or None if it does not exist
rpath_arg: Optional[str] = "-Wl,-rpath,"
#: Flag that needs to be used to pass an argument to the linker
linker_arg: str = "-Wl,"
#: Flag used to produce Position Independent Code
pic_flag: str = "-fPIC"
#: Flag used to get verbose output
verbose_flags: str = "-v"
#: Flag to activate OpenMP support
openmp_flag: str = "-fopenmp"
def standard_flag(self, *, language: str, standard: str) -> str:
"""Returns the flag used to enforce a given standard for a language"""
if language not in self.supported_languages:
# FIXME (compiler as nodes): Use UnsupportedCompilerFlag ?
raise RuntimeError(f"{self.spec} does not provide the '{language}' language")
try:
return self._standard_flag(language=language, standard=standard)
except (KeyError, RuntimeError) as e:
raise RuntimeError(
f"{self.spec} does not provide the '{language}' standard {standard}"
) from e
def _standard_flag(self, *, language: str, standard: str) -> str:
raise NotImplementedError("Must be implemented by derived classes")
@property
def disable_new_dtags(self) -> str:
if platform.system() == "Darwin":
return ""
return "--disable-new-dtags"
@property
def enable_new_dtags(self) -> str:
if platform.system() == "Darwin":
return ""
return "--enable-new-dtags"
def setup_dependent_build_environment(self, env, dependent_spec):
# FIXME (compiler as nodes): check if this is good enough or should be made more general
# The package is not used as a compiler, so skip this setup
if not any(
lang in dependent_spec and dependent_spec[lang].name == self.spec.name
for lang in ("c", "cxx", "fortran")
):
return
# Populate an object with the list of environment modifications and return it
link_dir = pathlib.Path(spack.paths.build_env_path)
for language, attr_name, wrapper_var_name, spack_var_name in [
("c", "cc", "CC", "SPACK_CC"),
("cxx", "cxx", "CXX", "SPACK_CXX"),
("fortran", "fortran", "F77", "SPACK_F77"),
("fortran", "fortran", "FC", "SPACK_FC"),
]:
if language not in dependent_spec or dependent_spec[language].name != self.spec.name:
continue
if not hasattr(self, attr_name):
continue
compiler = getattr(self, attr_name)
env.set(spack_var_name, compiler)
if language not in self.link_paths:
continue
wrapper_path = link_dir / self.link_paths.get(language)
env.set(wrapper_var_name, str(wrapper_path))
env.set(f"SPACK_{wrapper_var_name}_RPATH_ARG", self.rpath_arg)
uarch = dependent_spec.architecture.target
version_number, _ = archspec.cpu.version_components(
self.spec.version.dotted_numeric_string
)
try:
isa_arg = uarch.optimization_flags(self.spec.name, version_number)
except (ValueError, archspec.cpu.UnsupportedMicroarchitecture):
isa_arg = ""
if isa_arg:
env.set(f"SPACK_TARGET_ARGS_{attr_name.upper()}", isa_arg)
# FIXME (compiler as nodes): make these paths language specific
env.set("SPACK_LINKER_ARG", self.linker_arg)
paths = _implicit_rpaths(pkg=self)
if paths:
env.set("SPACK_COMPILER_IMPLICIT_RPATHS", ":".join(paths))
# Check whether we want to force RPATH or RUNPATH
if spack.config.CONFIG.get("config:shared_linking:type") == "rpath":
env.set("SPACK_DTAGS_TO_STRIP", self.enable_new_dtags)
env.set("SPACK_DTAGS_TO_ADD", self.disable_new_dtags)
else:
env.set("SPACK_DTAGS_TO_STRIP", self.disable_new_dtags)
env.set("SPACK_DTAGS_TO_ADD", self.enable_new_dtags)
spec = self.spec
env.set("SPACK_COMPILER_SPEC", spec.format("{name}{@version}{variants}{/hash:7}"))
if spec.extra_attributes:
extra_rpaths = spec.extra_attributes.get("extra_rpaths")
if extra_rpaths:
extra_rpaths = ":".join(compiler.extra_rpaths)
env.append_path("SPACK_COMPILER_EXTRA_RPATHS", extra_rpaths)
# Add spack build environment path with compiler wrappers first in
# the path. We add the compiler wrapper path, which includes default
# wrappers (cc, c++, f77, f90), AND a subdirectory containing
# compiler-specific symlinks. The latter ensures that builds that
# are sensitive to the *name* of the compiler see the right name when
# we're building with the wrappers.
#
# Conflicts on case-insensitive systems (like "CC" and "cc") are
# handled by putting one in the <build_env_path>/case-insensitive
# directory. Add that to the path too.
env_paths = []
compiler_specific = os.path.join(
spack.paths.build_env_path, os.path.dirname(self.link_paths["c"])
)
for item in [spack.paths.build_env_path, compiler_specific]:
env_paths.append(item)
ci = os.path.join(item, "case-insensitive")
if os.path.isdir(ci):
env_paths.append(ci)
tty.debug("Adding compiler bin/ paths: " + " ".join(env_paths))
for item in env_paths:
env.prepend_path("PATH", item)
env.set_path("SPACK_ENV_PATH", env_paths)
def _implicit_rpaths(pkg: spack.package_base.PackageBase) -> List[str]:
detector = spack.compilers.libraries.CompilerPropertyDetector(pkg.spec)
paths = detector.implicit_rpaths()
return paths
@memoized
def _compiler_output(
compiler_path: Path, *, version_argument: str, ignore_errors: Tuple[int, ...] = ()
) -> str:
"""Returns the output from the compiler invoked with the given version argument.
Args:
compiler_path: path of the compiler to be invoked
version_argument: the argument used to extract version information
"""
compiler = spack.util.executable.Executable(compiler_path)
compiler_invocation_args = {
"output": str,
"error": str,
"ignore_errors": ignore_errors,
"timeout": 120,
"fail_on_error": True,
}
if version_argument:
output = compiler(version_argument, **compiler_invocation_args)
else:
output = compiler(**compiler_invocation_args)
return output
def compiler_output(
compiler_path: Path, *, version_argument: str, ignore_errors: Tuple[int, ...] = ()
) -> str:
"""Wrapper for _get_compiler_version_output()."""
# This ensures that we memoize compiler output by *absolute path*,
# not just executable name. If we don't do this, and the path changes
# (e.g., during testing), we can get incorrect results.
if not os.path.isabs(compiler_path):
compiler_path = spack.util.executable.which_string(compiler_path, required=True)
return _compiler_output(
compiler_path, version_argument=version_argument, ignore_errors=ignore_errors
)

View File

@@ -72,10 +72,7 @@ def v2_layout(self):
def component_prefix(self):
"""Path to component <prefix>/<component>/<version>."""
v = self.spec.version.up_to(2) if self.v2_layout else self.spec.version
base_dir = self.prefix
if self.component_dir not in str(self.prefix):
base_dir = base_dir.join(self.component_dir).join(str(v))
return base_dir
return self.prefix.join(self.component_dir).join(str(v))
@property
def env_script_args(self):
@@ -143,7 +140,7 @@ def setup_run_environment(self, env):
$ source {prefix}/{component}/{version}/env/vars.sh
"""
# Only if environment modifications are desired (default is +envmods)
if "+envmods" in self.spec:
if "~envmods" not in self.spec:
env.extend(
EnvironmentModifications.from_sourcing_file(
self.component_prefix.env.join("vars.sh"), *self.env_script_args
@@ -258,7 +255,7 @@ def libs(self):
return find_libraries("*", root=self.component_prefix.lib, recursive=not self.v2_layout)
class IntelOneApiLibraryPackageWithSdk(IntelOneApiPackage):
class IntelOneApiLibraryPackageWithSdk(IntelOneApiLibraryPackage):
"""Base class for Intel oneAPI library packages with SDK components.
Contains some convenient default implementations for libraries

View File

@@ -277,6 +277,10 @@ def update_external_dependencies(self, extendee_spec=None):
if not python.architecture.target:
python.architecture.target = archspec.cpu.host().family.name
# Ensure compiler information is present
if not python.compiler:
python.compiler = self.spec.compiler
python.external_path = self.spec.external_path
python._mark_concrete()
self.spec.add_dependency_edge(python, depflag=dt.BUILD | dt.LINK | dt.RUN, virtuals=())

View File

@@ -2137,7 +2137,7 @@ def build_name(self):
Returns: (str) current spec's CDash build name."""
spec = self.current_spec
if spec:
build_name = f"{spec.name}@{spec.version} \
build_name = f"{spec.name}@{spec.version}%{spec.compiler} \
hash={spec.dag_hash()} arch={spec.architecture} ({self.build_group})"
tty.debug(f"Generated CDash build name ({build_name}) from the {spec.name}")
return build_name

View File

@@ -369,13 +369,8 @@ def iter_groups(specs, indent, all_headers):
index = index_by(specs, ("architecture", "compiler"))
ispace = indent * " "
def _key(item):
if item is None:
return ""
return str(item)
# Traverse the index and print out each package
for i, (architecture, compiler) in enumerate(sorted(index, key=_key)):
for i, (architecture, compiler) in enumerate(sorted(index)):
if i > 0:
print()
@@ -433,7 +428,6 @@ def display_specs(specs, args=None, **kwargs):
"""
# FIXME (compiler as nodes): remove the "show full compiler" arguments, and its use
def get_arg(name, default=None):
"""Prefer kwargs, then args, then default."""
if name in kwargs:
@@ -448,6 +442,7 @@ def get_arg(name, default=None):
hashes = get_arg("long", False)
namespaces = get_arg("namespaces", False)
flags = get_arg("show_flags", False)
full_compiler = get_arg("show_full_compiler", False)
variants = get_arg("variants", False)
groups = get_arg("groups", True)
all_headers = get_arg("all_headers", False)
@@ -469,7 +464,10 @@ def get_arg(name, default=None):
if format_string is None:
nfmt = "{fullname}" if namespaces else "{name}"
ffmt = ""
if flags:
if full_compiler or flags:
ffmt += "{%compiler.name}"
if full_compiler:
ffmt += "{@compiler.version}"
ffmt += " {compiler_flags}"
vfmt = "{variants}" if variants else ""
format_string = nfmt + "{@version}" + ffmt + vfmt

View File

@@ -5,14 +5,13 @@
import argparse
import sys
import warnings
import llnl.util.tty as tty
from llnl.util.lang import index_by
from llnl.util.tty.colify import colify
from llnl.util.tty.color import colorize
import spack.compilers.config
import spack.compilers
import spack.config
import spack.spec
from spack.cmd.common import arguments
@@ -36,13 +35,13 @@ def setup_parser(subparser):
"--mixed-toolchain",
action="store_true",
default=sys.platform == "darwin",
help="(DEPRECATED) Allow mixed toolchains (for example: clang, clang++, gfortran)",
help="Allow mixed toolchains (for example: clang, clang++, gfortran)",
)
mixed_toolchain_group.add_argument(
"--no-mixed-toolchain",
action="store_false",
dest="mixed_toolchain",
help="(DEPRECATED) Do not allow mixed toolchains (for example: clang, clang++, gfortran)",
help="Do not allow mixed toolchains (for example: clang, clang++, gfortran)",
)
find_parser.add_argument("add_paths", nargs=argparse.REMAINDER)
find_parser.add_argument(
@@ -81,97 +80,77 @@ def compiler_find(args):
"""Search either $PATH or a list of paths OR MODULES for compilers and
add them to Spack's configuration.
"""
if args.mixed_toolchain:
warnings.warn(
"The '--mixed-toolchain' option has been deprecated in Spack v0.23, and currently "
"has no effect. The option will be removed in Spack v0.25"
)
paths = args.add_paths or None
new_compilers = spack.compilers.config.find_compilers(
path_hints=paths, scope=args.scope, max_workers=args.jobs
new_compilers = spack.compilers.find_compilers(
path_hints=paths,
scope=args.scope,
mixed_toolchain=args.mixed_toolchain,
max_workers=args.jobs,
)
if new_compilers:
n = len(new_compilers)
s = "s" if n > 1 else ""
filename = spack.config.CONFIG.get_config_filename(args.scope, "packages")
filename = spack.config.CONFIG.get_config_filename(args.scope, "compilers")
tty.msg(f"Added {n:d} new compiler{s} to {filename}")
compiler_strs = sorted(f"{spec.name}@{spec.versions}" for spec in new_compilers)
compiler_strs = sorted(f"{c.spec.name}@{c.spec.version}" for c in new_compilers)
colify(reversed(compiler_strs), indent=4)
else:
tty.msg("Found no new compilers")
tty.msg("Compilers are defined in the following files:")
colify(spack.compilers.config.compiler_config_files(), indent=4)
colify(spack.compilers.compiler_config_files(), indent=4)
def compiler_remove(args):
remover = spack.compilers.config.CompilerRemover(spack.config.CONFIG)
candidates = remover.mark_compilers(match=args.compiler_spec, scope=args.scope)
if not candidates:
tty.die(f"No compiler matches '{args.compiler_spec}'")
compiler_spec = spack.spec.CompilerSpec(args.compiler_spec)
candidate_compilers = spack.compilers.compilers_for_spec(compiler_spec, scope=args.scope)
compiler_strs = reversed(sorted(f"{spec.name}@{spec.versions}" for spec in candidates))
if not candidate_compilers:
tty.die("No compilers match spec %s" % compiler_spec)
if not args.all and len(candidates) > 1:
tty.error(f"multiple compilers match the spec '{args.compiler_spec}':")
print()
colify(compiler_strs, indent=4)
print()
print(
"Either use a stricter spec to select only one, or use `spack compiler remove -a`"
" to remove all of them."
)
if not args.all and len(candidate_compilers) > 1:
tty.error(f"Multiple compilers match spec {compiler_spec}. Choose one:")
colify(reversed(sorted([c.spec.display_str for c in candidate_compilers])), indent=4)
tty.msg("Or, use `spack compiler remove -a` to remove all of them.")
sys.exit(1)
remover.flush()
tty.msg("The following compilers have been removed:")
print()
colify(compiler_strs, indent=4)
print()
for current_compiler in candidate_compilers:
spack.compilers.remove_compiler_from_config(current_compiler.spec, scope=args.scope)
tty.msg(f"{current_compiler.spec.display_str} has been removed")
def compiler_info(args):
"""Print info about all compilers matching a spec."""
query = spack.spec.Spec(args.compiler_spec)
all_compilers = spack.compilers.config.all_compilers(scope=args.scope, init_config=False)
compilers = [x for x in all_compilers if x.satisfies(query)]
cspec = spack.spec.CompilerSpec(args.compiler_spec)
compilers = spack.compilers.compilers_for_spec(cspec, scope=args.scope)
if not compilers:
tty.die(f"No compilers match spec {query.cformat()}")
tty.die("No compilers match spec %s" % cspec)
else:
for c in compilers:
print(f"{c.cformat()}:")
print(f" prefix: {c.external_path}")
extra_attributes = getattr(c, "extra_attributes", {})
if "compilers" in extra_attributes:
print(" compilers:")
for language, exe in extra_attributes.get("compilers", {}).items():
print(f" {language}: {exe}")
if "flags" in extra_attributes:
print(" flags:")
for flag, flag_value in extra_attributes["flags"].items():
print(f" {flag} = {flag_value}")
# FIXME (compiler as nodes): recover this printing
# if "environment" in extra_attributes:
# if len(c.environment.get("set", {})) != 0:
# print("\tenvironment:")
# print("\t set:")
# for key, value in c.environment["set"].items():
# print("\t %s = %s" % (key, value))
if "extra_rpaths" in extra_attributes:
print(" extra rpaths:")
for extra_rpath in extra_attributes["extra_rpaths"]:
print(f" {extra_rpath}")
if getattr(c, "external_modules", []):
print(" modules: ")
for module in c.external_modules:
print(f" {module}")
print()
print(c.spec.display_str + ":")
print("\tpaths:")
for cpath in ["cc", "cxx", "f77", "fc"]:
print("\t\t%s = %s" % (cpath, getattr(c, cpath, None)))
if c.flags:
print("\tflags:")
for flag, flag_value in c.flags.items():
print("\t\t%s = %s" % (flag, flag_value))
if len(c.environment) != 0:
if len(c.environment.get("set", {})) != 0:
print("\tenvironment:")
print("\t set:")
for key, value in c.environment["set"].items():
print("\t %s = %s" % (key, value))
if c.extra_rpaths:
print("\tExtra rpaths:")
for extra_rpath in c.extra_rpaths:
print("\t\t%s" % extra_rpath)
print("\tmodules = %s" % c.modules)
print("\toperating system = %s" % c.operating_system)
def compiler_list(args):
compilers = spack.compilers.config.all_compilers(scope=args.scope, init_config=False)
compilers = spack.compilers.all_compilers(scope=args.scope, init_config=False)
# If there are no compilers in any scope, and we're outputting to a tty, give a
# hint to the user.
@@ -184,7 +163,7 @@ def compiler_list(args):
tty.msg(msg)
return
index = index_by(compilers, spack.compilers.config.name_os_target)
index = index_by(compilers, lambda c: (c.spec.name, c.operating_system, c.target))
tty.msg("Available compilers")
@@ -203,10 +182,10 @@ def compiler_list(args):
name, os, target = key
os_str = os
if target:
os_str += f"-{target}"
cname = f"{spack.spec.COMPILER_COLOR}{{{name}}} {os_str}"
os_str += "-%s" % target
cname = "%s{%s} %s" % (spack.spec.COMPILER_COLOR, name, os_str)
tty.hline(colorize(cname), char="-")
colify(reversed(sorted(c.format("{name}@{version}") for c in compilers)))
colify(reversed(sorted(c.spec.display_str for c in compilers)))
def compiler(parser, args):

View File

@@ -518,6 +518,8 @@ def config_prefer_upstream(args):
for spec in pref_specs:
# Collect all the upstream compilers and versions for this package.
pkg = pkgs.get(spec.name, {"version": []})
all = pkgs.get("all", {"compiler": []})
pkgs["all"] = all
pkgs[spec.name] = pkg
# We have no existing variant if this is our first added version.
@@ -527,6 +529,10 @@ def config_prefer_upstream(args):
if version not in pkg["version"]:
pkg["version"].append(version)
compiler = str(spec.compiler)
if compiler not in all["compiler"]:
all["compiler"].append(compiler)
# Get and list all the variants that differ from the default.
variants = []
for var_name, variant in spec.variants.items():

View File

@@ -99,7 +99,7 @@ def setup_parser(subparser):
"--show-full-compiler",
action="store_true",
dest="show_full_compiler",
help="(DEPRECATED) show full compiler specs. Currently it's a no-op",
help="show full compiler specs",
)
implicit_explicit = subparser.add_mutually_exclusive_group()
implicit_explicit.add_argument(
@@ -279,6 +279,7 @@ def root_decorator(spec, string):
# these enforce details in the root specs to show what the user asked for
namespaces=True,
show_flags=True,
show_full_compiler=True,
decorator=root_decorator,
variants=True,
)
@@ -301,6 +302,7 @@ def root_decorator(spec, string):
decorator=lambda s, f: color.colorize("@*{%s}" % f),
namespace=True,
show_flags=True,
show_full_compiler=True,
variants=True,
)
print()

View File

@@ -8,6 +8,7 @@
import spack.cmd.common.arguments
import spack.cmd.modules
import spack.config
import spack.modules
import spack.modules.lmod

View File

@@ -7,6 +7,7 @@
import spack.cmd.common.arguments
import spack.cmd.modules
import spack.config
import spack.modules
import spack.modules.tcl

View File

@@ -15,6 +15,7 @@
from llnl.util.filesystem import working_dir
import spack.paths
import spack.repo
import spack.util.git
from spack.util.executable import Executable, which
@@ -38,7 +39,7 @@ def grouper(iterable, n, fillvalue=None):
#: double-check the results of other tools (if, e.g., --fix was provided)
#: The list maps an executable name to a method to ensure the tool is
#: bootstrapped or present in the environment.
tool_names = ["import-check", "isort", "black", "flake8", "mypy"]
tool_names = ["import", "isort", "black", "flake8", "mypy"]
#: warnings to ignore in mypy
mypy_ignores = [
@@ -370,10 +371,19 @@ def run_black(black_cmd, file_list, args):
def _module_part(root: str, expr: str):
parts = expr.split(".")
# spack.pkg is for repositories, don't try to resolve it here.
if ".".join(parts[:2]) == spack.repo.ROOT_PYTHON_NAMESPACE:
return None
while parts:
f1 = os.path.join(root, "lib", "spack", *parts) + ".py"
f2 = os.path.join(root, "lib", "spack", *parts, "__init__.py")
if os.path.exists(f1) or os.path.exists(f2):
if (
os.path.exists(f1)
# ensure case sensitive match
and f"{parts[-1]}.py" in os.listdir(os.path.dirname(f1))
or os.path.exists(f2)
):
return ".".join(parts)
parts.pop()
return None
@@ -389,7 +399,7 @@ def _run_import_check(
out=sys.stdout,
):
if sys.version_info < (3, 9):
print("import-check requires Python 3.9 or later")
print("import check requires Python 3.9 or later")
return 0
is_use = re.compile(r"(?<!from )(?<!import )(?:llnl|spack)\.[a-zA-Z0-9_\.]+")
@@ -431,10 +441,11 @@ def _run_import_check(
module = _module_part(root, m.group(0))
if not module or module in to_add:
continue
if f"import {module}" not in filtered_contents:
to_add.add(module)
exit_code = 1
print(f"{pretty_path}: missing import: {module}", file=out)
if re.search(rf"import {re.escape(module)}\b(?!\.)", contents):
continue
to_add.add(module)
exit_code = 1
print(f"{pretty_path}: missing import: {module} ({m.group(0)})", file=out)
if not fix or not to_add and not to_remove:
continue
@@ -465,7 +476,7 @@ def _run_import_check(
return exit_code
@tool("import-check", external=False)
@tool("import", external=False)
def run_import_check(import_check_cmd, file_list, args):
exit_code = _run_import_check(
file_list,
@@ -474,7 +485,7 @@ def run_import_check(import_check_cmd, file_list, args):
root=args.root,
working_dir=args.initial_working_dir,
)
print_tool_result("import-check", exit_code)
print_tool_result("import", exit_code)
return exit_code

View File

@@ -217,7 +217,7 @@ def unit_test(parser, args, unknown_args):
# Ensure clingo is available before switching to the
# mock configuration used by unit tests
with spack.bootstrap.ensure_bootstrap_configuration():
spack.bootstrap.ensure_clingo_importable_or_raise()
spack.bootstrap.ensure_core_dependencies()
if pytest is None:
spack.bootstrap.ensure_environment_dependencies()
import pytest

850
lib/spack/spack/compiler.py Normal file
View File

@@ -0,0 +1,850 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import contextlib
import hashlib
import itertools
import json
import os
import platform
import re
import shutil
import sys
import tempfile
from typing import Dict, List, Optional, Sequence
import llnl.path
import llnl.util.lang
import llnl.util.tty as tty
from llnl.util.filesystem import path_contains_subdirectory, paths_containing_libs
import spack.caches
import spack.error
import spack.schema.environment
import spack.spec
import spack.util.executable
import spack.util.libc
import spack.util.module_cmd
import spack.version
from spack.util.environment import filter_system_paths
from spack.util.file_cache import FileCache
__all__ = ["Compiler"]
PATH_INSTANCE_VARS = ["cc", "cxx", "f77", "fc"]
FLAG_INSTANCE_VARS = ["cflags", "cppflags", "cxxflags", "fflags"]
@llnl.util.lang.memoized
def _get_compiler_version_output(compiler_path, version_arg, ignore_errors=()) -> str:
"""Invokes the compiler at a given path passing a single
version argument and returns the output.
Args:
compiler_path (path): path of the compiler to be invoked
version_arg (str): the argument used to extract version information
"""
compiler = spack.util.executable.Executable(compiler_path)
compiler_invocation_args = {
"output": str,
"error": str,
"ignore_errors": ignore_errors,
"timeout": 120,
"fail_on_error": True,
}
if version_arg:
output = compiler(version_arg, **compiler_invocation_args)
else:
output = compiler(**compiler_invocation_args)
return output
def get_compiler_version_output(compiler_path, *args, **kwargs) -> str:
"""Wrapper for _get_compiler_version_output()."""
# This ensures that we memoize compiler output by *absolute path*,
# not just executable name. If we don't do this, and the path changes
# (e.g., during testing), we can get incorrect results.
if not os.path.isabs(compiler_path):
compiler_path = spack.util.executable.which_string(compiler_path, required=True)
return _get_compiler_version_output(compiler_path, *args, **kwargs)
def tokenize_flags(flags_values, propagate=False):
"""Given a compiler flag specification as a string, this returns a list
where the entries are the flags. For compiler options which set values
using the syntax "-flag value", this function groups flags and their
values together. Any token not preceded by a "-" is considered the
value of a prior flag."""
tokens = flags_values.split()
if not tokens:
return []
flag = tokens[0]
flags_with_propagation = []
for token in tokens[1:]:
if not token.startswith("-"):
flag += " " + token
else:
flags_with_propagation.append((flag, propagate))
flag = token
flags_with_propagation.append((flag, propagate))
return flags_with_propagation
#: regex for parsing linker lines
_LINKER_LINE = re.compile(r"^( *|.*[/\\])" r"(link|ld|([^/\\]+-)?ld|collect2)" r"[^/\\]*( |$)")
#: components of linker lines to ignore
_LINKER_LINE_IGNORE = re.compile(r"(collect2 version|^[A-Za-z0-9_]+=|/ldfe )")
#: regex to match linker search paths
_LINK_DIR_ARG = re.compile(r"^-L(.:)?(?P<dir>[/\\].*)")
#: regex to match linker library path arguments
_LIBPATH_ARG = re.compile(r"^[-/](LIBPATH|libpath):(?P<dir>.*)")
def _parse_link_paths(string):
"""Parse implicit link paths from compiler debug output.
This gives the compiler runtime library paths that we need to add to
the RPATH of generated binaries and libraries. It allows us to
ensure, e.g., that codes load the right libstdc++ for their compiler.
"""
lib_search_paths = False
raw_link_dirs = []
for line in string.splitlines():
if lib_search_paths:
if line.startswith("\t"):
raw_link_dirs.append(line[1:])
continue
else:
lib_search_paths = False
elif line.startswith("Library search paths:"):
lib_search_paths = True
if not _LINKER_LINE.match(line):
continue
if _LINKER_LINE_IGNORE.match(line):
continue
tty.debug(f"implicit link dirs: link line: {line}")
next_arg = False
for arg in line.split():
if arg in ("-L", "-Y"):
next_arg = True
continue
if next_arg:
raw_link_dirs.append(arg)
next_arg = False
continue
link_dir_arg = _LINK_DIR_ARG.match(arg)
if link_dir_arg:
link_dir = link_dir_arg.group("dir")
raw_link_dirs.append(link_dir)
link_dir_arg = _LIBPATH_ARG.match(arg)
if link_dir_arg:
link_dir = link_dir_arg.group("dir")
raw_link_dirs.append(link_dir)
implicit_link_dirs = list()
visited = set()
for link_dir in raw_link_dirs:
normalized_path = os.path.abspath(link_dir)
if normalized_path not in visited:
implicit_link_dirs.append(normalized_path)
visited.add(normalized_path)
tty.debug(f"implicit link dirs: result: {', '.join(implicit_link_dirs)}")
return implicit_link_dirs
@llnl.path.system_path_filter
def _parse_non_system_link_dirs(string: str) -> List[str]:
"""Parses link paths out of compiler debug output.
Args:
string: compiler debug output as a string
Returns:
Implicit link paths parsed from the compiler output
"""
link_dirs = _parse_link_paths(string)
# Remove directories that do not exist. Some versions of the Cray compiler
# report nonexistent directories
link_dirs = [d for d in link_dirs if os.path.isdir(d)]
# Return set of directories containing needed compiler libs, minus
# system paths. Note that 'filter_system_paths' only checks for an
# exact match, while 'in_system_subdirectory' checks if a path contains
# a system directory as a subdirectory
link_dirs = filter_system_paths(link_dirs)
return list(p for p in link_dirs if not in_system_subdirectory(p))
def in_system_subdirectory(path):
system_dirs = [
"/lib/",
"/lib64/",
"/usr/lib/",
"/usr/lib64/",
"/usr/local/lib/",
"/usr/local/lib64/",
]
return any(path_contains_subdirectory(path, x) for x in system_dirs)
class Compiler:
"""This class encapsulates a Spack "compiler", which includes C,
C++, and Fortran compilers. Subclasses should implement
support for specific compilers, their possible names, arguments,
and how to identify the particular type of compiler."""
# Optional prefix regexes for searching for this type of compiler.
# Prefixes are sometimes used for toolchains
prefixes: List[str] = []
# Optional suffix regexes for searching for this type of compiler.
# Suffixes are used by some frameworks, e.g. macports uses an '-mp-X.Y'
# version suffix for gcc.
suffixes = [r"-.*"]
#: Compiler argument that produces version information
version_argument = "-dumpversion"
#: Return values to ignore when invoking the compiler to get its version
ignore_version_errors: Sequence[int] = ()
#: Regex used to extract version from compiler's output
version_regex = "(.*)"
# These libraries are anticipated to be required by all executables built
# by any compiler
_all_compiler_rpath_libraries = ["libc", "libc++", "libstdc++"]
#: Platform matcher for Platform objects supported by compiler
is_supported_on_platform = lambda x: True
# Default flags used by a compiler to set an rpath
@property
def cc_rpath_arg(self):
return "-Wl,-rpath,"
@property
def cxx_rpath_arg(self):
return "-Wl,-rpath,"
@property
def f77_rpath_arg(self):
return "-Wl,-rpath,"
@property
def fc_rpath_arg(self):
return "-Wl,-rpath,"
@property
def linker_arg(self):
"""Flag that need to be used to pass an argument to the linker."""
return "-Wl,"
@property
def disable_new_dtags(self):
if platform.system() == "Darwin":
return ""
return "--disable-new-dtags"
@property
def enable_new_dtags(self):
if platform.system() == "Darwin":
return ""
return "--enable-new-dtags"
@property
def debug_flags(self):
return ["-g"]
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3"]
def __init__(
self,
cspec,
operating_system,
target,
paths,
modules: Optional[List[str]] = None,
alias=None,
environment=None,
extra_rpaths=None,
enable_implicit_rpaths=None,
**kwargs,
):
self.spec = cspec
self.operating_system = str(operating_system)
self.target = target
self.modules = modules or []
self.alias = alias
self.environment = environment or {}
self.extra_rpaths = extra_rpaths or []
self.enable_implicit_rpaths = enable_implicit_rpaths
self.cache = COMPILER_CACHE
self.cc = paths[0]
self.cxx = paths[1]
self.f77 = None
self.fc = None
if len(paths) > 2:
self.f77 = paths[2]
if len(paths) == 3:
self.fc = self.f77
else:
self.fc = paths[3]
# Unfortunately have to make sure these params are accepted
# in the same order they are returned by sorted(flags)
# in compilers/__init__.py
self.flags = spack.spec.FlagMap(self.spec)
for flag in self.flags.valid_compiler_flags():
value = kwargs.get(flag, None)
if value is not None:
values_with_propagation = tokenize_flags(value, False)
for value, propagation in values_with_propagation:
self.flags.add_flag(flag, value, propagation)
# caching value for compiler reported version
# used for version checks for API, e.g. C++11 flag
self._real_version = None
def __eq__(self, other):
return (
self.cc == other.cc
and self.cxx == other.cxx
and self.fc == other.fc
and self.f77 == other.f77
and self.spec == other.spec
and self.operating_system == other.operating_system
and self.target == other.target
and self.flags == other.flags
and self.modules == other.modules
and self.environment == other.environment
and self.extra_rpaths == other.extra_rpaths
and self.enable_implicit_rpaths == other.enable_implicit_rpaths
)
def __hash__(self):
return hash(
(
self.cc,
self.cxx,
self.fc,
self.f77,
self.spec,
self.operating_system,
self.target,
str(self.flags),
str(self.modules),
str(self.environment),
str(self.extra_rpaths),
self.enable_implicit_rpaths,
)
)
def verify_executables(self):
"""Raise an error if any of the compiler executables is not valid.
This method confirms that for all of the compilers (cc, cxx, f77, fc)
that have paths, those paths exist and are executable by the current
user.
Raises a CompilerAccessError if any of the non-null paths for the
compiler are not accessible.
"""
def accessible_exe(exe):
# compilers may contain executable names (on Cray or user edited)
if not os.path.isabs(exe):
exe = spack.util.executable.which_string(exe)
if not exe:
return False
return os.path.isfile(exe) and os.access(exe, os.X_OK)
# setup environment before verifying in case we have executable names
# instead of absolute paths
with self.compiler_environment():
missing = [
cmp
for cmp in (self.cc, self.cxx, self.f77, self.fc)
if cmp and not accessible_exe(cmp)
]
if missing:
raise CompilerAccessError(self, missing)
@property
def version(self):
return self.spec.version
@property
def real_version(self):
"""Executable reported compiler version used for API-determinations
E.g. C++11 flag checks.
"""
real_version_str = self.cache.get(self).real_version
if not real_version_str or real_version_str == "unknown":
return self.version
return spack.version.StandardVersion.from_string(real_version_str)
def implicit_rpaths(self) -> List[str]:
if self.enable_implicit_rpaths is False:
return []
output = self.compiler_verbose_output
if not output:
return []
link_dirs = _parse_non_system_link_dirs(output)
all_required_libs = list(self.required_libs) + Compiler._all_compiler_rpath_libraries
return list(paths_containing_libs(link_dirs, all_required_libs))
@property
def default_dynamic_linker(self) -> Optional[str]:
"""Determine default dynamic linker from compiler link line"""
output = self.compiler_verbose_output
if not output:
return None
return spack.util.libc.parse_dynamic_linker(output)
@property
def default_libc(self) -> Optional["spack.spec.Spec"]:
"""Determine libc targeted by the compiler from link line"""
# technically this should be testing the target platform of the compiler, but we don't have
# that, so stick to host platform for now.
if sys.platform in ("darwin", "win32"):
return None
dynamic_linker = self.default_dynamic_linker
if not dynamic_linker:
return None
return spack.util.libc.libc_from_dynamic_linker(dynamic_linker)
@property
def required_libs(self):
"""For executables created with this compiler, the compiler libraries
that would be generally required to run it.
"""
# By default every compiler returns the empty list
return []
@property
def compiler_verbose_output(self) -> Optional[str]:
"""Verbose output from compiling a dummy C source file. Output is cached."""
return self.cache.get(self).c_compiler_output
def _compile_dummy_c_source(self) -> Optional[str]:
if self.cc:
cc = self.cc
ext = "c"
else:
cc = self.cxx
ext = "cc"
if not cc or not self.verbose_flag:
return None
try:
tmpdir = tempfile.mkdtemp(prefix="spack-implicit-link-info")
fout = os.path.join(tmpdir, "output")
fin = os.path.join(tmpdir, f"main.{ext}")
with open(fin, "w") as csource:
csource.write(
"int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }\n"
)
cc_exe = spack.util.executable.Executable(cc)
for flag_type in ["cflags" if cc == self.cc else "cxxflags", "cppflags", "ldflags"]:
cc_exe.add_default_arg(*self.flags.get(flag_type, []))
with self.compiler_environment():
return cc_exe(self.verbose_flag, fin, "-o", fout, output=str, error=str)
except spack.util.executable.ProcessError as pe:
tty.debug("ProcessError: Command exited with non-zero status: " + pe.long_message)
return None
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
@property
def verbose_flag(self) -> Optional[str]:
"""
This property should be overridden in the compiler subclass if a
verbose flag is available.
If it is not overridden, it is assumed to not be supported.
"""
# This property should be overridden in the compiler subclass if
# OpenMP is supported by that compiler
@property
def openmp_flag(self):
# If it is not overridden, assume it is not supported and warn the user
raise UnsupportedCompilerFlag(self, "OpenMP", "openmp_flag")
# This property should be overridden in the compiler subclass if
# C++98 is not the default standard for that compiler
@property
def cxx98_flag(self):
return ""
# This property should be overridden in the compiler subclass if
# C++11 is supported by that compiler
@property
def cxx11_flag(self):
# If it is not overridden, assume it is not supported and warn the user
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag")
# This property should be overridden in the compiler subclass if
# C++14 is supported by that compiler
@property
def cxx14_flag(self):
# If it is not overridden, assume it is not supported and warn the user
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag")
# This property should be overridden in the compiler subclass if
# C++17 is supported by that compiler
@property
def cxx17_flag(self):
# If it is not overridden, assume it is not supported and warn the user
raise UnsupportedCompilerFlag(self, "the C++17 standard", "cxx17_flag")
# This property should be overridden in the compiler subclass if
# C99 is supported by that compiler
@property
def c99_flag(self):
# If it is not overridden, assume it is not supported and warn the user
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag")
# This property should be overridden in the compiler subclass if
# C11 is supported by that compiler
@property
def c11_flag(self):
# If it is not overridden, assume it is not supported and warn the user
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag")
@property
def cc_pic_flag(self):
"""Returns the flag used by the C compiler to produce
Position Independent Code (PIC)."""
return "-fPIC"
@property
def cxx_pic_flag(self):
"""Returns the flag used by the C++ compiler to produce
Position Independent Code (PIC)."""
return "-fPIC"
@property
def f77_pic_flag(self):
"""Returns the flag used by the F77 compiler to produce
Position Independent Code (PIC)."""
return "-fPIC"
@property
def fc_pic_flag(self):
"""Returns the flag used by the FC compiler to produce
Position Independent Code (PIC)."""
return "-fPIC"
# Note: This is not a class method. The class methods are used to detect
# compilers on PATH based systems, and do not set up the run environment of
# the compiler. This method can be called on `module` based systems as well
def get_real_version(self) -> str:
"""Query the compiler for its version.
This is the "real" compiler version, regardless of what is in the
compilers.yaml file, which the user can change to name their compiler.
Use the runtime environment of the compiler (modules and environment
modifications) to enable the compiler to run properly on any platform.
"""
cc = spack.util.executable.Executable(self.cc)
try:
with self.compiler_environment():
output = cc(
self.version_argument,
output=str,
error=str,
ignore_errors=tuple(self.ignore_version_errors),
)
return self.extract_version_from_output(output)
except spack.util.executable.ProcessError:
return "unknown"
@property
def prefix(self):
"""Query the compiler for its install prefix. This is the install
path as reported by the compiler. Note that paths for cc, cxx, etc
are not enough to find the install prefix of the compiler, since
the can be symlinks, wrappers, or filenames instead of absolute paths."""
raise NotImplementedError("prefix is not implemented for this compiler")
#
# Compiler classes have methods for querying the version of
# specific compiler executables. This is used when discovering compilers.
#
# Compiler *instances* are just data objects, and can only be
# constructed from an actual set of executables.
#
@classmethod
def default_version(cls, cc):
"""Override just this to override all compiler version functions."""
output = get_compiler_version_output(
cc, cls.version_argument, tuple(cls.ignore_version_errors)
)
return cls.extract_version_from_output(output)
@classmethod
@llnl.util.lang.memoized
def extract_version_from_output(cls, output: str) -> str:
"""Extracts the version from compiler's output."""
match = re.search(cls.version_regex, output)
return match.group(1) if match else "unknown"
@classmethod
def cc_version(cls, cc):
return cls.default_version(cc)
@classmethod
def search_regexps(cls, language):
# Compile all the regular expressions used for files beforehand.
# This searches for any combination of <prefix><name><suffix>
# defined for the compiler
compiler_names = getattr(cls, "{0}_names".format(language))
prefixes = [""] + cls.prefixes
suffixes = [""]
if sys.platform == "win32":
ext = r"\.(?:exe|bat)"
cls_suf = [suf + ext for suf in cls.suffixes]
ext_suf = [ext]
suffixes = suffixes + cls.suffixes + cls_suf + ext_suf
else:
suffixes = suffixes + cls.suffixes
regexp_fmt = r"^({0}){1}({2})$"
return [
re.compile(regexp_fmt.format(prefix, re.escape(name), suffix))
for prefix, name, suffix in itertools.product(prefixes, compiler_names, suffixes)
]
def setup_custom_environment(self, pkg, env):
"""Set any environment variables necessary to use the compiler."""
pass
def __repr__(self):
"""Return a string representation of the compiler toolchain."""
return self.__str__()
def __str__(self):
"""Return a string representation of the compiler toolchain."""
return "%s(%s)" % (
self.name,
"\n ".join(
(
str(s)
for s in (
self.cc,
self.cxx,
self.f77,
self.fc,
self.modules,
str(self.operating_system),
)
)
),
)
@contextlib.contextmanager
def compiler_environment(self):
# Avoid modifying os.environ if possible.
if not self.modules and not self.environment:
yield
return
# store environment to replace later
backup_env = os.environ.copy()
try:
# load modules and set env variables
for module in self.modules:
spack.util.module_cmd.load_module(module)
# apply other compiler environment changes
spack.schema.environment.parse(self.environment).apply_modifications()
yield
finally:
# Restore environment regardless of whether inner code succeeded
os.environ.clear()
os.environ.update(backup_env)
def to_dict(self):
flags_dict = {fname: " ".join(fvals) for fname, fvals in self.flags.items()}
flags_dict.update(
{attr: getattr(self, attr, None) for attr in FLAG_INSTANCE_VARS if hasattr(self, attr)}
)
result = {
"spec": str(self.spec),
"paths": {attr: getattr(self, attr, None) for attr in PATH_INSTANCE_VARS},
"flags": flags_dict,
"operating_system": str(self.operating_system),
"target": str(self.target),
"modules": self.modules or [],
"environment": self.environment or {},
"extra_rpaths": self.extra_rpaths or [],
}
if self.enable_implicit_rpaths is not None:
result["implicit_rpaths"] = self.enable_implicit_rpaths
if self.alias:
result["alias"] = self.alias
return result
class CompilerAccessError(spack.error.SpackError):
def __init__(self, compiler, paths):
msg = "Compiler '%s' has executables that are missing" % compiler.spec
msg += " or are not executable: %s" % paths
super().__init__(msg)
class InvalidCompilerError(spack.error.SpackError):
def __init__(self):
super().__init__("Compiler has no executables.")
class UnsupportedCompilerFlag(spack.error.SpackError):
def __init__(self, compiler, feature, flag_name, ver_string=None):
super().__init__(
"{0} ({1}) does not support {2} (as compiler.{3}).".format(
compiler.name, ver_string if ver_string else compiler.version, feature, flag_name
),
"If you think it should, please edit the compiler.{0} subclass to".format(
compiler.name
)
+ " implement the {0} property and submit a pull request or issue.".format(flag_name),
)
class CompilerCacheEntry:
"""Deserialized cache entry for a compiler"""
__slots__ = ["c_compiler_output", "real_version"]
def __init__(self, c_compiler_output: Optional[str], real_version: str):
self.c_compiler_output = c_compiler_output
self.real_version = real_version
@classmethod
def from_dict(cls, data: Dict[str, Optional[str]]):
if not isinstance(data, dict):
raise ValueError(f"Invalid {cls.__name__} data")
c_compiler_output = data.get("c_compiler_output")
real_version = data.get("real_version")
if not isinstance(real_version, str) or not isinstance(
c_compiler_output, (str, type(None))
):
raise ValueError(f"Invalid {cls.__name__} data")
return cls(c_compiler_output, real_version)
class CompilerCache:
"""Base class for compiler output cache. Default implementation does not cache anything."""
def value(self, compiler: Compiler) -> Dict[str, Optional[str]]:
return {
"c_compiler_output": compiler._compile_dummy_c_source(),
"real_version": compiler.get_real_version(),
}
def get(self, compiler: Compiler) -> CompilerCacheEntry:
return CompilerCacheEntry.from_dict(self.value(compiler))
class FileCompilerCache(CompilerCache):
"""Cache for compiler output, which is used to determine implicit link paths, the default libc
version, and the compiler version."""
name = os.path.join("compilers", "compilers.json")
def __init__(self, cache: "FileCache") -> None:
self.cache = cache
self.cache.init_entry(self.name)
self._data: Dict[str, Dict[str, Optional[str]]] = {}
def _get_entry(self, key: str) -> Optional[CompilerCacheEntry]:
try:
return CompilerCacheEntry.from_dict(self._data[key])
except ValueError:
del self._data[key]
except KeyError:
pass
return None
def get(self, compiler: Compiler) -> CompilerCacheEntry:
# Cache hit
try:
with self.cache.read_transaction(self.name) as f:
assert f is not None
self._data = json.loads(f.read())
assert isinstance(self._data, dict)
except (json.JSONDecodeError, AssertionError):
self._data = {}
key = self._key(compiler)
value = self._get_entry(key)
if value is not None:
return value
# Cache miss
with self.cache.write_transaction(self.name) as (old, new):
try:
assert old is not None
self._data = json.loads(old.read())
assert isinstance(self._data, dict)
except (json.JSONDecodeError, AssertionError):
self._data = {}
# Use cache entry that may have been created by another process in the meantime.
entry = self._get_entry(key)
# Finally compute the cache entry
if entry is None:
self._data[key] = self.value(compiler)
entry = CompilerCacheEntry.from_dict(self._data[key])
new.write(json.dumps(self._data, separators=(",", ":")))
return entry
def _key(self, compiler: Compiler) -> str:
as_bytes = json.dumps(compiler.to_dict(), separators=(",", ":")).encode("utf-8")
return hashlib.sha256(as_bytes).hexdigest()
def _make_compiler_cache():
return FileCompilerCache(spack.caches.MISC_CACHE)
COMPILER_CACHE: CompilerCache = llnl.util.lang.Singleton(_make_compiler_cache) # type: ignore

View File

@@ -2,3 +2,836 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""This module contains functions related to finding compilers on the
system and configuring Spack to use multiple compilers.
"""
import importlib
import os
import re
import sys
import warnings
from typing import Dict, List, Optional
import archspec.cpu
import llnl.util.filesystem as fs
import llnl.util.lang
import llnl.util.tty as tty
import spack.compiler
import spack.config
import spack.error
import spack.paths
import spack.platforms
import spack.repo
import spack.spec
from spack.operating_systems import windows_os
from spack.util.environment import get_path
from spack.util.naming import mod_to_class
_other_instance_vars = [
"modules",
"operating_system",
"environment",
"implicit_rpaths",
"extra_rpaths",
]
# TODO: Caches at module level make it difficult to mock configurations in
# TODO: unit tests. It might be worth reworking their implementation.
#: cache of compilers constructed from config data, keyed by config entry id.
_compiler_cache: Dict[str, "spack.compiler.Compiler"] = {}
_compiler_to_pkg = {
"clang": "llvm+clang",
"oneapi": "intel-oneapi-compilers",
"rocmcc": "llvm-amdgpu",
"intel@2020:": "intel-oneapi-compilers-classic",
"arm": "acfl",
}
# TODO: generating this from the previous dict causes docs errors
package_name_to_compiler_name = {
"llvm": "clang",
"intel-oneapi-compilers": "oneapi",
"llvm-amdgpu": "rocmcc",
"intel-oneapi-compilers-classic": "intel",
"acfl": "arm",
}
#: Tag used to identify packages providing a compiler
COMPILER_TAG = "compiler"
def pkg_spec_for_compiler(cspec):
"""Return the spec of the package that provides the compiler."""
for spec, package in _compiler_to_pkg.items():
if cspec.satisfies(spec):
spec_str = "%s@%s" % (package, cspec.versions)
break
else:
spec_str = str(cspec)
return spack.spec.parse_with_version_concrete(spec_str)
def _auto_compiler_spec(function):
def converter(cspec_like, *args, **kwargs):
if not isinstance(cspec_like, spack.spec.CompilerSpec):
cspec_like = spack.spec.CompilerSpec(cspec_like)
return function(cspec_like, *args, **kwargs)
return converter
def _to_dict(compiler):
"""Return a dict version of compiler suitable to insert in YAML."""
return {"compiler": compiler.to_dict()}
def get_compiler_config(
configuration: "spack.config.Configuration",
*,
scope: Optional[str] = None,
init_config: bool = False,
) -> List[Dict]:
"""Return the compiler configuration for the specified architecture."""
config = configuration.get("compilers", scope=scope) or []
if config or not init_config:
return config
merged_config = configuration.get("compilers")
if merged_config:
# Config is empty for this scope
# Do not init config because there is a non-empty scope
return config
find_compilers(scope=scope)
config = configuration.get("compilers", scope=scope)
return config
def get_compiler_config_from_packages(
configuration: "spack.config.Configuration", *, scope: Optional[str] = None
) -> List[Dict]:
"""Return the compiler configuration from packages.yaml"""
packages_yaml = configuration.get("packages", scope=scope)
return CompilerConfigFactory.from_packages_yaml(packages_yaml)
def compiler_config_files():
config_files = list()
config = spack.config.CONFIG
for scope in config.writable_scopes:
name = scope.name
compiler_config = config.get("compilers", scope=name)
if compiler_config:
config_files.append(config.get_config_filename(name, "compilers"))
compiler_config_from_packages = get_compiler_config_from_packages(config, scope=name)
if compiler_config_from_packages:
config_files.append(config.get_config_filename(name, "packages"))
return config_files
def add_compilers_to_config(compilers, scope=None):
"""Add compilers to the config for the specified architecture.
Arguments:
compilers: a list of Compiler objects.
scope: configuration scope to modify.
"""
compiler_config = get_compiler_config(configuration=spack.config.CONFIG, scope=scope)
for compiler in compilers:
if not compiler.cc:
tty.debug(f"{compiler.spec} does not have a C compiler")
if not compiler.cxx:
tty.debug(f"{compiler.spec} does not have a C++ compiler")
if not compiler.f77:
tty.debug(f"{compiler.spec} does not have a Fortran77 compiler")
if not compiler.fc:
tty.debug(f"{compiler.spec} does not have a Fortran compiler")
compiler_config.append(_to_dict(compiler))
spack.config.set("compilers", compiler_config, scope=scope)
@_auto_compiler_spec
def remove_compiler_from_config(compiler_spec, scope=None):
"""Remove compilers from configuration by spec.
If scope is None, all the scopes are searched for removal.
Arguments:
compiler_spec: compiler to be removed
scope: configuration scope to modify
"""
candidate_scopes = [scope]
if scope is None:
candidate_scopes = spack.config.CONFIG.scopes.keys()
removal_happened = False
for current_scope in candidate_scopes:
removal_happened |= _remove_compiler_from_scope(compiler_spec, scope=current_scope)
msg = "`spack compiler remove` will not remove compilers defined in packages.yaml"
msg += "\nTo remove these compilers, either edit the config or use `spack external remove`"
tty.debug(msg)
return removal_happened
def _remove_compiler_from_scope(compiler_spec, scope):
"""Removes a compiler from a specific configuration scope.
Args:
compiler_spec: compiler to be removed
scope: configuration scope under consideration
Returns:
True if one or more compiler entries were actually removed, False otherwise
"""
assert scope is not None, "a specific scope is needed when calling this function"
compiler_config = get_compiler_config(configuration=spack.config.CONFIG, scope=scope)
filtered_compiler_config = [
compiler_entry
for compiler_entry in compiler_config
if not spack.spec.parse_with_version_concrete(
compiler_entry["compiler"]["spec"], compiler=True
).satisfies(compiler_spec)
]
if len(filtered_compiler_config) == len(compiler_config):
return False
# We need to preserve the YAML type for comments, hence we are copying the
# items in the list that has just been retrieved
compiler_config[:] = filtered_compiler_config
spack.config.CONFIG.set("compilers", compiler_config, scope=scope)
return True
def all_compilers_config(
configuration: "spack.config.Configuration",
*,
scope: Optional[str] = None,
init_config: bool = True,
) -> List["spack.compiler.Compiler"]:
"""Return a set of specs for all the compiler versions currently
available to build with. These are instances of CompilerSpec.
"""
from_packages_yaml = get_compiler_config_from_packages(configuration, scope=scope)
if from_packages_yaml:
init_config = False
from_compilers_yaml = get_compiler_config(configuration, scope=scope, init_config=init_config)
result = from_compilers_yaml + from_packages_yaml
# Dedupe entries by the compiler they represent
# If the entry is invalid, treat it as unique for deduplication
key = lambda c: _compiler_from_config_entry(c["compiler"] or id(c))
return list(llnl.util.lang.dedupe(result, key=key))
def all_compiler_specs(scope=None, init_config=True):
# Return compiler specs from the merged config.
return [
spack.spec.parse_with_version_concrete(s["compiler"]["spec"], compiler=True)
for s in all_compilers_config(spack.config.CONFIG, scope=scope, init_config=init_config)
]
def find_compilers(
path_hints: Optional[List[str]] = None,
*,
scope: Optional[str] = None,
mixed_toolchain: bool = False,
max_workers: Optional[int] = None,
) -> List["spack.compiler.Compiler"]:
"""Searches for compiler in the paths given as argument. If any new compiler is found, the
configuration is updated, and the list of new compiler objects is returned.
Args:
path_hints: list of path hints where to look for. A sensible default based on the ``PATH``
environment variable will be used if the value is None
scope: configuration scope to modify
mixed_toolchain: allow mixing compilers from different toolchains if otherwise missing for
a certain language
max_workers: number of processes used to search for compilers
"""
import spack.detection
known_compilers = set(all_compilers(init_config=False))
if path_hints is None:
path_hints = get_path("PATH")
default_paths = fs.search_paths_for_executables(*path_hints)
if sys.platform == "win32":
default_paths.extend(windows_os.WindowsOs().compiler_search_paths)
compiler_pkgs = spack.repo.PATH.packages_with_tags(COMPILER_TAG, full=True)
detected_packages = spack.detection.by_path(
compiler_pkgs, path_hints=default_paths, max_workers=max_workers
)
valid_compilers = {}
for name, detected in detected_packages.items():
compilers = [x for x in detected if CompilerConfigFactory.from_external_spec(x)]
if not compilers:
continue
valid_compilers[name] = compilers
def _has_fortran_compilers(x):
if "compilers" not in x.extra_attributes:
return False
return "fortran" in x.extra_attributes["compilers"]
if mixed_toolchain:
gccs = [x for x in valid_compilers.get("gcc", []) if _has_fortran_compilers(x)]
if gccs:
best_gcc = sorted(
gccs, key=lambda x: spack.spec.parse_with_version_concrete(x).version
)[-1]
gfortran = best_gcc.extra_attributes["compilers"]["fortran"]
for name in ("llvm", "apple-clang"):
if name not in valid_compilers:
continue
candidates = valid_compilers[name]
for candidate in candidates:
if _has_fortran_compilers(candidate):
continue
candidate.extra_attributes["compilers"]["fortran"] = gfortran
new_compilers = []
for name, detected in valid_compilers.items():
for config in CompilerConfigFactory.from_specs(detected):
c = _compiler_from_config_entry(config["compiler"])
if c in known_compilers:
continue
new_compilers.append(c)
add_compilers_to_config(new_compilers, scope=scope)
return new_compilers
def select_new_compilers(compilers, scope=None):
"""Given a list of compilers, remove those that are already defined in
the configuration.
"""
compilers_not_in_config = []
for c in compilers:
arch_spec = spack.spec.ArchSpec((None, c.operating_system, c.target))
same_specs = compilers_for_spec(
c.spec, arch_spec=arch_spec, scope=scope, init_config=False
)
if not same_specs:
compilers_not_in_config.append(c)
return compilers_not_in_config
def supported_compilers() -> List[str]:
"""Return a set of names of compilers supported by Spack.
See available_compilers() to get a list of all the available
versions of supported compilers.
"""
# Hack to be able to call the compiler `apple-clang` while still
# using a valid python name for the module
return sorted(all_compiler_names())
def supported_compilers_for_host_platform() -> List[str]:
"""Return a set of compiler class objects supported by Spack
that are also supported by the current host platform
"""
host_plat = spack.platforms.real_host()
return supported_compilers_for_platform(host_plat)
def supported_compilers_for_platform(platform: "spack.platforms.Platform") -> List[str]:
"""Return a set of compiler class objects supported by Spack
that are also supported by the provided platform
Args:
platform (str): string representation of platform
for which compiler compatability should be determined
"""
return [
name
for name in supported_compilers()
if class_for_compiler_name(name).is_supported_on_platform(platform)
]
def all_compiler_names() -> List[str]:
def replace_apple_clang(name):
return name if name != "apple_clang" else "apple-clang"
return [replace_apple_clang(name) for name in all_compiler_module_names()]
@llnl.util.lang.memoized
def all_compiler_module_names() -> List[str]:
return list(llnl.util.lang.list_modules(spack.paths.compilers_path))
@_auto_compiler_spec
def supported(compiler_spec):
"""Test if a particular compiler is supported."""
return compiler_spec.name in supported_compilers()
@_auto_compiler_spec
def find(compiler_spec, scope=None, init_config=True):
"""Return specs of available compilers that match the supplied
compiler spec. Return an empty list if nothing found."""
return [c for c in all_compiler_specs(scope, init_config) if c.satisfies(compiler_spec)]
@_auto_compiler_spec
def find_specs_by_arch(compiler_spec, arch_spec, scope=None, init_config=True):
"""Return specs of available compilers that match the supplied
compiler spec. Return an empty list if nothing found."""
return [
c.spec
for c in compilers_for_spec(
compiler_spec, arch_spec=arch_spec, scope=scope, init_config=init_config
)
]
def all_compilers(scope=None, init_config=True):
return all_compilers_from(
configuration=spack.config.CONFIG, scope=scope, init_config=init_config
)
def all_compilers_from(configuration, scope=None, init_config=True):
compilers = []
for items in all_compilers_config(
configuration=configuration, scope=scope, init_config=init_config
):
items = items["compiler"]
compiler = _compiler_from_config_entry(items) # can be None in error case
if compiler:
compilers.append(compiler)
return compilers
@_auto_compiler_spec
def compilers_for_spec(compiler_spec, *, arch_spec=None, scope=None, init_config=True):
"""This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found.
"""
config = all_compilers_config(spack.config.CONFIG, scope=scope, init_config=init_config)
matches = set(find(compiler_spec, scope, init_config))
compilers = []
for cspec in matches:
compilers.extend(get_compilers(config, cspec, arch_spec))
return compilers
def compilers_for_arch(arch_spec, scope=None):
config = all_compilers_config(spack.config.CONFIG, scope=scope, init_config=False)
return list(get_compilers(config, arch_spec=arch_spec))
def compiler_specs_for_arch(arch_spec, scope=None):
return [c.spec for c in compilers_for_arch(arch_spec, scope)]
class CacheReference:
"""This acts as a hashable reference to any object (regardless of whether
the object itself is hashable) and also prevents the object from being
garbage-collected (so if two CacheReference objects are equal, they
will refer to the same object, since it will not have been gc'ed since
the creation of the first CacheReference).
"""
def __init__(self, val):
self.val = val
self.id = id(val)
def __hash__(self):
return self.id
def __eq__(self, other):
return isinstance(other, CacheReference) and self.id == other.id
def compiler_from_dict(items):
cspec = spack.spec.parse_with_version_concrete(items["spec"], compiler=True)
os = items.get("operating_system", None)
target = items.get("target", None)
if not (
"paths" in items and all(n in items["paths"] for n in spack.compiler.PATH_INSTANCE_VARS)
):
raise InvalidCompilerConfigurationError(cspec)
cls = class_for_compiler_name(cspec.name)
compiler_paths = []
for c in spack.compiler.PATH_INSTANCE_VARS:
compiler_path = items["paths"][c]
if compiler_path != "None":
compiler_paths.append(compiler_path)
else:
compiler_paths.append(None)
mods = items.get("modules")
if mods == "None":
mods = []
alias = items.get("alias", None)
compiler_flags = items.get("flags", {})
environment = items.get("environment", {})
extra_rpaths = items.get("extra_rpaths", [])
implicit_rpaths = items.get("implicit_rpaths", None)
# Starting with c22a145, 'implicit_rpaths' was a list. Now it is a
# boolean which can be set by the user to disable all automatic
# RPATH insertion of compiler libraries
if implicit_rpaths is not None and not isinstance(implicit_rpaths, bool):
implicit_rpaths = None
return cls(
cspec,
os,
target,
compiler_paths,
mods,
alias,
environment,
extra_rpaths,
enable_implicit_rpaths=implicit_rpaths,
**compiler_flags,
)
def _compiler_from_config_entry(items):
"""Note this is intended for internal use only. To avoid re-parsing
the same config dictionary this keeps track of its location in
memory. If you provide the same dictionary twice it will return
the same Compiler object (regardless of whether the dictionary
entries have changed).
"""
config_id = CacheReference(items)
compiler = _compiler_cache.get(config_id, None)
if compiler is None:
try:
compiler = compiler_from_dict(items)
except UnknownCompilerError as e:
warnings.warn(e.message)
_compiler_cache[config_id] = compiler
return compiler
def get_compilers(config, cspec=None, arch_spec=None):
compilers = []
for items in config:
items = items["compiler"]
# We might use equality here.
if cspec and not spack.spec.parse_with_version_concrete(
items["spec"], compiler=True
).satisfies(cspec):
continue
# If an arch spec is given, confirm that this compiler
# is for the given operating system
os = items.get("operating_system", None)
if arch_spec and os != arch_spec.os:
continue
# If an arch spec is given, confirm that this compiler
# is for the given target. If the target is 'any', match
# any given arch spec. If the compiler has no assigned
# target this is an old compiler config file, skip this logic.
target = items.get("target", None)
try:
current_target = archspec.cpu.TARGETS[str(arch_spec.target)]
family = str(current_target.family)
except KeyError:
# TODO: Check if this exception handling makes sense, or if we
# TODO: need to change / refactor tests
family = str(arch_spec.target)
except AttributeError:
assert arch_spec is None
if arch_spec and target and (target != family and target != "any"):
# If the family of the target is the family we are seeking,
# there's an error in the underlying configuration
if archspec.cpu.TARGETS[target].family == family:
msg = (
'the "target" field in compilers.yaml accepts only '
'target families [replace "{0}" with "{1}"'
' in "{2}" specification]'
)
msg = msg.format(str(target), family, items.get("spec", "??"))
raise ValueError(msg)
continue
compiler = _compiler_from_config_entry(items)
if compiler:
compilers.append(compiler)
return compilers
@_auto_compiler_spec
def compiler_for_spec(compiler_spec, arch_spec):
"""Get the compiler that satisfies compiler_spec. compiler_spec must
be concrete."""
assert compiler_spec.concrete
assert arch_spec.concrete
compilers = compilers_for_spec(compiler_spec, arch_spec=arch_spec)
if len(compilers) < 1:
raise NoCompilerForSpecError(compiler_spec, arch_spec.os)
if len(compilers) > 1:
msg = "Multiple definitions of compiler %s " % compiler_spec
msg += "for architecture %s:\n %s" % (arch_spec, compilers)
tty.debug(msg)
return compilers[0]
@llnl.util.lang.memoized
def class_for_compiler_name(compiler_name):
"""Given a compiler module name, get the corresponding Compiler class."""
if not supported(compiler_name):
raise UnknownCompilerError(compiler_name)
# Hack to be able to call the compiler `apple-clang` while still
# using a valid python name for the module
submodule_name = compiler_name
if compiler_name == "apple-clang":
submodule_name = compiler_name.replace("-", "_")
module_name = ".".join(["spack", "compilers", submodule_name])
module_obj = importlib.import_module(module_name)
cls = getattr(module_obj, mod_to_class(compiler_name))
# make a note of the name in the module so we can get to it easily.
cls.name = compiler_name
return cls
def all_compiler_types():
return [class_for_compiler_name(c) for c in supported_compilers()]
def is_mixed_toolchain(compiler):
"""Returns True if the current compiler is a mixed toolchain,
False otherwise.
Args:
compiler (spack.compiler.Compiler): a valid compiler object
"""
import spack.detection.path
executables = [
os.path.basename(compiler.cc or ""),
os.path.basename(compiler.cxx or ""),
os.path.basename(compiler.f77 or ""),
os.path.basename(compiler.fc or ""),
]
toolchains = set()
finder = spack.detection.path.ExecutablesFinder()
for pkg_name in spack.repo.PATH.packages_with_tags(COMPILER_TAG):
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
patterns = finder.search_patterns(pkg=pkg_cls)
if not patterns:
continue
joined_pattern = re.compile(r"|".join(patterns))
if any(joined_pattern.search(exe) for exe in executables):
tty.debug(f"[TOOLCHAIN] MATCH {pkg_name}")
toolchains.add(pkg_name)
if len(toolchains) > 1:
if (
toolchains == {"llvm", "apple-clang", "aocc"}
# Msvc toolchain uses Intel ifx
or toolchains == {"msvc", "intel-oneapi-compilers"}
):
return False
tty.debug("[TOOLCHAINS] {0}".format(toolchains))
return True
return False
_EXTRA_ATTRIBUTES_KEY = "extra_attributes"
_COMPILERS_KEY = "compilers"
_C_KEY = "c"
_CXX_KEY, _FORTRAN_KEY = "cxx", "fortran"
class CompilerConfigFactory:
"""Class aggregating all ways of constructing a list of compiler config entries."""
@staticmethod
def from_specs(specs: List["spack.spec.Spec"]) -> List[dict]:
result = []
compiler_package_names = supported_compilers() + list(package_name_to_compiler_name.keys())
for s in specs:
if s.name not in compiler_package_names:
continue
candidate = CompilerConfigFactory.from_external_spec(s)
if candidate is None:
continue
result.append(candidate)
return result
@staticmethod
def from_packages_yaml(packages_yaml) -> List[dict]:
compiler_specs = []
compiler_package_names = supported_compilers() + list(package_name_to_compiler_name.keys())
for name, entry in packages_yaml.items():
if name not in compiler_package_names:
continue
externals_config = entry.get("externals", None)
if not externals_config:
continue
current_specs = []
for current_external in externals_config:
compiler = CompilerConfigFactory._spec_from_external_config(current_external)
if compiler:
current_specs.append(compiler)
compiler_specs.extend(current_specs)
return CompilerConfigFactory.from_specs(compiler_specs)
@staticmethod
def _spec_from_external_config(config):
# Allow `@x.y.z` instead of `@=x.y.z`
err_header = f"The external spec '{config['spec']}' cannot be used as a compiler"
# If extra_attributes is not there I might not want to use this entry as a compiler,
# therefore just leave a debug message, but don't be loud with a warning.
if _EXTRA_ATTRIBUTES_KEY not in config:
tty.debug(f"[{__file__}] {err_header}: missing the '{_EXTRA_ATTRIBUTES_KEY}' key")
return None
extra_attributes = config[_EXTRA_ATTRIBUTES_KEY]
result = spack.spec.Spec(
str(spack.spec.parse_with_version_concrete(config["spec"])),
external_modules=config.get("modules"),
)
result.extra_attributes = extra_attributes
return result
@staticmethod
def from_external_spec(spec: "spack.spec.Spec") -> Optional[dict]:
spec = spack.spec.parse_with_version_concrete(spec)
extra_attributes = getattr(spec, _EXTRA_ATTRIBUTES_KEY, None)
if extra_attributes is None:
return None
paths = CompilerConfigFactory._extract_compiler_paths(spec)
if paths is None:
return None
compiler_spec = spack.spec.CompilerSpec(
package_name_to_compiler_name.get(spec.name, spec.name), spec.version
)
operating_system, target = CompilerConfigFactory._extract_os_and_target(spec)
compiler_entry = {
"compiler": {
"spec": str(compiler_spec),
"paths": paths,
"flags": extra_attributes.get("flags", {}),
"operating_system": str(operating_system),
"target": str(target.family),
"modules": getattr(spec, "external_modules", []),
"environment": extra_attributes.get("environment", {}),
"extra_rpaths": extra_attributes.get("extra_rpaths", []),
"implicit_rpaths": extra_attributes.get("implicit_rpaths", None),
}
}
return compiler_entry
@staticmethod
def _extract_compiler_paths(spec: "spack.spec.Spec") -> Optional[Dict[str, str]]:
err_header = f"The external spec '{spec}' cannot be used as a compiler"
extra_attributes = spec.extra_attributes
# If I have 'extra_attributes' warn if 'compilers' is missing,
# or we don't have a C compiler
if _COMPILERS_KEY not in extra_attributes:
warnings.warn(
f"{err_header}: missing the '{_COMPILERS_KEY}' key under '{_EXTRA_ATTRIBUTES_KEY}'"
)
return None
attribute_compilers = extra_attributes[_COMPILERS_KEY]
if _C_KEY not in attribute_compilers:
warnings.warn(
f"{err_header}: missing the C compiler path under "
f"'{_EXTRA_ATTRIBUTES_KEY}:{_COMPILERS_KEY}'"
)
return None
c_compiler = attribute_compilers[_C_KEY]
# C++ and Fortran compilers are not mandatory, so let's just leave a debug trace
if _CXX_KEY not in attribute_compilers:
tty.debug(f"[{__file__}] The external spec {spec} does not have a C++ compiler")
if _FORTRAN_KEY not in attribute_compilers:
tty.debug(f"[{__file__}] The external spec {spec} does not have a Fortran compiler")
# compilers format has cc/fc/f77, externals format has "c/fortran"
return {
"cc": c_compiler,
"cxx": attribute_compilers.get(_CXX_KEY, None),
"fc": attribute_compilers.get(_FORTRAN_KEY, None),
"f77": attribute_compilers.get(_FORTRAN_KEY, None),
}
@staticmethod
def _extract_os_and_target(spec: "spack.spec.Spec"):
if not spec.architecture:
host_platform = spack.platforms.host()
operating_system = host_platform.operating_system("default_os")
target = host_platform.target("default_target")
else:
target = spec.architecture.target
if not target:
target = spack.platforms.host().target("default_target")
operating_system = spec.os
if not operating_system:
host_platform = spack.platforms.host()
operating_system = host_platform.operating_system("default_os")
return operating_system, target
class InvalidCompilerConfigurationError(spack.error.SpackError):
def __init__(self, compiler_spec):
super().__init__(
f'Invalid configuration for [compiler "{compiler_spec}"]: ',
f"Compiler configuration must contain entries for "
f"all compilers: {spack.compiler.PATH_INSTANCE_VARS}",
)
class UnknownCompilerError(spack.error.SpackError):
def __init__(self, compiler_name):
super().__init__("Spack doesn't support the requested compiler: {0}".format(compiler_name))
class NoCompilerForSpecError(spack.error.SpackError):
def __init__(self, compiler_spec, target):
super().__init__(
"No compilers for operating system %s satisfy spec %s" % (target, compiler_spec)
)

View File

@@ -1,343 +0,0 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
# import os
# import re
# import subprocess
# import tempfile
# from typing import Dict
#
# import archspec.cpu
#
# import spack.operating_systems.windows_os
# import spack.platforms
# import spack.util.executable
# from spack.compiler import Compiler
# from spack.version import Version, VersionRange
#
# FC_PATH: Dict[str, str] = dict()
#
#
# class CmdCall:
# """Compose a call to `cmd` for an ordered series of cmd commands/scripts"""
#
# def __init__(self, *cmds):
# if not cmds:
# raise RuntimeError(
# """Attempting to run commands from CMD without specifying commands.
# Please add commands to be run."""
# )
# self._cmds = cmds
#
# def __call__(self):
# out = subprocess.check_output(self.cmd_line, stderr=subprocess.STDOUT) # novermin
# return out.decode("utf-16le", errors="replace") # novermin
#
# @property
# def cmd_line(self):
# base_call = "cmd /u /c "
# commands = " && ".join([x.command_str() for x in self._cmds])
# # If multiple commands are being invoked by a single subshell
# # they must be encapsulated by a double quote. Always double
# # quote to be sure of proper handling
# # cmd will properly resolve nested double quotes as needed
# #
# # `set`` writes out the active env to the subshell stdout,
# # and in this context we are always trying to obtain env
# # state so it should always be appended
# return base_call + f'"{commands} && set"'
#
#
# class VarsInvocation:
# def __init__(self, script):
# self._script = script
#
# def command_str(self):
# return f'"{self._script}"'
#
# @property
# def script(self):
# return self._script
#
#
# class VCVarsInvocation(VarsInvocation):
# def __init__(self, script, arch, msvc_version):
# super(VCVarsInvocation, self).__init__(script)
# self._arch = arch
# self._msvc_version = msvc_version
#
# @property
# def sdk_ver(self):
# """Accessor for Windows SDK version property
#
# Note: This property may not be set by
# the calling context and as such this property will
# return an empty string
#
# This property will ONLY be set if the SDK package
# is a dependency somewhere in the Spack DAG of the package
# for which we are constructing an MSVC compiler env.
# Otherwise this property should be unset to allow the VCVARS
# script to use its internal heuristics to determine appropriate
# SDK version
# """
# if getattr(self, "_sdk_ver", None):
# return self._sdk_ver + ".0"
# return ""
#
# @sdk_ver.setter
# def sdk_ver(self, val):
# self._sdk_ver = val
#
# @property
# def arch(self):
# return self._arch
#
# @property
# def vcvars_ver(self):
# return f"-vcvars_ver={self._msvc_version}"
#
# def command_str(self):
# script = super(VCVarsInvocation, self).command_str()
# return f"{script} {self.arch} {self.sdk_ver} {self.vcvars_ver}"
#
#
# def get_valid_fortran_pth():
# """Assign maximum available fortran compiler version"""
# # TODO (johnwparent): validate compatibility w/ try compiler
# # functionality when added
# sort_fn = lambda fc_ver: Version(fc_ver)
# sort_fc_ver = sorted(list(FC_PATH.keys()), key=sort_fn)
# return FC_PATH[sort_fc_ver[-1]] if sort_fc_ver else None
#
#
# class Msvc(Compiler):
# #: Compiler argument that produces version information
# version_argument = ""
#
# # For getting ifx's version, call it with version_argument
# # and ignore the error code
# ignore_version_errors = [1]
#
# #: Regex used to extract version from compiler's output
# version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)"
# # The MSVC compiler class overrides this to prevent instances
# # of erroneous matching on executable names that cannot be msvc
# # compilers
# suffixes = []
#
# is_supported_on_platform = lambda x: isinstance(x, spack.platforms.Windows)
#
# def __init__(self, *args, **kwargs):
# # This positional argument "paths" is later parsed and process by the base class
# # via the call to `super` later in this method
# paths = args[3]
# latest_fc = get_valid_fortran_pth()
# new_pth = [pth if pth else latest_fc for pth in paths[2:]]
# paths[2:] = new_pth
# # Initialize, deferring to base class but then adding the vcvarsallfile
# # file based on compiler executable path.
# super().__init__(*args, **kwargs)
# # To use the MSVC compilers, VCVARS must be invoked
# # VCVARS is located at a fixed location, referencable
# # idiomatically by the following relative path from the
# # compiler.
# # Spack first finds the compilers via VSWHERE
# # and stores their path, but their respective VCVARS
# # file must be invoked before useage.
# env_cmds = []
# compiler_root = os.path.join(os.path.dirname(self.cc), "../../../../../..")
# vcvars_script_path = os.path.join(compiler_root, "Auxiliary", "Build", "vcvars64.bat")
# # get current platform architecture and format for vcvars argument
# arch = spack.platforms.real_host().default.lower()
# arch = arch.replace("-", "_")
# if str(archspec.cpu.host().family) == "x86_64":
# arch = "amd64"
#
# self.vcvars_call = VCVarsInvocation(vcvars_script_path, arch, self.msvc_version)
# env_cmds.append(self.vcvars_call)
# # Below is a check for a valid fortran path
# # paths has c, cxx, fc, and f77 paths in that order
# # paths[2] refers to the fc path and is a generic check
# # for a fortran compiler
# if paths[2]:
#
# def get_oneapi_root(pth: str):
# """From within a prefix known to be a oneAPI path
# determine the oneAPI root path from arbitrary point
# under root
#
# Args:
# pth: path prefixed within oneAPI root
# """
# if not pth:
# return ""
# while os.path.basename(pth) and os.path.basename(pth) != "oneAPI":
# pth = os.path.dirname(pth)
# return pth
#
# # If this found, it sets all the vars
# oneapi_root = get_oneapi_root(self.fc)
# if not oneapi_root:
# raise RuntimeError(f"Non-oneAPI Fortran compiler {self.fc} assigned to MSVC")
# oneapi_root_setvars = os.path.join(oneapi_root, "setvars.bat")
# # some oneAPI exes return a version more precise than their
# # install paths specify, so we determine path from
# # the install path rather than the fc executable itself
# numver = r"\d+\.\d+(?:\.\d+)?"
# pattern = f"((?:{numver})|(?:latest))"
# version_from_path = re.search(pattern, self.fc).group(1)
# oneapi_version_setvars = os.path.join(
# oneapi_root, "compiler", version_from_path, "env", "vars.bat"
# )
# # order matters here, the specific version env must be invoked first,
# # otherwise it will be ignored if the root setvars sets up the oneapi
# # env first
# env_cmds.extend(
# [VarsInvocation(oneapi_version_setvars), VarsInvocation(oneapi_root_setvars)]
# )
# self.msvc_compiler_environment = CmdCall(*env_cmds)
#
# @property
# def msvc_version(self):
# """This is the VCToolset version *NOT* the actual version of the cl compiler
# For CL version, query `Msvc.cl_version`"""
# return Version(re.search(Msvc.version_regex, self.cc).group(1))
#
# @property
# def short_msvc_version(self):
# """This is the shorthand VCToolset version of form
# MSVC<short-ver>
# """
# return "MSVC" + self.vc_toolset_ver
#
# @property
# def vc_toolset_ver(self):
# """
# The toolset version is the version of the combined set of cl and link
# This typically relates directly to VS version i.e. VS 2022 is v143
# VS 19 is v142, etc.
# This value is defined by the first three digits of the major + minor
# version of the VS toolset (143 for 14.3x.bbbbb). Traditionally the
# minor version has remained a static two digit number for a VS release
# series, however, as of VS22, this is no longer true, both
# 14.4x.bbbbb and 14.3x.bbbbb are considered valid VS22 VC toolset
# versions due to a change in toolset minor version sentiment.
#
# This is *NOT* the full version, for that see
# Msvc.msvc_version or MSVC.platform_toolset_ver for the
# raw platform toolset version
#
# """
# ver = self.msvc_version[:2].joined.string[:3]
# return ver
#
# @property
# def platform_toolset_ver(self):
# """
# This is the platform toolset version of current MSVC compiler
# i.e. 142. The platform toolset is the targeted MSVC library/compiler
# versions by compilation (this is different from the VC Toolset)
#
#
# This is different from the VC toolset version as established
# by `short_msvc_version`, but typically are represented by the same
# three digit value
# """
# # Typically VS toolset version and platform toolset versions match
# # VS22 introduces the first divergence of VS toolset version
# # (144 for "recent" releases) and platform toolset version (143)
# # so it needs additional handling until MS releases v144
# # (assuming v144 is also for VS22)
# # or adds better support for detection
# # TODO: (johnwparent) Update this logic for the next platform toolset
# # or VC toolset version update
# toolset_ver = self.vc_toolset_ver
# vs22_toolset = Version(toolset_ver) > Version("142")
# return toolset_ver if not vs22_toolset else "143"
#
# @property
# def visual_studio_version(self):
# """The four digit Visual Studio version (i.e. 2019 or 2022)
#
# Note: This differs from the msvc version or toolset version as
# those properties track the compiler and build tools version
# respectively, whereas this tracks the VS release associated
# with a given MSVC compiler.
# """
# return re.search(r"[0-9]{4}", self.cc).group(0)
#
# def _compiler_version(self, compiler):
# """Returns version object for given compiler"""
# # ignore_errors below is true here due to ifx's
# # non zero return code if it is not provided
# # and input file
# return Version(
# re.search(
# Msvc.version_regex,
# spack.build_systems.compiler.compiler_output(
# compiler, version_arg=None, ignore_errors=True
# ),
# ).group(1)
# )
#
# @property
# def cl_version(self):
# """Cl toolset version"""
# return self._compiler_version(self.cc)
#
# @property
# def ifx_version(self):
# """Ifx compiler version associated with this version of MSVC"""
# return self._compiler_version(self.fc)
#
# @property
# def vs_root(self):
# # The MSVC install root is located at a fix level above the compiler
# # and is referenceable idiomatically via the pattern below
# # this should be consistent accross versions
# return os.path.abspath(os.path.join(self.cc, "../../../../../../../.."))
#
# def setup_custom_environment(self, pkg, env):
# """Set environment variables for MSVC using the
# Microsoft-provided script."""
# # Set the build environment variables for spack. Just using
# # subprocess.call() doesn't work since that operates in its own
# # environment which is destroyed (along with the adjusted variables)
# # once the process terminates. So go the long way around: examine
# # output, sort into dictionary, use that to make the build
# # environment.
#
# # vcvars can target specific sdk versions, force it to pick up concretized sdk
# # version, if needed by spec
# if pkg.name != "win-sdk" and "win-sdk" in pkg.spec:
# self.vcvars_call.sdk_ver = pkg.spec["win-sdk"].version.string
#
# out = self.msvc_compiler_environment()
# int_env = dict(
# (key, value)
# for key, _, value in (line.partition("=") for line in out.splitlines())
# if key and value
# )
#
# for env_var in int_env:
# if os.pathsep not in int_env[env_var]:
# env.set(env_var, int_env[env_var])
# else:
# env.set_path(env_var, int_env[env_var].split(os.pathsep))
#
# # certain versions of ifx (2021.3.0:2023.1.0) do not play well with env:TMP
# # that has a "." character in the path
# # Work around by pointing tmp to the stage for the duration of the build
# if self.fc and Version(self.fc_version(self.fc)).satisfies(
# VersionRange("2021.3.0", "2023.1.0")
# ):
# new_tmp = tempfile.mkdtemp(dir=pkg.stage.path)
# env.set("TMP", new_tmp)
#
# env.set("CC", self.cc)
# env.set("CXX", self.cxx)
# env.set("FC", self.fc)
# env.set("F77", self.f77)

View File

@@ -1,212 +0,0 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import enum
import typing
from typing import Dict, List
from llnl.util import lang
from .libraries import CompilerPropertyDetector
if typing.TYPE_CHECKING:
import spack.spec
class Languages(enum.Enum):
C = "c"
CXX = "cxx"
FORTRAN = "fortran"
class CompilerAdaptor:
def __init__(
self, compiled_spec: "spack.spec.Spec", compilers: Dict[Languages, "spack.spec.Spec"]
) -> None:
if not compilers:
raise AttributeError(f"{compiled_spec} has no 'compiler' attribute")
self.compilers = compilers
self.compiled_spec = compiled_spec
def _lang_exists_or_raise(self, name: str, *, lang: Languages) -> None:
if lang not in self.compilers:
raise AttributeError(
f"'{self.compiled_spec}' has no {lang.value} compiler, so the "
f"'{name}' property cannot be retrieved"
)
def _maybe_return_attribute(self, name: str, *, lang: Languages) -> str:
self._lang_exists_or_raise(name, lang=lang)
return getattr(self.compilers[lang].package, name)
@property
def cc_rpath_arg(self) -> str:
self._lang_exists_or_raise("cc_rpath_arg", lang=Languages.C)
return self.compilers[Languages.C].package.rpath_arg
@property
def cxx_rpath_arg(self) -> str:
self._lang_exists_or_raise("cxx_rpath_arg", lang=Languages.CXX)
return self.compilers[Languages.CXX].package.rpath_arg
@property
def fc_rpath_arg(self) -> str:
self._lang_exists_or_raise("fc_rpath_arg", lang=Languages.FORTRAN)
return self.compilers[Languages.FORTRAN].package.rpath_arg
@property
def f77_rpath_arg(self) -> str:
self._lang_exists_or_raise("f77_rpath_arg", lang=Languages.FORTRAN)
return self.compilers[Languages.FORTRAN].package.rpath_arg
@property
def linker_arg(self) -> str:
return self._maybe_return_attribute("linker_arg", lang=Languages.C)
@property
def name(self):
return next(iter(self.compilers.values())).name
@property
def version(self):
return next(iter(self.compilers.values())).version
def implicit_rpaths(self) -> List[str]:
result, seen = [], set()
for compiler in self.compilers.values():
if compiler in seen:
continue
seen.add(compiler)
result.extend(CompilerPropertyDetector(compiler).implicit_rpaths())
return result
@property
def openmp_flag(self) -> str:
return next(iter(self.compilers.values())).package.openmp_flag
@property
def cxx98_flag(self) -> str:
return self.compilers[Languages.CXX].package.standard_flag(
language=Languages.CXX.value, standard="98"
)
@property
def cxx11_flag(self) -> str:
return self.compilers[Languages.CXX].package.standard_flag(
language=Languages.CXX.value, standard="11"
)
@property
def cxx14_flag(self) -> str:
return self.compilers[Languages.CXX].package.standard_flag(
language=Languages.CXX.value, standard="14"
)
@property
def cxx17_flag(self) -> str:
return self.compilers[Languages.CXX].package.standard_flag(
language=Languages.CXX.value, standard="17"
)
@property
def cxx20_flag(self) -> str:
return self.compilers[Languages.CXX].package.standard_flag(
language=Languages.CXX.value, standard="20"
)
@property
def cxx23_flag(self) -> str:
return self.compilers[Languages.CXX].package.standard_flag(
language=Languages.CXX.value, standard="23"
)
@property
def c99_flag(self) -> str:
return self.compilers[Languages.C].package.standard_flag(
language=Languages.C.value, standard="99"
)
@property
def c11_flag(self) -> str:
return self.compilers[Languages.C].package.standard_flag(
language=Languages.C.value, standard="11"
)
@property
def c17_flag(self) -> str:
return self.compilers[Languages.C].package.standard_flag(
language=Languages.C.value, standard="17"
)
@property
def c23_flag(self) -> str:
return self.compilers[Languages.C].package.standard_flag(
language=Languages.C.value, standard="17"
)
@property
def cc_pic_flag(self) -> str:
self._lang_exists_or_raise("cc_pic_flag", lang=Languages.C)
return self.compilers[Languages.C].package.pic_flag
@property
def cxx_pic_flag(self) -> str:
self._lang_exists_or_raise("cxx_pic_flag", lang=Languages.CXX)
return self.compilers[Languages.CXX].package.pic_flag
@property
def fc_pic_flag(self) -> str:
self._lang_exists_or_raise("fc_pic_flag", lang=Languages.FORTRAN)
return self.compilers[Languages.FORTRAN].package.pic_flag
@property
def f77_pic_flag(self) -> str:
self._lang_exists_or_raise("f77_pic_flag", lang=Languages.FORTRAN)
return self.compilers[Languages.FORTRAN].package.pic_flag
@property
def prefix(self) -> str:
return next(iter(self.compilers.values())).prefix
@property
def extra_rpaths(self) -> List[str]:
compiler = next(iter(self.compilers.values()))
return getattr(compiler, "extra_attributes", {}).get("extra_rpaths", [])
@property
def cc(self):
return self._maybe_return_attribute("cc", lang=Languages.C)
@property
def cxx(self):
return self._maybe_return_attribute("cxx", lang=Languages.CXX)
@property
def fc(self):
self._lang_exists_or_raise("fc", lang=Languages.FORTRAN)
return self.compilers[Languages.FORTRAN].package.fortran
@property
def f77(self):
self._lang_exists_or_raise("f77", lang=Languages.FORTRAN)
return self.compilers[Languages.FORTRAN].package.fortran
class DeprecatedCompiler(lang.DeprecatedProperty):
def __init__(self) -> None:
super().__init__(name="compiler")
def factory(self, instance, owner) -> CompilerAdaptor:
spec = instance.spec
if not spec.concrete:
raise ValueError("Can only get a compiler for a concrete package.")
compilers = {}
for language in Languages:
deps = spec.dependencies(virtuals=[language.value])
if deps:
compilers[language] = deps[0]
return CompilerAdaptor(instance, compilers)

View File

@@ -0,0 +1,120 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import re
import llnl.util.lang
from spack.compiler import Compiler
from spack.version import ver
class Aocc(Compiler):
version_argument = "--version"
@property
def debug_flags(self):
return [
"-gcodeview",
"-gdwarf-2",
"-gdwarf-3",
"-gdwarf-4",
"-gdwarf-5",
"-gline-tables-only",
"-gmodules",
"-g",
]
@property
def opt_flags(self):
return ["-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os", "-Oz", "-Og", "-O", "-O4"]
@property
def link_paths(self):
link_paths = {
"cc": os.path.join("aocc", "clang"),
"cxx": os.path.join("aocc", "clang++"),
"f77": os.path.join("aocc", "flang"),
"fc": os.path.join("aocc", "flang"),
}
return link_paths
@property
def verbose_flag(self):
return "-v"
@property
def openmp_flag(self):
return "-fopenmp"
@property
def cxx11_flag(self):
return "-std=c++11"
@property
def cxx14_flag(self):
return "-std=c++14"
@property
def cxx17_flag(self):
return "-std=c++17"
@property
def c99_flag(self):
return "-std=c99"
@property
def c11_flag(self):
return "-std=c11"
@property
def cc_pic_flag(self):
return "-fPIC"
@property
def cxx_pic_flag(self):
return "-fPIC"
@property
def f77_pic_flag(self):
return "-fPIC"
@property
def fc_pic_flag(self):
return "-fPIC"
required_libs = ["libclang"]
@classmethod
@llnl.util.lang.memoized
def extract_version_from_output(cls, output):
match = re.search(r"AOCC_(\d+)[._](\d+)[._](\d+)", output)
if match:
return ".".join(match.groups())
return "unknown"
@property
def stdcxx_libs(self):
return ("-lstdc++",)
@property
def cflags(self):
return self._handle_default_flag_addtions()
@property
def cxxflags(self):
return self._handle_default_flag_addtions()
@property
def fflags(self):
return self._handle_default_flag_addtions()
def _handle_default_flag_addtions(self):
# This is a known issue for AOCC 3.0 see:
# https://developer.amd.com/wp-content/resources/AOCC-3.0-Install-Guide.pdf
if self.version.satisfies(ver("3.0.0")):
return "-Wno-unused-command-line-argument " "-mllvm -eliminate-similar-expr=false"

View File

@@ -0,0 +1,116 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import re
import llnl.util.lang
import spack.compiler
import spack.compilers.clang
from spack.version import Version
class AppleClang(spack.compilers.clang.Clang):
openmp_flag = "-Xpreprocessor -fopenmp"
@classmethod
@llnl.util.lang.memoized
def extract_version_from_output(cls, output):
ver = "unknown"
match = re.search(
# Apple's LLVM compiler has its own versions, so suffix them.
r"^Apple (?:LLVM|clang) version ([^ )]+)",
output,
# Multi-line, since 'Apple clang' may not be on the first line
# in particular, when run as gcc, it seems to output
# "Configured with: --prefix=..." as the first line
re.M,
)
if match:
ver = match.group(match.lastindex)
return ver
# C++ flags based on CMake Modules/Compiler/AppleClang-CXX.cmake
@property
def cxx11_flag(self):
# Spack's AppleClang detection only valid from Xcode >= 4.6
if self.real_version < Version("4.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++11 standard", "cxx11_flag", "Xcode < 4.0"
)
return "-std=c++11"
@property
def cxx14_flag(self):
if self.real_version < Version("5.1"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++14 standard", "cxx14_flag", "Xcode < 5.1"
)
elif self.real_version < Version("6.1"):
return "-std=c++1y"
return "-std=c++14"
@property
def cxx17_flag(self):
if self.real_version < Version("6.1"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++17 standard", "cxx17_flag", "Xcode < 6.1"
)
elif self.real_version < Version("10.0"):
return "-std=c++1z"
return "-std=c++17"
@property
def cxx20_flag(self):
if self.real_version < Version("10.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++20 standard", "cxx20_flag", "Xcode < 10.0"
)
elif self.real_version < Version("13.0"):
return "-std=c++2a"
return "-std=c++20"
@property
def cxx23_flag(self):
if self.real_version < Version("13.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++23 standard", "cxx23_flag", "Xcode < 13.0"
)
return "-std=c++2b"
# C flags based on CMake Modules/Compiler/AppleClang-C.cmake
@property
def c99_flag(self):
if self.real_version < Version("4.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C99 standard", "c99_flag", "< 4.0"
)
return "-std=c99"
@property
def c11_flag(self):
if self.real_version < Version("4.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C11 standard", "c11_flag", "< 4.0"
)
return "-std=c11"
@property
def c17_flag(self):
if self.real_version < Version("11.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C17 standard", "c17_flag", "< 11.0"
)
return "-std=c17"
@property
def c23_flag(self):
if self.real_version < Version("11.0.3"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C23 standard", "c23_flag", "< 11.0.3"
)
return "-std=c2x"

View File

@@ -0,0 +1,80 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import spack.compiler
class Arm(spack.compiler.Compiler):
# Named wrapper links within lib/spack/env
link_paths = {
"cc": os.path.join("arm", "armclang"),
"cxx": os.path.join("arm", "armclang++"),
"f77": os.path.join("arm", "armflang"),
"fc": os.path.join("arm", "armflang"),
}
# The ``--version`` option seems to be the most consistent one for
# arm compilers. Output looks like this:
#
# $ arm<c/f>lang --version
# Arm C/C++/Fortran Compiler version 19.0 (build number 73) (based on LLVM 7.0.2)
# Target: aarch64--linux-gnu
# Thread model: posix
# InstalledDir:
# /opt/arm/arm-hpc-compiler-19.0_Generic-AArch64_RHEL-7_aarch64-linux/bin
version_argument = "--version"
version_regex = r"Arm C\/C\+\+\/Fortran Compiler version ([\d\.]+) "
@property
def verbose_flag(self):
return "-v"
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast"]
@property
def openmp_flag(self):
return "-fopenmp"
@property
def cxx11_flag(self):
return "-std=c++11"
@property
def cxx14_flag(self):
return "-std=c++14"
@property
def cxx17_flag(self):
return "-std=c++1z"
@property
def c99_flag(self):
return "-std=c99"
@property
def c11_flag(self):
return "-std=c11"
@property
def cc_pic_flag(self):
return "-fPIC"
@property
def cxx_pic_flag(self):
return "-fPIC"
@property
def f77_pic_flag(self):
return "-fPIC"
@property
def fc_pic_flag(self):
return "-fPIC"
required_libs = ["libclang", "libflang"]

View File

@@ -0,0 +1,128 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
from spack.compiler import Compiler, UnsupportedCompilerFlag
from spack.version import Version
class Cce(Compiler):
"""Cray compiler environment compiler."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# For old cray compilers on module based systems we replace
# ``version_argument`` with the old value. Cannot be a property
# as the new value is used in classmethods for path-based detection
if not self.is_clang_based:
self.version_argument = "-V"
# MacPorts builds gcc versions with prefixes and -mp-X.Y suffixes.
suffixes = [r"-mp-\d\.\d"]
@property
def link_paths(self):
if any("PrgEnv-cray" in m for m in self.modules):
# Old module-based interface to cray compilers
return {
"cc": os.path.join("cce", "cc"),
"cxx": os.path.join("case-insensitive", "CC"),
"f77": os.path.join("cce", "ftn"),
"fc": os.path.join("cce", "ftn"),
}
return {
"cc": os.path.join("cce", "craycc"),
"cxx": os.path.join("cce", "case-insensitive", "crayCC"),
"f77": os.path.join("cce", "crayftn"),
"fc": os.path.join("cce", "crayftn"),
}
@property
def is_clang_based(self):
version = self._real_version or self.version
return version >= Version("9.0") and "classic" not in str(version)
version_argument = "--version"
version_regex = r"[Cc]ray (?:clang|C :|C\+\+ :|Fortran :) [Vv]ersion.*?(\d+(\.\d+)+)"
@property
def verbose_flag(self):
return "-v"
@property
def debug_flags(self):
return ["-g", "-G0", "-G1", "-G2", "-Gfast"]
@property
def openmp_flag(self):
if self.is_clang_based:
return "-fopenmp"
return "-h omp"
@property
def cxx11_flag(self):
if self.is_clang_based:
return "-std=c++11"
return "-h std=c++11"
@property
def cxx14_flag(self):
if self.is_clang_based:
return "-std=c++14"
return "-h std=c++14"
@property
def cxx17_flag(self):
if self.is_clang_based:
return "-std=c++17"
@property
def c99_flag(self):
if self.is_clang_based:
return "-std=c99"
elif self.real_version >= Version("8.4"):
return "-h std=c99,noconform,gnu"
elif self.real_version >= Version("8.1"):
return "-h c99,noconform,gnu"
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 8.1")
@property
def c11_flag(self):
if self.is_clang_based:
return "-std=c11"
elif self.real_version >= Version("8.5"):
return "-h std=c11,noconform,gnu"
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 8.5")
@property
def cc_pic_flag(self):
if self.is_clang_based:
return "-fPIC"
return "-h PIC"
@property
def cxx_pic_flag(self):
if self.is_clang_based:
return "-fPIC"
return "-h PIC"
@property
def f77_pic_flag(self):
if self.is_clang_based:
return "-fPIC"
return "-h PIC"
@property
def fc_pic_flag(self):
if self.is_clang_based:
return "-fPIC"
return "-h PIC"
@property
def stdcxx_libs(self):
# Cray compiler wrappers link to the standard C++ library
# without additional flags.
return ()

View File

@@ -0,0 +1,192 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import re
import llnl.util.lang
from spack.compiler import Compiler, UnsupportedCompilerFlag
from spack.version import Version
#: compiler symlink mappings for mixed f77 compilers
f77_mapping = [
("gfortran", os.path.join("clang", "gfortran")),
("xlf_r", os.path.join("xl_r", "xlf_r")),
("xlf", os.path.join("xl", "xlf")),
("ifort", os.path.join("intel", "ifort")),
]
#: compiler symlink mappings for mixed f90/fc compilers
fc_mapping = [
("gfortran", os.path.join("clang", "gfortran")),
("xlf90_r", os.path.join("xl_r", "xlf90_r")),
("xlf90", os.path.join("xl", "xlf90")),
("ifort", os.path.join("intel", "ifort")),
]
class Clang(Compiler):
version_argument = "--version"
@property
def debug_flags(self):
return [
"-gcodeview",
"-gdwarf-2",
"-gdwarf-3",
"-gdwarf-4",
"-gdwarf-5",
"-gline-tables-only",
"-gmodules",
"-g",
]
@property
def opt_flags(self):
return ["-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os", "-Oz", "-Og", "-O", "-O4"]
# Clang has support for using different fortran compilers with the
# clang executable.
@property
def link_paths(self):
# clang links are always the same
link_paths = {
"cc": os.path.join("clang", "clang"),
"cxx": os.path.join("clang", "clang++"),
}
# fortran links need to look at the actual compiler names from
# compilers.yaml to figure out which named symlink to use
for compiler_name, link_path in f77_mapping:
if self.f77 and compiler_name in self.f77:
link_paths["f77"] = link_path
break
else:
link_paths["f77"] = os.path.join("clang", "flang")
for compiler_name, link_path in fc_mapping:
if self.fc and compiler_name in self.fc:
link_paths["fc"] = link_path
break
else:
link_paths["fc"] = os.path.join("clang", "flang")
return link_paths
@property
def verbose_flag(self):
return "-v"
openmp_flag = "-fopenmp"
# C++ flags based on CMake Modules/Compiler/Clang.cmake
@property
def cxx11_flag(self):
if self.real_version < Version("3.3"):
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 3.3")
return "-std=c++11"
@property
def cxx14_flag(self):
if self.real_version < Version("3.4"):
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 3.5")
elif self.real_version < Version("3.5"):
return "-std=c++1y"
return "-std=c++14"
@property
def cxx17_flag(self):
if self.real_version < Version("3.5"):
raise UnsupportedCompilerFlag(self, "the C++17 standard", "cxx17_flag", "< 3.5")
elif self.real_version < Version("5.0"):
return "-std=c++1z"
return "-std=c++17"
@property
def cxx20_flag(self):
if self.real_version < Version("5.0"):
raise UnsupportedCompilerFlag(self, "the C++20 standard", "cxx20_flag", "< 5.0")
elif self.real_version < Version("11.0"):
return "-std=c++2a"
else:
return "-std=c++20"
@property
def cxx23_flag(self):
if self.real_version < Version("12.0"):
raise UnsupportedCompilerFlag(self, "the C++23 standard", "cxx23_flag", "< 12.0")
elif self.real_version < Version("17.0"):
return "-std=c++2b"
else:
return "-std=c++23"
@property
def c99_flag(self):
return "-std=c99"
@property
def c11_flag(self):
if self.real_version < Version("3.0"):
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 3.0")
if self.real_version < Version("3.1"):
return "-std=c1x"
return "-std=c11"
@property
def c17_flag(self):
if self.real_version < Version("6.0"):
raise UnsupportedCompilerFlag(self, "the C17 standard", "c17_flag", "< 6.0")
return "-std=c17"
@property
def c23_flag(self):
if self.real_version < Version("9.0"):
raise UnsupportedCompilerFlag(self, "the C23 standard", "c23_flag", "< 9.0")
elif self.real_version < Version("18.0"):
return "-std=c2x"
else:
return "-std=c23"
@property
def cc_pic_flag(self):
return "-fPIC"
@property
def cxx_pic_flag(self):
return "-fPIC"
@property
def f77_pic_flag(self):
return "-fPIC"
@property
def fc_pic_flag(self):
return "-fPIC"
required_libs = ["libclang"]
@classmethod
@llnl.util.lang.memoized
def extract_version_from_output(cls, output):
ver = "unknown"
if ("Apple" in output) or ("AMD" in output):
return ver
match = re.search(
# Normal clang compiler versions are left as-is
r"(?:clang|flang-new) version ([^ )\n]+)-svn[~.\w\d-]*|"
# Don't include hyphenated patch numbers in the version
# (see https://github.com/spack/spack/pull/14365 for details)
r"(?:clang|flang-new) version ([^ )\n]+?)-[~.\w\d-]*|"
r"(?:clang|flang-new) version ([^ )\n]+)",
output,
)
if match:
ver = match.group(match.lastindex)
return ver

View File

@@ -1,427 +0,0 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""This module contains functions related to finding compilers on the system,
and configuring Spack to use multiple compilers.
"""
import os
import re
import sys
import warnings
from typing import Any, Dict, List, Optional, Tuple
import archspec.cpu
import llnl.util.filesystem as fs
import llnl.util.lang
import llnl.util.tty as tty
import spack.config
import spack.detection
import spack.error
import spack.platforms
import spack.repo
import spack.spec
from spack.operating_systems import windows_os
from spack.util.environment import get_path
package_name_to_compiler_name = {
"llvm": "clang",
"intel-oneapi-compilers": "oneapi",
"llvm-amdgpu": "rocmcc",
"intel-oneapi-compilers-classic": "intel",
"acfl": "arm",
}
#: Tag used to identify packages providing a compiler
COMPILER_TAG = "compiler"
def compiler_config_files():
config_files = []
configuration = spack.config.CONFIG
for scope in configuration.writable_scopes:
name = scope.name
from_packages_yaml = CompilerFactory.from_packages_yaml(configuration, scope=name)
if from_packages_yaml:
config_files.append(configuration.get_config_filename(name, "packages"))
compiler_config = configuration.get("compilers", scope=name)
if compiler_config:
config_files.append(configuration.get_config_filename(name, "compilers"))
return config_files
def add_compiler_to_config(new_compilers, *, scope=None) -> None:
"""Add a Compiler object to the configuration, at the required scope."""
# FIXME (compiler as nodes): still needed to read Cray manifest
by_name: Dict[str, List["spack.spec.Spec"]] = {}
for x in new_compilers:
by_name.setdefault(x.name, []).append(x)
spack.detection.update_configuration(by_name, buildable=True, scope=scope)
def find_compilers(
path_hints: Optional[List[str]] = None,
*,
scope: Optional[str] = None,
max_workers: Optional[int] = None,
) -> List["spack.spec.Spec"]:
"""Searches for compiler in the paths given as argument. If any new compiler is found, the
configuration is updated, and the list of new compiler objects is returned.
Args:
path_hints: list of path hints where to look for. A sensible default based on the ``PATH``
environment variable will be used if the value is None
scope: configuration scope to modify
max_workers: number of processes used to search for compilers
"""
if path_hints is None:
path_hints = get_path("PATH")
default_paths = fs.search_paths_for_executables(*path_hints)
if sys.platform == "win32":
default_paths.extend(windows_os.WindowsOs().compiler_search_paths)
compiler_pkgs = spack.repo.PATH.packages_with_tags(COMPILER_TAG, full=True)
detected_packages = spack.detection.by_path(
compiler_pkgs, path_hints=default_paths, max_workers=max_workers
)
new_compilers = spack.detection.update_configuration(
detected_packages, buildable=True, scope=scope
)
return new_compilers
def select_new_compilers(
candidates: List["spack.spec.Spec"], *, scope: Optional[str] = None
) -> List["spack.spec.Spec"]:
"""Given a list of compilers, remove those that are already defined in
the configuration.
"""
compilers_in_config = all_compilers_from(configuration=spack.config.CONFIG, scope=scope)
return [c for c in candidates if c not in compilers_in_config]
def supported_compilers() -> List[str]:
"""Returns all the currently supported compiler packages"""
return sorted(spack.repo.PATH.packages_with_tags(COMPILER_TAG))
def all_compilers(
scope: Optional[str] = None, init_config: bool = True
) -> List["spack.spec.Spec"]:
"""Returns all the compilers from the current global configuration.
Args:
scope: configuration scope from which to extract the compilers. If None, the merged
configuration is used.
init_config: if True, search for compilers if none is found in configuration.
"""
compilers = all_compilers_from(configuration=spack.config.CONFIG, scope=scope)
if not compilers and init_config:
find_compilers(scope=scope)
compilers = all_compilers_from(configuration=spack.config.CONFIG, scope=scope)
return compilers
def all_compilers_from(
configuration: "spack.config.ConfigurationType", scope: Optional[str] = None
) -> List["spack.spec.Spec"]:
"""Returns all the compilers from the current global configuration.
Args:
configuration: configuration to be queried
scope: configuration scope from which to extract the compilers. If None, the merged
configuration is used.
"""
compilers = CompilerFactory.from_packages_yaml(configuration, scope=scope)
if os.environ.get("SPACK_EXPERIMENTAL_DEPRECATE_COMPILERS_YAML") != "1":
legacy_compilers = CompilerFactory.from_compilers_yaml(configuration, scope=scope)
if legacy_compilers:
# FIXME (compiler as nodes): write how to update the file. Maybe an ad-hoc command
warnings.warn(
"Some compilers are still defined in 'compilers.yaml', which has been deprecated "
"in v0.23. Those configuration files will be ignored from Spack v0.25.\n"
)
for legacy in legacy_compilers:
if not any(c.satisfies(f"{legacy.name}@{legacy.versions}") for c in compilers):
compilers.append(legacy)
return compilers
class CompilerRemover:
"""Removes compiler from configuration."""
def __init__(self, configuration: "spack.config.ConfigurationType") -> None:
self.configuration = configuration
self.marked_packages_yaml: List[Tuple[str, Any]] = []
self.marked_compilers_yaml: List[Tuple[str, Any]] = []
def mark_compilers(
self, *, match: str, scope: Optional[str] = None
) -> List["spack.spec.Spec"]:
"""Marks compilers to be removed in configuration, and returns a corresponding list
of specs.
Args:
match: constraint that the compiler must match to be removed.
scope: scope where to remove the compiler. If None, all writeable scopes are checked.
"""
self.marked_packages_yaml = []
self.marked_compilers_yaml = []
candidate_scopes = [scope]
if scope is None:
candidate_scopes = [x.name for x in self.configuration.writable_scopes]
all_removals = self._mark_in_packages_yaml(match, candidate_scopes)
all_removals.extend(self._mark_in_compilers_yaml(match, candidate_scopes))
return all_removals
def _mark_in_packages_yaml(self, match, candidate_scopes):
compiler_package_names = supported_compilers()
all_removals = []
for current_scope in candidate_scopes:
packages_yaml = self.configuration.get("packages", scope=current_scope)
if not packages_yaml:
continue
removed_from_scope = []
for name, entry in packages_yaml.items():
if name not in compiler_package_names:
continue
externals_config = entry.get("externals", None)
if not externals_config:
continue
def _partition_match(external_yaml):
s = CompilerFactory.from_external_yaml(external_yaml)
return not s.satisfies(match)
to_keep, to_remove = llnl.util.lang.stable_partition(
externals_config, _partition_match
)
if not to_remove:
continue
removed_from_scope.extend(to_remove)
entry["externals"] = to_keep
if not removed_from_scope:
continue
self.marked_packages_yaml.append((current_scope, packages_yaml))
all_removals.extend(
[CompilerFactory.from_external_yaml(x) for x in removed_from_scope]
)
return all_removals
def _mark_in_compilers_yaml(self, match, candidate_scopes):
if os.environ.get("SPACK_EXPERIMENTAL_DEPRECATE_COMPILERS_YAML") == "1":
return []
all_removals = []
for current_scope in candidate_scopes:
compilers_yaml = self.configuration.get("compilers", scope=current_scope)
if not compilers_yaml:
continue
def _partition_match(entry):
external_specs = CompilerFactory.from_legacy_yaml(entry["compiler"])
return not any(x.satisfies(match) for x in external_specs)
to_keep, to_remove = llnl.util.lang.stable_partition(compilers_yaml, _partition_match)
if not to_remove:
continue
compilers_yaml[:] = to_keep
self.marked_compilers_yaml.append((current_scope, compilers_yaml))
for entry in to_remove:
all_removals.extend(CompilerFactory.from_legacy_yaml(entry["compiler"]))
return all_removals
def flush(self):
"""Removes from configuration the specs that have been marked by the previous call
of ``remove_compilers``.
"""
for scope, packages_yaml in self.marked_packages_yaml:
self.configuration.set("packages", packages_yaml, scope=scope)
for scope, compilers_yaml in self.marked_compilers_yaml:
self.configuration.set("compilers", compilers_yaml, scope=scope)
def compilers_for_spec(compiler_spec, *, arch_spec=None, scope=None, init_config=True):
"""This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found.
"""
# FIXME (compiler as nodes): to be removed, or reimplemented
raise NotImplementedError("still to be implemented")
def compilers_for_arch(
arch_spec: "spack.spec.ArchSpec", *, scope: Optional[str] = None
) -> List["spack.spec.Spec"]:
"""Returns the compilers that can be used on the input architecture"""
compilers = all_compilers_from(spack.config.CONFIG, scope=scope)
query = f"platform={arch_spec.platform} target=:{arch_spec.target}"
return [x for x in compilers if x.satisfies(query)]
def class_for_compiler_name(compiler_name):
"""Given a compiler module name, get the corresponding Compiler class."""
# FIXME (compiler as nodes): to be removed, or reimplemented
raise NotImplementedError("still to be implemented")
_EXTRA_ATTRIBUTES_KEY = "extra_attributes"
_COMPILERS_KEY = "compilers"
_C_KEY = "c"
_CXX_KEY, _FORTRAN_KEY = "cxx", "fortran"
def name_os_target(spec: "spack.spec.Spec") -> Tuple[str, str, str]:
if not spec.architecture:
host_platform = spack.platforms.host()
operating_system = host_platform.operating_system("default_os")
target = host_platform.target("default_target")
else:
target = spec.architecture.target
if not target:
target = spack.platforms.host().target("default_target")
target = target
operating_system = spec.os
if not operating_system:
host_platform = spack.platforms.host()
operating_system = host_platform.operating_system("default_os")
return spec.name, str(operating_system), str(target)
class CompilerFactory:
"""Class aggregating all ways of constructing a list of compiler specs from config entries."""
_PACKAGES_YAML_CACHE: Dict[str, Optional["spack.spec.Spec"]] = {}
_COMPILERS_YAML_CACHE: Dict[str, List["spack.spec.Spec"]] = {}
_GENERIC_TARGET = None
@staticmethod
def from_packages_yaml(
configuration: "spack.config.ConfigurationType", *, scope: Optional[str] = None
) -> List["spack.spec.Spec"]:
"""Returns the compiler specs defined in the "packages" section of the configuration"""
compilers = []
compiler_package_names = supported_compilers()
packages_yaml = configuration.get("packages", scope=scope)
for name, entry in packages_yaml.items():
if name not in compiler_package_names:
continue
externals_config = entry.get("externals", None)
if not externals_config:
continue
compiler_specs = []
for current_external in externals_config:
key = str(current_external)
if key not in CompilerFactory._PACKAGES_YAML_CACHE:
CompilerFactory._PACKAGES_YAML_CACHE[key] = CompilerFactory.from_external_yaml(
current_external
)
compiler = CompilerFactory._PACKAGES_YAML_CACHE[key]
if compiler:
compiler_specs.append(compiler)
compilers.extend(compiler_specs)
return compilers
@staticmethod
def from_external_yaml(config: Dict[str, Any]) -> Optional["spack.spec.Spec"]:
"""Returns a compiler spec from an external definition from packages.yaml."""
# Allow `@x.y.z` instead of `@=x.y.z`
err_header = f"The external spec '{config['spec']}' cannot be used as a compiler"
# If extra_attributes is not there I might not want to use this entry as a compiler,
# therefore just leave a debug message, but don't be loud with a warning.
if _EXTRA_ATTRIBUTES_KEY not in config:
tty.debug(f"[{__file__}] {err_header}: missing the '{_EXTRA_ATTRIBUTES_KEY}' key")
return None
extra_attributes = config[_EXTRA_ATTRIBUTES_KEY]
result = spack.spec.Spec(
str(spack.spec.parse_with_version_concrete(config["spec"])),
external_path=config.get("prefix"),
external_modules=config.get("modules"),
)
result.extra_attributes = extra_attributes
CompilerFactory._finalize_external_concretization(result)
return result
@staticmethod
def _finalize_external_concretization(abstract_spec):
if CompilerFactory._GENERIC_TARGET is None:
CompilerFactory._GENERIC_TARGET = archspec.cpu.host().family
if abstract_spec.architecture:
abstract_spec.architecture.complete_with_defaults()
else:
abstract_spec.constrain(spack.spec.Spec.default_arch())
abstract_spec.architecture.target = CompilerFactory._GENERIC_TARGET
abstract_spec._finalize_concretization()
@staticmethod
def from_legacy_yaml(compiler_dict: Dict[str, Any]) -> List["spack.spec.Spec"]:
"""Returns a list of external specs, corresponding to a compiler entry
from compilers.yaml.
"""
from spack.detection.path import ExecutablesFinder
# FIXME (compiler as nodes): should we look at targets too?
result = []
candidate_paths = [x for x in compiler_dict["paths"].values() if x is not None]
finder = ExecutablesFinder()
for pkg_name in spack.repo.PATH.packages_with_tags("compiler"):
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
pattern = re.compile(r"|".join(finder.search_patterns(pkg=pkg_cls)))
filtered_paths = [x for x in candidate_paths if pattern.search(os.path.basename(x))]
detected = finder.detect_specs(pkg=pkg_cls, paths=filtered_paths)
result.extend(detected)
for item in result:
CompilerFactory._finalize_external_concretization(item)
return result
@staticmethod
def from_compilers_yaml(
configuration: "spack.config.ConfigurationType", *, scope: Optional[str] = None
) -> List["spack.spec.Spec"]:
"""Returns the compiler specs defined in the "compilers" section of the configuration"""
result: List["spack.spec.Spec"] = []
for item in configuration.get("compilers", scope=scope):
key = str(item)
if key not in CompilerFactory._COMPILERS_YAML_CACHE:
CompilerFactory._COMPILERS_YAML_CACHE[key] = CompilerFactory.from_legacy_yaml(
item["compiler"]
)
result.extend(CompilerFactory._COMPILERS_YAML_CACHE[key])
return result
class UnknownCompilerError(spack.error.SpackError):
def __init__(self, compiler_name):
super().__init__(f"Spack doesn't support the requested compiler: {compiler_name}")

View File

@@ -1,23 +0,0 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from ..error import SpackError
class CompilerAccessError(SpackError):
def __init__(self, compiler, paths):
super().__init__(
f"Compiler '{compiler.spec}' has executables that are missing"
f" or are not executable: {paths}"
)
class UnsupportedCompilerFlag(SpackError):
def __init__(self, compiler, feature, flag_name, ver_string=None):
super().__init__(
f"{compiler.name} ({ver_string if ver_string else compiler.version}) does not support"
f" {feature} (as compiler.{flag_name}). If you think it should, please edit the "
f"compiler.{compiler.name} subclass to implement the {flag_name} property and submit "
f"a pull request or issue."
)

View File

@@ -0,0 +1,79 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import spack.compiler
class Fj(spack.compiler.Compiler):
# Named wrapper links within build_env_path
link_paths = {
"cc": os.path.join("fj", "fcc"),
"cxx": os.path.join("fj", "case-insensitive", "FCC"),
"f77": os.path.join("fj", "frt"),
"fc": os.path.join("fj", "frt"),
}
version_argument = "--version"
version_regex = r"\((?:FCC|FRT)\) ([a-z\d.]+)"
required_libs = ["libfj90i", "libfj90f", "libfjsrcinfo"]
@property
def verbose_flag(self):
return "-v"
@property
def debug_flags(self):
return "-g"
@property
def opt_flags(self):
return ["-O0", "-O1", "-O2", "-O3", "-Ofast"]
@property
def openmp_flag(self):
return "-Kopenmp"
@property
def cxx98_flag(self):
return "-std=c++98"
@property
def cxx11_flag(self):
return "-std=c++11"
@property
def cxx14_flag(self):
return "-std=c++14"
@property
def cxx17_flag(self):
return "-std=c++17"
@property
def c99_flag(self):
return "-std=c99"
@property
def c11_flag(self):
return "-std=c11"
@property
def cc_pic_flag(self):
return "-KPIC"
@property
def cxx_pic_flag(self):
return "-KPIC"
@property
def f77_pic_flag(self):
return "-KPIC"
@property
def fc_pic_flag(self):
return "-KPIC"

View File

@@ -1,26 +0,0 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from typing import List, Tuple
def tokenize_flags(flags_values: str, propagate: bool = False) -> List[Tuple[str, bool]]:
"""Given a compiler flag specification as a string, this returns a list
where the entries are the flags. For compiler options which set values
using the syntax "-flag value", this function groups flags and their
values together. Any token not preceded by a "-" is considered the
value of a prior flag."""
tokens = flags_values.split()
if not tokens:
return []
flag = tokens[0]
flags_with_propagation = []
for token in tokens[1:]:
if not token.startswith("-"):
flag += " " + token
else:
flags_with_propagation.append((flag, propagate))
flag = token
flags_with_propagation.append((flag, propagate))
return flags_with_propagation

View File

@@ -0,0 +1,191 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
from llnl.util.filesystem import ancestor
import spack.compiler
import spack.compilers.apple_clang as apple_clang
import spack.util.executable
from spack.version import Version
class Gcc(spack.compiler.Compiler):
# MacPorts builds gcc versions with prefixes and -mp-X or -mp-X.Y suffixes.
# Homebrew and Linuxbrew may build gcc with -X, -X.Y suffixes.
# Old compatibility versions may contain XY suffixes.
suffixes = [r"-mp-\d+(?:\.\d+)?", r"-\d+(?:\.\d+)?", r"\d\d"]
# Named wrapper links within build_env_path
link_paths = {
"cc": os.path.join("gcc", "gcc"),
"cxx": os.path.join("gcc", "g++"),
"f77": os.path.join("gcc", "gfortran"),
"fc": os.path.join("gcc", "gfortran"),
}
@property
def verbose_flag(self):
return "-v"
@property
def debug_flags(self):
return ["-g", "-gstabs+", "-gstabs", "-gxcoff+", "-gxcoff", "-gvms"]
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Os", "-Ofast", "-Og"]
@property
def openmp_flag(self):
return "-fopenmp"
@property
def cxx98_flag(self):
if self.real_version < Version("6.0"):
return ""
else:
return "-std=c++98"
@property
def cxx11_flag(self):
if self.real_version < Version("4.3"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++11 standard", "cxx11_flag", " < 4.3"
)
elif self.real_version < Version("4.7"):
return "-std=c++0x"
else:
return "-std=c++11"
@property
def cxx14_flag(self):
if self.real_version < Version("4.8"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++14 standard", "cxx14_flag", "< 4.8"
)
elif self.real_version < Version("4.9"):
return "-std=c++1y"
else:
return "-std=c++14"
@property
def cxx17_flag(self):
if self.real_version < Version("5.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++17 standard", "cxx17_flag", "< 5.0"
)
elif self.real_version < Version("6.0"):
return "-std=c++1z"
else:
return "-std=c++17"
@property
def cxx20_flag(self):
if self.real_version < Version("8.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++20 standard", "cxx20_flag", "< 8.0"
)
elif self.real_version < Version("11.0"):
return "-std=c++2a"
else:
return "-std=c++20"
@property
def cxx23_flag(self):
if self.real_version < Version("11.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++23 standard", "cxx23_flag", "< 11.0"
)
elif self.real_version < Version("14.0"):
return "-std=c++2b"
else:
return "-std=c++23"
@property
def c99_flag(self):
if self.real_version < Version("4.5"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C99 standard", "c99_flag", "< 4.5"
)
return "-std=c99"
@property
def c11_flag(self):
if self.real_version < Version("4.7"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C11 standard", "c11_flag", "< 4.7"
)
return "-std=c11"
@property
def cc_pic_flag(self):
return "-fPIC"
@property
def cxx_pic_flag(self):
return "-fPIC"
@property
def f77_pic_flag(self):
return "-fPIC"
@property
def fc_pic_flag(self):
return "-fPIC"
required_libs = ["libgcc", "libgfortran"]
@classmethod
def default_version(cls, cc):
"""Older versions of gcc use the ``-dumpversion`` option.
Output looks like this::
4.4.7
In GCC 7, this option was changed to only return the major
version of the compiler::
7
A new ``-dumpfullversion`` option was added that gives us
what we want::
7.2.0
"""
# Apple's gcc is actually apple clang, so skip it. Returning
# "unknown" ensures this compiler is not detected by default.
# Users can add it manually to compilers.yaml at their own risk.
if apple_clang.AppleClang.default_version(cc) != "unknown":
return "unknown"
version = super(Gcc, cls).default_version(cc)
if Version(version) >= Version("7"):
output = spack.compiler.get_compiler_version_output(cc, "-dumpfullversion")
version = cls.extract_version_from_output(output)
return version
@property
def stdcxx_libs(self):
return ("-lstdc++",)
@property
def prefix(self):
# GCC reports its install prefix when running ``-print-search-dirs``
# on the first line ``install: <prefix>``.
cc = spack.util.executable.Executable(self.cc)
with self.compiler_environment():
gcc_output = cc("-print-search-dirs", output=str, error=str)
for line in gcc_output.splitlines():
if line.startswith("install:"):
gcc_prefix = line.split(":")[1].strip()
# Go from <prefix>/lib/gcc/<triplet>/<version>/ to <prefix>
return ancestor(gcc_prefix, 4)
raise RuntimeError(
"could not find install prefix of GCC from output:\n\t{}".format(gcc_output)
)

View File

@@ -0,0 +1,131 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import sys
from spack.compiler import Compiler, UnsupportedCompilerFlag
from spack.version import Version
class Intel(Compiler):
# Named wrapper links within build_env_path
link_paths = {
"cc": os.path.join("intel", "icc"),
"cxx": os.path.join("intel", "icpc"),
"f77": os.path.join("intel", "ifort"),
"fc": os.path.join("intel", "ifort"),
}
if sys.platform == "win32":
version_argument = "/QV"
else:
version_argument = "--version"
if sys.platform == "win32":
version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)"
else:
version_regex = r"\((?:IFORT|ICC)\) ([^ ]+)"
@property
def verbose_flag(self):
return "-v"
required_libs = ["libirc", "libifcore", "libifcoremt", "libirng"]
@property
def debug_flags(self):
return ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"]
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"]
@property
def openmp_flag(self):
if self.real_version < Version("16.0"):
return "-openmp"
else:
return "-qopenmp"
@property
def cxx11_flag(self):
if self.real_version < Version("11.1"):
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 11.1")
elif self.real_version < Version("13"):
return "-std=c++0x"
else:
return "-std=c++11"
@property
def cxx14_flag(self):
# Adapted from CMake's Intel-CXX rules.
if self.real_version < Version("15"):
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 15")
elif self.real_version < Version("15.0.2"):
return "-std=c++1y"
else:
return "-std=c++14"
@property
def cxx17_flag(self):
# https://www.intel.com/content/www/us/en/developer/articles/news/c17-features-supported-by-c-compiler.html
if self.real_version < Version("19"):
raise UnsupportedCompilerFlag(self, "the C++17 standard", "cxx17_flag", "< 19")
else:
return "-std=c++17"
@property
def c99_flag(self):
if self.real_version < Version("12"):
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 12")
else:
return "-std=c99"
@property
def c11_flag(self):
if self.real_version < Version("16"):
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 16")
else:
return "-std=c1x"
@property
def c18_flag(self):
# c18 supported since oneapi 2022, which is classic version 2021.5.0
if self.real_version < Version("21.5.0"):
raise UnsupportedCompilerFlag(self, "the C18 standard", "c18_flag", "< 21.5.0")
else:
return "-std=c18"
@property
def cc_pic_flag(self):
return "-fPIC"
@property
def cxx_pic_flag(self):
return "-fPIC"
@property
def f77_pic_flag(self):
return "-fPIC"
@property
def fc_pic_flag(self):
return "-fPIC"
@property
def stdcxx_libs(self):
return ("-cxxlib",)
def setup_custom_environment(self, pkg, env):
# Edge cases for Intel's oneAPI compilers when using the legacy classic compilers:
# Always pass flags to disable deprecation warnings, since these warnings can
# confuse tools that parse the output of compiler commands (e.g. version checks).
if self.real_version >= Version("2021") and self.real_version < Version("2024"):
env.append_flags("SPACK_ALWAYS_CFLAGS", "-diag-disable=10441")
env.append_flags("SPACK_ALWAYS_CXXFLAGS", "-diag-disable=10441")
if self.real_version >= Version("2021") and self.real_version < Version("2025"):
env.append_flags("SPACK_ALWAYS_FFLAGS", "-diag-disable=10448")

View File

@@ -1,426 +0,0 @@
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import contextlib
import hashlib
import json
import os
import re
import shutil
import stat
import sys
import tempfile
import typing
from typing import Dict, List, Optional, Set, Tuple
import llnl.path
import llnl.util.lang
from llnl.util import tty
from llnl.util.filesystem import path_contains_subdirectory, paths_containing_libs
import spack.caches
import spack.util.libc
from spack.util.environment import filter_system_paths
from spack.util.file_cache import FileCache
if typing.TYPE_CHECKING:
import spack.spec
#: regex for parsing linker lines
_LINKER_LINE = re.compile(r"^( *|.*[/\\])" r"(link|ld|([^/\\]+-)?ld|collect2)" r"[^/\\]*( |$)")
#: components of linker lines to ignore
_LINKER_LINE_IGNORE = re.compile(r"(collect2 version|^[A-Za-z0-9_]+=|/ldfe )")
#: regex to match linker search paths
_LINK_DIR_ARG = re.compile(r"^-L(.:)?(?P<dir>[/\\].*)")
#: regex to match linker library path arguments
_LIBPATH_ARG = re.compile(r"^[-/](LIBPATH|libpath):(?P<dir>.*)")
@llnl.path.system_path_filter
def parse_non_system_link_dirs(compiler_debug_output: str) -> List[str]:
"""Parses link paths out of compiler debug output.
Args:
compiler_debug_output: compiler debug output as a string
Returns:
Implicit link paths parsed from the compiler output
"""
link_dirs = _parse_link_paths(compiler_debug_output)
# Remove directories that do not exist. Some versions of the Cray compiler
# report nonexistent directories
link_dirs = filter_non_existing_dirs(link_dirs)
# Return set of directories containing needed compiler libs, minus
# system paths. Note that 'filter_system_paths' only checks for an
# exact match, while 'in_system_subdirectory' checks if a path contains
# a system directory as a subdirectory
link_dirs = filter_system_paths(link_dirs)
return list(p for p in link_dirs if not in_system_subdirectory(p))
def filter_non_existing_dirs(dirs):
return [d for d in dirs if os.path.isdir(d)]
def in_system_subdirectory(path):
system_dirs = [
"/lib/",
"/lib64/",
"/usr/lib/",
"/usr/lib64/",
"/usr/local/lib/",
"/usr/local/lib64/",
]
return any(path_contains_subdirectory(path, x) for x in system_dirs)
def _parse_link_paths(string):
"""Parse implicit link paths from compiler debug output.
This gives the compiler runtime library paths that we need to add to
the RPATH of generated binaries and libraries. It allows us to
ensure, e.g., that codes load the right libstdc++ for their compiler.
"""
lib_search_paths = False
raw_link_dirs = []
for line in string.splitlines():
if lib_search_paths:
if line.startswith("\t"):
raw_link_dirs.append(line[1:])
continue
else:
lib_search_paths = False
elif line.startswith("Library search paths:"):
lib_search_paths = True
if not _LINKER_LINE.match(line):
continue
if _LINKER_LINE_IGNORE.match(line):
continue
tty.debug(f"implicit link dirs: link line: {line}")
next_arg = False
for arg in line.split():
if arg in ("-L", "-Y"):
next_arg = True
continue
if next_arg:
raw_link_dirs.append(arg)
next_arg = False
continue
link_dir_arg = _LINK_DIR_ARG.match(arg)
if link_dir_arg:
link_dir = link_dir_arg.group("dir")
raw_link_dirs.append(link_dir)
link_dir_arg = _LIBPATH_ARG.match(arg)
if link_dir_arg:
link_dir = link_dir_arg.group("dir")
raw_link_dirs.append(link_dir)
implicit_link_dirs = list()
visited = set()
for link_dir in raw_link_dirs:
normalized_path = os.path.abspath(link_dir)
if normalized_path not in visited:
implicit_link_dirs.append(normalized_path)
visited.add(normalized_path)
tty.debug(f"implicit link dirs: result: {', '.join(implicit_link_dirs)}")
return implicit_link_dirs
class CompilerPropertyDetector:
def __init__(self, compiler_spec: "spack.spec.Spec"):
assert compiler_spec.external, "only external compiler specs are allowed, so far"
assert compiler_spec.concrete, "only concrete compiler specs are allowed, so far"
self.spec = compiler_spec
self.cache = COMPILER_CACHE
@contextlib.contextmanager
def compiler_environment(self):
"""Sets the environment to run this compiler"""
import spack.schema.environment
import spack.util.module_cmd
# Avoid modifying os.environ if possible.
environment = self.spec.extra_attributes.get("environment", {})
modules = self.spec.external_modules or []
if not self.spec.external_modules and not environment:
yield
return
# store environment to replace later
backup_env = os.environ.copy()
try:
# load modules and set env variables
for module in modules:
spack.util.module_cmd.load_module(module)
# apply other compiler environment changes
spack.schema.environment.parse(environment).apply_modifications()
yield
finally:
# Restore environment regardless of whether inner code succeeded
os.environ.clear()
os.environ.update(backup_env)
def _compile_dummy_c_source(self) -> Optional[str]:
import spack.util.executable
assert self.spec.external, "only external compiler specs are allowed, so far"
compiler_pkg = self.spec.package
if getattr(compiler_pkg, "cc"):
cc = compiler_pkg.cc
ext = "c"
else:
cc = compiler_pkg.cxx
ext = "cc"
if not cc or not self.spec.package.verbose_flags:
return None
try:
tmpdir = tempfile.mkdtemp(prefix="spack-implicit-link-info")
fout = os.path.join(tmpdir, "output")
fin = os.path.join(tmpdir, f"main.{ext}")
with open(fin, "w") as csource:
csource.write(
"int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }\n"
)
cc_exe = spack.util.executable.Executable(cc)
# FIXME (compiler as nodes): this operation should be encapsulated somewhere else
compiler_flags = self.spec.extra_attributes.get("flags", {})
for flag_type in [
"cflags" if cc == compiler_pkg.cc else "cxxflags",
"cppflags",
"ldflags",
]:
current_flags = compiler_flags.get(flag_type, "").strip()
if current_flags:
cc_exe.add_default_arg(*current_flags.split(" "))
with self.compiler_environment():
return cc_exe("-v", fin, "-o", fout, output=str, error=str)
except spack.util.executable.ProcessError as pe:
tty.debug(f"ProcessError: Command exited with non-zero status: {pe.long_message}")
return None
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
def compiler_verbose_output(self) -> Optional[str]:
return self.cache.get(self.spec).c_compiler_output
def default_dynamic_linker(self) -> Optional[str]:
output = self.compiler_verbose_output()
if not output:
return None
return spack.util.libc.parse_dynamic_linker(output)
def default_libc(self) -> Optional["spack.spec.Spec"]:
"""Determine libc targeted by the compiler from link line"""
# technically this should be testing the target platform of the compiler, but we don't have
# that, so stick to host platform for now.
if sys.platform in ("darwin", "win32"):
return None
dynamic_linker = self.default_dynamic_linker()
if dynamic_linker is None:
return None
return spack.util.libc.libc_from_dynamic_linker(dynamic_linker)
def implicit_rpaths(self) -> List[str]:
output = self.compiler_verbose_output()
if output is None:
return []
link_dirs = parse_non_system_link_dirs(output)
all_required_libs = list(self.spec.package.required_libs) + ["libc", "libc++", "libstdc++"]
dynamic_linker = self.default_dynamic_linker()
# FIXME (compiler as nodes): is this needed ?
# if dynamic_linker is None:
# return []
result = DefaultDynamicLinkerFilter(dynamic_linker)(
paths_containing_libs(link_dirs, all_required_libs)
)
return list(result)
class DefaultDynamicLinkerFilter:
"""Remove rpaths to directories that are default search paths of the dynamic linker."""
_CACHE: Dict[Optional[str], Set[Tuple[int, int]]] = {}
def __init__(self, dynamic_linker: Optional[str]) -> None:
if dynamic_linker not in DefaultDynamicLinkerFilter._CACHE:
# Identify directories by (inode, device) tuple, which handles symlinks too.
default_path_identifiers: Set[Tuple[int, int]] = set()
if not dynamic_linker:
self.default_path_identifiers = None
return
for path in spack.util.libc.default_search_paths_from_dynamic_linker(dynamic_linker):
try:
s = os.stat(path)
if stat.S_ISDIR(s.st_mode):
default_path_identifiers.add((s.st_ino, s.st_dev))
except OSError:
continue
DefaultDynamicLinkerFilter._CACHE[dynamic_linker] = default_path_identifiers
self.default_path_identifiers = DefaultDynamicLinkerFilter._CACHE[dynamic_linker]
def is_dynamic_loader_default_path(self, p: str) -> bool:
if self.default_path_identifiers is None:
return False
try:
s = os.stat(p)
return (s.st_ino, s.st_dev) in self.default_path_identifiers
except OSError:
return False
def __call__(self, dirs: List[str]) -> List[str]:
if not self.default_path_identifiers:
return dirs
return [p for p in dirs if not self.is_dynamic_loader_default_path(p)]
def dynamic_linker_filter_for(node: "spack.spec.Spec") -> Optional[DefaultDynamicLinkerFilter]:
compiler = compiler_spec(node)
if compiler is None:
return None
detector = CompilerPropertyDetector(compiler)
dynamic_linker = detector.default_dynamic_linker()
if dynamic_linker is None:
return None
return DefaultDynamicLinkerFilter(dynamic_linker)
def compiler_spec(node: "spack.spec.Spec") -> Optional["spack.spec.Spec"]:
"""Returns the compiler spec associated with the node passed as argument.
The function looks for a "c", "cxx", and "fortran" compiler in that order,
and returns the first found. If none is found, returns None.
"""
for language in ("c", "cxx", "fortran"):
candidates = node.dependencies(virtuals=[language])
if candidates:
break
else:
return None
return candidates[0]
class CompilerCacheEntry:
"""Deserialized cache entry for a compiler"""
__slots__ = ["c_compiler_output"]
def __init__(self, c_compiler_output: Optional[str]):
self.c_compiler_output = c_compiler_output
@classmethod
def from_dict(cls, data: Dict[str, Optional[str]]):
if not isinstance(data, dict):
raise ValueError(f"Invalid {cls.__name__} data")
c_compiler_output = data.get("c_compiler_output")
if not isinstance(c_compiler_output, (str, type(None))):
raise ValueError(f"Invalid {cls.__name__} data")
return cls(c_compiler_output)
class CompilerCache:
"""Base class for compiler output cache. Default implementation does not cache anything."""
def value(self, compiler: "spack.spec.Spec") -> Dict[str, Optional[str]]:
return {"c_compiler_output": CompilerPropertyDetector(compiler)._compile_dummy_c_source()}
def get(self, compiler: "spack.spec.Spec") -> CompilerCacheEntry:
return CompilerCacheEntry.from_dict(self.value(compiler))
class FileCompilerCache(CompilerCache):
"""Cache for compiler output, which is used to determine implicit link paths, the default libc
version, and the compiler version."""
name = os.path.join("compilers", "compilers.json")
def __init__(self, cache: "FileCache") -> None:
self.cache = cache
self.cache.init_entry(self.name)
self._data: Dict[str, Dict[str, Optional[str]]] = {}
def _get_entry(self, key: str) -> Optional[CompilerCacheEntry]:
try:
return CompilerCacheEntry.from_dict(self._data[key])
except ValueError:
del self._data[key]
except KeyError:
pass
return None
def get(self, compiler: "spack.spec.Spec") -> CompilerCacheEntry:
# Cache hit
try:
with self.cache.read_transaction(self.name) as f:
assert f is not None
self._data = json.loads(f.read())
assert isinstance(self._data, dict)
except (json.JSONDecodeError, AssertionError):
self._data = {}
key = self._key(compiler)
value = self._get_entry(key)
if value is not None:
return value
# Cache miss
with self.cache.write_transaction(self.name) as (old, new):
try:
assert old is not None
self._data = json.loads(old.read())
assert isinstance(self._data, dict)
except (json.JSONDecodeError, AssertionError):
self._data = {}
# Use cache entry that may have been created by another process in the meantime.
entry = self._get_entry(key)
# Finally compute the cache entry
if entry is None:
self._data[key] = self.value(compiler)
entry = CompilerCacheEntry.from_dict(self._data[key])
new.write(json.dumps(self._data, separators=(",", ":")))
return entry
def _key(self, compiler: "spack.spec.Spec") -> str:
as_bytes = json.dumps(compiler.to_dict(), separators=(",", ":")).encode("utf-8")
return hashlib.sha256(as_bytes).hexdigest()
def _make_compiler_cache():
return FileCompilerCache(spack.caches.MISC_CACHE)
COMPILER_CACHE: CompilerCache = llnl.util.lang.Singleton(_make_compiler_cache) # type: ignore

View File

@@ -0,0 +1,394 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import re
import subprocess
import sys
import tempfile
from typing import Dict
import archspec.cpu
import spack.compiler
import spack.operating_systems.windows_os
import spack.platforms
import spack.util.executable
from spack.compiler import Compiler
from spack.error import SpackError
from spack.version import Version, VersionRange
FC_PATH: Dict[str, str] = dict()
class CmdCall:
"""Compose a call to `cmd` for an ordered series of cmd commands/scripts"""
def __init__(self, *cmds):
if not cmds:
raise RuntimeError(
"""Attempting to run commands from CMD without specifying commands.
Please add commands to be run."""
)
self._cmds = cmds
def __call__(self):
out = subprocess.check_output(self.cmd_line, stderr=subprocess.STDOUT) # novermin
return out.decode("utf-16le", errors="replace") # novermin
@property
def cmd_line(self):
base_call = "cmd /u /c "
commands = " && ".join([x.command_str() for x in self._cmds])
# If multiple commands are being invoked by a single subshell
# they must be encapsulated by a double quote. Always double
# quote to be sure of proper handling
# cmd will properly resolve nested double quotes as needed
#
# `set`` writes out the active env to the subshell stdout,
# and in this context we are always trying to obtain env
# state so it should always be appended
return base_call + f'"{commands} && set"'
class VarsInvocation:
def __init__(self, script):
self._script = script
def command_str(self):
return f'"{self._script}"'
@property
def script(self):
return self._script
class VCVarsInvocation(VarsInvocation):
def __init__(self, script, arch, msvc_version):
super(VCVarsInvocation, self).__init__(script)
self._arch = arch
self._msvc_version = msvc_version
@property
def sdk_ver(self):
"""Accessor for Windows SDK version property
Note: This property may not be set by
the calling context and as such this property will
return an empty string
This property will ONLY be set if the SDK package
is a dependency somewhere in the Spack DAG of the package
for which we are constructing an MSVC compiler env.
Otherwise this property should be unset to allow the VCVARS
script to use its internal heuristics to determine appropriate
SDK version
"""
if getattr(self, "_sdk_ver", None):
return self._sdk_ver + ".0"
return ""
@sdk_ver.setter
def sdk_ver(self, val):
self._sdk_ver = val
@property
def arch(self):
return self._arch
@property
def vcvars_ver(self):
return f"-vcvars_ver={self._msvc_version}"
def command_str(self):
script = super(VCVarsInvocation, self).command_str()
return f"{script} {self.arch} {self.sdk_ver} {self.vcvars_ver}"
def get_valid_fortran_pth():
"""Assign maximum available fortran compiler version"""
# TODO (johnwparent): validate compatibility w/ try compiler
# functionality when added
sort_fn = lambda fc_ver: Version(fc_ver)
sort_fc_ver = sorted(list(FC_PATH.keys()), key=sort_fn)
return FC_PATH[sort_fc_ver[-1]] if sort_fc_ver else None
class Msvc(Compiler):
# Named wrapper links within build_env_path
# Due to the challenges of supporting compiler wrappers
# in Windows, we leave these blank, and dynamically compute
# based on proper versions of MSVC from there
# pending acceptance of #28117 for full support using
# compiler wrappers
link_paths = {"cc": "", "cxx": "", "f77": "", "fc": ""}
#: Compiler argument that produces version information
version_argument = ""
# For getting ifx's version, call it with version_argument
# and ignore the error code
ignore_version_errors = [1]
#: Regex used to extract version from compiler's output
version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)"
# The MSVC compiler class overrides this to prevent instances
# of erroneous matching on executable names that cannot be msvc
# compilers
suffixes = []
is_supported_on_platform = lambda x: isinstance(x, spack.platforms.Windows)
def __init__(self, *args, **kwargs):
# This positional argument "paths" is later parsed and process by the base class
# via the call to `super` later in this method
paths = args[3]
latest_fc = get_valid_fortran_pth()
new_pth = [pth if pth else latest_fc for pth in paths[2:]]
paths[2:] = new_pth
# Initialize, deferring to base class but then adding the vcvarsallfile
# file based on compiler executable path.
super().__init__(*args, **kwargs)
# To use the MSVC compilers, VCVARS must be invoked
# VCVARS is located at a fixed location, referencable
# idiomatically by the following relative path from the
# compiler.
# Spack first finds the compilers via VSWHERE
# and stores their path, but their respective VCVARS
# file must be invoked before useage.
env_cmds = []
compiler_root = os.path.join(os.path.dirname(self.cc), "../../../../../..")
vcvars_script_path = os.path.join(compiler_root, "Auxiliary", "Build", "vcvars64.bat")
# get current platform architecture and format for vcvars argument
arch = spack.platforms.real_host().default.lower()
arch = arch.replace("-", "_")
if str(archspec.cpu.host().family) == "x86_64":
arch = "amd64"
self.vcvars_call = VCVarsInvocation(vcvars_script_path, arch, self.msvc_version)
env_cmds.append(self.vcvars_call)
# Below is a check for a valid fortran path
# paths has c, cxx, fc, and f77 paths in that order
# paths[2] refers to the fc path and is a generic check
# for a fortran compiler
if paths[2]:
def get_oneapi_root(pth: str):
"""From within a prefix known to be a oneAPI path
determine the oneAPI root path from arbitrary point
under root
Args:
pth: path prefixed within oneAPI root
"""
if not pth:
return ""
while os.path.basename(pth) and os.path.basename(pth) != "oneAPI":
pth = os.path.dirname(pth)
return pth
# If this found, it sets all the vars
oneapi_root = get_oneapi_root(self.fc)
if not oneapi_root:
raise RuntimeError(f"Non-oneAPI Fortran compiler {self.fc} assigned to MSVC")
oneapi_root_setvars = os.path.join(oneapi_root, "setvars.bat")
# some oneAPI exes return a version more precise than their
# install paths specify, so we determine path from
# the install path rather than the fc executable itself
numver = r"\d+\.\d+(?:\.\d+)?"
pattern = f"((?:{numver})|(?:latest))"
version_from_path = re.search(pattern, self.fc).group(1)
oneapi_version_setvars = os.path.join(
oneapi_root, "compiler", version_from_path, "env", "vars.bat"
)
# order matters here, the specific version env must be invoked first,
# otherwise it will be ignored if the root setvars sets up the oneapi
# env first
env_cmds.extend(
[VarsInvocation(oneapi_version_setvars), VarsInvocation(oneapi_root_setvars)]
)
self.msvc_compiler_environment = CmdCall(*env_cmds)
@property
def cxx11_flag(self):
return "/std:c++11"
@property
def cxx14_flag(self):
return "/std:c++14"
@property
def cxx17_flag(self):
return "/std:c++17"
@property
def cxx20_flag(self):
return "/std:c++20"
@property
def c11_flag(self):
return "/std:c11"
@property
def c17_flag(self):
return "/std:c17"
@property
def msvc_version(self):
"""This is the VCToolset version *NOT* the actual version of the cl compiler
For CL version, query `Msvc.cl_version`"""
return Version(re.search(Msvc.version_regex, self.cc).group(1))
@property
def short_msvc_version(self):
"""This is the shorthand VCToolset version of form
MSVC<short-ver>
"""
return "MSVC" + self.vc_toolset_ver
@property
def vc_toolset_ver(self):
"""
The toolset version is the version of the combined set of cl and link
This typically relates directly to VS version i.e. VS 2022 is v143
VS 19 is v142, etc.
This value is defined by the first three digits of the major + minor
version of the VS toolset (143 for 14.3x.bbbbb). Traditionally the
minor version has remained a static two digit number for a VS release
series, however, as of VS22, this is no longer true, both
14.4x.bbbbb and 14.3x.bbbbb are considered valid VS22 VC toolset
versions due to a change in toolset minor version sentiment.
This is *NOT* the full version, for that see
Msvc.msvc_version or MSVC.platform_toolset_ver for the
raw platform toolset version
"""
ver = self.msvc_version[:2].joined.string[:3]
return ver
@property
def platform_toolset_ver(self):
"""
This is the platform toolset version of current MSVC compiler
i.e. 142. The platform toolset is the targeted MSVC library/compiler
versions by compilation (this is different from the VC Toolset)
This is different from the VC toolset version as established
by `short_msvc_version`, but typically are represented by the same
three digit value
"""
# Typically VS toolset version and platform toolset versions match
# VS22 introduces the first divergence of VS toolset version
# (144 for "recent" releases) and platform toolset version (143)
# so it needs additional handling until MS releases v144
# (assuming v144 is also for VS22)
# or adds better support for detection
# TODO: (johnwparent) Update this logic for the next platform toolset
# or VC toolset version update
toolset_ver = self.vc_toolset_ver
vs22_toolset = Version(toolset_ver) > Version("142")
return toolset_ver if not vs22_toolset else "143"
@property
def visual_studio_version(self):
"""The four digit Visual Studio version (i.e. 2019 or 2022)
Note: This differs from the msvc version or toolset version as
those properties track the compiler and build tools version
respectively, whereas this tracks the VS release associated
with a given MSVC compiler.
"""
return re.search(r"[0-9]{4}", self.cc).group(0)
def _compiler_version(self, compiler):
"""Returns version object for given compiler"""
# ignore_errors below is true here due to ifx's
# non zero return code if it is not provided
# and input file
return Version(
re.search(
Msvc.version_regex,
spack.compiler.get_compiler_version_output(
compiler, version_arg=None, ignore_errors=True
),
).group(1)
)
@property
def cl_version(self):
"""Cl toolset version"""
return self._compiler_version(self.cc)
@property
def ifx_version(self):
"""Ifx compiler version associated with this version of MSVC"""
return self._compiler_version(self.fc)
@property
def vs_root(self):
# The MSVC install root is located at a fix level above the compiler
# and is referenceable idiomatically via the pattern below
# this should be consistent accross versions
return os.path.abspath(os.path.join(self.cc, "../../../../../../../.."))
def setup_custom_environment(self, pkg, env):
"""Set environment variables for MSVC using the
Microsoft-provided script."""
# Set the build environment variables for spack. Just using
# subprocess.call() doesn't work since that operates in its own
# environment which is destroyed (along with the adjusted variables)
# once the process terminates. So go the long way around: examine
# output, sort into dictionary, use that to make the build
# environment.
# vcvars can target specific sdk versions, force it to pick up concretized sdk
# version, if needed by spec
if pkg.name != "win-sdk" and "win-sdk" in pkg.spec:
self.vcvars_call.sdk_ver = pkg.spec["win-sdk"].version.string
out = self.msvc_compiler_environment()
int_env = dict(
(key, value)
for key, _, value in (line.partition("=") for line in out.splitlines())
if key and value
)
for env_var in int_env:
if os.pathsep not in int_env[env_var]:
env.set(env_var, int_env[env_var])
else:
env.set_path(env_var, int_env[env_var].split(os.pathsep))
# certain versions of ifx (2021.3.0:2023.1.0) do not play well with env:TMP
# that has a "." character in the path
# Work around by pointing tmp to the stage for the duration of the build
if self.fc and Version(self.fc_version(self.fc)).satisfies(
VersionRange("2021.3.0", "2023.1.0")
):
new_tmp = tempfile.mkdtemp(dir=pkg.stage.path)
env.set("TMP", new_tmp)
env.set("CC", self.cc)
env.set("CXX", self.cxx)
env.set("FC", self.fc)
env.set("F77", self.f77)
@classmethod
def fc_version(cls, fc):
if not sys.platform == "win32":
return "unknown"
fc_ver = cls.default_version(fc)
FC_PATH[fc_ver] = fc
try:
sps = spack.operating_systems.windows_os.WindowsOs().compiler_search_paths
except AttributeError:
raise SpackError(
"Windows compiler search paths not established, "
"please report this behavior to github.com/spack/spack"
)
clp = spack.util.executable.which_string("cl", path=sps)
return cls.default_version(clp) if clp else fc_ver

View File

@@ -0,0 +1,112 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import re
import llnl.util.lang
import spack.compiler
class Nag(spack.compiler.Compiler):
# Named wrapper links within build_env_path
# Use default wrappers for C and C++, in case provided in compilers.yaml
link_paths = {
"cc": "cc",
"cxx": "c++",
"f77": os.path.join("nag", "nagfor"),
"fc": os.path.join("nag", "nagfor"),
}
version_argument = "-V"
@classmethod
@llnl.util.lang.memoized
def extract_version_from_output(cls, output):
match = re.search(r"NAG Fortran Compiler Release (\d+).(\d+)\(.*\) Build (\d+)", output)
if match:
return ".".join(match.groups())
@property
def verbose_flag(self):
# NAG does not support a flag that would enable verbose output and
# compilation/linking at the same time (with either '-#' or '-dryrun'
# the compiler only prints the commands but does not run them).
# Therefore, the only thing we can do is to pass the '-v' argument to
# the underlying GCC. In order to get verbose output from the latter
# at both compile and linking stages, we need to call NAG with two
# additional flags: '-Wc,-v' and '-Wl,-v'. However, we return only
# '-Wl,-v' for the following reasons:
# 1) the interface of this method does not support multiple flags in
# the return value and, at least currently, verbose output at the
# linking stage has a higher priority for us;
# 2) NAG is usually mixed with GCC compiler, which also accepts
# '-Wl,-v' and produces meaningful result with it: '-v' is passed
# to the linker and the latter produces verbose output for the
# linking stage ('-Wc,-v', however, would break the compilation
# with a message from GCC that the flag is not recognized).
#
# This way, we at least enable the implicit rpath detection, which is
# based on compilation of a C file (see method
# spack.compiler._compile_dummy_c_source): in the case of a mixed
# NAG/GCC toolchain, the flag will be passed to g++ (e.g.
# 'g++ -Wl,-v ./main.c'), otherwise, the flag will be passed to nagfor
# (e.g. 'nagfor -Wl,-v ./main.c' - note that nagfor recognizes '.c'
# extension and treats the file accordingly). The list of detected
# rpaths will contain only GCC-related directories and rpaths to
# NAG-related directories are injected by nagfor anyway.
return "-Wl,-v"
@property
def openmp_flag(self):
return "-openmp"
@property
def debug_flags(self):
return ["-g", "-gline", "-g90"]
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"]
@property
def cxx11_flag(self):
# NAG does not have a C++ compiler
# However, it can be mixed with a compiler that does support it
return "-std=c++11"
@property
def f77_pic_flag(self):
return "-PIC"
@property
def fc_pic_flag(self):
return "-PIC"
# Unlike other compilers, the NAG compiler passes options to GCC, which
# then passes them to the linker. Therefore, we need to doubly wrap the
# options with '-Wl,-Wl,,'
@property
def f77_rpath_arg(self):
return "-Wl,-Wl,,-rpath,,"
@property
def fc_rpath_arg(self):
return "-Wl,-Wl,,-rpath,,"
@property
def linker_arg(self):
return "-Wl,-Wl,,"
@property
def disable_new_dtags(self):
# Disable RPATH/RUNPATH forcing for NAG/GCC mixed toolchains:
return ""
@property
def enable_new_dtags(self):
# Disable RPATH/RUNPATH forcing for NAG/GCC mixed toolchains:
return ""

View File

@@ -0,0 +1,79 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
from spack.compiler import Compiler
class Nvhpc(Compiler):
# Named wrapper links within build_env_path
link_paths = {
"cc": os.path.join("nvhpc", "nvc"),
"cxx": os.path.join("nvhpc", "nvc++"),
"f77": os.path.join("nvhpc", "nvfortran"),
"fc": os.path.join("nvhpc", "nvfortran"),
}
version_argument = "--version"
version_regex = r"nv[^ ]* (?:[^ ]+ Dev-r)?([0-9.]+)(?:-[0-9]+)?"
@property
def verbose_flag(self):
return "-v"
@property
def debug_flags(self):
return ["-g", "-gopt"]
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"]
@property
def openmp_flag(self):
return "-mp"
@property
def cc_pic_flag(self):
return "-fpic"
@property
def cxx_pic_flag(self):
return "-fpic"
@property
def f77_pic_flag(self):
return "-fpic"
@property
def fc_pic_flag(self):
return "-fpic"
@property
def c99_flag(self):
return "-c99"
@property
def c11_flag(self):
return "-c11"
@property
def cxx11_flag(self):
return "--c++11"
@property
def cxx14_flag(self):
return "--c++14"
@property
def cxx17_flag(self):
return "--c++17"
@property
def stdcxx_libs(self):
return ("-c++libs",)
required_libs = ["libnvc", "libnvf"]

View File

@@ -0,0 +1,172 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
from os.path import dirname, join
from llnl.util import tty
from llnl.util.filesystem import ancestor
import spack.util.executable
from spack.compiler import Compiler
from spack.version import Version
class Oneapi(Compiler):
# Named wrapper links within build_env_path
link_paths = {
"cc": os.path.join("oneapi", "icx"),
"cxx": os.path.join("oneapi", "icpx"),
"f77": os.path.join("oneapi", "ifx"),
"fc": os.path.join("oneapi", "ifx"),
}
version_argument = "--version"
version_regex = r"(?:(?:oneAPI DPC\+\+(?:\/C\+\+)? Compiler)|(?:\(IFORT\))|(?:\(IFX\))) (\S+)"
@property
def verbose_flag(self):
return "-v"
required_libs = [
"libirc",
"libifcore",
"libifcoremt",
"libirng",
"libsvml",
"libintlc",
"libimf",
"libsycl",
"libOpenCL",
]
@property
def debug_flags(self):
return ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"]
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"]
@property
def openmp_flag(self):
return "-fiopenmp"
# There may be some additional options here for offload, e.g. :
# -fopenmp-simd Emit OpenMP code only for SIMD-based constructs.
# -fopenmp-targets=<value>
# -fopenmp-version=<value>
# -fopenmp Parse OpenMP pragmas and generate parallel code.
# -qno-openmp Disable OpenMP support
# -qopenmp-link=<value> Choose whether to link with the static or
# dynamic OpenMP libraries. Default is dynamic.
# -qopenmp-simd Emit OpenMP code only for SIMD-based constructs.
# -qopenmp-stubs enables the user to compile OpenMP programs in
# sequential mode. The OpenMP directives are
# ignored and a stub OpenMP library is linked.
# -qopenmp-threadprivate=<value>
# -qopenmp Parse OpenMP pragmas and generate parallel code.
# -static-openmp Use the static host OpenMP runtime while
# linking.
# -Xopenmp-target=<triple> <arg>
# -Xopenmp-target <arg> Pass <arg> to the target offloading toolchain.
# Source: icx --help output
@property
def cxx11_flag(self):
return "-std=c++11"
@property
def cxx14_flag(self):
return "-std=c++14"
@property
def cxx17_flag(self):
return "-std=c++17"
@property
def cxx20_flag(self):
return "-std=c++20"
@property
def c99_flag(self):
return "-std=c99"
@property
def c11_flag(self):
return "-std=c1x"
@property
def cc_pic_flag(self):
return "-fPIC"
@property
def cxx_pic_flag(self):
return "-fPIC"
@property
def f77_pic_flag(self):
return "-fPIC"
@property
def fc_pic_flag(self):
return "-fPIC"
@property
def stdcxx_libs(self):
return ("-cxxlib",)
@property
def prefix(self):
# OneAPI reports its install prefix when running ``--version``
# on the line ``InstalledDir: <prefix>/bin/compiler``.
cc = spack.util.executable.Executable(self.cc)
with self.compiler_environment():
oneapi_output = cc("--version", output=str, error=str)
for line in oneapi_output.splitlines():
if line.startswith("InstalledDir:"):
oneapi_prefix = line.split(":")[1].strip()
# Go from <prefix>/bin/compiler to <prefix>
return ancestor(oneapi_prefix, 2)
raise RuntimeError(
"could not find install prefix of OneAPI from output:\n\t{}".format(oneapi_output)
)
def setup_custom_environment(self, pkg, env):
# workaround bug in icpx driver where it requires sycl-post-link is on the PATH
# It is located in the same directory as the driver. Error message:
# clang++: error: unable to execute command:
# Executable "sycl-post-link" doesn't exist!
# also ensures that shared objects and libraries required by the compiler,
# e.g. libonnx, can be found succesfully
# due to a fix, this is no longer required for OneAPI versions >= 2024.2
if self.cxx and pkg.spec.satisfies("%oneapi@:2024.1"):
env.prepend_path("PATH", dirname(self.cxx))
env.prepend_path("LD_LIBRARY_PATH", join(dirname(dirname(self.cxx)), "lib"))
# Edge cases for Intel's oneAPI compilers when using the legacy classic compilers:
# Always pass flags to disable deprecation warnings, since these warnings can
# confuse tools that parse the output of compiler commands (e.g. version checks).
# This is really only needed for Fortran, since oneapi@ should be using either
# icx+icpx+ifx or icx+icpx+ifort. But to be on the safe side (some users may
# want to try to swap icpx against icpc, for example), and since the Intel LLVM
# compilers accept these diag-disable flags, we apply them for all compilers.
if self.real_version >= Version("2021") and self.real_version < Version("2024"):
env.append_flags("SPACK_ALWAYS_CFLAGS", "-diag-disable=10441")
env.append_flags("SPACK_ALWAYS_CXXFLAGS", "-diag-disable=10441")
if self.real_version >= Version("2021") and self.real_version < Version("2025"):
env.append_flags("SPACK_ALWAYS_FFLAGS", "-diag-disable=10448")
# 2024 release bumped the libsycl version because of an ABI
# change, 2024 compilers are required. You will see this
# error:
#
# /usr/bin/ld: warning: libsycl.so.7, needed by ...., not found
if pkg.spec.satisfies("%oneapi@:2023"):
for c in ["dnn"]:
if pkg.spec.satisfies(f"^intel-oneapi-{c}@2024:"):
tty.warn(f"intel-oneapi-{c}@2024 SYCL APIs requires %oneapi@2024:")

View File

@@ -0,0 +1,54 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import re
import llnl.util.lang
import spack.compilers.clang
class Rocmcc(spack.compilers.clang.Clang):
@property
def link_paths(self):
link_paths = {
"cc": "rocmcc/amdclang",
"cxx": "rocmcc/amdclang++",
"f77": "rocmcc/amdflang",
"fc": "rocmcc/amdflang",
}
return link_paths
@property
def cxx11_flag(self):
return "-std=c++11"
@property
def cxx14_flag(self):
return "-std=c++14"
@property
def cxx17_flag(self):
return "-std=c++17"
@property
def c99_flag(self):
return "-std=c99"
@property
def c11_flag(self):
return "-std=c11"
@classmethod
@llnl.util.lang.memoized
def extract_version_from_output(cls, output):
match = re.search(r"llvm-project roc-(\d+)[._](\d+)[._](\d+)", output)
if match:
return ".".join(match.groups())
@property
def stdcxx_libs(self):
return ("-lstdc++",)

View File

@@ -0,0 +1,93 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
from spack.compiler import Compiler, UnsupportedCompilerFlag
from spack.version import Version
class Xl(Compiler):
# Named wrapper links within build_env_path
link_paths = {
"cc": os.path.join("xl", "xlc"),
"cxx": os.path.join("xl", "xlc++"),
"f77": os.path.join("xl", "xlf"),
"fc": os.path.join("xl", "xlf90"),
}
version_argument = "-qversion"
version_regex = r"([0-9]?[0-9]\.[0-9])"
@property
def verbose_flag(self):
return "-V"
@property
def debug_flags(self):
return ["-g", "-g0", "-g1", "-g2", "-g8", "-g9"]
@property
def opt_flags(self):
return ["-O", "-O0", "-O1", "-O2", "-O3", "-O4", "-O5", "-Ofast"]
@property
def openmp_flag(self):
return "-qsmp=omp"
@property
def cxx11_flag(self):
if self.real_version < Version("13.1"):
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 13.1")
else:
return "-qlanglvl=extended0x"
@property
def c99_flag(self):
if self.real_version >= Version("13.1.1"):
return "-std=gnu99"
if self.real_version >= Version("10.1"):
return "-qlanglvl=extc99"
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 10.1")
@property
def c11_flag(self):
if self.real_version >= Version("13.1.2"):
return "-std=gnu11"
if self.real_version >= Version("12.1"):
return "-qlanglvl=extc1x"
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 12.1")
@property
def cxx14_flag(self):
# .real_version does not have the "y.z" component of "w.x.y.z", which
# is required to distinguish whether support is available
if self.version >= Version("16.1.1.8"):
return "-std=c++14"
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 16.1.1.8")
@property
def cc_pic_flag(self):
return "-qpic"
@property
def cxx_pic_flag(self):
return "-qpic"
@property
def f77_pic_flag(self):
return "-qpic"
@property
def fc_pic_flag(self):
return "-qpic"
@property
def fflags(self):
# The -qzerosize flag is effective only for the Fortran 77
# compilers and allows the use of zero size objects.
# For Fortran 90 and beyond, it is set by default and has not impact.
# Its use has no negative side effects.
return "-qzerosize"

View File

@@ -0,0 +1,18 @@
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
import spack.compilers.xl
class XlR(spack.compilers.xl.Xl):
# Named wrapper links within build_env_path
link_paths = {
"cc": os.path.join("xl_r", "xlc_r"),
"cxx": os.path.join("xl_r", "xlc++_r"),
"f77": os.path.join("xl_r", "xlf_r"),
"fc": os.path.join("xl_r", "xlf90_r"),
}

View File

@@ -11,7 +11,6 @@
import llnl.util.tty as tty
import spack.compilers
import spack.compilers.config
import spack.config
import spack.error
import spack.repo
@@ -147,7 +146,7 @@ def concretize_separately(
# Ensure we have compilers in compilers.yaml to avoid that
# processes try to write the config file in parallel
_ = spack.compilers.config.all_compilers_from(spack.config.CONFIG)
_ = spack.compilers.all_compilers_config(spack.config.CONFIG)
# Early return if there is nothing to do
if len(args) == 0:

View File

@@ -715,9 +715,6 @@ def print_section(self, section: str, blame: bool = False, *, scope=None) -> Non
raise spack.error.ConfigError(f"cannot read '{section}' configuration") from e
ConfigurationType = Union[Configuration, lang.Singleton]
@contextlib.contextmanager
def override(
path_or_scope: Union[ConfigScope, str], value: Optional[Any] = None

View File

@@ -14,7 +14,7 @@
import llnl.util.tty as tty
import spack.cmd
import spack.compilers.config
import spack.compilers
import spack.deptypes as dt
import spack.error
import spack.hash_types as hash_types
@@ -28,13 +28,13 @@
#: packages here.
default_path = "/opt/cray/pe/cpe-descriptive-manifest/"
compiler_name_translation = {"nvidia": "nvhpc", "rocm": "rocmcc", "clang": "llvm"}
compiler_name_translation = {"nvidia": "nvhpc", "rocm": "rocmcc"}
def translated_compiler_name(manifest_compiler_name):
"""
When creating a Compiler object, Spack expects a name matching
one of the classes in `spack.compilers.config`. Names in the Cray manifest
one of the classes in `spack.compilers`. Names in the Cray manifest
may differ; for cases where we know the name refers to a compiler in
Spack, this function translates it automatically.
@@ -43,25 +43,25 @@ def translated_compiler_name(manifest_compiler_name):
"""
if manifest_compiler_name in compiler_name_translation:
return compiler_name_translation[manifest_compiler_name]
elif manifest_compiler_name in spack.compilers.config.supported_compilers():
elif manifest_compiler_name in spack.compilers.supported_compilers():
return manifest_compiler_name
else:
raise spack.compilers.config.UnknownCompilerError(
raise spack.compilers.UnknownCompilerError(
"Manifest parsing - unknown compiler: {0}".format(manifest_compiler_name)
)
def compiler_from_entry(entry: dict, *, manifest_path: str) -> "spack.spec.Spec":
def compiler_from_entry(entry: dict, manifest_path: str):
# Note that manifest_path is only passed here to compose a
# useful warning message when paths appear to be missing.
compiler_name = translated_compiler_name(entry["name"])
prefix = None
if "prefix" in entry:
prefix = entry["prefix"]
paths = {
lang: os.path.join(prefix, relpath) for lang, relpath in entry["executables"].items()
}
paths = dict(
(lang, os.path.join(prefix, relpath))
for (lang, relpath) in entry["executables"].items()
)
else:
paths = entry["executables"]
@@ -75,38 +75,25 @@ def compiler_from_entry(entry: dict, *, manifest_path: str) -> "spack.spec.Spec"
missing_paths.append(path)
# to instantiate a compiler class we may need a concrete version:
version = "={}".format(entry["version"])
arch = entry["arch"]
operating_system = arch["os"]
target = arch["target"]
spec_str = f"{compiler_name}@={entry['version']} os={operating_system} target={target}"
compilers = {}
for x in ("cc", "cxx", "fc"):
language = {"cc": "c", "fc": "fortran"}
if x not in paths:
continue
if prefix is None:
prefix = os.path.dirname(paths[x])
compilers[language.get(x, x)] = paths[x]
compiler_cls = spack.compilers.class_for_compiler_name(compiler_name)
spec = spack.spec.CompilerSpec(compiler_cls.name, version)
path_list = [paths.get(x, None) for x in ("cc", "cxx", "f77", "fc")]
if missing_paths:
warnings.warn(
"Manifest entry refers to nonexistent paths:\n\t"
+ "\n\t".join(missing_paths)
+ f"\nfor {spec_str}"
+ f"\nfor {str(spec)}"
+ f"\nin {manifest_path}"
+ "\nPlease report this issue"
)
assert prefix is not None, "compiler prefix must be set"
result = spack.spec.Spec(
str(spack.spec.parse_with_version_concrete(spec_str)), external_path=prefix
)
result.extra_attributes = {"compilers": compilers}
result._finalize_concretization()
return result
return compiler_cls(spec, operating_system, target, path_list)
def spec_from_entry(entry):
@@ -134,7 +121,7 @@ def spec_from_entry(entry):
version=entry["compiler"]["version"],
)
spec_format = "{name}@={version} {arch}"
spec_format = "{name}@={version} {compiler} {arch}"
spec_str = spec_format.format(
name=entry["name"], version=entry["version"], compiler=compiler_str, arch=arch_str
)
@@ -195,7 +182,6 @@ def entries_to_specs(entries):
for entry in entries:
try:
spec = spec_from_entry(entry)
assert spec.concrete, f"{spec} is not concrete"
spec_dict[spec._hash] = spec
except spack.repo.UnknownPackageError:
tty.debug("Omitting package {0}: no corresponding repo package".format(entry["name"]))
@@ -236,24 +222,23 @@ def read(path, apply_updates):
tty.debug("{0}: {1} specs read from manifest".format(path, str(len(specs))))
compilers = list()
if "compilers" in json_data:
compilers.extend(
compiler_from_entry(x, manifest_path=path) for x in json_data["compilers"]
)
tty.debug(f"{path}: {str(len(compilers))} compilers read from manifest")
compilers.extend(compiler_from_entry(x, path) for x in json_data["compilers"])
tty.debug("{0}: {1} compilers read from manifest".format(path, str(len(compilers))))
# Filter out the compilers that already appear in the configuration
compilers = spack.compilers.config.select_new_compilers(compilers)
compilers = spack.compilers.select_new_compilers(compilers)
if apply_updates and compilers:
try:
spack.compilers.config.add_compiler_to_config(compilers)
except Exception:
warnings.warn(
f"Could not add compilers from manifest: {path}"
"\nPlease reexecute with 'spack -d' and include the stack trace"
)
tty.debug(f"Include this\n{traceback.format_exc()}")
for compiler in compilers:
try:
spack.compilers.add_compilers_to_config([compiler])
except Exception:
warnings.warn(
f"Could not add compiler {str(compiler.spec)}: "
f"\n\tfrom manifest: {path}"
"\nPlease reexecute with 'spack -d' and include the stack trace"
)
tty.debug(f"Include this\n{traceback.format_exc()}")
if apply_updates:
for spec in specs.values():
assert spec.concrete, f"{spec} is not concrete"
spack.store.STORE.db.add(spec)

View File

@@ -80,7 +80,7 @@
#: DB version. This is stuck in the DB file to track changes in format.
#: Increment by one when the database format changes.
#: Versions before 5 were not integers.
_DB_VERSION = vn.Version("8")
_DB_VERSION = vn.Version("7")
#: For any version combinations here, skip reindex when upgrading.
#: Reindexing can take considerable time and is not always necessary.
@@ -93,8 +93,6 @@
(vn.Version("0.9.3"), vn.Version("5")),
(vn.Version("5"), vn.Version("6")),
(vn.Version("6"), vn.Version("7")),
(vn.Version("6"), vn.Version("8")),
(vn.Version("7"), vn.Version("8")),
]
#: Default timeout for spack database locks in seconds or None (no timeout).
@@ -143,7 +141,6 @@ def reader(version: vn.StandardVersion) -> Type["spack.spec.SpecfileReaderBase"]
vn.Version("5"): spack.spec.SpecfileV1,
vn.Version("6"): spack.spec.SpecfileV3,
vn.Version("7"): spack.spec.SpecfileV4,
vn.Version("8"): spack.spec.SpecfileV5,
}
return reader_cls[version]

View File

@@ -85,6 +85,9 @@ class OpenMpi(Package):
PatchesType = Optional[Union[Patcher, str, List[Union[Patcher, str]]]]
SUPPORTED_LANGUAGES = ("fortran", "cxx", "c")
def _make_when_spec(value: WhenType) -> Optional[spack.spec.Spec]:
"""Create a ``Spec`` that indicates when a directive should be applied.
@@ -363,6 +366,9 @@ def depends_on(
"""
dep_spec = spack.spec.Spec(spec)
if dep_spec.name in SUPPORTED_LANGUAGES:
assert type == "build", "languages must be of 'build' type"
return _language(lang_spec_str=spec, when=when)
def _execute_depends_on(pkg: spack.package_base.PackageBase):
_depends_on(pkg, dep_spec, when=when, type=type, patches=patches)
@@ -896,6 +902,21 @@ def _execute_requires(pkg: spack.package_base.PackageBase):
return _execute_requires
@directive("languages")
def _language(lang_spec_str: str, *, when: Optional[Union[str, bool]] = None):
"""Temporary implementation of language virtuals, until compilers are proper dependencies."""
def _execute_languages(pkg: spack.package_base.PackageBase):
when_spec = _make_when_spec(when)
if not when_spec:
return
languages = pkg.languages.setdefault(when_spec, set())
languages.add(lang_spec_str)
return _execute_languages
class DependencyError(DirectiveError):
"""This is raised when a dependency specification is invalid."""

View File

@@ -23,7 +23,7 @@
from spack.error import SpackError
default_projections = {
"all": "{architecture.platform}/{architecture.target}/{name}-{version}-{hash}"
"all": "{architecture}/{compiler.name}-{compiler.version}/{name}-{version}-{hash}"
}

View File

@@ -166,7 +166,9 @@ def __init__(
item.target.safe_name(),
" ".join(self._install_target(s.safe_name()) for s in item.prereqs),
item.target.spec_hash(),
item.target.unsafe_format("{name}{@version}{variants}{ arch=architecture}"),
item.target.unsafe_format(
"{name}{@version}{%compiler}{variants}{arch=architecture}"
),
item.buildcache_flag,
)
for item in adjacency_list

View File

@@ -135,7 +135,7 @@ def default_manifest_yaml():
valid_environment_name_re = r"^\w[\w-]*$"
#: version of the lockfile format. Must increase monotonically.
lockfile_format_version = 6
lockfile_format_version = 5
READER_CLS = {
@@ -144,7 +144,6 @@ def default_manifest_yaml():
3: spack.spec.SpecfileV2,
4: spack.spec.SpecfileV3,
5: spack.spec.SpecfileV4,
6: spack.spec.SpecfileV5,
}

View File

@@ -325,12 +325,7 @@ def write(self, spec, color=None, out=None):
self._out = llnl.util.tty.color.ColorStream(out, color=color)
# We'll traverse the spec in topological order as we graph it.
nodes_in_topological_order = [
edge.spec
for edge in spack.traverse.traverse_edges_topo(
[spec], direction="children", deptype=self.depflag
)
]
nodes_in_topological_order = list(spec.traverse(order="topo", deptype=self.depflag))
nodes_in_topological_order.reverse()
# Work on a copy to be nondestructive

View File

@@ -375,23 +375,16 @@ def phase_tests(self, builder, phase_name: str, method_names: List[str]):
for name in method_names:
try:
# Prefer the method in the package over the builder's.
# We need this primarily to pick up arbitrarily named test
# methods but also some build-time checks.
fn = getattr(builder.pkg, name, getattr(builder, name))
msg = f"RUN-TESTS: {phase_name}-time tests [{name}]"
print_message(logger, msg, verbose)
fn()
fn = getattr(builder, name, None) or getattr(builder.pkg, name)
except AttributeError as e:
msg = f"RUN-TESTS: method not implemented [{name}]"
print_message(logger, msg, verbose)
self.add_failure(e, msg)
print_message(logger, f"RUN-TESTS: method not implemented [{name}]", verbose)
self.add_failure(e, f"RUN-TESTS: method not implemented [{name}]")
if fail_fast:
break
continue
print_message(logger, f"RUN-TESTS: {phase_name}-time tests [{name}]", verbose)
fn()
if have_tests:
print_message(logger, "Completed testing", verbose)

View File

@@ -71,16 +71,12 @@ def _filter_compiler_wrappers_impl(pkg_or_builder):
x = llnl.util.filesystem.FileFilter(*abs_files)
compiler_vars = []
if "c" in pkg.spec:
compiler_vars.append(("CC", pkg.spec["c"].package.cc))
if "cxx" in pkg.spec:
compiler_vars.append(("CXX", pkg.spec["cxx"].package.cxx))
if "fortran" in pkg.spec:
compiler_vars.append(("FC", pkg.spec["fortran"].package.fortran))
compiler_vars.append(("F77", pkg.spec["fortran"].package.fortran))
compiler_vars = [
("CC", pkg.compiler.cc),
("CXX", pkg.compiler.cxx),
("F77", pkg.compiler.f77),
("FC", pkg.compiler.fc),
]
# Some paths to the compiler wrappers might be substrings of the others.
# For example:
@@ -108,11 +104,7 @@ def _filter_compiler_wrappers_impl(pkg_or_builder):
x.filter(wrapper_path, compiler_path, **filter_kwargs)
# Remove this linking flag if present (it turns RPATH into RUNPATH)
for compiler_lang in ("c", "cxx", "fortran"):
if compiler_lang not in pkg.spec:
continue
compiler_pkg = pkg.spec[compiler_lang].package
x.filter(f"{compiler_pkg.linker_arg}--enable-new-dtags", "", **filter_kwargs)
x.filter("{0}--enable-new-dtags".format(pkg.compiler.linker_arg), "", **filter_kwargs)
# NAG compiler is usually mixed with GCC, which has a different
# prefix for linker arguments.

View File

@@ -330,17 +330,18 @@ class BaseConfiguration:
default_projections = {"all": "{name}/{version}-{compiler.name}-{compiler.version}"}
def __init__(self, spec: spack.spec.Spec, module_set_name: str, explicit: bool) -> None:
# Module where type(self) is defined
m = inspect.getmodule(self)
assert m is not None # make mypy happy
self.module = m
# Spec for which we want to generate a module file
self.spec = spec
self.name = module_set_name
self.explicit = explicit
# Dictionary of configuration options that should be applied to the spec
# Dictionary of configuration options that should be applied
# to the spec
self.conf = merge_config_rules(self.module.configuration(self.name), self.spec)
@property
def module(self):
return inspect.getmodule(self)
@property
def projections(self):
"""Projection from specs to module names"""

View File

@@ -6,13 +6,12 @@
import collections
import itertools
import os.path
import pathlib
from typing import Dict, List, Optional, Tuple
import llnl.util.filesystem as fs
import llnl.util.lang as lang
import spack.compilers.config
import spack.compilers
import spack.config
import spack.error
import spack.repo
@@ -60,7 +59,7 @@ def make_context(
return LmodContext(make_configuration(spec, module_set_name, explicit))
def guess_core_compilers(name, store=False) -> List[spack.spec.Spec]:
def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]:
"""Guesses the list of core compilers installed in the system.
Args:
@@ -71,12 +70,16 @@ def guess_core_compilers(name, store=False) -> List[spack.spec.Spec]:
List of found core compilers
"""
core_compilers = []
for compiler in spack.compilers.config.all_compilers():
for compiler in spack.compilers.all_compilers():
try:
cc_dir = pathlib.Path(compiler.package.cc).parent
is_system_compiler = str(cc_dir) in spack.util.environment.SYSTEM_DIRS
# A compiler is considered to be a core compiler if any of the
# C, C++ or Fortran compilers reside in a system directory
is_system_compiler = any(
os.path.dirname(getattr(compiler, x, "")) in spack.util.environment.SYSTEM_DIRS
for x in ("cc", "cxx", "f77", "fc")
)
if is_system_compiler:
core_compilers.append(compiler)
core_compilers.append(compiler.spec)
except (KeyError, TypeError, AttributeError):
continue
@@ -98,32 +101,18 @@ class LmodConfiguration(BaseConfiguration):
default_projections = {"all": "{name}/{version}"}
def __init__(self, spec: spack.spec.Spec, module_set_name: str, explicit: bool) -> None:
super().__init__(spec, module_set_name, explicit)
# FIXME (compiler as nodes): make this a bit more robust
candidates = collections.defaultdict(list)
for node in spec.traverse(deptype=("link", "run")):
candidates["c"].extend(node.dependencies(virtuals="c"))
candidates["cxx"].extend(node.dependencies(virtuals="c"))
# FIXME (compiler as nodes): decide what to do when we have more than one C compiler
if candidates["c"] and len(set(candidates["c"])) == 1:
self.compiler = candidates["c"][0]
elif not candidates["c"]:
self.compiler = None
@property
def core_compilers(self) -> List[spack.spec.Spec]:
def core_compilers(self) -> List[spack.spec.CompilerSpec]:
"""Returns the list of "Core" compilers
Raises:
CoreCompilersNotFoundError: if the key was not specified in the configuration file or
the sequence is empty
CoreCompilersNotFoundError: if the key was not
specified in the configuration file or the sequence
is empty
"""
compilers = []
for c in configuration(self.name).get("core_compilers", []):
compilers.extend(spack.spec.Spec(f"%{c}").dependencies())
compilers = [
spack.spec.CompilerSpec(c) for c in configuration(self.name).get("core_compilers", [])
]
if not compilers:
compilers = guess_core_compilers(self.name, store=True)
@@ -172,15 +161,12 @@ def hierarchy_tokens(self):
@property
@lang.memoized
def requires(self):
"""Returns a dictionary mapping all the requirements of this spec to the actual provider.
The 'compiler' key is always present among the requirements.
"""Returns a dictionary mapping all the requirements of this spec
to the actual provider. 'compiler' is always present among the
requirements.
"""
# If it's a core_spec, lie and say it requires a core compiler
if (
any(self.spec.satisfies(core_spec) for core_spec in self.core_specs)
or self.compiler is None
):
if any(self.spec.satisfies(core_spec) for core_spec in self.core_specs):
return {"compiler": self.core_compilers[0]}
hierarchy_filter_list = []
@@ -191,8 +177,7 @@ def requires(self):
# Keep track of the requirements that this package has in terms
# of virtual packages that participate in the hierarchical structure
requirements = {"compiler": self.compiler}
requirements = {"compiler": self.spec.compiler}
# For each virtual dependency in the hierarchy
for x in self.hierarchy_tokens:
# Skip anything filtered for this spec
@@ -215,17 +200,17 @@ def provides(self):
# virtual dependencies in spack
# If it is in the list of supported compilers family -> compiler
if self.spec.name in spack.compilers.config.supported_compilers():
provides["compiler"] = spack.spec.Spec(self.spec.format("{name}{@versions}"))
elif self.spec.name in spack.compilers.config.package_name_to_compiler_name:
if self.spec.name in spack.compilers.supported_compilers():
provides["compiler"] = spack.spec.CompilerSpec(self.spec.format("{name}{@versions}"))
elif self.spec.name in spack.compilers.package_name_to_compiler_name:
# If it is the package for a supported compiler, but of a different name
cname = spack.compilers.config.package_name_to_compiler_name[self.spec.name]
provides["compiler"] = spack.spec.Spec(cname, self.spec.versions)
cname = spack.compilers.package_name_to_compiler_name[self.spec.name]
provides["compiler"] = spack.spec.CompilerSpec(cname, self.spec.versions)
# All the other tokens in the hierarchy must be virtual dependencies
for x in self.hierarchy_tokens:
if self.spec.package.provides(x):
provides[x] = self.spec
provides[x] = self.spec[x]
return provides
@property
@@ -316,10 +301,12 @@ def path_part_fmt(token):
# If we are dealing with a core compiler, return 'Core'
core_compilers = self.conf.core_compilers
if name == "compiler" and any(spack.spec.Spec(value).satisfies(c) for c in core_compilers):
if name == "compiler" and any(
spack.spec.CompilerSpec(value).satisfies(c) for c in core_compilers
):
return "Core"
# Spec does not have a hash, as we are not allowed to
# CompilerSpec does not have a hash, as we are not allowed to
# use different flavors of the same compiler
if name == "compiler":
return path_part_fmt(token=value)

View File

@@ -32,6 +32,7 @@
from llnl.util.lang import classproperty, memoized
from llnl.util.link_tree import LinkTree
import spack.compilers
import spack.config
import spack.dependency
import spack.deptypes as dt
@@ -51,7 +52,6 @@
import spack.util.path
import spack.util.web
import spack.variant
from spack.compilers.adaptor import DeprecatedCompiler
from spack.error import InstallError, NoURLError, PackageError
from spack.filesystem_view import YamlFilesystemView
from spack.solver.version_order import concretization_version_order
@@ -64,9 +64,10 @@
]
FLAG_HANDLER_TYPE = Callable[[str, Iterable[str]], FLAG_HANDLER_RETURN_TYPE]
#: Allowed URL schemes for spack packages
"""Allowed URL schemes for spack packages."""
_ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"]
#: Filename for the Spack build/install log.
_spack_build_logfile = "spack-build-out.txt"
@@ -577,8 +578,6 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta):
"""
compiler = DeprecatedCompiler()
#
# These are default values for instance variables.
#
@@ -1495,6 +1494,15 @@ def prefix(self):
def home(self):
return self.prefix
@property # type: ignore[misc]
@memoized
def compiler(self):
"""Get the spack.compiler.Compiler object used to build this package"""
if not self.spec.concrete:
raise ValueError("Can only get a compiler for a concrete package.")
return spack.compilers.compiler_for_spec(self.spec.compiler, self.spec.architecture)
def url_version(self, version):
"""
Given a version, this returns a string that should be substituted
@@ -1606,7 +1614,7 @@ def do_stage(self, mirror_only=False):
self.stage.create()
# Fetch/expand any associated code.
if self.has_code and not self.spec.external:
if self.has_code:
self.do_fetch(mirror_only)
self.stage.expand_archive()
else:
@@ -1936,14 +1944,17 @@ def _resource_stage(self, resource):
return resource_stage_folder
def do_test(self, dirty=False, externals=False):
if self.test_requires_compiler and not any(
lang in self.spec for lang in ("c", "cxx", "fortran")
):
tty.error(
f"Skipping tests for package {self.spec}, since a compiler is required, "
f"but not available"
if self.test_requires_compiler:
compilers = spack.compilers.compilers_for_spec(
self.spec.compiler, arch_spec=self.spec.architecture
)
return
if not compilers:
tty.error(
"Skipping tests for package %s\n"
% self.spec.format("{name}-{version}-{hash:7}")
+ "Package test requires missing compiler %s" % self.spec.compiler
)
return
kwargs = {
"dirty": dirty,

View File

@@ -375,11 +375,12 @@ def all_specs(self) -> List["spack.spec.Spec"]:
class SpecNodeParser:
"""Parse a single spec node from a stream of tokens"""
__slots__ = "ctx", "has_version", "literal_str"
__slots__ = "ctx", "has_compiler", "has_version", "literal_str"
def __init__(self, ctx, literal_str):
self.ctx = ctx
self.literal_str = literal_str
self.has_compiler = False
self.has_version = False
def parse(
@@ -426,24 +427,23 @@ def add_flag(name: str, value: str, propagate: bool):
raise_parsing_error(str(e), e)
while True:
if self.ctx.accept(TokenType.COMPILER) or self.ctx.accept(
TokenType.COMPILER_AND_VERSION
):
build_dependency = spack.spec.Spec(self.ctx.current_token.value[1:])
name_conversion = {
"clang": "llvm",
"oneapi": "intel-oneapi-compilers",
"rocmcc": "llvm-amdgpu",
"intel": "intel-oneapi-compiler-classic",
"arm": "acfl",
}
if self.ctx.accept(TokenType.COMPILER):
if self.has_compiler:
raise_parsing_error("Spec cannot have multiple compilers")
if build_dependency.name in name_conversion:
build_dependency.name = name_conversion[build_dependency.name]
compiler_name = self.ctx.current_token.value[1:]
initial_spec.compiler = spack.spec.CompilerSpec(compiler_name.strip(), ":")
self.has_compiler = True
initial_spec._add_dependency(
build_dependency, depflag=spack.deptypes.BUILD, virtuals=(), direct=True
elif self.ctx.accept(TokenType.COMPILER_AND_VERSION):
if self.has_compiler:
raise_parsing_error("Spec cannot have multiple compilers")
compiler_name, compiler_version = self.ctx.current_token.value[1:].split("@")
initial_spec.compiler = spack.spec.CompilerSpec(
compiler_name.strip(), compiler_version
)
self.has_compiler = True
elif (
self.ctx.accept(TokenType.VERSION_HASH_PAIR)

View File

@@ -240,10 +240,9 @@ def from_json(stream, repository):
providers = data["provider_index"]["providers"]
index.providers = _transform(
providers,
# FIXME (compiler as nodes): avoid to hard-code the Specfile version
lambda vpkg, plist: (
spack.spec.SpecfileV5.from_node_dict(vpkg),
set(spack.spec.SpecfileV5.from_node_dict(p) for p in plist),
spack.spec.SpecfileV4.from_node_dict(vpkg),
set(spack.spec.SpecfileV4.from_node_dict(p) for p in plist),
),
)
return index

View File

@@ -13,6 +13,7 @@
import macholib.mach_o
import macholib.MachO
import llnl.util.filesystem as fs
import llnl.util.lang
import llnl.util.tty as tty
from llnl.util.lang import memoized
@@ -275,10 +276,10 @@ def modify_macho_object(cur_path, rpaths, deps, idpath, paths_to_paths):
# Deduplicate and flatten
args = list(itertools.chain.from_iterable(llnl.util.lang.dedupe(args)))
install_name_tool = executable.Executable("install_name_tool")
if args:
args.append(str(cur_path))
install_name_tool = executable.Executable("install_name_tool")
install_name_tool(*args)
with fs.edit_in_place_through_temporary_file(cur_path) as temp_path:
install_name_tool(*args, temp_path)
def macholib_get_paths(cur_path):
@@ -717,8 +718,8 @@ def fixup_macos_rpath(root, filename):
# No fixes needed
return False
args.append(abspath)
executable.Executable("install_name_tool")(*args)
with fs.edit_in_place_through_temporary_file(abspath) as temp_path:
executable.Executable("install_name_tool")(*args, temp_path)
return True

View File

@@ -78,6 +78,7 @@ def __enter__(self):
"packages": [],
}
spec_record["properties"].append(Property("architecture", input_spec.architecture))
spec_record["properties"].append(Property("compiler", input_spec.compiler))
self.init_spec_record(input_spec, spec_record)
self.specs.append(spec_record)

View File

@@ -173,7 +173,7 @@ def build_report_for_package(self, report_dir, package, duration):
# something went wrong pre-cdash "configure" phase b/c we have an exception and only
# "update" was encounterd.
# dump the report in the configure line so teams can see what the issue is
if len(phases_encountered) == 1 and package.get("exception"):
if len(phases_encountered) == 1 and package["exception"]:
# TODO this mapping is not ideal since these are pre-configure errors
# we need to determine if a more appropriate cdash phase can be utilized
# for now we will add a message to the log explaining this

View File

@@ -88,6 +88,8 @@
"strategy": {"type": "string", "enum": ["none", "minimal", "full"]}
},
},
"timeout": {"type": "integer", "minimum": 0},
"error_on_timeout": {"type": "boolean"},
"os_compatible": {"type": "object", "additionalProperties": {"type": "array"}},
},
}

View File

@@ -139,15 +139,6 @@
},
"variants": variants,
},
"deprecatedProperties": [
{
"names": ["compiler"],
"message": "The packages:all:compiler preference has been deprecated in "
"Spack v0.24, and is currently ignored. It will be removed from config in "
"Spack v0.26.",
"error": False,
}
],
}
},
"patternProperties": {

View File

@@ -27,8 +27,9 @@
import spack
import spack.binary_distribution
import spack.compilers.config
import spack.compilers.flags
import spack.compiler
import spack.compilers
import spack.concretize
import spack.config
import spack.deptypes as dt
import spack.environment as ev
@@ -47,7 +48,6 @@
import spack.version as vn
import spack.version.git_ref_lookup
from spack import traverse
from spack.compilers.libraries import CompilerPropertyDetector
from spack.config import get_mark_from_yaml_data
from spack.error import SpecSyntaxError
@@ -71,6 +71,9 @@
TransformFunction = Callable[["spack.spec.Spec", List[AspFunction]], List[AspFunction]]
#: Enable the addition of a runtime node
WITH_RUNTIME = sys.platform != "win32"
#: Data class that contain configuration on what a
#: clingo solve should output.
#:
@@ -292,11 +295,12 @@ def _create_counter(specs: List[spack.spec.Spec], tests: bool):
def all_libcs() -> Set[spack.spec.Spec]:
"""Return a set of all libc specs targeted by any configured compiler. If none, fall back to
libc determined from the current Python process if dynamically linked."""
libcs = set()
for c in spack.compilers.config.all_compilers_from(spack.config.CONFIG):
candidate = CompilerPropertyDetector(c).default_libc()
if candidate is not None:
libcs.add(candidate)
libcs = {
c.default_libc
for c in spack.compilers.all_compilers_from(spack.config.CONFIG)
if c.default_libc
}
if libcs:
return libcs
@@ -305,7 +309,7 @@ def all_libcs() -> Set[spack.spec.Spec]:
return {libc} if libc else set()
def libc_is_compatible(lhs: spack.spec.Spec, rhs: spack.spec.Spec) -> bool:
def libc_is_compatible(lhs: spack.spec.Spec, rhs: spack.spec.Spec) -> List[spack.spec.Spec]:
return (
lhs.name == rhs.name
and lhs.external_path == rhs.external_path
@@ -318,8 +322,8 @@ def using_libc_compatibility() -> bool:
return spack.platforms.host().name == "linux"
def c_compiler_runs(compiler) -> bool:
return CompilerPropertyDetector(compiler).compiler_verbose_output() is not None
def c_compiler_runs(compiler: spack.compiler.Compiler) -> bool:
return compiler.compiler_verbose_output is not None
def extend_flag_list(flag_list, new_flags):
@@ -609,12 +613,10 @@ def _external_config_with_implicit_externals(configuration):
if not using_libc_compatibility():
return packages_yaml
seen = set()
for compiler in spack.compilers.config.all_compilers_from(configuration):
libc = CompilerPropertyDetector(compiler).default_libc()
if libc and libc not in seen:
seen.add(libc)
entry = {"spec": f"{libc}", "prefix": libc.external_path}
for compiler in spack.compilers.all_compilers_from(configuration):
libc = compiler.default_libc
if libc:
entry = {"spec": f"{libc} %{compiler.spec}", "prefix": libc.external_path}
packages_yaml.setdefault(libc.name, {}).setdefault("externals", []).append(entry)
return packages_yaml
@@ -766,6 +768,27 @@ class RequirementRule(NamedTuple):
message: Optional[str]
class KnownCompiler(NamedTuple):
"""Data class to collect information on compilers"""
spec: "spack.spec.Spec"
os: str
target: str
available: bool
compiler_obj: Optional["spack.compiler.Compiler"]
def _key(self):
return self.spec, self.os, self.target
def __eq__(self, other: object):
if not isinstance(other, KnownCompiler):
return NotImplemented
return self._key() == other._key()
def __hash__(self):
return hash(self._key())
class PyclingoDriver:
def __init__(self, cores=True):
"""Driver for the Python clingo interface.
@@ -863,7 +886,22 @@ def on_model(model):
solve_kwargs["on_unsat"] = cores.append
timer.start("solve")
solve_result = self.control.solve(**solve_kwargs)
time_limit = spack.config.CONFIG.get("concretizer:timeout", -1)
error_on_timeout = spack.config.CONFIG.get("concretizer:error_on_timeout", True)
# Spack uses 0 to set no time limit, clingo API uses -1
if time_limit == 0:
time_limit = -1
with self.control.solve(**solve_kwargs, async_=True) as handle:
finished = handle.wait(time_limit)
if not finished:
specs_str = ", ".join(llnl.util.lang.elide_list([str(s) for s in specs], 4))
header = f"Spack is taking more than {time_limit} seconds to solve for {specs_str}"
if error_on_timeout:
raise UnsatisfiableSpecError(f"{header}, stopping concretization")
warnings.warn(f"{header}, using the best configuration found so far")
handle.cancel()
solve_result = handle.get()
timer.stop("solve")
# once done, construct the solve result
@@ -1047,13 +1085,13 @@ class SourceContext:
Facts generated for the spec may include this context.
"""
def __init__(self, *, source: Optional[str] = None):
def __init__(self):
# This can be "literal" for constraints that come from a user
# spec (e.g. from the command line); it can be the output of
# `ConstraintOrigin.append_type_suffix`; the default is "none"
# (which means it isn't important to keep track of the source
# in that case).
self.source = "none" if source is None else source
self.source = "none"
class ConditionIdContext(SourceContext):
@@ -1246,6 +1284,16 @@ def conflict_rules(self, pkg):
)
self.gen.newline()
def package_languages(self, pkg):
for when_spec, languages in pkg.languages.items():
condition_msg = f"{pkg.name} needs the {', '.join(sorted(languages))} language"
if when_spec != spack.spec.Spec():
condition_msg += f" when {when_spec}"
condition_id = self.condition(when_spec, required_name=pkg.name, msg=condition_msg)
for language in sorted(languages):
self.gen.fact(fn.pkg_fact(pkg.name, fn.language(condition_id, language)))
self.gen.newline()
def config_compatible_os(self):
"""Facts about compatible os's specified in configs"""
self.gen.h2("Compatible OS from concretizer config file")
@@ -1255,6 +1303,34 @@ def config_compatible_os(self):
self.gen.fact(fn.os_compatible(recent, old))
self.gen.newline()
def compiler_facts(self):
"""Facts about available compilers."""
self.gen.h2("Available compilers")
for compiler_id, compiler in enumerate(self.possible_compilers):
self.gen.fact(fn.compiler_id(compiler_id))
self.gen.fact(fn.compiler_name(compiler_id, compiler.spec.name))
self.gen.fact(fn.compiler_version(compiler_id, compiler.spec.version))
if compiler.os:
self.gen.fact(fn.compiler_os(compiler_id, compiler.os))
if compiler.target is not None:
self.gen.fact(fn.compiler_target(compiler_id, compiler.target))
if compiler.compiler_obj is not None:
c = compiler.compiler_obj
for flag_type, flags in c.flags.items():
flag_group = " ".join(flags)
for flag in flags:
self.gen.fact(fn.compiler_flag(compiler_id, flag_type, flag, flag_group))
if compiler.available:
self.gen.fact(fn.compiler_available(compiler_id))
self.gen.fact(fn.compiler_weight(compiler_id, compiler_id))
self.gen.newline()
def package_requirement_rules(self, pkg):
parser = RequirementParser(spack.config.CONFIG)
self.emit_facts_from_requirement_rules(parser.rules(pkg))
@@ -1269,6 +1345,9 @@ def pkg_rules(self, pkg, tests):
self.pkg_version_rules(pkg)
self.gen.newline()
# languages
self.package_languages(pkg)
# variants
self.variant_rules(pkg)
@@ -1285,6 +1364,12 @@ def pkg_rules(self, pkg, tests):
if self.enable_splicing:
self.package_splice_rules(pkg)
# virtual preferences
self.virtual_preferences(
pkg.name,
lambda v, p, i: self.gen.fact(fn.pkg_fact(pkg.name, fn.provider_preference(v, p, i))),
)
self.package_requirement_rules(pkg)
# trigger and effect tables
@@ -1814,6 +1899,8 @@ def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
def external_packages(self):
"""Facts on external packages, from packages.yaml and implicit externals."""
packages_yaml = _external_config_with_implicit_externals(spack.config.CONFIG)
self.gen.h1("External packages")
spec_filters = []
concretizer_yaml = spack.config.get("concretizer")
@@ -1821,6 +1908,7 @@ def external_packages(self):
if isinstance(reuse_yaml, typing.Mapping):
default_include = reuse_yaml.get("include", [])
default_exclude = reuse_yaml.get("exclude", [])
libc_externals = list(all_libcs())
for source in reuse_yaml.get("from", []):
if source["type"] != "external":
continue
@@ -1828,7 +1916,7 @@ def external_packages(self):
include = source.get("include", default_include)
if include:
# Since libcs are implicit externals, we need to implicitly include them
include = include + self.libcs
include = include + libc_externals
exclude = source.get("exclude", default_exclude)
spec_filters.append(
SpecFilter(
@@ -1839,7 +1927,6 @@ def external_packages(self):
)
)
packages_yaml = _external_config_with_implicit_externals(spack.config.CONFIG)
for pkg_name, data in packages_yaml.items():
if pkg_name == "all":
continue
@@ -1848,15 +1935,10 @@ def external_packages(self):
if pkg_name not in self.pkgs:
continue
# This package is not in the possible dependencies
if pkg_name not in self.pkgs:
continue
# This package is not among possible dependencies
if pkg_name not in self.pkgs:
continue
self.gen.h2(f"External package: {pkg_name}")
# Check if the external package is buildable. If it is
# not then "external(<pkg>)" is a fact, unless we can
# reuse an already installed spec.
@@ -2073,6 +2155,28 @@ def _spec_clauses(
else:
clauses.append(f.variant_value(spec.name, vname, value))
# compiler and compiler version
if spec.compiler:
clauses.append(f.node_compiler(spec.name, spec.compiler.name))
if spec.compiler.concrete:
clauses.append(
f.node_compiler_version(spec.name, spec.compiler.name, spec.compiler.version)
)
elif spec.compiler.versions and spec.compiler.versions != vn.any_version:
# The condition above emits a facts only if we have an actual constraint
# on the compiler version, and avoids emitting them if any version is fine
clauses.append(
fn.attr(
"node_compiler_version_satisfies",
spec.name,
spec.compiler.name,
spec.compiler.versions,
)
)
self.compiler_version_constraints.add(spec.compiler)
# compiler flags
source = context.source if context else "none"
for flag_type, flags in spec.compiler_flags.items():
@@ -2110,7 +2214,6 @@ def _spec_clauses(
# If the spec is external and concrete, we allow all the libcs on the system
if spec.external and spec.concrete and using_libc_compatibility():
clauses.append(fn.attr("needs_libc", spec.name))
for libc in self.libcs:
clauses.append(fn.attr("compatible_libc", spec.name, libc.name, libc.version))
@@ -2124,17 +2227,11 @@ def _spec_clauses(
# GCC runtime is solved again by clingo, even on concrete specs, to give
# the possibility to reuse specs built against a different runtime.
if dep.name == "gcc-runtime":
clauses.append(
fn.attr("compatible_runtime", spec.name, dep.name, f"{dep.version}:")
)
constraint_spec = spack.spec.Spec(f"{dep.name}@{dep.version}")
self.spec_versions(constraint_spec)
continue
# libc is also solved again by clingo, but in this case the compatibility
# is not encoded in the parent node - so we need to emit explicit facts
if "libc" in dspec.virtuals:
clauses.append(fn.attr("needs_libc", spec.name))
for libc in self.libcs:
if libc_is_compatible(libc, dep):
clauses.append(
@@ -2169,23 +2266,15 @@ def _spec_clauses(
# if it's concrete, then the hashes above take care of dependency
# constraints, but expand the hashes if asked for.
if not spec.concrete or expand_hashes:
dependency_clauses = self._spec_clauses(
dep,
body=body,
expand_hashes=expand_hashes,
concrete_build_deps=concrete_build_deps,
context=context,
clauses.extend(
self._spec_clauses(
dep,
body=body,
expand_hashes=expand_hashes,
concrete_build_deps=concrete_build_deps,
context=context,
)
)
if dspec.depflag == dt.BUILD:
clauses.append(fn.attr("depends_on", spec.name, dep.name, "build"))
if body is False:
for clause in dependency_clauses:
clause.name = "build_requirement"
clauses.append(fn.attr("build_requirement", spec.name, clause))
else:
clauses.extend(dependency_clauses)
else:
clauses.extend(dependency_clauses)
return clauses
@@ -2283,9 +2372,7 @@ def _supported_targets(self, compiler_name, compiler_version, targets):
try:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
target.optimization_flags(
compiler_name, compiler_version.dotted_numeric_string
)
target.optimization_flags(compiler_name, str(compiler_version))
supported.append(target)
except archspec.cpu.UnsupportedMicroarchitecture:
continue
@@ -2382,21 +2469,39 @@ def target_defaults(self, specs):
candidate_targets.append(ancestor)
best_targets = {uarch.family.name}
for compiler in self.possible_compilers:
for compiler_id, known_compiler in enumerate(self.possible_compilers):
if not known_compiler.available:
continue
compiler = known_compiler.compiler_obj
# Stub support for cross-compilation, to be expanded later
if known_compiler.target is not None and compiler.target not in (
str(uarch.family),
"any",
):
self.gen.fact(fn.compiler_supports_target(compiler_id, compiler.target))
self.gen.newline()
continue
supported = self._supported_targets(compiler.name, compiler.version, candidate_targets)
# If we can't find supported targets it may be due to custom
# versions in the spec, e.g. gcc@foo. Try to match the
# real_version from the compiler object to get more accurate
# results.
if not supported:
supported = self._supported_targets(
compiler.name, compiler.real_version, candidate_targets
)
if not supported:
continue
for target in supported:
best_targets.add(target.name)
self.gen.fact(
fn.compiler_supports_target(compiler.name, compiler.version, target.name)
)
self.gen.fact(fn.compiler_supports_target(compiler_id, target.name))
self.gen.fact(
fn.compiler_supports_target(compiler.name, compiler.version, uarch.family.name)
)
self.gen.fact(fn.compiler_supports_target(compiler_id, uarch.family.name))
self.gen.newline()
i = 0 # TODO compute per-target offset?
@@ -2613,6 +2718,8 @@ def setup(
self.explicitly_required_namespaces[node.name] = node.namespace
self.gen = ProblemInstanceBuilder()
compiler_parser = CompilerParser(configuration=spack.config.CONFIG).with_input_specs(specs)
if using_libc_compatibility():
for libc in self.libcs:
self.gen.fact(fn.host_libc(libc.name, libc.version))
@@ -2641,11 +2748,11 @@ def setup(
if reuse:
self.gen.fact(fn.optimize_for_reuse())
for reusable_spec in reuse:
compiler_parser.add_compiler_from_concrete_spec(reusable_spec)
self.register_concrete_spec(reusable_spec, self.pkgs)
self.concrete_specs()
_ = spack.compilers.config.all_compilers(init_config=True)
self.possible_compilers = possible_compilers(configuration=spack.config.CONFIG)
self.possible_compilers = compiler_parser.possible_compilers()
self.gen.h1("Generic statements on possible packages")
node_counter.possible_packages_facts(self.gen, fn)
@@ -2657,6 +2764,7 @@ def setup(
self.gen.h1("General Constraints")
self.config_compatible_os()
self.compiler_facts()
# architecture defaults
self.platform_defaults()
@@ -2710,8 +2818,9 @@ def setup(
self.gen.h1("Variant Values defined in specs")
self.define_variant_values()
self.gen.h1("Runtimes")
self.define_runtime_constraints()
if WITH_RUNTIME:
self.gen.h1("Runtimes")
self.define_runtime_constraints()
self.gen.h1("Version Constraints")
self.collect_virtual_constraints()
@@ -2751,37 +2860,35 @@ def define_runtime_constraints(self):
recorder = RuntimePropertyRecorder(self)
for compiler in self.possible_compilers:
compiler_with_different_cls_names = {
"oneapi": "intel-oneapi-compilers",
"clang": "llvm",
}
compiler_cls_name = compiler_with_different_cls_names.get(
compiler.spec.name, compiler.spec.name
)
try:
compiler_cls = spack.repo.PATH.get_pkg_class(compiler.name)
compiler_cls = spack.repo.PATH.get_pkg_class(compiler_cls_name)
if hasattr(compiler_cls, "runtime_constraints"):
compiler_cls.runtime_constraints(spec=compiler.spec, pkg=recorder)
except spack.repo.UnknownPackageError:
pass
else:
if hasattr(compiler_cls, "runtime_constraints"):
compiler_cls.runtime_constraints(spec=compiler, pkg=recorder)
# Inject default flags for compilers
recorder("*").default_flags(compiler)
if not using_libc_compatibility():
# Inject libc from available compilers, on Linux
if not compiler.available:
continue
current_libc = CompilerPropertyDetector(compiler).default_libc()
# If this is a compiler yet to be built infer libc from the Python process
# FIXME (compiler as nodes): recover this use case
# if not current_libc and compiler.compiler_obj.cc is None:
# current_libc = spack.util.libc.libc_from_current_python_process()
current_libc = compiler.compiler_obj.default_libc
if current_libc:
if using_libc_compatibility() and current_libc:
recorder("*").depends_on(
"libc",
when=f"%{compiler.name}@{compiler.versions}",
type="link",
description=f"Add libc when using {compiler}",
"libc", when=f"%{compiler.spec}", type="link", description="Add libc"
)
recorder("*").depends_on(
str(current_libc),
when=f"%{compiler.name}@{compiler.versions}",
when=f"%{compiler.spec}",
type="link",
description=f"Libc is {current_libc} when using {compiler}",
description="Add libc",
)
recorder.consume_facts()
@@ -2817,9 +2924,6 @@ def literal_specs(self, specs):
# These facts are needed to compute the "condition_set" of the root
pkg_name = clause.args[1]
self.gen.fact(fn.mentioned_in_literal(trigger_id, root_name, pkg_name))
elif clause_name == "depends_on":
pkg_name = clause.args[2]
self.gen.fact(fn.mentioned_in_literal(trigger_id, root_name, pkg_name))
requirements.append(fn.attr("virtual_root" if spec.virtual else "root", spec.name))
cache[imposed_spec_key] = (effect_id, requirements)
@@ -2916,6 +3020,8 @@ class _Head:
node_os = fn.attr("node_os_set")
node_target = fn.attr("node_target_set")
variant_value = fn.attr("variant_set")
node_compiler = fn.attr("node_compiler_set")
node_compiler_version = fn.attr("node_compiler_version_set")
node_flag = fn.attr("node_flag_set")
propagate = fn.attr("propagate")
@@ -2930,6 +3036,8 @@ class _Body:
node_os = fn.attr("node_os")
node_target = fn.attr("node_target")
variant_value = fn.attr("variant_value")
node_compiler = fn.attr("node_compiler")
node_compiler_version = fn.attr("node_compiler_version")
node_flag = fn.attr("node_flag")
propagate = fn.attr("propagate")
@@ -3159,66 +3267,119 @@ def reject_requirement_constraint(
self, pkg_name: str, *, constraint: spack.spec.Spec, kind: RequirementKind
) -> bool:
"""Returns True if a requirement constraint should be rejected"""
# If it's a specific package requirement, it's never rejected
if kind != RequirementKind.DEFAULT:
return False
# Reject default requirements for runtimes and compilers
if pkg_name in spack.repo.PATH.packages_with_tags("runtime"):
return True
if pkg_name in spack.repo.PATH.packages_with_tags("compiler"):
return True
# Requirements under all: are applied only if they are satisfiable considering only
# package rules, so e.g. variants must exist etc. Otherwise, they are rejected.
try:
s = spack.spec.Spec(pkg_name)
s.constrain(constraint)
s.validate_or_raise()
except spack.error.SpackError as e:
tty.debug(
f"[SETUP] Rejecting the default '{constraint}' requirement "
f"on '{pkg_name}': {str(e)}",
level=2,
)
return True
if kind == RequirementKind.DEFAULT:
# Requirements under all: are applied only if they are satisfiable considering only
# package rules, so e.g. variants must exist etc. Otherwise, they are rejected.
try:
s = spack.spec.Spec(pkg_name)
s.constrain(constraint)
s.validate_or_raise()
except spack.error.SpackError as e:
tty.debug(
f"[SETUP] Rejecting the default '{constraint}' requirement "
f"on '{pkg_name}': {str(e)}",
level=2,
)
return True
return False
def possible_compilers(*, configuration) -> List["spack.spec.Spec"]:
result = set()
for c in spack.compilers.config.all_compilers_from(configuration):
# FIXME (compiler as nodes): Discard early specs that are not marked for this target?
class CompilerParser:
"""Parses configuration files, and builds a list of possible compilers for the solve."""
if using_libc_compatibility() and not c_compiler_runs(c):
try:
compiler = c.extra_attributes["compilers"]["c"]
def __init__(self, configuration) -> None:
self.compilers: Set[KnownCompiler] = set()
for c in spack.compilers.all_compilers_from(configuration):
if using_libc_compatibility() and not c_compiler_runs(c):
tty.debug(
f"the C compiler {compiler} does not exist, or does not run correctly."
f" The compiler {c} will not be used during concretization."
f"the C compiler {c.cc} does not exist, or does not run correctly."
f" The compiler {c.spec} will not be used during concretization."
)
except KeyError:
tty.debug(f"the spec {c} does not provide a C compiler.")
continue
continue
if using_libc_compatibility() and not c.default_libc:
warnings.warn(
f"cannot detect libc from {c.spec}. The compiler will not be used "
f"during concretization."
)
continue
if using_libc_compatibility() and not CompilerPropertyDetector(c).default_libc():
warnings.warn(
f"cannot detect libc from {c}. The compiler will not be used "
f"during concretization."
target = c.target if c.target != "any" else None
candidate = KnownCompiler(
spec=c.spec, os=c.operating_system, target=target, available=True, compiler_obj=c
)
continue
if candidate in self.compilers:
warnings.warn(
f"duplicate found for {c.spec} on {c.operating_system}/{c.target}. "
f"Edit your compilers.yaml configuration to remove it."
)
continue
if c in result:
warnings.warn(f"duplicate {c} compiler found. Edit your packages.yaml to remove it.")
continue
self.compilers.add(candidate)
result.add(c)
def with_input_specs(self, input_specs: List["spack.spec.Spec"]) -> "CompilerParser":
"""Accounts for input specs when building the list of possible compilers.
result = list(result)
result.sort()
return result
Args:
input_specs: specs to be concretized
"""
strict = spack.concretize.CHECK_COMPILER_EXISTENCE
default_os = str(spack.platforms.host().default_os)
default_target = str(archspec.cpu.host().family)
for s in traverse.traverse_nodes(input_specs):
# we don't need to validate compilers for already-built specs
if s.concrete or not s.compiler:
continue
version = s.compiler.versions.concrete
if not version or any(item.spec.satisfies(s.compiler) for item in self.compilers):
continue
# Error when a compiler is not found and strict mode is enabled
if strict:
raise spack.concretize.UnavailableCompilerVersionError(s.compiler)
# Make up a compiler matching the input spec. This is for bootstrapping.
compiler_cls = spack.compilers.class_for_compiler_name(s.compiler.name)
compiler_obj = compiler_cls(
s.compiler, operating_system=default_os, target=default_target, paths=[None] * 4
)
self.compilers.add(
KnownCompiler(
spec=s.compiler,
os=default_os,
target=default_target,
available=True,
compiler_obj=compiler_obj,
)
)
return self
def add_compiler_from_concrete_spec(self, spec: "spack.spec.Spec") -> None:
"""Account for compilers that are coming from concrete specs, through reuse.
Args:
spec: concrete spec to be reused
"""
assert spec.concrete, "the spec argument must be concrete"
candidate = KnownCompiler(
spec=spec.compiler,
os=str(spec.architecture.os),
target=str(spec.architecture.target.family),
available=False,
compiler_obj=None,
)
self.compilers.add(candidate)
def possible_compilers(self) -> List[KnownCompiler]:
# Here we have to sort two times, first sort by name and ascending version
result = sorted(self.compilers, key=lambda x: (x.spec.name, x.spec.version), reverse=True)
# Then stable sort to prefer available compilers and account for preferences
ppk = spack.package_prefs.PackagePrefs("all", "compiler", all=False)
result.sort(key=lambda x: (not x.available, ppk(x.spec)))
return result
class RuntimePropertyRecorder:
@@ -3261,7 +3422,15 @@ def reset(self):
"""Resets the current state."""
self.current_package = None
def depends_on(self, dependency_str: str, *, when: str, type: str, description: str) -> None:
def depends_on(
self,
dependency_str: str,
*,
when: str,
type: str,
description: str,
languages: Optional[List[str]] = None,
) -> None:
"""Injects conditional dependencies on packages.
Conditional dependencies can be either "real" packages or virtual dependencies.
@@ -3270,6 +3439,7 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
dependency_str: the dependency spec to inject
when: anonymous condition to be met on a package to have the dependency
type: dependency type
languages: languages needed by the package for the dependency to be considered
description: human-readable description of the rule for adding the dependency
"""
# TODO: The API for this function is not final, and is still subject to change. At
@@ -3285,10 +3455,25 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
if dependency_spec.versions != vn.any_version:
self._setup.version_constraints.add((dependency_spec.name, dependency_spec.versions))
body_str, node_variable = self.rule_body_from(when_spec)
placeholder = "XXX"
node_variable = "node(ID, Package)"
when_spec.name = placeholder
body_clauses = self._setup.spec_clauses(when_spec, body=True)
body_str = (
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
f" not external({node_variable}),\n"
f" not runtime(Package)"
).replace(f'"{placeholder}"', f"{node_variable}")
if languages:
body_str += ",\n"
for language in languages:
body_str += f' attr("language", {node_variable}, "{language}")'
head_clauses = self._setup.spec_clauses(dependency_spec, body=False)
runtime_pkg = dependency_spec.name
is_virtual = head_clauses[0].args[0] == "virtual_node"
main_rule = (
f"% {description}\n"
@@ -3323,38 +3508,6 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
self.reset()
@staticmethod
def node_for(name: str) -> str:
return f'node(ID{name.replace("-", "_")}, "{name}")'
def rule_body_from(self, when_spec: "spack.spec.Spec") -> Tuple[str, str]:
"""Computes the rule body from a "when" spec, and returns it, along with the
node variable.
"""
node_placeholder = "XXX"
node_variable = "node(ID, Package)"
when_substitutions = {}
for s in when_spec.traverse(root=False):
when_substitutions[f'"{s.name}"'] = self.node_for(s.name)
when_spec.name = node_placeholder
body_clauses = self._setup.spec_clauses(when_spec, body=True)
for clause in body_clauses:
if clause.args[0] == "virtual_on_incoming_edges":
# Substitute: attr("virtual_on_incoming_edges", ProviderNode, Virtual)
# with: attr("virtual_on_edge", ParentNode, ProviderNode, Virtual)
# (avoid adding virtuals everywhere, if a single edge needs it)
_, provider, virtual = clause.args
clause.args = "virtual_on_edge", node_placeholder, provider, virtual
body_str = (
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
f" not external({node_variable}),\n"
f" not runtime(Package)"
).replace(f'"{node_placeholder}"', f"{node_variable}")
for old, replacement in when_substitutions.items():
body_str = body_str.replace(old, replacement)
return body_str, node_variable
def requires(self, impose: str, *, when: str):
"""Injects conditional requirements on a given package.
@@ -3369,6 +3522,7 @@ def requires(self, impose: str, *, when: str):
when_spec = spack.spec.Spec(f"{self.current_package}{when}")
assert imposed_spec.versions.concrete, f"{impose} must have a concrete version"
assert when_spec.compiler.concrete, f"{when} must have a concrete compiler"
# Add versions to possible versions
for s in (imposed_spec, when_spec):
@@ -3389,54 +3543,32 @@ def propagate(self, constraint_str: str, *, when: str):
when_spec = spack.spec.Spec(when)
assert when_spec.name is None, "only anonymous when specs are accepted"
when_substitutions = {}
for s in when_spec.traverse(root=False):
when_substitutions[f'"{s.name}"'] = self.node_for(s.name)
placeholder = "XXX"
node_variable = "node(ID, Package)"
when_spec.name = placeholder
body_clauses = self._setup.spec_clauses(when_spec, body=True)
body_str = (
f" {f',{os.linesep} '.join(str(x) for x in body_clauses)},\n"
f" not external({node_variable}),\n"
f" not runtime(Package)"
).replace(f'"{placeholder}"', f"{node_variable}")
body_str, node_variable = self.rule_body_from(when_spec)
constraint_spec = spack.spec.Spec(constraint_str)
assert constraint_spec.name is None, "only anonymous constraint specs are accepted"
# constraint_spec.name = placeholder
constraint_spec.name = placeholder
constraint_clauses = self._setup.spec_clauses(constraint_spec, body=False)
for clause in constraint_clauses:
if clause.args[0] == "node_version_satisfies":
self._setup.version_constraints.add(
(constraint_spec.name, constraint_spec.versions)
)
args = f'"{constraint_spec.name}", "{constraint_spec.versions}"'
head_str = f"propagate({node_variable}, node_version_satisfies({args}))"
if clause.args[0] == "node_compiler_version_satisfies":
self._setup.compiler_version_constraints.add(constraint_spec.compiler)
args = f'"{constraint_spec.compiler.name}", "{constraint_spec.compiler.versions}"'
head_str = f"propagate({node_variable}, node_compiler_version_satisfies({args}))"
rule = f"{head_str} :-\n{body_str}.\n\n"
self.rules.append(rule)
self.reset()
def default_flags(self, spec: "spack.spec.Spec"):
if not spec.external or "flags" not in spec.extra_attributes:
self.reset()
return
when_spec = spack.spec.Spec(f"^[deptypes=build] {spec}")
body_str, node_variable = self.rule_body_from(when_spec)
node_placeholder = "XXX"
flags = spec.extra_attributes["flags"]
root_spec_str = f"{node_placeholder}"
for flag_type, default_values in flags.items():
root_spec_str = f"{root_spec_str} {flag_type}='{default_values}'"
root_spec = spack.spec.Spec(root_spec_str)
head_clauses = self._setup.spec_clauses(
root_spec, body=False, context=SourceContext(source="compiler")
)
self.rules.append(f"% Default compiler flags for {spec}\n")
for clause in head_clauses:
if clause.args[0] == "node":
continue
head_str = str(clause).replace(f'"{node_placeholder}"', f"{node_variable}")
rule = f"{head_str} :-\n{body_str}.\n\n"
self.rules.append(rule)
self.reset()
def consume_facts(self):
"""Consume the facts collected by this object, and emits rules and
facts for the runtimes.
@@ -3484,6 +3616,7 @@ class SpecBuilder:
r"^compatible_libc$",
r"^dependency_holds$",
r"^external_conditions_hold$",
r"^node_compiler$",
r"^package_hash$",
r"^root$",
r"^track_dependencies$",
@@ -3564,6 +3697,10 @@ def variant_selected(self, node, name, value, variant_type, variant_id):
def version(self, node, version):
self._specs[node].versions = vn.VersionList([vn.Version(version)])
def node_compiler_version(self, node, compiler, version):
self._specs[node].compiler = spack.spec.CompilerSpec(compiler)
self._specs[node].compiler.versions = vn.VersionList([vn.Version(version)])
def node_flag(self, node, node_flag):
self._specs[node].compiler_flags.add_flag(
node_flag.flag_type, node_flag.flag, False, node_flag.flag_group, node_flag.source
@@ -3618,14 +3755,17 @@ def reorder_flags(self):
e.g. for `y cflags="-z -a"` "-z" and "-a" should never have any intervening
flags inserted, and should always appear in that order.
"""
cmd_specs = {s.name: s for spec in self._command_line_specs for s in spec.traverse()}
# reverse compilers so we get highest priority compilers that share a spec
compilers = dict(
(c.spec, c) for c in reversed(spack.compilers.all_compilers_from(spack.config.CONFIG))
)
cmd_specs = dict((s.name, s) for spec in self._command_line_specs for s in spec.traverse())
for spec in self._specs.values():
# if bootstrapping, compiler is not in config and has no flags
flagmap_from_compiler = {
flag_type: [x for x in values if x.source == "compiler"]
for flag_type, values in spec.compiler_flags.items()
}
flagmap_from_compiler = {}
if spec.compiler in compilers:
flagmap_from_compiler = compilers[spec.compiler].flags
for flag_type in spec.compiler_flags.valid_compiler_flags():
node = SpecBuilder.make_node(pkg=spec.name)
@@ -3675,7 +3815,7 @@ def _order_index(flag_group):
for grp in prioritized_groups:
grp_flags = tuple(
x for (x, y) in spack.compilers.flags.tokenize_flags(grp.flag_group)
x for (x, y) in spack.compiler.tokenize_flags(grp.flag_group)
)
if grp_flags == from_compiler:
continue
@@ -3780,8 +3920,9 @@ def sort_fn(function_tuple) -> Tuple[int, int]:
return (-1, 0)
def build_specs(self, function_tuples):
# Functions don't seem to be in particular order in output. Sort them here so that
# directives that build objects, like node, are called in the right order.
# Functions don't seem to be in particular order in output. Sort
# them here so that directives that build objects (like node and
# node_compiler) are called in the right order.
self.function_tuples = sorted(set(function_tuples), key=self.sort_fn)
self._specs = {}
for name, args in self.function_tuples:
@@ -3845,14 +3986,6 @@ def build_specs(self, function_tuples):
for root in roots.values():
root._finalize_concretization()
# Unify hashes (this is to avoid duplicates of runtimes and compilers)
unifier = ConcreteSpecsByHash()
keys = list(self._specs)
for key in keys:
current_spec = self._specs[key]
unifier.add(current_spec)
self._specs[key] = unifier[current_spec.dag_hash()]
self._resolve_automatic_splices()
for s in self._specs.values():
@@ -3973,10 +4106,13 @@ def _is_reusable(spec: spack.spec.Spec, packages, local: bool) -> bool:
def _has_runtime_dependencies(spec: spack.spec.Spec) -> bool:
if "gcc" in spec and "gcc-runtime" not in spec:
if not WITH_RUNTIME:
return True
if spec.compiler.name == "gcc" and not spec.dependencies("gcc-runtime"):
return False
if "intel-oneapi-compilers" in spec and "intel-oneapi-runtime" not in spec:
if spec.compiler.name == "oneapi" and not spec.dependencies("intel-oneapi-runtime"):
return False
return True
@@ -4225,41 +4361,10 @@ def _check_input_and_extract_concrete_specs(specs):
reusable = []
for root in specs:
for s in root.traverse():
candidates = s.edges_to_dependencies(depflag=dt.BUILD)
if candidates:
virtuals = set()
non_virtuals = spack.package_base.possible_dependencies(
s, transitive=False, virtuals=virtuals
)
possible_direct_deps = set(non_virtuals) | virtuals
not_possible = set(
[
x.spec.name
for x in candidates
if x.direct and x.spec.name not in possible_direct_deps
]
)
if not_possible:
start_str = f"'{s}' in '{root}'"
if s == root:
start_str = f"'{root}'"
raise UnsatisfiableSpecError(
f"{start_str} cannot have a dependency on {', '.join(not_possible)}, "
f"according to its recipe"
)
if s.virtual:
continue
if s.concrete:
reusable.append(s)
try:
s.package_class
except spack.repo.UnknownPackageError:
raise UnsatisfiableSpecError(
f"cannot concretize '{root}', since '{s.name}' does not exist"
)
spack.spec.Spec.ensure_valid_variants(s)
return reusable

View File

@@ -39,6 +39,8 @@
internal_error("Only nodes can have node_os").
:- attr("node_target", PackageNode, _), not attr("node", PackageNode),
internal_error("Only nodes can have node_target").
:- attr("node_compiler_version", PackageNode, _, _), not attr("node", PackageNode),
internal_error("Only nodes can have node_compiler_version").
:- attr("variant_value", PackageNode, _, _), not attr("node", PackageNode),
internal_error("variant_value true for a non-node").
:- attr("node_flag", PackageNode, _), not attr("node", PackageNode),
@@ -145,15 +147,6 @@ unification_set(SetID, VirtualNode)
max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1,
internal_error("virtual node skipped id number").
% Prefer to assign lower ID to virtuals associated with a lower penalty provider
:- not unification_set("root", node(X, Virtual)),
not unification_set("root", node(Y, Virtual)),
X < Y,
provider_weight(_, node(X, Virtual), WeightX),
provider_weight(_, node(Y, Virtual), WeightY),
WeightY < WeightX.
%-----------------------------------------------------------------------------
% Map literal input specs to facts that drive the solve
%-----------------------------------------------------------------------------
@@ -224,6 +217,14 @@ error(100, multiple_values_error, Attribute, Package)
attr_single_value(Attribute),
2 { attr(Attribute, node(ID, Package), Value) }.
%-----------------------------------------------------------------------------
% Languages used
%-----------------------------------------------------------------------------
attr("language", node(X, Package), Language) :-
condition_holds(ConditionID, node(X, Package)),
pkg_fact(Package,language(ConditionID, Language)).
%-----------------------------------------------------------------------------
% Version semantics
%-----------------------------------------------------------------------------
@@ -316,21 +317,18 @@ possible_version_weight(node(ID, Package), Weight)
% If there is at least a version that satisfy the constraint, impose a lower
% bound on the choice rule to avoid false positives with the error below
{ attr("version", node(ID, Package), Version) : pkg_fact(Package, version_satisfies(Constraint, Version)) }
1 { attr("version", node(ID, Package), Version) : pkg_fact(Package, version_satisfies(Constraint, Version)) }
:- attr("node_version_satisfies", node(ID, Package), Constraint),
pkg_fact(Package, version_satisfies(Constraint, _)).
pkg_fact(Package, version_satisfies(Constraint, _)),
internal_error("must choose a single version to satisfy version constraints").
% More specific error message if the version cannot satisfy some constraint
% Otherwise covered by `no_version_error` and `versions_conflict_error`.
error(1, "Cannot satisfy '{0}@{1}'", Package, Constraint)
error(10, "Cannot satisfy '{0}@{1}'", Package, Constraint)
:- attr("node_version_satisfies", node(ID, Package), Constraint),
attr("version", node(ID, Package), Version),
not pkg_fact(Package, version_satisfies(Constraint, Version)).
error(10, "Cannot satisfy '{0}@{1}'", Package, Constraint)
:- attr("node_version_satisfies", node(ID, Package), Constraint),
not attr("version", node(ID, Package), _).
attr("node_version_satisfies", node(ID, Package), Constraint)
:- attr("version", node(ID, Package), Version),
pkg_fact(Package, version_satisfies(Constraint, Version)).
@@ -379,7 +377,6 @@ trigger_node(ID, node(PackageID, Package), node(VirtualID, Virtual)) :- pkg_fact
condition_nodes(TriggerID, PackageNode, node(X, A1))
:- condition_packages(TriggerID, A1),
condition_set(PackageNode, node(X, A1)),
not self_build_requirement(PackageNode, node(X, A1)),
trigger_node(TriggerID, PackageNode, _).
cannot_hold(TriggerID, PackageNode)
@@ -394,7 +391,6 @@ trigger_condition_holds(ID, RequestorNode) :-
attr(Name, node(X, A1), A2, A3) : condition_requirement(ID, Name, A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), not multiple_nodes_attribute(Name);
attr(Name, node(X, A1), A2, A3, A4) : condition_requirement(ID, Name, A1, A2, A3, A4), condition_nodes(ID, PackageNode, node(X, A1));
% Special cases
attr("depends_on", node(X, A1), node(Y, A2), A3) : condition_requirement(ID, "depends_on", A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), condition_nodes(ID, PackageNode, node(Y, A2));
not cannot_hold(ID, PackageNode).
condition_holds(ConditionID, node(X, Package))
@@ -424,12 +420,7 @@ imposed_nodes(EffectID, node(NodeID, Package), node(X, A1))
pkg_fact(Package, condition_effect(ID, EffectID)),
imposed_packages(EffectID, A1),
condition_set(node(NodeID, Package), node(X, A1)),
trigger_node(TriggerID, _, node(NodeID, Package)),
% We don't want to add build requirements to imposed nodes, to avoid
% unsat problems when we deal with self-dependencies: gcc@14 %gcc@10
not self_build_requirement(node(NodeID, Package), node(X, A1)).
self_build_requirement(node(X, Package), node(Y, Package)) :- build_requirement(node(X, Package), node(Y, Package)).
trigger_node(TriggerID, _, node(NodeID, Package)).
imposed_nodes(ConditionID, PackageNode, node(X, A1))
:- imposed_packages(ConditionID, A1),
@@ -465,45 +456,6 @@ provider(ProviderNode, VirtualNode) :- attr("provider_set", ProviderNode, Virtua
imposed_constraint(ID, "depends_on", A1, A2, A3),
internal_error("Build deps must land in exactly one duplicate").
% The rule below accounts for expressions like:
%
% root ^dep %compiler
%
% where "compiler" is a dependency of "dep", but is enforced by a condition imposed by "root"
1 { attr("depends_on", node(min_dupe_id, A1), node(0..Y-1, A2), A3) : max_dupes(A2, Y) } 1
:- impose(ID, RootNode),
unification_set("root", RootNode),
unification_set("root", node(min_dupe_id, A1)),
imposed_constraint(ID, "depends_on", A1, A2, A3),
internal_error("Build deps must land in exactly one duplicate").
1 { build_requirement(node(X, Parent), node(0..Y-1, BuildDependency)) : max_dupes(BuildDependency, Y) } 1
:- attr("build_requirement", node(X, Parent), build_requirement("node", BuildDependency)),
impose(ID, node(X, Parent)),
imposed_constraint(ID,"build_requirement",Parent,_).
1 { virtual_build_requirement(ParentNode, node(0..Y-1, Virtual)) : max_dupes(Virtual, Y) } 1
:- attr("dependency_holds", ParentNode, Virtual, "build"),
not attr("dependency_holds", ParentNode, Virtual,"link"),
not attr("dependency_holds", ParentNode, Virtual,"run"),
virtual(Virtual).
attr("virtual_node", VirtualNode) :- virtual_build_requirement(ParentNode, VirtualNode).
build_requirement(ParentNode, ProviderNode) :- virtual_build_requirement(ParentNode, VirtualNode), provider(ProviderNode, VirtualNode).
% From cli we can have literal expressions like:
%
% root %gcc@12.0 ^dep %gcc@11.2
%
% Adding a "build_requirement" is a way to discriminate between the incompatible
% version constraints on "gcc" in the "imposed_constraint".
attr("node_version_satisfies", node(X, BuildDependency), Constraint) :-
attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, Constraint)),
build_requirement(ParentNode, node(X, BuildDependency)).
attr("depends_on", node(X, Parent), node(Y, BuildDependency), "build") :- build_requirement(node(X, Parent), node(Y, BuildDependency)).
% Reconstruct virtual dependencies for reused specs
attr("virtual_on_edge", node(X, A1), node(Y, A2), Virtual)
:- impose(ID, node(X, A1)),
@@ -543,12 +495,9 @@ virtual_condition_holds(node(Y, A2), Virtual)
%-----------------------------------------------------------------------------
% Concrete specs
%-----------------------------------------------------------------------------
% if a package is assigned a hash, it's concrete.
concrete(PackageNode) :- attr("hash", PackageNode, _), attr("node", PackageNode).
:- concrete(PackageNode), depends_on(PackageNode, DependencyNode), not concrete(DependencyNode).
%-----------------------------------------------------------------------------
% Dependency semantics
%-----------------------------------------------------------------------------
@@ -570,45 +519,11 @@ attr("track_dependencies", Node) :- build(Node), not external(Node).
% this ensures a user can't say `zlib ^libiconv` (neither of which have any
% dependencies) and get a two-node unconnected graph
needed(PackageNode) :- attr("root", PackageNode).
needed(ChildNode) :- edge_needed(ParentNode, ChildNode).
edge_needed(ParentNode, node(X, Child)) :- depends_on(ParentNode, node(X, Child)), runtime(Child).
edge_needed(ParentNode, ChildNode) :- depends_on(ParentNode, ChildNode) , concrete(ParentNode).
edge_needed(ParentNode, node(X, Child)) :-
depends_on(ParentNode, node(X, Child)),
build(ParentNode),
attr("dependency_holds", ParentNode, Child, _).
virtual_edge_needed(ParentNode, ChildNode, node(X, Virtual)) :-
depends_on(ParentNode, ChildNode),
build(ParentNode),
node_depends_on_virtual(ParentNode, Virtual),
provider(ChildNode, node(X, Virtual)).
virtual_edge_needed(ParentNode, ChildNode, node(X, Virtual)) :-
concrete(ParentNode),
concrete(ChildNode),
provider(ChildNode, node(X, Virtual)),
attr("virtual_on_edge", ParentNode, ChildNode, Virtual).
edge_needed(ParentNode, ChildNode) :- virtual_edge_needed(ParentNode, ChildNode, _).
provider_needed(ChildNode, VirtualNode) :- virtual_edge_needed(_, ChildNode, VirtualNode).
provider_needed(ChildNode, VirtualNode) :- attr("virtual_root", VirtualNode), provider(ChildNode, VirtualNode).
needed(DependencyNode) :- needed(PackageNode), depends_on(PackageNode, DependencyNode).
error(10, "'{0}' is not a valid dependency for any package in the DAG", Package)
:- attr("node", node(ID, Package)),
not needed(node(ID, Package)).
:- depends_on(ParentNode, ChildNode),
not edge_needed(ParentNode, ChildNode),
build(ParentNode).
:- provider(PackageNode, VirtualNode),
not provider_needed(PackageNode, VirtualNode),
not attr("virtual_root", VirtualNode).
#defined dependency_type/2.
%-----------------------------------------------------------------------------
@@ -635,18 +550,14 @@ possible_provider_weight(ProviderNode, VirtualNode, 0, "Set on the command line"
% Enforces all virtuals to be provided, if multiple of them are provided together
error(100, "Package '{0}' needs to provide both '{1}' and '{2}' together, but provides only '{1}'", Package, Virtual1, Virtual2)
:- % This package provides 2 or more virtuals together
condition_holds(ID, node(X, Package)),
:- condition_holds(ID, node(X, Package)),
pkg_fact(Package, provided_together(ID, SetID, Virtual1)),
pkg_fact(Package, provided_together(ID, SetID, Virtual2)),
Virtual1 != Virtual2,
% One node depends on those virtuals AND on this package
node_depends_on_virtual(ClientNode, Virtual1),
node_depends_on_virtual(ClientNode, Virtual2),
depends_on(ClientNode, node(X, Package)),
% But this package is a provider of only one of them
provider(node(X, Package), node(_, Virtual1)),
not provider(node(X, Package), node(_, Virtual2)).
attr("virtual_on_incoming_edges", node(X, Package), Virtual1),
not attr("virtual_on_incoming_edges", node(X, Package), Virtual2),
attr("virtual_node", node(_, Virtual1)),
attr("virtual_node", node(_, Virtual2)).
% if a package depends on a virtual, it's not external and we have a
% provider for that virtual then it depends on the provider
@@ -725,7 +636,6 @@ do_not_impose(EffectID, node(X, Package))
virtual_condition_holds(_, PossibleProvider, Virtual),
PossibleProvider != ProviderNode,
explicitly_requested_root(PossibleProvider),
not self_build_requirement(PossibleProvider, ProviderNode),
not explicitly_requested_root(ProviderNode),
internal_error("If a root can provide a virtual, it must be the provider").
@@ -1225,10 +1135,9 @@ error(100, "Cannot propagate the variant '{0}' from the package: {1} because pac
propagated_flag(node(PackageID, Package), node_flag(FlagType, Flag, FlagGroup, Source), SourceNode) :-
propagate(node(PackageID, Package), node_flag(FlagType, Flag, FlagGroup, Source), _),
not attr("node_flag_set", node(PackageID, Package), node_flag(FlagType, _, _, "literal")),
% FIXME (compiler as nodes): do we need to match the compiler?
% Same compiler as propagation source
% node_compiler(node(PackageID, Package), CompilerID),
% node_compiler(SourceNode, CompilerID),
node_compiler(node(PackageID, Package), CompilerID),
node_compiler(SourceNode, CompilerID),
attr("propagate", SourceNode, node_flag(FlagType, Flag, FlagGroup, Source), _),
node(PackageID, Package) != SourceNode,
not runtime(Package).
@@ -1236,7 +1145,7 @@ propagated_flag(node(PackageID, Package), node_flag(FlagType, Flag, FlagGroup, S
attr("node_flag", PackageNode, NodeFlag) :- propagated_flag(PackageNode, NodeFlag, _).
% Cannot propagate the same flag from two distinct sources
error(100, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Source1, Source2, FlagType, Package) :-
error(100, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Source1, Source2, Package, FlagType) :-
propagated_flag(node(ID, Package), node_flag(FlagType, _, _, _), node(_, Source1)),
propagated_flag(node(ID, Package), node_flag(FlagType, _, _, _), node(_, Source2)),
Source1 < Source2.
@@ -1245,17 +1154,12 @@ error(100, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Sour
% Compiler constraints
%----
% If a node is built, impose constraints on the compiler coming from dependents
attr("node_version_satisfies", node(Y, Compiler), VersionRange) :-
propagate(node(X, Package), node_version_satisfies(Compiler, VersionRange)),
attr("depends_on", node(X, Package), node(Y, Compiler), "build"),
not external(node(X, Package)),
not runtime(Package).
attr("node_version_satisfies", node(X, Runtime), VersionRange) :-
attr("node", node(X, Runtime)),
attr("compatible_runtime", PackageNode, Runtime, VersionRange),
concrete(PackageNode).
attr("node_compiler_version_satisfies", node(ID, Package), Compiler, Version) :-
propagate(node(ID, Package), node_compiler_version_satisfies(Compiler, Version)),
node_compiler(node(ID, Package), CompilerID),
compiler_name(CompilerID, Compiler),
not runtime(Package),
not external(Package).
%-----------------------------------------------------------------------------
% Runtimes
@@ -1264,19 +1168,11 @@ attr("node_version_satisfies", node(X, Runtime), VersionRange) :-
% Check whether the DAG has any built package
has_built_packages() :- build(X), not external(X).
% "gcc-runtime" is always built
:- concrete(node(X, "gcc-runtime")), has_built_packages().
% FIXME (compiler as nodes): is this version constraint always required and better than the callback?
% "gcc-runtime" and the "gcc" it depends on must be at the same version
% attr("version", node(Y, "gcc"), Version) :-
% attr("version", node(X, "gcc-runtime"), Version),
% attr("depends_on", node(X, "gcc-runtime"), node(Y, "gcc"), "build").
% The "gcc" linked to "gcc-runtime" must be used by at least another package
:- attr("depends_on", node(X, "gcc-runtime"), node(Y, "gcc"), "build"),
not 2 { attr("depends_on", PackageNode, node(Y, "gcc"), "build") : attr("node", PackageNode) }.
% If we build packages, the runtime nodes must use an available compiler
1 { node_compiler(PackageNode, CompilerID) : build(PackageNode), not external(PackageNode) } :-
has_built_packages(),
runtime(RuntimePackage),
node_compiler(node(_, RuntimePackage), CompilerID).
%-----------------------------------------------------------------------------
% Platform semantics
@@ -1349,7 +1245,7 @@ attr("node_target_satisfies", PackageNode, Constraint)
% If a node has a target, all of its dependencies must be compatible with that target
error(100, "Cannot find compatible targets for {0} and {1}", Package, Dependency)
:- attr("depends_on", node(X, Package), node(Y, Dependency), Type), Type != "build",
:- depends_on(node(X, Package), node(Y, Dependency)),
attr("node_target", node(X, Package), Target),
not node_target_compatible(node(Y, Dependency), Target).
@@ -1361,27 +1257,29 @@ node_target_compatible(PackageNode, Target)
target_compatible(Target, MyTarget).
#defined target_satisfies/2.
compiler(Compiler) :- compiler_supports_target(Compiler, _, _).
% Can't use targets on node if the compiler for the node doesn't support them
language("c").
language("cxx").
language("fortran").
error(10, "{0} compiler '{2}@{3}' incompatible with 'target={1}'", Package, Target, Compiler, Version)
% can't use targets on node if the compiler for the node doesn't support them
error(100, "{0} compiler '{2}@{3}' incompatible with 'target={1}'", Package, Target, Compiler, Version)
:- attr("node_target", node(X, Package), Target),
attr("virtual_on_edge", node(X, Package), node(Y, Compiler), Language),
attr("version", node(Y, Compiler), Version),
compiler(Compiler), language(Language),
not compiler_supports_target(Compiler, Version, Target),
node_compiler(node(X, Package), CompilerID),
not compiler_supports_target(CompilerID, Target),
compiler_name(CompilerID, Compiler),
compiler_version(CompilerID, Version),
build(node(X, Package)).
#defined compiler_supports_target/2.
#defined compiler_available/1.
% if a target is set explicitly, respect it
attr("node_target", PackageNode, Target)
:- attr("node", PackageNode), attr("node_target_set", PackageNode, Target).
% each node has the weight of its assigned target
target_weight(Target, 0)
:- attr("node", PackageNode),
attr("node_target", PackageNode, Target),
attr("node_target_set", PackageNode, Target).
node_target_weight(PackageNode, MinWeight)
:- attr("node", PackageNode),
attr("node_target", PackageNode, Target),
@@ -1406,12 +1304,150 @@ error(100, "'{0} target={1}' is not compatible with this machine", Package, Targ
attr("node_target", node(X, Package), Target),
not target(Target).
%-----------------------------------------------------------------------------
% Compiler semantics
%-----------------------------------------------------------------------------
% There must be only one compiler set per built node.
{ node_compiler(PackageNode, CompilerID) : compiler_id(CompilerID), compiler_available(CompilerID) } :-
attr("node", PackageNode),
build(PackageNode).
% Infer the compiler that matches a reused node
node_compiler(PackageNode, CompilerID)
:- attr("node_compiler_version", PackageNode, CompilerName, CompilerVersion),
attr("node", PackageNode),
compiler_name(CompilerID, CompilerName),
compiler_version(CompilerID, CompilerVersion),
concrete(PackageNode).
% Expand the internal attribute into "attr("node_compiler_version")
attr("node_compiler_version", PackageNode, CompilerName, CompilerVersion)
:- node_compiler(PackageNode, CompilerID),
compiler_name(CompilerID, CompilerName),
compiler_version(CompilerID, CompilerVersion),
compiler_available(CompilerID),
build(PackageNode).
attr("node_compiler", PackageNode, CompilerName)
:- attr("node_compiler_version", PackageNode, CompilerName, CompilerVersion).
error(100, "No valid compiler version found for '{0}'", Package)
:- attr("node", node(X, Package)),
not node_compiler(node(X, Package), _).
% We can't have a compiler be enforced and select the version from another compiler
error(100, "Cannot select a single compiler for package {0}", Package)
:- attr("node", node(X, Package)),
2 { attr("node_compiler_version", node(X, Package), C, V) }.
% If the compiler of a node cannot be satisfied, raise
error(10, "No valid compiler for {0} satisfies '%{1}'", Package, Compiler)
:- attr("node", node(X, Package)),
attr("node_compiler_version_satisfies", node(X, Package), Compiler, ":"),
not compiler_version_satisfies(Compiler, ":", _).
% If the compiler of a node must satisfy a constraint, then its version
% must be chosen among the ones that satisfy said constraint
error(100, "Package {0} cannot satisfy '%{1}@{2}'", Package, Compiler, Constraint)
:- attr("node", node(X, Package)),
attr("node_compiler_version_satisfies", node(X, Package), Compiler, Constraint),
not compiler_version_satisfies(Compiler, Constraint, _).
error(100, "Package {0} cannot satisfy '%{1}@{2}'", Package, Compiler, Constraint)
:- attr("node", node(X, Package)),
attr("node_compiler_version_satisfies", node(X, Package), Compiler, Constraint),
not compiler_version_satisfies(Compiler, Constraint, ID),
node_compiler(node(X, Package), ID).
% If the node is associated with a compiler and the compiler satisfy a constraint, then
% the compiler associated with the node satisfy the same constraint
attr("node_compiler_version_satisfies", PackageNode, Compiler, Constraint)
:- node_compiler(PackageNode, CompilerID),
compiler_name(CompilerID, Compiler),
compiler_version_satisfies(Compiler, Constraint, CompilerID).
#defined compiler_version_satisfies/3.
% If the compiler version was set from the command line,
% respect it verbatim
error(100, "Cannot set the required compiler: {2}%{0}@{1}", Compiler, Version, Package)
:- attr("node_compiler_version_set", node(X, Package), Compiler, Version),
not attr("node_compiler_version", node(X, Package), Compiler, Version).
error(100, "Cannot set the required compiler: {1}%{0}", Compiler, Package)
:- attr("node_compiler_set", node(X, Package), Compiler),
not attr("node_compiler_version", node(X, Package), Compiler, _).
% Cannot select a compiler if it is not supported on the OS
% Compilers that are explicitly marked as allowed
% are excluded from this check
error(100, "{0} compiler '%{1}@{2}' incompatible with 'os={3}'", Package, Compiler, Version, OS)
:- attr("node_os", node(X, Package), OS),
node_compiler(node(X, Package), CompilerID),
compiler_name(CompilerID, Compiler),
compiler_version(CompilerID, Version),
compiler_os(CompilerID, CompilerOS),
not os_compatible(CompilerOS, OS),
build(node(X, Package)).
% If a package and one of its dependencies don't have the
% same compiler there's a mismatch.
compiler_match(PackageNode, DependencyNode)
:- depends_on(PackageNode, DependencyNode),
node_compiler(PackageNode, CompilerID),
node_compiler(DependencyNode, CompilerID).
compiler_mismatch(PackageNode, DependencyNode)
:- depends_on(PackageNode, DependencyNode),
not attr("node_compiler_set", DependencyNode, _),
not compiler_match(PackageNode, DependencyNode).
compiler_mismatch_required(PackageNode, DependencyNode)
:- depends_on(PackageNode, DependencyNode),
attr("node_compiler_set", DependencyNode, _),
not compiler_match(PackageNode, DependencyNode).
#defined compiler_os/3.
% compilers weighted by preference according to packages.yaml
node_compiler_weight(node(ID, Package), Weight)
:- node_compiler(node(ID, Package), CompilerID),
compiler_name(CompilerID, Compiler),
compiler_version(CompilerID, V),
compiler_weight(CompilerID, Weight).
node_compiler_weight(node(ID, Package), 100)
:- node_compiler(node(ID, Package), CompilerID),
compiler_name(CompilerID, Compiler),
compiler_version(CompilerID, V),
not compiler_weight(CompilerID, _).
% For the time being, be strict and reuse only if the compiler match one we have on the system
error(100, "Compiler {1}@{2} requested for {0} cannot be found.", Package, Compiler, Version)
:- attr("node_compiler_version", node(ID, Package), Compiler, Version),
not node_compiler(node(ID, Package), _).
#defined node_compiler_preference/4.
#defined compiler_weight/3.
%-----------------------------------------------------------------------------
% Compiler flags
%-----------------------------------------------------------------------------
% compiler flags from compilers.yaml are put on nodes if compiler matches
attr("node_flag", PackageNode, node_flag(FlagType, Flag, FlagGroup, CompilerID))
:- compiler_flag(CompilerID, FlagType, Flag, FlagGroup),
node_compiler(PackageNode, CompilerID),
flag_type(FlagType),
compiler_id(CompilerID),
compiler_name(CompilerID, CompilerName),
compiler_version(CompilerID, Version).
attr("node_flag", PackageNode, NodeFlag) :- attr("node_flag_set", PackageNode, NodeFlag).
#defined compiler_flag/4.
%-----------------------------------------------------------------------------
% Installed Packages
%-----------------------------------------------------------------------------
@@ -1554,7 +1590,7 @@ opt_criterion(310, "requirement weight").
% Try hard to reuse installed packages (i.e., minimize the number built)
opt_criterion(110, "number of packages to build (vs. reuse)").
#minimize { 0@110: #true }.
#minimize { 1@110,PackageNode : build(PackageNode) }.
#minimize { 1@110,PackageNode : build(PackageNode), optimize_for_reuse() }.
opt_criterion(100, "number of nodes from the same package").
#minimize { 0@100: #true }.
@@ -1628,17 +1664,6 @@ opt_criterion(50, "number of non-default variants (non-roots)").
build_priority(PackageNode, Priority)
}.
% Minimize the weights of the providers, i.e. use as much as
% possible the first providers
opt_criterion(48, "number of duplicate virtuals needed").
#minimize{ 0@248: #true }.
#minimize{ 0@48: #true }.
#minimize{
Weight@48+Priority,ProviderNode,Virtual
: provider(ProviderNode, node(Weight, Virtual)),
build_priority(ProviderNode, Priority)
}.
% Minimize the weights of the providers, i.e. use as much as
% possible the most preferred providers
opt_criterion(45, "preferred providers (non-roots)").
@@ -1651,6 +1676,27 @@ opt_criterion(45, "preferred providers (non-roots)").
build_priority(ProviderNode, Priority)
}.
% Try to minimize the number of compiler mismatches in the DAG.
opt_criterion(40, "compiler mismatches that are not required").
#minimize{ 0@240: #true }.
#minimize{ 0@40: #true }.
#minimize{
1@40+Priority,PackageNode,node(ID, Dependency)
: compiler_mismatch(PackageNode, node(ID, Dependency)),
build_priority(node(ID, Dependency), Priority),
not runtime(Dependency)
}.
opt_criterion(39, "compiler mismatches that are required").
#minimize{ 0@239: #true }.
#minimize{ 0@39: #true }.
#minimize{
1@39+Priority,PackageNode,node(ID, Dependency)
: compiler_mismatch_required(PackageNode, node(ID, Dependency)),
build_priority(node(ID, Dependency), Priority),
not runtime(Dependency)
}.
opt_criterion(30, "non-preferred OS's").
#minimize{ 0@230: #true }.
#minimize{ 0@30: #true }.
@@ -1683,6 +1729,17 @@ opt_criterion(20, "default values of variants not being used (non-roots)").
build_priority(PackageNode, Priority)
}.
% Try to use preferred compilers
opt_criterion(15, "non-preferred compilers").
#minimize{ 0@215: #true }.
#minimize{ 0@15: #true }.
#minimize{
Weight@15+Priority,node(X, Package)
: node_compiler_weight(node(X, Package), Weight),
build_priority(node(X, Package), Priority),
not runtime(Package)
}.
% Minimize the number of mismatches for targets in the DAG, try
% to select the preferred target.
opt_criterion(10, "target mismatches").
@@ -1706,6 +1763,20 @@ opt_criterion(5, "non-preferred targets").
}.
% Minimize the number of compiler mismatches for runtimes
opt_criterion(4, "compiler mismatches (runtimes)").
#minimize{ 0@204: #true }.
#minimize{ 0@4: #true }.
#minimize{
1@4,PackageNode,node(ID, Dependency)
: compiler_mismatch(PackageNode, node(ID, Dependency)), runtime(Dependency)
}.
#minimize{
1@4,PackageNode,node(ID, Dependency)
: compiler_mismatch_required(PackageNode, node(ID, Dependency)), runtime(Dependency)
}.
% Choose more recent versions for runtimes
opt_criterion(3, "version badness (runtimes)").
#minimize{ 0@203: #true }.

View File

@@ -126,28 +126,22 @@ def _compute_cache_values(self):
self._possible_dependencies = set(self._link_run) | set(self._total_build)
def possible_packages_facts(self, gen, fn):
build_tools = set()
for current_tag in ("build-tools", "compiler"):
build_tools.update(spack.repo.PATH.packages_with_tags(current_tag))
build_tools = spack.repo.PATH.packages_with_tags("build-tools")
gen.h2("Packages with at most a single node")
for package_name in sorted(self.possible_dependencies() - build_tools):
gen.fact(fn.max_dupes(package_name, 1))
gen.newline()
gen.h2("Packages with multiple possible nodes (build-tools)")
gen.h2("Packages with at multiple possible nodes (build-tools)")
for package_name in sorted(self.possible_dependencies() & build_tools):
gen.fact(fn.max_dupes(package_name, 2))
gen.fact(fn.multiple_unification_sets(package_name))
gen.newline()
gen.h2("Maximum number of nodes (virtual packages)")
for package_name in sorted(self._link_run_virtuals):
for package_name in sorted(self.possible_virtuals()):
gen.fact(fn.max_dupes(package_name, 1))
gen.newline()
for package_name in sorted(self.possible_virtuals() - self._link_run_virtuals):
gen.fact(fn.max_dupes(package_name, 2))
gen.newline()
gen.h2("Possible package in link-run subDAG")
for name in sorted(self._link_run):

View File

@@ -15,8 +15,6 @@
#heuristic attr("virtual_node", node(X, Virtual)). [60, init]
#heuristic attr("virtual_node", node(X, Virtual)). [-1, sign]
#heuristic attr("virtual_node", node(0, Virtual)) : node_depends_on_virtual(PackageNode, Virtual). [1@2, sign]
#heuristic attr("virtual_node", node(0, "c")). [1@3, sign]
#heuristic attr("virtual_node", node(0, "cxx")). [1@3, sign]
#heuristic attr("depends_on", ParentNode, ChildNode, Type). [150, init]
#heuristic attr("depends_on", ParentNode, ChildNode, Type). [4, factor]
@@ -40,3 +38,6 @@
% Use default targets
#heuristic attr("node_target", node(PackageID, Package), Target). [-1, sign]
#heuristic attr("node_target", node(PackageID, Package), Target) : target_weight(Target, 0), attr("node", node(PackageID, Package)). [1@2, sign]
% Use the default compilers
#heuristic node_compiler(node(PackageID, Package), ID) : compiler_weight(ID, 0), compiler_id(ID), attr("node", node(PackageID, Package)). [30, init]

View File

@@ -9,29 +9,25 @@
% These rules are used on Linux
%=============================================================================
% A package cannot be reused if it needs a libc that is not compatible with the current one
% A package cannot be reused if the libc is not compatible with it
error(100, "Cannot reuse {0} since we cannot determine libc compatibility", ReusedPackage)
:- provider(node(X, LibcPackage), node(0, "libc")),
attr("version", node(X, LibcPackage), LibcVersion),
concrete(node(R, ReusedPackage)),
attr("needs_libc", node(R, ReusedPackage)),
attr("hash", node(R, ReusedPackage), Hash),
% Libc packages can be reused without the "compatible_libc" attribute
ReusedPackage != LibcPackage,
not attr("compatible_libc", node(R, ReusedPackage), LibcPackage, LibcVersion).
% In case we don't need a provider for libc, ensure there's at least one compatible libc on the host
error(100, "Cannot reuse {0} since we cannot determine libc compatibility", ReusedPackage)
:- not provider(_, node(0, "libc")),
concrete(node(R, ReusedPackage)),
attr("needs_libc", node(R, ReusedPackage)),
not attr("compatible_libc", node(R, ReusedPackage), _, _).
% A libc is needed in the DAG
:- has_built_packages(), not provider(_, node(0, "libc")).
% Non-libc reused specs must be host libc compatible. In case we build packages, we get a
% host compatible libc provider from other rules. If nothing is built, there is no libc provider,
% since it's pruned from reusable specs, meaning we have to explicitly impose reused specs are host
% compatible.
%:- attr("hash", node(R, ReusedPackage), Hash),
% not provider(node(R, ReusedPackage), node(0, "libc")),
% not attr("compatible_libc", node(R, ReusedPackage), _, _).
:- attr("hash", node(R, ReusedPackage), Hash),
not provider(node(R, ReusedPackage), node(0, "libc")),
not attr("compatible_libc", node(R, ReusedPackage), _, _).
% The libc provider must be one that a compiler can target
:- has_built_packages(),
@@ -39,3 +35,9 @@ error(100, "Cannot reuse {0} since we cannot determine libc compatibility", Reus
attr("node", node(X, LibcPackage)),
attr("version", node(X, LibcPackage), LibcVersion),
not host_libc(LibcPackage, LibcVersion).
% A built node must depend on libc
:- build(PackageNode),
provider(LibcNode, node(0, "libc")),
not external(PackageNode),
not depends_on(PackageNode, LibcNode).

View File

@@ -71,7 +71,8 @@
import llnl.util.tty.color as clr
import spack
import spack.compilers.flags
import spack.compiler
import spack.compilers
import spack.config
import spack.deptypes as dt
import spack.error
@@ -82,7 +83,6 @@
import spack.provider_index
import spack.repo
import spack.solver
import spack.spec
import spack.store
import spack.traverse as traverse
import spack.util.executable
@@ -163,13 +163,15 @@
#: Default format for Spec.format(). This format can be round-tripped, so that:
#: Spec(Spec("string").format()) == Spec("string)"
DEFAULT_FORMAT = (
"{name}{@versions}{compiler_flags}"
"{name}{@versions}"
"{%compiler.name}{@compiler.versions}{compiler_flags}"
"{variants}{ namespace=namespace_if_anonymous}{ arch=architecture}{/abstract_hash}"
)
#: Display format, which eliminates extra `@=` in the output, for readability.
DISPLAY_FORMAT = (
"{name}{@version}{compiler_flags}"
"{name}{@version}"
"{%compiler.name}{@compiler.version}{compiler_flags}"
"{variants}{ namespace=namespace_if_anonymous}{ arch=architecture}{/abstract_hash}"
)
@@ -184,7 +186,7 @@
)
#: specfile format version. Must increase monotonically
SPECFILE_FORMAT_VERSION = 5
SPECFILE_FORMAT_VERSION = 4
class InstallStatus(enum.Enum):
@@ -594,82 +596,137 @@ def __repr__(self):
def __contains__(self, string):
return string in str(self) or string in self.target
def complete_with_defaults(self) -> None:
default_architecture = ArchSpec.default_arch()
if not self.platform:
self.platform = default_architecture.platform
if not self.os:
self.os = default_architecture.os
if not self.target:
self.target = default_architecture.target
@lang.lazy_lexicographic_ordering
class CompilerSpec:
"""Adaptor to the old compiler spec interface. Exposes just a few attributes"""
"""The CompilerSpec field represents the compiler or range of compiler
versions that a package should be built with. CompilerSpecs have a
name and a version list."""
def __init__(self, spec):
self.spec = spec
__slots__ = "name", "versions"
def __init__(self, *args):
nargs = len(args)
if nargs == 1:
arg = args[0]
# If there is one argument, it's either another CompilerSpec
# to copy or a string to parse
if isinstance(arg, str):
spec = spack.parser.parse_one_or_raise(f"%{arg}")
self.name = spec.compiler.name
self.versions = spec.compiler.versions
elif isinstance(arg, CompilerSpec):
self.name = arg.name
self.versions = arg.versions.copy()
else:
raise TypeError(
"Can only build CompilerSpec from string or "
+ "CompilerSpec. Found %s" % type(arg)
)
elif nargs == 2:
name, version = args
self.name = name
self.versions = vn.VersionList([vn.ver(version)])
else:
raise TypeError("__init__ takes 1 or 2 arguments. (%d given)" % nargs)
def _autospec(self, compiler_spec_like):
if isinstance(compiler_spec_like, CompilerSpec):
return compiler_spec_like
return CompilerSpec(compiler_spec_like)
def intersects(self, other: "CompilerSpec") -> bool:
"""Return True if all concrete specs matching self also match other, otherwise False.
For compiler specs this means that the name of the compiler must be the same for
self and other, and that the versions ranges should intersect.
Args:
other: spec to be satisfied
"""
other = self._autospec(other)
return self.name == other.name and self.versions.intersects(other.versions)
def satisfies(self, other: "CompilerSpec") -> bool:
"""Return True if all concrete specs matching self also match other, otherwise False.
For compiler specs this means that the name of the compiler must be the same for
self and other, and that the version range of self is a subset of that of other.
Args:
other: spec to be satisfied
"""
other = self._autospec(other)
return self.name == other.name and self.versions.satisfies(other.versions)
def constrain(self, other: "CompilerSpec") -> bool:
"""Intersect self's versions with other.
Return whether the CompilerSpec changed.
"""
other = self._autospec(other)
# ensure that other will actually constrain this spec.
if not other.intersects(self):
raise UnsatisfiableCompilerSpecError(other, self)
return self.versions.intersect(other.versions)
@property
def name(self):
return self.spec.name
def concrete(self):
"""A CompilerSpec is concrete if its versions are concrete and there
is an available compiler with the right version."""
return self.versions.concrete
@property
def version(self):
return self.spec.version
if not self.concrete:
raise spack.error.SpecError("Spec is not concrete: " + str(self))
return self.versions[0]
@property
def versions(self):
return self.spec.versions
def copy(self):
clone = CompilerSpec.__new__(CompilerSpec)
clone.name = self.name
clone.versions = self.versions.copy()
return clone
def _cmp_iter(self):
yield self.name
yield self.versions
def to_dict(self):
d = syaml.syaml_dict([("name", self.name)])
d.update(self.versions.to_dict())
return syaml.syaml_dict([("compiler", d)])
@staticmethod
def from_dict(d):
d = d["compiler"]
return CompilerSpec(d["name"], vn.VersionList.from_dict(d))
@property
def display_str(self):
"""Equivalent to {compiler.name}{@compiler.version} for Specs, without extra
@= for readability."""
if self.spec.concrete:
if self.concrete:
return f"{self.name}@{self.version}"
elif self.versions != vn.any_version:
return f"{self.name}@{self.versions}"
return self.name
def __lt__(self, other):
if not isinstance(other, CompilerSpec):
return self.spec < other
return self.spec < other.spec
def __eq__(self, other):
if not isinstance(other, CompilerSpec):
return self.spec == other
return self.spec == other.spec
def __hash__(self):
return hash(self.spec)
def __str__(self):
return str(self.spec)
out = self.name
if self.versions and self.versions != vn.any_version:
out += f"@{self.versions}"
return out
def _cmp_iter(self):
return self.spec._cmp_iter()
def __bool__(self):
if self.spec == Spec():
return False
return bool(self.spec)
class DeprecatedCompilerSpec(lang.DeprecatedProperty):
def __init__(self):
super().__init__(name="compiler")
def factory(self, instance, owner):
for language in ("c", "cxx", "fortran"):
deps = instance.dependencies(virtuals=language)
if deps:
return CompilerSpec(deps[0])
raise AttributeError(f"{instance} has no C, C++, or Fortran compiler")
def __repr__(self):
return str(self)
@lang.lazy_lexicographic_ordering
@@ -690,22 +747,15 @@ class DependencySpec:
virtuals: virtual packages provided from child to parent node.
"""
__slots__ = "parent", "spec", "depflag", "virtuals", "direct"
__slots__ = "parent", "spec", "depflag", "virtuals"
def __init__(
self,
parent: "Spec",
spec: "Spec",
*,
depflag: dt.DepFlag,
virtuals: Tuple[str, ...],
direct: bool = False,
self, parent: "Spec", spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...]
):
self.parent = parent
self.spec = spec
self.depflag = depflag
self.virtuals = tuple(sorted(set(virtuals)))
self.direct = direct
def update_deptypes(self, depflag: dt.DepFlag) -> bool:
"""Update the current dependency types"""
@@ -724,13 +774,7 @@ def update_virtuals(self, virtuals: Tuple[str, ...]) -> bool:
def copy(self) -> "DependencySpec":
"""Return a copy of this edge"""
return DependencySpec(
self.parent,
self.spec,
depflag=self.depflag,
virtuals=self.virtuals,
direct=self.direct,
)
return DependencySpec(self.parent, self.spec, depflag=self.depflag, virtuals=self.virtuals)
def _cmp_iter(self):
yield self.parent.name if self.parent else None
@@ -1385,34 +1429,12 @@ def tree(
return out
class SpecAnnotations:
def __init__(self) -> None:
self.original_spec_format = SPECFILE_FORMAT_VERSION
self.compiler_node_attribute: Optional["Spec"] = None
def with_spec_format(self, spec_format: int) -> "SpecAnnotations":
self.original_spec_format = spec_format
return self
def with_compiler(self, compiler: "Spec") -> "SpecAnnotations":
self.compiler_node_attribute = compiler
return self
def __repr__(self) -> str:
result = f"SpecAnnotations().with_spec_format({self.original_spec_format})"
if self.compiler_node_attribute:
result += f"with_compiler({str(self.compiler_node_attribute)})"
return result
@lang.lazy_lexicographic_ordering(set_hash=False)
class Spec:
#: Cache for spec's prefix, computed lazily in the corresponding property
_prefix = None
abstract_hash = None
compiler = DeprecatedCompilerSpec()
@staticmethod
def default_arch():
"""Return an anonymous spec for the default architecture"""
@@ -1452,6 +1474,7 @@ def __init__(
self.versions = vn.VersionList(":")
self.variants = VariantMap(self)
self.architecture = None
self.compiler = None
self.compiler_flags = FlagMap(self)
self._dependents = _EdgeMap(store_by_child=False)
self._dependencies = _EdgeMap(store_by_child=True)
@@ -1488,13 +1511,12 @@ def __init__(
# is deployed "as built."
# Build spec should be the actual build spec unless marked dirty.
self._build_spec = None
self.annotations = SpecAnnotations()
if isinstance(spec_like, str):
spack.parser.parse_one_or_raise(spec_like, self)
elif spec_like is not None:
raise TypeError(f"Can't make spec out of {type(spec_like)}")
raise TypeError("Can't make spec out of %s" % type(spec_like))
@staticmethod
def _format_module_list(modules):
@@ -1698,7 +1720,7 @@ def _add_flag(self, name, value, propagate):
self.namespace = value
elif name in valid_flags:
assert self.compiler_flags is not None
flags_and_propagation = spack.compilers.flags.tokenize_flags(value, propagate)
flags_and_propagation = spack.compiler.tokenize_flags(value, propagate)
flag_group = " ".join(x for (x, y) in flags_and_propagation)
for flag, propagation in flags_and_propagation:
self.compiler_flags.add_flag(name, flag, propagation, flag_group)
@@ -1729,18 +1751,10 @@ def _set_architecture(self, **kwargs):
else:
setattr(self.architecture, new_attr, new_value)
def _add_dependency(
self, spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...], direct: bool = False
):
"""Called by the parser to add another spec as a dependency.
Args:
depflag: dependency type for this edge
virtuals: virtuals on this edge
direct: if True denotes a direct dependency (associated with the % sigil)
"""
def _add_dependency(self, spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...]):
"""Called by the parser to add another spec as a dependency."""
if spec.name not in self._dependencies or not spec.name:
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals, direct=direct)
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals)
return
# Keep the intersection of constraints when a dependency is added multiple times with
@@ -1763,7 +1777,7 @@ def _add_dependency(
f"\t'{str(self)}' cannot depend on '{required_dep_str}'"
)
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals, direct=direct)
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals)
return
try:
@@ -1775,12 +1789,7 @@ def _add_dependency(
)
def add_dependency_edge(
self,
dependency_spec: "Spec",
*,
depflag: dt.DepFlag,
virtuals: Tuple[str, ...],
direct: bool = False,
self, dependency_spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[str, ...]
):
"""Add a dependency edge to this spec.
@@ -1788,7 +1797,6 @@ def add_dependency_edge(
dependency_spec: spec of the dependency
deptypes: dependency types for this edge
virtuals: virtuals provided by this edge
direct: if True denotes a direct dependency
"""
# Check if we need to update edges that are already present
selected = self._dependencies.select(child=dependency_spec.name)
@@ -1827,9 +1835,7 @@ def add_dependency_edge(
edge.update_virtuals(virtuals=virtuals)
return
edge = DependencySpec(
self, dependency_spec, depflag=depflag, virtuals=virtuals, direct=direct
)
edge = DependencySpec(self, dependency_spec, depflag=depflag, virtuals=virtuals)
self._dependencies.add(edge)
dependency_spec._dependents.add(edge)
@@ -2132,6 +2138,10 @@ def to_node_dict(self, hash=ht.dag_hash):
'platform_os': 'mojave',
'target': 'x86_64',
},
'compiler': {
'name': 'apple-clang',
'version': '10.0.0',
},
'namespace': 'builtin',
'parameters': {
'fts': 'true',
@@ -2174,6 +2184,9 @@ def to_node_dict(self, hash=ht.dag_hash):
if self.architecture:
d.update(self.architecture.to_dict())
if self.compiler:
d.update(self.compiler.to_dict())
if self.namespace:
d["namespace"] = self.namespace
@@ -2263,14 +2276,6 @@ def to_node_dict(self, hash=ht.dag_hash):
d["build_spec"] = syaml.syaml_dict(
[("name", self.build_spec.name), (hash.name, self.build_spec._cached_hash(hash))]
)
# Annotations
d["annotations"] = syaml.syaml_dict(
[("original_specfile_version", self.annotations.original_spec_format)]
)
if self.annotations.original_spec_format < 5:
d["annotations"]["compiler"] = str(self.annotations.compiler_node_attribute)
return d
def to_dict(self, hash=ht.dag_hash):
@@ -2427,6 +2432,8 @@ def override(init_spec, change_spec):
else:
raise ValueError("{0} is not a variant of {1}".format(vname, new_spec.name))
if change_spec.compiler:
new_spec.compiler = change_spec.compiler
if change_spec.compiler_flags:
for flagname, flagvals in change_spec.compiler_flags.items():
new_spec.compiler_flags[flagname] = flagvals
@@ -2598,10 +2605,8 @@ def from_dict(data):
spec = SpecfileV2.load(data)
elif int(data["spec"]["_meta"]["version"]) == 3:
spec = SpecfileV3.load(data)
elif int(data["spec"]["_meta"]["version"]) == 4:
spec = SpecfileV4.load(data)
else:
spec = SpecfileV5.load(data)
spec = SpecfileV4.load(data)
# Any git version should
for s in spec.traverse():
@@ -2786,6 +2791,12 @@ def inject_patches_variant(root):
@staticmethod
def ensure_external_path_if_external(external_spec):
if external_spec.external_modules and not external_spec.external_path:
compiler = spack.compilers.compiler_for_spec(
external_spec.compiler, external_spec.architecture
)
for mod in compiler.modules:
md.load_module(mod)
# Get the path from the module the package can override the default
# (this is mostly needed for Cray)
pkg_cls = spack.repo.PATH.get_pkg_class(external_spec.name)
@@ -2947,7 +2958,7 @@ def _finalize_concretization(self):
for spec in self.traverse():
spec._cached_hash(ht.dag_hash)
def concretized(self, tests: Union[bool, Iterable[str]] = False) -> "Spec":
def concretized(self, tests: Union[bool, Iterable[str]] = False) -> "spack.spec.Spec":
"""This is a non-destructive version of concretize().
First clones, then returns a concrete version of this package
@@ -2984,14 +2995,9 @@ def validate_or_raise(self):
spack.repo.PATH.get_pkg_class(spec.fullname)
# validate compiler in addition to the package name.
if spec.dependencies(deptype="build"):
pkg_cls = spack.repo.PATH.get_pkg_class(spec.fullname)
# FIXME (compiler as nodes): raise if we use %gcc on pkgs that do not depend on C
pkg_dependencies = pkg_cls.dependency_names()
if not any(x in pkg_dependencies for x in ("c", "cxx", "fortran")):
raise UnsupportedCompilerError(
f"{spec.fullname} does not depend on 'c', 'cxx, or 'fortran'"
)
if spec.compiler:
if not spack.compilers.supported(spec.compiler):
raise UnsupportedCompilerError(spec.compiler.name)
# Ensure correctness of variants (if the spec is not virtual)
if not spec.virtual:
@@ -3098,6 +3104,12 @@ def constrain(self, other, deps=True):
self.namespace = other.namespace
changed = True
if self.compiler is not None and other.compiler is not None:
changed |= self.compiler.constrain(other.compiler)
elif self.compiler is None:
changed |= self.compiler != other.compiler
self.compiler = other.compiler
changed |= self.versions.intersect(other.versions)
changed |= self.variants.constrain(other.variants)
@@ -3124,8 +3136,10 @@ def constrain(self, other, deps=True):
return changed
def _constrain_dependencies(self, other: "Spec") -> bool:
def _constrain_dependencies(self, other):
"""Apply constraints of other spec's dependencies to this spec."""
other = self._autospec(other)
if not other._dependencies:
return False
@@ -3140,10 +3154,8 @@ def _constrain_dependencies(self, other: "Spec") -> bool:
# Handle common first-order constraints directly
changed = False
common_dependencies = {x.name for x in self.dependencies()}
common_dependencies &= {x.name for x in other.dependencies()}
for name in common_dependencies:
changed |= self[name].constrain(other[name], deps=True)
for name in self.common_dependencies(other):
changed |= self[name].constrain(other[name], deps=False)
if name in self._dependencies:
# WARNING: This function is an implementation detail of the
# WARNING: original concretizer. Since with that greedy
@@ -3166,14 +3178,13 @@ def _constrain_dependencies(self, other: "Spec") -> bool:
dep_spec_copy.spec.copy(),
depflag=dep_spec_copy.depflag,
virtuals=dep_spec_copy.virtuals,
direct=dep_spec_copy.direct,
)
changed = True
return changed
def common_dependencies(self, other):
"""Return names of dependencies that self and other have in common."""
"""Return names of dependencies that self an other have in common."""
common = set(s.name for s in self.traverse(root=False))
common.intersection_update(s.name for s in other.traverse(root=False))
return common
@@ -3273,6 +3284,10 @@ def intersects(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
if not self.versions.intersects(other.versions):
return False
if self.compiler and other.compiler:
if not self.compiler.intersects(other.compiler):
return False
if not self.variants.intersects(other.variants):
return False
@@ -3295,10 +3310,8 @@ def _intersects_dependencies(self, other):
return True
# Handle first-order constraints directly
common_dependencies = {x.name for x in self.dependencies()}
common_dependencies &= {x.name for x in other.dependencies()}
for name in common_dependencies:
if not self[name].intersects(other[name], deps=True):
for name in self.common_dependencies(other):
if not self[name].intersects(other[name], deps=False):
return False
# For virtual dependencies, we need to dig a little deeper.
@@ -3375,6 +3388,12 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
if not self.versions.satisfies(other.versions):
return False
if self.compiler and other.compiler:
if not self.compiler.satisfies(other.compiler):
return False
elif other.compiler and not self.compiler:
return False
if not self.variants.satisfies(other.variants):
return False
@@ -3412,19 +3431,6 @@ def satisfies(self, other: Union[str, "Spec"], deps: bool = True) -> bool:
if rhs_edge.spec.virtual:
rhs_edge.update_virtuals(virtuals=(rhs_edge.spec.name,))
if rhs_edge.direct:
# Note: this relies on abstract specs from string not being deeper than 2 levels
# e.g. in foo %fee ^bar %baz we cannot go deeper than "baz" and e.g. specify its
# dependencies too.
current_node = self if rhs_edge.parent.name is None else self[rhs_edge.parent.name]
candidates = current_node.dependencies(
name=rhs_edge.spec.name,
deptype=rhs_edge.depflag,
virtuals=rhs_edge.virtuals or None,
)
if not candidates or not any(x.satisfies(rhs_edge.spec) for x in candidates):
return False
if not rhs_edge.virtuals:
continue
@@ -3528,6 +3534,7 @@ def _dup(self, other, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, clearde
self.name != other.name
and self.versions != other.versions
and self.architecture != other.architecture
and self.compiler != other.compiler
and self.variants != other.variants
and self._normal != other._normal
and self.concrete != other.concrete
@@ -3543,10 +3550,10 @@ def _dup(self, other, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, clearde
self.name = other.name
self.versions = other.versions.copy()
self.architecture = other.architecture.copy() if other.architecture else None
self.compiler = other.compiler.copy() if other.compiler else None
if cleardeps:
self._dependents = _EdgeMap(store_by_child=False)
self._dependencies = _EdgeMap(store_by_child=True)
self.compiler_flags = other.compiler_flags.copy()
self.compiler_flags.spec = self
self.variants = other.variants.copy()
@@ -3565,7 +3572,6 @@ def _dup(self, other, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, clearde
self.external_modules = other.external_modules
self.extra_attributes = other.extra_attributes
self.namespace = other.namespace
self.annotations = other.annotations
# If we copy dependencies, preserve DAG structure in the new spec
if deps:
@@ -3611,10 +3617,7 @@ def spid(spec):
new_specs[spid(edge.spec)] = edge.spec.copy(deps=False)
new_specs[spid(edge.parent)].add_dependency_edge(
new_specs[spid(edge.spec)],
depflag=edge.depflag,
virtuals=edge.virtuals,
direct=edge.direct,
new_specs[spid(edge.spec)], depflag=edge.depflag, virtuals=edge.virtuals
)
def copy(self, deps: Union[bool, dt.DepTypes, dt.DepFlag] = True, **kwargs):
@@ -3675,14 +3678,21 @@ def __getitem__(self, name: str):
query_parameters = re.split(r"\s*,\s*", csv)
order = lambda: itertools.chain(
self.traverse_edges(deptype=dt.LINK | dt.RUN, order="breadth", cover="edges"),
self.edges_to_dependencies(depflag=dt.BUILD | dt.TEST),
self.traverse_edges(deptype=dt.LINK, order="breadth", cover="edges"),
self.edges_to_dependencies(depflag=dt.BUILD | dt.RUN | dt.TEST),
self.traverse_edges(deptype=dt.ALL, order="breadth", cover="edges"),
)
# Consider runtime dependencies and direct build/test deps only
# Consider runtime dependencies and direct build/test deps before transitive dependencies,
# and prefer matches closest to the root.
try:
child: Spec = next(
e.spec for e in order() if e.spec.name == name or name in e.virtuals
e.spec
for e in itertools.chain(
(e for e in order() if e.spec.name == name or name in e.virtuals),
# for historical reasons
(e for e in order() if e.spec.concrete and e.spec.package.provides(name)),
)
)
except StopIteration:
raise KeyError(f"No spec with name {name} in {self}")
@@ -3706,11 +3716,8 @@ def __contains__(self, spec):
# if anonymous or same name, we only have to look at the root
if not spec.name or spec.name == self.name:
return self.satisfies(spec)
try:
return self[spec.name].satisfies(spec)
except KeyError:
return False
else:
return any(s.satisfies(spec) for s in self.traverse(root=False))
def eq_dag(self, other, deptypes=True, vs=None, vo=None):
"""True if the full dependency DAGs of specs are equal."""
@@ -3760,6 +3767,7 @@ def _cmp_node(self):
yield self.namespace
yield self.versions
yield self.variants
yield self.compiler
yield self.compiler_flags
yield self.architecture
yield self.abstract_hash
@@ -3813,6 +3821,9 @@ def format(self, format_string: str = DEFAULT_FORMAT, color: Optional[bool] = Fa
name
version
compiler
compiler.name
compiler.version
compiler_flags
variants
architecture
@@ -3954,9 +3965,6 @@ def format_attribute(match_object: Match) -> str:
try:
current = getattr(current, part)
except AttributeError:
if part == "compiler":
return "none"
raise SpecFormatStringError(
f"Attempted to format attribute {attribute}. "
f"Spec {'.'.join(parts[:idx])} has no attribute {part}"
@@ -4058,28 +4066,15 @@ def __str__(self):
if not self._dependencies:
return self.format()
name_conversion = {
"llvm": "clang",
"intel-oneapi-compilers": "oneapi",
"llvm-amdgpu": "rocmcc",
"intel-oneapi-compiler-classic": "intel",
"acfl": "arm",
}
parts = [self.format()]
direct, transitive = lang.stable_partition(
self.edges_to_dependencies(), predicate_fn=lambda x: x.direct
root_str = [self.format()]
sorted_dependencies = sorted(
self.traverse(root=False), key=lambda x: (x.name, x.abstract_hash)
)
for item in sorted(direct, key=lambda x: x.spec.name):
current_name = item.spec.name
new_name = name_conversion.get(current_name, current_name)
parts.append(f"%{item.spec.format()}".replace(current_name, new_name))
for item in sorted(transitive, key=lambda x: x.spec.name):
# Recurse to attach build deps in order
edge_attributes = ""
if item.virtuals or item.depflag:
edge_attributes = item.spec.format("{edge_attributes}") + " "
parts.append(f"^{edge_attributes}{str(item.spec)}")
return " ".join(parts).strip()
sorted_dependencies = [
d.format("{edge_attributes} " + DEFAULT_FORMAT) for d in sorted_dependencies
]
spec_str = " ^".join(root_str + sorted_dependencies)
return spec_str.strip()
@property
def colored_str(self):
@@ -4500,10 +4495,6 @@ def attach_git_version_lookup(self):
if isinstance(v, vn.GitVersion) and v._ref_version is None:
v.attach_lookup(spack.version.git_ref_lookup.GitRefLookup(self.fullname))
def original_spec_format(self) -> int:
"""Returns the spec format originally used for this spec."""
return self.annotations.original_spec_format
class VariantMap(lang.HashableMap):
"""Map containing variant instances. New values can be added only
@@ -4733,9 +4724,9 @@ def substitute_abstract_variants(spec: Spec):
)
def parse_with_version_concrete(spec_like: Union[str, Spec]):
def parse_with_version_concrete(spec_like: Union[str, Spec], compiler: bool = False):
"""Same as Spec(string), but interprets @x as @=x"""
s = Spec(spec_like)
s: Union[CompilerSpec, Spec] = CompilerSpec(spec_like) if compiler else Spec(spec_like)
interpreted_version = s.versions.concrete_range_as_version
if interpreted_version:
s.versions = vn.VersionList([interpreted_version])
@@ -4817,6 +4808,11 @@ def from_node_dict(cls, node):
if "arch" in node:
spec.architecture = ArchSpec.from_dict(node)
if "compiler" in node:
spec.compiler = CompilerSpec.from_dict(node)
else:
spec.compiler = None
propagated_names = node.get("propagate", [])
for name, values in node.get("parameters", {}).items():
propagate = name in propagated_names
@@ -4857,28 +4853,12 @@ def from_node_dict(cls, node):
# FIXME: Monkey patches mvar to store patches order
mvar._patches_in_order_of_appearance = patches
# Annotate the compiler spec, might be used later
if "annotations" not in node:
# Specfile v4 and earlier
spec.annotations.with_spec_format(cls.SPEC_VERSION)
if "compiler" in node:
spec.annotations.with_compiler(cls.legacy_compiler(node))
else:
spec.annotations.with_spec_format(node["annotations"]["original_specfile_version"])
if "compiler" in node["annotations"]:
spec.annotations.with_compiler(Spec(f"{node['annotations']['compiler']}"))
# Don't read dependencies here; from_dict() is used by
# from_yaml() and from_json() to read the root *and* each dependency
# spec.
return spec
@classmethod
def legacy_compiler(cls, node):
d = node["compiler"]
return Spec(f"{d['name']}@{vn.VersionList.from_dict(d)}")
@classmethod
def _load(cls, data):
"""Construct a spec from JSON/YAML using the format version 2.
@@ -4945,8 +4925,6 @@ def read_specfile_dep_specs(cls, deps, hash_type=ht.dag_hash.name):
class SpecfileV1(SpecfileReaderBase):
SPEC_VERSION = 1
@classmethod
def load(cls, data):
"""Construct a spec from JSON/YAML using the format version 1.
@@ -5016,8 +4994,6 @@ def read_specfile_dep_specs(cls, deps, hash_type=ht.dag_hash.name):
class SpecfileV2(SpecfileReaderBase):
SPEC_VERSION = 2
@classmethod
def load(cls, data):
result = cls._load(data)
@@ -5072,12 +5048,10 @@ def extract_build_spec_info_from_node_dict(cls, node, hash_type=ht.dag_hash.name
class SpecfileV3(SpecfileV2):
SPEC_VERSION = 3
pass
class SpecfileV4(SpecfileV2):
SPEC_VERSION = 4
@classmethod
def extract_info_from_dep(cls, elt, hash):
dep_hash = elt[hash.name]
@@ -5091,14 +5065,6 @@ def load(cls, data):
return cls._load(data)
class SpecfileV5(SpecfileV4):
SPEC_VERSION = 5
@classmethod
def legacy_compiler(cls, node):
raise RuntimeError("The 'compiler' option is unexpected in specfiles at v5 or greater")
class LazySpecCache(collections.defaultdict):
"""Cache for Specs that uses a spec_like as key, and computes lazily
the corresponding value ``Spec(spec_like``.
@@ -5216,6 +5182,9 @@ class DuplicateCompilerSpecError(spack.error.SpecError):
class UnsupportedCompilerError(spack.error.SpecError):
"""Raised when the user asks for a compiler spack doesn't know about."""
def __init__(self, compiler_name):
super().__init__("The '%s' compiler is not yet supported." % compiler_name)
class DuplicateArchitectureError(spack.error.SpecError):
"""Raised when the same architecture occurs in a spec twice."""

View File

@@ -14,8 +14,6 @@
from spack.installer import PackageInstaller
from spack.spec import Spec
pytestmark = [pytest.mark.skip(reason="FIXME (compiler as nodes): fix splicing tests")]
class CacheManager:
def __init__(self, specs: List[str]) -> None:

View File

@@ -27,7 +27,7 @@
import spack.binary_distribution as bindist
import spack.caches
import spack.compilers.config
import spack.compilers
import spack.config
import spack.fetch_strategy
import spack.hooks.sbang as sbang
@@ -84,7 +84,7 @@ def config_directory(tmp_path_factory):
for name in [f"site/{platform.system().lower()}", "site", "user"]
]
with spack.config.use_configuration(*cfg_scopes):
_ = spack.compilers.config.find_compilers(scope="site")
_ = spack.compilers.find_compilers(scope="site")
yield defaults_dir
@@ -392,7 +392,7 @@ def test_spec_needs_rebuild(monkeypatch, tmpdir):
s = Spec("libdwarf").concretized()
# Install a package
install_cmd("--fake", s.name)
install_cmd(s.name)
# Put installed package in the buildcache
buildcache_cmd("push", "-u", mirror_dir.strpath, s.name)
@@ -421,7 +421,7 @@ def test_generate_index_missing(monkeypatch, tmpdir, mutable_config):
s = Spec("libdwarf").concretized()
# Install a package
install_cmd("--fake", "--no-cache", s.name)
install_cmd("--no-cache", s.name)
# Create a buildcache and update index
buildcache_cmd("push", "-u", mirror_dir.strpath, s.name)
@@ -573,8 +573,11 @@ def test_install_legacy_buildcache_layout(mutable_config, compiler_factory, inst
where the .spack file contained a repeated spec.json and another
compressed archive file containing the install tree. This test
makes sure we can still read that layout."""
mutable_config.set(
"compilers", [compiler_factory(spec="gcc@4.5.0", operating_system="debian6")]
)
legacy_layout_dir = os.path.join(test_path, "data", "mirrors", "legacy_layout")
mirror_url = f"file://{legacy_layout_dir}"
mirror_url = "file://{0}".format(legacy_layout_dir)
filename = (
"test-debian6-core2-gcc-4.5.0-archive-files-2.0-"
"l3vdiqvbobmspwyb4q2b62fz6nitd4hk.spec.json"
@@ -583,7 +586,9 @@ def test_install_legacy_buildcache_layout(mutable_config, compiler_factory, inst
mirror_cmd("add", "--scope", "site", "test-legacy-layout", mirror_url)
output = install_cmd("--no-check-signature", "--cache-only", "-f", spec_json_path, output=str)
mirror_cmd("rm", "--scope=site", "test-legacy-layout")
expect_line = "Extracting archive-files-2.0-l3vdiqvbobmspwyb4q2b62fz6nitd4hk from binary cache"
expect_line = (
"Extracting archive-files-2.0-" "l3vdiqvbobmspwyb4q2b62fz6nitd4hk from binary cache"
)
assert expect_line in output
@@ -1185,11 +1190,10 @@ def test_get_valid_spec_file_no_json(tmp_path, filename):
bindist._get_valid_spec_file(str(tmp_path / filename), max_supported_layout=1)
def test_download_tarball_with_unsupported_layout_fails(
tmp_path, mock_packages, mutable_config, capsys
):
def test_download_tarball_with_unsupported_layout_fails(tmp_path, mutable_config, capsys):
layout_version = bindist.CURRENT_BUILD_CACHE_LAYOUT_VERSION + 1
spec = Spec("pkg-c").concretized()
spec = Spec("gmake@4.4.1%gcc@13.1.0 arch=linux-ubuntu23.04-zen2")
spec._mark_concrete()
spec_dict = spec.to_dict()
spec_dict["buildcache_layout_version"] = layout_version

View File

@@ -8,14 +8,12 @@
import spack.bootstrap
import spack.bootstrap.config
import spack.bootstrap.core
import spack.compilers.config
import spack.compilers
import spack.config
import spack.environment
import spack.store
import spack.util.path
from .conftest import _true
@pytest.fixture
def active_mock_environment(mutable_config, mutable_mock_env_path):
@@ -96,14 +94,12 @@ def test_raising_exception_if_bootstrap_disabled(mutable_config):
spack.bootstrap.config.store_path()
def test_raising_exception_module_importable(config, monkeypatch):
monkeypatch.setattr(spack.bootstrap.core, "source_is_enabled", _true)
def test_raising_exception_module_importable():
with pytest.raises(ImportError, match='cannot bootstrap the "asdf" Python module'):
spack.bootstrap.core.ensure_module_importable_or_raise("asdf")
def test_raising_exception_executables_in_path(config, monkeypatch):
monkeypatch.setattr(spack.bootstrap.core, "source_is_enabled", _true)
def test_raising_exception_executables_in_path():
with pytest.raises(RuntimeError, match="cannot bootstrap any of the asdf, fdsa executables"):
spack.bootstrap.core.ensure_executables_in_path_or_raise(["asdf", "fdsa"], "python")
@@ -132,22 +128,22 @@ def test_bootstrap_disables_modulefile_generation(mutable_config):
@pytest.mark.regression("25992")
@pytest.mark.requires_executables("gcc")
def test_bootstrap_search_for_compilers_with_no_environment(no_packages_yaml):
assert not spack.compilers.config.all_compilers(init_config=False)
def test_bootstrap_search_for_compilers_with_no_environment(no_compilers_yaml):
assert not spack.compilers.all_compiler_specs(init_config=False)
with spack.bootstrap.ensure_bootstrap_configuration():
assert spack.compilers.config.all_compilers(init_config=False)
assert not spack.compilers.config.all_compilers(init_config=False)
assert spack.compilers.all_compiler_specs(init_config=False)
assert not spack.compilers.all_compiler_specs(init_config=False)
@pytest.mark.regression("25992")
@pytest.mark.requires_executables("gcc")
def test_bootstrap_search_for_compilers_with_environment_active(
no_packages_yaml, active_mock_environment
no_compilers_yaml, active_mock_environment
):
assert not spack.compilers.config.all_compilers(init_config=False)
assert not spack.compilers.all_compiler_specs(init_config=False)
with spack.bootstrap.ensure_bootstrap_configuration():
assert spack.compilers.config.all_compilers(init_config=False)
assert not spack.compilers.config.all_compilers(init_config=False)
assert spack.compilers.all_compiler_specs(init_config=False)
assert not spack.compilers.all_compiler_specs(init_config=False)
@pytest.mark.regression("26189")
@@ -223,12 +219,16 @@ def test_source_is_disabled(mutable_config):
# Get the configuration dictionary of the current bootstrapping source
conf = next(iter(spack.bootstrap.core.bootstrapping_sources()))
# The source is not explicitly enabled or disabled, so the following should return False
assert not spack.bootstrap.core.source_is_enabled(conf)
# The source is not explicitly enabled or disabled, so the following
# call should raise to skip using it for bootstrapping
with pytest.raises(ValueError):
spack.bootstrap.core.source_is_enabled_or_raise(conf)
# Try to explicitly disable the source and verify that the behavior is the same as above
# Try to explicitly disable the source and verify that the behavior
# is the same as above
spack.config.add("bootstrap:trusted:{0}:{1}".format(conf["name"], False))
assert not spack.bootstrap.core.source_is_enabled(conf)
with pytest.raises(ValueError):
spack.bootstrap.core.source_is_enabled_or_raise(conf)
@pytest.mark.regression("45247")

View File

@@ -5,6 +5,7 @@
import os
import platform
import posixpath
import sys
import pytest
@@ -14,13 +15,13 @@
from llnl.util.filesystem import HeaderList, LibraryList
import spack.build_environment
import spack.build_systems.compiler
import spack.compiler
import spack.compilers
import spack.config
import spack.deptypes as dt
import spack.package_base
import spack.paths
import spack.spec
import spack.util.environment
import spack.util.spack_yaml as syaml
from spack.build_environment import UseMode, _static_to_shared_library, dso_suffix
from spack.context import Context
@@ -96,7 +97,7 @@ def build_environment(working_env):
@pytest.fixture
def ensure_env_variables(mutable_config, mock_packages, monkeypatch, working_env):
def ensure_env_variables(config, mock_packages, monkeypatch, working_env):
"""Returns a function that takes a dictionary and updates os.environ
for the test lifetime accordingly. Plugs-in mock config and repo.
"""
@@ -161,24 +162,20 @@ def test_static_to_shared_library(build_environment):
@pytest.mark.regression("8345")
@pytest.mark.usefixtures("mock_packages")
@pytest.mark.not_on_windows("Module files are not supported on Windows")
def test_cc_not_changed_by_modules(monkeypatch, mutable_config, working_env, compiler_factory):
"""Tests that external module files that are loaded cannot change the
CC environment variable.
"""
gcc_entry = compiler_factory(spec="gcc@14.0.1 languages=c,c++")
gcc_entry["modules"] = ["some_module"]
mutable_config.set("packages", {"gcc": {"externals": [gcc_entry]}})
@pytest.mark.usefixtures("config", "mock_packages")
def test_cc_not_changed_by_modules(monkeypatch, working_env):
s = spack.spec.Spec("cmake")
s.concretize()
pkg = s.package
def _set_wrong_cc(x):
os.environ["CC"] = "NOT_THIS_PLEASE"
os.environ["ANOTHER_VAR"] = "THIS_IS_SET"
monkeypatch.setattr(spack.build_environment, "load_module", _set_wrong_cc)
monkeypatch.setattr(pkg.compiler, "modules", ["some_module"])
s = spack.spec.Spec("cmake %gcc@14").concretized()
spack.build_environment.setup_package(s.package, dirty=False)
spack.build_environment.setup_package(pkg, False)
assert os.environ["CC"] != "NOT_THIS_PLEASE"
assert os.environ["ANOTHER_VAR"] == "THIS_IS_SET"
@@ -189,7 +186,7 @@ def test_setup_dependent_package_inherited_modules(
):
# This will raise on regression
s = spack.spec.Spec("cmake-client-inheritor").concretized()
PackageInstaller([s.package], fake=True).install()
PackageInstaller([s.package]).install()
@pytest.mark.parametrize(
@@ -269,30 +266,22 @@ def test_setup_dependent_package_inherited_modules(
],
)
def test_compiler_config_modifications(
initial,
modifications,
expected,
ensure_env_variables,
compiler_factory,
mutable_config,
monkeypatch,
initial, modifications, expected, ensure_env_variables, monkeypatch
):
# Set the environment as per prerequisites
ensure_env_variables(initial)
gcc_entry = compiler_factory(spec="gcc@14.0.1 languages=c,c++")
gcc_entry["extra_attributes"]["environment"] = modifications
mutable_config.set("packages", {"gcc": {"externals": [gcc_entry]}})
def platform_pathsep(pathlist):
if Path.platform_path == Path.windows:
pathlist = pathlist.replace(":", ";")
return convert_to_platform_path(pathlist)
pkg = spack.spec.Spec("cmake %gcc@14").concretized().package
# Monkeypatch a pkg.compiler.environment with the required modifications
pkg = spack.spec.Spec("cmake").concretized().package
monkeypatch.setattr(pkg.compiler, "environment", modifications)
# Trigger the modifications
spack.build_environment.setup_package(pkg, dirty=False)
spack.build_environment.setup_package(pkg, False)
# Check they were applied
for name, value in expected.items():
@@ -303,6 +292,25 @@ def platform_pathsep(pathlist):
assert name not in os.environ
def test_compiler_custom_env(config, mock_packages, monkeypatch, working_env):
if sys.platform == "win32":
test_path = r"C:\test\path\element\custom-env" + "\\"
else:
test_path = r"/test/path/element/custom-env/"
def custom_env(pkg, env):
env.prepend_path("PATH", test_path)
env.append_flags("ENV_CUSTOM_CC_FLAGS", "--custom-env-flag1")
pkg = spack.spec.Spec("cmake").concretized().package
monkeypatch.setattr(pkg.compiler, "setup_custom_environment", custom_env)
spack.build_environment.setup_package(pkg, False)
# Note: trailing slash may be stripped by internal logic
assert test_path[:-1] in os.environ["PATH"]
assert "--custom-env-flag1" in os.environ["ENV_CUSTOM_CC_FLAGS"]
def test_external_config_env(mock_packages, mutable_config, working_env):
cmake_config = {
"externals": [
@@ -322,27 +330,25 @@ def test_external_config_env(mock_packages, mutable_config, working_env):
@pytest.mark.regression("9107")
@pytest.mark.not_on_windows("Windows does not support module files")
def test_spack_paths_before_module_paths(
mutable_config, mock_packages, compiler_factory, monkeypatch, working_env
):
gcc_entry = compiler_factory(spec="gcc@14.0.1 languages=c,c++")
gcc_entry["modules"] = ["some_module"]
mutable_config.set("packages", {"gcc": {"externals": [gcc_entry]}})
def test_spack_paths_before_module_paths(config, mock_packages, monkeypatch, working_env):
s = spack.spec.Spec("cmake")
s.concretize()
pkg = s.package
module_path = os.path.join("path", "to", "module")
spack_path = os.path.join(spack.paths.prefix, os.path.join("lib", "spack", "env"))
def _set_wrong_cc(x):
os.environ["PATH"] = module_path + os.pathsep + os.environ["PATH"]
monkeypatch.setattr(spack.build_environment, "load_module", _set_wrong_cc)
monkeypatch.setattr(pkg.compiler, "modules", ["some_module"])
s = spack.spec.Spec("cmake").concretized()
spack.build_environment.setup_package(pkg, False)
spack.build_environment.setup_package(s.package, dirty=False)
spack_path = os.path.join(spack.paths.prefix, os.path.join("lib", "spack", "env"))
paths = os.environ["PATH"].split(os.pathsep)
assert paths.index(spack_path) < paths.index(module_path)
@@ -493,13 +499,15 @@ def test_parallel_false_is_not_propagating(default_mock_concretization):
("rpath", "" if platform.system() == "Darwin" else "--disable-new-dtags"),
],
)
def test_setting_dtags_based_on_config(
config_setting, expected_flag, config, mock_packages, working_env
):
def test_setting_dtags_based_on_config(config_setting, expected_flag, config, mock_packages):
# Pick a random package to be able to set compiler's variables
s = spack.spec.Spec("cmake").concretized()
s = spack.spec.Spec("cmake")
s.concretize()
pkg = s.package
env = EnvironmentModifications()
with spack.config.override("config:shared_linking", {"type": config_setting, "bind": False}):
env = spack.build_environment.setup_package(s.package, dirty=False)
spack.build_environment.set_compiler_environment_variables(pkg, env)
modifications = env.group_by_name()
assert "SPACK_DTAGS_TO_STRIP" in modifications
assert "SPACK_DTAGS_TO_ADD" in modifications
@@ -762,44 +770,59 @@ def test_rpath_with_duplicate_link_deps():
@pytest.mark.parametrize(
"compiler_spec,target_name,expected_flags",
[
# Semver versions
# Homogeneous compilers
("gcc@4.7.2", "ivybridge", "-march=core-avx-i -mtune=core-avx-i"),
("clang@3.5", "x86_64", "-march=x86-64 -mtune=generic"),
("apple-clang@9.1.0", "x86_64", "-march=x86-64"),
("gcc@=9.2.0", "haswell", "-march=haswell -mtune=haswell"),
# Check that custom string versions are accepted
("gcc@=9.2.0-foo", "icelake", "-march=icelake-client -mtune=icelake-client"),
# Check that the special case for Apple's clang is treated correctly
# i.e. it won't try to detect the version again
("apple-clang@=9.1.0", "x86_64", "-march=x86-64"),
# FIXME (compiler as nodes): Check mixed toolchain
# ("clang@8.0.0", "broadwell", ""),
# Mixed toolchain
("clang@8.0.0", "broadwell", ""),
],
)
@pytest.mark.filterwarnings("ignore:microarchitecture specific")
@pytest.mark.not_on_windows("Windows doesn't support the compiler wrapper")
def test_optimization_flags(compiler_spec, target_name, expected_flags, compiler_factory):
target = archspec.cpu.TARGETS[target_name]
compiler = spack.spec.parse_with_version_concrete(compiler_spec)
compiler_dict = compiler_factory(spec=compiler_spec, operating_system="")["compiler"]
if compiler_spec == "clang@8.0.0":
compiler_dict["paths"] = {
"cc": "/path/to/clang-8",
"cxx": "/path/to/clang++-8",
"f77": "/path/to/gfortran-9",
"fc": "/path/to/gfortran-9",
}
compiler = spack.compilers.compiler_from_dict(compiler_dict)
opt_flags = spack.build_environment.optimization_flags(compiler, target)
assert opt_flags == expected_flags
@pytest.mark.skipif(
str(archspec.cpu.host().family) != "x86_64", reason="tests check specific x86_64 uarch flags"
@pytest.mark.parametrize(
"compiler_str,real_version,target_str,expected_flags",
[
("gcc@=9.2.0", None, "haswell", "-march=haswell -mtune=haswell"),
# Check that custom string versions are accepted
("gcc@=10foo", "9.2.0", "icelake", "-march=icelake-client -mtune=icelake-client"),
# Check that we run version detection (4.4.0 doesn't support icelake)
("gcc@=4.4.0-special", "9.2.0", "icelake", "-march=icelake-client -mtune=icelake-client"),
# Check that the special case for Apple's clang is treated correctly
# i.e. it won't try to detect the version again
("apple-clang@=9.1.0", None, "x86_64", "-march=x86-64"),
],
)
@pytest.mark.not_on_windows("Windows doesn't support the compiler wrapper")
def test_optimization_flags_are_using_node_target(default_mock_concretization, monkeypatch):
"""Tests that we are using the target on the node to be compiled to retrieve the uarch
specific flags, and not the target of the compiler.
"""
monkeypatch.setattr(spack.build_systems.compiler, "_implicit_rpaths", lambda pkg: [])
gcc = default_mock_concretization("gcc target=core2")
mpileaks = default_mock_concretization("mpileaks target=x86_64")
def test_optimization_flags_with_custom_versions(
compiler_str,
real_version,
target_str,
expected_flags,
monkeypatch,
mutable_config,
compiler_factory,
):
target = archspec.cpu.TARGETS[target_str]
compiler_dict = compiler_factory(spec=compiler_str, operating_system="redhat6")
mutable_config.set("compilers", [compiler_dict])
if real_version:
monkeypatch.setattr(spack.compiler.Compiler, "get_real_version", lambda x: real_version)
compiler = spack.compilers.compiler_from_dict(compiler_dict["compiler"])
env = EnvironmentModifications()
gcc.package.setup_dependent_build_environment(env, mpileaks)
actions = env.group_by_name()["SPACK_TARGET_ARGS_CC"]
assert len(actions) == 1 and isinstance(actions[0], spack.util.environment.SetEnv)
assert actions[0].value == "-march=x86-64 -mtune=generic"
opt_flags = spack.build_environment.optimization_flags(compiler, target)
assert opt_flags == expected_flags

View File

@@ -133,7 +133,6 @@
headerpad = ["-headerpad_max_install_names"]
target_args = ["-march=znver2", "-mtune=znver2"]
target_args_fc = ["-march=znver4", "-mtune=znver4"]
# common compile arguments: includes, libs, -Wl linker args, other args
common_compile_args = (
@@ -168,9 +167,7 @@ def wrapper_environment(working_env):
SPACK_LINK_DIRS=None,
SPACK_INCLUDE_DIRS=None,
SPACK_RPATH_DIRS=None,
SPACK_TARGET_ARGS_CC="-march=znver2 -mtune=znver2",
SPACK_TARGET_ARGS_CXX="-march=znver2 -mtune=znver2",
SPACK_TARGET_ARGS_FORTRAN="-march=znver4 -mtune=znver4",
SPACK_TARGET_ARGS="-march=znver2 -mtune=znver2",
SPACK_LINKER_ARG="-Wl,",
SPACK_DTAGS_TO_ADD="--disable-new-dtags",
SPACK_DTAGS_TO_STRIP="--enable-new-dtags",
@@ -380,7 +377,7 @@ def test_fc_flags(wrapper_environment, wrapper_flags):
fc,
test_args,
[real_cc]
+ target_args_fc
+ target_args
+ test_include_paths
+ ["-Lfoo"]
+ test_library_paths
@@ -427,7 +424,7 @@ def test_Wl_parsing_NAG_is_ignored(wrapper_environment):
check_args(
fc,
["-Wl,-Wl,,x,,y,,z"],
[real_cc] + target_args_fc + ["-Wl,--disable-new-dtags", "-Wl,-Wl,,x,,y,,z"],
[real_cc] + target_args + ["-Wl,--disable-new-dtags", "-Wl,-Wl,,x,,y,,z"],
)
@@ -836,14 +833,14 @@ def test_no_ccache_prepend_for_fc(wrapper_environment):
fc,
test_args,
# no ccache for Fortran
[real_cc] + target_args_fc + common_compile_args,
[real_cc] + target_args + common_compile_args,
)
os.environ["SPACK_SHORT_SPEC"] = "foo@1.2=darwin-x86_64"
check_args(
fc,
test_args,
# no ccache for Fortran
[real_cc] + target_args_fc + lheaderpad + common_compile_args,
[real_cc] + target_args + lheaderpad + common_compile_args,
)

View File

@@ -198,7 +198,6 @@ def __call__(self, *args, **kwargs):
assert "Unable to merge {0}".format(c1) in err
@pytest.mark.xfail(reason="FIXME (compiler as nodes): revisit this test")
def test_get_spec_filter_list(mutable_mock_env_path, mutable_mock_repo):
"""Test that given an active environment and list of touched pkgs,
we get the right list of possibly-changed env specs"""

View File

@@ -13,7 +13,7 @@
build_env = SpackCommand("build-env")
@pytest.mark.parametrize("pkg", [("pkg-c",), ("pkg-c", "--")])
@pytest.mark.parametrize("pkg", [("zlib",), ("zlib", "--")])
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
def test_it_just_runs(pkg):
build_env(*pkg)
@@ -39,7 +39,7 @@ def test_build_env_requires_a_spec(args):
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
def test_dump(shell_as, shell, tmpdir):
with tmpdir.as_cwd():
build_env("--dump", _out_file, "pkg-c")
build_env("--dump", _out_file, "zlib")
with open(_out_file) as f:
if shell == "pwsh":
assert any(line.startswith("$Env:PATH") for line in f.readlines())
@@ -52,7 +52,7 @@ def test_dump(shell_as, shell, tmpdir):
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
def test_pickle(tmpdir):
with tmpdir.as_cwd():
build_env("--pickle", _out_file, "pkg-c")
build_env("--pickle", _out_file, "zlib")
environment = pickle.load(open(_out_file, "rb"))
assert isinstance(environment, dict)
assert "PATH" in environment

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