Merge branch 'develop' into f/env-location
This commit is contained in:
commit
41cf807804
2
.github/workflows/audit.yaml
vendored
2
.github/workflows/audit.yaml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
python-version: ${{inputs.python_version}}
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
pip install --upgrade pip six setuptools pytest codecov 'coverage[toml]<=6.2'
|
||||
pip install --upgrade pip six setuptools pytest codecov coverage[toml]
|
||||
- name: Package audits (with coverage)
|
||||
if: ${{ inputs.with_coverage == 'true' }}
|
||||
run: |
|
||||
|
2
.github/workflows/bootstrap-test.sh
vendored
2
.github/workflows/bootstrap-test.sh
vendored
@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
source share/spack/setup-env.sh
|
||||
$PYTHON bin/spack bootstrap untrust spack-install
|
||||
$PYTHON bin/spack bootstrap disable spack-install
|
||||
$PYTHON bin/spack -d solve zlib
|
||||
tree $BOOTSTRAP/store
|
||||
exit 0
|
||||
|
22
.github/workflows/bootstrap.yml
vendored
22
.github/workflows/bootstrap.yml
vendored
@ -42,7 +42,8 @@ jobs:
|
||||
shell: runuser -u spack-test -- bash {0}
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
spack bootstrap untrust github-actions-v0.2
|
||||
spack bootstrap disable github-actions-v0.4
|
||||
spack bootstrap disable github-actions-v0.3
|
||||
spack external find cmake bison
|
||||
spack -d solve zlib
|
||||
tree ~/.spack/bootstrap/store/
|
||||
@ -79,7 +80,8 @@ jobs:
|
||||
shell: runuser -u spack-test -- bash {0}
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
spack bootstrap untrust github-actions-v0.2
|
||||
spack bootstrap disable github-actions-v0.4
|
||||
spack bootstrap disable github-actions-v0.3
|
||||
spack external find cmake bison
|
||||
spack -d solve zlib
|
||||
tree ~/.spack/bootstrap/store/
|
||||
@ -143,7 +145,8 @@ jobs:
|
||||
- name: Bootstrap clingo
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
spack bootstrap untrust github-actions-v0.2
|
||||
spack bootstrap disable github-actions-v0.4
|
||||
spack bootstrap disable github-actions-v0.3
|
||||
spack external find cmake bison
|
||||
spack -d solve zlib
|
||||
tree ~/.spack/bootstrap/store/
|
||||
@ -160,7 +163,8 @@ jobs:
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
export PATH=/usr/local/opt/bison@2.7/bin:$PATH
|
||||
spack bootstrap untrust github-actions-v0.2
|
||||
spack bootstrap disable github-actions-v0.4
|
||||
spack bootstrap disable github-actions-v0.3
|
||||
spack external find --not-buildable cmake bison
|
||||
spack -d solve zlib
|
||||
tree ~/.spack/bootstrap/store/
|
||||
@ -261,7 +265,7 @@ jobs:
|
||||
shell: runuser -u spack-test -- bash {0}
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
spack bootstrap untrust spack-install
|
||||
spack bootstrap disable spack-install
|
||||
spack -d gpg list
|
||||
tree ~/.spack/bootstrap/store/
|
||||
|
||||
@ -298,7 +302,8 @@ jobs:
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
spack solve zlib
|
||||
spack bootstrap untrust github-actions-v0.2
|
||||
spack bootstrap disable github-actions-v0.4
|
||||
spack bootstrap disable github-actions-v0.3
|
||||
spack -d gpg list
|
||||
tree ~/.spack/bootstrap/store/
|
||||
|
||||
@ -315,7 +320,7 @@ jobs:
|
||||
- name: Bootstrap GnuPG
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
spack bootstrap untrust spack-install
|
||||
spack bootstrap disable spack-install
|
||||
spack -d gpg list
|
||||
tree ~/.spack/bootstrap/store/
|
||||
|
||||
@ -333,7 +338,8 @@ jobs:
|
||||
run: |
|
||||
source share/spack/setup-env.sh
|
||||
spack solve zlib
|
||||
spack bootstrap untrust github-actions-v0.2
|
||||
spack bootstrap disable github-actions-v0.4
|
||||
spack bootstrap disable github-actions-v0.3
|
||||
spack -d gpg list
|
||||
tree ~/.spack/bootstrap/store/
|
||||
|
||||
|
14
.github/workflows/build-containers.yml
vendored
14
.github/workflows/build-containers.yml
vendored
@ -13,7 +13,7 @@ on:
|
||||
paths:
|
||||
- '.github/workflows/build-containers.yml'
|
||||
- 'share/spack/docker/*'
|
||||
- 'share/templates/container/*'
|
||||
- 'share/spack/templates/container/*'
|
||||
- 'lib/spack/spack/container/*'
|
||||
# Let's also build & tag Spack containers on releases.
|
||||
release:
|
||||
@ -80,19 +80,19 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload Dockerfile
|
||||
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
|
||||
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb
|
||||
with:
|
||||
name: dockerfiles
|
||||
path: dockerfiles
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@8b122486cedac8393e77aa9734c3528886e4a1a8 # @v1
|
||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # @v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6 # @v1
|
||||
uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # @v1
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b # @v1
|
||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # @v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@ -100,13 +100,13 @@ jobs:
|
||||
|
||||
- name: Log in to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b # @v1
|
||||
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # @v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build & Deploy ${{ matrix.dockerfile[0] }}
|
||||
uses: docker/build-push-action@c84f38281176d4c9cdb1626ffafcd6b3911b5d94 # @v2
|
||||
uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # @v2
|
||||
with:
|
||||
context: dockerfiles/${{ matrix.dockerfile[0] }}
|
||||
platforms: ${{ matrix.dockerfile[1] }}
|
||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# For pull requests it's not necessary to checkout the code
|
||||
- uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721
|
||||
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50
|
||||
id: filter
|
||||
with:
|
||||
# See https://github.com/dorny/paths-filter/issues/56 for the syntax used below
|
||||
|
4
.github/workflows/setup_git.ps1
vendored
4
.github/workflows/setup_git.ps1
vendored
@ -6,6 +6,10 @@ git config --global user.email "spack@example.com"
|
||||
git config --global user.name "Test User"
|
||||
git config --global core.longpaths true
|
||||
|
||||
# See https://github.com/git/git/security/advisories/GHSA-3wp6-j8xr-qw85 (CVE-2022-39253)
|
||||
# This is needed to let some fixture in our unit-test suite run
|
||||
git config --global protocol.file.allow always
|
||||
|
||||
if ($(git branch --show-current) -ne "develop")
|
||||
{
|
||||
git branch develop origin/develop
|
||||
|
4
.github/workflows/setup_git.sh
vendored
4
.github/workflows/setup_git.sh
vendored
@ -2,6 +2,10 @@
|
||||
git config --global user.email "spack@example.com"
|
||||
git config --global user.name "Test User"
|
||||
|
||||
# See https://github.com/git/git/security/advisories/GHSA-3wp6-j8xr-qw85 (CVE-2022-39253)
|
||||
# This is needed to let some fixture in our unit-test suite run
|
||||
git config --global protocol.file.allow always
|
||||
|
||||
# create a local pr base branch
|
||||
if [[ -n $GITHUB_BASE_REF ]]; then
|
||||
git fetch origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}"
|
||||
|
41
.github/workflows/unit_tests.yaml
vendored
41
.github/workflows/unit_tests.yaml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
|
||||
python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
|
||||
concretizer: ['clingo']
|
||||
on_develop:
|
||||
- ${{ github.ref == 'refs/heads/develop' }}
|
||||
@ -22,7 +22,7 @@ jobs:
|
||||
- python-version: 2.7
|
||||
concretizer: original
|
||||
on_develop: ${{ github.ref == 'refs/heads/develop' }}
|
||||
- python-version: '3.10'
|
||||
- python-version: '3.11'
|
||||
concretizer: original
|
||||
on_develop: ${{ github.ref == 'refs/heads/develop' }}
|
||||
exclude:
|
||||
@ -35,6 +35,9 @@ jobs:
|
||||
- python-version: '3.9'
|
||||
concretizer: 'clingo'
|
||||
on_develop: false
|
||||
- python-version: '3.10'
|
||||
concretizer: 'clingo'
|
||||
on_develop: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # @v2
|
||||
@ -52,7 +55,12 @@ jobs:
|
||||
patchelf cmake bison libbison-dev kcov
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
pip install --upgrade pip six setuptools pytest codecov[toml] pytest-cov pytest-xdist
|
||||
pip install --upgrade pip six setuptools pytest codecov[toml] pytest-xdist
|
||||
# Install pytest-cov only on recent Python, to avoid stalling on Python 2.7 due
|
||||
# to bugs on an unmaintained version of the package when used with xdist.
|
||||
if [[ ${{ matrix.python-version }} != "2.7" ]]; then
|
||||
pip install --upgrade pytest-cov
|
||||
fi
|
||||
# ensure style checks are not skipped in unit tests for python >= 3.6
|
||||
# note that true/false (i.e., 1/0) are opposite in conditions in python and bash
|
||||
if python -c 'import sys; sys.exit(not sys.version_info >= (3, 6))'; then
|
||||
@ -61,7 +69,7 @@ jobs:
|
||||
- name: Pin pathlib for Python 2.7
|
||||
if: ${{ matrix.python-version == 2.7 }}
|
||||
run: |
|
||||
pip install -U pathlib2==2.3.6
|
||||
pip install -U pathlib2==2.3.6 toml
|
||||
- name: Setup git configuration
|
||||
run: |
|
||||
# Need this for the git tests to succeed.
|
||||
@ -73,7 +81,7 @@ jobs:
|
||||
SPACK_PYTHON: python
|
||||
run: |
|
||||
. share/spack/setup-env.sh
|
||||
spack bootstrap untrust spack-install
|
||||
spack bootstrap disable spack-install
|
||||
spack -v solve zlib
|
||||
- name: Run unit tests
|
||||
env:
|
||||
@ -81,11 +89,9 @@ jobs:
|
||||
SPACK_TEST_SOLVER: ${{ matrix.concretizer }}
|
||||
SPACK_TEST_PARALLEL: 2
|
||||
COVERAGE: true
|
||||
UNIT_TEST_COVERAGE: ${{ (matrix.concretizer == 'original' && matrix.python-version == '2.7') || (matrix.python-version == '3.10') }}
|
||||
UNIT_TEST_COVERAGE: ${{ (matrix.python-version == '3.11') }}
|
||||
run: |
|
||||
share/spack/qa/run-unit-tests
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
|
||||
with:
|
||||
flags: unittests,linux,${{ matrix.concretizer }}
|
||||
@ -98,7 +104,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
- name: Install System packages
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
@ -106,7 +112,7 @@ jobs:
|
||||
sudo apt-get install -y coreutils kcov csh zsh tcsh fish dash bash
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
pip install --upgrade pip six setuptools pytest codecov coverage[toml]==6.2 pytest-xdist
|
||||
pip install --upgrade pip six setuptools pytest codecov coverage[toml] pytest-xdist
|
||||
- name: Setup git configuration
|
||||
run: |
|
||||
# Need this for the git tests to succeed.
|
||||
@ -155,7 +161,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
- name: Install System packages
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
@ -177,8 +183,6 @@ jobs:
|
||||
SPACK_TEST_SOLVER: clingo
|
||||
run: |
|
||||
share/spack/qa/run-unit-tests
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # @v2.1.0
|
||||
with:
|
||||
flags: unittests,linux,clingo
|
||||
@ -187,7 +191,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: ["3.10"]
|
||||
steps:
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # @v2
|
||||
with:
|
||||
@ -210,15 +214,10 @@ jobs:
|
||||
git --version
|
||||
. .github/workflows/setup_git.sh
|
||||
. share/spack/setup-env.sh
|
||||
$(which spack) bootstrap untrust spack-install
|
||||
$(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' -x)
|
||||
$(which spack) unit-test --cov --cov-config=pyproject.toml "${common_args[@]}"
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
# Delete the symlink going from ./lib/spack/docs/_spack_root back to
|
||||
# the initial directory, since it causes ELOOP errors with codecov/actions@2
|
||||
rm lib/spack/docs/_spack_root
|
||||
$(which spack) unit-test --cov --cov-config=pyproject.toml --cov-report=xml:coverage.xml "${common_args[@]}"
|
||||
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
|
||||
with:
|
||||
flags: unittests,macos
|
||||
|
6
.github/workflows/valid-style.yml
vendored
6
.github/workflows/valid-style.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # @v2
|
||||
- uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
- name: Install Python Packages
|
||||
run: |
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
@ -57,4 +57,4 @@ jobs:
|
||||
uses: ./.github/workflows/audit.yaml
|
||||
with:
|
||||
with_coverage: ${{ inputs.with_coverage }}
|
||||
python_version: '3.10'
|
||||
python_version: '3.11'
|
||||
|
16
.github/workflows/windows_python.yml
vendored
16
.github/workflows/windows_python.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
python-version: 3.9
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip six pywin32 setuptools codecov pytest-cov
|
||||
python -m pip install --upgrade pip six pywin32 setuptools codecov pytest-cov clingo
|
||||
- name: Create local develop
|
||||
run: |
|
||||
.\spack\.github\workflows\setup_git.ps1
|
||||
@ -32,8 +32,7 @@ jobs:
|
||||
echo F|xcopy .\spack\share\spack\qa\configuration\windows_config.yaml $env:USERPROFILE\.spack\windows\config.yaml
|
||||
cd spack
|
||||
dir
|
||||
(Get-Item '.\lib\spack\docs\_spack_root').Delete()
|
||||
spack unit-test --verbose --cov --cov-config=pyproject.toml --ignore=lib/spack/spack/test/cmd
|
||||
spack unit-test -x --verbose --cov --cov-config=pyproject.toml --ignore=lib/spack/spack/test/cmd
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
|
||||
@ -50,7 +49,7 @@ jobs:
|
||||
python-version: 3.9
|
||||
- name: Install Python packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip six pywin32 setuptools codecov coverage pytest-cov
|
||||
python -m pip install --upgrade pip six pywin32 setuptools codecov coverage pytest-cov clingo
|
||||
- name: Create local develop
|
||||
run: |
|
||||
.\spack\.github\workflows\setup_git.ps1
|
||||
@ -58,8 +57,7 @@ jobs:
|
||||
run: |
|
||||
echo F|xcopy .\spack\share\spack\qa\configuration\windows_config.yaml $env:USERPROFILE\.spack\windows\config.yaml
|
||||
cd spack
|
||||
(Get-Item '.\lib\spack\docs\_spack_root').Delete()
|
||||
spack unit-test --verbose --cov --cov-config=pyproject.toml lib/spack/spack/test/cmd
|
||||
spack unit-test -x --verbose --cov --cov-config=pyproject.toml lib/spack/spack/test/cmd
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
|
||||
@ -83,7 +81,7 @@ jobs:
|
||||
echo F|xcopy .\spack\share\spack\qa\configuration\windows_config.yaml $env:USERPROFILE\.spack\windows\config.yaml
|
||||
spack external find cmake
|
||||
spack external find ninja
|
||||
spack install abseil-cpp
|
||||
spack -d install abseil-cpp
|
||||
make-installer:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
@ -111,11 +109,11 @@ jobs:
|
||||
echo "installer_root=$((pwd).Path)" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
env:
|
||||
ProgressPreference: SilentlyContinue
|
||||
- uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb
|
||||
with:
|
||||
name: Windows Spack Installer Bundle
|
||||
path: ${{ env.installer_root }}\pkg\Spack.exe
|
||||
- uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb
|
||||
with:
|
||||
name: Windows Spack Installer
|
||||
path: ${{ env.installer_root}}\pkg\Spack.msi
|
||||
|
@ -62,6 +62,7 @@ Resources:
|
||||
|
||||
* **Slack workspace**: [spackpm.slack.com](https://spackpm.slack.com).
|
||||
To get an invitation, visit [slack.spack.io](https://slack.spack.io).
|
||||
* [**Github Discussions**](https://github.com/spack/spack/discussions): not just for discussions, also Q&A.
|
||||
* **Mailing list**: [groups.google.com/d/forum/spack](https://groups.google.com/d/forum/spack)
|
||||
* **Twitter**: [@spackpm](https://twitter.com/spackpm). Be sure to
|
||||
`@mention` us!
|
||||
|
48
bin/spack
48
bin/spack
@ -49,52 +49,8 @@ spack_prefix = os.path.dirname(os.path.dirname(spack_file))
|
||||
spack_lib_path = os.path.join(spack_prefix, "lib", "spack")
|
||||
sys.path.insert(0, spack_lib_path)
|
||||
|
||||
# Add external libs
|
||||
spack_external_libs = os.path.join(spack_lib_path, "external")
|
||||
|
||||
if sys.version_info[:2] <= (2, 7):
|
||||
sys.path.insert(0, os.path.join(spack_external_libs, "py2"))
|
||||
|
||||
sys.path.insert(0, spack_external_libs)
|
||||
|
||||
# Here we delete ruamel.yaml in case it has been already imported from site
|
||||
# (see #9206 for a broader description of the issue).
|
||||
#
|
||||
# Briefly: ruamel.yaml produces a .pth file when installed with pip that
|
||||
# makes the site installed package the preferred one, even though sys.path
|
||||
# is modified to point to another version of ruamel.yaml.
|
||||
if "ruamel.yaml" in sys.modules:
|
||||
del sys.modules["ruamel.yaml"]
|
||||
|
||||
if "ruamel" in sys.modules:
|
||||
del sys.modules["ruamel"]
|
||||
|
||||
# The following code is here to avoid failures when updating
|
||||
# the develop version, due to spurious argparse.pyc files remaining
|
||||
# in the libs/spack/external directory, see:
|
||||
# https://github.com/spack/spack/pull/25376
|
||||
# TODO: Remove in v0.18.0 or later
|
||||
try:
|
||||
import argparse
|
||||
except ImportError:
|
||||
argparse_pyc = os.path.join(spack_external_libs, "argparse.pyc")
|
||||
if not os.path.exists(argparse_pyc):
|
||||
raise
|
||||
try:
|
||||
os.remove(argparse_pyc)
|
||||
import argparse # noqa: F401
|
||||
except Exception:
|
||||
msg = (
|
||||
"The file\n\n\t{0}\n\nis corrupted and cannot be deleted by Spack. "
|
||||
"Either delete it manually or ask some administrator to "
|
||||
"delete it for you."
|
||||
)
|
||||
print(msg.format(argparse_pyc))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
import spack.main # noqa: E402
|
||||
from spack_installable.main import main # noqa: E402
|
||||
|
||||
# Once we've set up the system path, run the spack main method
|
||||
if __name__ == "__main__":
|
||||
sys.exit(spack.main.main())
|
||||
sys.exit(main())
|
||||
|
@ -9,16 +9,15 @@ bootstrap:
|
||||
# may not be able to bootstrap all the software that Spack needs,
|
||||
# depending on its type.
|
||||
sources:
|
||||
- name: 'github-actions-v0.4'
|
||||
metadata: $spack/share/spack/bootstrap/github-actions-v0.4
|
||||
- name: 'github-actions-v0.3'
|
||||
metadata: $spack/share/spack/bootstrap/github-actions-v0.3
|
||||
- name: 'github-actions-v0.2'
|
||||
metadata: $spack/share/spack/bootstrap/github-actions-v0.2
|
||||
- name: 'github-actions-v0.1'
|
||||
metadata: $spack/share/spack/bootstrap/github-actions-v0.1
|
||||
- name: 'spack-install'
|
||||
metadata: $spack/share/spack/bootstrap/spack-install
|
||||
trusted:
|
||||
# By default we trust bootstrapping from sources and from binaries
|
||||
# produced on Github via the workflow
|
||||
github-actions-v0.4: true
|
||||
github-actions-v0.3: true
|
||||
spack-install: true
|
||||
|
@ -33,4 +33,4 @@ concretizer:
|
||||
# environments can always be activated. When "false" perform concretization separately
|
||||
# on each root spec, allowing different versions and variants of the same package in
|
||||
# an environment.
|
||||
unify: false
|
||||
unify: true
|
@ -191,10 +191,20 @@ config:
|
||||
package_lock_timeout: null
|
||||
|
||||
|
||||
# Control whether Spack embeds RPATH or RUNPATH attributes in ELF binaries.
|
||||
# Has no effect on macOS. DO NOT MIX these within the same install tree.
|
||||
# See the Spack documentation for details.
|
||||
shared_linking: 'rpath'
|
||||
# Control how shared libraries are located at runtime on Linux. See the
|
||||
# the Spack documentation for details.
|
||||
shared_linking:
|
||||
# Spack automatically embeds runtime search paths in ELF binaries for their
|
||||
# dependencies. Their type can either be "rpath" or "runpath". For glibc, rpath is
|
||||
# inherited and has precedence over LD_LIBRARY_PATH; runpath is not inherited
|
||||
# and of lower precedence. DO NOT MIX these within the same install tree.
|
||||
type: rpath
|
||||
|
||||
|
||||
# (Experimental) Embed absolute paths of dependent libraries directly in ELF
|
||||
# binaries to avoid runtime search. This can improve startup time of
|
||||
# executables with many dependencies, in particular on slow filesystems.
|
||||
bind: false
|
||||
|
||||
|
||||
# Set to 'false' to allow installation on filesystems that doesn't allow setgid bit
|
||||
@ -205,3 +215,7 @@ config:
|
||||
# building and installing packages. This gives information about Spack's
|
||||
# current progress as well as the current and total number of packages.
|
||||
terminal_title: false
|
||||
|
||||
# Number of seconds a buildcache's index.json is cached locally before probing
|
||||
# for updates, within a single Spack invocation. Defaults to 10 minutes.
|
||||
binary_index_ttl: 600
|
@ -27,7 +27,8 @@ packages:
|
||||
fuse: [libfuse]
|
||||
gl: [glx, osmesa]
|
||||
glu: [mesa-glu, openglu]
|
||||
golang: [gcc]
|
||||
golang: [go, gcc]
|
||||
go-external-or-gccgo-bootstrap: [go-bootstrap, gcc]
|
||||
iconv: [libiconv]
|
||||
ipp: [intel-ipp]
|
||||
java: [openjdk, jdk, ibm-java]
|
||||
|
@ -1,5 +1,5 @@
|
||||
config:
|
||||
locks: false
|
||||
concretizer: original
|
||||
concretizer: clingo
|
||||
build_stage::
|
||||
- '$spack/.staging'
|
||||
|
@ -1 +0,0 @@
|
||||
../../..
|
@ -85,7 +85,7 @@ All packages whose names or descriptions contain documentation:
|
||||
To get more information on a particular package from `spack list`, use
|
||||
`spack info`. Just supply the name of a package:
|
||||
|
||||
.. command-output:: spack info mpich
|
||||
.. command-output:: spack info --all mpich
|
||||
|
||||
Most of the information is self-explanatory. The *safe versions* are
|
||||
versions that Spack knows the checksum for, and it will use the
|
||||
@ -998,11 +998,15 @@ More formally, a spec consists of the following pieces:
|
||||
* ``%`` Optional compiler specifier, with an optional compiler version
|
||||
(``gcc`` or ``gcc@4.7.3``)
|
||||
* ``+`` or ``-`` or ``~`` Optional variant specifiers (``+debug``,
|
||||
``-qt``, or ``~qt``) for boolean variants
|
||||
``-qt``, or ``~qt``) for boolean variants. Use ``++`` or ``--`` or
|
||||
``~~`` to propagate variants through the dependencies (``++debug``,
|
||||
``--qt``, or ``~~qt``).
|
||||
* ``name=<value>`` Optional variant specifiers that are not restricted to
|
||||
boolean variants
|
||||
boolean variants. Use ``name==<value>`` to propagate variant through the
|
||||
dependencies.
|
||||
* ``name=<value>`` Optional compiler flag specifiers. Valid flag names are
|
||||
``cflags``, ``cxxflags``, ``fflags``, ``cppflags``, ``ldflags``, and ``ldlibs``.
|
||||
Use ``name==<value>`` to propagate compiler flags through the dependencies.
|
||||
* ``target=<value> os=<value>`` Optional architecture specifier
|
||||
(``target=haswell os=CNL10``)
|
||||
* ``^`` Dependency specs (``^callpath@1.1``)
|
||||
@ -1226,6 +1230,23 @@ variants using the backwards compatibility syntax and uses only ``~``
|
||||
for disabled boolean variants. The ``-`` and spaces on the command
|
||||
line are provided for convenience and legibility.
|
||||
|
||||
Spack allows variants to propagate their value to the package's
|
||||
dependency by using ``++``, ``--``, and ``~~`` for boolean variants.
|
||||
For example, for a ``debug`` variant:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
mpileaks ++debug # enabled debug will be propagated to dependencies
|
||||
mpileaks +debug # only mpileaks will have debug enabled
|
||||
|
||||
To propagate the value of non-boolean variants Spack uses ``name==value``.
|
||||
For example, for the ``stackstart`` variant:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
mpileaks stackstart=4 # variant will be propagated to dependencies
|
||||
mpileaks stackstart==4 # only mpileaks will have this variant value
|
||||
|
||||
^^^^^^^^^^^^^^
|
||||
Compiler Flags
|
||||
^^^^^^^^^^^^^^
|
||||
@ -1233,10 +1254,15 @@ Compiler Flags
|
||||
Compiler flags are specified using the same syntax as non-boolean variants,
|
||||
but fulfill a different purpose. While the function of a variant is set by
|
||||
the package, compiler flags are used by the compiler wrappers to inject
|
||||
flags into the compile line of the build. Additionally, compiler flags are
|
||||
inherited by dependencies. ``spack install libdwarf cppflags="-g"`` will
|
||||
install both libdwarf and libelf with the ``-g`` flag injected into their
|
||||
compile line.
|
||||
flags into the compile line of the build. Additionally, compiler flags can
|
||||
be inherited by dependencies by using ``==``.
|
||||
``spack install libdwarf cppflags=="-g"`` will install both libdwarf and
|
||||
libelf with the ``-g`` flag injected into their compile line.
|
||||
|
||||
.. note::
|
||||
|
||||
versions of spack prior to 0.19.0 will propagate compiler flags using
|
||||
the ``=`` syntax.
|
||||
|
||||
Notice that the value of the compiler flags must be quoted if it
|
||||
contains any spaces. Any of ``cppflags=-O3``, ``cppflags="-O3"``,
|
||||
@ -1438,7 +1464,7 @@ built.
|
||||
You can see what virtual packages a particular package provides by
|
||||
getting info on it:
|
||||
|
||||
.. command-output:: spack info mpich
|
||||
.. command-output:: spack info --virtuals mpich
|
||||
|
||||
Spack is unique in that its virtual packages can be versioned, just
|
||||
like regular packages. A particular version of a package may provide
|
||||
|
@ -15,15 +15,13 @@ is an entire command dedicated to the management of every aspect of bootstrappin
|
||||
|
||||
.. command-output:: spack bootstrap --help
|
||||
|
||||
The first thing to know to understand bootstrapping in Spack is that each of
|
||||
Spack's dependencies is bootstrapped lazily; i.e. the first time it is needed and
|
||||
can't be found. You can readily check if any prerequisite for using Spack
|
||||
is missing by running:
|
||||
Spack is configured to bootstrap its dependencies lazily by default; i.e. the first time they are needed and
|
||||
can't be found. You can readily check if any prerequisite for using Spack is missing by running:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
% spack bootstrap status
|
||||
Spack v0.17.1 - python@3.8
|
||||
Spack v0.19.0 - python@3.8
|
||||
|
||||
[FAIL] Core Functionalities
|
||||
[B] MISSING "clingo": required to concretize specs
|
||||
@ -48,6 +46,21 @@ they can be bootstrapped. Running a command that concretize a spec, like:
|
||||
|
||||
triggers the bootstrapping of clingo from pre-built binaries as expected.
|
||||
|
||||
Users can also bootstrap all the dependencies needed by Spack in a single command, which
|
||||
might be useful to setup containers or other similar environments:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack bootstrap now
|
||||
==> Bootstrapping clingo from pre-built binaries
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.3/build_cache/linux-centos7-x86_64-gcc-10.2.1-clingo-bootstrap-spack-shqedxgvjnhiwdcdrvjhbd73jaevv7wt.spec.json
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.3/build_cache/linux-centos7-x86_64/gcc-10.2.1/clingo-bootstrap-spack/linux-centos7-x86_64-gcc-10.2.1-clingo-bootstrap-spack-shqedxgvjnhiwdcdrvjhbd73jaevv7wt.spack
|
||||
==> Installing "clingo-bootstrap@spack%gcc@10.2.1~docs~ipo+python+static_libstdcpp build_type=Release arch=linux-centos7-x86_64" from a buildcache
|
||||
==> Bootstrapping patchelf from pre-built binaries
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.3/build_cache/linux-centos7-x86_64-gcc-10.2.1-patchelf-0.15.0-htk62k7efo2z22kh6kmhaselru7bfkuc.spec.json
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.3/build_cache/linux-centos7-x86_64/gcc-10.2.1/patchelf-0.15.0/linux-centos7-x86_64-gcc-10.2.1-patchelf-0.15.0-htk62k7efo2z22kh6kmhaselru7bfkuc.spack
|
||||
==> Installing "patchelf@0.15.0%gcc@10.2.1 ldflags="-static-libstdc++ -static-libgcc" arch=linux-centos7-x86_64" from a buildcache
|
||||
|
||||
-----------------------
|
||||
The Bootstrapping store
|
||||
-----------------------
|
||||
@ -107,19 +120,19 @@ If need be, you can disable bootstrapping altogether by running:
|
||||
|
||||
in which case it's your responsibility to ensure Spack runs in an
|
||||
environment where all its prerequisites are installed. You can
|
||||
also configure Spack to skip certain bootstrapping methods by *untrusting*
|
||||
them. For instance:
|
||||
also configure Spack to skip certain bootstrapping methods by disabling
|
||||
them specifically:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
% spack bootstrap untrust github-actions
|
||||
==> "github-actions" is now untrusted and will not be used for bootstrapping
|
||||
% spack bootstrap disable github-actions
|
||||
==> "github-actions" is now disabled and will not be used for bootstrapping
|
||||
|
||||
tells Spack to skip trying to bootstrap from binaries. To add the "github-actions" method back you can:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
% spack bootstrap trust github-actions
|
||||
% spack bootstrap enable github-actions
|
||||
|
||||
There is also an option to reset the bootstrapping configuration to Spack's defaults:
|
||||
|
||||
|
@ -302,88 +302,31 @@ microarchitectures considered during the solve are constrained to be compatible
|
||||
host Spack is currently running on. For instance, if this option is set to ``true``, a
|
||||
user cannot concretize for ``target=icelake`` while running on an Haswell node.
|
||||
|
||||
.. _package-preferences:
|
||||
|
||||
-------------------
|
||||
Package Preferences
|
||||
-------------------
|
||||
|
||||
Spack can be configured to prefer certain compilers, package
|
||||
versions, dependencies, and variants during concretization.
|
||||
The preferred configuration can be controlled via the
|
||||
``~/.spack/packages.yaml`` file for user configurations, or the
|
||||
``etc/spack/packages.yaml`` site configuration.
|
||||
|
||||
Here's an example ``packages.yaml`` file that sets preferred packages:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
opencv:
|
||||
compiler: [gcc@4.9]
|
||||
variants: +debug
|
||||
gperftools:
|
||||
version: [2.2, 2.4, 2.3]
|
||||
all:
|
||||
compiler: [gcc@4.4.7, 'gcc@4.6:', intel, clang, pgi]
|
||||
target: [sandybridge]
|
||||
providers:
|
||||
mpi: [mvapich2, mpich, openmpi]
|
||||
|
||||
At a high level, this example is specifying how packages should be
|
||||
concretized. The opencv package should prefer using GCC 4.9 and
|
||||
be built with debug options. The gperftools package should prefer version
|
||||
2.2 over 2.4. Every package on the system should prefer mvapich2 for
|
||||
its MPI and GCC 4.4.7 (except for opencv, which overrides this by preferring GCC 4.9).
|
||||
These options are used to fill in implicit defaults. Any of them can be overwritten
|
||||
on the command line if explicitly requested.
|
||||
|
||||
Each ``packages.yaml`` file begins with the string ``packages:`` and
|
||||
package names are specified on the next level. The special string ``all``
|
||||
applies settings to *all* packages. Underneath each package name is one
|
||||
or more components: ``compiler``, ``variants``, ``version``,
|
||||
``providers``, and ``target``. Each component has an ordered list of
|
||||
spec ``constraints``, with earlier entries in the list being preferred
|
||||
over later entries.
|
||||
|
||||
Sometimes a package installation may have constraints that forbid
|
||||
the first concretization rule, in which case Spack will use the first
|
||||
legal concretization rule. Going back to the example, if a user
|
||||
requests gperftools 2.3 or later, then Spack will install version 2.4
|
||||
as the 2.4 version of gperftools is preferred over 2.3.
|
||||
|
||||
An explicit concretization rule in the preferred section will always
|
||||
take preference over unlisted concretizations. In the above example,
|
||||
xlc isn't listed in the compiler list. Every listed compiler from
|
||||
gcc to pgi will thus be preferred over the xlc compiler.
|
||||
|
||||
The syntax for the ``provider`` section differs slightly from other
|
||||
concretization rules. A provider lists a value that packages may
|
||||
``depend_on`` (e.g, MPI) and a list of rules for fulfilling that
|
||||
dependency.
|
||||
|
||||
.. _package-requirements:
|
||||
|
||||
--------------------
|
||||
Package Requirements
|
||||
--------------------
|
||||
|
||||
You can use the configuration to force the concretizer to choose
|
||||
specific properties for packages when building them. Like preferences,
|
||||
these are only applied when the package is required by some other
|
||||
request (e.g. if the package is needed as a dependency of a
|
||||
request to ``spack install``).
|
||||
Spack can be configured to always use certain compilers, package
|
||||
versions, and variants during concretization through package
|
||||
requirements.
|
||||
|
||||
An example of where this is useful is if you have a package that
|
||||
is normally built as a dependency but only under certain circumstances
|
||||
(e.g. only when a variant on a dependent is active): you can make
|
||||
sure that it always builds the way you want it to; this distinguishes
|
||||
package configuration requirements from constraints that you add to
|
||||
``spack install`` or to environments (in those cases, the associated
|
||||
packages are always built).
|
||||
Package requirements are useful when you find yourself repeatedly
|
||||
specifying the same constraints on the command line, and wish that
|
||||
Spack respects these constraints whether you mention them explicitly
|
||||
or not. Another use case is specifying constraints that should apply
|
||||
to all root specs in an environment, without having to repeat the
|
||||
constraint everywhere.
|
||||
|
||||
The following is an example of how to enforce package properties in
|
||||
``packages.yaml``:
|
||||
Apart from that, requirements config is more flexible than constraints
|
||||
on the command line, because it can specify constraints on packages
|
||||
*when they occur* as a dependency. In contrast, on the command line it
|
||||
is not possible to specify constraints on dependencies while also keeping
|
||||
those dependencies optional.
|
||||
|
||||
The package requirements configuration is specified in ``packages.yaml``
|
||||
keyed by package name:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@ -452,15 +395,15 @@ under ``all`` are disregarded. For example, with a configuration like this:
|
||||
cmake:
|
||||
require: '%gcc'
|
||||
|
||||
Spack requires ``cmake`` to use ``gcc`` and all other nodes (including cmake dependencies)
|
||||
to use ``clang``.
|
||||
Spack requires ``cmake`` to use ``gcc`` and all other nodes (including ``cmake``
|
||||
dependencies) to use ``clang``.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Setting requirements on virtual specs
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A requirement on a virtual spec applies whenever that virtual is present in the DAG. This
|
||||
can be useful for fixing which virtual provider you want to use:
|
||||
A requirement on a virtual spec applies whenever that virtual is present in the DAG.
|
||||
This can be useful for fixing which virtual provider you want to use:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@ -470,8 +413,8 @@ can be useful for fixing which virtual provider you want to use:
|
||||
|
||||
With the configuration above the only allowed ``mpi`` provider is ``mvapich2 %gcc``.
|
||||
|
||||
Requirements on the virtual spec and on the specific provider are both applied, if present. For
|
||||
instance with a configuration like:
|
||||
Requirements on the virtual spec and on the specific provider are both applied, if
|
||||
present. For instance with a configuration like:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@ -483,6 +426,66 @@ instance with a configuration like:
|
||||
|
||||
you will use ``mvapich2~cuda %gcc`` as an ``mpi`` provider.
|
||||
|
||||
.. _package-preferences:
|
||||
|
||||
-------------------
|
||||
Package Preferences
|
||||
-------------------
|
||||
|
||||
In some cases package requirements can be too strong, and package
|
||||
preferences are the better option. Package preferences do not impose
|
||||
constraints on packages for particular versions or variants values,
|
||||
they rather only set defaults -- the concretizer is free to change
|
||||
them if it must due to other constraints. Also note that package
|
||||
preferences are of lower priority than reuse of already installed
|
||||
packages.
|
||||
|
||||
Here's an example ``packages.yaml`` file that sets preferred packages:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
opencv:
|
||||
compiler: [gcc@4.9]
|
||||
variants: +debug
|
||||
gperftools:
|
||||
version: [2.2, 2.4, 2.3]
|
||||
all:
|
||||
compiler: [gcc@4.4.7, 'gcc@4.6:', intel, clang, pgi]
|
||||
target: [sandybridge]
|
||||
providers:
|
||||
mpi: [mvapich2, mpich, openmpi]
|
||||
|
||||
At a high level, this example is specifying how packages are preferably
|
||||
concretized. The opencv package should prefer using GCC 4.9 and
|
||||
be built with debug options. The gperftools package should prefer version
|
||||
2.2 over 2.4. Every package on the system should prefer mvapich2 for
|
||||
its MPI and GCC 4.4.7 (except for opencv, which overrides this by preferring GCC 4.9).
|
||||
These options are used to fill in implicit defaults. Any of them can be overwritten
|
||||
on the command line if explicitly requested.
|
||||
|
||||
Package preferences accept the follow keys or components under
|
||||
the specific package (or ``all``) section: ``compiler``, ``variants``,
|
||||
``version``, ``providers``, and ``target``. Each component has an
|
||||
ordered list of spec ``constraints``, with earlier entries in the
|
||||
list being preferred over later entries.
|
||||
|
||||
Sometimes a package installation may have constraints that forbid
|
||||
the first concretization rule, in which case Spack will use the first
|
||||
legal concretization rule. Going back to the example, if a user
|
||||
requests gperftools 2.3 or later, then Spack will install version 2.4
|
||||
as the 2.4 version of gperftools is preferred over 2.3.
|
||||
|
||||
An explicit concretization rule in the preferred section will always
|
||||
take preference over unlisted concretizations. In the above example,
|
||||
xlc isn't listed in the compiler list. Every listed compiler from
|
||||
gcc to pgi will thus be preferred over the xlc compiler.
|
||||
|
||||
The syntax for the ``provider`` section differs slightly from other
|
||||
concretization rules. A provider lists a value that packages may
|
||||
``depends_on`` (e.g, MPI) and a list of rules for fulfilling that
|
||||
dependency.
|
||||
|
||||
.. _package_permissions:
|
||||
|
||||
-------------------
|
||||
@ -531,3 +534,25 @@ directories inside the install prefix. This will ensure that even
|
||||
manually placed files within the install prefix are owned by the
|
||||
assigned group. If no group is assigned, Spack will allow the OS
|
||||
default behavior to go as expected.
|
||||
|
||||
----------------------------
|
||||
Assigning Package Attributes
|
||||
----------------------------
|
||||
|
||||
You can assign class-level attributes in the configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
packages:
|
||||
mpileaks:
|
||||
# Override existing attributes
|
||||
url: http://www.somewhereelse.com/mpileaks-1.0.tar.gz
|
||||
# ... or add new ones
|
||||
x: 1
|
||||
|
||||
Attributes set this way will be accessible to any method executed
|
||||
in the package.py file (e.g. the ``install()`` method). Values for these
|
||||
attributes may be any value parseable by yaml.
|
||||
|
||||
These can only be applied to specific packages, not "all" or
|
||||
virtual packages.
|
||||
|
@ -65,7 +65,6 @@ on these ideas for each distinct build system that Spack supports:
|
||||
build_systems/custompackage
|
||||
build_systems/inteloneapipackage
|
||||
build_systems/intelpackage
|
||||
build_systems/multiplepackage
|
||||
build_systems/rocmpackage
|
||||
build_systems/sourceforgepackage
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _autotoolspackage:
|
||||
|
||||
----------------
|
||||
AutotoolsPackage
|
||||
----------------
|
||||
---------
|
||||
Autotools
|
||||
---------
|
||||
|
||||
Autotools is a GNU build system that provides a build-script generator.
|
||||
By running the platform-independent ``./configure`` script that comes
|
||||
@ -17,7 +17,7 @@ with the package, you can generate a platform-dependent Makefile.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``AutotoolsPackage`` base class comes with the following phases:
|
||||
The ``AutotoolsBuilder`` and ``AutotoolsPackage`` base classes come with the following phases:
|
||||
|
||||
#. ``autoreconf`` - generate the configure script
|
||||
#. ``configure`` - generate the Makefiles
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _bundlepackage:
|
||||
|
||||
-------------
|
||||
BundlePackage
|
||||
-------------
|
||||
------
|
||||
Bundle
|
||||
------
|
||||
|
||||
``BundlePackage`` represents a set of packages that are expected to work well
|
||||
together, such as a collection of commonly used software libraries. The
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _cmakepackage:
|
||||
|
||||
------------
|
||||
CMakePackage
|
||||
------------
|
||||
-----
|
||||
CMake
|
||||
-----
|
||||
|
||||
Like Autotools, CMake is a widely-used build-script generator. Designed
|
||||
by Kitware, CMake is the most popular build system for new C, C++, and
|
||||
@ -21,7 +21,7 @@ whereas Autotools is Unix-only.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``CMakePackage`` base class comes with the following phases:
|
||||
The ``CMakeBuilder`` and ``CMakePackage`` base classes come with the following phases:
|
||||
|
||||
#. ``cmake`` - generate the Makefile
|
||||
#. ``build`` - build the package
|
||||
@ -130,8 +130,8 @@ Adding flags to cmake
|
||||
To add additional flags to the ``cmake`` call, simply override the
|
||||
``cmake_args`` function. The following example defines values for the flags
|
||||
``WHATEVER``, ``ENABLE_BROKEN_FEATURE``, ``DETECT_HDF5``, and ``THREADS`` with
|
||||
and without the :meth:`~spack.build_systems.cmake.CMakePackage.define` and
|
||||
:meth:`~spack.build_systems.cmake.CMakePackage.define_from_variant` helper functions:
|
||||
and without the :meth:`~spack.build_systems.cmake.CMakeBuilder.define` and
|
||||
:meth:`~spack.build_systems.cmake.CMakeBuilder.define_from_variant` helper functions:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -32,7 +32,7 @@ oneAPI packages or use::
|
||||
|
||||
For more information on a specific package, do::
|
||||
|
||||
spack info <package-name>
|
||||
spack info --all <package-name>
|
||||
|
||||
Intel no longer releases new versions of Parallel Studio, which can be
|
||||
used in Spack via the :ref:`intelpackage`. All of its components can
|
||||
|
@ -5,11 +5,11 @@
|
||||
|
||||
.. _luapackage:
|
||||
|
||||
------------
|
||||
LuaPackage
|
||||
------------
|
||||
---
|
||||
Lua
|
||||
---
|
||||
|
||||
LuaPackage is a helper for the common case of Lua packages that provide
|
||||
The ``Lua`` build-system is a helper for the common case of Lua packages that provide
|
||||
a rockspec file. This is not meant to take a rock archive, but to build
|
||||
a source archive or repository that provides a rockspec, which should cover
|
||||
most lua packages. In the case a Lua package builds by Make rather than
|
||||
@ -19,7 +19,7 @@ luarocks, prefer MakefilePackage.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``LuaPackage`` base class comes with the following phases:
|
||||
The ``LuaBuilder`` and `LuaPackage`` base classes come with the following phases:
|
||||
|
||||
#. ``unpack`` - if using a rock, unpacks the rock and moves into the source directory
|
||||
#. ``preprocess`` - adjust sources or rockspec to fix build
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _makefilepackage:
|
||||
|
||||
---------------
|
||||
MakefilePackage
|
||||
---------------
|
||||
--------
|
||||
Makefile
|
||||
--------
|
||||
|
||||
The most primitive build system a package can use is a plain Makefile.
|
||||
Makefiles are simple to write for small projects, but they usually
|
||||
@ -18,7 +18,7 @@ variables.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``MakefilePackage`` base class comes with 3 phases:
|
||||
The ``MakefileBuilder`` and ``MakefilePackage`` base classes come with 3 phases:
|
||||
|
||||
#. ``edit`` - edit the Makefile
|
||||
#. ``build`` - build the project
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _mavenpackage:
|
||||
|
||||
------------
|
||||
MavenPackage
|
||||
------------
|
||||
-----
|
||||
Maven
|
||||
-----
|
||||
|
||||
Apache Maven is a general-purpose build system that does not rely
|
||||
on Makefiles to build software. It is designed for building and
|
||||
@ -17,7 +17,7 @@ managing and Java-based project.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``MavenPackage`` base class comes with the following phases:
|
||||
The ``MavenBuilder`` and ``MavenPackage`` base classes come with the following phases:
|
||||
|
||||
#. ``build`` - compile code and package into a JAR file
|
||||
#. ``install`` - copy to installation prefix
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _mesonpackage:
|
||||
|
||||
------------
|
||||
MesonPackage
|
||||
------------
|
||||
-----
|
||||
Meson
|
||||
-----
|
||||
|
||||
Much like Autotools and CMake, Meson is a build system. But it is
|
||||
meant to be both fast and as user friendly as possible. GNOME's goal
|
||||
@ -17,7 +17,7 @@ is to port modules to use the Meson build system.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``MesonPackage`` base class comes with the following phases:
|
||||
The ``MesonBuilder`` and ``MesonPackage`` base classes come with the following phases:
|
||||
|
||||
#. ``meson`` - generate ninja files
|
||||
#. ``build`` - build the project
|
||||
|
@ -1,350 +0,0 @@
|
||||
.. Copyright 2013-2022 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)
|
||||
|
||||
.. _multiplepackage:
|
||||
|
||||
----------------------
|
||||
Multiple Build Systems
|
||||
----------------------
|
||||
|
||||
Quite frequently, a package will change build systems from one version to the
|
||||
next. For example, a small project that once used a single Makefile to build
|
||||
may now require Autotools to handle the increased number of files that need to
|
||||
be compiled. Or, a package that once used Autotools may switch to CMake for
|
||||
Windows support. In this case, it becomes a bit more challenging to write a
|
||||
single build recipe for this package in Spack.
|
||||
|
||||
There are several ways that this can be handled in Spack:
|
||||
|
||||
#. Subclass the new build system, and override phases as needed (preferred)
|
||||
#. Subclass ``Package`` and implement ``install`` as needed
|
||||
#. Create separate ``*-cmake``, ``*-autotools``, etc. packages for each build system
|
||||
#. Rename the old package to ``*-legacy`` and create a new package
|
||||
#. Move the old package to a ``legacy`` repository and create a new package
|
||||
#. Drop older versions that only support the older build system
|
||||
|
||||
Of these options, 1 is preferred, and will be demonstrated in this
|
||||
documentation. Options 3-5 have issues with concretization, so shouldn't be
|
||||
used. Options 4-5 also don't support more than two build systems. Option 6 only
|
||||
works if the old versions are no longer needed. Option 1 is preferred over 2
|
||||
because it makes it easier to drop the old build system entirely.
|
||||
|
||||
The exact syntax of the package depends on which build systems you need to
|
||||
support. Below are a couple of common examples.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
Makefile -> Autotools
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Let's say we have the following package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(MakefilePackage):
|
||||
version("1.2.0", sha256="...")
|
||||
|
||||
def edit(self, spec, prefix):
|
||||
filter_file("CC=", "CC=" + spack_cc, "Makefile")
|
||||
|
||||
def install(self, spec, prefix):
|
||||
install_tree(".", prefix)
|
||||
|
||||
|
||||
The package subclasses from :ref:`makefilepackage`, which has three phases:
|
||||
|
||||
#. ``edit`` (does nothing by default)
|
||||
#. ``build`` (runs ``make`` by default)
|
||||
#. ``install`` (runs ``make install`` by default)
|
||||
|
||||
In this case, the ``install`` phase needed to be overridden because the
|
||||
Makefile did not have an install target. We also modify the Makefile to use
|
||||
Spack's compiler wrappers. The default ``build`` phase is not changed.
|
||||
|
||||
Starting with version 1.3.0, we want to use Autotools to build instead.
|
||||
:ref:`autotoolspackage` has four phases:
|
||||
|
||||
#. ``autoreconf`` (does not if a configure script already exists)
|
||||
#. ``configure`` (runs ``./configure --prefix=...`` by default)
|
||||
#. ``build`` (runs ``make`` by default)
|
||||
#. ``install`` (runs ``make install`` by default)
|
||||
|
||||
If the only version we need to support is 1.3.0, the package would look as
|
||||
simple as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(AutotoolsPackage):
|
||||
version("1.3.0", sha256="...")
|
||||
|
||||
def configure_args(self):
|
||||
return ["--enable-shared"]
|
||||
|
||||
|
||||
In this case, we use the default methods for each phase and only override
|
||||
``configure_args`` to specify additional flags to pass to ``./configure``.
|
||||
|
||||
If we wanted to write a single package that supports both versions 1.2.0 and
|
||||
1.3.0, it would look something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(AutotoolsPackage):
|
||||
version("1.3.0", sha256="...")
|
||||
version("1.2.0", sha256="...", deprecated=True)
|
||||
|
||||
def configure_args(self):
|
||||
return ["--enable-shared"]
|
||||
|
||||
# Remove the following once version 1.2.0 is dropped
|
||||
@when("@:1.2")
|
||||
def patch(self):
|
||||
filter_file("CC=", "CC=" + spack_cc, "Makefile")
|
||||
|
||||
@when("@:1.2")
|
||||
def autoreconf(self, spec, prefix):
|
||||
pass
|
||||
|
||||
@when("@:1.2")
|
||||
def configure(self, spec, prefix):
|
||||
pass
|
||||
|
||||
@when("@:1.2")
|
||||
def install(self, spec, prefix):
|
||||
install_tree(".", prefix)
|
||||
|
||||
|
||||
There are a few interesting things to note here:
|
||||
|
||||
* We added ``deprecated=True`` to version 1.2.0. This signifies that version
|
||||
1.2.0 is deprecated and shouldn't be used. However, if a user still relies
|
||||
on version 1.2.0, it's still there and builds just fine.
|
||||
* We moved the contents of the ``edit`` phase to the ``patch`` function. Since
|
||||
``AutotoolsPackage`` doesn't have an ``edit`` phase, the only way for this
|
||||
step to be executed is to move it to the ``patch`` function, which always
|
||||
gets run.
|
||||
* The ``autoreconf`` and ``configure`` phases become no-ops. Since the old
|
||||
Makefile-based build system doesn't use these, we ignore these phases when
|
||||
building ``foo@1.2.0``.
|
||||
* The ``@when`` decorator is used to override these phases only for older
|
||||
versions. The default methods are used for ``foo@1.3:``.
|
||||
|
||||
Once a new Spack release comes out, version 1.2.0 and everything below the
|
||||
comment can be safely deleted. The result is the same as if we had written a
|
||||
package for version 1.3.0 from scratch.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Autotools -> CMake
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Let's say we have the following package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Bar(AutotoolsPackage):
|
||||
version("1.2.0", sha256="...")
|
||||
|
||||
def configure_args(self):
|
||||
return ["--enable-shared"]
|
||||
|
||||
|
||||
The package subclasses from :ref:`autotoolspackage`, which has four phases:
|
||||
|
||||
#. ``autoreconf`` (does not if a configure script already exists)
|
||||
#. ``configure`` (runs ``./configure --prefix=...`` by default)
|
||||
#. ``build`` (runs ``make`` by default)
|
||||
#. ``install`` (runs ``make install`` by default)
|
||||
|
||||
In this case, we use the default methods for each phase and only override
|
||||
``configure_args`` to specify additional flags to pass to ``./configure``.
|
||||
|
||||
Starting with version 1.3.0, we want to use CMake to build instead.
|
||||
:ref:`cmakepackage` has three phases:
|
||||
|
||||
#. ``cmake`` (runs ``cmake ...`` by default)
|
||||
#. ``build`` (runs ``make`` by default)
|
||||
#. ``install`` (runs ``make install`` by default)
|
||||
|
||||
If the only version we need to support is 1.3.0, the package would look as
|
||||
simple as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Bar(CMakePackage):
|
||||
version("1.3.0", sha256="...")
|
||||
|
||||
def cmake_args(self):
|
||||
return [self.define("BUILD_SHARED_LIBS", True)]
|
||||
|
||||
|
||||
In this case, we use the default methods for each phase and only override
|
||||
``cmake_args`` to specify additional flags to pass to ``cmake``.
|
||||
|
||||
If we wanted to write a single package that supports both versions 1.2.0 and
|
||||
1.3.0, it would look something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Bar(CMakePackage):
|
||||
version("1.3.0", sha256="...")
|
||||
version("1.2.0", sha256="...", deprecated=True)
|
||||
|
||||
def cmake_args(self):
|
||||
return [self.define("BUILD_SHARED_LIBS", True)]
|
||||
|
||||
# Remove the following once version 1.2.0 is dropped
|
||||
def configure_args(self):
|
||||
return ["--enable-shared"]
|
||||
|
||||
@when("@:1.2")
|
||||
def cmake(self, spec, prefix):
|
||||
configure("--prefix=" + prefix, *self.configure_args())
|
||||
|
||||
|
||||
There are a few interesting things to note here:
|
||||
|
||||
* We added ``deprecated=True`` to version 1.2.0. This signifies that version
|
||||
1.2.0 is deprecated and shouldn't be used. However, if a user still relies
|
||||
on version 1.2.0, it's still there and builds just fine.
|
||||
* Since CMake and Autotools are so similar, we only need to override the
|
||||
``cmake`` phase, we can use the default ``build`` and ``install`` phases.
|
||||
* We override ``cmake`` to run ``./configure`` for older versions.
|
||||
``configure_args`` remains the same.
|
||||
* The ``@when`` decorator is used to override these phases only for older
|
||||
versions. The default methods are used for ``bar@1.3:``.
|
||||
|
||||
Once a new Spack release comes out, version 1.2.0 and everything below the
|
||||
comment can be safely deleted. The result is the same as if we had written a
|
||||
package for version 1.3.0 from scratch.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Multiple build systems for the same version
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
During the transition from one build system to another, developers often
|
||||
support multiple build systems at the same time. Spack can only use a single
|
||||
build system for a single version. To decide which build system to use for a
|
||||
particular version, take the following things into account:
|
||||
|
||||
1. If the developers explicitly state that one build system is preferred over
|
||||
another, use that one.
|
||||
2. If one build system is considered "experimental" while another is considered
|
||||
"stable", use the stable build system.
|
||||
3. Otherwise, use the newer build system.
|
||||
|
||||
The developer preference for which build system to use can change over time as
|
||||
a newer build system becomes stable/recommended.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Dropping support for old build systems
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When older versions of a package don't support a newer build system, it can be
|
||||
tempting to simply delete them from a package. This significantly reduces
|
||||
package complexity and makes the build recipe much easier to maintain. However,
|
||||
other packages or Spack users may rely on these older versions. The recommended
|
||||
approach is to first support both build systems (as demonstrated above),
|
||||
:ref:`deprecate <deprecate>` versions that rely on the old build system, and
|
||||
remove those versions and any phases that needed to be overridden in the next
|
||||
Spack release.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Three or more build systems
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In rare cases, a package may change build systems multiple times. For example,
|
||||
a package may start with Makefiles, then switch to Autotools, then switch to
|
||||
CMake. The same logic used above can be extended to any number of build systems.
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Baz(CMakePackage):
|
||||
version("1.4.0", sha256="...") # CMake
|
||||
version("1.3.0", sha256="...") # Autotools
|
||||
version("1.2.0", sha256="...") # Makefile
|
||||
|
||||
def cmake_args(self):
|
||||
return [self.define("BUILD_SHARED_LIBS", True)]
|
||||
|
||||
# Remove the following once version 1.3.0 is dropped
|
||||
def configure_args(self):
|
||||
return ["--enable-shared"]
|
||||
|
||||
@when("@1.3")
|
||||
def cmake(self, spec, prefix):
|
||||
configure("--prefix=" + prefix, *self.configure_args())
|
||||
|
||||
# Remove the following once version 1.2.0 is dropped
|
||||
@when("@:1.2")
|
||||
def patch(self):
|
||||
filter_file("CC=", "CC=" + spack_cc, "Makefile")
|
||||
|
||||
@when("@:1.2")
|
||||
def cmake(self, spec, prefix):
|
||||
pass
|
||||
|
||||
@when("@:1.2")
|
||||
def install(self, spec, prefix):
|
||||
install_tree(".", prefix)
|
||||
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Additional examples
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When writing new packages, it often helps to see examples of existing packages.
|
||||
Here is an incomplete list of existing Spack packages that have changed build
|
||||
systems before:
|
||||
|
||||
================ ===================== ================
|
||||
Package Previous Build System New Build System
|
||||
================ ===================== ================
|
||||
amber custom CMake
|
||||
arpack-ng Autotools CMake
|
||||
atk Autotools Meson
|
||||
blast None Autotools
|
||||
dyninst Autotools CMake
|
||||
evtgen Autotools CMake
|
||||
fish Autotools CMake
|
||||
gdk-pixbuf Autotools Meson
|
||||
glib Autotools Meson
|
||||
glog Autotools CMake
|
||||
gmt Autotools CMake
|
||||
gtkplus Autotools Meson
|
||||
hpl Makefile Autotools
|
||||
interproscan Perl Maven
|
||||
jasper Autotools CMake
|
||||
kahip SCons CMake
|
||||
kokkos Makefile CMake
|
||||
kokkos-kernels Makefile CMake
|
||||
leveldb Makefile CMake
|
||||
libdrm Autotools Meson
|
||||
libjpeg-turbo Autotools CMake
|
||||
mesa Autotools Meson
|
||||
metis None CMake
|
||||
mpifileutils Autotools CMake
|
||||
muparser Autotools CMake
|
||||
mxnet Makefile CMake
|
||||
nest Autotools CMake
|
||||
neuron Autotools CMake
|
||||
nsimd CMake nsconfig
|
||||
opennurbs Makefile CMake
|
||||
optional-lite None CMake
|
||||
plasma Makefile CMake
|
||||
preseq Makefile Autotools
|
||||
protobuf Autotools CMake
|
||||
py-pygobject Autotools Python
|
||||
singularity Autotools Makefile
|
||||
span-lite None CMake
|
||||
ssht Makefile CMake
|
||||
string-view-lite None CMake
|
||||
superlu Makefile CMake
|
||||
superlu-dist Makefile CMake
|
||||
uncrustify Autotools CMake
|
||||
================ ===================== ================
|
||||
|
||||
Packages that support multiple build systems can be a bit confusing to write.
|
||||
Don't hesitate to open an issue or draft pull request and ask for advice from
|
||||
other Spack developers!
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _octavepackage:
|
||||
|
||||
-------------
|
||||
OctavePackage
|
||||
-------------
|
||||
------
|
||||
Octave
|
||||
------
|
||||
|
||||
Octave has its own build system for installing packages.
|
||||
|
||||
@ -15,7 +15,7 @@ Octave has its own build system for installing packages.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``OctavePackage`` base class has a single phase:
|
||||
The ``OctaveBuilder`` and ``OctavePackage`` base classes have a single phase:
|
||||
|
||||
#. ``install`` - install the package
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _perlpackage:
|
||||
|
||||
-----------
|
||||
PerlPackage
|
||||
-----------
|
||||
----
|
||||
Perl
|
||||
----
|
||||
|
||||
Much like Octave, Perl has its own language-specific
|
||||
build system.
|
||||
@ -16,7 +16,7 @@ build system.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``PerlPackage`` base class comes with 3 phases that can be overridden:
|
||||
The ``PerlBuilder`` and ``PerlPackage`` base classes come with 3 phases that can be overridden:
|
||||
|
||||
#. ``configure`` - configure the package
|
||||
#. ``build`` - build the package
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _qmakepackage:
|
||||
|
||||
------------
|
||||
QMakePackage
|
||||
------------
|
||||
-----
|
||||
QMake
|
||||
-----
|
||||
|
||||
Much like Autotools and CMake, QMake is a build-script generator
|
||||
designed by the developers of Qt. In its simplest form, Spack's
|
||||
@ -29,7 +29,7 @@ variables or edit ``*.pro`` files to get things working properly.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``QMakePackage`` base class comes with the following phases:
|
||||
The ``QMakeBuilder`` and ``QMakePackage`` base classes come with the following phases:
|
||||
|
||||
#. ``qmake`` - generate Makefiles
|
||||
#. ``build`` - build the project
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _racketpackage:
|
||||
|
||||
-------------
|
||||
RacketPackage
|
||||
-------------
|
||||
------
|
||||
Racket
|
||||
------
|
||||
|
||||
Much like Python, Racket packages and modules have their own special build system.
|
||||
To learn more about the specifics of Racket package system, please refer to the
|
||||
@ -17,7 +17,7 @@ To learn more about the specifics of Racket package system, please refer to the
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``RacketPackage`` base class provides an ``install`` phase that
|
||||
The ``RacketBuilder`` and ``RacketPackage`` base classes provides an ``install`` phase that
|
||||
can be overridden, corresponding to the use of:
|
||||
|
||||
.. code-block:: console
|
||||
|
@ -19,7 +19,7 @@ new Spack packages for.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``RPackage`` base class has a single phase:
|
||||
The ``RBuilder`` and ``RPackage`` base classes have a single phase:
|
||||
|
||||
#. ``install`` - install the package
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _rubypackage:
|
||||
|
||||
-----------
|
||||
RubyPackage
|
||||
-----------
|
||||
----
|
||||
Ruby
|
||||
----
|
||||
|
||||
Like Perl, Python, and R, Ruby has its own build system for
|
||||
installing Ruby gems.
|
||||
@ -16,7 +16,7 @@ installing Ruby gems.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``RubyPackage`` base class provides the following phases that
|
||||
The ``RubyBuilder`` and ``RubyPackage`` base classes provide the following phases that
|
||||
can be overridden:
|
||||
|
||||
#. ``build`` - build everything needed to install
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _sconspackage:
|
||||
|
||||
------------
|
||||
SConsPackage
|
||||
------------
|
||||
-----
|
||||
SCons
|
||||
-----
|
||||
|
||||
SCons is a general-purpose build system that does not rely on
|
||||
Makefiles to build software. SCons is written in Python, and handles
|
||||
@ -42,7 +42,7 @@ As previously mentioned, SCons allows developers to add subcommands like
|
||||
$ scons install
|
||||
|
||||
|
||||
To facilitate this, the ``SConsPackage`` base class provides the
|
||||
To facilitate this, the ``SConsBuilder`` and ``SconsPackage`` base classes provide the
|
||||
following phases:
|
||||
|
||||
#. ``build`` - build the package
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _sippackage:
|
||||
|
||||
----------
|
||||
SIPPackage
|
||||
----------
|
||||
---
|
||||
SIP
|
||||
---
|
||||
|
||||
SIP is a tool that makes it very easy to create Python bindings for C and C++
|
||||
libraries. It was originally developed to create PyQt, the Python bindings for
|
||||
@ -22,7 +22,7 @@ provides support functions to the automatically generated code.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``SIPPackage`` base class comes with the following phases:
|
||||
The ``SIPBuilder`` and ``SIPPackage`` base classes come with the following phases:
|
||||
|
||||
#. ``configure`` - configure the package
|
||||
#. ``build`` - build the package
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
.. _wafpackage:
|
||||
|
||||
----------
|
||||
WafPackage
|
||||
----------
|
||||
---
|
||||
Waf
|
||||
---
|
||||
|
||||
Like SCons, Waf is a general-purpose build system that does not rely
|
||||
on Makefiles to build software.
|
||||
@ -16,7 +16,7 @@ on Makefiles to build software.
|
||||
Phases
|
||||
^^^^^^
|
||||
|
||||
The ``WafPackage`` base class comes with the following phases:
|
||||
The ``WafBuilder`` and ``WafPackage`` base classes come with the following phases:
|
||||
|
||||
#. ``configure`` - configure the project
|
||||
#. ``build`` - build the project
|
||||
|
@ -32,6 +32,9 @@
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
link_name = os.path.abspath("_spack_root")
|
||||
if not os.path.exists(link_name):
|
||||
os.symlink(os.path.abspath("../../.."), link_name, target_is_directory=True)
|
||||
sys.path.insert(0, os.path.abspath("_spack_root/lib/spack/external"))
|
||||
sys.path.insert(0, os.path.abspath("_spack_root/lib/spack/external/pytest-fallback"))
|
||||
|
||||
@ -206,6 +209,9 @@ def setup(sphinx):
|
||||
# Spack classes that are private and we don't want to expose
|
||||
("py:class", "spack.provider_index._IndexBase"),
|
||||
("py:class", "spack.repo._PrependFileLoader"),
|
||||
("py:class", "spack.build_systems._checks.BaseBuilder"),
|
||||
# Spack classes that intersphinx is unable to resolve
|
||||
("py:class", "spack.version.VersionBase"),
|
||||
]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
|
@ -224,9 +224,9 @@ them). Please note that we currently disable ccache's ``hash_dir``
|
||||
feature to avoid an issue with the stage directory (see
|
||||
https://github.com/LLNL/spack/pull/3761#issuecomment-294352232).
|
||||
|
||||
------------------
|
||||
``shared_linking``
|
||||
------------------
|
||||
-----------------------
|
||||
``shared_linking:type``
|
||||
-----------------------
|
||||
|
||||
Control whether Spack embeds ``RPATH`` or ``RUNPATH`` attributes in ELF binaries
|
||||
so that they can find their dependencies. Has no effect on macOS.
|
||||
@ -245,6 +245,52 @@ the loading object.
|
||||
|
||||
DO NOT MIX the two options within the same install tree.
|
||||
|
||||
-----------------------
|
||||
``shared_linking:bind``
|
||||
-----------------------
|
||||
|
||||
This is an *experimental option* that controls whether Spack embeds absolute paths
|
||||
to needed shared libraries in ELF executables and shared libraries on Linux. Setting
|
||||
this option to ``true`` has two advantages:
|
||||
|
||||
1. **Improved startup time**: when running an executable, the dynamic loader does not
|
||||
have to perform a search for needed libraries, they are loaded directly.
|
||||
2. **Reliability**: libraries loaded at runtime are those that were linked to. This
|
||||
minimizes the risk of accidentally picking up system libraries.
|
||||
|
||||
In the current implementation, Spack sets the soname (shared object name) of
|
||||
libraries to their install path upon installation. This has two implications:
|
||||
|
||||
1. binding does not apply to libraries installed *before* the option was enabled;
|
||||
2. toggling the option off does *not* prevent binding of libraries installed when
|
||||
the option was still enabled.
|
||||
|
||||
It is also worth noting that:
|
||||
|
||||
1. Applications relying on ``dlopen(3)`` will continue to work, even when they open
|
||||
a library by name. This is because ``RPATH``\s are retained in binaries also
|
||||
when ``bind`` is enabled.
|
||||
2. ``LD_PRELOAD`` continues to work for the typical use case of overriding
|
||||
symbols, such as preloading a library with a more efficient ``malloc``.
|
||||
However, the preloaded library will be loaded *additionally to*, instead of
|
||||
*in place of* another library with the same name --- this can be problematic
|
||||
in very rare cases where libraries rely on a particular ``init`` or ``fini``
|
||||
order.
|
||||
|
||||
.. note::
|
||||
|
||||
In some cases packages provide *stub libraries* that only contain an interface
|
||||
for linking, but lack an implementation for runtime. An example of this is
|
||||
``libcuda.so``, provided by the CUDA toolkit; it can be used to link against,
|
||||
but the library needed at runtime is the one installed with the CUDA driver.
|
||||
To avoid binding those libraries, they can be marked as non-bindable using
|
||||
a property in the package:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Example(Package):
|
||||
non_bindable_shared_objects = ["libinterface.so"]
|
||||
|
||||
----------------------
|
||||
``terminal_title``
|
||||
----------------------
|
||||
|
@ -405,6 +405,17 @@ Spack understands several special variables. These are:
|
||||
* ``$user``: name of the current user
|
||||
* ``$user_cache_path``: user cache directory (``~/.spack`` unless
|
||||
:ref:`overridden <local-config-overrides>`)
|
||||
* ``$architecture``: the architecture triple of the current host, as
|
||||
detected by Spack.
|
||||
* ``$arch``: alias for ``$architecture``.
|
||||
* ``$platform``: the platform of the current host, as detected by Spack.
|
||||
* ``$operating_system``: the operating system of the current host, as
|
||||
detected by the ``distro`` python module.
|
||||
* ``$os``: alias for ``$operating_system``.
|
||||
* ``$target``: the ISA target for the current host, as detected by
|
||||
ArchSpec. E.g. ``skylake`` or ``neoverse-n1``.
|
||||
* ``$target_family``. The target family for the current host, as
|
||||
detected by ArchSpec. E.g. ``x86_64`` or ``aarch64``.
|
||||
|
||||
Note that, as with shell variables, you can write these as ``$varname``
|
||||
or with braces to distinguish the variable from surrounding characters:
|
||||
@ -549,7 +560,7 @@ down the problem:
|
||||
|
||||
You can see above that the ``build_jobs`` and ``debug`` settings are
|
||||
built in and are not overridden by a configuration file. The
|
||||
``verify_ssl`` setting comes from the ``--insceure`` option on the
|
||||
``verify_ssl`` setting comes from the ``--insecure`` option on the
|
||||
command line. ``dirty`` and ``install_tree`` come from the custom
|
||||
scopes ``./my-scope`` and ``./my-scope-2``, and all other configuration
|
||||
options come from the default configuration files that ship with Spack.
|
||||
|
@ -149,11 +149,9 @@ grouped by functionality.
|
||||
Package-related modules
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:mod:`spack.package`
|
||||
Contains the :class:`~spack.package_base.Package` class, which
|
||||
is the superclass for all packages in Spack. Methods on ``Package``
|
||||
implement all phases of the :ref:`package lifecycle
|
||||
<package-lifecycle>` and manage the build process.
|
||||
:mod:`spack.package_base`
|
||||
Contains the :class:`~spack.package_base.PackageBase` class, which
|
||||
is the superclass for all packages in Spack.
|
||||
|
||||
:mod:`spack.util.naming`
|
||||
Contains functions for mapping between Spack package names,
|
||||
|
@ -520,27 +520,33 @@ available from the yaml file.
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Spec concretization
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
An environment can be concretized in three different modes and the behavior active under any environment
|
||||
is determined by the ``concretizer:unify`` property. By default specs are concretized *separately*, one after the other:
|
||||
An environment can be concretized in three different modes and the behavior active under
|
||||
any environment is determined by the ``concretizer:unify`` configuration option.
|
||||
|
||||
The *default* mode is to unify all specs:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
specs:
|
||||
- hdf5~mpi
|
||||
- hdf5+mpi
|
||||
- zlib@1.2.8
|
||||
concretizer:
|
||||
unify: false
|
||||
unify: true
|
||||
|
||||
This mode of operation permits to deploy a full software stack where multiple configurations of the same package
|
||||
need to be installed alongside each other using the best possible selection of transitive dependencies. The downside
|
||||
is that redundancy of installations is disregarded completely, and thus environments might be more bloated than
|
||||
strictly needed. In the example above, for instance, if a version of ``zlib`` newer than ``1.2.8`` is known to Spack,
|
||||
then it will be used for both ``hdf5`` installations.
|
||||
This means that any package in the environment corresponds to a single concrete spec. In
|
||||
the above example, when ``hdf5`` depends down the line of ``zlib``, it is required to
|
||||
take ``zlib@1.2.8`` instead of a newer version. This mode of concretization is
|
||||
particularly useful when environment views are used: if every package occurs in
|
||||
only one flavor, it is usually possible to merge all install directories into a view.
|
||||
|
||||
If redundancy of the environment is a concern, Spack provides a way to install it *together where possible*,
|
||||
i.e. trying to maximize reuse of dependencies across different specs:
|
||||
A downside of unified concretization is that it can be overly strict. For example, a
|
||||
concretization error would happen when both ``hdf5+mpi`` and ``hdf5~mpi`` are specified
|
||||
in an environment.
|
||||
|
||||
The second mode is to *unify when possible*: this makes concretization of root specs
|
||||
more independendent. Instead of requiring reuse of dependencies across different root
|
||||
specs, it is only maximized:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@ -552,26 +558,27 @@ i.e. trying to maximize reuse of dependencies across different specs:
|
||||
concretizer:
|
||||
unify: when_possible
|
||||
|
||||
Also in this case Spack allows having multiple configurations of the same package, but privileges the reuse of
|
||||
specs over other factors. Going back to our example, this means that both ``hdf5`` installations will use
|
||||
``zlib@1.2.8`` as a dependency even if newer versions of that library are available.
|
||||
Central installations done at HPC centers by system administrators or user support groups are a common case
|
||||
that fits either of these two modes.
|
||||
This means that both ``hdf5`` installations will use ``zlib@1.2.8`` as a dependency even
|
||||
if newer versions of that library are available.
|
||||
|
||||
Environments can also be configured to concretize all the root specs *together*, in a self-consistent way, to
|
||||
ensure that each package in the environment comes with a single configuration:
|
||||
The third mode of operation is to concretize root specs entirely independently by
|
||||
disabling unified concretization:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
specs:
|
||||
- hdf5~mpi
|
||||
- hdf5+mpi
|
||||
- zlib@1.2.8
|
||||
concretizer:
|
||||
unify: true
|
||||
unify: false
|
||||
|
||||
This mode of operation is usually what is required by software developers that want to deploy their development
|
||||
environment and have a single view of it in the filesystem.
|
||||
In this example ``hdf5`` is concretized separately, and does not consider ``zlib@1.2.8``
|
||||
as a constraint or preference. Instead, it will take the latest possible version.
|
||||
|
||||
The last two concretization options are typically useful for system administrators and
|
||||
user support groups providing a large software stack for their HPC center.
|
||||
|
||||
.. note::
|
||||
|
||||
@ -582,10 +589,10 @@ environment and have a single view of it in the filesystem.
|
||||
|
||||
.. admonition:: Re-concretization of user specs
|
||||
|
||||
When concretizing specs *together* or *together where possible* the entire set of specs will be
|
||||
When using *unified* concretization (when possible), the entire set of specs will be
|
||||
re-concretized after any addition of new user specs, to ensure that
|
||||
the environment remains consistent / minimal. When instead the specs are concretized
|
||||
separately only the new specs will be re-concretized after any addition.
|
||||
the environment remains consistent / minimal. When instead unified concretization is
|
||||
disabled, only the new specs will be concretized after any addition.
|
||||
|
||||
^^^^^^^^^^^^^
|
||||
Spec Matrices
|
||||
@ -987,7 +994,7 @@ A typical workflow is as follows:
|
||||
spack env create -d .
|
||||
spack -e . add perl
|
||||
spack -e . concretize
|
||||
spack -e . env depfile > Makefile
|
||||
spack -e . env depfile -o Makefile
|
||||
make -j64
|
||||
|
||||
This generates a ``Makefile`` from a concretized environment in the
|
||||
@ -1000,7 +1007,6 @@ load, even when packages are built in parallel.
|
||||
By default the following phony convenience targets are available:
|
||||
|
||||
- ``make all``: installs the environment (default target);
|
||||
- ``make fetch-all``: only fetch sources of all packages;
|
||||
- ``make clean``: cleans files used by make, but does not uninstall packages.
|
||||
|
||||
.. tip::
|
||||
@ -1010,8 +1016,17 @@ By default the following phony convenience targets are available:
|
||||
printed orderly per package install. To get synchronized output with colors,
|
||||
use ``make -j<N> SPACK_COLOR=always --output-sync=recurse``.
|
||||
|
||||
The following advanced example shows how generated targets can be used in a
|
||||
``Makefile``:
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Specifying dependencies on generated ``make`` targets
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
An interesting question is how to include generated ``Makefile``\s in your own
|
||||
``Makefile``\s. This comes up when you want to install an environment that provides
|
||||
executables required in a command for a make target of your own.
|
||||
|
||||
The example below shows how to accomplish this: the ``env`` target specifies
|
||||
the generated ``spack/env`` target as a prerequisite, meaning that the environment
|
||||
gets installed and is available for use in the ``env`` target.
|
||||
|
||||
.. code:: Makefile
|
||||
|
||||
@ -1037,11 +1052,10 @@ The following advanced example shows how generated targets can be used in a
|
||||
include env.mk
|
||||
endif
|
||||
|
||||
When ``make`` is invoked, it first "remakes" the missing include ``env.mk``
|
||||
from its rule, which triggers concretization. When done, the generated target
|
||||
``spack/env`` is available. In the above example, the ``env`` target uses this generated
|
||||
target as a prerequisite, meaning that it can make use of the installed packages in
|
||||
its commands.
|
||||
This works as follows: when ``make`` is invoked, it first "remakes" the missing
|
||||
include ``env.mk`` as there is a target for it. This triggers concretization of
|
||||
the environment and makes spack output ``env.mk``. At that point the
|
||||
generated target ``spack/env`` becomes available through ``include env.mk``.
|
||||
|
||||
As it is typically undesirable to remake ``env.mk`` as part of ``make clean``,
|
||||
the include is conditional.
|
||||
@ -1052,3 +1066,24 @@ the include is conditional.
|
||||
the ``--make-target-prefix`` flag and use the non-phony target
|
||||
``<target-prefix>/env`` as prerequisite, instead of the phony target
|
||||
``<target-prefix>/all``.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Building a subset of the environment
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The generated ``Makefile``\s contain install targets for each spec. Given the hash
|
||||
of a particular spec, you can use the ``.install/<hash>`` target to install the
|
||||
spec with its dependencies. There is also ``.install-deps/<hash>`` to *only* install
|
||||
its dependencies. This can be useful when certain flags should only apply to
|
||||
dependencies. Below we show a use case where a spec is installed with verbose
|
||||
output (``spack install --verbose``) while its dependencies are installed silently:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ spack env depfile -o Makefile --make-target-prefix my_env
|
||||
|
||||
# Install dependencies in parallel, only show a log on error.
|
||||
$ make -j16 my_env/.install-deps/<hash> SPACK_INSTALL_FLAGS=--show-log-on-error
|
||||
|
||||
# Install the root spec with verbose output.
|
||||
$ make -j16 my_env/.install/<hash> SPACK_INSTALL_FLAGS=--verbose
|
@ -98,40 +98,42 @@ For example, this command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack create http://www.mr511.de/software/libelf-0.8.13.tar.gz
|
||||
$ spack create https://ftp.osuosl.org/pub/blfs/conglomeration/libelf/libelf-0.8.13.tar.gz
|
||||
|
||||
creates a simple python file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from spack import *
|
||||
from spack.package import *
|
||||
|
||||
|
||||
class Libelf(Package):
|
||||
class Libelf(AutotoolsPackage):
|
||||
"""FIXME: Put a proper description of your package here."""
|
||||
|
||||
# FIXME: Add a proper url for your package's homepage here.
|
||||
homepage = "http://www.example.com"
|
||||
url = "http://www.mr511.de/software/libelf-0.8.13.tar.gz"
|
||||
homepage = "https://www.example.com"
|
||||
url = "https://ftp.osuosl.org/pub/blfs/conglomeration/libelf/libelf-0.8.13.tar.gz"
|
||||
|
||||
version('0.8.13', '4136d7b4c04df68b686570afa26988ac')
|
||||
# FIXME: Add a list of GitHub accounts to
|
||||
# notify when the package is updated.
|
||||
# maintainers = ["github_user1", "github_user2"]
|
||||
|
||||
version("0.8.13", sha256="591a9b4ec81c1f2042a97aa60564e0cb79d041c52faa7416acb38bc95bd2c76d")
|
||||
|
||||
# FIXME: Add dependencies if required.
|
||||
# depends_on('foo')
|
||||
# depends_on("foo")
|
||||
|
||||
def install(self, spec, prefix):
|
||||
# FIXME: Modify the configure line to suit your build system here.
|
||||
configure('--prefix={0}'.format(prefix))
|
||||
|
||||
# FIXME: Add logic to build and install here.
|
||||
make()
|
||||
make('install')
|
||||
def configure_args(self):
|
||||
# FIXME: Add arguments other than --prefix
|
||||
# FIXME: If not needed delete this function
|
||||
args = []
|
||||
return args
|
||||
|
||||
It doesn't take much python coding to get from there to a working
|
||||
package:
|
||||
|
||||
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/libelf/package.py
|
||||
:lines: 6-
|
||||
:lines: 5-
|
||||
|
||||
Spack also provides wrapper functions around common commands like
|
||||
``configure``, ``make``, and ``cmake`` to make writing packages
|
||||
|
@ -44,7 +44,7 @@ A build matrix showing which packages are working on which systems is shown belo
|
||||
yum install -y epel-release
|
||||
yum update -y
|
||||
yum --enablerepo epel groupinstall -y "Development Tools"
|
||||
yum --enablerepo epel install -y curl findutils gcc-c++ gcc gcc-gfortran git gnupg2 hostname iproute make patch python3 python3-pip python3-setuptools unzip
|
||||
yum --enablerepo epel install -y curl findutils gcc-c++ gcc gcc-gfortran git gnupg2 hostname iproute redhat-lsb-core make patch python3 python3-pip python3-setuptools unzip
|
||||
python3 -m pip install boto3
|
||||
|
||||
.. tab-item:: macOS Brew
|
||||
@ -124,88 +124,41 @@ Spack provides two ways of bootstrapping ``clingo``: from pre-built binaries
|
||||
(default), or from sources. The fastest way to get started is to bootstrap from
|
||||
pre-built binaries.
|
||||
|
||||
.. note::
|
||||
|
||||
When bootstrapping from pre-built binaries, Spack currently requires
|
||||
``patchelf`` on Linux and ``otool`` on macOS. If ``patchelf`` is not in the
|
||||
``PATH``, Spack will build it from sources, and a C++ compiler is required.
|
||||
|
||||
The first time you concretize a spec, Spack will bootstrap in the background:
|
||||
The first time you concretize a spec, Spack will bootstrap automatically:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ time spack spec zlib
|
||||
$ spack spec zlib
|
||||
==> Bootstrapping clingo from pre-built binaries
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.4/build_cache/linux-centos7-x86_64-gcc-10.2.1-clingo-bootstrap-spack-ba5ijauisd3uuixtmactc36vps7yfsrl.spec.json
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.4/build_cache/linux-centos7-x86_64/gcc-10.2.1/clingo-bootstrap-spack/linux-centos7-x86_64-gcc-10.2.1-clingo-bootstrap-spack-ba5ijauisd3uuixtmactc36vps7yfsrl.spack
|
||||
==> Installing "clingo-bootstrap@spack%gcc@10.2.1~docs~ipo+python+static_libstdcpp build_type=Release arch=linux-centos7-x86_64" from a buildcache
|
||||
==> Bootstrapping patchelf from pre-built binaries
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.4/build_cache/linux-centos7-x86_64-gcc-10.2.1-patchelf-0.16.1-p72zyan5wrzuabtmzq7isa5mzyh6ahdp.spec.json
|
||||
==> Fetching https://mirror.spack.io/bootstrap/github-actions/v0.4/build_cache/linux-centos7-x86_64/gcc-10.2.1/patchelf-0.16.1/linux-centos7-x86_64-gcc-10.2.1-patchelf-0.16.1-p72zyan5wrzuabtmzq7isa5mzyh6ahdp.spack
|
||||
==> Installing "patchelf@0.16.1%gcc@10.2.1 ldflags="-static-libstdc++ -static-libgcc" build_system=autotools arch=linux-centos7-x86_64" from a buildcache
|
||||
Input spec
|
||||
--------------------------------
|
||||
zlib
|
||||
|
||||
Concretized
|
||||
--------------------------------
|
||||
zlib@1.2.11%gcc@7.5.0+optimize+pic+shared arch=linux-ubuntu18.04-zen
|
||||
|
||||
real 0m20.023s
|
||||
user 0m18.351s
|
||||
sys 0m0.784s
|
||||
|
||||
After this command you'll see that ``clingo`` has been installed for Spack's own use:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack find -b
|
||||
==> Showing internal bootstrap store at "/root/.spack/bootstrap/store"
|
||||
==> 3 installed packages
|
||||
-- linux-rhel5-x86_64 / gcc@9.3.0 -------------------------------
|
||||
clingo-bootstrap@spack python@3.6
|
||||
|
||||
-- linux-ubuntu18.04-zen / gcc@7.5.0 ----------------------------
|
||||
patchelf@0.13
|
||||
|
||||
Subsequent calls to the concretizer will then be much faster:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ time spack spec zlib
|
||||
[ ... ]
|
||||
real 0m0.490s
|
||||
user 0m0.431s
|
||||
sys 0m0.041s
|
||||
|
||||
zlib@1.2.13%gcc@9.4.0+optimize+pic+shared build_system=makefile arch=linux-ubuntu20.04-icelake
|
||||
|
||||
If for security concerns you cannot bootstrap ``clingo`` from pre-built
|
||||
binaries, you have to mark this bootstrapping method as untrusted. This makes
|
||||
Spack fall back to bootstrapping from sources:
|
||||
binaries, you have to disable fetching the binaries we generated with Github Actions.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack bootstrap untrust github-actions-v0.2
|
||||
==> "github-actions-v0.2" is now untrusted and will not be used for bootstrapping
|
||||
$ spack bootstrap disable github-actions-v0.4
|
||||
==> "github-actions-v0.4" is now disabled and will not be used for bootstrapping
|
||||
$ spack bootstrap disable github-actions-v0.3
|
||||
==> "github-actions-v0.3" is now disabled and will not be used for bootstrapping
|
||||
|
||||
You can verify that the new settings are effective with:
|
||||
|
||||
.. code-block:: console
|
||||
.. command-output:: spack bootstrap list
|
||||
|
||||
$ spack bootstrap list
|
||||
Name: github-actions-v0.2 UNTRUSTED
|
||||
|
||||
Type: buildcache
|
||||
|
||||
Info:
|
||||
url: https://mirror.spack.io/bootstrap/github-actions/v0.2
|
||||
homepage: https://github.com/spack/spack-bootstrap-mirrors
|
||||
releases: https://github.com/spack/spack-bootstrap-mirrors/releases
|
||||
|
||||
Description:
|
||||
Buildcache generated from a public workflow using Github Actions.
|
||||
The sha256 checksum of binaries is checked before installation.
|
||||
|
||||
[ ... ]
|
||||
|
||||
Name: spack-install TRUSTED
|
||||
|
||||
Type: install
|
||||
|
||||
Description:
|
||||
Specs built from sources by Spack. May take a long time.
|
||||
|
||||
.. note::
|
||||
|
||||
@ -235,9 +188,7 @@ under the ``${HOME}/.spack`` directory. The software installed there can be quer
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack find --bootstrap
|
||||
==> Showing internal bootstrap store at "/home/spack/.spack/bootstrap/store"
|
||||
==> 3 installed packages
|
||||
$ spack -b find
|
||||
-- linux-ubuntu18.04-x86_64 / gcc@10.1.0 ------------------------
|
||||
clingo-bootstrap@spack python@3.6.9 re2c@1.2.1
|
||||
|
||||
@ -246,7 +197,7 @@ In case it's needed the bootstrap store can also be cleaned with:
|
||||
.. code-block:: console
|
||||
|
||||
$ spack clean -b
|
||||
==> Removing software in "/home/spack/.spack/bootstrap/store"
|
||||
==> Removing bootstrapped software and configuration in "/home/spack/.spack/bootstrap"
|
||||
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Check Installation
|
||||
|
BIN
lib/spack/docs/images/adapter.png
Normal file
BIN
lib/spack/docs/images/adapter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 658 KiB |
BIN
lib/spack/docs/images/builder_package_architecture.png
Normal file
BIN
lib/spack/docs/images/builder_package_architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 449 KiB |
BIN
lib/spack/docs/images/builder_phases.png
Normal file
BIN
lib/spack/docs/images/builder_phases.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 128 KiB |
BIN
lib/spack/docs/images/installation_pipeline.png
Normal file
BIN
lib/spack/docs/images/installation_pipeline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
BIN
lib/spack/docs/images/original_package_architecture.png
Normal file
BIN
lib/spack/docs/images/original_package_architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
3092
lib/spack/docs/images/packaging.excalidrawlib
Normal file
3092
lib/spack/docs/images/packaging.excalidrawlib
Normal file
File diff suppressed because it is too large
Load Diff
@ -56,7 +56,6 @@ or refer to the full manual below.
|
||||
basic_usage
|
||||
Tutorial: Spack 101 <https://spack-tutorial.readthedocs.io>
|
||||
replace_conda_homebrew
|
||||
known_issues
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
@ -1,40 +0,0 @@
|
||||
.. Copyright 2013-2022 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)
|
||||
|
||||
============
|
||||
Known Issues
|
||||
============
|
||||
|
||||
This is a list of known issues in Spack. It provides ways of getting around these
|
||||
problems if you encounter them.
|
||||
|
||||
------------------------------------------------
|
||||
Spack does not seem to respect ``packages.yaml``
|
||||
------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This issue is **resolved** as of v0.19.0.dev0 commit
|
||||
`8281a0c5feabfc4fe180846d6fe95cfe53420bc5`, through the introduction of package
|
||||
requirements. See :ref:`package-requirements`.
|
||||
|
||||
A common problem in Spack v0.18.0 up to v0.19.0.dev0 is that package, compiler and target
|
||||
preferences specified in ``packages.yaml`` do not seem to be respected. Spack picks the
|
||||
"wrong" compilers and their versions, package versions and variants, and
|
||||
micro-architectures.
|
||||
|
||||
This is however not a bug. In order to reduce the number of builds of the same
|
||||
packages, the concretizer values reuse of installed packages higher than preferences
|
||||
set in ``packages.yaml``. Note that ``packages.yaml`` specifies only preferences, not
|
||||
hard constraints.
|
||||
|
||||
There are multiple workarounds:
|
||||
|
||||
1. Disable reuse during concretization: ``spack install --fresh <spec>`` when installing
|
||||
from the command line, or ``spack concretize --fresh --force`` when using
|
||||
environments.
|
||||
2. Turn preferences into constrains, by moving them to the input spec. For example,
|
||||
use ``spack spec zlib%gcc@12`` when you want to force GCC 12 even if ``zlib`` was
|
||||
already installed with GCC 10.
|
@ -34,24 +34,155 @@ ubiquitous in the scientific software community. Second, it's a modern
|
||||
language and has many powerful features to help make package writing
|
||||
easy.
|
||||
|
||||
---------------------------
|
||||
Creating & editing packages
|
||||
---------------------------
|
||||
|
||||
.. _installation_procedure:
|
||||
|
||||
--------------------------------------
|
||||
Overview of the installation procedure
|
||||
--------------------------------------
|
||||
|
||||
Whenever Spack installs software, it goes through a series of predefined steps:
|
||||
|
||||
.. image:: images/installation_pipeline.png
|
||||
:scale: 60 %
|
||||
:align: center
|
||||
|
||||
All these steps are influenced by the metadata in each ``package.py`` and
|
||||
by the current Spack configuration.
|
||||
Since build systems are different from one another, the execution of the
|
||||
last block in the figure is further expanded in a build system specific way.
|
||||
An example for ``CMake`` is, for instance:
|
||||
|
||||
.. image:: images/builder_phases.png
|
||||
:align: center
|
||||
:scale: 60 %
|
||||
|
||||
The predefined steps for each build system are called "phases".
|
||||
In general, the name and order in which the phases will be executed can be
|
||||
obtained by either reading the API docs at :py:mod:`~.spack.build_systems`, or
|
||||
using the ``spack info`` command:
|
||||
|
||||
.. code-block:: console
|
||||
:emphasize-lines: 13,14
|
||||
|
||||
$ spack info --phases m4
|
||||
AutotoolsPackage: m4
|
||||
Homepage: https://www.gnu.org/software/m4/m4.html
|
||||
|
||||
Safe versions:
|
||||
1.4.17 ftp://ftp.gnu.org/gnu/m4/m4-1.4.17.tar.gz
|
||||
|
||||
Variants:
|
||||
Name Default Description
|
||||
|
||||
sigsegv on Build the libsigsegv dependency
|
||||
|
||||
Installation Phases:
|
||||
autoreconf configure build install
|
||||
|
||||
Build Dependencies:
|
||||
libsigsegv
|
||||
|
||||
...
|
||||
|
||||
An extensive list of available build systems and phases is provided in :ref:`installation_process`.
|
||||
|
||||
|
||||
------------------------
|
||||
Writing a package recipe
|
||||
------------------------
|
||||
|
||||
Since v0.19, Spack supports two ways of writing a package recipe. The most commonly used is to encode both the metadata
|
||||
(directives, etc.) and the build behavior in a single class, like shown in the following example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Openjpeg(CMakePackage):
|
||||
"""OpenJPEG is an open-source JPEG 2000 codec written in C language"""
|
||||
|
||||
homepage = "https://github.com/uclouvain/openjpeg"
|
||||
url = "https://github.com/uclouvain/openjpeg/archive/v2.3.1.tar.gz"
|
||||
|
||||
version("2.4.0", sha256="8702ba68b442657f11aaeb2b338443ca8d5fb95b0d845757968a7be31ef7f16d")
|
||||
|
||||
variant("codec", default=False, description="Build the CODEC executables")
|
||||
depends_on("libpng", when="+codec")
|
||||
|
||||
def url_for_version(self, version):
|
||||
if version >= Version("2.1.1"):
|
||||
return super(Openjpeg, self).url_for_version(version)
|
||||
url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz"
|
||||
return url_fmt.format(version)
|
||||
|
||||
def cmake_args(self):
|
||||
args = [
|
||||
self.define_from_variant("BUILD_CODEC", "codec"),
|
||||
self.define("BUILD_MJ2", False),
|
||||
self.define("BUILD_THIRDPARTY", False),
|
||||
]
|
||||
return args
|
||||
|
||||
A package encoded with a single class is backward compatible with versions of Spack
|
||||
lower than v0.19, and so are custom repositories containing only recipes of this kind.
|
||||
The downside is that *this format doesn't allow packagers to use more than one build system in a single recipe*.
|
||||
|
||||
To do that, we have to resort to the second way Spack has of writing packages, which involves writing a
|
||||
builder class explicitly. Using the same example as above, this reads:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Openjpeg(CMakePackage):
|
||||
"""OpenJPEG is an open-source JPEG 2000 codec written in C language"""
|
||||
|
||||
homepage = "https://github.com/uclouvain/openjpeg"
|
||||
url = "https://github.com/uclouvain/openjpeg/archive/v2.3.1.tar.gz"
|
||||
|
||||
version("2.4.0", sha256="8702ba68b442657f11aaeb2b338443ca8d5fb95b0d845757968a7be31ef7f16d")
|
||||
|
||||
variant("codec", default=False, description="Build the CODEC executables")
|
||||
depends_on("libpng", when="+codec")
|
||||
|
||||
def url_for_version(self, version):
|
||||
if version >= Version("2.1.1"):
|
||||
return super(Openjpeg, self).url_for_version(version)
|
||||
url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz"
|
||||
return url_fmt.format(version)
|
||||
|
||||
class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder):
|
||||
def cmake_args(self):
|
||||
args = [
|
||||
self.define_from_variant("BUILD_CODEC", "codec"),
|
||||
self.define("BUILD_MJ2", False),
|
||||
self.define("BUILD_THIRDPARTY", False),
|
||||
]
|
||||
return args
|
||||
|
||||
This way of writing packages allows extending the recipe to support multiple build systems,
|
||||
see :ref:`multiple_build_systems` for more details. The downside is that recipes of this kind
|
||||
are only understood by Spack since v0.19+. More information on the internal architecture of
|
||||
Spack can be found at :ref:`package_class_structure`.
|
||||
|
||||
.. note::
|
||||
|
||||
If a builder is implemented in ``package.py``, all build-specific methods must be moved
|
||||
to the builder. This means that if you have a package like
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(CmakePackage):
|
||||
def cmake_args(self):
|
||||
...
|
||||
|
||||
and you add a builder to the ``package.py``, you must move ``cmake_args`` to the builder.
|
||||
|
||||
.. _cmd-spack-create:
|
||||
|
||||
^^^^^^^^^^^^^^^^
|
||||
``spack create``
|
||||
^^^^^^^^^^^^^^^^
|
||||
---------------------
|
||||
Creating new packages
|
||||
---------------------
|
||||
|
||||
The ``spack create`` command creates a directory with the package name and
|
||||
generates a ``package.py`` file with a boilerplate package template. If given
|
||||
a URL pointing to a tarball or other software archive, ``spack create`` is
|
||||
smart enough to determine basic information about the package, including its name
|
||||
and build system. In most cases, ``spack create`` plus a few modifications is
|
||||
all you need to get a package working.
|
||||
|
||||
Here's an example:
|
||||
To help creating a new package Spack provides a command that generates a ``package.py``
|
||||
file in an existing repository, with a boilerplate package template. Here's an example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@ -87,23 +218,6 @@ You do not *have* to download all of the versions up front. You can
|
||||
always choose to download just one tarball initially, and run
|
||||
:ref:`cmd-spack-checksum` later if you need more versions.
|
||||
|
||||
Let's say you download 3 tarballs:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
How many would you like to checksum? (default is 1, q to abort) 3
|
||||
==> Downloading...
|
||||
==> Fetching https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
|
||||
######################################################################## 100.0%
|
||||
==> Fetching https://gmplib.org/download/gmp/gmp-6.1.1.tar.bz2
|
||||
######################################################################## 100.0%
|
||||
==> Fetching https://gmplib.org/download/gmp/gmp-6.1.0.tar.bz2
|
||||
######################################################################## 100.0%
|
||||
==> Checksummed 3 versions of gmp:
|
||||
==> This package looks like it uses the autotools build system
|
||||
==> Created template for gmp package
|
||||
==> Created package file: /Users/Adam/spack/var/spack/repos/builtin/packages/gmp/package.py
|
||||
|
||||
Spack automatically creates a directory in the appropriate repository,
|
||||
generates a boilerplate template for your package, and opens up the new
|
||||
``package.py`` in your favorite ``$EDITOR``:
|
||||
@ -111,6 +225,14 @@ generates a boilerplate template for your package, and opens up the new
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
|
||||
# Copyright 2013-2022 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)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# If you submit this package back to Spack as a pull request,
|
||||
# please first remove this boilerplate and all FIXME comments.
|
||||
#
|
||||
# This is a template package file for Spack. We've put "FIXME"
|
||||
# next to all the things you'll want to change. Once you've handled
|
||||
@ -123,9 +245,8 @@ generates a boilerplate template for your package, and opens up the new
|
||||
# spack edit gmp
|
||||
#
|
||||
# See the Spack documentation for more information on packaging.
|
||||
# If you submit this package back to Spack as a pull request,
|
||||
# please first remove this boilerplate and all FIXME comments.
|
||||
#
|
||||
# ----------------------------------------------------------------------------
|
||||
import spack.build_systems.autotools
|
||||
from spack.package import *
|
||||
|
||||
|
||||
@ -133,19 +254,17 @@ generates a boilerplate template for your package, and opens up the new
|
||||
"""FIXME: Put a proper description of your package here."""
|
||||
|
||||
# FIXME: Add a proper url for your package's homepage here.
|
||||
homepage = "http://www.example.com"
|
||||
url = "https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2"
|
||||
homepage = "https://www.example.com"
|
||||
url = "https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2"
|
||||
|
||||
# FIXME: Add a list of GitHub accounts to
|
||||
# notify when the package is updated.
|
||||
# maintainers = ['github_user1', 'github_user2']
|
||||
# maintainers = ["github_user1", "github_user2"]
|
||||
|
||||
version('6.1.2', '8ddbb26dc3bd4e2302984debba1406a5')
|
||||
version('6.1.1', '4c175f86e11eb32d8bf9872ca3a8e11d')
|
||||
version('6.1.0', '86ee6e54ebfc4a90b643a65e402c4048')
|
||||
version("6.2.1", sha256="eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c")
|
||||
|
||||
# FIXME: Add dependencies if required.
|
||||
# depends_on('foo')
|
||||
# depends_on("foo")
|
||||
|
||||
def configure_args(self):
|
||||
# FIXME: Add arguments other than --prefix
|
||||
@ -154,15 +273,16 @@ generates a boilerplate template for your package, and opens up the new
|
||||
return args
|
||||
|
||||
The tedious stuff (creating the class, checksumming archives) has been
|
||||
done for you. You'll notice that ``spack create`` correctly detected that
|
||||
``gmp`` uses the Autotools build system. It created a new ``Gmp`` package
|
||||
that subclasses the ``AutotoolsPackage`` base class. This base class
|
||||
provides basic installation methods common to all Autotools packages:
|
||||
done for you. Spack correctly detected that ``gmp`` uses the ``autotools``
|
||||
build system, so it created a new ``Gmp`` package that subclasses the
|
||||
``AutotoolsPackage`` base class.
|
||||
|
||||
The default installation procedure for a package subclassing the ``AutotoolsPackage``
|
||||
is to go through the typical process of:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./configure --prefix=/path/to/installation/directory
|
||||
|
||||
make
|
||||
make check
|
||||
make install
|
||||
@ -209,12 +329,14 @@ The rest of the tasks you need to do are as follows:
|
||||
Your new package may require specific flags during ``configure``.
|
||||
These can be added via ``configure_args``. Specifics will differ
|
||||
depending on the package and its build system.
|
||||
:ref:`Implementing the install method <install-method>` is
|
||||
:ref:`installation_process` is
|
||||
covered in detail later.
|
||||
|
||||
Passing a URL to ``spack create`` is a convenient and easy way to get
|
||||
a basic package template, but what if your software is licensed and
|
||||
cannot be downloaded from a URL? You can still create a boilerplate
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Non-downloadable software
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If your software cannot be downloaded from a URL you can still create a boilerplate
|
||||
``package.py`` by telling ``spack create`` what name you want to use:
|
||||
|
||||
.. code-block:: console
|
||||
@ -223,40 +345,23 @@ cannot be downloaded from a URL? You can still create a boilerplate
|
||||
|
||||
This will create a simple ``intel`` package with an ``install()``
|
||||
method that you can craft to install your package.
|
||||
|
||||
What if ``spack create <url>`` guessed the wrong name or build system?
|
||||
For example, if your package uses the Autotools build system but does
|
||||
not come with a ``configure`` script, Spack won't realize it uses
|
||||
Autotools. You can overwrite the old package with ``--force`` and specify
|
||||
a name with ``--name`` or a build system template to use with ``--template``:
|
||||
Likewise, you can force the build system to be used with ``--template`` and,
|
||||
in case it's needed, you can overwrite a package already in the repository
|
||||
with ``--force``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack create --name gmp https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
|
||||
$ spack create --force --template autotools https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
|
||||
|
||||
.. note::
|
||||
|
||||
If you are creating a package that uses the Autotools build system
|
||||
but does not come with a ``configure`` script, you'll need to add an
|
||||
``autoreconf`` method to your package that explains how to generate
|
||||
the ``configure`` script. You may also need the following dependencies:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
depends_on('autoconf', type='build')
|
||||
depends_on('automake', type='build')
|
||||
depends_on('libtool', type='build')
|
||||
depends_on('m4', type='build')
|
||||
|
||||
A complete list of available build system templates can be found by running
|
||||
``spack create --help``.
|
||||
|
||||
.. _cmd-spack-edit:
|
||||
|
||||
^^^^^^^^^^^^^^
|
||||
``spack edit``
|
||||
^^^^^^^^^^^^^^
|
||||
-------------------------
|
||||
Editing existing packages
|
||||
-------------------------
|
||||
|
||||
One of the easiest ways to learn how to write packages is to look at
|
||||
existing ones. You can edit a package file by name with the ``spack
|
||||
@ -266,10 +371,15 @@ edit`` command:
|
||||
|
||||
$ spack edit gmp
|
||||
|
||||
So, if you used ``spack create`` to create a package, then saved and
|
||||
closed the resulting file, you can get back to it with ``spack edit``.
|
||||
The ``gmp`` package actually lives in
|
||||
``$SPACK_ROOT/var/spack/repos/builtin/packages/gmp/package.py``,
|
||||
If you used ``spack create`` to create a package, you can get back to
|
||||
it later with ``spack edit``. For instance, the ``gmp`` package actually
|
||||
lives in:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack location -p gmp
|
||||
${SPACK_ROOT}/var/spack/repos/builtin/packages/gmp/package.py
|
||||
|
||||
but ``spack edit`` provides a much simpler shortcut and saves you the
|
||||
trouble of typing the full path.
|
||||
|
||||
@ -2422,7 +2532,7 @@ Spack provides a mechanism for dependencies to influence the
|
||||
environment of their dependents by overriding the
|
||||
:meth:`setup_dependent_run_environment <spack.package_base.PackageBase.setup_dependent_run_environment>`
|
||||
or the
|
||||
:meth:`setup_dependent_build_environment <spack.package_base.PackageBase.setup_dependent_build_environment>`
|
||||
:meth:`setup_dependent_build_environment <spack.builder.Builder.setup_dependent_build_environment>`
|
||||
methods.
|
||||
The Qt package, for instance, uses this call:
|
||||
|
||||
@ -3280,67 +3390,91 @@ the Python extensions provided by them: once for ``+python`` and once
|
||||
for ``~python``. Other than using a little extra disk space, that
|
||||
solution has no serious problems.
|
||||
|
||||
.. _installation_procedure:
|
||||
.. _installation_process:
|
||||
|
||||
---------------------------------------
|
||||
Implementing the installation procedure
|
||||
---------------------------------------
|
||||
--------------------------------
|
||||
Overriding build system defaults
|
||||
--------------------------------
|
||||
|
||||
The last element of a package is its **installation procedure**. This is
|
||||
where the real work of installation happens, and it's the main part of
|
||||
the package you'll need to customize for each piece of software.
|
||||
.. note::
|
||||
|
||||
Defining an installation procedure means overriding a set of methods or attributes
|
||||
that will be called at some point during the installation of the package.
|
||||
The package base class, usually specialized for a given build system, determines the
|
||||
actual set of entities available for overriding.
|
||||
The classes that are currently provided by Spack are:
|
||||
If you code a single class in ``package.py`` all the functions shown in the table below
|
||||
can be implemented with the same signature on the ``*Package`` instead of the corresponding builder.
|
||||
|
||||
|
||||
Most of the time the default implementation of methods or attributes in build system base classes
|
||||
is what a packager needs, and just a very few entities need to be overwritten. Typically we just
|
||||
need to override methods like ``configure_args``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def configure_args(self):
|
||||
args = ["--enable-cxx"] + self.enable_or_disable("libs")
|
||||
if "libs=static" in self.spec:
|
||||
args.append("--with-pic")
|
||||
return args
|
||||
|
||||
The actual set of entities available for overriding in ``package.py`` depend on
|
||||
the build system. The build systems currently supported by Spack are:
|
||||
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| **Base Class** | **Purpose** |
|
||||
| **API docs** | **Description** |
|
||||
+==========================================================+==================================+
|
||||
| :class:`~spack.package_base.Package` | General base class not |
|
||||
| | specialized for any build system |
|
||||
| :class:`~spack.build_systems.generic` | Generic build system without any |
|
||||
| | base implementation |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.makefile.MakefilePackage` | Specialized class for packages |
|
||||
| | built invoking |
|
||||
| :class:`~spack.build_systems.makefile` | Specialized build system for |
|
||||
| | software built invoking |
|
||||
| | hand-written Makefiles |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.autotools.AutotoolsPackage` | Specialized class for packages |
|
||||
| | built using GNU Autotools |
|
||||
| :class:`~spack.build_systems.autotools` | Specialized build system for |
|
||||
| | software built using |
|
||||
| | GNU Autotools |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.cmake.CMakePackage` | Specialized class for packages |
|
||||
| | built using CMake |
|
||||
| :class:`~spack.build_systems.cmake` | Specialized build system for |
|
||||
| | software built using CMake |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.cuda.CudaPackage` | A helper class for packages that |
|
||||
| | use CUDA |
|
||||
| :class:`~spack.build_systems.maven` | Specialized build system for |
|
||||
| | software built using Maven |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.qmake.QMakePackage` | Specialized class for packages |
|
||||
| | built using QMake |
|
||||
| :class:`~spack.build_systems.meson` | Specialized build system for |
|
||||
| | software built using Meson |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.rocm.ROCmPackage` | A helper class for packages that |
|
||||
| | use ROCm |
|
||||
| :class:`~spack.build_systems.nmake` | Specialized build system for |
|
||||
| | software built using NMake |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.scons.SConsPackage` | Specialized class for packages |
|
||||
| | built using SCons |
|
||||
| :class:`~spack.build_systems.qmake` | Specialized build system for |
|
||||
| | software built using QMake |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.waf.WafPackage` | Specialized class for packages |
|
||||
| | built using Waf |
|
||||
| :class:`~spack.build_systems.scons` | Specialized build system for |
|
||||
| | software built using SCons |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.r.RPackage` | Specialized class for |
|
||||
| :class:`~spack.build_systems.waf` | Specialized build system for |
|
||||
| | software built using Waf |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.r` | Specialized build system for |
|
||||
| | R extensions |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.octave.OctavePackage` | Specialized class for |
|
||||
| :class:`~spack.build_systems.octave` | Specialized build system for |
|
||||
| | Octave packages |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.python.PythonPackage` | Specialized class for |
|
||||
| :class:`~spack.build_systems.python` | Specialized build system for |
|
||||
| | Python extensions |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.perl.PerlPackage` | Specialized class for |
|
||||
| :class:`~spack.build_systems.perl` | Specialized build system for |
|
||||
| | Perl extensions |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.intel.IntelPackage` | Specialized class for licensed |
|
||||
| | Intel software |
|
||||
| :class:`~spack.build_systems.ruby` | Specialized build system for |
|
||||
| | Ruby extensions |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.intel` | Specialized build system for |
|
||||
| | licensed Intel software |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.oneapi` | Specialized build system for |
|
||||
| | Intel onaAPI software |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.aspell_dict` | Specialized build system for |
|
||||
| | Aspell dictionaries |
|
||||
+----------------------------------------------------------+----------------------------------+
|
||||
|
||||
|
||||
@ -3353,52 +3487,17 @@ The classes that are currently provided by Spack are:
|
||||
For example, a Python extension installed with CMake would ``extends('python')`` and
|
||||
subclass from :class:`~spack.build_systems.cmake.CMakePackage`.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
Installation pipeline
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Overriding builder methods
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When a user runs ``spack install``, Spack:
|
||||
|
||||
1. Fetches an archive for the correct version of the software.
|
||||
2. Expands the archive.
|
||||
3. Sets the current working directory to the root directory of the expanded archive.
|
||||
|
||||
Then, depending on the base class of the package under consideration, it will execute
|
||||
a certain number of **phases** that reflect the way a package of that type is usually built.
|
||||
The name and order in which the phases will be executed can be obtained either reading the API
|
||||
docs at :py:mod:`~.spack.build_systems`, or using the ``spack info`` command:
|
||||
|
||||
.. code-block:: console
|
||||
:emphasize-lines: 13,14
|
||||
|
||||
$ spack info m4
|
||||
AutotoolsPackage: m4
|
||||
Homepage: https://www.gnu.org/software/m4/m4.html
|
||||
|
||||
Safe versions:
|
||||
1.4.17 ftp://ftp.gnu.org/gnu/m4/m4-1.4.17.tar.gz
|
||||
|
||||
Variants:
|
||||
Name Default Description
|
||||
|
||||
sigsegv on Build the libsigsegv dependency
|
||||
|
||||
Installation Phases:
|
||||
autoreconf configure build install
|
||||
|
||||
Build Dependencies:
|
||||
libsigsegv
|
||||
|
||||
...
|
||||
|
||||
|
||||
Typically, phases have default implementations that fit most of the common cases:
|
||||
Build-system "phases" have default implementations that fit most of the common cases:
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/build_systems/autotools.py
|
||||
:pyobject: AutotoolsPackage.configure
|
||||
:pyobject: AutotoolsBuilder.configure
|
||||
:linenos:
|
||||
|
||||
It is thus just sufficient for a packager to override a few
|
||||
It is usually sufficient for a packager to override a few
|
||||
build system specific helper methods or attributes to provide, for instance,
|
||||
configure arguments:
|
||||
|
||||
@ -3406,31 +3505,31 @@ configure arguments:
|
||||
:pyobject: M4.configure_args
|
||||
:linenos:
|
||||
|
||||
.. note::
|
||||
Each specific build system has a list of attributes that can be overridden to
|
||||
fine-tune the installation of a package without overriding an entire phase. To
|
||||
have more information on them the place to go is the API docs of the :py:mod:`~.spack.build_systems`
|
||||
module.
|
||||
Each specific build system has a list of attributes and methods that can be overridden to
|
||||
fine-tune the installation of a package without overriding an entire phase. To
|
||||
have more information on them the place to go is the API docs of the :py:mod:`~.spack.build_systems`
|
||||
module.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Overriding an entire phase
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In extreme cases it may be necessary to override an entire phase. Regardless
|
||||
of the build system, the signature is the same. For example, the signature
|
||||
for the install phase is:
|
||||
Sometimes it is necessary to override an entire phase. If the ``package.py`` contains
|
||||
a single class recipe, see :ref:`package_class_structure`, then the signature for a
|
||||
phase is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo(Package):
|
||||
class Openjpeg(CMakePackage):
|
||||
def install(self, spec, prefix):
|
||||
...
|
||||
|
||||
regardless of the build system. The arguments for the phase are:
|
||||
|
||||
``self``
|
||||
For those not used to Python instance methods, this is the
|
||||
package itself. In this case it's an instance of ``Foo``, which
|
||||
extends ``Package``. For API docs on Package objects, see
|
||||
:py:class:`Package <spack.package_base.Package>`.
|
||||
This is the package object, which extends ``CMakePackage``.
|
||||
For API docs on Package objects, see
|
||||
:py:class:`Package <spack.package_base.PackageBase>`.
|
||||
|
||||
``spec``
|
||||
This is the concrete spec object created by Spack from an
|
||||
@ -3445,12 +3544,111 @@ for the install phase is:
|
||||
The arguments ``spec`` and ``prefix`` are passed only for convenience, as they always
|
||||
correspond to ``self.spec`` and ``self.spec.prefix`` respectively.
|
||||
|
||||
As mentioned in :ref:`install-environment`, you will usually not need to refer
|
||||
to dependencies explicitly in your package file, as the compiler wrappers take care of most of
|
||||
the heavy lifting here. There will be times, though, when you need to refer to
|
||||
the install locations of dependencies, or when you need to do something different
|
||||
depending on the version, compiler, dependencies, etc. that your package is
|
||||
built with. These parameters give you access to this type of information.
|
||||
If the ``package.py`` encodes builders explicitly, the signature for a phase changes slightly:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder):
|
||||
def install(self, pkg, spec, prefix):
|
||||
...
|
||||
|
||||
In this case the package is passed as the second argument, and ``self`` is the builder instance.
|
||||
|
||||
.. _multiple_build_systems:
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Multiple build systems
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are cases where a software actively supports two build systems, or changes build systems
|
||||
as it evolves, or needs different build systems on different platforms. Spack allows dealing with
|
||||
these cases natively, if a recipe is written using builders explicitly.
|
||||
|
||||
For instance, software that supports two build systems unconditionally should derive from
|
||||
both ``*Package`` base classes, and declare the possible use of multiple build systems using
|
||||
a directive:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ArpackNg(CMakePackage, AutotoolsPackage):
|
||||
|
||||
build_system("cmake", "autotools", default="cmake")
|
||||
|
||||
In this case the software can be built with both ``autotools`` and ``cmake``. Since the package
|
||||
supports multiple build systems, it is necessary to declare which one is the default. The ``package.py``
|
||||
will likely contain some overriding of default builder methods:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder):
|
||||
def cmake_args(self):
|
||||
pass
|
||||
|
||||
class Autotoolsbuilder(spack.build_systems.autotools.AutotoolsBuilder):
|
||||
def configure_args(self):
|
||||
pass
|
||||
|
||||
In more complex cases it might happen that the build system changes according to certain conditions,
|
||||
for instance across versions. That can be expressed with conditional variant values:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ArpackNg(CMakePackage, AutotoolsPackage):
|
||||
|
||||
build_system(
|
||||
conditional("cmake", when="@0.64:"),
|
||||
conditional("autotools", when="@:0.63"),
|
||||
default="cmake",
|
||||
)
|
||||
|
||||
In the example the directive impose a change from ``Autotools`` to ``CMake`` going
|
||||
from ``v0.63`` to ``v0.64``.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Mixin base classes
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Besides build systems, there are other cases where common metadata and behavior can be extracted
|
||||
and reused by many packages. For instance, packages that depend on ``Cuda`` or ``Rocm``, share
|
||||
common dependencies and constraints. To factor these attributes into a single place, Spack provides
|
||||
a few mixin classes in the ``spack.build_systems`` module:
|
||||
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
| **API docs** | **Description** |
|
||||
+===============================================================+==================================+
|
||||
| :class:`~spack.build_systems.cuda.CudaPackage` | A helper class for packages that |
|
||||
| | use CUDA |
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.rocm.ROCmPackage` | A helper class for packages that |
|
||||
| | use ROCm |
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.gnu.GNUMirrorPackage` | A helper class for GNU packages |
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.python.PythonExtension` | A helper class for Python |
|
||||
| | extensions |
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.sourceforge.SourceforgePackage` | A helper class for packages |
|
||||
| | from sourceforge.org |
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.sourceware.SourcewarePackage` | A helper class for packages |
|
||||
| | from sourceware.org |
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
| :class:`~spack.build_systems.xorg.XorgPackage` | A helper class for x.org |
|
||||
| | packages |
|
||||
+---------------------------------------------------------------+----------------------------------+
|
||||
|
||||
These classes should be used by adding them to the inheritance tree of the package that needs them,
|
||||
for instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Cp2k(MakefilePackage, CudaPackage):
|
||||
"""CP2K is a quantum chemistry and solid state physics software package
|
||||
that can perform atomistic simulations of solid state, liquid, molecular,
|
||||
periodic, material, crystal, and biological systems
|
||||
"""
|
||||
|
||||
In the example above ``Cp2k`` inherits all the conflicts and variants that ``CudaPackage`` defines.
|
||||
|
||||
.. _install-environment:
|
||||
|
||||
@ -4208,16 +4406,9 @@ In addition to invoking the right compiler, the compiler wrappers add
|
||||
flags to the compile line so that dependencies can be easily found.
|
||||
These flags are added for each dependency, if they exist:
|
||||
|
||||
Compile-time library search paths
|
||||
* ``-L$dep_prefix/lib``
|
||||
* ``-L$dep_prefix/lib64``
|
||||
|
||||
Runtime library search paths (RPATHs)
|
||||
* ``$rpath_flag$dep_prefix/lib``
|
||||
* ``$rpath_flag$dep_prefix/lib64``
|
||||
|
||||
Include search paths
|
||||
* ``-I$dep_prefix/include``
|
||||
* Compile-time library search paths: ``-L$dep_prefix/lib``, ``-L$dep_prefix/lib64``
|
||||
* Runtime library search paths (RPATHs): ``$rpath_flag$dep_prefix/lib``, ``$rpath_flag$dep_prefix/lib64``
|
||||
* Include search paths: ``-I$dep_prefix/include``
|
||||
|
||||
An example of this would be the ``libdwarf`` build, which has one
|
||||
dependency: ``libelf``. Every call to ``cc`` in the ``libdwarf``
|
||||
@ -5062,6 +5253,16 @@ where each argument has the following meaning:
|
||||
will run.
|
||||
|
||||
The default of ``None`` corresponds to the current directory (``'.'``).
|
||||
Each call starts with the working directory set to the spec's test stage
|
||||
directory (i.e., ``self.test_suite.test_dir_for_spec(self.spec)``).
|
||||
|
||||
.. warning::
|
||||
|
||||
Use of the package spec's installation directory for building and running
|
||||
tests is **strongly** discouraged. Doing so has caused permission errors
|
||||
for shared spack instances *and* for facilities that install the software
|
||||
in read-only file systems or directories.
|
||||
|
||||
|
||||
"""""""""""""""""""""""""""""""""""""""""
|
||||
Accessing package- and test-related files
|
||||
@ -5069,10 +5270,10 @@ Accessing package- and test-related files
|
||||
|
||||
You may need to access files from one or more locations when writing
|
||||
stand-alone tests. This can happen if the software's repository does not
|
||||
include test source files or includes files but no way to build the
|
||||
executables using the installed headers and libraries. In these
|
||||
cases, you may need to reference the files relative to one or more
|
||||
root directory. The properties containing package- and test-related
|
||||
include test source files or includes files but has no way to build the
|
||||
executables using the installed headers and libraries. In these cases,
|
||||
you may need to reference the files relative to one or more root
|
||||
directory. The properties containing package- (or spec-) and test-related
|
||||
directory paths are provided in the table below.
|
||||
|
||||
.. list-table:: Directory-to-property mapping
|
||||
@ -5081,19 +5282,22 @@ directory paths are provided in the table below.
|
||||
* - Root Directory
|
||||
- Package Property
|
||||
- Example(s)
|
||||
* - Package Installation Files
|
||||
* - Package (Spec) Installation
|
||||
- ``self.prefix``
|
||||
- ``self.prefix.include``, ``self.prefix.lib``
|
||||
* - Package Dependency's Files
|
||||
* - Dependency Installation
|
||||
- ``self.spec['<dependency-package>'].prefix``
|
||||
- ``self.spec['trilinos'].prefix.include``
|
||||
* - Test Suite Stage Files
|
||||
* - Test Suite Stage
|
||||
- ``self.test_suite.stage``
|
||||
- ``join_path(self.test_suite.stage, 'results.txt')``
|
||||
* - Staged Cached Build-time Files
|
||||
* - Spec's Test Stage
|
||||
- ``self.test_suite.test_dir_for_spec``
|
||||
- ``self.test_suite.test_dir_for_spec(self.spec)``
|
||||
* - Current Spec's Build-time Files
|
||||
- ``self.test_suite.current_test_cache_dir``
|
||||
- ``join_path(self.test_suite.current_test_cache_dir, 'examples', 'foo.c')``
|
||||
* - Staged Custom Package Files
|
||||
* - Current Spec's Custom Test Files
|
||||
- ``self.test_suite.current_test_data_dir``
|
||||
- ``join_path(self.test_suite.current_test_data_dir, 'hello.f90')``
|
||||
|
||||
@ -6099,3 +6303,82 @@ might write:
|
||||
DWARF_PREFIX = $(spack location --install-dir libdwarf)
|
||||
CXXFLAGS += -I$DWARF_PREFIX/include
|
||||
CXXFLAGS += -L$DWARF_PREFIX/lib
|
||||
|
||||
|
||||
.. _package_class_structure:
|
||||
|
||||
--------------------------
|
||||
Package class architecture
|
||||
--------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section aims to provide a high-level knowledge of how the package class architecture evolved
|
||||
in Spack, and provides some insights on the current design.
|
||||
|
||||
Packages in Spack were originally designed to support only a single build system. The overall
|
||||
class structure for a package looked like:
|
||||
|
||||
.. image:: images/original_package_architecture.png
|
||||
:scale: 60 %
|
||||
:align: center
|
||||
|
||||
In this architecture the base class ``AutotoolsPackage`` was responsible for both the metadata
|
||||
related to the ``autotools`` build system (e.g. dependencies or variants common to all packages
|
||||
using it), and for encoding the default installation procedure.
|
||||
|
||||
In reality, a non-negligible number of packages are either changing their build system during the evolution of the
|
||||
project, or using different build systems for different platforms. An architecture based on a single class
|
||||
requires hacks or other workarounds to deal with these cases.
|
||||
|
||||
To support a model more adherent to reality, Spack v0.19 changed its internal design by extracting
|
||||
the attributes and methods related to building a software into a separate hierarchy:
|
||||
|
||||
.. image:: images/builder_package_architecture.png
|
||||
:scale: 60 %
|
||||
:align: center
|
||||
|
||||
In this new format each ``package.py`` contains one ``*Package`` class that gathers all the metadata,
|
||||
and one or more ``*Builder`` classes that encode the installation procedure. A specific builder object
|
||||
is created just before the software is built, so at a time where Spack knows which build system needs
|
||||
to be used for the current installation, and receives a ``package`` object during initialization.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
``build_system`` variant
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To allow imposing conditions based on the build system, each package must a have ``build_system`` variant,
|
||||
which is usually inherited from base classes. This variant allows for writing metadata that is conditional
|
||||
on the build system:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with when("build_system=cmake"):
|
||||
depends_on("cmake", type="build")
|
||||
|
||||
and also for selecting a specific build system from a spec literal, like in the following command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack install arpack-ng build_system=autotools
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Compatibility with single-class format
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Internally, Spack always uses builders to perform operations related to the installation of a specific software.
|
||||
The builders are created in the ``spack.builder.create`` function
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/builder.py
|
||||
:pyobject: create
|
||||
|
||||
To achieve backward compatibility with the single-class format Spack creates in this function a special
|
||||
"adapter builder", if no custom builder is detected in the recipe:
|
||||
|
||||
.. image:: images/adapter.png
|
||||
:scale: 60 %
|
||||
:align: center
|
||||
|
||||
Overall the role of the adapter is to route access to attributes of methods first through the ``*Package``
|
||||
hierarchy, and then back to the base class builder. This is schematically shown in the diagram above, where
|
||||
the adapter role is to "emulate" a method resolution order like the one represented by the red arrows.
|
||||
|
@ -20,6 +20,8 @@ spack:
|
||||
- py-docutils@:0.16
|
||||
- py-sphinx-design
|
||||
- py-sphinx-rtd-theme
|
||||
- py-pygments@:2.12
|
||||
|
||||
# VCS
|
||||
- git
|
||||
- mercurial
|
||||
|
@ -1,5 +1,5 @@
|
||||
Name, Supported Versions, Notes, Requirement Reason
|
||||
Python, 2.7/3.6-3.10, , Interpreter for Spack
|
||||
Python, 2.7/3.6-3.11, , Interpreter for Spack
|
||||
C/C++ Compilers, , , Building software
|
||||
make, , , Build software
|
||||
patch, , , Build software
|
||||
@ -11,6 +11,7 @@ bzip2, , , Compress/Decompress archives
|
||||
xz, , , Compress/Decompress archives
|
||||
zstd, , Optional, Compress/Decompress archives
|
||||
file, , , Create/Use Buildcaches
|
||||
lsb-release, , , Linux: identify operating system version
|
||||
gnupg2, , , Sign/Verify Buildcaches
|
||||
git, , , Manage Software Repositories
|
||||
svn, , Optional, Manage Software Repositories
|
||||
|
|
8
lib/spack/env/cc
vendored
8
lib/spack/env/cc
vendored
@ -241,28 +241,28 @@ case "$command" in
|
||||
mode=cpp
|
||||
debug_flags="-g"
|
||||
;;
|
||||
cc|c89|c99|gcc|clang|armclang|icc|icx|pgcc|nvc|xlc|xlc_r|fcc|amdclang|cl.exe)
|
||||
cc|c89|c99|gcc|clang|armclang|icc|icx|pgcc|nvc|xlc|xlc_r|fcc|amdclang|cl.exe|craycc)
|
||||
command="$SPACK_CC"
|
||||
language="C"
|
||||
comp="CC"
|
||||
lang_flags=C
|
||||
debug_flags="-g"
|
||||
;;
|
||||
c++|CC|g++|clang++|armclang++|icpc|icpx|dpcpp|pgc++|nvc++|xlc++|xlc++_r|FCC|amdclang++)
|
||||
c++|CC|g++|clang++|armclang++|icpc|icpx|dpcpp|pgc++|nvc++|xlc++|xlc++_r|FCC|amdclang++|crayCC)
|
||||
command="$SPACK_CXX"
|
||||
language="C++"
|
||||
comp="CXX"
|
||||
lang_flags=CXX
|
||||
debug_flags="-g"
|
||||
;;
|
||||
ftn|f90|fc|f95|gfortran|flang|armflang|ifort|ifx|pgfortran|nvfortran|xlf90|xlf90_r|nagfor|frt|amdflang)
|
||||
ftn|f90|fc|f95|gfortran|flang|armflang|ifort|ifx|pgfortran|nvfortran|xlf90|xlf90_r|nagfor|frt|amdflang|crayftn)
|
||||
command="$SPACK_FC"
|
||||
language="Fortran 90"
|
||||
comp="FC"
|
||||
lang_flags=F
|
||||
debug_flags="-g"
|
||||
;;
|
||||
f77|xlf|xlf_r|pgf77|amdflang)
|
||||
f77|xlf|xlf_r|pgf77)
|
||||
command="$SPACK_F77"
|
||||
language="Fortran 77"
|
||||
comp="F77"
|
||||
|
1
lib/spack/env/cce/case-insensitive/crayCC
vendored
Symbolic link
1
lib/spack/env/cce/case-insensitive/crayCC
vendored
Symbolic link
@ -0,0 +1 @@
|
||||
../../cc
|
1
lib/spack/env/cce/craycc
vendored
Symbolic link
1
lib/spack/env/cce/craycc
vendored
Symbolic link
@ -0,0 +1 @@
|
||||
../cc
|
1
lib/spack/env/cce/crayftn
vendored
Symbolic link
1
lib/spack/env/cce/crayftn
vendored
Symbolic link
@ -0,0 +1 @@
|
||||
../cc
|
2
lib/spack/external/__init__.py
vendored
2
lib/spack/external/__init__.py
vendored
@ -18,7 +18,7 @@
|
||||
|
||||
* Homepage: https://pypi.python.org/pypi/archspec
|
||||
* Usage: Labeling, comparison and detection of microarchitectures
|
||||
* Version: 0.1.4 (commit e2cfdc266174488dee78b8c9058e36d60dc1b548)
|
||||
* Version: 0.2.0 (commit 77640e572725ad97f18e63a04857155752ace045)
|
||||
|
||||
argparse
|
||||
--------
|
||||
|
34
lib/spack/external/archspec/cpu/detect.py
vendored
34
lib/spack/external/archspec/cpu/detect.py
vendored
@ -132,9 +132,15 @@ def sysctl(*args):
|
||||
"model name": sysctl("-n", "machdep.cpu.brand_string"),
|
||||
}
|
||||
else:
|
||||
model = (
|
||||
"m1" if "Apple" in sysctl("-n", "machdep.cpu.brand_string") else "unknown"
|
||||
)
|
||||
model = "unknown"
|
||||
model_str = sysctl("-n", "machdep.cpu.brand_string").lower()
|
||||
if "m2" in model_str:
|
||||
model = "m2"
|
||||
elif "m1" in model_str:
|
||||
model = "m1"
|
||||
elif "apple" in model_str:
|
||||
model = "m1"
|
||||
|
||||
info = {
|
||||
"vendor_id": "Apple",
|
||||
"flags": [],
|
||||
@ -322,14 +328,26 @@ def compatibility_check_for_aarch64(info, target):
|
||||
features = set(info.get("Features", "").split())
|
||||
vendor = info.get("CPU implementer", "generic")
|
||||
|
||||
# At the moment it's not clear how to detect compatibility with
|
||||
# a specific version of the architecture
|
||||
if target.vendor == "generic" and target.name != "aarch64":
|
||||
return False
|
||||
|
||||
arch_root = TARGETS[basename]
|
||||
return (
|
||||
(target == arch_root or arch_root in target.ancestors)
|
||||
and target.vendor in (vendor, "generic")
|
||||
# On macOS it seems impossible to get all the CPU features with syctl info
|
||||
and (target.features.issubset(features) or platform.system() == "Darwin")
|
||||
arch_root_and_vendor = arch_root == target.family and target.vendor in (
|
||||
vendor,
|
||||
"generic",
|
||||
)
|
||||
|
||||
# On macOS it seems impossible to get all the CPU features
|
||||
# with syctl info, but for ARM we can get the exact model
|
||||
if platform.system() == "Darwin":
|
||||
model_key = info.get("model", basename)
|
||||
model = TARGETS[model_key]
|
||||
return arch_root_and_vendor and (target == model or target in model.ancestors)
|
||||
|
||||
return arch_root_and_vendor and target.features.issubset(features)
|
||||
|
||||
|
||||
@compatibility_check(architecture_family="riscv64")
|
||||
def compatibility_check_for_riscv64(info, target):
|
||||
|
@ -85,7 +85,7 @@
|
||||
"intel": [
|
||||
{
|
||||
"versions": ":",
|
||||
"name": "x86-64",
|
||||
"name": "pentium4",
|
||||
"flags": "-march={name} -mtune=generic"
|
||||
}
|
||||
],
|
||||
@ -2093,8 +2093,163 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"thunderx2": {
|
||||
"armv8.1a": {
|
||||
"from": ["aarch64"],
|
||||
"vendor": "generic",
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "5:",
|
||||
"flags": "-march=armv8.1-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.1-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"apple-clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.1-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"arm": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.1-a -mtune=generic"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"armv8.2a": {
|
||||
"from": ["armv8.1a"],
|
||||
"vendor": "generic",
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "6:",
|
||||
"flags": "-march=armv8.2-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.2-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"apple-clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.2-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"arm": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.2-a -mtune=generic"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"armv8.3a": {
|
||||
"from": ["armv8.2a"],
|
||||
"vendor": "generic",
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "6:",
|
||||
"flags": "-march=armv8.3-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": "6:",
|
||||
"flags": "-march=armv8.3-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"apple-clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.3-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"arm": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.3-a -mtune=generic"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"armv8.4a": {
|
||||
"from": ["armv8.3a"],
|
||||
"vendor": "generic",
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "8:",
|
||||
"flags": "-march=armv8.4-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": "8:",
|
||||
"flags": "-march=armv8.4-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"apple-clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.4-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"arm": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.4-a -mtune=generic"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"armv8.5a": {
|
||||
"from": ["armv8.4a"],
|
||||
"vendor": "generic",
|
||||
"features": [],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "9:",
|
||||
"flags": "-march=armv8.5-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"clang": [
|
||||
{
|
||||
"versions": "11:",
|
||||
"flags": "-march=armv8.5-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"apple-clang": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.5-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"arm": [
|
||||
{
|
||||
"versions": ":",
|
||||
"flags": "-march=armv8.5-a -mtune=generic"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"thunderx2": {
|
||||
"from": ["armv8.1a"],
|
||||
"vendor": "Cavium",
|
||||
"features": [
|
||||
"fp",
|
||||
@ -2141,7 +2296,7 @@
|
||||
}
|
||||
},
|
||||
"a64fx": {
|
||||
"from": ["aarch64"],
|
||||
"from": ["armv8.2a"],
|
||||
"vendor": "Fujitsu",
|
||||
"features": [
|
||||
"fp",
|
||||
@ -2209,7 +2364,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"graviton": {
|
||||
"cortex_a72": {
|
||||
"from": ["aarch64"],
|
||||
"vendor": "ARM",
|
||||
"features": [
|
||||
@ -2235,19 +2390,19 @@
|
||||
},
|
||||
{
|
||||
"versions": "6:",
|
||||
"flags" : "-march=armv8-a+crc+crypto -mtune=cortex-a72"
|
||||
"flags" : "-mcpu=cortex-a72"
|
||||
}
|
||||
],
|
||||
"clang" : [
|
||||
{
|
||||
"versions": "3.9:",
|
||||
"flags" : "-march=armv8-a+crc+crypto"
|
||||
"flags" : "-mcpu=cortex-a72"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"graviton2": {
|
||||
"from": ["graviton"],
|
||||
"neoverse_n1": {
|
||||
"from": ["cortex_a72", "armv8.2a"],
|
||||
"vendor": "ARM",
|
||||
"features": [
|
||||
"fp",
|
||||
@ -2296,7 +2451,7 @@
|
||||
},
|
||||
{
|
||||
"versions": "9.0:",
|
||||
"flags" : "-march=armv8.2-a+fp16+rcpc+dotprod+crypto -mtune=neoverse-n1"
|
||||
"flags" : "-mcpu=neoverse-n1"
|
||||
}
|
||||
],
|
||||
"clang" : [
|
||||
@ -2307,6 +2462,10 @@
|
||||
{
|
||||
"versions": "5:",
|
||||
"flags" : "-march=armv8.2-a+fp16+rcpc+dotprod+crypto"
|
||||
},
|
||||
{
|
||||
"versions": "10:",
|
||||
"flags" : "-mcpu=neoverse-n1"
|
||||
}
|
||||
],
|
||||
"arm" : [
|
||||
@ -2317,11 +2476,11 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"graviton3": {
|
||||
"from": ["graviton2"],
|
||||
"neoverse_v1": {
|
||||
"from": ["neoverse_n1", "armv8.4a"],
|
||||
"vendor": "ARM",
|
||||
"features": [
|
||||
"fp",
|
||||
"fp",
|
||||
"asimd",
|
||||
"evtstrm",
|
||||
"aes",
|
||||
@ -2384,11 +2543,11 @@
|
||||
},
|
||||
{
|
||||
"versions": "9.0:9.9",
|
||||
"flags" : "-march=armv8.4-a+crypto+rcpc+sha3+sm4+sve+rng+nodotprod -mtune=neoverse-v1"
|
||||
"flags" : "-mcpu=neoverse-v1"
|
||||
},
|
||||
{
|
||||
"versions": "10.0:",
|
||||
"flags" : "-march=armv8.4-a+crypto+rcpc+sha3+sm4+sve+rng+ssbs+i8mm+bf16+nodotprod -mtune=neoverse-v1"
|
||||
"flags" : "-mcpu=neoverse-v1"
|
||||
}
|
||||
|
||||
],
|
||||
@ -2404,6 +2563,10 @@
|
||||
{
|
||||
"versions": "11:",
|
||||
"flags" : "-march=armv8.4-a+sve+ssbs+fp16+bf16+crypto+i8mm+rng"
|
||||
},
|
||||
{
|
||||
"versions": "12:",
|
||||
"flags" : "-mcpu=neoverse-v1"
|
||||
}
|
||||
],
|
||||
"arm" : [
|
||||
@ -2419,7 +2582,7 @@
|
||||
}
|
||||
},
|
||||
"m1": {
|
||||
"from": ["aarch64"],
|
||||
"from": ["armv8.4a"],
|
||||
"vendor": "Apple",
|
||||
"features": [
|
||||
"fp",
|
||||
@ -2484,6 +2647,76 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"m2": {
|
||||
"from": ["m1", "armv8.5a"],
|
||||
"vendor": "Apple",
|
||||
"features": [
|
||||
"fp",
|
||||
"asimd",
|
||||
"evtstrm",
|
||||
"aes",
|
||||
"pmull",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"crc32",
|
||||
"atomics",
|
||||
"fphp",
|
||||
"asimdhp",
|
||||
"cpuid",
|
||||
"asimdrdm",
|
||||
"jscvt",
|
||||
"fcma",
|
||||
"lrcpc",
|
||||
"dcpop",
|
||||
"sha3",
|
||||
"asimddp",
|
||||
"sha512",
|
||||
"asimdfhm",
|
||||
"dit",
|
||||
"uscat",
|
||||
"ilrcpc",
|
||||
"flagm",
|
||||
"ssbs",
|
||||
"sb",
|
||||
"paca",
|
||||
"pacg",
|
||||
"dcpodp",
|
||||
"flagm2",
|
||||
"frint",
|
||||
"ecv",
|
||||
"bf16",
|
||||
"i8mm",
|
||||
"bti"
|
||||
],
|
||||
"compilers": {
|
||||
"gcc": [
|
||||
{
|
||||
"versions": "8.0:",
|
||||
"flags" : "-march=armv8.5-a -mtune=generic"
|
||||
}
|
||||
],
|
||||
"clang" : [
|
||||
{
|
||||
"versions": "9.0:12.0",
|
||||
"flags" : "-march=armv8.5-a"
|
||||
},
|
||||
{
|
||||
"versions": "13.0:",
|
||||
"flags" : "-mcpu=apple-m1"
|
||||
}
|
||||
],
|
||||
"apple-clang": [
|
||||
{
|
||||
"versions": "11.0:12.5",
|
||||
"flags" : "-march=armv8.5-a"
|
||||
},
|
||||
{
|
||||
"versions": "13.0:",
|
||||
"flags" : "-mcpu=vortex"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arm": {
|
||||
"from": [],
|
||||
"vendor": "generic",
|
||||
|
@ -505,8 +505,15 @@ def group_ids(uid=None):
|
||||
|
||||
if uid is None:
|
||||
uid = getuid()
|
||||
user = pwd.getpwuid(uid).pw_name
|
||||
return [g.gr_gid for g in grp.getgrall() if user in g.gr_mem]
|
||||
|
||||
pwd_entry = pwd.getpwuid(uid)
|
||||
user = pwd_entry.pw_name
|
||||
|
||||
# user's primary group id may not be listed in grp (i.e. /etc/group)
|
||||
# you have to check pwd for that, so start the list with that
|
||||
gids = [pwd_entry.pw_gid]
|
||||
|
||||
return sorted(set(gids + [g.gr_gid for g in grp.getgrall() if user in g.gr_mem]))
|
||||
|
||||
|
||||
@system_path_filter(arg_slice=slice(1))
|
||||
@ -1083,7 +1090,11 @@ def temp_cwd():
|
||||
with working_dir(tmp_dir):
|
||||
yield tmp_dir
|
||||
finally:
|
||||
shutil.rmtree(tmp_dir)
|
||||
kwargs = {}
|
||||
if is_windows:
|
||||
kwargs["ignore_errors"] = False
|
||||
kwargs["onerror"] = readonly_file_handler(ignore_errors=True)
|
||||
shutil.rmtree(tmp_dir, **kwargs)
|
||||
|
||||
|
||||
@contextmanager
|
||||
@ -2095,7 +2106,7 @@ def find_system_libraries(libraries, shared=True):
|
||||
return libraries_found
|
||||
|
||||
|
||||
def find_libraries(libraries, root, shared=True, recursive=False):
|
||||
def find_libraries(libraries, root, shared=True, recursive=False, runtime=True):
|
||||
"""Returns an iterable of full paths to libraries found in a root dir.
|
||||
|
||||
Accepts any glob characters accepted by fnmatch:
|
||||
@ -2116,6 +2127,10 @@ def find_libraries(libraries, root, shared=True, recursive=False):
|
||||
otherwise for static. Defaults to True.
|
||||
recursive (bool): if False search only root folder,
|
||||
if True descends top-down from the root. Defaults to False.
|
||||
runtime (bool): Windows only option, no-op elsewhere. If true,
|
||||
search for runtime shared libs (.DLL), otherwise, search
|
||||
for .Lib files. If shared is false, this has no meaning.
|
||||
Defaults to True.
|
||||
|
||||
Returns:
|
||||
LibraryList: The libraries that have been found
|
||||
@ -2130,7 +2145,9 @@ def find_libraries(libraries, root, shared=True, recursive=False):
|
||||
|
||||
if is_windows:
|
||||
static_ext = "lib"
|
||||
shared_ext = "dll"
|
||||
# For linking (runtime=False) you need the .lib files regardless of
|
||||
# whether you are doing a shared or static link
|
||||
shared_ext = "dll" if runtime else "lib"
|
||||
else:
|
||||
# Used on both Linux and macOS
|
||||
static_ext = "a"
|
||||
@ -2174,13 +2191,13 @@ def find_libraries(libraries, root, shared=True, recursive=False):
|
||||
return LibraryList(found_libs)
|
||||
|
||||
|
||||
def find_all_shared_libraries(root, recursive=False):
|
||||
def find_all_shared_libraries(root, recursive=False, runtime=True):
|
||||
"""Convenience function that returns the list of all shared libraries found
|
||||
in the directory passed as argument.
|
||||
|
||||
See documentation for `llnl.util.filesystem.find_libraries` for more information
|
||||
"""
|
||||
return find_libraries("*", root=root, shared=True, recursive=recursive)
|
||||
return find_libraries("*", root=root, shared=True, recursive=recursive, runtime=runtime)
|
||||
|
||||
|
||||
def find_all_static_libraries(root, recursive=False):
|
||||
@ -2226,48 +2243,36 @@ def __init__(self, package, link_install_prefix=True):
|
||||
self.pkg = package
|
||||
self._addl_rpaths = set()
|
||||
self.link_install_prefix = link_install_prefix
|
||||
self._internal_links = set()
|
||||
self._additional_library_dependents = set()
|
||||
|
||||
@property
|
||||
def link_dest(self):
|
||||
def library_dependents(self):
|
||||
"""
|
||||
Set of directories where package binaries/libraries are located.
|
||||
"""
|
||||
if hasattr(self.pkg, "libs") and self.pkg.libs:
|
||||
pkg_libs = set(self.pkg.libs.directories)
|
||||
else:
|
||||
pkg_libs = set((self.pkg.prefix.lib, self.pkg.prefix.lib64))
|
||||
return set([self.pkg.prefix.bin]) | self._additional_library_dependents
|
||||
|
||||
return pkg_libs | set([self.pkg.prefix.bin]) | self.internal_links
|
||||
|
||||
@property
|
||||
def internal_links(self):
|
||||
def add_library_dependent(self, *dest):
|
||||
"""
|
||||
linking that would need to be established within the package itself. Useful for links
|
||||
against extension modules/build time executables/internal linkage
|
||||
"""
|
||||
return self._internal_links
|
||||
Add paths to directories or libraries/binaries to set of
|
||||
common paths that need to link against other libraries
|
||||
|
||||
def add_internal_links(self, *dest):
|
||||
"""
|
||||
Incorporate additional paths into the rpath (sym)linking scheme.
|
||||
|
||||
Paths provided to this method are linked against by a package's libraries
|
||||
and libraries found at these paths are linked against a package's binaries.
|
||||
(i.e. /site-packages -> /bin and /bin -> /site-packages)
|
||||
|
||||
Specified paths should be outside of a package's lib, lib64, and bin
|
||||
Specified paths should fall outside of a package's common
|
||||
link paths, i.e. the bin
|
||||
directories.
|
||||
"""
|
||||
self._internal_links = self._internal_links | set(*dest)
|
||||
for pth in dest:
|
||||
if os.path.isfile(pth):
|
||||
self._additional_library_dependents.add(os.path.dirname)
|
||||
else:
|
||||
self._additional_library_dependents.add(pth)
|
||||
|
||||
@property
|
||||
def link_targets(self):
|
||||
def rpaths(self):
|
||||
"""
|
||||
Set of libraries this package needs to link against during runtime
|
||||
These packages will each be symlinked into the packages lib and binary dir
|
||||
"""
|
||||
|
||||
dependent_libs = []
|
||||
for path in self.pkg.rpath:
|
||||
dependent_libs.extend(list(find_all_shared_libraries(path, recursive=True)))
|
||||
@ -2275,18 +2280,43 @@ def link_targets(self):
|
||||
dependent_libs.extend(list(find_all_shared_libraries(extra_path, recursive=True)))
|
||||
return set(dependent_libs)
|
||||
|
||||
def include_additional_link_paths(self, *paths):
|
||||
def add_rpath(self, *paths):
|
||||
"""
|
||||
Add libraries found at the root of provided paths to runtime linking
|
||||
|
||||
These are libraries found outside of the typical scope of rpath linking
|
||||
that require manual inclusion in a runtime linking scheme
|
||||
that require manual inclusion in a runtime linking scheme.
|
||||
These links are unidirectional, and are only
|
||||
intended to bring outside dependencies into this package
|
||||
|
||||
Args:
|
||||
*paths (str): arbitrary number of paths to be added to runtime linking
|
||||
"""
|
||||
self._addl_rpaths = self._addl_rpaths | set(paths)
|
||||
|
||||
def _link(self, path, dest):
|
||||
file_name = os.path.basename(path)
|
||||
dest_file = os.path.join(dest, file_name)
|
||||
if os.path.exists(dest):
|
||||
try:
|
||||
symlink(path, dest_file)
|
||||
# For py2 compatibility, we have to catch the specific Windows error code
|
||||
# associate with trying to create a file that already exists (winerror 183)
|
||||
except OSError as e:
|
||||
if e.winerror == 183:
|
||||
# We have either already symlinked or we are encoutering a naming clash
|
||||
# either way, we don't want to overwrite existing libraries
|
||||
already_linked = islink(dest_file)
|
||||
tty.debug(
|
||||
"Linking library %s to %s failed, " % (path, dest_file) + "already linked."
|
||||
if already_linked
|
||||
else "library with name %s already exists at location %s."
|
||||
% (file_name, dest)
|
||||
)
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
|
||||
def establish_link(self):
|
||||
"""
|
||||
(sym)link packages to runtime dependencies based on RPath configuration for
|
||||
@ -2298,29 +2328,8 @@ def establish_link(self):
|
||||
|
||||
# for each binary install dir in self.pkg (i.e. pkg.prefix.bin, pkg.prefix.lib)
|
||||
# install a symlink to each dependent library
|
||||
for library, lib_dir in itertools.product(self.link_targets, self.link_dest):
|
||||
if not path_contains_subdirectory(library, lib_dir):
|
||||
file_name = os.path.basename(library)
|
||||
dest_file = os.path.join(lib_dir, file_name)
|
||||
if os.path.exists(lib_dir):
|
||||
try:
|
||||
symlink(library, dest_file)
|
||||
# For py2 compatibility, we have to catch the specific Windows error code
|
||||
# associate with trying to create a file that already exists (winerror 183)
|
||||
except OSError as e:
|
||||
if e.winerror == 183:
|
||||
# We have either already symlinked or we are encoutering a naming clash
|
||||
# either way, we don't want to overwrite existing libraries
|
||||
already_linked = islink(dest_file)
|
||||
tty.debug(
|
||||
"Linking library %s to %s failed, " % (library, dest_file)
|
||||
+ "already linked."
|
||||
if already_linked
|
||||
else "library with name %s already exists." % file_name
|
||||
)
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
for library, lib_dir in itertools.product(self.rpaths, self.library_dependents):
|
||||
self._link(library, lib_dir)
|
||||
|
||||
|
||||
@system_path_filter
|
||||
|
@ -749,6 +749,26 @@ def _n_xxx_ago(x):
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def pretty_seconds(seconds):
|
||||
"""Seconds to string with appropriate units
|
||||
|
||||
Arguments:
|
||||
seconds (float): Number of seconds
|
||||
|
||||
Returns:
|
||||
str: Time string with units
|
||||
"""
|
||||
if seconds >= 1:
|
||||
value, unit = seconds, "s"
|
||||
elif seconds >= 1e-3:
|
||||
value, unit = seconds * 1e3, "ms"
|
||||
elif seconds >= 1e-6:
|
||||
value, unit = seconds * 1e6, "us"
|
||||
else:
|
||||
value, unit = seconds * 1e9, "ns"
|
||||
return "%.3f%s" % (value, unit)
|
||||
|
||||
|
||||
class RequiredAttributeError(ValueError):
|
||||
def __init__(self, message):
|
||||
super(RequiredAttributeError, self).__init__(message)
|
||||
@ -1002,6 +1022,14 @@ def stable_partition(
|
||||
return true_items, false_items
|
||||
|
||||
|
||||
def ensure_last(lst, *elements):
|
||||
"""Performs a stable partition of lst, ensuring that ``elements``
|
||||
occur at the end of ``lst`` in specified order. Mutates ``lst``.
|
||||
Raises ``ValueError`` if any ``elements`` are not already in ``lst``."""
|
||||
for elt in elements:
|
||||
lst.append(lst.pop(lst.index(elt)))
|
||||
|
||||
|
||||
class TypedMutableSequence(MutableSequence):
|
||||
"""Base class that behaves like a list, just with a different type.
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
from typing import Dict, Tuple # novm
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.lang import pretty_seconds
|
||||
|
||||
import spack.util.string
|
||||
|
||||
@ -166,7 +167,7 @@ def _attempts_str(wait_time, nattempts):
|
||||
return ""
|
||||
|
||||
attempts = spack.util.string.plural(nattempts, "attempt")
|
||||
return " after {0:0.2f}s and {1}".format(wait_time, attempts)
|
||||
return " after {} and {}".format(pretty_seconds(wait_time), attempts)
|
||||
|
||||
|
||||
class LockType(object):
|
||||
@ -318,8 +319,8 @@ def _lock(self, op, timeout=None):
|
||||
raise LockROFileError(self.path)
|
||||
|
||||
self._log_debug(
|
||||
"{0} locking [{1}:{2}]: timeout {3} sec".format(
|
||||
op_str.lower(), self._start, self._length, timeout
|
||||
"{} locking [{}:{}]: timeout {}".format(
|
||||
op_str.lower(), self._start, self._length, pretty_seconds(timeout or 0)
|
||||
)
|
||||
)
|
||||
|
||||
@ -340,7 +341,8 @@ def _lock(self, op, timeout=None):
|
||||
total_wait_time = time.time() - start_time
|
||||
return total_wait_time, num_attempts
|
||||
|
||||
raise LockTimeoutError("Timed out waiting for a {0} lock.".format(op_str.lower()))
|
||||
total_wait_time = time.time() - start_time
|
||||
raise LockTimeoutError(op_str.lower(), self.path, total_wait_time, num_attempts)
|
||||
|
||||
def _poll_lock(self, op):
|
||||
"""Attempt to acquire the lock in a non-blocking manner. Return whether
|
||||
@ -780,6 +782,18 @@ class LockLimitError(LockError):
|
||||
class LockTimeoutError(LockError):
|
||||
"""Raised when an attempt to acquire a lock times out."""
|
||||
|
||||
def __init__(self, lock_type, path, time, attempts):
|
||||
fmt = "Timed out waiting for a {} lock after {}.\n Made {} {} on file: {}"
|
||||
super(LockTimeoutError, self).__init__(
|
||||
fmt.format(
|
||||
lock_type,
|
||||
pretty_seconds(time),
|
||||
attempts,
|
||||
"attempt" if attempts == 1 else "attempts",
|
||||
path,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class LockUpgradeError(LockError):
|
||||
"""Raised when unable to upgrade from a read to a write lock."""
|
||||
|
@ -3,11 +3,20 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
#: (major, minor, micro, dev release) tuple
|
||||
spack_version_info = (0, 19, 0, "dev0")
|
||||
|
||||
#: PEP440 canonical <major>.<minor>.<micro>.<devN> string
|
||||
spack_version = ".".join(str(s) for s in spack_version_info)
|
||||
__version__ = "0.19.0.dev0"
|
||||
spack_version = __version__
|
||||
|
||||
|
||||
def __try_int(v):
|
||||
try:
|
||||
return int(v)
|
||||
except ValueError:
|
||||
return v
|
||||
|
||||
|
||||
#: (major, minor, micro, dev release) tuple
|
||||
spack_version_info = tuple([__try_int(v) for v in __version__.split(".")])
|
||||
|
||||
|
||||
__all__ = ["spack_version_info", "spack_version"]
|
||||
__version__ = spack_version
|
||||
|
@ -503,6 +503,33 @@ def invalid_sha256_digest(fetcher):
|
||||
return errors
|
||||
|
||||
|
||||
@package_properties
|
||||
def _ensure_env_methods_are_ported_to_builders(pkgs, error_cls):
|
||||
"""Ensure that methods modifying the build environment are ported to builder classes."""
|
||||
errors = []
|
||||
for pkg_name in pkgs:
|
||||
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||
buildsystem_variant, _ = pkg_cls.variants["build_system"]
|
||||
buildsystem_names = [getattr(x, "value", x) for x in buildsystem_variant.values]
|
||||
builder_cls_names = [spack.builder.BUILDER_CLS[x].__name__ for x in buildsystem_names]
|
||||
module = pkg_cls.module
|
||||
has_builders_in_package_py = any(
|
||||
getattr(module, name, False) for name in builder_cls_names
|
||||
)
|
||||
if not has_builders_in_package_py:
|
||||
continue
|
||||
|
||||
for method_name in ("setup_build_environment", "setup_dependent_build_environment"):
|
||||
if hasattr(pkg_cls, method_name):
|
||||
msg = (
|
||||
"Package '{}' need to move the '{}' method from the package class to the"
|
||||
" appropriate builder class".format(pkg_name, method_name)
|
||||
)
|
||||
errors.append(error_cls(msg, []))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@package_https_directives
|
||||
def _linting_package_file(pkgs, error_cls):
|
||||
"""Check for correctness of links"""
|
||||
@ -660,7 +687,13 @@ def _ensure_variant_defaults_are_parsable(pkgs, error_cls):
|
||||
errors.append(error_cls(error_msg.format(variant_name, pkg_name), []))
|
||||
continue
|
||||
|
||||
vspec = variant.make_default()
|
||||
try:
|
||||
vspec = variant.make_default()
|
||||
except spack.variant.MultipleValuesInExclusiveVariantError:
|
||||
error_msg = "Cannot create a default value for the variant '{}' in package '{}'"
|
||||
errors.append(error_cls(error_msg.format(variant_name, pkg_name), []))
|
||||
continue
|
||||
|
||||
try:
|
||||
variant.validate_or_raise(vspec, pkg_cls=pkg_cls)
|
||||
except spack.variant.InvalidVariantValueError:
|
||||
|
@ -7,11 +7,13 @@
|
||||
import collections
|
||||
import hashlib
|
||||
import json
|
||||
import multiprocessing.pool
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
import warnings
|
||||
from contextlib import closing
|
||||
@ -22,7 +24,7 @@
|
||||
import llnl.util.filesystem as fsys
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import mkdirp
|
||||
from llnl.util.filesystem import BaseDirectoryVisitor, mkdirp, visit_directory_tree
|
||||
|
||||
import spack.cmd
|
||||
import spack.config as config
|
||||
@ -41,8 +43,10 @@
|
||||
import spack.util.url as url_util
|
||||
import spack.util.web as web_util
|
||||
from spack.caches import misc_cache_location
|
||||
from spack.relocate import utf8_paths_to_single_binary_regex
|
||||
from spack.spec import Spec
|
||||
from spack.stage import Stage
|
||||
from spack.util.executable import which
|
||||
|
||||
_build_cache_relative_path = "build_cache"
|
||||
_build_cache_keys_relative_path = "_pgp"
|
||||
@ -70,6 +74,10 @@ def __init__(self, errors):
|
||||
super(FetchCacheError, self).__init__(self.message)
|
||||
|
||||
|
||||
class ListMirrorSpecsError(spack.error.SpackError):
|
||||
"""Raised when unable to retrieve list of specs from the mirror"""
|
||||
|
||||
|
||||
class BinaryCacheIndex(object):
|
||||
"""
|
||||
The BinaryCacheIndex tracks what specs are available on (usually remote)
|
||||
@ -105,6 +113,10 @@ def __init__(self, cache_root):
|
||||
# cache (_mirrors_for_spec)
|
||||
self._specs_already_associated = set()
|
||||
|
||||
# mapping from mirror urls to the time.time() of the last index fetch and a bool indicating
|
||||
# whether the fetch succeeded or not.
|
||||
self._last_fetch_times = {}
|
||||
|
||||
# _mirrors_for_spec is a dictionary mapping DAG hashes to lists of
|
||||
# entries indicating mirrors where that concrete spec can be found.
|
||||
# Each entry is a dictionary consisting of:
|
||||
@ -137,6 +149,7 @@ def clear(self):
|
||||
self._index_file_cache = None
|
||||
self._local_index_cache = None
|
||||
self._specs_already_associated = set()
|
||||
self._last_fetch_times = {}
|
||||
self._mirrors_for_spec = {}
|
||||
|
||||
def _write_local_index_cache(self):
|
||||
@ -242,7 +255,6 @@ def find_built_spec(self, spec, mirrors_to_check=None):
|
||||
}
|
||||
]
|
||||
"""
|
||||
self.regenerate_spec_cache()
|
||||
return self.find_by_hash(spec.dag_hash(), mirrors_to_check=mirrors_to_check)
|
||||
|
||||
def find_by_hash(self, find_hash, mirrors_to_check=None):
|
||||
@ -253,6 +265,9 @@ def find_by_hash(self, find_hash, mirrors_to_check=None):
|
||||
mirrors_to_check: Optional mapping containing mirrors to check. If
|
||||
None, just assumes all configured mirrors.
|
||||
"""
|
||||
if find_hash not in self._mirrors_for_spec:
|
||||
# Not found in the cached index, pull the latest from the server.
|
||||
self.update(with_cooldown=True)
|
||||
if find_hash not in self._mirrors_for_spec:
|
||||
return None
|
||||
results = self._mirrors_for_spec[find_hash]
|
||||
@ -283,7 +298,7 @@ def update_spec(self, spec, found_list):
|
||||
"spec": new_entry["spec"],
|
||||
}
|
||||
|
||||
def update(self):
|
||||
def update(self, with_cooldown=False):
|
||||
"""Make sure local cache of buildcache index files is up to date.
|
||||
If the same mirrors are configured as the last time this was called
|
||||
and none of the remote buildcache indices have changed, calling this
|
||||
@ -325,24 +340,41 @@ def update(self):
|
||||
|
||||
fetch_errors = []
|
||||
all_methods_failed = True
|
||||
ttl = spack.config.get("config:binary_index_ttl", 600)
|
||||
now = time.time()
|
||||
|
||||
for cached_mirror_url in self._local_index_cache:
|
||||
cache_entry = self._local_index_cache[cached_mirror_url]
|
||||
cached_index_hash = cache_entry["index_hash"]
|
||||
cached_index_path = cache_entry["index_path"]
|
||||
if cached_mirror_url in configured_mirror_urls:
|
||||
# May need to fetch the index and update the local caches
|
||||
try:
|
||||
needs_regen = self._fetch_and_cache_index(
|
||||
cached_mirror_url, expect_hash=cached_index_hash
|
||||
)
|
||||
all_methods_failed = False
|
||||
except FetchCacheError as fetch_error:
|
||||
needs_regen = False
|
||||
fetch_errors.extend(fetch_error.errors)
|
||||
# The need to regenerate implies a need to clear as well.
|
||||
spec_cache_clear_needed |= needs_regen
|
||||
spec_cache_regenerate_needed |= needs_regen
|
||||
# Only do a fetch if the last fetch was longer than TTL ago
|
||||
if (
|
||||
with_cooldown
|
||||
and ttl > 0
|
||||
and cached_mirror_url in self._last_fetch_times
|
||||
and now - self._last_fetch_times[cached_mirror_url][0] < ttl
|
||||
):
|
||||
# We're in the cooldown period, don't try to fetch again
|
||||
# If the fetch succeeded last time, consider this update a success, otherwise
|
||||
# re-report the error here
|
||||
if self._last_fetch_times[cached_mirror_url][1]:
|
||||
all_methods_failed = False
|
||||
else:
|
||||
# May need to fetch the index and update the local caches
|
||||
try:
|
||||
needs_regen = self._fetch_and_cache_index(
|
||||
cached_mirror_url, expect_hash=cached_index_hash
|
||||
)
|
||||
self._last_fetch_times[cached_mirror_url] = (now, True)
|
||||
all_methods_failed = False
|
||||
except FetchCacheError as fetch_error:
|
||||
needs_regen = False
|
||||
fetch_errors.extend(fetch_error.errors)
|
||||
self._last_fetch_times[cached_mirror_url] = (now, False)
|
||||
# The need to regenerate implies a need to clear as well.
|
||||
spec_cache_clear_needed |= needs_regen
|
||||
spec_cache_regenerate_needed |= needs_regen
|
||||
else:
|
||||
# No longer have this mirror, cached index should be removed
|
||||
items_to_remove.append(
|
||||
@ -351,6 +383,8 @@ def update(self):
|
||||
"cache_key": os.path.join(self._index_cache_root, cached_index_path),
|
||||
}
|
||||
)
|
||||
if cached_mirror_url in self._last_fetch_times:
|
||||
del self._last_fetch_times[cached_mirror_url]
|
||||
spec_cache_clear_needed = True
|
||||
spec_cache_regenerate_needed = True
|
||||
|
||||
@ -369,10 +403,12 @@ def update(self):
|
||||
# Need to fetch the index and update the local caches
|
||||
try:
|
||||
needs_regen = self._fetch_and_cache_index(mirror_url)
|
||||
self._last_fetch_times[mirror_url] = (now, True)
|
||||
all_methods_failed = False
|
||||
except FetchCacheError as fetch_error:
|
||||
fetch_errors.extend(fetch_error.errors)
|
||||
needs_regen = False
|
||||
self._last_fetch_times[mirror_url] = (now, False)
|
||||
# Generally speaking, a new mirror wouldn't imply the need to
|
||||
# clear the spec cache, so leave it as is.
|
||||
if needs_regen:
|
||||
@ -619,6 +655,57 @@ def read_buildinfo_file(prefix):
|
||||
return buildinfo
|
||||
|
||||
|
||||
class BuildManifestVisitor(BaseDirectoryVisitor):
|
||||
"""Visitor that collects a list of files and symlinks
|
||||
that can be checked for need of relocation. It knows how
|
||||
to dedupe hardlinks and deal with symlinks to files and
|
||||
directories."""
|
||||
|
||||
def __init__(self):
|
||||
# Save unique identifiers of files to avoid
|
||||
# relocating hardlink files for each path.
|
||||
self.visited = set()
|
||||
|
||||
# Lists of files we will check
|
||||
self.files = []
|
||||
self.symlinks = []
|
||||
|
||||
def seen_before(self, root, rel_path):
|
||||
stat_result = os.lstat(os.path.join(root, rel_path))
|
||||
identifier = (stat_result.st_dev, stat_result.st_ino)
|
||||
if identifier in self.visited:
|
||||
return True
|
||||
else:
|
||||
self.visited.add(identifier)
|
||||
return False
|
||||
|
||||
def visit_file(self, root, rel_path, depth):
|
||||
if self.seen_before(root, rel_path):
|
||||
return
|
||||
self.files.append(rel_path)
|
||||
|
||||
def visit_symlinked_file(self, root, rel_path, depth):
|
||||
# Note: symlinks *can* be hardlinked, but it is unclear if
|
||||
# symlinks can be relinked in-place (preserving inode).
|
||||
# Therefore, we do *not* de-dupe hardlinked symlinks.
|
||||
self.symlinks.append(rel_path)
|
||||
|
||||
def before_visit_dir(self, root, rel_path, depth):
|
||||
return os.path.basename(rel_path) not in (".spack", "man")
|
||||
|
||||
def before_visit_symlinked_dir(self, root, rel_path, depth):
|
||||
# Treat symlinked directories simply as symlinks.
|
||||
self.visit_symlinked_file(root, rel_path, depth)
|
||||
# Never recurse into symlinked directories.
|
||||
return False
|
||||
|
||||
|
||||
def file_matches(path, regex):
|
||||
with open(path, "rb") as f:
|
||||
contents = f.read()
|
||||
return bool(regex.search(contents))
|
||||
|
||||
|
||||
def get_buildfile_manifest(spec):
|
||||
"""
|
||||
Return a data structure with information about a build, including
|
||||
@ -634,57 +721,61 @@ def get_buildfile_manifest(spec):
|
||||
"link_to_relocate": [],
|
||||
"other": [],
|
||||
"binary_to_relocate_fullpath": [],
|
||||
"hardlinks_deduped": True,
|
||||
}
|
||||
|
||||
exclude_list = (".spack", "man")
|
||||
# Guard against filesystem footguns of hardlinks and symlinks by using
|
||||
# a visitor to retrieve a list of files and symlinks, so we don't have
|
||||
# to worry about hardlinks of symlinked dirs and what not.
|
||||
visitor = BuildManifestVisitor()
|
||||
root = spec.prefix
|
||||
visit_directory_tree(root, visitor)
|
||||
|
||||
# Do this at during tarball creation to save time when tarball unpacked.
|
||||
# Used by make_package_relative to determine binaries to change.
|
||||
for root, dirs, files in os.walk(spec.prefix, topdown=True):
|
||||
dirs[:] = [d for d in dirs if d not in exclude_list]
|
||||
# Collect a list of prefixes for this package and it's dependencies, Spack will
|
||||
# look for them to decide if text file needs to be relocated or not
|
||||
prefixes = [d.prefix for d in spec.traverse(root=True, deptype="all") if not d.external]
|
||||
prefixes.append(spack.hooks.sbang.sbang_install_path())
|
||||
prefixes.append(str(spack.store.layout.root))
|
||||
|
||||
# Directories may need to be relocated too.
|
||||
for directory in dirs:
|
||||
dir_path_name = os.path.join(root, directory)
|
||||
rel_path_name = os.path.relpath(dir_path_name, spec.prefix)
|
||||
if os.path.islink(dir_path_name):
|
||||
link = os.readlink(dir_path_name)
|
||||
if os.path.isabs(link) and link.startswith(spack.store.layout.root):
|
||||
data["link_to_relocate"].append(rel_path_name)
|
||||
# Create a giant regex that matches all prefixes
|
||||
regex = utf8_paths_to_single_binary_regex(prefixes)
|
||||
|
||||
for filename in files:
|
||||
path_name = os.path.join(root, filename)
|
||||
m_type, m_subtype = fsys.mime_type(path_name)
|
||||
rel_path_name = os.path.relpath(path_name, spec.prefix)
|
||||
added = False
|
||||
# Symlinks.
|
||||
|
||||
if os.path.islink(path_name):
|
||||
link = os.readlink(path_name)
|
||||
if os.path.isabs(link):
|
||||
# Relocate absolute links into the spack tree
|
||||
if link.startswith(spack.store.layout.root):
|
||||
data["link_to_relocate"].append(rel_path_name)
|
||||
added = True
|
||||
# Obvious bugs:
|
||||
# 1. relative links are not relocated.
|
||||
# 2. paths are used as strings.
|
||||
for rel_path in visitor.symlinks:
|
||||
abs_path = os.path.join(root, rel_path)
|
||||
link = os.readlink(abs_path)
|
||||
if os.path.isabs(link) and link.startswith(spack.store.layout.root):
|
||||
data["link_to_relocate"].append(rel_path)
|
||||
|
||||
if relocate.needs_binary_relocation(m_type, m_subtype):
|
||||
if (
|
||||
(
|
||||
m_subtype in ("x-executable", "x-sharedlib", "x-pie-executable")
|
||||
and sys.platform != "darwin"
|
||||
)
|
||||
or (m_subtype in ("x-mach-binary") and sys.platform == "darwin")
|
||||
or (not filename.endswith(".o"))
|
||||
):
|
||||
data["binary_to_relocate"].append(rel_path_name)
|
||||
data["binary_to_relocate_fullpath"].append(path_name)
|
||||
added = True
|
||||
# Non-symlinks.
|
||||
for rel_path in visitor.files:
|
||||
abs_path = os.path.join(root, rel_path)
|
||||
m_type, m_subtype = fsys.mime_type(abs_path)
|
||||
|
||||
if relocate.needs_text_relocation(m_type, m_subtype):
|
||||
data["text_to_relocate"].append(rel_path_name)
|
||||
added = True
|
||||
if relocate.needs_binary_relocation(m_type, m_subtype):
|
||||
# Why is this branch not part of needs_binary_relocation? :(
|
||||
if (
|
||||
(
|
||||
m_subtype in ("x-executable", "x-sharedlib", "x-pie-executable")
|
||||
and sys.platform != "darwin"
|
||||
)
|
||||
or (m_subtype in ("x-mach-binary") and sys.platform == "darwin")
|
||||
or (not rel_path.endswith(".o"))
|
||||
):
|
||||
data["binary_to_relocate"].append(rel_path)
|
||||
data["binary_to_relocate_fullpath"].append(abs_path)
|
||||
continue
|
||||
|
||||
elif relocate.needs_text_relocation(m_type, m_subtype) and file_matches(abs_path, regex):
|
||||
data["text_to_relocate"].append(rel_path)
|
||||
continue
|
||||
|
||||
data["other"].append(abs_path)
|
||||
|
||||
if not added:
|
||||
data["other"].append(path_name)
|
||||
return data
|
||||
|
||||
|
||||
@ -698,7 +789,7 @@ def write_buildinfo_file(spec, workdir, rel=False):
|
||||
prefix_to_hash = dict()
|
||||
prefix_to_hash[str(spec.package.prefix)] = spec.dag_hash()
|
||||
deps = spack.build_environment.get_rpath_deps(spec.package)
|
||||
for d in deps:
|
||||
for d in deps + spec.dependencies(deptype="run"):
|
||||
prefix_to_hash[str(d.prefix)] = d.dag_hash()
|
||||
|
||||
# Create buildinfo data and write it to disk
|
||||
@ -711,6 +802,7 @@ def write_buildinfo_file(spec, workdir, rel=False):
|
||||
buildinfo["relocate_textfiles"] = manifest["text_to_relocate"]
|
||||
buildinfo["relocate_binaries"] = manifest["binary_to_relocate"]
|
||||
buildinfo["relocate_links"] = manifest["link_to_relocate"]
|
||||
buildinfo["hardlinks_deduped"] = manifest["hardlinks_deduped"]
|
||||
buildinfo["prefix_to_hash"] = prefix_to_hash
|
||||
filename = buildinfo_file_name(workdir)
|
||||
with open(filename, "w") as outfile:
|
||||
@ -795,37 +887,52 @@ def sign_specfile(key, force, specfile_path):
|
||||
spack.util.gpg.sign(key, specfile_path, signed_specfile_path, clearsign=True)
|
||||
|
||||
|
||||
def _fetch_spec_from_mirror(spec_url):
|
||||
s = None
|
||||
tty.debug("fetching {0}".format(spec_url))
|
||||
_, _, spec_file = web_util.read_from_url(spec_url)
|
||||
spec_file_contents = codecs.getreader("utf-8")(spec_file).read()
|
||||
# Need full spec.json name or this gets confused with index.json.
|
||||
if spec_url.endswith(".json.sig"):
|
||||
specfile_json = Spec.extract_json_from_clearsig(spec_file_contents)
|
||||
s = Spec.from_dict(specfile_json)
|
||||
elif spec_url.endswith(".json"):
|
||||
s = Spec.from_json(spec_file_contents)
|
||||
elif spec_url.endswith(".yaml"):
|
||||
s = Spec.from_yaml(spec_file_contents)
|
||||
return s
|
||||
def _read_specs_and_push_index(file_list, read_method, cache_prefix, db, temp_dir, concurrency):
|
||||
"""Read all the specs listed in the provided list, using thread given thread parallelism,
|
||||
generate the index, and push it to the mirror.
|
||||
|
||||
Args:
|
||||
file_list (list(str)): List of urls or file paths pointing at spec files to read
|
||||
read_method: A function taking a single argument, either a url or a file path,
|
||||
and which reads the spec file at that location, and returns the spec.
|
||||
cache_prefix (str): prefix of the build cache on s3 where index should be pushed.
|
||||
db: A spack database used for adding specs and then writing the index.
|
||||
temp_dir (str): Location to write index.json and hash for pushing
|
||||
concurrency (int): Number of parallel processes to use when fetching
|
||||
|
||||
def _read_specs_and_push_index(file_list, cache_prefix, db, db_root_dir):
|
||||
for file_path in file_list:
|
||||
try:
|
||||
s = _fetch_spec_from_mirror(url_util.join(cache_prefix, file_path))
|
||||
except (URLError, web_util.SpackWebError) as url_err:
|
||||
tty.error("Error reading specfile: {0}".format(file_path))
|
||||
tty.error(url_err)
|
||||
Return:
|
||||
None
|
||||
"""
|
||||
|
||||
if s:
|
||||
db.add(s, None)
|
||||
db.mark(s, "in_buildcache", True)
|
||||
def _fetch_spec_from_mirror(spec_url):
|
||||
spec_file_contents = read_method(spec_url)
|
||||
|
||||
if spec_file_contents:
|
||||
# Need full spec.json name or this gets confused with index.json.
|
||||
if spec_url.endswith(".json.sig"):
|
||||
specfile_json = Spec.extract_json_from_clearsig(spec_file_contents)
|
||||
return Spec.from_dict(specfile_json)
|
||||
if spec_url.endswith(".json"):
|
||||
return Spec.from_json(spec_file_contents)
|
||||
if spec_url.endswith(".yaml"):
|
||||
return Spec.from_yaml(spec_file_contents)
|
||||
|
||||
tp = multiprocessing.pool.ThreadPool(processes=concurrency)
|
||||
try:
|
||||
fetched_specs = tp.map(
|
||||
llnl.util.lang.star(_fetch_spec_from_mirror), [(f,) for f in file_list]
|
||||
)
|
||||
finally:
|
||||
tp.terminate()
|
||||
tp.join()
|
||||
|
||||
for fetched_spec in fetched_specs:
|
||||
db.add(fetched_spec, None)
|
||||
db.mark(fetched_spec, "in_buildcache", True)
|
||||
|
||||
# Now generate the index, compute its hash, and push the two files to
|
||||
# the mirror.
|
||||
index_json_path = os.path.join(db_root_dir, "index.json")
|
||||
index_json_path = os.path.join(temp_dir, "index.json")
|
||||
with open(index_json_path, "w") as f:
|
||||
db._write_to_file(f)
|
||||
|
||||
@ -835,7 +942,7 @@ def _read_specs_and_push_index(file_list, cache_prefix, db, db_root_dir):
|
||||
index_hash = compute_hash(index_string)
|
||||
|
||||
# Write the hash out to a local file
|
||||
index_hash_path = os.path.join(db_root_dir, "index.json.hash")
|
||||
index_hash_path = os.path.join(temp_dir, "index.json.hash")
|
||||
with open(index_hash_path, "w") as f:
|
||||
f.write(index_hash)
|
||||
|
||||
@ -856,33 +963,152 @@ def _read_specs_and_push_index(file_list, cache_prefix, db, db_root_dir):
|
||||
)
|
||||
|
||||
|
||||
def generate_package_index(cache_prefix):
|
||||
"""Create the build cache index page.
|
||||
def _specs_from_cache_aws_cli(cache_prefix):
|
||||
"""Use aws cli to sync all the specs into a local temporary directory.
|
||||
|
||||
Creates (or replaces) the "index.json" page at the location given in
|
||||
cache_prefix. This page contains a link for each binary package (.yaml or
|
||||
.json) under cache_prefix.
|
||||
Args:
|
||||
cache_prefix (str): prefix of the build cache on s3
|
||||
|
||||
Return:
|
||||
List of the local file paths and a function that can read each one from the file system.
|
||||
"""
|
||||
read_fn = None
|
||||
file_list = None
|
||||
aws = which("aws")
|
||||
|
||||
def file_read_method(file_path):
|
||||
with open(file_path) as fd:
|
||||
return fd.read()
|
||||
|
||||
tmpspecsdir = tempfile.mkdtemp()
|
||||
sync_command_args = [
|
||||
"s3",
|
||||
"sync",
|
||||
"--exclude",
|
||||
"*",
|
||||
"--include",
|
||||
"*.spec.json.sig",
|
||||
"--include",
|
||||
"*.spec.json",
|
||||
"--include",
|
||||
"*.spec.yaml",
|
||||
cache_prefix,
|
||||
tmpspecsdir,
|
||||
]
|
||||
|
||||
try:
|
||||
file_list = (
|
||||
entry
|
||||
tty.debug(
|
||||
"Using aws s3 sync to download specs from {0} to {1}".format(cache_prefix, tmpspecsdir)
|
||||
)
|
||||
aws(*sync_command_args, output=os.devnull, error=os.devnull)
|
||||
file_list = fsys.find(tmpspecsdir, ["*.spec.json.sig", "*.spec.json", "*.spec.yaml"])
|
||||
read_fn = file_read_method
|
||||
except Exception:
|
||||
tty.warn("Failed to use aws s3 sync to retrieve specs, falling back to parallel fetch")
|
||||
shutil.rmtree(tmpspecsdir)
|
||||
|
||||
return file_list, read_fn
|
||||
|
||||
|
||||
def _specs_from_cache_fallback(cache_prefix):
|
||||
"""Use spack.util.web module to get a list of all the specs at the remote url.
|
||||
|
||||
Args:
|
||||
cache_prefix (str): Base url of mirror (location of spec files)
|
||||
|
||||
Return:
|
||||
The list of complete spec file urls and a function that can read each one from its
|
||||
remote location (also using the spack.util.web module).
|
||||
"""
|
||||
read_fn = None
|
||||
file_list = None
|
||||
|
||||
def url_read_method(url):
|
||||
contents = None
|
||||
try:
|
||||
_, _, spec_file = web_util.read_from_url(url)
|
||||
contents = codecs.getreader("utf-8")(spec_file).read()
|
||||
except (URLError, web_util.SpackWebError) as url_err:
|
||||
tty.error("Error reading specfile: {0}".format(url))
|
||||
tty.error(url_err)
|
||||
return contents
|
||||
|
||||
try:
|
||||
file_list = [
|
||||
url_util.join(cache_prefix, entry)
|
||||
for entry in web_util.list_url(cache_prefix)
|
||||
if entry.endswith(".yaml")
|
||||
or entry.endswith("spec.json")
|
||||
or entry.endswith("spec.json.sig")
|
||||
)
|
||||
]
|
||||
read_fn = url_read_method
|
||||
except KeyError as inst:
|
||||
msg = "No packages at {0}: {1}".format(cache_prefix, inst)
|
||||
tty.warn(msg)
|
||||
return
|
||||
except Exception as err:
|
||||
# If we got some kind of S3 (access denied or other connection
|
||||
# error), the first non boto-specific class in the exception
|
||||
# hierarchy is Exception. Just print a warning and return
|
||||
msg = "Encountered problem listing packages at {0}: {1}".format(cache_prefix, err)
|
||||
tty.warn(msg)
|
||||
|
||||
return file_list, read_fn
|
||||
|
||||
|
||||
def _spec_files_from_cache(cache_prefix):
|
||||
"""Get a list of all the spec files in the mirror and a function to
|
||||
read them.
|
||||
|
||||
Args:
|
||||
cache_prefix (str): Base url of mirror (location of spec files)
|
||||
|
||||
Return:
|
||||
A tuple where the first item is a list of absolute file paths or
|
||||
urls pointing to the specs that should be read from the mirror,
|
||||
and the second item is a function taking a url or file path and
|
||||
returning the spec read from that location.
|
||||
"""
|
||||
callbacks = []
|
||||
if cache_prefix.startswith("s3"):
|
||||
callbacks.append(_specs_from_cache_aws_cli)
|
||||
|
||||
callbacks.append(_specs_from_cache_fallback)
|
||||
|
||||
for specs_from_cache_fn in callbacks:
|
||||
file_list, read_fn = specs_from_cache_fn(cache_prefix)
|
||||
if file_list:
|
||||
return file_list, read_fn
|
||||
|
||||
raise ListMirrorSpecsError("Failed to get list of specs from {0}".format(cache_prefix))
|
||||
|
||||
|
||||
def generate_package_index(cache_prefix, concurrency=32):
|
||||
"""Create or replace the build cache index on the given mirror. The
|
||||
buildcache index contains an entry for each binary package under the
|
||||
cache_prefix.
|
||||
|
||||
Args:
|
||||
cache_prefix(str): Base url of binary mirror.
|
||||
concurrency: (int): The desired threading concurrency to use when
|
||||
fetching the spec files from the mirror.
|
||||
|
||||
Return:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
file_list, read_fn = _spec_files_from_cache(cache_prefix)
|
||||
except ListMirrorSpecsError as err:
|
||||
tty.error("Unabled to generate package index, {0}".format(err))
|
||||
return
|
||||
|
||||
if any(x.endswith(".yaml") for x in file_list):
|
||||
msg = (
|
||||
"The mirror in '{}' contains specs in the deprecated YAML format.\n\n\tSupport for "
|
||||
"this format will be removed in v0.20, please regenerate the build cache with a "
|
||||
"recent Spack\n"
|
||||
).format(cache_prefix)
|
||||
warnings.warn(msg)
|
||||
|
||||
tty.debug("Retrieving spec descriptor files from {0} to build index".format(cache_prefix))
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
@ -895,7 +1121,7 @@ def generate_package_index(cache_prefix):
|
||||
)
|
||||
|
||||
try:
|
||||
_read_specs_and_push_index(file_list, cache_prefix, db, db_root_dir)
|
||||
_read_specs_and_push_index(file_list, read_fn, cache_prefix, db, db_root_dir, concurrency)
|
||||
except Exception as err:
|
||||
msg = "Encountered problem pushing package index to {0}: {1}".format(cache_prefix, err)
|
||||
tty.warn(msg)
|
||||
@ -1071,7 +1297,11 @@ def _build_tarball(
|
||||
tty.die(e)
|
||||
|
||||
# create gzip compressed tarball of the install prefix
|
||||
with closing(tarfile.open(tarfile_path, "w:gz")) as tar:
|
||||
# On AMD Ryzen 3700X and an SSD disk, we have the following on compression speed:
|
||||
# compresslevel=6 gzip default: llvm takes 4mins, roughly 2.1GB
|
||||
# compresslevel=9 python default: llvm takes 12mins, roughly 2.1GB
|
||||
# So we follow gzip.
|
||||
with closing(tarfile.open(tarfile_path, "w:gz", compresslevel=6)) as tar:
|
||||
tar.add(name="%s" % workdir, arcname="%s" % os.path.basename(spec.prefix))
|
||||
# remove copy of install directory
|
||||
shutil.rmtree(workdir)
|
||||
@ -1346,6 +1576,13 @@ def download_tarball(spec, unsigned=False, mirrors_for_spec=None):
|
||||
# the remaining mirrors, looking for one we can use.
|
||||
tarball_stage = try_fetch(spackfile_url)
|
||||
if tarball_stage:
|
||||
if ext == "yaml":
|
||||
msg = (
|
||||
"Reading {} from mirror.\n\n\tThe YAML format for buildcaches is "
|
||||
"deprecated and will be removed in v0.20\n"
|
||||
).format(spackfile_url)
|
||||
warnings.warn(msg)
|
||||
|
||||
return {
|
||||
"tarball_stage": tarball_stage,
|
||||
"specfile_stage": local_specfile_stage,
|
||||
@ -1418,6 +1655,38 @@ def check_package_relocatable(workdir, spec, allow_root):
|
||||
relocate.raise_if_not_relocatable(cur_path_names, allow_root)
|
||||
|
||||
|
||||
def dedupe_hardlinks_if_necessary(root, buildinfo):
|
||||
"""Updates a buildinfo dict for old archives that did
|
||||
not dedupe hardlinks. De-duping hardlinks is necessary
|
||||
when relocating files in parallel and in-place. This
|
||||
means we must preserve inodes when relocating."""
|
||||
|
||||
# New archives don't need this.
|
||||
if buildinfo.get("hardlinks_deduped", False):
|
||||
return
|
||||
|
||||
# Clearly we can assume that an inode is either in the
|
||||
# textfile or binary group, but let's just stick to
|
||||
# a single set of visited nodes.
|
||||
visited = set()
|
||||
|
||||
# Note: we do *not* dedupe hardlinked symlinks, since
|
||||
# it seems difficult or even impossible to relink
|
||||
# symlinks while preserving inode.
|
||||
for key in ("relocate_textfiles", "relocate_binaries"):
|
||||
if key not in buildinfo:
|
||||
continue
|
||||
new_list = []
|
||||
for rel_path in buildinfo[key]:
|
||||
stat_result = os.lstat(os.path.join(root, rel_path))
|
||||
identifier = (stat_result.st_dev, stat_result.st_ino)
|
||||
if identifier in visited:
|
||||
continue
|
||||
visited.add(identifier)
|
||||
new_list.append(rel_path)
|
||||
buildinfo[key] = new_list
|
||||
|
||||
|
||||
def relocate_package(spec, allow_root):
|
||||
"""
|
||||
Relocate the given package
|
||||
@ -1451,7 +1720,7 @@ def relocate_package(spec, allow_root):
|
||||
hash_to_prefix = dict()
|
||||
hash_to_prefix[spec.format("{hash}")] = str(spec.package.prefix)
|
||||
new_deps = spack.build_environment.get_rpath_deps(spec.package)
|
||||
for d in new_deps:
|
||||
for d in new_deps + spec.dependencies(deptype="run"):
|
||||
hash_to_prefix[d.format("{hash}")] = str(d.prefix)
|
||||
# Spurious replacements (e.g. sbang) will cause issues with binaries
|
||||
# For example, the new sbang can be longer than the old one.
|
||||
@ -1463,13 +1732,19 @@ def relocate_package(spec, allow_root):
|
||||
install_path = spack.hooks.sbang.sbang_install_path()
|
||||
prefix_to_prefix_text[old_sbang_install_path] = install_path
|
||||
|
||||
# First match specific prefix paths. Possibly the *local* install prefix
|
||||
# of some dependency is in an upstream, so we cannot assume the original
|
||||
# spack store root can be mapped uniformly to the new spack store root.
|
||||
for orig_prefix, hash in prefix_to_hash.items():
|
||||
prefix_to_prefix_text[orig_prefix] = hash_to_prefix.get(hash, None)
|
||||
prefix_to_prefix_bin[orig_prefix] = hash_to_prefix.get(hash, None)
|
||||
|
||||
# Only then add the generic fallback of install prefix -> install prefix.
|
||||
prefix_to_prefix_text[old_prefix] = new_prefix
|
||||
prefix_to_prefix_bin[old_prefix] = new_prefix
|
||||
prefix_to_prefix_text[old_layout_root] = new_layout_root
|
||||
prefix_to_prefix_bin[old_layout_root] = new_layout_root
|
||||
for orig_prefix, hash in prefix_to_hash.items():
|
||||
prefix_to_prefix_text[orig_prefix] = hash_to_prefix.get(hash, None)
|
||||
prefix_to_prefix_bin[orig_prefix] = hash_to_prefix.get(hash, None)
|
||||
|
||||
# This is vestigial code for the *old* location of sbang. Previously,
|
||||
# sbang was a bash script, and it lived in the spack prefix. It is
|
||||
# now a POSIX script that lives in the install prefix. Old packages
|
||||
@ -1480,6 +1755,9 @@ def relocate_package(spec, allow_root):
|
||||
|
||||
tty.debug("Relocating package from", "%s to %s." % (old_layout_root, new_layout_root))
|
||||
|
||||
# Old archives maybe have hardlinks repeated.
|
||||
dedupe_hardlinks_if_necessary(workdir, buildinfo)
|
||||
|
||||
def is_backup_file(file):
|
||||
return file.endswith("~")
|
||||
|
||||
@ -1509,7 +1787,11 @@ def is_backup_file(file):
|
||||
old_prefix,
|
||||
new_prefix,
|
||||
)
|
||||
if "elf" in platform.binary_formats:
|
||||
elif "elf" in platform.binary_formats and not rel:
|
||||
# The new ELF dynamic section relocation logic only handles absolute to
|
||||
# absolute relocation.
|
||||
relocate.new_relocate_elf_binaries(files_to_relocate, prefix_to_prefix_bin)
|
||||
elif "elf" in platform.binary_formats and rel:
|
||||
relocate.relocate_elf_binaries(
|
||||
files_to_relocate,
|
||||
old_layout_root,
|
||||
@ -1519,35 +1801,23 @@ def is_backup_file(file):
|
||||
old_prefix,
|
||||
new_prefix,
|
||||
)
|
||||
# Relocate links to the new install prefix
|
||||
links = [link for link in buildinfo.get("relocate_links", [])]
|
||||
relocate.relocate_links(links, old_layout_root, old_prefix, new_prefix)
|
||||
|
||||
# Relocate links to the new install prefix
|
||||
links = [os.path.join(workdir, f) for f in buildinfo.get("relocate_links", [])]
|
||||
relocate.relocate_links(links, prefix_to_prefix_bin)
|
||||
|
||||
# For all buildcaches
|
||||
# relocate the install prefixes in text files including dependencies
|
||||
relocate.relocate_text(text_names, prefix_to_prefix_text)
|
||||
relocate.unsafe_relocate_text(text_names, prefix_to_prefix_text)
|
||||
|
||||
paths_to_relocate = [old_prefix, old_layout_root]
|
||||
paths_to_relocate.extend(prefix_to_hash.keys())
|
||||
files_to_relocate = list(
|
||||
filter(
|
||||
lambda pathname: not relocate.file_is_relocatable(
|
||||
pathname, paths_to_relocate=paths_to_relocate
|
||||
),
|
||||
map(
|
||||
lambda filename: os.path.join(workdir, filename),
|
||||
buildinfo["relocate_binaries"],
|
||||
),
|
||||
)
|
||||
)
|
||||
# relocate the install prefixes in binary files including dependencies
|
||||
relocate.relocate_text_bin(files_to_relocate, prefix_to_prefix_bin)
|
||||
relocate.unsafe_relocate_text_bin(files_to_relocate, prefix_to_prefix_bin)
|
||||
|
||||
# If we are installing back to the same location
|
||||
# relocate the sbang location if the spack directory changed
|
||||
else:
|
||||
if old_spack_prefix != new_spack_prefix:
|
||||
relocate.relocate_text(text_names, prefix_to_prefix_text)
|
||||
relocate.unsafe_relocate_text(text_names, prefix_to_prefix_text)
|
||||
|
||||
|
||||
def _extract_inner_tarball(spec, filename, extract_to, unsigned, remote_checksum):
|
||||
@ -1878,8 +2148,8 @@ def get_mirrors_for_spec(spec=None, mirrors_to_check=None, index_only=False):
|
||||
|
||||
results = binary_index.find_built_spec(spec, mirrors_to_check=mirrors_to_check)
|
||||
|
||||
# Maybe we just didn't have the latest information from the mirror, so
|
||||
# try to fetch directly, unless we are only considering the indices.
|
||||
# The index may be out-of-date. If we aren't only considering indices, try
|
||||
# to fetch directly since we know where the file should be.
|
||||
if not results and not index_only:
|
||||
results = try_direct_fetch(spec, mirrors=mirrors_to_check)
|
||||
# We found a spec by the direct fetch approach, we might as well
|
||||
|
@ -91,6 +91,14 @@ def _try_import_from_store(module, query_spec, query_info=None):
|
||||
os.path.join(candidate_spec.prefix, pkg.platlib),
|
||||
] # type: list[str]
|
||||
path_before = list(sys.path)
|
||||
|
||||
# Python 3.8+ on Windows does not search dependent DLLs in PATH,
|
||||
# so we need to manually add it using os.add_dll_directory
|
||||
# https://docs.python.org/3/whatsnew/3.8.html#bpo-36085-whatsnew
|
||||
if sys.version_info[:2] >= (3, 8) and sys.platform == "win32":
|
||||
if os.path.isdir(candidate_spec.prefix.bin):
|
||||
os.add_dll_directory(candidate_spec.prefix.bin) # novermin
|
||||
|
||||
# NOTE: try module_paths first and last, last allows an existing version in path
|
||||
# to be picked up and used, possibly depending on something in the store, first
|
||||
# allows the bootstrap version to work when an incompatible version is in
|
||||
@ -667,6 +675,11 @@ def _add_externals_if_missing():
|
||||
_REF_COUNT = 0
|
||||
|
||||
|
||||
def is_bootstrapping():
|
||||
global _REF_COUNT
|
||||
return _REF_COUNT > 0
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ensure_bootstrap_configuration():
|
||||
# The context manager is reference counted to ensure we don't swap multiple
|
||||
|
@ -52,6 +52,7 @@
|
||||
|
||||
import spack.build_systems.cmake
|
||||
import spack.build_systems.meson
|
||||
import spack.builder
|
||||
import spack.config
|
||||
import spack.install_test
|
||||
import spack.main
|
||||
@ -120,18 +121,18 @@
|
||||
stat_suffix = "lib" if sys.platform == "win32" else "a"
|
||||
|
||||
|
||||
def should_set_parallel_jobs(jobserver_support=False):
|
||||
"""Returns true in general, except when:
|
||||
- The env variable SPACK_NO_PARALLEL_MAKE=1 is set
|
||||
- jobserver_support is enabled, and a jobserver was found.
|
||||
"""
|
||||
if (
|
||||
jobserver_support
|
||||
and "MAKEFLAGS" in os.environ
|
||||
and "--jobserver" in os.environ["MAKEFLAGS"]
|
||||
):
|
||||
return False
|
||||
return not env_flag(SPACK_NO_PARALLEL_MAKE)
|
||||
def jobserver_enabled():
|
||||
"""Returns true if a posix jobserver (make) is detected."""
|
||||
return "MAKEFLAGS" in os.environ and "--jobserver" in os.environ["MAKEFLAGS"]
|
||||
|
||||
|
||||
def get_effective_jobs(jobs, parallel=True, supports_jobserver=False):
|
||||
"""Return the number of jobs, or None if supports_jobserver and a jobserver is detected."""
|
||||
if not parallel or jobs <= 1 or env_flag(SPACK_NO_PARALLEL_MAKE):
|
||||
return 1
|
||||
if supports_jobserver and jobserver_enabled():
|
||||
return None
|
||||
return jobs
|
||||
|
||||
|
||||
class MakeExecutable(Executable):
|
||||
@ -146,26 +147,33 @@ class MakeExecutable(Executable):
|
||||
"""
|
||||
|
||||
def __init__(self, name, jobs, **kwargs):
|
||||
supports_jobserver = kwargs.pop("supports_jobserver", True)
|
||||
super(MakeExecutable, self).__init__(name, **kwargs)
|
||||
self.supports_jobserver = supports_jobserver
|
||||
self.jobs = jobs
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""parallel, and jobs_env from kwargs are swallowed and used here;
|
||||
remaining arguments are passed through to the superclass.
|
||||
"""
|
||||
# TODO: figure out how to check if we are using a jobserver-supporting ninja,
|
||||
# the two split ninja packages make this very difficult right now
|
||||
parallel = should_set_parallel_jobs(jobserver_support=True) and kwargs.pop(
|
||||
"parallel", self.jobs > 1
|
||||
)
|
||||
parallel = kwargs.pop("parallel", True)
|
||||
jobs_env = kwargs.pop("jobs_env", None)
|
||||
jobs_env_supports_jobserver = kwargs.pop("jobs_env_supports_jobserver", False)
|
||||
|
||||
if parallel:
|
||||
args = ("-j{0}".format(self.jobs),) + args
|
||||
jobs_env = kwargs.pop("jobs_env", None)
|
||||
if jobs_env:
|
||||
# Caller wants us to set an environment variable to
|
||||
# control the parallelism.
|
||||
kwargs["extra_env"] = {jobs_env: str(self.jobs)}
|
||||
jobs = get_effective_jobs(
|
||||
self.jobs, parallel=parallel, supports_jobserver=self.supports_jobserver
|
||||
)
|
||||
if jobs is not None:
|
||||
args = ("-j{0}".format(jobs),) + args
|
||||
|
||||
if jobs_env:
|
||||
# Caller wants us to set an environment variable to
|
||||
# control the parallelism.
|
||||
jobs_env_jobs = get_effective_jobs(
|
||||
self.jobs, parallel=parallel, supports_jobserver=jobs_env_supports_jobserver
|
||||
)
|
||||
if jobs_env_jobs is not None:
|
||||
kwargs["extra_env"] = {jobs_env: str(jobs_env_jobs)}
|
||||
|
||||
return super(MakeExecutable, self).__call__(*args, **kwargs)
|
||||
|
||||
@ -201,6 +209,8 @@ def clean_environment():
|
||||
|
||||
env.unset("CMAKE_PREFIX_PATH")
|
||||
env.unset("PYTHONPATH")
|
||||
env.unset("R_HOME")
|
||||
env.unset("R_ENVIRON")
|
||||
|
||||
# Affects GNU make, can e.g. indirectly inhibit enabling parallel build
|
||||
# env.unset('MAKEFLAGS')
|
||||
@ -314,7 +324,7 @@ def set_compiler_environment_variables(pkg, env):
|
||||
env.set("SPACK_LINKER_ARG", compiler.linker_arg)
|
||||
|
||||
# Check whether we want to force RPATH or RUNPATH
|
||||
if spack.config.get("config:shared_linking") == "rpath":
|
||||
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:
|
||||
@ -322,7 +332,11 @@ def set_compiler_environment_variables(pkg, env):
|
||||
env.set("SPACK_DTAGS_TO_ADD", compiler.enable_new_dtags)
|
||||
|
||||
# Set the target parameters that the compiler will add
|
||||
isa_arg = spec.architecture.target.optimization_flags(compiler)
|
||||
# Don't set on cray platform because the targeting module handles this
|
||||
if spec.satisfies("platform=cray"):
|
||||
isa_arg = ""
|
||||
else:
|
||||
isa_arg = spec.architecture.target.optimization_flags(compiler)
|
||||
env.set("SPACK_TARGET_ARGS", isa_arg)
|
||||
|
||||
# Trap spack-tracked compiler flags as appropriate.
|
||||
@ -343,7 +357,7 @@ def set_compiler_environment_variables(pkg, env):
|
||||
handler = pkg.flag_handler.__func__
|
||||
else:
|
||||
handler = pkg.flag_handler.im_func
|
||||
injf, envf, bsf = handler(pkg, flag, spec.compiler_flags[flag])
|
||||
injf, envf, bsf = handler(pkg, flag, spec.compiler_flags[flag][:])
|
||||
inject_flags[flag] = injf or []
|
||||
env_flags[flag] = envf or []
|
||||
build_system_flags[flag] = bsf or []
|
||||
@ -544,7 +558,7 @@ def _set_variables_for_single_module(pkg, module):
|
||||
# TODO: make these build deps that can be installed if not found.
|
||||
m.make = MakeExecutable("make", jobs)
|
||||
m.gmake = MakeExecutable("gmake", jobs)
|
||||
m.ninja = MakeExecutable("ninja", jobs)
|
||||
m.ninja = MakeExecutable("ninja", jobs, supports_jobserver=False)
|
||||
|
||||
# easy shortcut to os.environ
|
||||
m.env = os.environ
|
||||
@ -556,9 +570,9 @@ def _set_variables_for_single_module(pkg, module):
|
||||
if sys.platform == "win32":
|
||||
m.nmake = Executable("nmake")
|
||||
# Standard CMake arguments
|
||||
m.std_cmake_args = spack.build_systems.cmake.CMakePackage._std_args(pkg)
|
||||
m.std_meson_args = spack.build_systems.meson.MesonPackage._std_args(pkg)
|
||||
m.std_pip_args = spack.build_systems.python.PythonPackage._std_args(pkg)
|
||||
m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg)
|
||||
m.std_meson_args = spack.build_systems.meson.MesonBuilder.std_args(pkg)
|
||||
m.std_pip_args = spack.build_systems.python.PythonPipBuilder.std_args(pkg)
|
||||
|
||||
# Put spack compiler paths in module scope.
|
||||
link_dir = spack.paths.build_env_path
|
||||
@ -725,38 +739,6 @@ def get_rpaths(pkg):
|
||||
return list(dedupe(filter_system_paths(rpaths)))
|
||||
|
||||
|
||||
def get_std_cmake_args(pkg):
|
||||
"""List of standard arguments used if a package is a CMakePackage.
|
||||
|
||||
Returns:
|
||||
list: standard arguments that would be used if this
|
||||
package were a CMakePackage instance.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package under consideration
|
||||
|
||||
Returns:
|
||||
list: arguments for cmake
|
||||
"""
|
||||
return spack.build_systems.cmake.CMakePackage._std_args(pkg)
|
||||
|
||||
|
||||
def get_std_meson_args(pkg):
|
||||
"""List of standard arguments used if a package is a MesonPackage.
|
||||
|
||||
Returns:
|
||||
list: standard arguments that would be used if this
|
||||
package were a MesonPackage instance.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package under consideration
|
||||
|
||||
Returns:
|
||||
list: arguments for meson
|
||||
"""
|
||||
return spack.build_systems.meson.MesonPackage._std_args(pkg)
|
||||
|
||||
|
||||
def parent_class_modules(cls):
|
||||
"""
|
||||
Get list of superclass modules that descend from spack.package_base.PackageBase
|
||||
@ -817,7 +799,8 @@ def setup_package(pkg, dirty, context="build"):
|
||||
platform.setup_platform_environment(pkg, env_mods)
|
||||
|
||||
if context == "build":
|
||||
pkg.setup_build_environment(env_mods)
|
||||
builder = spack.builder.create(pkg)
|
||||
builder.setup_build_environment(env_mods)
|
||||
|
||||
if (not dirty) and (not env_mods.is_unset("CPATH")):
|
||||
tty.debug(
|
||||
@ -1013,7 +996,8 @@ def add_modifications_for_dep(dep):
|
||||
module.__dict__.update(changes.__dict__)
|
||||
|
||||
if context == "build":
|
||||
dpkg.setup_dependent_build_environment(env, spec)
|
||||
builder = spack.builder.create(dpkg)
|
||||
builder.setup_dependent_build_environment(env, spec)
|
||||
else:
|
||||
dpkg.setup_dependent_run_environment(env, spec)
|
||||
|
||||
@ -1115,8 +1099,20 @@ def _setup_pkg_and_run(
|
||||
pkg.test_suite.stage, spack.install_test.TestSuite.test_log_name(pkg.spec)
|
||||
)
|
||||
|
||||
error_msg = str(exc)
|
||||
if isinstance(exc, (spack.multimethod.NoSuchMethodError, AttributeError)):
|
||||
error_msg = (
|
||||
"The '{}' package cannot find an attribute while trying to build "
|
||||
"from sources. This might be due to a change in Spack's package format "
|
||||
"to support multiple build-systems for a single package. You can fix this "
|
||||
"by updating the build recipe, and you can also report the issue as a bug. "
|
||||
"More information at https://spack.readthedocs.io/en/latest/packaging_guide.html#installation-procedure"
|
||||
).format(pkg.name)
|
||||
error_msg = colorize("@*R{{{}}}".format(error_msg))
|
||||
error_msg = "{}\n\n{}".format(str(exc), error_msg)
|
||||
|
||||
# make a pickleable exception to send to parent.
|
||||
msg = "%s: %s" % (exc_type.__name__, str(exc))
|
||||
msg = "%s: %s" % (exc_type.__name__, error_msg)
|
||||
|
||||
ce = ChildError(
|
||||
msg,
|
||||
|
124
lib/spack/spack/build_systems/_checks.py
Normal file
124
lib/spack/spack/build_systems/_checks.py
Normal file
@ -0,0 +1,124 @@
|
||||
# Copyright 2013-2022 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 six
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
import spack.builder
|
||||
import spack.installer
|
||||
import spack.relocate
|
||||
import spack.store
|
||||
|
||||
|
||||
def sanity_check_prefix(builder):
|
||||
"""Check that specific directories and files are created after installation.
|
||||
|
||||
The files to be checked are in the ``sanity_check_is_file`` attribute of the
|
||||
package object, while the directories are in the ``sanity_check_is_dir``.
|
||||
|
||||
Args:
|
||||
builder (spack.builder.Builder): builder that installed the package
|
||||
"""
|
||||
pkg = builder.pkg
|
||||
|
||||
def check_paths(path_list, filetype, predicate):
|
||||
if isinstance(path_list, six.string_types):
|
||||
path_list = [path_list]
|
||||
|
||||
for path in path_list:
|
||||
abs_path = os.path.join(pkg.prefix, path)
|
||||
if not predicate(abs_path):
|
||||
msg = "Install failed for {0}. No such {1} in prefix: {2}"
|
||||
msg = msg.format(pkg.name, filetype, path)
|
||||
raise spack.installer.InstallError(msg)
|
||||
|
||||
check_paths(pkg.sanity_check_is_file, "file", os.path.isfile)
|
||||
check_paths(pkg.sanity_check_is_dir, "directory", os.path.isdir)
|
||||
|
||||
ignore_file = llnl.util.lang.match_predicate(spack.store.layout.hidden_file_regexes)
|
||||
if all(map(ignore_file, os.listdir(pkg.prefix))):
|
||||
msg = "Install failed for {0}. Nothing was installed!"
|
||||
raise spack.installer.InstallError(msg.format(pkg.name))
|
||||
|
||||
|
||||
def apply_macos_rpath_fixups(builder):
|
||||
"""On Darwin, make installed libraries more easily relocatable.
|
||||
|
||||
Some build systems (handrolled, autotools, makefiles) can set their own
|
||||
rpaths that are duplicated by spack's compiler wrapper. This fixup
|
||||
interrogates, and postprocesses if necessary, all libraries installed
|
||||
by the code.
|
||||
|
||||
It should be added as a @run_after to packaging systems (or individual
|
||||
packages) that do not install relocatable libraries by default.
|
||||
|
||||
Args:
|
||||
builder (spack.builder.Builder): builder that installed the package
|
||||
"""
|
||||
spack.relocate.fixup_macos_rpaths(builder.spec)
|
||||
|
||||
|
||||
def ensure_build_dependencies_or_raise(spec, dependencies, error_msg):
|
||||
"""Ensure that some build dependencies are present in the concrete spec.
|
||||
|
||||
If not, raise a RuntimeError with a helpful error message.
|
||||
|
||||
Args:
|
||||
spec (spack.spec.Spec): concrete spec to be checked.
|
||||
dependencies (list of spack.spec.Spec): list of abstract specs to be satisfied
|
||||
error_msg (str): brief error message to be prepended to a longer description
|
||||
|
||||
Raises:
|
||||
RuntimeError: when the required build dependencies are not found
|
||||
"""
|
||||
assert spec.concrete, "Can ensure build dependencies only on concrete specs"
|
||||
build_deps = [d.name for d in spec.dependencies(deptype="build")]
|
||||
missing_deps = [x for x in dependencies if x not in build_deps]
|
||||
|
||||
if not missing_deps:
|
||||
return
|
||||
|
||||
# Raise an exception on missing deps.
|
||||
msg = (
|
||||
"{0}: missing dependencies: {1}.\n\nPlease add "
|
||||
"the following lines to the package:\n\n".format(error_msg, ", ".join(missing_deps))
|
||||
)
|
||||
|
||||
for dep in missing_deps:
|
||||
msg += " depends_on('{0}', type='build', when='@{1} {2}')\n".format(
|
||||
dep, spec.version, "build_system=autotools"
|
||||
)
|
||||
|
||||
msg += "\nUpdate the version (when='@{0}') as needed.".format(spec.version)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
def execute_build_time_tests(builder):
|
||||
"""Execute the build-time tests prescribed by builder.
|
||||
|
||||
Args:
|
||||
builder (Builder): builder prescribing the test callbacks. The name of the callbacks is
|
||||
stored as a list of strings in the ``build_time_test_callbacks`` attribute.
|
||||
"""
|
||||
builder.pkg.run_test_callbacks(builder, builder.build_time_test_callbacks, "build")
|
||||
|
||||
|
||||
def execute_install_time_tests(builder):
|
||||
"""Execute the install-time tests prescribed by builder.
|
||||
|
||||
Args:
|
||||
builder (Builder): builder prescribing the test callbacks. The name of the callbacks is
|
||||
stored as a list of strings in the ``install_time_test_callbacks`` attribute.
|
||||
"""
|
||||
builder.pkg.run_test_callbacks(builder, builder.install_time_test_callbacks, "install")
|
||||
|
||||
|
||||
class BaseBuilder(spack.builder.Builder):
|
||||
"""Base class for builders to register common checks"""
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
spack.builder.run_after("install")(sanity_check_prefix)
|
@ -2,18 +2,36 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
# Why doesn't this work for me?
|
||||
# from spack import *
|
||||
from llnl.util.filesystem import filter_file
|
||||
import spack.directives
|
||||
import spack.package_base
|
||||
import spack.util.executable
|
||||
|
||||
from spack.build_systems.autotools import AutotoolsPackage
|
||||
from spack.directives import extends
|
||||
from spack.package_base import ExtensionError
|
||||
from spack.util.executable import which
|
||||
from .autotools import AutotoolsBuilder, AutotoolsPackage
|
||||
|
||||
|
||||
class AspellBuilder(AutotoolsBuilder):
|
||||
"""The Aspell builder is close enough to an autotools builder to allow
|
||||
specializing the builder class, so to use variables that are specific
|
||||
to the Aspell extensions.
|
||||
"""
|
||||
|
||||
def configure(self, pkg, spec, prefix):
|
||||
aspell = spec["aspell"].prefix.bin.aspell
|
||||
prezip = spec["aspell"].prefix.bin.prezip
|
||||
destdir = prefix
|
||||
|
||||
sh = spack.util.executable.which("sh")
|
||||
sh(
|
||||
"./configure",
|
||||
"--vars",
|
||||
"ASPELL={0}".format(aspell),
|
||||
"PREZIP={0}".format(prezip),
|
||||
"DESTDIR={0}".format(destdir),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Aspell dictionaries install their bits into their prefix.lib
|
||||
# and when activated they'll get symlinked into the appropriate aspell's
|
||||
# dict dir (see aspell's {de,}activate methods).
|
||||
@ -23,12 +41,17 @@
|
||||
class AspellDictPackage(AutotoolsPackage):
|
||||
"""Specialized class for building aspell dictionairies."""
|
||||
|
||||
extends("aspell")
|
||||
spack.directives.extends("aspell", when="build_system=autotools")
|
||||
|
||||
#: Override the default autotools builder
|
||||
AutotoolsBuilder = AspellBuilder
|
||||
|
||||
def view_destination(self, view):
|
||||
aspell_spec = self.spec["aspell"]
|
||||
if view.get_projection_for_spec(aspell_spec) != aspell_spec.prefix:
|
||||
raise ExtensionError("aspell does not support non-global extensions")
|
||||
raise spack.package_base.ExtensionError(
|
||||
"aspell does not support non-global extensions"
|
||||
)
|
||||
aspell = aspell_spec.command
|
||||
return aspell("dump", "config", "dict-dir", output=str).strip()
|
||||
|
||||
@ -36,19 +59,5 @@ def view_source(self):
|
||||
return self.prefix.lib
|
||||
|
||||
def patch(self):
|
||||
filter_file(r"^dictdir=.*$", "dictdir=/lib", "configure")
|
||||
filter_file(r"^datadir=.*$", "datadir=/lib", "configure")
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
aspell = spec["aspell"].prefix.bin.aspell
|
||||
prezip = spec["aspell"].prefix.bin.prezip
|
||||
destdir = prefix
|
||||
|
||||
sh = which("sh")
|
||||
sh(
|
||||
"./configure",
|
||||
"--vars",
|
||||
"ASPELL={0}".format(aspell),
|
||||
"PREZIP={0}".format(prezip),
|
||||
"DESTDIR={0}".format(destdir),
|
||||
)
|
||||
fs.filter_file(r"^dictdir=.*$", "dictdir=/lib", "configure")
|
||||
fs.filter_file(r"^datadir=.*$", "datadir=/lib", "configure")
|
||||
|
@ -6,87 +6,140 @@
|
||||
import os
|
||||
import os.path
|
||||
import stat
|
||||
from subprocess import PIPE, check_call
|
||||
import subprocess
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import force_remove, working_dir
|
||||
|
||||
from spack.build_environment import InstallError
|
||||
from spack.directives import conflicts, depends_on
|
||||
import spack.build_environment
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, conflicts, depends_on
|
||||
from spack.multimethod import when
|
||||
from spack.operating_systems.mac_os import macos_version
|
||||
from spack.package_base import PackageBase, run_after, run_before
|
||||
from spack.util.executable import Executable
|
||||
from spack.version import Version
|
||||
|
||||
from ._checks import (
|
||||
BaseBuilder,
|
||||
apply_macos_rpath_fixups,
|
||||
ensure_build_dependencies_or_raise,
|
||||
execute_build_time_tests,
|
||||
execute_install_time_tests,
|
||||
)
|
||||
|
||||
class AutotoolsPackage(PackageBase):
|
||||
"""Specialized class for packages built using GNU Autotools.
|
||||
|
||||
This class provides four phases that can be overridden:
|
||||
class AutotoolsPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using GNU Autotools."""
|
||||
|
||||
1. :py:meth:`~.AutotoolsPackage.autoreconf`
|
||||
2. :py:meth:`~.AutotoolsPackage.configure`
|
||||
3. :py:meth:`~.AutotoolsPackage.build`
|
||||
4. :py:meth:`~.AutotoolsPackage.install`
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "AutotoolsPackage"
|
||||
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "autotools"
|
||||
|
||||
build_system("autotools")
|
||||
|
||||
with when("build_system=autotools"):
|
||||
depends_on("gnuconfig", type="build", when="target=ppc64le:")
|
||||
depends_on("gnuconfig", type="build", when="target=aarch64:")
|
||||
depends_on("gnuconfig", type="build", when="target=riscv64:")
|
||||
conflicts("platform=windows")
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass specified
|
||||
compiler flags to configure."""
|
||||
# Has to be dynamic attribute due to caching.
|
||||
setattr(self, "configure_flag_args", [])
|
||||
for flag, values in flags.items():
|
||||
if values:
|
||||
values_str = "{0}={1}".format(flag.upper(), " ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
# Spack's fflags are meant for both F77 and FC, therefore we
|
||||
# additionaly set FCFLAGS if required.
|
||||
values = flags.get("fflags", None)
|
||||
if values:
|
||||
values_str = "FCFLAGS={0}".format(" ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
|
||||
# Legacy methods (used by too many packages to change them,
|
||||
# need to forward to the builder)
|
||||
def enable_or_disable(self, *args, **kwargs):
|
||||
return self.builder.enable_or_disable(*args, **kwargs)
|
||||
|
||||
def with_or_without(self, *args, **kwargs):
|
||||
return self.builder.with_or_without(*args, **kwargs)
|
||||
|
||||
|
||||
@spack.builder.builder("autotools")
|
||||
class AutotoolsBuilder(BaseBuilder):
|
||||
"""The autotools builder encodes the default way of installing software built
|
||||
with autotools. It has four phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.AutotoolsBuilder.autoreconf`
|
||||
2. :py:meth:`~.AutotoolsBuilder.configure`
|
||||
3. :py:meth:`~.AutotoolsBuilder.build`
|
||||
4. :py:meth:`~.AutotoolsBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing necessary
|
||||
is to override the helper method
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.configure_args`.
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override the helper method
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args`.
|
||||
For a finer tuning you may also override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:attr:`~.AutotoolsPackage.build_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.AutotoolsBuilder.build_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | build phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:attr:`~.AutotoolsPackage.install_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.AutotoolsBuilder.install_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | install phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.AutotoolsPackage.check` | Run build time |
|
||||
| :py:meth:`~.AutotoolsBuilder.check` | Run build time |
|
||||
| | tests if required |
|
||||
+-----------------------------------------------+--------------------+
|
||||
|
||||
"""
|
||||
|
||||
#: Phases of a GNU Autotools package
|
||||
phases = ["autoreconf", "configure", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "AutotoolsPackage"
|
||||
phases = ("autoreconf", "configure", "build", "install")
|
||||
|
||||
@property
|
||||
def patch_config_files(self):
|
||||
"""
|
||||
Whether or not to update old ``config.guess`` and ``config.sub`` files
|
||||
distributed with the tarball. This currently only applies to
|
||||
``ppc64le:``, ``aarch64:``, and ``riscv64`` target architectures. The
|
||||
substitutes are taken from the ``gnuconfig`` package, which is
|
||||
automatically added as a build dependency for these architectures. In
|
||||
case system versions of these config files are required, the
|
||||
``gnuconfig`` package can be marked external with a prefix pointing to
|
||||
the directory containing the system ``config.guess`` and ``config.sub``
|
||||
files.
|
||||
"""
|
||||
return (
|
||||
self.spec.satisfies("target=ppc64le:")
|
||||
or self.spec.satisfies("target=aarch64:")
|
||||
or self.spec.satisfies("target=riscv64:")
|
||||
)
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = (
|
||||
"configure_args",
|
||||
"check",
|
||||
"installcheck",
|
||||
)
|
||||
|
||||
#: Whether or not to update ``libtool``
|
||||
#: (currently only for Arm/Clang/Fujitsu/NVHPC compilers)
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"archive_files",
|
||||
"patch_libtool",
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"force_autoreconf",
|
||||
"autoreconf_extra_args",
|
||||
"install_libtool_archives",
|
||||
"patch_config_files",
|
||||
"configure_directory",
|
||||
"configure_abs_path",
|
||||
"build_directory",
|
||||
"autoreconf_search_path_args",
|
||||
)
|
||||
|
||||
#: Whether to update ``libtool`` (e.g. for Arm/Clang/Fujitsu/NVHPC compilers)
|
||||
patch_libtool = True
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.build`
|
||||
#: phase
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsBuilder.build` phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.install`
|
||||
#: phase
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsBuilder.install` phase
|
||||
install_targets = ["install"]
|
||||
|
||||
#: Callback names for build-time test
|
||||
@ -97,24 +150,40 @@ def patch_config_files(self):
|
||||
|
||||
#: Set to true to force the autoreconf step even if configure is present
|
||||
force_autoreconf = False
|
||||
|
||||
#: Options to be passed to autoreconf when using the default implementation
|
||||
autoreconf_extra_args = [] # type: List[str]
|
||||
|
||||
#: If False deletes all the .la files in the prefix folder
|
||||
#: after the installation. If True instead it installs them.
|
||||
#: If False deletes all the .la files in the prefix folder after the installation.
|
||||
#: If True instead it installs them.
|
||||
install_libtool_archives = False
|
||||
|
||||
depends_on("gnuconfig", type="build", when="target=ppc64le:")
|
||||
depends_on("gnuconfig", type="build", when="target=aarch64:")
|
||||
depends_on("gnuconfig", type="build", when="target=riscv64:")
|
||||
conflicts("platform=windows")
|
||||
@property
|
||||
def patch_config_files(self):
|
||||
"""Whether to update old ``config.guess`` and ``config.sub`` files
|
||||
distributed with the tarball.
|
||||
|
||||
This currently only applies to ``ppc64le:``, ``aarch64:``, and
|
||||
``riscv64`` target architectures.
|
||||
|
||||
The substitutes are taken from the ``gnuconfig`` package, which is
|
||||
automatically added as a build dependency for these architectures. In case
|
||||
system versions of these config files are required, the ``gnuconfig`` package
|
||||
can be marked external, with a prefix pointing to the directory containing the
|
||||
system ``config.guess`` and ``config.sub`` files.
|
||||
"""
|
||||
return (
|
||||
self.pkg.spec.satisfies("target=ppc64le:")
|
||||
or self.pkg.spec.satisfies("target=aarch64:")
|
||||
or self.pkg.spec.satisfies("target=riscv64:")
|
||||
)
|
||||
|
||||
@property
|
||||
def _removed_la_files_log(self):
|
||||
"""File containing the list of remove libtool archives"""
|
||||
"""File containing the list of removed libtool archives"""
|
||||
build_dir = self.build_directory
|
||||
if not os.path.isabs(self.build_directory):
|
||||
build_dir = os.path.join(self.stage.path, build_dir)
|
||||
build_dir = os.path.join(self.pkg.stage.path, build_dir)
|
||||
return os.path.join(build_dir, "removed_la_files.txt")
|
||||
|
||||
@property
|
||||
@ -125,13 +194,13 @@ def archive_files(self):
|
||||
files.append(self._removed_la_files_log)
|
||||
return files
|
||||
|
||||
@run_after("autoreconf")
|
||||
@spack.builder.run_after("autoreconf")
|
||||
def _do_patch_config_files(self):
|
||||
"""Some packages ship with older config.guess/config.sub files and
|
||||
need to have these updated when installed on a newer architecture.
|
||||
In particular, config.guess fails for PPC64LE for version prior
|
||||
to a 2013-06-10 build date (automake 1.13.4) and for ARM (aarch64) and
|
||||
RISC-V (riscv64).
|
||||
"""Some packages ship with older config.guess/config.sub files and need to
|
||||
have these updated when installed on a newer architecture.
|
||||
|
||||
In particular, config.guess fails for PPC64LE for version prior to a
|
||||
2013-06-10 build date (automake 1.13.4) and for AArch64 and RISC-V.
|
||||
"""
|
||||
if not self.patch_config_files:
|
||||
return
|
||||
@ -139,11 +208,11 @@ def _do_patch_config_files(self):
|
||||
# TODO: Expand this to select the 'config.sub'-compatible architecture
|
||||
# for each platform (e.g. 'config.sub' doesn't accept 'power9le', but
|
||||
# does accept 'ppc64le').
|
||||
if self.spec.satisfies("target=ppc64le:"):
|
||||
if self.pkg.spec.satisfies("target=ppc64le:"):
|
||||
config_arch = "ppc64le"
|
||||
elif self.spec.satisfies("target=aarch64:"):
|
||||
elif self.pkg.spec.satisfies("target=aarch64:"):
|
||||
config_arch = "aarch64"
|
||||
elif self.spec.satisfies("target=riscv64:"):
|
||||
elif self.pkg.spec.satisfies("target=riscv64:"):
|
||||
config_arch = "riscv64"
|
||||
else:
|
||||
config_arch = "local"
|
||||
@ -155,7 +224,7 @@ def runs_ok(script_abs_path):
|
||||
args = [script_abs_path] + additional_args.get(script_name, [])
|
||||
|
||||
try:
|
||||
check_call(args, stdout=PIPE, stderr=PIPE)
|
||||
subprocess.check_call(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except Exception as e:
|
||||
tty.debug(e)
|
||||
return False
|
||||
@ -163,7 +232,7 @@ def runs_ok(script_abs_path):
|
||||
return True
|
||||
|
||||
# Get the list of files that needs to be patched
|
||||
to_be_patched = fs.find(self.stage.path, files=["config.sub", "config.guess"])
|
||||
to_be_patched = fs.find(self.pkg.stage.path, files=["config.sub", "config.guess"])
|
||||
to_be_patched = [f for f in to_be_patched if not runs_ok(f)]
|
||||
|
||||
# If there are no files to be patched, return early
|
||||
@ -171,22 +240,21 @@ def runs_ok(script_abs_path):
|
||||
return
|
||||
|
||||
# Otherwise, require `gnuconfig` to be a build dependency
|
||||
self._require_build_deps(
|
||||
pkgs=["gnuconfig"], spec=self.spec, err="Cannot patch config files"
|
||||
ensure_build_dependencies_or_raise(
|
||||
spec=self.pkg.spec, dependencies=["gnuconfig"], error_msg="Cannot patch config files"
|
||||
)
|
||||
|
||||
# Get the config files we need to patch (config.sub / config.guess).
|
||||
to_be_found = list(set(os.path.basename(f) for f in to_be_patched))
|
||||
gnuconfig = self.spec["gnuconfig"]
|
||||
gnuconfig = self.pkg.spec["gnuconfig"]
|
||||
gnuconfig_dir = gnuconfig.prefix
|
||||
|
||||
# An external gnuconfig may not not have a prefix.
|
||||
if gnuconfig_dir is None:
|
||||
raise InstallError(
|
||||
"Spack could not find substitutes for GNU config "
|
||||
"files because no prefix is available for the "
|
||||
"`gnuconfig` package. Make sure you set a prefix "
|
||||
"path instead of modules for external `gnuconfig`."
|
||||
raise spack.build_environment.InstallError(
|
||||
"Spack could not find substitutes for GNU config files because no "
|
||||
"prefix is available for the `gnuconfig` package. Make sure you set a "
|
||||
"prefix path instead of modules for external `gnuconfig`."
|
||||
)
|
||||
|
||||
candidates = fs.find(gnuconfig_dir, files=to_be_found, recursive=False)
|
||||
@ -203,7 +271,7 @@ def runs_ok(script_abs_path):
|
||||
msg += (
|
||||
" or the `gnuconfig` package prefix is misconfigured as" " an external package"
|
||||
)
|
||||
raise InstallError(msg)
|
||||
raise spack.build_environment.InstallError(msg)
|
||||
|
||||
# Filter working substitutes
|
||||
candidates = [f for f in candidates if runs_ok(f)]
|
||||
@ -228,7 +296,9 @@ def runs_ok(script_abs_path):
|
||||
and set the prefix to the directory containing the `config.guess` and
|
||||
`config.sub` files.
|
||||
"""
|
||||
raise InstallError(msg.format(", ".join(to_be_found), self.name))
|
||||
raise spack.build_environment.InstallError(
|
||||
msg.format(", ".join(to_be_found), self.name)
|
||||
)
|
||||
|
||||
# Copy the good files over the bad ones
|
||||
for abs_path in to_be_patched:
|
||||
@ -238,7 +308,7 @@ def runs_ok(script_abs_path):
|
||||
fs.copy(substitutes[name], abs_path)
|
||||
os.chmod(abs_path, mode)
|
||||
|
||||
@run_before("configure")
|
||||
@spack.builder.run_before("configure")
|
||||
def _patch_usr_bin_file(self):
|
||||
"""On NixOS file is not available in /usr/bin/file. Patch configure
|
||||
scripts to use file from path."""
|
||||
@ -250,7 +320,7 @@ def _patch_usr_bin_file(self):
|
||||
with fs.keep_modification_time(*x.filenames):
|
||||
x.filter(regex="/usr/bin/file", repl="file", string=True)
|
||||
|
||||
@run_before("configure")
|
||||
@spack.builder.run_before("configure")
|
||||
def _set_autotools_environment_variables(self):
|
||||
"""Many autotools builds use a version of mknod.m4 that fails when
|
||||
running as root unless FORCE_UNSAFE_CONFIGURE is set to 1.
|
||||
@ -261,11 +331,10 @@ def _set_autotools_environment_variables(self):
|
||||
Without it, configure just fails halfway through, but it can
|
||||
still run things *before* this check. Forcing this just removes a
|
||||
nuisance -- this is not circumventing any real protection.
|
||||
|
||||
"""
|
||||
os.environ["FORCE_UNSAFE_CONFIGURE"] = "1"
|
||||
|
||||
@run_before("configure")
|
||||
@spack.builder.run_before("configure")
|
||||
def _do_patch_libtool_configure(self):
|
||||
"""Patch bugs that propagate from libtool macros into "configure" and
|
||||
further into "libtool". Note that patches that can be fixed by patching
|
||||
@ -293,7 +362,7 @@ def _do_patch_libtool_configure(self):
|
||||
# Support Libtool 2.4.2 and older:
|
||||
x.filter(regex=r'^(\s*test \$p = "-R")(; then\s*)$', repl=r'\1 || test x-l = x"$p"\2')
|
||||
|
||||
@run_after("configure")
|
||||
@spack.builder.run_after("configure")
|
||||
def _do_patch_libtool(self):
|
||||
"""If configure generates a "libtool" script that does not correctly
|
||||
detect the compiler (and patch_libtool is set), patch in the correct
|
||||
@ -328,31 +397,33 @@ def _do_patch_libtool(self):
|
||||
markers[tag] = "LIBTOOL TAG CONFIG: {0}".format(tag.upper())
|
||||
|
||||
# Replace empty linker flag prefixes:
|
||||
if self.compiler.name == "nag":
|
||||
if self.pkg.compiler.name == "nag":
|
||||
# Nag is mixed with gcc and g++, which are recognized correctly.
|
||||
# Therefore, we change only Fortran values:
|
||||
for tag in ["fc", "f77"]:
|
||||
marker = markers[tag]
|
||||
x.filter(
|
||||
regex='^wl=""$',
|
||||
repl='wl="{0}"'.format(self.compiler.linker_arg),
|
||||
repl='wl="{0}"'.format(self.pkg.compiler.linker_arg),
|
||||
start_at="# ### BEGIN {0}".format(marker),
|
||||
stop_at="# ### END {0}".format(marker),
|
||||
)
|
||||
else:
|
||||
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.compiler.linker_arg))
|
||||
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.pkg.compiler.linker_arg))
|
||||
|
||||
# Replace empty PIC flag values:
|
||||
for cc, marker in markers.items():
|
||||
x.filter(
|
||||
regex='^pic_flag=""$',
|
||||
repl='pic_flag="{0}"'.format(getattr(self.compiler, "{0}_pic_flag".format(cc))),
|
||||
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.compiler.name == "fj":
|
||||
if self.pkg.compiler.name == "fj":
|
||||
x.filter(regex="-nostdlib", repl="", string=True)
|
||||
rehead = r"/\S*/"
|
||||
for o in [
|
||||
@ -365,12 +436,12 @@ def _do_patch_libtool(self):
|
||||
"crtendS.o",
|
||||
]:
|
||||
x.filter(regex=(rehead + o), repl="", string=True)
|
||||
elif self.compiler.name == "dpcpp":
|
||||
elif self.pkg.compiler.name == "dpcpp":
|
||||
# Hack to filter out spurious predep_objects when building with Intel dpcpp
|
||||
# (see https://github.com/spack/spack/issues/32863):
|
||||
x.filter(regex=r"^(predep_objects=.*)/tmp/conftest-[0-9A-Fa-f]+\.o", repl=r"\1")
|
||||
x.filter(regex=r"^(predep_objects=.*)/tmp/a-[0-9A-Fa-f]+\.o", repl=r"\1")
|
||||
elif self.compiler.name == "nag":
|
||||
elif self.pkg.compiler.name == "nag":
|
||||
for tag in ["fc", "f77"]:
|
||||
marker = markers[tag]
|
||||
start_at = "# ### BEGIN {0}".format(marker)
|
||||
@ -446,11 +517,8 @@ def _do_patch_libtool(self):
|
||||
|
||||
@property
|
||||
def configure_directory(self):
|
||||
"""Returns the directory where 'configure' resides.
|
||||
|
||||
:return: directory where to find configure
|
||||
"""
|
||||
return self.stage.source_path
|
||||
"""Return the directory where 'configure' resides."""
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
@property
|
||||
def configure_abs_path(self):
|
||||
@ -463,34 +531,12 @@ def build_directory(self):
|
||||
"""Override to provide another place to build the package"""
|
||||
return self.configure_directory
|
||||
|
||||
@run_before("autoreconf")
|
||||
@spack.builder.run_before("autoreconf")
|
||||
def delete_configure_to_force_update(self):
|
||||
if self.force_autoreconf:
|
||||
force_remove(self.configure_abs_path)
|
||||
fs.force_remove(self.configure_abs_path)
|
||||
|
||||
def _require_build_deps(self, pkgs, spec, err):
|
||||
"""Require `pkgs` to be direct build dependencies of `spec`. Raises a
|
||||
RuntimeError with a helpful error messages when any dep is missing."""
|
||||
|
||||
build_deps = [d.name for d in spec.dependencies(deptype="build")]
|
||||
missing_deps = [x for x in pkgs if x not in build_deps]
|
||||
|
||||
if not missing_deps:
|
||||
return
|
||||
|
||||
# Raise an exception on missing deps.
|
||||
msg = (
|
||||
"{0}: missing dependencies: {1}.\n\nPlease add "
|
||||
"the following lines to the package:\n\n".format(err, ", ".join(missing_deps))
|
||||
)
|
||||
|
||||
for dep in missing_deps:
|
||||
msg += " depends_on('{0}', type='build', when='@{1}')\n".format(dep, spec.version)
|
||||
|
||||
msg += "\nUpdate the version (when='@{0}') as needed.".format(spec.version)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def autoreconf(self, spec, prefix):
|
||||
def autoreconf(self, pkg, spec, prefix):
|
||||
"""Not needed usually, configure should be already there"""
|
||||
|
||||
# If configure exists nothing needs to be done
|
||||
@ -498,8 +544,10 @@ def autoreconf(self, spec, prefix):
|
||||
return
|
||||
|
||||
# Else try to regenerate it, which reuquires a few build dependencies
|
||||
self._require_build_deps(
|
||||
pkgs=["autoconf", "automake", "libtool"], spec=spec, err="Cannot generate configure"
|
||||
ensure_build_dependencies_or_raise(
|
||||
spec=spec,
|
||||
dependencies=["autoconf", "automake", "libtool"],
|
||||
error_msg="Cannot generate configure",
|
||||
)
|
||||
|
||||
tty.msg("Configure script not found: trying to generate it")
|
||||
@ -507,8 +555,8 @@ def autoreconf(self, spec, prefix):
|
||||
tty.warn("* If the default procedure fails, consider implementing *")
|
||||
tty.warn("* a custom AUTORECONF phase in the package *")
|
||||
tty.warn("*********************************************************")
|
||||
with working_dir(self.configure_directory):
|
||||
m = inspect.getmodule(self)
|
||||
with fs.working_dir(self.configure_directory):
|
||||
m = inspect.getmodule(self.pkg)
|
||||
# This line is what is needed most of the time
|
||||
# --install, --verbose, --force
|
||||
autoreconf_args = ["-ivf"]
|
||||
@ -524,98 +572,66 @@ def autoreconf_search_path_args(self):
|
||||
spack dependencies."""
|
||||
return _autoreconf_search_path_args(self.spec)
|
||||
|
||||
@run_after("autoreconf")
|
||||
@spack.builder.run_after("autoreconf")
|
||||
def set_configure_or_die(self):
|
||||
"""Checks the presence of a ``configure`` file after the
|
||||
autoreconf phase. If it is found sets a module attribute
|
||||
appropriately, otherwise raises an error.
|
||||
"""Ensure the presence of a "configure" script, or raise. If the "configure"
|
||||
is found, a module level attribute is set.
|
||||
|
||||
:raises RuntimeError: if a configure script is not found in
|
||||
:py:meth:`~AutotoolsPackage.configure_directory`
|
||||
Raises:
|
||||
RuntimeError: if the "configure" script is not found
|
||||
"""
|
||||
# Check if a configure script is there. If not raise a RuntimeError.
|
||||
# Check if the "configure" script is there. If not raise a RuntimeError.
|
||||
if not os.path.exists(self.configure_abs_path):
|
||||
msg = "configure script not found in {0}"
|
||||
raise RuntimeError(msg.format(self.configure_directory))
|
||||
|
||||
# Monkey-patch the configure script in the corresponding module
|
||||
inspect.getmodule(self).configure = Executable(self.configure_abs_path)
|
||||
inspect.getmodule(self.pkg).configure = Executable(self.configure_abs_path)
|
||||
|
||||
def configure_args(self):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
configure, except ``--prefix`` which will be pre-pended to the list.
|
||||
|
||||
:return: list of arguments for configure
|
||||
"""Return the list of all the arguments that must be passed to configure,
|
||||
except ``--prefix`` which will be pre-pended to the list.
|
||||
"""
|
||||
return []
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass specified
|
||||
compiler flags to configure."""
|
||||
# Has to be dynamic attribute due to caching.
|
||||
setattr(self, "configure_flag_args", [])
|
||||
for flag, values in flags.items():
|
||||
if values:
|
||||
values_str = "{0}={1}".format(flag.upper(), " ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
# Spack's fflags are meant for both F77 and FC, therefore we
|
||||
# additionaly set FCFLAGS if required.
|
||||
values = flags.get("fflags", None)
|
||||
if values:
|
||||
values_str = "FCFLAGS={0}".format(" ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
"""Runs configure with the arguments specified in
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args`
|
||||
and an appropriately set prefix.
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Run "configure", with the arguments specified by the builder and an
|
||||
appropriately set prefix.
|
||||
"""
|
||||
options = getattr(self, "configure_flag_args", [])
|
||||
options = getattr(self.pkg, "configure_flag_args", [])
|
||||
options += ["--prefix={0}".format(prefix)]
|
||||
options += self.configure_args()
|
||||
|
||||
with working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self).configure(*options)
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).configure(*options)
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
if self.spec.platform == "darwin" and macos_version() >= Version("11"):
|
||||
# Many configure files rely on matching '10.*' for macOS version
|
||||
# detection and fail to add flags if it shows as version 11.
|
||||
env.set("MACOSX_DEPLOYMENT_TARGET", "10.16")
|
||||
|
||||
def build(self, spec, prefix):
|
||||
"""Makes the build targets specified by
|
||||
:py:attr:``~.AutotoolsPackage.build_targets``
|
||||
"""
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
# See https://autotools.io/automake/silent.html
|
||||
params = ["V=1"]
|
||||
params += self.build_targets
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*params)
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*params)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Makes the install targets specified by
|
||||
:py:attr:``~.AutotoolsPackage.install_targets``
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*self.install_targets)
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the Makefile for targets ``test`` and ``check``
|
||||
and runs them if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("test")
|
||||
self._if_make_target_execute("check")
|
||||
"""Run "make" on the ``test`` and ``check`` targets, if found."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
self.pkg._if_make_target_execute("test")
|
||||
self.pkg._if_make_target_execute("check")
|
||||
|
||||
def _activate_or_not(
|
||||
self, name, activation_word, deactivation_word, activation_value=None, variant=None
|
||||
):
|
||||
"""This function contains the current implementation details of
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without` and
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.enable_or_disable`.
|
||||
"""This function contain the current implementation details of
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without` and
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.enable_or_disable`.
|
||||
|
||||
Args:
|
||||
name (str): name of the option that is being activated or not
|
||||
@ -671,7 +687,7 @@ def _activate_or_not(
|
||||
Raises:
|
||||
KeyError: if name is not among known variants
|
||||
"""
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
args = []
|
||||
|
||||
if activation_value == "prefix":
|
||||
@ -681,16 +697,16 @@ def _activate_or_not(
|
||||
|
||||
# Defensively look that the name passed as argument is among
|
||||
# variants
|
||||
if variant not in self.variants:
|
||||
if variant not in self.pkg.variants:
|
||||
msg = '"{0}" is not a variant of "{1}"'
|
||||
raise KeyError(msg.format(variant, self.name))
|
||||
raise KeyError(msg.format(variant, self.pkg.name))
|
||||
|
||||
if variant not in spec.variants:
|
||||
return []
|
||||
|
||||
# Create a list of pairs. Each pair includes a configuration
|
||||
# option and whether or not that option is activated
|
||||
variant_desc, _ = self.variants[variant]
|
||||
variant_desc, _ = self.pkg.variants[variant]
|
||||
if set(variant_desc.values) == set((True, False)):
|
||||
# BoolValuedVariant carry information about a single option.
|
||||
# Nonetheless, for uniformity of treatment we'll package them
|
||||
@ -718,14 +734,18 @@ def _activate_or_not(
|
||||
override_name = "{0}_or_{1}_{2}".format(
|
||||
activation_word, deactivation_word, option_value
|
||||
)
|
||||
line_generator = getattr(self, override_name, None)
|
||||
line_generator = getattr(self, override_name, None) or getattr(
|
||||
self.pkg, override_name, None
|
||||
)
|
||||
# If not available use a sensible default
|
||||
if line_generator is None:
|
||||
|
||||
def _default_generator(is_activated):
|
||||
if is_activated:
|
||||
line = "--{0}-{1}".format(activation_word, option_value)
|
||||
if activation_value is not None and activation_value(option_value):
|
||||
if activation_value is not None and activation_value(
|
||||
option_value
|
||||
): # NOQA=ignore=E501
|
||||
line += "={0}".format(activation_value(option_value))
|
||||
return line
|
||||
return "--{0}-{1}".format(deactivation_word, option_value)
|
||||
@ -764,7 +784,7 @@ def with_or_without(self, name, activation_value=None, variant=None):
|
||||
|
||||
def enable_or_disable(self, name, activation_value=None, variant=None):
|
||||
"""Same as
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without`
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without`
|
||||
but substitute ``with`` with ``enable`` and ``without`` with ``disable``.
|
||||
|
||||
Args:
|
||||
@ -781,19 +801,14 @@ def enable_or_disable(self, name, activation_value=None, variant=None):
|
||||
"""
|
||||
return self._activate_or_not(name, "enable", "disable", activation_value, variant)
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
def installcheck(self):
|
||||
"""Searches the Makefile for an ``installcheck`` target
|
||||
and runs it if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("installcheck")
|
||||
"""Run "make" on the ``installcheck`` target, if found."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
self.pkg._if_make_target_execute("installcheck")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def remove_libtool_archives(self):
|
||||
"""Remove all .la files in prefix sub-folders if the package sets
|
||||
``install_libtool_archives`` to be False.
|
||||
@ -803,14 +818,20 @@ def remove_libtool_archives(self):
|
||||
return
|
||||
|
||||
# Remove the files and create a log of what was removed
|
||||
libtool_files = fs.find(str(self.prefix), "*.la", recursive=True)
|
||||
libtool_files = fs.find(str(self.pkg.prefix), "*.la", recursive=True)
|
||||
with fs.safe_remove(*libtool_files):
|
||||
fs.mkdirp(os.path.dirname(self._removed_la_files_log))
|
||||
with open(self._removed_la_files_log, mode="w") as f:
|
||||
f.write("\n".join(libtool_files))
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
if self.spec.platform == "darwin" and macos_version() >= Version("11"):
|
||||
# Many configure files rely on matching '10.*' for macOS version
|
||||
# detection and fail to add flags if it shows as version 11.
|
||||
env.set("MACOSX_DEPLOYMENT_TARGET", "10.16")
|
||||
|
||||
# On macOS, force rpaths for shared library IDs and remove duplicate rpaths
|
||||
run_after("install")(PackageBase.apply_macos_rpath_fixups)
|
||||
spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups)
|
||||
|
||||
|
||||
def _autoreconf_search_path_args(spec):
|
||||
|
31
lib/spack/spack/build_systems/bundle.py
Normal file
31
lib/spack/spack/build_systems/bundle.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright 2013-2022 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 spack.builder
|
||||
import spack.directives
|
||||
import spack.package_base
|
||||
|
||||
|
||||
class BundlePackage(spack.package_base.PackageBase):
|
||||
"""General purpose bundle, or no-code, package class."""
|
||||
|
||||
#: This attribute is used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "BundlePackage"
|
||||
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "bundle"
|
||||
|
||||
#: Bundle packages do not have associated source or binary code.
|
||||
has_code = False
|
||||
|
||||
spack.directives.build_system("bundle")
|
||||
|
||||
|
||||
@spack.builder.builder("bundle")
|
||||
class BundleBuilder(spack.builder.Builder):
|
||||
phases = ("install",)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
pass
|
@ -3,12 +3,14 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import os
|
||||
from typing import Tuple
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import install, mkdirp
|
||||
|
||||
from spack.build_systems.cmake import CMakePackage
|
||||
from spack.package_base import run_after
|
||||
import spack.builder
|
||||
|
||||
from .cmake import CMakeBuilder, CMakePackage
|
||||
|
||||
|
||||
def cmake_cache_path(name, value, comment=""):
|
||||
@ -28,44 +30,50 @@ def cmake_cache_option(name, boolean_value, comment=""):
|
||||
return 'set({0} {1} CACHE BOOL "{2}")\n'.format(name, value, comment)
|
||||
|
||||
|
||||
class CachedCMakePackage(CMakePackage):
|
||||
"""Specialized class for packages built using CMake initial cache.
|
||||
class CachedCMakeBuilder(CMakeBuilder):
|
||||
|
||||
This feature of CMake allows packages to increase reproducibility,
|
||||
especially between Spack- and manual builds. It also allows packages to
|
||||
sidestep certain parsing bugs in extremely long ``cmake`` commands, and to
|
||||
avoid system limits on the length of the command line."""
|
||||
#: Phases of a Cached CMake package
|
||||
#: Note: the initconfig phase is used for developer builds as a final phase to stop on
|
||||
phases = ("initconfig", "cmake", "build", "install") # type: Tuple[str, ...]
|
||||
|
||||
phases = ["initconfig", "cmake", "build", "install"]
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = CMakeBuilder.legacy_methods + (
|
||||
"initconfig_compiler_entries",
|
||||
"initconfig_mpi_entries",
|
||||
"initconfig_hardware_entries",
|
||||
"std_initconfig_entries",
|
||||
"initconfig_package_entries",
|
||||
) # type: Tuple[str, ...]
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = CMakeBuilder.legacy_attributes + (
|
||||
"cache_name",
|
||||
"cache_path",
|
||||
) # type: Tuple[str, ...]
|
||||
|
||||
@property
|
||||
def cache_name(self):
|
||||
return "{0}-{1}-{2}@{3}.cmake".format(
|
||||
self.name,
|
||||
self.spec.architecture,
|
||||
self.spec.compiler.name,
|
||||
self.spec.compiler.version,
|
||||
self.pkg.name,
|
||||
self.pkg.spec.architecture,
|
||||
self.pkg.spec.compiler.name,
|
||||
self.pkg.spec.compiler.version,
|
||||
)
|
||||
|
||||
@property
|
||||
def cache_path(self):
|
||||
return os.path.join(self.stage.source_path, self.cache_name)
|
||||
|
||||
def flag_handler(self, name, flags):
|
||||
if name in ("cflags", "cxxflags", "cppflags", "fflags"):
|
||||
return (None, None, None) # handled in the cmake cache
|
||||
return (flags, None, None)
|
||||
return os.path.join(self.pkg.stage.source_path, self.cache_name)
|
||||
|
||||
def initconfig_compiler_entries(self):
|
||||
# This will tell cmake to use the Spack compiler wrappers when run
|
||||
# through Spack, but use the underlying compiler when run outside of
|
||||
# Spack
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
|
||||
# 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.compiler.fc)
|
||||
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"
|
||||
@ -81,8 +89,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.compiler.cc),
|
||||
" " + cmake_cache_path("CMAKE_CXX_COMPILER", self.compiler.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",
|
||||
]
|
||||
@ -126,7 +134,7 @@ def initconfig_compiler_entries(self):
|
||||
return entries
|
||||
|
||||
def initconfig_mpi_entries(self):
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
|
||||
if not spec.satisfies("^mpi"):
|
||||
return []
|
||||
@ -160,13 +168,13 @@ def initconfig_mpi_entries(self):
|
||||
mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpiexec")
|
||||
|
||||
if not os.path.exists(mpiexec):
|
||||
msg = "Unable to determine MPIEXEC, %s tests may fail" % self.name
|
||||
msg = "Unable to determine MPIEXEC, %s tests may fail" % self.pkg.name
|
||||
entries.append("# {0}\n".format(msg))
|
||||
tty.warn(msg)
|
||||
else:
|
||||
# starting with cmake 3.10, FindMPI expects MPIEXEC_EXECUTABLE
|
||||
# vs the older versions which expect MPIEXEC
|
||||
if self.spec["cmake"].satisfies("@3.10:"):
|
||||
if self.pkg.spec["cmake"].satisfies("@3.10:"):
|
||||
entries.append(cmake_cache_path("MPIEXEC_EXECUTABLE", mpiexec))
|
||||
else:
|
||||
entries.append(cmake_cache_path("MPIEXEC", mpiexec))
|
||||
@ -180,7 +188,7 @@ def initconfig_mpi_entries(self):
|
||||
return entries
|
||||
|
||||
def initconfig_hardware_entries(self):
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
|
||||
entries = [
|
||||
"#------------------{0}".format("-" * 60),
|
||||
@ -197,13 +205,7 @@ def initconfig_hardware_entries(self):
|
||||
entries.append(cmake_cache_path("CUDA_TOOLKIT_ROOT_DIR", cudatoolkitdir))
|
||||
cudacompiler = "${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc"
|
||||
entries.append(cmake_cache_path("CMAKE_CUDA_COMPILER", cudacompiler))
|
||||
|
||||
if spec.satisfies("^mpi"):
|
||||
entries.append(cmake_cache_path("CMAKE_CUDA_HOST_COMPILER", "${MPI_CXX_COMPILER}"))
|
||||
else:
|
||||
entries.append(
|
||||
cmake_cache_path("CMAKE_CUDA_HOST_COMPILER", "${CMAKE_CXX_COMPILER}")
|
||||
)
|
||||
entries.append(cmake_cache_path("CMAKE_CUDA_HOST_COMPILER", "${CMAKE_CXX_COMPILER}"))
|
||||
|
||||
return entries
|
||||
|
||||
@ -212,7 +214,7 @@ def std_initconfig_entries(self):
|
||||
"#------------------{0}".format("-" * 60),
|
||||
"# !!!! This is a generated file, edit at own risk !!!!",
|
||||
"#------------------{0}".format("-" * 60),
|
||||
"# CMake executable path: {0}".format(self.spec["cmake"].command.path),
|
||||
"# CMake executable path: {0}".format(self.pkg.spec["cmake"].command.path),
|
||||
"#------------------{0}\n".format("-" * 60),
|
||||
]
|
||||
|
||||
@ -220,7 +222,7 @@ def initconfig_package_entries(self):
|
||||
"""This method is to be overwritten by the package"""
|
||||
return []
|
||||
|
||||
def initconfig(self, spec, prefix):
|
||||
def initconfig(self, pkg, spec, prefix):
|
||||
cache_entries = (
|
||||
self.std_initconfig_entries()
|
||||
+ self.initconfig_compiler_entries()
|
||||
@ -236,11 +238,28 @@ def initconfig(self, spec, prefix):
|
||||
|
||||
@property
|
||||
def std_cmake_args(self):
|
||||
args = super(CachedCMakePackage, self).std_cmake_args
|
||||
args = super(CachedCMakeBuilder, self).std_cmake_args
|
||||
args.extend(["-C", self.cache_path])
|
||||
return args
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def install_cmake_cache(self):
|
||||
mkdirp(self.spec.prefix.share.cmake)
|
||||
install(self.cache_path, self.spec.prefix.share.cmake)
|
||||
fs.mkdirp(self.pkg.spec.prefix.share.cmake)
|
||||
fs.install(self.cache_path, self.pkg.spec.prefix.share.cmake)
|
||||
|
||||
|
||||
class CachedCMakePackage(CMakePackage):
|
||||
"""Specialized class for packages built using CMake initial cache.
|
||||
|
||||
This feature of CMake allows packages to increase reproducibility,
|
||||
especially between Spack- and manual builds. It also allows packages to
|
||||
sidestep certain parsing bugs in extremely long ``cmake`` commands, and to
|
||||
avoid system limits on the length of the command line.
|
||||
"""
|
||||
|
||||
CMakeBuilder = CachedCMakeBuilder
|
||||
|
||||
def flag_handler(self, name, flags):
|
||||
if name in ("cflags", "cxxflags", "cppflags", "fflags"):
|
||||
return None, None, None # handled in the cmake cache
|
||||
return flags, None, None
|
||||
|
@ -2,23 +2,26 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
import six
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
from llnl.util.compat import Sequence
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
import spack.build_environment
|
||||
from spack.directives import conflicts, depends_on, variant
|
||||
from spack.package_base import InstallError, PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
import spack.util.path
|
||||
from spack.directives import build_system, depends_on, variant
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
# Regex to extract the primary generator from the CMake generator
|
||||
# string.
|
||||
@ -34,56 +37,141 @@ def _extract_primary_generator(generator):
|
||||
return primary_generator
|
||||
|
||||
|
||||
class CMakePackage(PackageBase):
|
||||
class CMakePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using CMake
|
||||
|
||||
For more information on the CMake build system, see:
|
||||
https://cmake.org/cmake/help/latest/
|
||||
"""
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "CMakePackage"
|
||||
|
||||
1. :py:meth:`~.CMakePackage.cmake`
|
||||
2. :py:meth:`~.CMakePackage.build`
|
||||
3. :py:meth:`~.CMakePackage.install`
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "cmake"
|
||||
|
||||
build_system("cmake")
|
||||
|
||||
with when("build_system=cmake"):
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
|
||||
variant(
|
||||
"build_type",
|
||||
default="RelWithDebInfo",
|
||||
description="CMake build type",
|
||||
values=("Debug", "Release", "RelWithDebInfo", "MinSizeRel"),
|
||||
)
|
||||
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_INTERPROCEDURAL_OPTIMIZATION.html
|
||||
variant(
|
||||
"ipo",
|
||||
default=False,
|
||||
when="^cmake@3.9:",
|
||||
description="CMake interprocedural optimization",
|
||||
)
|
||||
depends_on("cmake", type="build")
|
||||
depends_on("ninja", type="build", when="platform=windows")
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Return a list of all command line arguments to pass the specified
|
||||
compiler flags to cmake. Note CMAKE does not have a cppflags option,
|
||||
so cppflags will be added to cflags, cxxflags, and fflags to mimic the
|
||||
behavior in other tools.
|
||||
"""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "cmake_flag_args", [])
|
||||
|
||||
flag_string = "-DCMAKE_{0}_FLAGS={1}"
|
||||
langs = {"C": "c", "CXX": "cxx", "Fortran": "f"}
|
||||
|
||||
# Handle language compiler flags
|
||||
for lang, pre in langs.items():
|
||||
flag = pre + "flags"
|
||||
# cmake has no explicit cppflags support -> add it to all langs
|
||||
lang_flags = " ".join(flags.get(flag, []) + flags.get("cppflags", []))
|
||||
if lang_flags:
|
||||
self.cmake_flag_args.append(flag_string.format(lang, lang_flags))
|
||||
|
||||
# Cmake has different linker arguments for different build types.
|
||||
# We specify for each of them.
|
||||
if flags["ldflags"]:
|
||||
ldflags = " ".join(flags["ldflags"])
|
||||
ld_string = "-DCMAKE_{0}_LINKER_FLAGS={1}"
|
||||
# cmake has separate linker arguments for types of builds.
|
||||
for type in ["EXE", "MODULE", "SHARED", "STATIC"]:
|
||||
self.cmake_flag_args.append(ld_string.format(type, ldflags))
|
||||
|
||||
# CMake has libs options separated by language. Apply ours to each.
|
||||
if flags["ldlibs"]:
|
||||
libs_flags = " ".join(flags["ldlibs"])
|
||||
libs_string = "-DCMAKE_{0}_STANDARD_LIBRARIES={1}"
|
||||
for lang in langs:
|
||||
self.cmake_flag_args.append(libs_string.format(lang, libs_flags))
|
||||
|
||||
# Legacy methods (used by too many packages to change them,
|
||||
# need to forward to the builder)
|
||||
def define(self, *args, **kwargs):
|
||||
return self.builder.define(*args, **kwargs)
|
||||
|
||||
def define_from_variant(self, *args, **kwargs):
|
||||
return self.builder.define_from_variant(*args, **kwargs)
|
||||
|
||||
|
||||
@spack.builder.builder("cmake")
|
||||
class CMakeBuilder(BaseBuilder):
|
||||
"""The cmake builder encodes the default way of building software with CMake. IT
|
||||
has three phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.CMakeBuilder.cmake`
|
||||
2. :py:meth:`~.CMakeBuilder.build`
|
||||
3. :py:meth:`~.CMakeBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.CMakePackage.cmake_args`.
|
||||
necessary will be to override :py:meth:`~.CMakeBuilder.cmake_args`.
|
||||
|
||||
For a finer tuning you may also override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:meth:`~.CMakePackage.root_cmakelists_dir` | Location of the |
|
||||
| :py:meth:`~.CMakeBuilder.root_cmakelists_dir` | Location of the |
|
||||
| | root CMakeLists.txt|
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.CMakePackage.build_directory` | Directory where to |
|
||||
| :py:meth:`~.CMakeBuilder.build_directory` | Directory where to |
|
||||
| | build the package |
|
||||
+-----------------------------------------------+--------------------+
|
||||
|
||||
|
||||
The generator used by CMake can be specified by providing the
|
||||
generator attribute. Per
|
||||
The generator used by CMake can be specified by providing the ``generator``
|
||||
attribute. Per
|
||||
https://cmake.org/cmake/help/git-master/manual/cmake-generators.7.html,
|
||||
the format is: [<secondary-generator> - ]<primary_generator>. The
|
||||
full list of primary and secondary generators supported by CMake may
|
||||
be found in the documentation for the version of CMake used;
|
||||
however, at this time Spack supports only the primary generators
|
||||
"Unix Makefiles" and "Ninja." Spack's CMake support is agnostic with
|
||||
respect to primary generators. Spack will generate a runtime error
|
||||
if the generator string does not follow the prescribed format, or if
|
||||
the format is: [<secondary-generator> - ]<primary_generator>.
|
||||
|
||||
The full list of primary and secondary generators supported by CMake may be found
|
||||
in the documentation for the version of CMake used; however, at this time Spack
|
||||
supports only the primary generators "Unix Makefiles" and "Ninja." Spack's CMake
|
||||
support is agnostic with respect to primary generators. Spack will generate a
|
||||
runtime error if the generator string does not follow the prescribed format, or if
|
||||
the primary generator is not supported.
|
||||
"""
|
||||
|
||||
#: Phases of a CMake package
|
||||
phases = ["cmake", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "CMakePackage"
|
||||
phases = ("cmake", "build", "install") # type: Tuple[str, ...]
|
||||
|
||||
build_targets = [] # type: List[str]
|
||||
install_targets = ["install"]
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("cmake_args", "check") # type: Tuple[str, ...]
|
||||
|
||||
build_time_test_callbacks = ["check"]
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"generator",
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"archive_files",
|
||||
"root_cmakelists_dir",
|
||||
"std_cmake_args",
|
||||
"build_dirname",
|
||||
"build_directory",
|
||||
) # type: Tuple[str, ...]
|
||||
|
||||
#: The build system generator to use.
|
||||
#:
|
||||
@ -93,27 +181,14 @@ class CMakePackage(PackageBase):
|
||||
#:
|
||||
#: See https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html
|
||||
#: for more information.
|
||||
generator = "Ninja" if sys.platform == "win32" else "Unix Makefiles"
|
||||
|
||||
generator = "Unix Makefiles"
|
||||
|
||||
if sys.platform == "win32":
|
||||
generator = "Ninja"
|
||||
depends_on("ninja")
|
||||
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
|
||||
variant(
|
||||
"build_type",
|
||||
default="RelWithDebInfo",
|
||||
description="CMake build type",
|
||||
values=("Debug", "Release", "RelWithDebInfo", "MinSizeRel"),
|
||||
)
|
||||
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_INTERPROCEDURAL_OPTIMIZATION.html
|
||||
variant("ipo", default=False, description="CMake interprocedural optimization")
|
||||
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
||||
conflicts("+ipo", when="^cmake@:3.8", msg="+ipo is not supported by CMake < 3.9")
|
||||
|
||||
depends_on("cmake", type="build")
|
||||
#: Targets to be used during the build phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets to be used during the install phase
|
||||
install_targets = ["install"]
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
@property
|
||||
def archive_files(self):
|
||||
@ -126,40 +201,30 @@ def root_cmakelists_dir(self):
|
||||
|
||||
This path is relative to the root of the extracted tarball,
|
||||
not to the ``build_directory``. Defaults to the current directory.
|
||||
|
||||
:return: directory containing CMakeLists.txt
|
||||
"""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
@property
|
||||
def std_cmake_args(self):
|
||||
"""Standard cmake arguments provided as a property for
|
||||
convenience of package writers
|
||||
|
||||
:return: standard cmake arguments
|
||||
"""
|
||||
# standard CMake arguments
|
||||
std_cmake_args = CMakePackage._std_args(self)
|
||||
std_cmake_args += getattr(self, "cmake_flag_args", [])
|
||||
std_cmake_args = CMakeBuilder.std_args(self.pkg, generator=self.generator)
|
||||
std_cmake_args += getattr(self.pkg, "cmake_flag_args", [])
|
||||
return std_cmake_args
|
||||
|
||||
@staticmethod
|
||||
def _std_args(pkg):
|
||||
def std_args(pkg, generator=None):
|
||||
"""Computes the standard cmake arguments for a generic package"""
|
||||
|
||||
try:
|
||||
generator = pkg.generator
|
||||
except AttributeError:
|
||||
generator = CMakePackage.generator
|
||||
|
||||
# Make sure a valid generator was chosen
|
||||
generator = generator or "Unix Makefiles"
|
||||
valid_primary_generators = ["Unix Makefiles", "Ninja"]
|
||||
primary_generator = _extract_primary_generator(generator)
|
||||
if primary_generator not in valid_primary_generators:
|
||||
msg = "Invalid CMake generator: '{0}'\n".format(generator)
|
||||
msg += "CMakePackage currently supports the following "
|
||||
msg += "primary generators: '{0}'".format("', '".join(valid_primary_generators))
|
||||
raise InstallError(msg)
|
||||
raise spack.package_base.InstallError(msg)
|
||||
|
||||
try:
|
||||
build_type = pkg.spec.variants["build_type"].value
|
||||
@ -171,7 +236,7 @@ def _std_args(pkg):
|
||||
except KeyError:
|
||||
ipo = False
|
||||
|
||||
define = CMakePackage.define
|
||||
define = CMakeBuilder.define
|
||||
args = [
|
||||
"-G",
|
||||
generator,
|
||||
@ -251,7 +316,7 @@ def define_from_variant(self, cmake_var, variant=None):
|
||||
of ``cmake_var``.
|
||||
|
||||
This utility function is similar to
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without`.
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without`.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -291,122 +356,75 @@ def define_from_variant(self, cmake_var, variant=None):
|
||||
if variant is None:
|
||||
variant = cmake_var.lower()
|
||||
|
||||
if variant not in self.variants:
|
||||
raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, self.name))
|
||||
if variant not in self.pkg.variants:
|
||||
raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, self.pkg.name))
|
||||
|
||||
if variant not in self.spec.variants:
|
||||
if variant not in self.pkg.spec.variants:
|
||||
return ""
|
||||
|
||||
value = self.spec.variants[variant].value
|
||||
value = self.pkg.spec.variants[variant].value
|
||||
if isinstance(value, (tuple, list)):
|
||||
# Sort multi-valued variants for reproducibility
|
||||
value = sorted(value)
|
||||
|
||||
return self.define(cmake_var, value)
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass the specified
|
||||
compiler flags to cmake. Note CMAKE does not have a cppflags option,
|
||||
so cppflags will be added to cflags, cxxflags, and fflags to mimic the
|
||||
behavior in other tools."""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "cmake_flag_args", [])
|
||||
|
||||
flag_string = "-DCMAKE_{0}_FLAGS={1}"
|
||||
langs = {"C": "c", "CXX": "cxx", "Fortran": "f"}
|
||||
|
||||
# Handle language compiler flags
|
||||
for lang, pre in langs.items():
|
||||
flag = pre + "flags"
|
||||
# cmake has no explicit cppflags support -> add it to all langs
|
||||
lang_flags = " ".join(flags.get(flag, []) + flags.get("cppflags", []))
|
||||
if lang_flags:
|
||||
self.cmake_flag_args.append(flag_string.format(lang, lang_flags))
|
||||
|
||||
# Cmake has different linker arguments for different build types.
|
||||
# We specify for each of them.
|
||||
if flags["ldflags"]:
|
||||
ldflags = " ".join(flags["ldflags"])
|
||||
ld_string = "-DCMAKE_{0}_LINKER_FLAGS={1}"
|
||||
# cmake has separate linker arguments for types of builds.
|
||||
for type in ["EXE", "MODULE", "SHARED", "STATIC"]:
|
||||
self.cmake_flag_args.append(ld_string.format(type, ldflags))
|
||||
|
||||
# CMake has libs options separated by language. Apply ours to each.
|
||||
if flags["ldlibs"]:
|
||||
libs_flags = " ".join(flags["ldlibs"])
|
||||
libs_string = "-DCMAKE_{0}_STANDARD_LIBRARIES={1}"
|
||||
for lang in langs:
|
||||
self.cmake_flag_args.append(libs_string.format(lang, libs_flags))
|
||||
|
||||
@property
|
||||
def build_dirname(self):
|
||||
"""Returns the directory name to use when building the package
|
||||
|
||||
:return: name of the subdirectory for building the package
|
||||
"""
|
||||
return "spack-build-%s" % self.spec.dag_hash(7)
|
||||
"""Directory name to use when building the package."""
|
||||
return "spack-build-%s" % self.pkg.spec.dag_hash(7)
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Returns the directory to use when building the package
|
||||
|
||||
:return: directory where to build the package
|
||||
"""
|
||||
return os.path.join(self.stage.path, self.build_dirname)
|
||||
"""Full-path to the directory to use when building the package."""
|
||||
return os.path.join(self.pkg.stage.path, self.build_dirname)
|
||||
|
||||
def cmake_args(self):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
cmake, except:
|
||||
"""List of all the arguments that must be passed to cmake, except:
|
||||
|
||||
* CMAKE_INSTALL_PREFIX
|
||||
* CMAKE_BUILD_TYPE
|
||||
* BUILD_TESTING
|
||||
|
||||
which will be set automatically.
|
||||
|
||||
:return: list of arguments for cmake
|
||||
"""
|
||||
return []
|
||||
|
||||
def cmake(self, spec, prefix):
|
||||
def cmake(self, pkg, spec, prefix):
|
||||
"""Runs ``cmake`` in the build directory"""
|
||||
options = self.std_cmake_args
|
||||
options += self.cmake_args()
|
||||
options.append(os.path.abspath(self.root_cmakelists_dir))
|
||||
with working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self).cmake(*options)
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).cmake(*options)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
inspect.getmodule(self).make(*self.build_targets)
|
||||
inspect.getmodule(self.pkg).make(*self.build_targets)
|
||||
elif self.generator == "Ninja":
|
||||
self.build_targets.append("-v")
|
||||
inspect.getmodule(self).ninja(*self.build_targets)
|
||||
inspect.getmodule(self.pkg).ninja(*self.build_targets)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
inspect.getmodule(self).make(*self.install_targets)
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
elif self.generator == "Ninja":
|
||||
inspect.getmodule(self).ninja(*self.install_targets)
|
||||
inspect.getmodule(self.pkg).ninja(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the CMake-generated Makefile for the target ``test``
|
||||
and runs it if found.
|
||||
"""Search the CMake-generated files for the targets ``test`` and ``check``,
|
||||
and runs them if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
self._if_make_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self._if_make_target_execute("check")
|
||||
self.pkg._if_make_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self.pkg._if_make_target_execute("check")
|
||||
elif self.generator == "Ninja":
|
||||
self._if_ninja_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self._if_ninja_target_execute("check")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
self.pkg._if_ninja_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self.pkg._if_ninja_target_execute("check")
|
||||
|
44
lib/spack/spack/build_systems/generic.py
Normal file
44
lib/spack/spack/build_systems/generic.py
Normal file
@ -0,0 +1,44 @@
|
||||
# Copyright 2013-2022 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 Tuple
|
||||
|
||||
import spack.builder
|
||||
import spack.directives
|
||||
import spack.package_base
|
||||
|
||||
from ._checks import BaseBuilder, apply_macos_rpath_fixups
|
||||
|
||||
|
||||
class Package(spack.package_base.PackageBase):
|
||||
"""General purpose class with a single ``install`` phase that needs to be
|
||||
coded by packagers.
|
||||
"""
|
||||
|
||||
#: This attribute is used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "Package"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "generic"
|
||||
|
||||
spack.directives.build_system("generic")
|
||||
|
||||
|
||||
@spack.builder.builder("generic")
|
||||
class GenericBuilder(BaseBuilder):
|
||||
"""A builder for a generic build system, that require packagers
|
||||
to implement an "install" phase.
|
||||
"""
|
||||
|
||||
#: A generic package has only the "install" phase
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = () # type: Tuple[str, ...]
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("archive_files",) # type: Tuple[str, ...]
|
||||
|
||||
# On macOS, force rpaths for shared library IDs and remove duplicate rpaths
|
||||
spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups)
|
@ -2,8 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import glob
|
||||
import inspect
|
||||
import os
|
||||
@ -26,12 +24,14 @@
|
||||
|
||||
import spack.error
|
||||
from spack.build_environment import dso_suffix
|
||||
from spack.package_base import InstallError, PackageBase, run_after
|
||||
from spack.package_base import InstallError
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.util.executable import Executable
|
||||
from spack.util.prefix import Prefix
|
||||
from spack.version import Version, ver
|
||||
|
||||
from .generic import Package
|
||||
|
||||
# A couple of utility functions that might be useful in general. If so, they
|
||||
# should really be defined elsewhere, unless deemed heretical.
|
||||
# (Or na"ive on my part).
|
||||
@ -86,7 +86,7 @@ def _expand_fields(s):
|
||||
return s
|
||||
|
||||
|
||||
class IntelPackage(PackageBase):
|
||||
class IntelPackage(Package):
|
||||
"""Specialized class for licensed Intel software.
|
||||
|
||||
This class provides two phases that can be overridden:
|
||||
@ -99,9 +99,6 @@ class IntelPackage(PackageBase):
|
||||
to set the appropriate environment variables.
|
||||
"""
|
||||
|
||||
#: Phases of an Intel package
|
||||
phases = ["configure", "install"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "IntelPackage"
|
||||
@ -1184,12 +1181,13 @@ def _determine_license_type(self):
|
||||
debug_print(license_type)
|
||||
return license_type
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
@spack.builder.run_before("install")
|
||||
def configure(self):
|
||||
"""Generates the silent.cfg file to pass to installer.sh.
|
||||
|
||||
See https://software.intel.com/en-us/articles/configuration-file-format
|
||||
"""
|
||||
|
||||
prefix = self.prefix
|
||||
# Both tokens AND values of the configuration file are validated during
|
||||
# the run of the underlying binary installer. Any unknown token or
|
||||
# unacceptable value will cause that installer to fail. Notably, this
|
||||
@ -1270,7 +1268,7 @@ def install(self, spec, prefix):
|
||||
for f in glob.glob("%s/intel*log" % tmpdir):
|
||||
install(f, dst)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def validate_install(self):
|
||||
# Sometimes the installer exits with an error but doesn't pass a
|
||||
# non-zero exit code to spack. Check for the existence of a 'bin'
|
||||
@ -1278,7 +1276,7 @@ def validate_install(self):
|
||||
if not os.path.exists(self.prefix.bin):
|
||||
raise InstallError("The installer has failed to install anything.")
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def configure_rpath(self):
|
||||
if "+rpath" not in self.spec:
|
||||
return
|
||||
@ -1296,7 +1294,7 @@ def configure_rpath(self):
|
||||
with open(compiler_cfg, "w") as fh:
|
||||
fh.write("-Xlinker -rpath={0}\n".format(compilers_lib_dir))
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def configure_auto_dispatch(self):
|
||||
if self._has_compilers:
|
||||
if "auto_dispatch=none" in self.spec:
|
||||
@ -1320,7 +1318,7 @@ def configure_auto_dispatch(self):
|
||||
with open(compiler_cfg, "a") as fh:
|
||||
fh.write("-ax{0}\n".format(",".join(ad)))
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def filter_compiler_wrappers(self):
|
||||
if ("+mpi" in self.spec or self.provides("mpi")) and "~newdtags" in self.spec:
|
||||
bin_dir = self.component_bin_dir("mpi")
|
||||
@ -1328,7 +1326,7 @@ def filter_compiler_wrappers(self):
|
||||
f = os.path.join(bin_dir, f)
|
||||
filter_file("-Xlinker --enable-new-dtags", " ", f, string=True)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def uninstall_ism(self):
|
||||
# The "Intel(R) Software Improvement Program" [ahem] gets installed,
|
||||
# apparently regardless of PHONEHOME_SEND_USAGE_DATA.
|
||||
@ -1360,7 +1358,7 @@ def base_lib_dir(self):
|
||||
debug_print(d)
|
||||
return d
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def modify_LLVMgold_rpath(self):
|
||||
"""Add libimf.so and other required libraries to the RUNPATH of LLVMgold.so.
|
||||
|
||||
@ -1391,6 +1389,3 @@ def modify_LLVMgold_rpath(self):
|
||||
]
|
||||
)
|
||||
patchelf("--set-rpath", rpath, lib)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
@ -2,59 +2,82 @@
|
||||
# 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 find
|
||||
|
||||
from spack.directives import depends_on, extends
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
import spack.util.executable
|
||||
from spack.directives import build_system, depends_on, extends
|
||||
from spack.multimethod import when
|
||||
from spack.package_base import PackageBase
|
||||
from spack.util.executable import Executable
|
||||
|
||||
|
||||
class LuaPackage(PackageBase):
|
||||
class LuaPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for lua packages"""
|
||||
|
||||
phases = ["unpack", "generate_luarocks_config", "preprocess", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "LuaPackage"
|
||||
|
||||
list_depth = 1 # LuaRocks requires at least one level of spidering to find versions
|
||||
depends_on("lua-lang")
|
||||
extends("lua", when="^lua")
|
||||
with when("^lua-luajit"):
|
||||
extends("lua-luajit")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit+lualinks")
|
||||
with when("^lua-luajit-openresty"):
|
||||
extends("lua-luajit-openresty")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit-openresty+lualinks")
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "lua"
|
||||
|
||||
def unpack(self, spec, prefix):
|
||||
if os.path.splitext(self.stage.archive_file)[1] == ".rock":
|
||||
directory = self.luarocks("unpack", self.stage.archive_file, output=str)
|
||||
list_depth = 1 # LuaRocks requires at least one level of spidering to find versions
|
||||
|
||||
build_system("lua")
|
||||
|
||||
with when("build_system=lua"):
|
||||
depends_on("lua-lang")
|
||||
extends("lua", when="^lua")
|
||||
with when("^lua-luajit"):
|
||||
extends("lua-luajit")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit+lualinks")
|
||||
with when("^lua-luajit-openresty"):
|
||||
extends("lua-luajit-openresty")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit-openresty+lualinks")
|
||||
|
||||
@property
|
||||
def lua(self):
|
||||
return spack.util.executable.Executable(self.spec["lua-lang"].prefix.bin.lua)
|
||||
|
||||
@property
|
||||
def luarocks(self):
|
||||
lr = spack.util.executable.Executable(self.spec["lua-lang"].prefix.bin.luarocks)
|
||||
return lr
|
||||
|
||||
|
||||
@spack.builder.builder("lua")
|
||||
class LuaBuilder(spack.builder.Builder):
|
||||
phases = ("unpack", "generate_luarocks_config", "preprocess", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("luarocks_args",)
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
def unpack(self, pkg, spec, prefix):
|
||||
if os.path.splitext(pkg.stage.archive_file)[1] == ".rock":
|
||||
directory = pkg.luarocks("unpack", pkg.stage.archive_file, output=str)
|
||||
dirlines = directory.split("\n")
|
||||
# TODO: figure out how to scope this better
|
||||
os.chdir(dirlines[2])
|
||||
|
||||
def _generate_tree_line(self, name, prefix):
|
||||
@staticmethod
|
||||
def _generate_tree_line(name, prefix):
|
||||
return """{{ name = "{name}", root = "{prefix}" }};""".format(
|
||||
name=name,
|
||||
prefix=prefix,
|
||||
)
|
||||
|
||||
def _luarocks_config_path(self):
|
||||
return os.path.join(self.stage.source_path, "spack_luarocks.lua")
|
||||
|
||||
def generate_luarocks_config(self, spec, prefix):
|
||||
spec = self.spec
|
||||
def generate_luarocks_config(self, pkg, spec, prefix):
|
||||
spec = self.pkg.spec
|
||||
table_entries = []
|
||||
for d in spec.traverse(deptypes=("build", "run"), deptype_query="run"):
|
||||
if d.package.extends(self.extendee_spec):
|
||||
for d in spec.traverse(deptype=("build", "run")):
|
||||
if d.package.extends(self.pkg.extendee_spec):
|
||||
table_entries.append(self._generate_tree_line(d.name, d.prefix))
|
||||
|
||||
path = self._luarocks_config_path()
|
||||
@ -71,30 +94,24 @@ def generate_luarocks_config(self, spec, prefix):
|
||||
)
|
||||
return path
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
env.set("LUAROCKS_CONFIG", self._luarocks_config_path())
|
||||
|
||||
def preprocess(self, spec, prefix):
|
||||
def preprocess(self, pkg, spec, prefix):
|
||||
"""Override this to preprocess source before building with luarocks"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def lua(self):
|
||||
return Executable(self.spec["lua-lang"].prefix.bin.lua)
|
||||
|
||||
@property
|
||||
def luarocks(self):
|
||||
lr = Executable(self.spec["lua-lang"].prefix.bin.luarocks)
|
||||
return lr
|
||||
|
||||
def luarocks_args(self):
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
rock = "."
|
||||
specs = find(".", "*.rockspec", recursive=False)
|
||||
if specs:
|
||||
rock = specs[0]
|
||||
rocks_args = self.luarocks_args()
|
||||
rocks_args.append(rock)
|
||||
self.luarocks("--tree=" + prefix, "make", *rocks_args)
|
||||
self.pkg.luarocks("--tree=" + prefix, "make", *rocks_args)
|
||||
|
||||
def _luarocks_config_path(self):
|
||||
return os.path.join(self.pkg.stage.source_path, "spack_luarocks.lua")
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
env.set("LUAROCKS_CONFIG", self._luarocks_config_path())
|
||||
|
@ -2,62 +2,85 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import working_dir
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
from spack.directives import conflicts
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, conflicts
|
||||
|
||||
from ._checks import (
|
||||
BaseBuilder,
|
||||
apply_macos_rpath_fixups,
|
||||
execute_build_time_tests,
|
||||
execute_install_time_tests,
|
||||
)
|
||||
|
||||
|
||||
class MakefilePackage(PackageBase):
|
||||
"""Specialized class for packages that are built using editable Makefiles
|
||||
class MakefilePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using a Makefiles."""
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MakefilePackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "makefile"
|
||||
|
||||
1. :py:meth:`~.MakefilePackage.edit`
|
||||
2. :py:meth:`~.MakefilePackage.build`
|
||||
3. :py:meth:`~.MakefilePackage.install`
|
||||
build_system("makefile")
|
||||
conflicts("platform=windows", when="build_system=makefile")
|
||||
|
||||
|
||||
@spack.builder.builder("makefile")
|
||||
class MakefileBuilder(BaseBuilder):
|
||||
"""The Makefile builder encodes the most common way of building software with
|
||||
Makefiles. It has three phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.MakefileBuilder.edit`
|
||||
2. :py:meth:`~.MakefileBuilder.build`
|
||||
3. :py:meth:`~.MakefileBuilder.install`
|
||||
|
||||
It is usually necessary to override the :py:meth:`~.MakefileBuilder.edit`
|
||||
phase (which is by default a no-op), while the other two have sensible defaults.
|
||||
|
||||
It is usually necessary to override the :py:meth:`~.MakefilePackage.edit`
|
||||
phase, while :py:meth:`~.MakefilePackage.build` and
|
||||
:py:meth:`~.MakefilePackage.install` have sensible defaults.
|
||||
For a finer tuning you may override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:attr:`~.MakefilePackage.build_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.MakefileBuilder.build_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | build phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:attr:`~.MakefilePackage.install_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.MakefileBuilder.install_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | install phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.MakefilePackage.build_directory` | Directory where the|
|
||||
| :py:meth:`~.MakefileBuilder.build_directory` | Directory where the|
|
||||
| | Makefile is located|
|
||||
+-----------------------------------------------+--------------------+
|
||||
"""
|
||||
|
||||
#: Phases of a package that is built with an hand-written Makefile
|
||||
phases = ["edit", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MakefilePackage"
|
||||
phases = ("edit", "build", "install")
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefilePackage.build`
|
||||
#: phase
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("check", "installcheck")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefileBuilder.build` phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefilePackage.install`
|
||||
#: phase
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefileBuilder.install` phase
|
||||
install_targets = ["install"]
|
||||
|
||||
conflicts("platform=windows")
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
@ -66,53 +89,39 @@ class MakefilePackage(PackageBase):
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Returns the directory containing the main Makefile
|
||||
"""Return the directory containing the main Makefile."""
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
:return: build directory
|
||||
"""
|
||||
return self.stage.source_path
|
||||
def edit(self, pkg, spec, prefix):
|
||||
"""Edit the Makefile before calling make. The default is a no-op."""
|
||||
pass
|
||||
|
||||
def edit(self, spec, prefix):
|
||||
"""Edits the Makefile before calling make. This phase cannot
|
||||
be defaulted.
|
||||
"""
|
||||
tty.msg("Using default implementation: skipping edit phase.")
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.build_targets)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
"""Calls make, passing :py:attr:`~.MakefilePackage.build_targets`
|
||||
as targets.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*self.build_targets)
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Calls make, passing :py:attr:`~.MakefilePackage.install_targets`
|
||||
as targets.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the Makefile for targets ``test`` and ``check``
|
||||
and runs them if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("test")
|
||||
self._if_make_target_execute("check")
|
||||
"""Run "make" on the ``test`` and ``check`` targets, if found."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
self.pkg._if_make_target_execute("test")
|
||||
self.pkg._if_make_target_execute("check")
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
def installcheck(self):
|
||||
"""Searches the Makefile for an ``installcheck`` target
|
||||
and runs it if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("installcheck")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
with fs.working_dir(self.build_directory):
|
||||
self.pkg._if_make_target_execute("installcheck")
|
||||
|
||||
# On macOS, force rpaths for shared library IDs and remove duplicate rpaths
|
||||
run_after("install")(PackageBase.apply_macos_rpath_fixups)
|
||||
spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups)
|
||||
|
@ -2,60 +2,73 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
|
||||
from llnl.util.filesystem import install_tree, working_dir
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
from spack.multimethod import when
|
||||
from spack.util.executable import which
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
class MavenPackage(PackageBase):
|
||||
|
||||
class MavenPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages that are built using the
|
||||
Maven build system. See https://maven.apache.org/index.html
|
||||
for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
* build
|
||||
* install
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["build", "install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "MavenPackage"
|
||||
|
||||
depends_on("java", type=("build", "run"))
|
||||
depends_on("maven", type="build")
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "maven"
|
||||
|
||||
build_system("maven")
|
||||
|
||||
with when("build_system=maven"):
|
||||
depends_on("java", type=("build", "run"))
|
||||
depends_on("maven", type="build")
|
||||
|
||||
|
||||
@spack.builder.builder("maven")
|
||||
class MavenBuilder(BaseBuilder):
|
||||
"""The Maven builder encodes the default way to build software with Maven.
|
||||
It has two phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.MavenBuilder.build`
|
||||
2. :py:meth:`~.MavenBuilder.install`
|
||||
"""
|
||||
|
||||
phases = ("build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("build_args",)
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory",)
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The directory containing the ``pom.xml`` file."""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
def build_args(self):
|
||||
"""List of args to pass to build phase."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Compile code and package into a JAR file."""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
mvn = which("mvn")
|
||||
if self.run_tests:
|
||||
if self.pkg.run_tests:
|
||||
mvn("verify", *self.build_args())
|
||||
else:
|
||||
mvn("package", "-DskipTests", *self.build_args())
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Copy to installation prefix."""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
install_tree(".", prefix)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
with fs.working_dir(self.build_directory):
|
||||
fs.install_tree(".", prefix)
|
||||
|
@ -2,108 +2,131 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
import os
|
||||
from typing import List # novm
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
from spack.directives import depends_on, variant
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on, variant
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class MesonPackage(PackageBase):
|
||||
"""Specialized class for packages built using Meson
|
||||
class MesonPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using Meson. For more information
|
||||
on the Meson build system, see https://mesonbuild.com/
|
||||
"""
|
||||
|
||||
For more information on the Meson build system, see:
|
||||
https://mesonbuild.com/
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MesonPackage"
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "meson"
|
||||
|
||||
1. :py:meth:`~.MesonPackage.meson`
|
||||
2. :py:meth:`~.MesonPackage.build`
|
||||
3. :py:meth:`~.MesonPackage.install`
|
||||
build_system("meson")
|
||||
|
||||
with when("build_system=meson"):
|
||||
variant(
|
||||
"buildtype",
|
||||
default="debugoptimized",
|
||||
description="Meson build type",
|
||||
values=("plain", "debug", "debugoptimized", "release", "minsize"),
|
||||
)
|
||||
variant(
|
||||
"default_library",
|
||||
default="shared",
|
||||
values=("shared", "static"),
|
||||
multi=True,
|
||||
description="Build shared libs, static libs or both",
|
||||
)
|
||||
variant("strip", default=False, description="Strip targets on install")
|
||||
depends_on("meson", type="build")
|
||||
depends_on("ninja", type="build")
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass the specified
|
||||
compiler flags to meson."""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "meson_flag_args", [])
|
||||
|
||||
|
||||
@spack.builder.builder("meson")
|
||||
class MesonBuilder(BaseBuilder):
|
||||
"""The Meson builder encodes the default way to build software with Meson.
|
||||
The builder has three phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.MesonBuilder.meson`
|
||||
2. :py:meth:`~.MesonBuilder.build`
|
||||
3. :py:meth:`~.MesonBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.MesonPackage.meson_args`.
|
||||
necessary will be to override :py:meth:`~.MesonBuilder.meson_args`.
|
||||
|
||||
For a finer tuning you may also override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:meth:`~.MesonPackage.root_mesonlists_dir` | Location of the |
|
||||
| :py:meth:`~.MesonBuilder.root_mesonlists_dir` | Location of the |
|
||||
| | root MesonLists.txt|
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.MesonPackage.build_directory` | Directory where to |
|
||||
| :py:meth:`~.MesonBuilder.build_directory` | Directory where to |
|
||||
| | build the package |
|
||||
+-----------------------------------------------+--------------------+
|
||||
|
||||
|
||||
"""
|
||||
|
||||
#: Phases of a Meson package
|
||||
phases = ["meson", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MesonPackage"
|
||||
phases = ("meson", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("meson_args", "check")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"root_mesonlists_dir",
|
||||
"std_meson_args",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
build_targets = [] # type: List[str]
|
||||
install_targets = ["install"]
|
||||
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
variant(
|
||||
"buildtype",
|
||||
default="debugoptimized",
|
||||
description="Meson build type",
|
||||
values=("plain", "debug", "debugoptimized", "release", "minsize"),
|
||||
)
|
||||
variant(
|
||||
"default_library",
|
||||
default="shared",
|
||||
values=("shared", "static"),
|
||||
multi=True,
|
||||
description="Build shared libs, static libs or both",
|
||||
)
|
||||
variant("strip", default=False, description="Strip targets on install")
|
||||
|
||||
depends_on("meson", type="build")
|
||||
depends_on("ninja", type="build")
|
||||
|
||||
@property
|
||||
def archive_files(self):
|
||||
"""Files to archive for packages based on Meson"""
|
||||
return [os.path.join(self.build_directory, "meson-logs/meson-log.txt")]
|
||||
return [os.path.join(self.build_directory, "meson-logs", "meson-log.txt")]
|
||||
|
||||
@property
|
||||
def root_mesonlists_dir(self):
|
||||
"""The relative path to the directory containing meson.build
|
||||
"""Relative path to the directory containing meson.build
|
||||
|
||||
This path is relative to the root of the extracted tarball,
|
||||
not to the ``build_directory``. Defaults to the current directory.
|
||||
|
||||
:return: directory containing meson.build
|
||||
"""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
@property
|
||||
def std_meson_args(self):
|
||||
"""Standard meson arguments provided as a property for
|
||||
convenience of package writers
|
||||
|
||||
:return: standard meson arguments
|
||||
"""Standard meson arguments provided as a property for convenience
|
||||
of package writers.
|
||||
"""
|
||||
# standard Meson arguments
|
||||
std_meson_args = MesonPackage._std_args(self)
|
||||
std_meson_args = MesonBuilder.std_args(self.pkg)
|
||||
std_meson_args += getattr(self, "meson_flag_args", [])
|
||||
return std_meson_args
|
||||
|
||||
@staticmethod
|
||||
def _std_args(pkg):
|
||||
"""Computes the standard meson arguments for a generic package"""
|
||||
|
||||
def std_args(pkg):
|
||||
"""Standard meson arguments for a generic package."""
|
||||
try:
|
||||
build_type = pkg.spec.variants["buildtype"].value
|
||||
except KeyError:
|
||||
@ -119,44 +142,33 @@ def _std_args(pkg):
|
||||
default_library = "shared"
|
||||
|
||||
args = [
|
||||
"--prefix={0}".format(pkg.prefix),
|
||||
"-Dprefix={0}".format(pkg.prefix),
|
||||
# If we do not specify libdir explicitly, Meson chooses something
|
||||
# like lib/x86_64-linux-gnu, which causes problems when trying to
|
||||
# find libraries and pkg-config files.
|
||||
# See https://github.com/mesonbuild/meson/issues/2197
|
||||
"--libdir={0}".format(pkg.prefix.lib),
|
||||
"-Dlibdir={0}".format(pkg.prefix.lib),
|
||||
"-Dbuildtype={0}".format(build_type),
|
||||
"-Dstrip={0}".format(strip),
|
||||
"-Ddefault_library={0}".format(default_library),
|
||||
# Do not automatically download and install dependencies
|
||||
"-Dwrap_mode=nodownload",
|
||||
]
|
||||
|
||||
return args
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass the specified
|
||||
compiler flags to meson."""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "meson_flag_args", [])
|
||||
|
||||
@property
|
||||
def build_dirname(self):
|
||||
"""Returns the directory name to use when building the package
|
||||
|
||||
:return: name of the subdirectory for building the package
|
||||
"""
|
||||
return "spack-build-%s" % self.spec.dag_hash(7)
|
||||
"""Returns the directory name to use when building the package."""
|
||||
return "spack-build-{}".format(self.spec.dag_hash(7))
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Returns the directory to use when building the package
|
||||
|
||||
:return: directory where to build the package
|
||||
"""
|
||||
return os.path.join(self.stage.path, self.build_dirname)
|
||||
"""Directory to use when building the package."""
|
||||
return os.path.join(self.pkg.stage.path, self.build_dirname)
|
||||
|
||||
def meson_args(self):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
meson, except:
|
||||
"""List of arguments that must be passed to meson, except:
|
||||
|
||||
* ``--prefix``
|
||||
* ``--libdir``
|
||||
@ -165,40 +177,33 @@ def meson_args(self):
|
||||
* ``--default_library``
|
||||
|
||||
which will be set automatically.
|
||||
|
||||
:return: list of arguments for meson
|
||||
"""
|
||||
return []
|
||||
|
||||
def meson(self, spec, prefix):
|
||||
"""Runs ``meson`` in the build directory"""
|
||||
def meson(self, pkg, spec, prefix):
|
||||
"""Run ``meson`` in the build directory"""
|
||||
options = [os.path.abspath(self.root_mesonlists_dir)]
|
||||
options += self.std_meson_args
|
||||
options += self.meson_args()
|
||||
with working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self).meson(*options)
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).meson(*options)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
options = ["-v"]
|
||||
options += self.build_targets
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).ninja(*options)
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).ninja(*options)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).ninja(*self.install_targets)
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).ninja(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the Meson-generated file for the target ``test``
|
||||
and runs it if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
"""Search Meson-generated files for the target ``test`` and run it if found."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
self._if_ninja_target_execute("test")
|
||||
self._if_ninja_target_execute("check")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
102
lib/spack/spack/build_systems/nmake.py
Normal file
102
lib/spack/spack/build_systems/nmake.py
Normal file
@ -0,0 +1,102 @@
|
||||
# Copyright 2013-2022 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 inspect
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, conflicts
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
|
||||
class NMakePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using a Makefiles."""
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "NmakePackage"
|
||||
|
||||
build_system("nmake")
|
||||
conflicts("platform=linux", when="build_system=nmake")
|
||||
conflicts("platform=darwin", when="build_system=nmake")
|
||||
conflicts("platform=cray", when="build_system=nmake")
|
||||
|
||||
|
||||
@spack.builder.builder("nmake")
|
||||
class NMakeBuilder(BaseBuilder):
|
||||
"""The NMake builder encodes the most common way of building software with
|
||||
NMake on Windows. It has three phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.NMakeBuilder.edit`
|
||||
2. :py:meth:`~.NMakeBuilder.build`
|
||||
3. :py:meth:`~.NMakeBuilder.install`
|
||||
|
||||
It is usually necessary to override the :py:meth:`~.NMakeBuilder.edit`
|
||||
phase (which is by default a no-op), while the other two have sensible defaults.
|
||||
|
||||
For a finer tuning you may override:
|
||||
|
||||
+--------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+============================================+====================+
|
||||
| :py:attr:`~.NMakeBuilder.build_targets` | Specify ``nmake`` |
|
||||
| | targets for the |
|
||||
| | build phase |
|
||||
+--------------------------------------------+--------------------+
|
||||
| :py:attr:`~.NMakeBuilder.install_targets` | Specify ``nmake`` |
|
||||
| | targets for the |
|
||||
| | install phase |
|
||||
+--------------------------------------------+--------------------+
|
||||
| :py:meth:`~.NMakeBuilder.build_directory` | Directory where the|
|
||||
| | Makefile is located|
|
||||
+--------------------------------------------+--------------------+
|
||||
"""
|
||||
|
||||
phases = ("edit", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("check", "installcheck")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.build` phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.install` phase
|
||||
install_targets = ["install"]
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["installcheck"]
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Return the directory containing the main Makefile."""
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
def edit(self, pkg, spec, prefix):
|
||||
"""Edit the Makefile before calling make. The default is a no-op."""
|
||||
pass
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).nmake(*self.build_targets)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).nmake(*self.install_targets)
|
@ -2,51 +2,62 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import inspect
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, extends
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
|
||||
class OctavePackage(PackageBase):
|
||||
class OctavePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for Octave packages. See
|
||||
https://www.gnu.org/software/octave/doc/v4.2.0/Installing-and-Removing-Packages.html
|
||||
for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.OctavePackage.install`
|
||||
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "OctavePackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "octave"
|
||||
|
||||
extends("octave")
|
||||
build_system("octave")
|
||||
|
||||
with when("build_system=octave"):
|
||||
extends("octave")
|
||||
|
||||
|
||||
@spack.builder.builder("octave")
|
||||
class OctaveBuilder(BaseBuilder):
|
||||
"""The octave builder provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.OctaveBuilder.install`
|
||||
"""
|
||||
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ()
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package from the archive file"""
|
||||
inspect.getmodule(self.pkg).octave(
|
||||
"--quiet",
|
||||
"--norc",
|
||||
"--built-in-docstrings-file=/dev/null",
|
||||
"--texi-macros-file=/dev/null",
|
||||
"--eval",
|
||||
"pkg prefix %s; pkg install %s" % (prefix, self.pkg.stage.archive_file),
|
||||
)
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
# octave does not like those environment variables to be set:
|
||||
env.unset("CC")
|
||||
env.unset("CXX")
|
||||
env.unset("FC")
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Install the package from the archive file"""
|
||||
inspect.getmodule(self).octave(
|
||||
"--quiet",
|
||||
"--norc",
|
||||
"--built-in-docstrings-file=/dev/null",
|
||||
"--texi-macros-file=/dev/null",
|
||||
"--eval",
|
||||
"pkg prefix %s; pkg install %s" % (prefix, self.stage.archive_file),
|
||||
)
|
||||
|
||||
# Testing
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
@ -2,11 +2,7 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Common utilities for managing intel oneapi packages.
|
||||
|
||||
"""
|
||||
|
||||
"""Common utilities for managing intel oneapi packages."""
|
||||
import getpass
|
||||
import platform
|
||||
import shutil
|
||||
@ -14,18 +10,17 @@
|
||||
|
||||
from llnl.util.filesystem import find_headers, find_libraries, join_path
|
||||
|
||||
from spack.package_base import Package
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.util.executable import Executable
|
||||
|
||||
from .generic import Package
|
||||
|
||||
|
||||
class IntelOneApiPackage(Package):
|
||||
"""Base class for Intel oneAPI packages."""
|
||||
|
||||
homepage = "https://software.intel.com/oneapi"
|
||||
|
||||
phases = ["install"]
|
||||
|
||||
# oneAPI license does not allow mirroring outside of the
|
||||
# organization (e.g. University/Company).
|
||||
redistribute_source = False
|
||||
|
@ -2,73 +2,87 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from llnl.util.filesystem import filter_file
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, extends
|
||||
from spack.package_base import PackageBase
|
||||
from spack.util.executable import Executable
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class PerlPackage(PackageBase):
|
||||
"""Specialized class for packages that are built using Perl.
|
||||
"""Specialized class for packages that are built using Perl."""
|
||||
|
||||
This class provides four phases that can be overridden if required:
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "PerlPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "perl"
|
||||
|
||||
1. :py:meth:`~.PerlPackage.configure`
|
||||
2. :py:meth:`~.PerlPackage.build`
|
||||
3. :py:meth:`~.PerlPackage.check`
|
||||
4. :py:meth:`~.PerlPackage.install`
|
||||
build_system("perl")
|
||||
|
||||
extends("perl", when="build_system=perl")
|
||||
|
||||
|
||||
@spack.builder.builder("perl")
|
||||
class PerlBuilder(BaseBuilder):
|
||||
"""The perl builder provides four phases that can be overridden, if required:
|
||||
|
||||
1. :py:meth:`~.PerlBuilder.configure`
|
||||
2. :py:meth:`~.PerlBuilder.build`
|
||||
3. :py:meth:`~.PerlBuilder.check`
|
||||
4. :py:meth:`~.PerlBuilder.install`
|
||||
|
||||
The default methods use, in order of preference:
|
||||
(1) Makefile.PL,
|
||||
(2) Build.PL.
|
||||
|
||||
Some packages may need to override
|
||||
:py:meth:`~.PerlPackage.configure_args`,
|
||||
which produces a list of arguments for
|
||||
:py:meth:`~.PerlPackage.configure`.
|
||||
Some packages may need to override :py:meth:`~.PerlBuilder.configure_args`,
|
||||
which produces a list of arguments for :py:meth:`~.PerlBuilder.configure`.
|
||||
|
||||
Arguments should not include the installation base directory.
|
||||
"""
|
||||
|
||||
#: Phases of a Perl package
|
||||
phases = ["configure", "build", "install"]
|
||||
phases = ("configure", "build", "install")
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "PerlPackage"
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("configure_args", "check")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
extends("perl")
|
||||
|
||||
def configure_args(self):
|
||||
"""Produces a list containing the arguments that must be passed to
|
||||
:py:meth:`~.PerlPackage.configure`. Arguments should not include
|
||||
the installation base directory, which is prepended automatically.
|
||||
"""List of arguments passed to :py:meth:`~.PerlBuilder.configure`.
|
||||
|
||||
:return: list of arguments for Makefile.PL or Build.PL
|
||||
Arguments should not include the installation base directory, which
|
||||
is prepended automatically.
|
||||
"""
|
||||
return []
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
"""Runs Makefile.PL or Build.PL with arguments consisting of
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Run Makefile.PL or Build.PL with arguments consisting of
|
||||
an appropriate installation base directory followed by the
|
||||
list returned by :py:meth:`~.PerlPackage.configure_args`.
|
||||
list returned by :py:meth:`~.PerlBuilder.configure_args`.
|
||||
|
||||
:raise RuntimeError: if neither Makefile.PL or Build.PL exist
|
||||
Raises:
|
||||
RuntimeError: if neither Makefile.PL nor Build.PL exist
|
||||
"""
|
||||
if os.path.isfile("Makefile.PL"):
|
||||
self.build_method = "Makefile.PL"
|
||||
self.build_executable = inspect.getmodule(self).make
|
||||
self.build_executable = inspect.getmodule(self.pkg).make
|
||||
elif os.path.isfile("Build.PL"):
|
||||
self.build_method = "Build.PL"
|
||||
self.build_executable = Executable(os.path.join(self.stage.source_path, "Build"))
|
||||
self.build_executable = Executable(os.path.join(self.pkg.stage.source_path, "Build"))
|
||||
else:
|
||||
raise RuntimeError("Unknown build_method for perl package")
|
||||
|
||||
@ -78,33 +92,30 @@ def configure(self, spec, prefix):
|
||||
options = ["Build.PL", "--install_base", prefix]
|
||||
options += self.configure_args()
|
||||
|
||||
inspect.getmodule(self).perl(*options)
|
||||
inspect.getmodule(self.pkg).perl(*options)
|
||||
|
||||
# It is possible that the shebang in the Build script that is created from
|
||||
# Build.PL may be too long causing the build to fail. Patching the shebang
|
||||
# does not happen until after install so set '/usr/bin/env perl' here in
|
||||
# the Build script.
|
||||
@run_after("configure")
|
||||
@spack.builder.run_after("configure")
|
||||
def fix_shebang(self):
|
||||
if self.build_method == "Build.PL":
|
||||
pattern = "#!{0}".format(self.spec["perl"].command.path)
|
||||
repl = "#!/usr/bin/env perl"
|
||||
filter_file(pattern, repl, "Build", backup=False)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Builds a Perl package."""
|
||||
self.build_executable()
|
||||
|
||||
# Ensure that tests run after build (if requested):
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Runs built-in tests of a Perl package."""
|
||||
self.build_executable("test")
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Installs a Perl package."""
|
||||
self.build_executable("install")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
@ -8,93 +8,23 @@
|
||||
import shutil
|
||||
from typing import Optional
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang as lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import (
|
||||
filter_file,
|
||||
find,
|
||||
find_all_headers,
|
||||
find_libraries,
|
||||
is_nonsymlink_exe_with_shebang,
|
||||
path_contains_subdirectory,
|
||||
same_path,
|
||||
working_dir,
|
||||
)
|
||||
from llnl.util.lang import classproperty, match_predicate
|
||||
|
||||
from spack.directives import depends_on, extends
|
||||
import spack.builder
|
||||
import spack.multimethod
|
||||
import spack.package_base
|
||||
import spack.spec
|
||||
from spack.directives import build_system, depends_on, extends
|
||||
from spack.error import NoHeadersError, NoLibrariesError, SpecError
|
||||
from spack.package_base import PackageBase, run_after
|
||||
from spack.version import Version
|
||||
|
||||
from ._checks import BaseBuilder, execute_install_time_tests
|
||||
|
||||
class PythonPackage(PackageBase):
|
||||
"""Specialized class for packages that are built using pip."""
|
||||
|
||||
#: Package name, version, and extension on PyPI
|
||||
pypi = None # type: Optional[str]
|
||||
|
||||
maintainers = ["adamjstewart", "pradyunsg"]
|
||||
|
||||
# Default phases
|
||||
phases = ["install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "PythonPackage"
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
|
||||
extends("python")
|
||||
depends_on("py-pip", type="build")
|
||||
# FIXME: technically wheel is only needed when building from source, not when
|
||||
# installing a downloaded wheel, but I don't want to add wheel as a dep to every
|
||||
# package manually
|
||||
depends_on("py-wheel", type="build")
|
||||
|
||||
py_namespace = None # type: Optional[str]
|
||||
|
||||
@staticmethod
|
||||
def _std_args(cls):
|
||||
return [
|
||||
# Verbose
|
||||
"-vvv",
|
||||
# Disable prompting for input
|
||||
"--no-input",
|
||||
# Disable the cache
|
||||
"--no-cache-dir",
|
||||
# Don't check to see if pip is up-to-date
|
||||
"--disable-pip-version-check",
|
||||
# Install packages
|
||||
"install",
|
||||
# Don't install package dependencies
|
||||
"--no-deps",
|
||||
# Overwrite existing packages
|
||||
"--ignore-installed",
|
||||
# Use env vars like PYTHONPATH
|
||||
"--no-build-isolation",
|
||||
# Don't warn that prefix.bin is not in PATH
|
||||
"--no-warn-script-location",
|
||||
# Ignore the PyPI package index
|
||||
"--no-index",
|
||||
]
|
||||
|
||||
@classproperty
|
||||
def homepage(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/project/" + name + "/"
|
||||
|
||||
@classproperty
|
||||
def url(cls):
|
||||
if cls.pypi:
|
||||
return "https://files.pythonhosted.org/packages/source/" + cls.pypi[0] + "/" + cls.pypi
|
||||
|
||||
@classproperty
|
||||
def list_url(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/simple/" + name + "/"
|
||||
class PythonExtension(spack.package_base.PackageBase):
|
||||
maintainers = ["adamjstewart"]
|
||||
|
||||
@property
|
||||
def import_modules(self):
|
||||
@ -124,7 +54,7 @@ def import_modules(self):
|
||||
|
||||
# Some Python libraries are packages: collections of modules
|
||||
# distributed in directories containing __init__.py files
|
||||
for path in find(root, "__init__.py", recursive=True):
|
||||
for path in fs.find(root, "__init__.py", recursive=True):
|
||||
modules.append(
|
||||
path.replace(root + os.sep, "", 1)
|
||||
.replace(os.sep + "__init__.py", "")
|
||||
@ -133,7 +63,7 @@ def import_modules(self):
|
||||
|
||||
# Some Python libraries are modules: individual *.py files
|
||||
# found in the site-packages directory
|
||||
for path in find(root, "*.py", recursive=False):
|
||||
for path in fs.find(root, "*.py", recursive=False):
|
||||
modules.append(
|
||||
path.replace(root + os.sep, "", 1).replace(".py", "").replace("/", ".")
|
||||
)
|
||||
@ -160,6 +90,229 @@ def skip_modules(self):
|
||||
"""
|
||||
return []
|
||||
|
||||
def view_file_conflicts(self, view, merge_map):
|
||||
"""Report all file conflicts, excepting special cases for python.
|
||||
Specifically, this does not report errors for duplicate
|
||||
__init__.py files for packages in the same namespace.
|
||||
"""
|
||||
conflicts = list(dst for src, dst in merge_map.items() if os.path.exists(dst))
|
||||
|
||||
if conflicts and self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
namespaces = set(x.package.py_namespace for x in ext_map.values())
|
||||
namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
find_namespace = lang.match_predicate(namespace_re)
|
||||
if self.py_namespace in namespaces:
|
||||
conflicts = list(x for x in conflicts if not find_namespace(x))
|
||||
|
||||
return conflicts
|
||||
|
||||
def add_files_to_view(self, view, merge_map, skip_if_exists=True):
|
||||
bin_dir = self.spec.prefix.bin
|
||||
python_prefix = self.extendee_spec.prefix
|
||||
python_is_external = self.extendee_spec.external
|
||||
global_view = fs.same_path(python_prefix, view.get_projection_for_spec(self.spec))
|
||||
for src, dst in merge_map.items():
|
||||
if os.path.exists(dst):
|
||||
continue
|
||||
elif global_view or not fs.path_contains_subdirectory(src, bin_dir):
|
||||
view.link(src, dst)
|
||||
elif not os.path.islink(src):
|
||||
shutil.copy2(src, dst)
|
||||
is_script = fs.is_nonsymlink_exe_with_shebang(src)
|
||||
if is_script and not python_is_external:
|
||||
fs.filter_file(
|
||||
python_prefix,
|
||||
os.path.abspath(view.get_projection_for_spec(self.spec)),
|
||||
dst,
|
||||
)
|
||||
else:
|
||||
orig_link_target = os.path.realpath(src)
|
||||
new_link_target = os.path.abspath(merge_map[orig_link_target])
|
||||
view.link(new_link_target, dst)
|
||||
|
||||
def remove_files_from_view(self, view, merge_map):
|
||||
ignore_namespace = False
|
||||
if self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
remaining_namespaces = set(
|
||||
spec.package.py_namespace for name, spec in ext_map.items() if name != self.name
|
||||
)
|
||||
if self.py_namespace in remaining_namespaces:
|
||||
namespace_init = lang.match_predicate(
|
||||
r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
)
|
||||
ignore_namespace = True
|
||||
|
||||
bin_dir = self.spec.prefix.bin
|
||||
global_view = self.extendee_spec.prefix == view.get_projection_for_spec(self.spec)
|
||||
|
||||
to_remove = []
|
||||
for src, dst in merge_map.items():
|
||||
if ignore_namespace and namespace_init(dst):
|
||||
continue
|
||||
|
||||
if global_view or not fs.path_contains_subdirectory(src, bin_dir):
|
||||
to_remove.append(dst)
|
||||
else:
|
||||
os.remove(dst)
|
||||
|
||||
view.remove_files(to_remove)
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
|
||||
class PythonPackage(PythonExtension):
|
||||
"""Specialized class for packages that are built using pip."""
|
||||
|
||||
#: Package name, version, and extension on PyPI
|
||||
pypi = None # type: Optional[str]
|
||||
|
||||
maintainers = ["adamjstewart", "pradyunsg"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "PythonPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "python_pip"
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
|
||||
build_system("python_pip")
|
||||
|
||||
with spack.multimethod.when("build_system=python_pip"):
|
||||
extends("python")
|
||||
depends_on("py-pip", type="build")
|
||||
# FIXME: technically wheel is only needed when building from source, not when
|
||||
# installing a downloaded wheel, but I don't want to add wheel as a dep to every
|
||||
# package manually
|
||||
depends_on("py-wheel", type="build")
|
||||
|
||||
py_namespace = None # type: Optional[str]
|
||||
|
||||
@lang.classproperty
|
||||
def homepage(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/project/" + name + "/"
|
||||
|
||||
@lang.classproperty
|
||||
def url(cls):
|
||||
if cls.pypi:
|
||||
return "https://files.pythonhosted.org/packages/source/" + cls.pypi[0] + "/" + cls.pypi
|
||||
|
||||
@lang.classproperty
|
||||
def list_url(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/simple/" + name + "/"
|
||||
|
||||
def update_external_dependencies(self):
|
||||
"""
|
||||
Ensure all external python packages have a python dependency
|
||||
|
||||
If another package in the DAG depends on python, we use that
|
||||
python for the dependency of the external. If not, we assume
|
||||
that the external PythonPackage is installed into the same
|
||||
directory as the python it depends on.
|
||||
"""
|
||||
# TODO: Include this in the solve, rather than instantiating post-concretization
|
||||
if "python" not in self.spec:
|
||||
if "python" in self.spec.root:
|
||||
python = self.spec.root["python"]
|
||||
else:
|
||||
python = spack.spec.Spec("python")
|
||||
repo = spack.repo.path.repo_for_pkg(python)
|
||||
python.namespace = repo.namespace
|
||||
python._mark_concrete()
|
||||
python.external_path = self.prefix
|
||||
self.spec.add_dependency_edge(python, ("build", "link", "run"))
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
"""Discover header files in platlib."""
|
||||
|
||||
# Headers may be in either location
|
||||
include = self.prefix.join(self.spec["python"].package.include)
|
||||
platlib = self.prefix.join(self.spec["python"].package.platlib)
|
||||
headers = fs.find_all_headers(include) + fs.find_all_headers(platlib)
|
||||
|
||||
if headers:
|
||||
return headers
|
||||
|
||||
msg = "Unable to locate {} headers in {} or {}"
|
||||
raise NoHeadersError(msg.format(self.spec.name, include, platlib))
|
||||
|
||||
@property
|
||||
def libs(self):
|
||||
"""Discover libraries in platlib."""
|
||||
|
||||
# Remove py- prefix in package name
|
||||
library = "lib" + self.spec.name[3:].replace("-", "?")
|
||||
root = self.prefix.join(self.spec["python"].package.platlib)
|
||||
|
||||
for shared in [True, False]:
|
||||
libs = fs.find_libraries(library, root, shared=shared, recursive=True)
|
||||
if libs:
|
||||
return libs
|
||||
|
||||
msg = "Unable to recursively locate {} libraries in {}"
|
||||
raise NoLibrariesError(msg.format(self.spec.name, root))
|
||||
|
||||
|
||||
@spack.builder.builder("python_pip")
|
||||
class PythonPipBuilder(BaseBuilder):
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("test",)
|
||||
|
||||
#: Same as legacy_methods, but the signature is different
|
||||
legacy_long_methods = ("install_options", "global_options", "config_settings")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory", "install_time_test_callbacks")
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
|
||||
@staticmethod
|
||||
def std_args(cls):
|
||||
return [
|
||||
# Verbose
|
||||
"-vvv",
|
||||
# Disable prompting for input
|
||||
"--no-input",
|
||||
# Disable the cache
|
||||
"--no-cache-dir",
|
||||
# Don't check to see if pip is up-to-date
|
||||
"--disable-pip-version-check",
|
||||
# Install packages
|
||||
"install",
|
||||
# Don't install package dependencies
|
||||
"--no-deps",
|
||||
# Overwrite existing packages
|
||||
"--ignore-installed",
|
||||
# Use env vars like PYTHONPATH
|
||||
"--no-build-isolation",
|
||||
# Don't warn that prefix.bin is not in PATH
|
||||
"--no-warn-script-location",
|
||||
# Ignore the PyPI package index
|
||||
"--no-index",
|
||||
]
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The root directory of the Python package.
|
||||
@ -170,11 +323,10 @@ def build_directory(self):
|
||||
* ``setup.cfg``
|
||||
* ``setup.py``
|
||||
"""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
def config_settings(self, spec, prefix):
|
||||
"""Configuration settings to be passed to the PEP 517 build backend.
|
||||
|
||||
Requires pip 22.1+, which requires Python 3.7+.
|
||||
|
||||
Args:
|
||||
@ -211,10 +363,10 @@ def global_options(self, spec, prefix):
|
||||
"""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install everything from build directory."""
|
||||
|
||||
args = PythonPackage._std_args(self) + ["--prefix=" + prefix]
|
||||
args = PythonPipBuilder.std_args(pkg) + ["--prefix=" + prefix]
|
||||
|
||||
for key, value in self.config_settings(spec, prefix).items():
|
||||
if spec["py-pip"].version < Version("22.1"):
|
||||
@ -223,137 +375,21 @@ def install(self, spec, prefix):
|
||||
"pip 22.1+. Add the following line to the package to fix this:\n\n"
|
||||
' depends_on("py-pip@22.1:", type="build")'.format(spec.name)
|
||||
)
|
||||
|
||||
args.append("--config-settings={}={}".format(key, value))
|
||||
|
||||
for option in self.install_options(spec, prefix):
|
||||
args.append("--install-option=" + option)
|
||||
for option in self.global_options(spec, prefix):
|
||||
args.append("--global-option=" + option)
|
||||
|
||||
if self.stage.archive_file and self.stage.archive_file.endswith(".whl"):
|
||||
args.append(self.stage.archive_file)
|
||||
if pkg.stage.archive_file and pkg.stage.archive_file.endswith(".whl"):
|
||||
args.append(pkg.stage.archive_file)
|
||||
else:
|
||||
args.append(".")
|
||||
|
||||
pip = inspect.getmodule(self).pip
|
||||
with working_dir(self.build_directory):
|
||||
pip = inspect.getmodule(pkg).pip
|
||||
with fs.working_dir(self.build_directory):
|
||||
pip(*args)
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
"""Discover header files in platlib."""
|
||||
|
||||
# Headers may be in either location
|
||||
include = self.prefix.join(self.spec["python"].package.include)
|
||||
platlib = self.prefix.join(self.spec["python"].package.platlib)
|
||||
headers = find_all_headers(include) + find_all_headers(platlib)
|
||||
|
||||
if headers:
|
||||
return headers
|
||||
|
||||
msg = "Unable to locate {} headers in {} or {}"
|
||||
raise NoHeadersError(msg.format(self.spec.name, include, platlib))
|
||||
|
||||
@property
|
||||
def libs(self):
|
||||
"""Discover libraries in platlib."""
|
||||
|
||||
# Remove py- prefix in package name
|
||||
library = "lib" + self.spec.name[3:].replace("-", "?")
|
||||
root = self.prefix.join(self.spec["python"].package.platlib)
|
||||
|
||||
for shared in [True, False]:
|
||||
libs = find_libraries(library, root, shared=shared, recursive=True)
|
||||
if libs:
|
||||
return libs
|
||||
|
||||
msg = "Unable to recursively locate {} libraries in {}"
|
||||
raise NoLibrariesError(msg.format(self.spec.name, root))
|
||||
|
||||
# Testing
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
def view_file_conflicts(self, view, merge_map):
|
||||
"""Report all file conflicts, excepting special cases for python.
|
||||
Specifically, this does not report errors for duplicate
|
||||
__init__.py files for packages in the same namespace.
|
||||
"""
|
||||
conflicts = list(dst for src, dst in merge_map.items() if os.path.exists(dst))
|
||||
|
||||
if conflicts and self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
namespaces = set(x.package.py_namespace for x in ext_map.values())
|
||||
namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
find_namespace = match_predicate(namespace_re)
|
||||
if self.py_namespace in namespaces:
|
||||
conflicts = list(x for x in conflicts if not find_namespace(x))
|
||||
|
||||
return conflicts
|
||||
|
||||
def add_files_to_view(self, view, merge_map, skip_if_exists=True):
|
||||
bin_dir = self.spec.prefix.bin
|
||||
python_prefix = self.extendee_spec.prefix
|
||||
python_is_external = self.extendee_spec.external
|
||||
global_view = same_path(python_prefix, view.get_projection_for_spec(self.spec))
|
||||
for src, dst in merge_map.items():
|
||||
if os.path.exists(dst):
|
||||
continue
|
||||
elif global_view or not path_contains_subdirectory(src, bin_dir):
|
||||
view.link(src, dst)
|
||||
elif not os.path.islink(src):
|
||||
shutil.copy2(src, dst)
|
||||
is_script = is_nonsymlink_exe_with_shebang(src)
|
||||
if is_script and not python_is_external:
|
||||
filter_file(
|
||||
python_prefix,
|
||||
os.path.abspath(view.get_projection_for_spec(self.spec)),
|
||||
dst,
|
||||
)
|
||||
else:
|
||||
orig_link_target = os.path.realpath(src)
|
||||
new_link_target = os.path.abspath(merge_map[orig_link_target])
|
||||
view.link(new_link_target, dst)
|
||||
|
||||
def remove_files_from_view(self, view, merge_map):
|
||||
ignore_namespace = False
|
||||
if self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
remaining_namespaces = set(
|
||||
spec.package.py_namespace for name, spec in ext_map.items() if name != self.name
|
||||
)
|
||||
if self.py_namespace in remaining_namespaces:
|
||||
namespace_init = match_predicate(
|
||||
r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
)
|
||||
ignore_namespace = True
|
||||
|
||||
bin_dir = self.spec.prefix.bin
|
||||
global_view = self.extendee_spec.prefix == view.get_projection_for_spec(self.spec)
|
||||
|
||||
to_remove = []
|
||||
for src, dst in merge_map.items():
|
||||
if ignore_namespace and namespace_init(dst):
|
||||
continue
|
||||
|
||||
if global_view or not path_contains_subdirectory(src, bin_dir):
|
||||
to_remove.append(dst)
|
||||
else:
|
||||
os.remove(dst)
|
||||
|
||||
view.remove_files(to_remove)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
@ -2,82 +2,85 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class QMakePackage(PackageBase):
|
||||
class QMakePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using qmake.
|
||||
|
||||
For more information on the qmake build system, see:
|
||||
http://doc.qt.io/qt-5/qmake-manual.html
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.QMakePackage.qmake`
|
||||
2. :py:meth:`~.QMakePackage.build`
|
||||
3. :py:meth:`~.QMakePackage.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.QMakePackage.qmake_args`.
|
||||
"""
|
||||
|
||||
#: Phases of a qmake package
|
||||
phases = ["qmake", "build", "install"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "QMakePackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "qmake"
|
||||
|
||||
build_system("qmake")
|
||||
|
||||
depends_on("qt", type="build", when="build_system=qmake")
|
||||
|
||||
|
||||
@spack.builder.builder("qmake")
|
||||
class QMakeBuilder(BaseBuilder):
|
||||
"""The qmake builder provides three phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.QMakeBuilder.qmake`
|
||||
2. :py:meth:`~.QMakeBuilder.build`
|
||||
3. :py:meth:`~.QMakeBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.QMakeBuilder.qmake_args`.
|
||||
"""
|
||||
|
||||
phases = ("qmake", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("qmake_args", "check")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory", "build_time_test_callbacks")
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
depends_on("qt", type="build")
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The directory containing the ``*.pro`` file."""
|
||||
return self.stage.source_path
|
||||
|
||||
def qmake_args(self):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
qmake
|
||||
"""
|
||||
"""List of arguments passed to qmake."""
|
||||
return []
|
||||
|
||||
def qmake(self, spec, prefix):
|
||||
def qmake(self, pkg, spec, prefix):
|
||||
"""Run ``qmake`` to configure the project and generate a Makefile."""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).qmake(*self.qmake_args())
|
||||
inspect.getmodule(self.pkg).qmake(*self.qmake_args())
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make()
|
||||
inspect.getmodule(self.pkg).make()
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make("install")
|
||||
|
||||
# Tests
|
||||
inspect.getmodule(self.pkg).make("install")
|
||||
|
||||
def check(self):
|
||||
"""Searches the Makefile for a ``check:`` target and runs it if found."""
|
||||
|
||||
"""Search the Makefile for a ``check:`` target and runs it if found."""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("check")
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
@ -3,30 +3,64 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import llnl.util.lang as lang
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
|
||||
from .generic import GenericBuilder, Package
|
||||
|
||||
|
||||
class RPackage(PackageBase):
|
||||
class RBuilder(GenericBuilder):
|
||||
"""The R builder provides a single phase that can be overridden:
|
||||
|
||||
1. :py:meth:`~.RBuilder.install`
|
||||
|
||||
It has sensible defaults, and for many packages the only thing
|
||||
necessary will be to add dependencies.
|
||||
"""
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = (
|
||||
"configure_args",
|
||||
"configure_vars",
|
||||
) + GenericBuilder.legacy_methods # type: Tuple[str, ...]
|
||||
|
||||
def configure_args(self):
|
||||
"""Arguments to pass to install via ``--configure-args``."""
|
||||
return []
|
||||
|
||||
def configure_vars(self):
|
||||
"""Arguments to pass to install via ``--configure-vars``."""
|
||||
return []
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Installs an R package."""
|
||||
|
||||
config_args = self.configure_args()
|
||||
config_vars = self.configure_vars()
|
||||
|
||||
args = ["--vanilla", "CMD", "INSTALL"]
|
||||
|
||||
if config_args:
|
||||
args.append("--configure-args={0}".format(" ".join(config_args)))
|
||||
|
||||
if config_vars:
|
||||
args.append("--configure-vars={0}".format(" ".join(config_vars)))
|
||||
|
||||
args.extend(["--library={0}".format(self.pkg.module.r_lib_dir), self.stage.source_path])
|
||||
|
||||
inspect.getmodule(self.pkg).R(*args)
|
||||
|
||||
|
||||
class RPackage(Package):
|
||||
"""Specialized class for packages that are built using R.
|
||||
|
||||
For more information on the R build system, see:
|
||||
https://stat.ethz.ch/R-manual/R-devel/library/utils/html/INSTALL.html
|
||||
|
||||
This class provides a single phase that can be overridden:
|
||||
|
||||
1. :py:meth:`~.RPackage.install`
|
||||
|
||||
It has sensible defaults, and for many packages the only thing
|
||||
necessary will be to add dependencies
|
||||
"""
|
||||
|
||||
phases = ["install"]
|
||||
|
||||
# package attributes that can be expanded to set the homepage, url,
|
||||
# list_url, and git values
|
||||
# For CRAN packages
|
||||
@ -35,6 +69,8 @@ class RPackage(PackageBase):
|
||||
# For Bioconductor packages
|
||||
bioc = None # type: Optional[str]
|
||||
|
||||
GenericBuilder = RBuilder
|
||||
|
||||
maintainers = ["glennpj"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
@ -70,32 +106,3 @@ def list_url(cls):
|
||||
def git(self):
|
||||
if self.bioc:
|
||||
return "https://git.bioconductor.org/packages/" + self.bioc
|
||||
|
||||
def configure_args(self):
|
||||
"""Arguments to pass to install via ``--configure-args``."""
|
||||
return []
|
||||
|
||||
def configure_vars(self):
|
||||
"""Arguments to pass to install via ``--configure-vars``."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Installs an R package."""
|
||||
|
||||
config_args = self.configure_args()
|
||||
config_vars = self.configure_vars()
|
||||
|
||||
args = ["--vanilla", "CMD", "INSTALL"]
|
||||
|
||||
if config_args:
|
||||
args.append("--configure-args={0}".format(" ".join(config_args)))
|
||||
|
||||
if config_vars:
|
||||
args.append("--configure-vars={0}".format(" ".join(config_vars)))
|
||||
|
||||
args.extend(["--library={0}".format(self.module.r_lib_dir), self.stage.source_path])
|
||||
|
||||
inspect.getmodule(self).R(*args)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
@ -3,14 +3,15 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import os
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang as lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
import spack.builder
|
||||
from spack.build_environment import SPACK_NO_PARALLEL_MAKE, determine_number_of_jobs
|
||||
from spack.directives import extends
|
||||
from spack.directives import build_system, extends
|
||||
from spack.package_base import PackageBase
|
||||
from spack.util.environment import env_flag
|
||||
from spack.util.executable import Executable, ProcessError
|
||||
@ -19,34 +20,52 @@
|
||||
class RacketPackage(PackageBase):
|
||||
"""Specialized class for packages that are built using Racket's
|
||||
`raco pkg install` and `raco setup` commands.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
* install
|
||||
* setup
|
||||
"""
|
||||
|
||||
#: Package name, version, and extension on PyPI
|
||||
maintainers = ["elfprince13"]
|
||||
|
||||
# Default phases
|
||||
phases = ["install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "RacketPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "racket"
|
||||
|
||||
extends("racket")
|
||||
build_system("racket")
|
||||
|
||||
extends("racket", when="build_system=racket")
|
||||
|
||||
pkgs = False
|
||||
subdirectory = None # type: Optional[str]
|
||||
racket_name = None # type: Optional[str]
|
||||
parallel = True
|
||||
|
||||
@lang.classproperty
|
||||
def homepage(cls):
|
||||
if cls.pkgs:
|
||||
if cls.racket_name:
|
||||
return "https://pkgs.racket-lang.org/package/{0}".format(cls.racket_name)
|
||||
return None
|
||||
|
||||
|
||||
@spack.builder.builder("racket")
|
||||
class RacketBuilder(spack.builder.Builder):
|
||||
"""The Racket builder provides an ``install`` phase that can be overridden."""
|
||||
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = tuple() # type: Tuple[str, ...]
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory", "build_time_test_callbacks", "subdirectory")
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
racket_name = None # type: Optional[str]
|
||||
|
||||
@property
|
||||
def subdirectory(self):
|
||||
if self.racket_name:
|
||||
return "pkgs/{0}".format(self.pkg.racket_name)
|
||||
return None
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
@ -55,25 +74,25 @@ def build_directory(self):
|
||||
ret = os.path.join(ret, self.subdirectory)
|
||||
return ret
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install everything from build directory."""
|
||||
raco = Executable("raco")
|
||||
with working_dir(self.build_directory):
|
||||
allow_parallel = self.parallel and (not env_flag(SPACK_NO_PARALLEL_MAKE))
|
||||
with fs.working_dir(self.build_directory):
|
||||
parallel = self.pkg.parallel and (not env_flag(SPACK_NO_PARALLEL_MAKE))
|
||||
args = [
|
||||
"pkg",
|
||||
"install",
|
||||
"-t",
|
||||
"dir",
|
||||
"-n",
|
||||
self.racket_name,
|
||||
self.pkg.racket_name,
|
||||
"--deps",
|
||||
"fail",
|
||||
"--ignore-implies",
|
||||
"--copy",
|
||||
"-i",
|
||||
"-j",
|
||||
str(determine_number_of_jobs(allow_parallel)),
|
||||
str(determine_number_of_jobs(parallel)),
|
||||
"--",
|
||||
os.getcwd(),
|
||||
]
|
||||
@ -82,9 +101,8 @@ def install(self, spec, prefix):
|
||||
except ProcessError:
|
||||
args.insert(-2, "--skip-installed")
|
||||
raco(*args)
|
||||
tty.warn(
|
||||
(
|
||||
"Racket package {0} was already installed, uninstalling via "
|
||||
"Spack may make someone unhappy!"
|
||||
).format(self.racket_name)
|
||||
msg = (
|
||||
"Racket package {0} was already installed, uninstalling via "
|
||||
"Spack may make someone unhappy!"
|
||||
)
|
||||
tty.warn(msg.format(self.pkg.racket_name))
|
||||
|
@ -2,35 +2,49 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import glob
|
||||
import inspect
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, extends
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
|
||||
class RubyPackage(PackageBase):
|
||||
"""Specialized class for building Ruby gems.
|
||||
|
||||
This class provides two phases that can be overridden if required:
|
||||
|
||||
#. :py:meth:`~.RubyPackage.build`
|
||||
#. :py:meth:`~.RubyPackage.install`
|
||||
"""
|
||||
class RubyPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for building Ruby gems."""
|
||||
|
||||
maintainers = ["Kerilk"]
|
||||
|
||||
#: Phases of a Ruby package
|
||||
phases = ["build", "install"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "RubyPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "ruby"
|
||||
|
||||
extends("ruby")
|
||||
build_system("ruby")
|
||||
|
||||
def build(self, spec, prefix):
|
||||
extends("ruby", when="build_system=ruby")
|
||||
|
||||
|
||||
@spack.builder.builder("ruby")
|
||||
class RubyBuilder(BaseBuilder):
|
||||
"""The Ruby builder provides two phases that can be overridden if required:
|
||||
|
||||
#. :py:meth:`~.RubyBuilder.build`
|
||||
#. :py:meth:`~.RubyBuilder.install`
|
||||
"""
|
||||
|
||||
phases = ("build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ()
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Build a Ruby gem."""
|
||||
|
||||
# ruby-rake provides both rake.gemspec and Rakefile, but only
|
||||
@ -38,15 +52,15 @@ def build(self, spec, prefix):
|
||||
gemspecs = glob.glob("*.gemspec")
|
||||
rakefiles = glob.glob("Rakefile")
|
||||
if gemspecs:
|
||||
inspect.getmodule(self).gem("build", "--norc", gemspecs[0])
|
||||
inspect.getmodule(self.pkg).gem("build", "--norc", gemspecs[0])
|
||||
elif rakefiles:
|
||||
jobs = inspect.getmodule(self).make_jobs
|
||||
inspect.getmodule(self).rake("package", "-j{0}".format(jobs))
|
||||
jobs = inspect.getmodule(self.pkg).make_jobs
|
||||
inspect.getmodule(self.pkg).rake("package", "-j{0}".format(jobs))
|
||||
else:
|
||||
# Some Ruby packages only ship `*.gem` files, so nothing to build
|
||||
pass
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install a Ruby gem.
|
||||
|
||||
The ruby package sets ``GEM_HOME`` to tell gem where to install to."""
|
||||
@ -56,9 +70,6 @@ def install(self, spec, prefix):
|
||||
# if --install-dir is not used, GEM_PATH is deleted from the
|
||||
# environement, and Gems required to build native extensions will
|
||||
# not be found. Those extensions are built during `gem install`.
|
||||
inspect.getmodule(self).gem(
|
||||
inspect.getmodule(self.pkg).gem(
|
||||
"install", "--norc", "--ignore-dependencies", "--install-dir", prefix, gems[0]
|
||||
)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
@ -2,63 +2,79 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class SConsPackage(PackageBase):
|
||||
class SConsPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using SCons.
|
||||
|
||||
See http://scons.org/documentation.html for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.SConsPackage.build`
|
||||
2. :py:meth:`~.SConsPackage.install`
|
||||
|
||||
Packages that use SCons as a build system are less uniform than packages
|
||||
that use other build systems. Developers can add custom subcommands or
|
||||
variables that control the build. You will likely need to override
|
||||
:py:meth:`~.SConsPackage.build_args` to pass the appropriate variables.
|
||||
"""
|
||||
|
||||
#: Phases of a SCons package
|
||||
phases = ["build", "install"]
|
||||
|
||||
#: To be used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "SConsPackage"
|
||||
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "scons"
|
||||
|
||||
build_system("scons")
|
||||
|
||||
depends_on("scons", type="build", when="build_system=scons")
|
||||
|
||||
|
||||
@spack.builder.builder("scons")
|
||||
class SConsBuilder(BaseBuilder):
|
||||
"""The Scons builder provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.SConsBuilder.build`
|
||||
2. :py:meth:`~.SConsBuilder.install`
|
||||
|
||||
Packages that use SCons as a build system are less uniform than packages that use
|
||||
other build systems. Developers can add custom subcommands or variables that
|
||||
control the build. You will likely need to override
|
||||
:py:meth:`~.SConsBuilder.build_args` to pass the appropriate variables.
|
||||
"""
|
||||
|
||||
#: Phases of a SCons package
|
||||
phases = ("build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("install_args", "build_test")
|
||||
|
||||
#: Same as legacy_methods, but the signature is different
|
||||
legacy_long_methods = ("build_args",)
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_time_test_callbacks",)
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["build_test"]
|
||||
|
||||
depends_on("scons", type="build")
|
||||
|
||||
def build_args(self, spec, prefix):
|
||||
"""Arguments to pass to build."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Build the package."""
|
||||
args = self.build_args(spec, prefix)
|
||||
inspect.getmodule(self.pkg).scons(*args)
|
||||
|
||||
inspect.getmodule(self).scons(*args)
|
||||
|
||||
def install_args(self, spec, prefix):
|
||||
def install_args(self):
|
||||
"""Arguments to pass to install."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package."""
|
||||
args = self.install_args(spec, prefix)
|
||||
args = self.install_args()
|
||||
|
||||
inspect.getmodule(self).scons("install", *args)
|
||||
|
||||
# Testing
|
||||
inspect.getmodule(self.pkg).scons("install", *args)
|
||||
|
||||
def build_test(self):
|
||||
"""Run unit tests after build.
|
||||
@ -68,7 +84,4 @@ def build_test(self):
|
||||
"""
|
||||
pass
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
@ -10,28 +9,20 @@
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import find, join_path, working_dir
|
||||
|
||||
from spack.directives import depends_on, extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on, extends
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder, execute_install_time_tests
|
||||
|
||||
|
||||
class SIPPackage(PackageBase):
|
||||
class SIPPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages that are built using the
|
||||
SIP build system. See https://www.riverbankcomputing.com/software/sip/intro
|
||||
for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
* configure
|
||||
* build
|
||||
* install
|
||||
|
||||
The configure phase already adds a set of default flags. To see more
|
||||
options, run ``python configure.py --help``.
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["configure", "build", "install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "SIPPackage"
|
||||
@ -41,11 +32,15 @@ class SIPPackage(PackageBase):
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "sip"
|
||||
|
||||
extends("python")
|
||||
build_system("sip")
|
||||
|
||||
depends_on("qt")
|
||||
depends_on("py-sip")
|
||||
with when("build_system=sip"):
|
||||
extends("python")
|
||||
depends_on("qt")
|
||||
depends_on("py-sip")
|
||||
|
||||
@property
|
||||
def import_modules(self):
|
||||
@ -95,11 +90,51 @@ def python(self, *args, **kwargs):
|
||||
"""The python ``Executable``."""
|
||||
inspect.getmodule(self).python(*args, **kwargs)
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
|
||||
@spack.builder.builder("sip")
|
||||
class SIPBuilder(BaseBuilder):
|
||||
"""The SIP builder provides the following phases that can be overridden:
|
||||
|
||||
* configure
|
||||
* build
|
||||
* install
|
||||
|
||||
The configure phase already adds a set of default flags. To see more
|
||||
options, run ``python configure.py --help``.
|
||||
"""
|
||||
|
||||
phases = ("configure", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("configure_file", "configure_args", "build_args", "install_args")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
def configure_file(self):
|
||||
"""Returns the name of the configure file to use."""
|
||||
return "configure.py"
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Configure the package."""
|
||||
configure = self.configure_file()
|
||||
|
||||
@ -118,7 +153,7 @@ def configure(self, spec, prefix):
|
||||
"--bindir",
|
||||
prefix.bin,
|
||||
"--destdir",
|
||||
inspect.getmodule(self).python_platlib,
|
||||
inspect.getmodule(self.pkg).python_platlib,
|
||||
]
|
||||
)
|
||||
|
||||
@ -128,53 +163,35 @@ def configure_args(self):
|
||||
"""Arguments to pass to configure."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Build the package."""
|
||||
args = self.build_args()
|
||||
|
||||
inspect.getmodule(self).make(*args)
|
||||
inspect.getmodule(self.pkg).make(*args)
|
||||
|
||||
def build_args(self):
|
||||
"""Arguments to pass to build."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package."""
|
||||
args = self.install_args()
|
||||
|
||||
inspect.getmodule(self).make("install", parallel=False, *args)
|
||||
inspect.getmodule(self.pkg).make("install", parallel=False, *args)
|
||||
|
||||
def install_args(self):
|
||||
"""Arguments to pass to install."""
|
||||
return []
|
||||
|
||||
# Testing
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def extend_path_setup(self):
|
||||
# See github issue #14121 and PR #15297
|
||||
module = self.spec["py-sip"].variants["module"].value
|
||||
module = self.pkg.spec["py-sip"].variants["module"].value
|
||||
if module != "sip":
|
||||
module = module.split(".")[0]
|
||||
with working_dir(inspect.getmodule(self).python_platlib):
|
||||
with working_dir(inspect.getmodule(self.pkg).python_platlib):
|
||||
with open(os.path.join(module, "__init__.py"), "a") as f:
|
||||
f.write("from pkgutil import extend_path\n")
|
||||
f.write("__path__ = extend_path(__path__, __name__)\n")
|
||||
|
@ -2,21 +2,38 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests, execute_install_time_tests
|
||||
|
||||
|
||||
class WafPackage(PackageBase):
|
||||
class WafPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages that are built using the
|
||||
Waf build system. See https://waf.io/book/ for more information.
|
||||
"""
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "WafPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "waf"
|
||||
|
||||
build_system("waf")
|
||||
# Much like AutotoolsPackage does not require automake and autoconf
|
||||
# to build, WafPackage does not require waf to build. It only requires
|
||||
# python to run the waf build script.
|
||||
depends_on("python@2.5:", type="build", when="build_system=waf")
|
||||
|
||||
|
||||
@spack.builder.builder("waf")
|
||||
class WafBuilder(BaseBuilder):
|
||||
"""The WAF builder provides the following phases that can be overridden:
|
||||
|
||||
* configure
|
||||
* build
|
||||
@ -40,12 +57,25 @@ class WafPackage(PackageBase):
|
||||
function, which passes ``--prefix=/path/to/installation/prefix``.
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["configure", "build", "install"]
|
||||
phases = ("configure", "build", "install")
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "WafPackage"
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = (
|
||||
"build_test",
|
||||
"install_test",
|
||||
"configure_args",
|
||||
"build_args",
|
||||
"install_args",
|
||||
"build_test",
|
||||
"install_test",
|
||||
)
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_time_test_callbacks",
|
||||
"build_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
# Callback names for build-time test
|
||||
build_time_test_callbacks = ["build_test"]
|
||||
@ -53,11 +83,6 @@ class WafPackage(PackageBase):
|
||||
# Callback names for install-time test
|
||||
install_time_test_callbacks = ["install_test"]
|
||||
|
||||
# Much like AutotoolsPackage does not require automake and autoconf
|
||||
# to build, WafPackage does not require waf to build. It only requires
|
||||
# python to run the waf build script.
|
||||
depends_on("python@2.5:", type="build")
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The directory containing the ``waf`` file."""
|
||||
@ -65,18 +90,18 @@ def build_directory(self):
|
||||
|
||||
def python(self, *args, **kwargs):
|
||||
"""The python ``Executable``."""
|
||||
inspect.getmodule(self).python(*args, **kwargs)
|
||||
inspect.getmodule(self.pkg).python(*args, **kwargs)
|
||||
|
||||
def waf(self, *args, **kwargs):
|
||||
"""Runs the waf ``Executable``."""
|
||||
jobs = inspect.getmodule(self).make_jobs
|
||||
jobs = inspect.getmodule(self.pkg).make_jobs
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
self.python("waf", "-j{0}".format(jobs), *args, **kwargs)
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Configures the project."""
|
||||
args = ["--prefix={0}".format(self.prefix)]
|
||||
args = ["--prefix={0}".format(self.pkg.prefix)]
|
||||
args += self.configure_args()
|
||||
|
||||
self.waf("configure", *args)
|
||||
@ -85,7 +110,7 @@ def configure_args(self):
|
||||
"""Arguments to pass to configure."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Executes the build."""
|
||||
args = self.build_args()
|
||||
|
||||
@ -95,7 +120,7 @@ def build_args(self):
|
||||
"""Arguments to pass to build."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Installs the targets on the system."""
|
||||
args = self.install_args()
|
||||
|
||||
@ -105,8 +130,6 @@ def install_args(self):
|
||||
"""Arguments to pass to install."""
|
||||
return []
|
||||
|
||||
# Testing
|
||||
|
||||
def build_test(self):
|
||||
"""Run unit tests after build.
|
||||
|
||||
@ -115,7 +138,7 @@ def build_test(self):
|
||||
"""
|
||||
pass
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def install_test(self):
|
||||
"""Run unit tests after install.
|
||||
@ -125,7 +148,4 @@ def install_test(self):
|
||||
"""
|
||||
pass
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
574
lib/spack/spack/builder.py
Normal file
574
lib/spack/spack/builder.py
Normal file
@ -0,0 +1,574 @@
|
||||
# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import collections
|
||||
import copy
|
||||
import functools
|
||||
import inspect
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import six
|
||||
|
||||
import llnl.util.compat
|
||||
|
||||
import spack.build_environment
|
||||
|
||||
#: Builder classes, as registered by the "builder" decorator
|
||||
BUILDER_CLS = {}
|
||||
|
||||
#: An object of this kind is a shared global state used to collect callbacks during
|
||||
#: class definition time, and is flushed when the class object is created at the end
|
||||
#: of the class definition
|
||||
#:
|
||||
#: Args:
|
||||
#: attribute_name (str): name of the attribute that will be attached to the builder
|
||||
#: callbacks (list): container used to temporarily aggregate the callbacks
|
||||
CallbackTemporaryStage = collections.namedtuple(
|
||||
"CallbackTemporaryStage", ["attribute_name", "callbacks"]
|
||||
)
|
||||
|
||||
#: Shared global state to aggregate "@run_before" callbacks
|
||||
_RUN_BEFORE = CallbackTemporaryStage(attribute_name="run_before_callbacks", callbacks=[])
|
||||
#: Shared global state to aggregate "@run_after" callbacks
|
||||
_RUN_AFTER = CallbackTemporaryStage(attribute_name="run_after_callbacks", callbacks=[])
|
||||
|
||||
#: Map id(pkg) to a builder, to avoid creating multiple
|
||||
#: builders for the same package object.
|
||||
_BUILDERS = {}
|
||||
|
||||
|
||||
def builder(build_system_name):
|
||||
"""Class decorator used to register the default builder
|
||||
for a given build-system.
|
||||
|
||||
Args:
|
||||
build_system_name (str): name of the build-system
|
||||
"""
|
||||
|
||||
def _decorator(cls):
|
||||
cls.build_system = build_system_name
|
||||
BUILDER_CLS[build_system_name] = cls
|
||||
return cls
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
def create(pkg):
|
||||
"""Given a package object with an associated concrete spec,
|
||||
return the builder object that can install it.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package for which we want the builder
|
||||
"""
|
||||
if id(pkg) not in _BUILDERS:
|
||||
_BUILDERS[id(pkg)] = _create(pkg)
|
||||
return _BUILDERS[id(pkg)]
|
||||
|
||||
|
||||
class _PhaseAdapter(object):
|
||||
def __init__(self, builder, phase_fn):
|
||||
self.builder = builder
|
||||
self.phase_fn = phase_fn
|
||||
|
||||
def __call__(self, spec, prefix):
|
||||
return self.phase_fn(self.builder.pkg, spec, prefix)
|
||||
|
||||
|
||||
def _create(pkg):
|
||||
"""Return a new builder object for the package object being passed as argument.
|
||||
|
||||
The function inspects the build-system used by the package object and try to:
|
||||
|
||||
1. Return a custom builder, if any is defined in the same ``package.py`` file.
|
||||
2. Return a customization of more generic builders, if any is defined in the
|
||||
class hierarchy (look at AspellDictPackage for an example of that)
|
||||
3. Return a run-time generated adapter builder otherwise
|
||||
|
||||
The run-time generated adapter builder is capable of adapting an old-style package
|
||||
to the new architecture, where the installation procedure has been extracted from
|
||||
the ``*Package`` hierarchy into a ``*Builder`` hierarchy. This means that the
|
||||
adapter looks for attribute or method overrides preferably in the ``*Package``
|
||||
before using the default builder implementation.
|
||||
|
||||
Note that in case a builder is explicitly coded in ``package.py``, no attempt is made
|
||||
to look for build-related methods in the ``*Package``.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package object for which we need a builder
|
||||
"""
|
||||
package_module = inspect.getmodule(pkg)
|
||||
package_buildsystem = buildsystem_name(pkg)
|
||||
default_builder_cls = BUILDER_CLS[package_buildsystem]
|
||||
builder_cls_name = default_builder_cls.__name__
|
||||
builder_cls = getattr(package_module, builder_cls_name, None)
|
||||
if builder_cls:
|
||||
return builder_cls(pkg)
|
||||
|
||||
# Specialized version of a given buildsystem can subclass some
|
||||
# base classes and specialize certain phases or methods or attributes.
|
||||
# In that case they can store their builder class as a class level attribute.
|
||||
# See e.g. AspellDictPackage as an example.
|
||||
base_cls = getattr(pkg, builder_cls_name, default_builder_cls)
|
||||
|
||||
# From here on we define classes to construct a special builder that adapts to the
|
||||
# old, single class, package format. The adapter forwards any call or access to an
|
||||
# attribute related to the installation procedure to a package object wrapped in
|
||||
# a class that falls-back on calling the base builder if no override is found on the
|
||||
# package. The semantic should be the same as the method in the base builder were still
|
||||
# present in the base class of the package.
|
||||
|
||||
class _ForwardToBaseBuilder(object):
|
||||
def __init__(self, wrapped_pkg_object, root_builder):
|
||||
self.wrapped_package_object = wrapped_pkg_object
|
||||
self.root_builder = root_builder
|
||||
|
||||
package_cls = type(wrapped_pkg_object)
|
||||
wrapper_cls = type(self)
|
||||
bases = (package_cls, wrapper_cls)
|
||||
new_cls_name = package_cls.__name__ + "Wrapper"
|
||||
new_cls = type(new_cls_name, bases, {})
|
||||
new_cls.__module__ = package_cls.__module__
|
||||
self.__class__ = new_cls
|
||||
self.__dict__.update(wrapped_pkg_object.__dict__)
|
||||
|
||||
def __getattr__(self, item):
|
||||
result = getattr(super(type(self.root_builder), self.root_builder), item)
|
||||
if item in super(type(self.root_builder), self.root_builder).phases:
|
||||
result = _PhaseAdapter(self.root_builder, result)
|
||||
return result
|
||||
|
||||
def forward_method_to_getattr(fn_name):
|
||||
def __forward(self, *args, **kwargs):
|
||||
return self.__getattr__(fn_name)(*args, **kwargs)
|
||||
|
||||
return __forward
|
||||
|
||||
# Add fallback methods for the Package object to refer to the builder. If a method
|
||||
# with the same name is defined in the Package, it will override this definition
|
||||
# (when _ForwardToBaseBuilder is initialized)
|
||||
for method_name in (
|
||||
base_cls.phases
|
||||
+ base_cls.legacy_methods
|
||||
+ getattr(base_cls, "legacy_long_methods", tuple())
|
||||
+ ("setup_build_environment", "setup_dependent_build_environment")
|
||||
):
|
||||
setattr(_ForwardToBaseBuilder, method_name, forward_method_to_getattr(method_name))
|
||||
|
||||
def forward_property_to_getattr(property_name):
|
||||
def __forward(self):
|
||||
return self.__getattr__(property_name)
|
||||
|
||||
return __forward
|
||||
|
||||
for attribute_name in base_cls.legacy_attributes:
|
||||
setattr(
|
||||
_ForwardToBaseBuilder,
|
||||
attribute_name,
|
||||
property(forward_property_to_getattr(attribute_name)),
|
||||
)
|
||||
|
||||
class Adapter(six.with_metaclass(_PackageAdapterMeta, base_cls)):
|
||||
def __init__(self, pkg):
|
||||
# Deal with custom phases in packages here
|
||||
if hasattr(pkg, "phases"):
|
||||
self.phases = pkg.phases
|
||||
for phase in self.phases:
|
||||
setattr(Adapter, phase, _PackageAdapterMeta.phase_method_adapter(phase))
|
||||
|
||||
# Attribute containing the package wrapped in dispatcher with a `__getattr__`
|
||||
# method that will forward certain calls to the default builder.
|
||||
self.pkg_with_dispatcher = _ForwardToBaseBuilder(pkg, root_builder=self)
|
||||
super(Adapter, self).__init__(pkg)
|
||||
|
||||
# These two methods don't follow the (self, spec, prefix) signature of phases nor
|
||||
# the (self) signature of methods, so they are added explicitly to avoid using a
|
||||
# catch-all (*args, **kwargs)
|
||||
def setup_build_environment(self, env):
|
||||
return self.pkg_with_dispatcher.setup_build_environment(env)
|
||||
|
||||
def setup_dependent_build_environment(self, env, dependent_spec):
|
||||
return self.pkg_with_dispatcher.setup_dependent_build_environment(env, dependent_spec)
|
||||
|
||||
return Adapter(pkg)
|
||||
|
||||
|
||||
def buildsystem_name(pkg):
|
||||
"""Given a package object with an associated concrete spec,
|
||||
return the name of its build system.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package for which we want
|
||||
the build system name
|
||||
"""
|
||||
try:
|
||||
return pkg.spec.variants["build_system"].value
|
||||
except KeyError:
|
||||
# We are reading an old spec without the build_system variant
|
||||
return pkg.legacy_buildsystem
|
||||
|
||||
|
||||
class PhaseCallbacksMeta(type):
|
||||
"""Permit to register arbitrary functions during class definition and run them
|
||||
later, before or after a given install phase.
|
||||
|
||||
Each method decorated with ``run_before`` or ``run_after`` gets temporarily
|
||||
stored in a global shared state when a class being defined is parsed by the Python
|
||||
interpreter. At class definition time that temporary storage gets flushed and a list
|
||||
of callbacks is attached to the class being defined.
|
||||
"""
|
||||
|
||||
def __new__(mcs, name, bases, attr_dict):
|
||||
for temporary_stage in (_RUN_BEFORE, _RUN_AFTER):
|
||||
staged_callbacks = temporary_stage.callbacks
|
||||
|
||||
# We don't have callbacks in this class, move on
|
||||
if not staged_callbacks:
|
||||
continue
|
||||
|
||||
# If we are here we have callbacks. To get a complete list, get first what
|
||||
# was attached to parent classes, then prepend what we have registered here.
|
||||
#
|
||||
# The order should be:
|
||||
# 1. Callbacks are registered in order within the same class
|
||||
# 2. Callbacks defined in derived classes precede those defined in base
|
||||
# classes
|
||||
for base in bases:
|
||||
callbacks_from_base = getattr(base, temporary_stage.attribute_name, None)
|
||||
if callbacks_from_base:
|
||||
break
|
||||
callbacks_from_base = callbacks_from_base or []
|
||||
|
||||
# Set the callbacks in this class and flush the temporary stage
|
||||
attr_dict[temporary_stage.attribute_name] = staged_callbacks[:] + callbacks_from_base
|
||||
del temporary_stage.callbacks[:]
|
||||
|
||||
return super(PhaseCallbacksMeta, mcs).__new__(mcs, name, bases, attr_dict)
|
||||
|
||||
@staticmethod
|
||||
def run_after(phase, when=None):
|
||||
"""Decorator to register a function for running after a given phase.
|
||||
|
||||
Args:
|
||||
phase (str): phase after which the function must run.
|
||||
when (str): condition under which the function is run (if None, it is always run).
|
||||
"""
|
||||
|
||||
def _decorator(fn):
|
||||
key = (phase, when)
|
||||
item = (key, fn)
|
||||
_RUN_AFTER.callbacks.append(item)
|
||||
return fn
|
||||
|
||||
return _decorator
|
||||
|
||||
@staticmethod
|
||||
def run_before(phase, when=None):
|
||||
"""Decorator to register a function for running before a given phase.
|
||||
|
||||
Args:
|
||||
phase (str): phase before which the function must run.
|
||||
when (str): condition under which the function is run (if None, it is always run).
|
||||
"""
|
||||
|
||||
def _decorator(fn):
|
||||
key = (phase, when)
|
||||
item = (key, fn)
|
||||
_RUN_BEFORE.callbacks.append(item)
|
||||
return fn
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
class BuilderMeta(PhaseCallbacksMeta, type(llnl.util.compat.Sequence)): # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class _PackageAdapterMeta(BuilderMeta):
|
||||
"""Metaclass to adapt old-style packages to the new architecture based on builders
|
||||
for the installation phase.
|
||||
|
||||
This class does the necessary mangling to function argument so that a call to a
|
||||
builder object can delegate to a package object.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def phase_method_adapter(phase_name):
|
||||
def _adapter(self, pkg, spec, prefix):
|
||||
phase_fn = getattr(self.pkg_with_dispatcher, phase_name)
|
||||
return phase_fn(spec, prefix)
|
||||
|
||||
return _adapter
|
||||
|
||||
@staticmethod
|
||||
def legacy_long_method_adapter(method_name):
|
||||
def _adapter(self, spec, prefix):
|
||||
bind_method = getattr(self.pkg_with_dispatcher, method_name)
|
||||
return bind_method(spec, prefix)
|
||||
|
||||
return _adapter
|
||||
|
||||
@staticmethod
|
||||
def legacy_method_adapter(method_name):
|
||||
def _adapter(self):
|
||||
bind_method = getattr(self.pkg_with_dispatcher, method_name)
|
||||
return bind_method()
|
||||
|
||||
return _adapter
|
||||
|
||||
@staticmethod
|
||||
def legacy_attribute_adapter(attribute_name):
|
||||
def _adapter(self):
|
||||
return getattr(self.pkg_with_dispatcher, attribute_name)
|
||||
|
||||
return property(_adapter)
|
||||
|
||||
@staticmethod
|
||||
def combine_callbacks(pipeline_attribute_name):
|
||||
"""This function combines callbacks from old-style packages with callbacks that might
|
||||
be registered for the default builder.
|
||||
|
||||
It works by:
|
||||
1. Extracting the callbacks from the old-style package
|
||||
2. Transforming those callbacks by adding an adapter that receives a builder as argument
|
||||
and calls the wrapped function with ``builder.pkg``
|
||||
3. Combining the list of transformed callbacks with those that might be present in the
|
||||
default builder
|
||||
"""
|
||||
|
||||
def _adapter(self):
|
||||
def unwrap_pkg(fn):
|
||||
@functools.wraps(fn)
|
||||
def _wrapped(builder):
|
||||
return fn(builder.pkg_with_dispatcher)
|
||||
|
||||
return _wrapped
|
||||
|
||||
# Concatenate the current list with the one from package
|
||||
callbacks_from_package = getattr(self.pkg, pipeline_attribute_name, [])
|
||||
callbacks_from_package = [(key, unwrap_pkg(x)) for key, x in callbacks_from_package]
|
||||
callbacks_from_builder = getattr(super(type(self), self), pipeline_attribute_name, [])
|
||||
return callbacks_from_package + callbacks_from_builder
|
||||
|
||||
return property(_adapter)
|
||||
|
||||
def __new__(mcs, name, bases, attr_dict):
|
||||
# Add ways to intercept methods and attribute calls and dispatch
|
||||
# them first to a package object
|
||||
default_builder_cls = bases[0]
|
||||
for phase_name in default_builder_cls.phases:
|
||||
attr_dict[phase_name] = _PackageAdapterMeta.phase_method_adapter(phase_name)
|
||||
|
||||
for method_name in default_builder_cls.legacy_methods:
|
||||
attr_dict[method_name] = _PackageAdapterMeta.legacy_method_adapter(method_name)
|
||||
|
||||
# These exist e.g. for Python, see discussion in https://github.com/spack/spack/pull/32068
|
||||
for method_name in getattr(default_builder_cls, "legacy_long_methods", []):
|
||||
attr_dict[method_name] = _PackageAdapterMeta.legacy_long_method_adapter(method_name)
|
||||
|
||||
for attribute_name in default_builder_cls.legacy_attributes:
|
||||
attr_dict[attribute_name] = _PackageAdapterMeta.legacy_attribute_adapter(
|
||||
attribute_name
|
||||
)
|
||||
|
||||
combine_callbacks = _PackageAdapterMeta.combine_callbacks
|
||||
attr_dict[_RUN_BEFORE.attribute_name] = combine_callbacks(_RUN_BEFORE.attribute_name)
|
||||
attr_dict[_RUN_AFTER.attribute_name] = combine_callbacks(_RUN_AFTER.attribute_name)
|
||||
|
||||
return super(_PackageAdapterMeta, mcs).__new__(mcs, name, bases, attr_dict)
|
||||
|
||||
|
||||
class InstallationPhase(object):
|
||||
"""Manages a single phase of the installation.
|
||||
|
||||
This descriptor stores at creation time the name of the method it should
|
||||
search for execution. The method is retrieved at __get__ time, so that
|
||||
it can be overridden by subclasses of whatever class declared the phases.
|
||||
|
||||
It also provides hooks to execute arbitrary callbacks before and after
|
||||
the phase.
|
||||
"""
|
||||
|
||||
def __init__(self, name, builder):
|
||||
self.name = name
|
||||
self.builder = builder
|
||||
self.phase_fn = self._select_phase_fn()
|
||||
self.run_before = self._make_callbacks(_RUN_BEFORE.attribute_name)
|
||||
self.run_after = self._make_callbacks(_RUN_AFTER.attribute_name)
|
||||
|
||||
def _make_callbacks(self, callbacks_attribute):
|
||||
result = []
|
||||
callbacks = getattr(self.builder, callbacks_attribute, [])
|
||||
for (phase, condition), fn in callbacks:
|
||||
# Same if it is for another phase
|
||||
if phase != self.name:
|
||||
continue
|
||||
|
||||
# If we have no condition or the callback satisfies a condition, register it
|
||||
if condition is None or self.builder.pkg.spec.satisfies(condition):
|
||||
result.append(fn)
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
msg = '{0}: executing "{1}" phase'
|
||||
return msg.format(self.builder, self.name)
|
||||
|
||||
def execute(self):
|
||||
pkg = self.builder.pkg
|
||||
self._on_phase_start(pkg)
|
||||
|
||||
for callback in self.run_before:
|
||||
callback(self.builder)
|
||||
|
||||
self.phase_fn(pkg, pkg.spec, pkg.prefix)
|
||||
|
||||
for callback in self.run_after:
|
||||
callback(self.builder)
|
||||
|
||||
self._on_phase_exit(pkg)
|
||||
|
||||
def _select_phase_fn(self):
|
||||
phase_fn = getattr(self.builder, self.name, None)
|
||||
|
||||
if not phase_fn:
|
||||
msg = (
|
||||
'unexpected error: package "{0.fullname}" must implement an '
|
||||
'"{1}" phase for the "{2}" build system'
|
||||
)
|
||||
raise RuntimeError(msg.format(self.builder.pkg, self.name, self.builder.build_system))
|
||||
|
||||
return phase_fn
|
||||
|
||||
def _on_phase_start(self, instance):
|
||||
# If a phase has a matching stop_before_phase attribute,
|
||||
# stop the installation process raising a StopPhase
|
||||
if getattr(instance, "stop_before_phase", None) == self.name:
|
||||
raise spack.build_environment.StopPhase(
|
||||
"Stopping before '{0}' phase".format(self.name)
|
||||
)
|
||||
|
||||
def _on_phase_exit(self, instance):
|
||||
# If a phase has a matching last_phase attribute,
|
||||
# stop the installation process raising a StopPhase
|
||||
if getattr(instance, "last_phase", None) == self.name:
|
||||
raise spack.build_environment.StopPhase("Stopping at '{0}' phase".format(self.name))
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
|
||||
class Builder(six.with_metaclass(BuilderMeta, llnl.util.compat.Sequence)):
|
||||
"""A builder is a class that, given a package object (i.e. associated with
|
||||
concrete spec), knows how to install it.
|
||||
|
||||
The builder behaves like a sequence, and when iterated over return the
|
||||
"phases" of the installation in the correct order.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package object to be built
|
||||
"""
|
||||
|
||||
#: Sequence of phases. Must be defined in derived classes
|
||||
phases = () # type: Tuple[str, ...]
|
||||
#: Build system name. Must also be defined in derived classes.
|
||||
build_system = None # type: Optional[str]
|
||||
|
||||
legacy_methods = () # type: Tuple[str, ...]
|
||||
legacy_attributes = () # type: Tuple[str, ...]
|
||||
|
||||
#: List of glob expressions. Each expression must either be
|
||||
#: absolute or relative to the package source path.
|
||||
#: Matching artifacts found at the end of the build process will be
|
||||
#: copied in the same directory tree as _spack_build_logfile and
|
||||
#: _spack_build_envfile.
|
||||
archive_files = [] # type: List[str]
|
||||
|
||||
def __init__(self, pkg):
|
||||
self.pkg = pkg
|
||||
self.callbacks = {}
|
||||
for phase in self.phases:
|
||||
self.callbacks[phase] = InstallationPhase(phase, self)
|
||||
|
||||
@property
|
||||
def spec(self):
|
||||
return self.pkg.spec
|
||||
|
||||
@property
|
||||
def stage(self):
|
||||
return self.pkg.stage
|
||||
|
||||
@property
|
||||
def prefix(self):
|
||||
return self.pkg.prefix
|
||||
|
||||
def test(self):
|
||||
# Defer tests to virtual and concrete packages
|
||||
pass
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
"""Sets up the build environment for a package.
|
||||
|
||||
This method will be called before the current package prefix exists in
|
||||
Spack's store.
|
||||
|
||||
Args:
|
||||
env (spack.util.environment.EnvironmentModifications): environment
|
||||
modifications to be applied when the package is built. Package authors
|
||||
can call methods on it to alter the build environment.
|
||||
"""
|
||||
if not hasattr(super(Builder, self), "setup_build_environment"):
|
||||
return
|
||||
super(Builder, self).setup_build_environment(env)
|
||||
|
||||
def setup_dependent_build_environment(self, env, dependent_spec):
|
||||
"""Sets up the build environment of packages that depend on this one.
|
||||
|
||||
This is similar to ``setup_build_environment``, but it is used to
|
||||
modify the build environments of packages that *depend* on this one.
|
||||
|
||||
This gives packages like Python and others that follow the extension
|
||||
model a way to implement common environment or compile-time settings
|
||||
for dependencies.
|
||||
|
||||
This method will be called before the dependent package prefix exists
|
||||
in Spack's store.
|
||||
|
||||
Examples:
|
||||
1. Installing python modules generally requires ``PYTHONPATH``
|
||||
to point to the ``lib/pythonX.Y/site-packages`` directory in the
|
||||
module's install prefix. This method could be used to set that
|
||||
variable.
|
||||
|
||||
Args:
|
||||
env (spack.util.environment.EnvironmentModifications): environment
|
||||
modifications to be applied when the dependent package is built.
|
||||
Package authors can call methods on it to alter the build environment.
|
||||
|
||||
dependent_spec (spack.spec.Spec): the spec of the dependent package
|
||||
about to be built. This allows the extendee (self) to query
|
||||
the dependent's state. Note that *this* package's spec is
|
||||
available as ``self.spec``
|
||||
"""
|
||||
if not hasattr(super(Builder, self), "setup_dependent_build_environment"):
|
||||
return
|
||||
super(Builder, self).setup_dependent_build_environment(env, dependent_spec)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
key = self.phases[idx]
|
||||
return self.callbacks[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.phases)
|
||||
|
||||
def __repr__(self):
|
||||
msg = "{0}({1})"
|
||||
return msg.format(type(self).__name__, self.pkg.spec.format("{name}/{hash:7}"))
|
||||
|
||||
def __str__(self):
|
||||
msg = '"{0}" builder for "{1}"'
|
||||
return msg.format(type(self).build_system, self.pkg.spec.format("{name}/{hash:7}"))
|
||||
|
||||
|
||||
# Export these names as standalone to be used in packages
|
||||
run_after = PhaseCallbacksMeta.run_after
|
||||
run_before = PhaseCallbacksMeta.run_before
|
@ -4,6 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
@ -11,11 +12,12 @@
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
from six import iteritems
|
||||
from six import iteritems, string_types
|
||||
from six.moves.urllib.error import HTTPError, URLError
|
||||
from six.moves.urllib.parse import urlencode
|
||||
from six.moves.urllib.request import HTTPHandler, Request, build_opener
|
||||
@ -41,7 +43,6 @@
|
||||
from spack.error import SpackError
|
||||
from spack.reporters.cdash import CDash
|
||||
from spack.reporters.cdash import build_stamp as cdash_build_stamp
|
||||
from spack.spec import Spec
|
||||
from spack.util.pattern import Bunch
|
||||
|
||||
JOB_RETRY_CONDITIONS = [
|
||||
@ -141,13 +142,6 @@ def _get_spec_string(spec):
|
||||
return spec.format("".join(format_elements))
|
||||
|
||||
|
||||
def _format_root_spec(spec, main_phase, strip_compiler):
|
||||
if main_phase is False and strip_compiler is True:
|
||||
return "{0}@{1} arch={2}".format(spec.name, spec.version, spec.architecture)
|
||||
else:
|
||||
return spec.dag_hash()
|
||||
|
||||
|
||||
def _spec_deps_key(s):
|
||||
return "{0}/{1}".format(s.name, s.dag_hash(7))
|
||||
|
||||
@ -173,8 +167,7 @@ def _get_spec_dependencies(
|
||||
|
||||
for entry in specs:
|
||||
spec_labels[entry["label"]] = {
|
||||
"spec": Spec(entry["spec"]),
|
||||
"rootSpec": entry["root_spec"],
|
||||
"spec": entry["spec"],
|
||||
"needs_rebuild": entry["needs_rebuild"],
|
||||
}
|
||||
|
||||
@ -201,7 +194,7 @@ def stage_spec_jobs(specs, check_index_only=False, mirrors_to_check=None):
|
||||
and stages:
|
||||
|
||||
spec_labels: A dictionary mapping the spec labels which are made of
|
||||
(pkg-name/hash-prefix), to objects containing "rootSpec" and "spec"
|
||||
(pkg-name/hash-prefix), to objects containing "spec" and "needs_rebuild"
|
||||
keys. The root spec is the spec of which this spec is a dependency
|
||||
and the spec is the formatted spec string for this spec.
|
||||
|
||||
@ -316,17 +309,14 @@ def _compute_spec_deps(spec_list, check_index_only=False, mirrors_to_check=None)
|
||||
],
|
||||
"specs": [
|
||||
{
|
||||
"root_spec": "readline@7.0%apple-clang@9.1.0 arch=darwin-...",
|
||||
"spec": "readline@7.0%apple-clang@9.1.0 arch=darwin-highs...",
|
||||
"label": "readline/ip6aiun"
|
||||
},
|
||||
{
|
||||
"root_spec": "readline@7.0%apple-clang@9.1.0 arch=darwin-...",
|
||||
"spec": "ncurses@6.1%apple-clang@9.1.0 arch=darwin-highsi...",
|
||||
"label": "ncurses/y43rifz"
|
||||
},
|
||||
{
|
||||
"root_spec": "readline@7.0%apple-clang@9.1.0 arch=darwin-...",
|
||||
"spec": "pkgconf@1.5.4%apple-clang@9.1.0 arch=darwin-high...",
|
||||
"label": "pkgconf/eg355zb"
|
||||
}
|
||||
@ -348,8 +338,6 @@ def append_dep(s, d):
|
||||
)
|
||||
|
||||
for spec in spec_list:
|
||||
root_spec = spec
|
||||
|
||||
for s in spec.traverse(deptype=all):
|
||||
if s.external:
|
||||
tty.msg("Will not stage external pkg: {0}".format(s))
|
||||
@ -361,8 +349,7 @@ def append_dep(s, d):
|
||||
|
||||
skey = _spec_deps_key(s)
|
||||
spec_labels[skey] = {
|
||||
"spec": _get_spec_string(s),
|
||||
"root": root_spec,
|
||||
"spec": s,
|
||||
"needs_rebuild": not up_to_date_mirrors,
|
||||
}
|
||||
|
||||
@ -379,7 +366,6 @@ def append_dep(s, d):
|
||||
{
|
||||
"label": spec_label,
|
||||
"spec": spec_holder["spec"],
|
||||
"root_spec": spec_holder["root"],
|
||||
"needs_rebuild": spec_holder["needs_rebuild"],
|
||||
}
|
||||
)
|
||||
@ -396,6 +382,14 @@ def _spec_matches(spec, match_string):
|
||||
return spec.satisfies(match_string)
|
||||
|
||||
|
||||
def _remove_attributes(src_dict, dest_dict):
|
||||
if "tags" in src_dict and "tags" in dest_dict:
|
||||
# For 'tags', we remove any tags that are listed for removal
|
||||
for tag in src_dict["tags"]:
|
||||
while tag in dest_dict["tags"]:
|
||||
dest_dict["tags"].remove(tag)
|
||||
|
||||
|
||||
def _copy_attributes(attrs_list, src_dict, dest_dict):
|
||||
for runner_attr in attrs_list:
|
||||
if runner_attr in src_dict:
|
||||
@ -429,23 +423,23 @@ def _find_matching_config(spec, gitlab_ci):
|
||||
|
||||
_copy_attributes(overridable_attrs, gitlab_ci, runner_attributes)
|
||||
|
||||
ci_mappings = gitlab_ci["mappings"]
|
||||
for ci_mapping in ci_mappings:
|
||||
matched = False
|
||||
only_first = gitlab_ci.get("match_behavior", "first") == "first"
|
||||
for ci_mapping in gitlab_ci["mappings"]:
|
||||
for match_string in ci_mapping["match"]:
|
||||
if _spec_matches(spec, match_string):
|
||||
matched = True
|
||||
if "remove-attributes" in ci_mapping:
|
||||
_remove_attributes(ci_mapping["remove-attributes"], runner_attributes)
|
||||
if "runner-attributes" in ci_mapping:
|
||||
_copy_attributes(
|
||||
overridable_attrs, ci_mapping["runner-attributes"], runner_attributes
|
||||
)
|
||||
return runner_attributes
|
||||
else:
|
||||
return None
|
||||
break
|
||||
if matched and only_first:
|
||||
break
|
||||
|
||||
return runner_attributes
|
||||
|
||||
|
||||
def _pkg_name_from_spec_label(spec_label):
|
||||
return spec_label[: spec_label.index("/")]
|
||||
return runner_attributes if matched else None
|
||||
|
||||
|
||||
def _format_job_needs(
|
||||
@ -521,38 +515,36 @@ def compute_affected_packages(rev1="HEAD^", rev2="HEAD"):
|
||||
return spack.repo.get_all_package_diffs("ARC", rev1=rev1, rev2=rev2)
|
||||
|
||||
|
||||
def get_spec_filter_list(env, affected_pkgs, dependencies=True, dependents=True):
|
||||
"""Given a list of package names, and assuming an active and
|
||||
concretized environment, return a set of concrete specs from
|
||||
the environment corresponding to any of the affected pkgs (or
|
||||
optionally to any of their dependencies/dependents).
|
||||
def get_spec_filter_list(env, affected_pkgs):
|
||||
"""Given a list of package names and an active/concretized
|
||||
environment, return the set of all concrete specs from the
|
||||
environment that could have been affected by changing the
|
||||
list of packages.
|
||||
|
||||
Arguments:
|
||||
|
||||
env (spack.environment.Environment): Active concrete environment
|
||||
affected_pkgs (List[str]): Affected package names
|
||||
dependencies (bool): Include dependencies of affected packages
|
||||
dependents (bool): Include dependents of affected pacakges
|
||||
|
||||
Returns:
|
||||
|
||||
A list of concrete specs from the active environment including
|
||||
those associated with affected packages, and possible their
|
||||
dependencies and dependents as well.
|
||||
A set of concrete specs from the active environment including
|
||||
those associated with affected packages, their dependencies and
|
||||
dependents, as well as their dependents dependencies.
|
||||
"""
|
||||
affected_specs = set()
|
||||
all_concrete_specs = env.all_specs()
|
||||
tty.debug("All concrete environment specs:")
|
||||
for s in all_concrete_specs:
|
||||
tty.debug(" {0}/{1}".format(s.name, s.dag_hash()[:7]))
|
||||
for pkg in affected_pkgs:
|
||||
env_matches = [s for s in all_concrete_specs if s.name == pkg]
|
||||
for match in env_matches:
|
||||
affected_specs.add(match)
|
||||
if dependencies:
|
||||
affected_specs.update(match.traverse(direction="children", root=False))
|
||||
if dependents:
|
||||
affected_specs.update(match.traverse(direction="parents", root=False))
|
||||
env_matches = [s for s in all_concrete_specs if s.name in frozenset(affected_pkgs)]
|
||||
visited = set()
|
||||
dag_hash = lambda s: s.dag_hash()
|
||||
for match in env_matches:
|
||||
for parent in match.traverse(direction="parents", key=dag_hash):
|
||||
affected_specs.update(
|
||||
parent.traverse(direction="children", visited=visited, key=dag_hash)
|
||||
)
|
||||
return affected_specs
|
||||
|
||||
|
||||
@ -613,11 +605,11 @@ def generate_gitlab_ci_yaml(
|
||||
cdash_handler = CDashHandler(yaml_root.get("cdash")) if "cdash" in yaml_root else None
|
||||
build_group = cdash_handler.build_group if cdash_handler else None
|
||||
|
||||
prune_untouched_packages = os.environ.get("SPACK_PRUNE_UNTOUCHED", None)
|
||||
if prune_untouched_packages:
|
||||
prune_untouched_packages = False
|
||||
spack_prune_untouched = os.environ.get("SPACK_PRUNE_UNTOUCHED", None)
|
||||
if spack_prune_untouched is not None and spack_prune_untouched.lower() == "true":
|
||||
# Requested to prune untouched packages, but assume we won't do that
|
||||
# unless we're actually in a git repo.
|
||||
prune_untouched_packages = False
|
||||
rev1, rev2 = get_change_revisions()
|
||||
tty.debug("Got following revisions: rev1={0}, rev2={1}".format(rev1, rev2))
|
||||
if rev1 and rev2:
|
||||
@ -631,7 +623,15 @@ def generate_gitlab_ci_yaml(
|
||||
affected_specs = get_spec_filter_list(env, affected_pkgs)
|
||||
tty.debug("all affected specs:")
|
||||
for s in affected_specs:
|
||||
tty.debug(" {0}".format(s.name))
|
||||
tty.debug(" {0}/{1}".format(s.name, s.dag_hash()[:7]))
|
||||
|
||||
# Allow overriding --prune-dag cli opt with environment variable
|
||||
prune_dag_override = os.environ.get("SPACK_PRUNE_UP_TO_DATE", None)
|
||||
if prune_dag_override is not None:
|
||||
prune_dag = True if prune_dag_override.lower() == "true" else False
|
||||
|
||||
# If we are not doing any kind of pruning, we are rebuilding everything
|
||||
rebuild_everything = not prune_dag and not prune_untouched_packages
|
||||
|
||||
# Downstream jobs will "need" (depend on, for both scheduling and
|
||||
# artifacts, which include spack.lock file) this pipeline generation
|
||||
@ -832,7 +832,6 @@ def generate_gitlab_ci_yaml(
|
||||
phase_name = phase["name"]
|
||||
strip_compilers = phase["strip-compilers"]
|
||||
|
||||
main_phase = _is_main_phase(phase_name)
|
||||
spec_labels, dependencies, stages = staged_phases[phase_name]
|
||||
|
||||
for stage_jobs in stages:
|
||||
@ -842,14 +841,16 @@ def generate_gitlab_ci_yaml(
|
||||
|
||||
for spec_label in stage_jobs:
|
||||
spec_record = spec_labels[spec_label]
|
||||
root_spec = spec_record["rootSpec"]
|
||||
pkg_name = _pkg_name_from_spec_label(spec_label)
|
||||
release_spec = root_spec[pkg_name]
|
||||
release_spec = spec_record["spec"]
|
||||
release_spec_dag_hash = release_spec.dag_hash()
|
||||
|
||||
if prune_untouched_packages:
|
||||
if release_spec not in affected_specs:
|
||||
tty.debug("Pruning {0}, untouched by change.".format(release_spec.name))
|
||||
tty.debug(
|
||||
"Pruning {0}/{1}, untouched by change.".format(
|
||||
release_spec.name, release_spec.dag_hash()[:7]
|
||||
)
|
||||
)
|
||||
spec_record["needs_rebuild"] = False
|
||||
continue
|
||||
|
||||
@ -865,7 +866,7 @@ def generate_gitlab_ci_yaml(
|
||||
# For spack pipelines "public" and "protected" are reserved tags
|
||||
tags = _remove_reserved_tags(tags)
|
||||
if spack_pipeline_type == "spack_protected_branch":
|
||||
tags.extend(["aws", "protected"])
|
||||
tags.extend(["protected"])
|
||||
elif spack_pipeline_type == "spack_pull_request":
|
||||
tags.extend(["public"])
|
||||
|
||||
@ -914,7 +915,6 @@ def generate_gitlab_ci_yaml(
|
||||
compiler_action = "INSTALL_MISSING"
|
||||
|
||||
job_vars = {
|
||||
"SPACK_ROOT_SPEC": _format_root_spec(root_spec, main_phase, strip_compilers),
|
||||
"SPACK_JOB_SPEC_DAG_HASH": release_spec_dag_hash,
|
||||
"SPACK_JOB_SPEC_PKG_NAME": release_spec.name,
|
||||
"SPACK_COMPILER_ACTION": compiler_action,
|
||||
@ -931,9 +931,7 @@ def generate_gitlab_ci_yaml(
|
||||
# purposes, so we only get the direct dependencies.
|
||||
dep_jobs = []
|
||||
for dep_label in dependencies[spec_label]:
|
||||
dep_pkg = _pkg_name_from_spec_label(dep_label)
|
||||
dep_root = spec_labels[dep_label]["rootSpec"]
|
||||
dep_jobs.append(dep_root[dep_pkg])
|
||||
dep_jobs.append(spec_labels[dep_label]["spec"])
|
||||
|
||||
job_dependencies.extend(
|
||||
_format_job_needs(
|
||||
@ -1017,13 +1015,15 @@ def generate_gitlab_ci_yaml(
|
||||
tty.debug(debug_msg)
|
||||
|
||||
if prune_dag and not rebuild_spec:
|
||||
tty.debug("Pruning {0}, does not need rebuild.".format(release_spec.name))
|
||||
tty.debug(
|
||||
"Pruning {0}/{1}, does not need rebuild.".format(
|
||||
release_spec.name, release_spec.dag_hash()
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
if broken_spec_urls is not None and release_spec_dag_hash in broken_spec_urls:
|
||||
known_broken_specs_encountered.append(
|
||||
"{0} ({1})".format(release_spec, release_spec_dag_hash)
|
||||
)
|
||||
known_broken_specs_encountered.append(release_spec_dag_hash)
|
||||
|
||||
# Only keep track of these if we are copying rebuilt cache entries
|
||||
if spack_buildcache_copy:
|
||||
@ -1167,7 +1167,14 @@ def generate_gitlab_ci_yaml(
|
||||
"after_script",
|
||||
]
|
||||
|
||||
service_job_retries = {"max": 2, "when": ["runner_system_failure", "stuck_or_timeout_failure"]}
|
||||
service_job_retries = {
|
||||
"max": 2,
|
||||
"when": [
|
||||
"runner_system_failure",
|
||||
"stuck_or_timeout_failure",
|
||||
"script_failure",
|
||||
],
|
||||
}
|
||||
|
||||
if job_id > 0:
|
||||
if temp_storage_url_prefix:
|
||||
@ -1286,6 +1293,9 @@ def generate_gitlab_ci_yaml(
|
||||
"SPACK_JOB_TEST_DIR": rel_job_test_dir,
|
||||
"SPACK_LOCAL_MIRROR_DIR": rel_local_mirror_dir,
|
||||
"SPACK_PIPELINE_TYPE": str(spack_pipeline_type),
|
||||
"SPACK_CI_STACK_NAME": os.environ.get("SPACK_CI_STACK_NAME", "None"),
|
||||
"SPACK_REBUILD_CHECK_UP_TO_DATE": str(prune_dag),
|
||||
"SPACK_REBUILD_EVERYTHING": str(rebuild_everything),
|
||||
}
|
||||
|
||||
if remote_mirror_override:
|
||||
@ -1343,13 +1353,11 @@ def generate_gitlab_ci_yaml(
|
||||
sorted_output = {"no-specs-to-rebuild": noop_job}
|
||||
|
||||
if known_broken_specs_encountered:
|
||||
error_msg = (
|
||||
"Pipeline generation failed due to the presence of the "
|
||||
"following specs that are known to be broken in develop:\n"
|
||||
)
|
||||
for broken_spec in known_broken_specs_encountered:
|
||||
error_msg += "* {0}\n".format(broken_spec)
|
||||
tty.die(error_msg)
|
||||
tty.error("This pipeline generated hashes known to be broken on develop:")
|
||||
display_broken_spec_messages(broken_specs_url, known_broken_specs_encountered)
|
||||
|
||||
if not rebuild_everything:
|
||||
sys.exit(1)
|
||||
|
||||
with open(output_file, "w") as outf:
|
||||
outf.write(syaml.dump_config(sorted_output, default_flow_style=True))
|
||||
@ -1461,64 +1469,6 @@ def configure_compilers(compiler_action, scope=None):
|
||||
return None
|
||||
|
||||
|
||||
def get_concrete_specs(env, root_spec, job_name, compiler_action):
|
||||
"""Build a dictionary of concrete specs relevant to a particular
|
||||
rebuild job. This includes the root spec and the spec to be
|
||||
rebuilt (which could be the same).
|
||||
|
||||
Arguments:
|
||||
|
||||
env (spack.environment.Environment): Activated spack environment
|
||||
used to get concrete root spec by hash in case compiler_action
|
||||
is anthing other than FIND_ANY.
|
||||
root_spec (str): If compiler_action is FIND_ANY root_spec is
|
||||
a string representation which can be turned directly into
|
||||
a spec, otherwise, it's a hash used to index the activated
|
||||
spack environment.
|
||||
job_name (str): Name of package to be built, used to index the
|
||||
concrete root spec and produce the concrete spec to be
|
||||
built.
|
||||
compiler_action (str): Determines how to interpret the root_spec
|
||||
parameter, either as a string representation as a hash.
|
||||
|
||||
Returns:
|
||||
|
||||
.. code-block:: JSON
|
||||
|
||||
{
|
||||
"root": "<spec>",
|
||||
"<job-pkg-name>": "<spec>",
|
||||
}
|
||||
|
||||
"""
|
||||
spec_map = {
|
||||
"root": None,
|
||||
}
|
||||
|
||||
if compiler_action == "FIND_ANY":
|
||||
# This corresponds to a bootstrapping phase where we need to
|
||||
# rely on any available compiler to build the package (i.e. the
|
||||
# compiler needed to be stripped from the spec when we generated
|
||||
# the job), and thus we need to concretize the root spec again.
|
||||
tty.debug("About to concretize {0}".format(root_spec))
|
||||
concrete_root = Spec(root_spec).concretized()
|
||||
tty.debug("Resulting concrete root: {0}".format(concrete_root))
|
||||
else:
|
||||
# in this case, either we're relying on Spack to install missing
|
||||
# compiler bootstrapped in a previous phase, or else we only had one
|
||||
# phase (like a site which already knows what compilers are available
|
||||
# on it's runners), so we don't want to concretize that root spec
|
||||
# again. The reason we take this path in the first case (bootstrapped
|
||||
# compiler), is that we can't concretize a spec at this point if we're
|
||||
# going to ask spack to "install_missing_compilers".
|
||||
concrete_root = env.specs_by_hash[root_spec]
|
||||
|
||||
spec_map["root"] = concrete_root
|
||||
spec_map[job_name] = concrete_root[job_name]
|
||||
|
||||
return spec_map
|
||||
|
||||
|
||||
def _push_mirror_contents(env, specfile_path, sign_binaries, mirror_url):
|
||||
"""Unchecked version of the public API, for easier mocking"""
|
||||
unsigned = not sign_binaries
|
||||
@ -1567,6 +1517,19 @@ def push_mirror_contents(env, specfile_path, mirror_url, sign_binaries):
|
||||
raise inst
|
||||
|
||||
|
||||
def remove_other_mirrors(mirrors_to_keep, scope=None):
|
||||
"""Remove all mirrors from the given config scope, the exceptions being
|
||||
any listed in in mirrors_to_keep, which is a list of mirror urls.
|
||||
"""
|
||||
mirrors_to_remove = []
|
||||
for name, mirror_url in spack.config.get("mirrors", scope=scope).items():
|
||||
if mirror_url not in mirrors_to_keep:
|
||||
mirrors_to_remove.append(name)
|
||||
|
||||
for mirror_name in mirrors_to_remove:
|
||||
spack.mirror.remove(mirror_name, scope)
|
||||
|
||||
|
||||
def copy_files_to_artifacts(src, artifacts_dir):
|
||||
"""
|
||||
Copy file(s) to the given artifacts directory
|
||||
@ -1982,26 +1945,35 @@ def reproduce_ci_job(url, work_dir):
|
||||
print("".join(inst_list))
|
||||
|
||||
|
||||
def process_command(cmd, cmd_args, repro_dir):
|
||||
def process_command(name, commands, repro_dir):
|
||||
"""
|
||||
Create a script for and run the command. Copy the script to the
|
||||
reproducibility directory.
|
||||
|
||||
Arguments:
|
||||
cmd (str): name of the command being processed
|
||||
cmd_args (list): string arguments to pass to the command
|
||||
name (str): name of the command being processed
|
||||
commands (list): list of arguments for single command or list of lists of
|
||||
arguments for multiple commands. No shell escape is performed.
|
||||
repro_dir (str): Job reproducibility directory
|
||||
|
||||
Returns: the exit code from processing the command
|
||||
"""
|
||||
tty.debug("spack {0} arguments: {1}".format(cmd, cmd_args))
|
||||
tty.debug("spack {0} arguments: {1}".format(name, commands))
|
||||
|
||||
if len(commands) == 0 or isinstance(commands[0], string_types):
|
||||
commands = [commands]
|
||||
|
||||
# Create a string [command 1] && [command 2] && ... && [command n] with commands
|
||||
# quoted using double quotes.
|
||||
args_to_string = lambda args: " ".join('"{}"'.format(arg) for arg in args)
|
||||
full_command = " && ".join(map(args_to_string, commands))
|
||||
|
||||
# Write the command to a shell script
|
||||
script = "{0}.sh".format(cmd)
|
||||
script = "{0}.sh".format(name)
|
||||
with open(script, "w") as fd:
|
||||
fd.write("#!/bin/bash\n\n")
|
||||
fd.write("\n# spack {0} command\n".format(cmd))
|
||||
fd.write(" ".join(['"{0}"'.format(i) for i in cmd_args]))
|
||||
fd.write("#!/bin/sh\n\n")
|
||||
fd.write("\n# spack {0} command\n".format(name))
|
||||
fd.write(full_command)
|
||||
fd.write("\n")
|
||||
|
||||
st = os.stat(script)
|
||||
@ -2013,15 +1985,15 @@ def process_command(cmd, cmd_args, repro_dir):
|
||||
# Run the generated install.sh shell script as if it were being run in
|
||||
# a login shell.
|
||||
try:
|
||||
cmd_process = subprocess.Popen(["bash", "./{0}".format(script)])
|
||||
cmd_process = subprocess.Popen(["/bin/sh", "./{0}".format(script)])
|
||||
cmd_process.wait()
|
||||
exit_code = cmd_process.returncode
|
||||
except (ValueError, subprocess.CalledProcessError, OSError) as err:
|
||||
tty.error("Encountered error running {0} script".format(cmd))
|
||||
tty.error("Encountered error running {0} script".format(name))
|
||||
tty.error(err)
|
||||
exit_code = 1
|
||||
|
||||
tty.debug("spack {0} exited {1}".format(cmd, exit_code))
|
||||
tty.debug("spack {0} exited {1}".format(name, exit_code))
|
||||
return exit_code
|
||||
|
||||
|
||||
@ -2060,6 +2032,75 @@ def create_buildcache(**kwargs):
|
||||
push_mirror_contents(env, json_path, pipeline_mirror_url, sign_binaries)
|
||||
|
||||
|
||||
def write_broken_spec(url, pkg_name, stack_name, job_url, pipeline_url, spec_dict):
|
||||
"""Given a url to write to and the details of the failed job, write an entry
|
||||
in the broken specs list.
|
||||
"""
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
file_path = os.path.join(tmpdir, "broken.txt")
|
||||
|
||||
broken_spec_details = {
|
||||
"broken-spec": {
|
||||
"job-name": pkg_name,
|
||||
"job-stack": stack_name,
|
||||
"job-url": job_url,
|
||||
"pipeline-url": pipeline_url,
|
||||
"concrete-spec-dict": spec_dict,
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
with open(file_path, "w") as fd:
|
||||
fd.write(syaml.dump(broken_spec_details))
|
||||
web_util.push_to_url(
|
||||
file_path,
|
||||
url,
|
||||
keep_original=False,
|
||||
extra_args={"ContentType": "text/plain"},
|
||||
)
|
||||
except Exception as err:
|
||||
# If there is an S3 error (e.g., access denied or connection
|
||||
# error), the first non boto-specific class in the exception
|
||||
# hierarchy is Exception. Just print a warning and return
|
||||
msg = "Error writing to broken specs list {0}: {1}".format(url, err)
|
||||
tty.warn(msg)
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
def read_broken_spec(broken_spec_url):
|
||||
"""Read data from broken specs file located at the url, return as a yaml
|
||||
object.
|
||||
"""
|
||||
try:
|
||||
_, _, fs = web_util.read_from_url(broken_spec_url)
|
||||
except (URLError, web_util.SpackWebError, HTTPError):
|
||||
tty.warn("Unable to read broken spec from {0}".format(broken_spec_url))
|
||||
return None
|
||||
|
||||
broken_spec_contents = codecs.getreader("utf-8")(fs).read()
|
||||
return syaml.load(broken_spec_contents)
|
||||
|
||||
|
||||
def display_broken_spec_messages(base_url, hashes):
|
||||
"""Fetch the broken spec file for each of the hashes under the base_url and
|
||||
print a message with some details about each one.
|
||||
"""
|
||||
broken_specs = [(h, read_broken_spec(url_util.join(base_url, h))) for h in hashes]
|
||||
for spec_hash, broken_spec in [tup for tup in broken_specs if tup[1]]:
|
||||
details = broken_spec["broken-spec"]
|
||||
if "job-name" in details:
|
||||
item_name = "{0}/{1}".format(details["job-name"], spec_hash[:7])
|
||||
else:
|
||||
item_name = spec_hash
|
||||
|
||||
if "job-stack" in details:
|
||||
item_name = "{0} (in stack {1})".format(item_name, details["job-stack"])
|
||||
|
||||
msg = " {0} was reported broken here: {1}".format(item_name, details["job-url"])
|
||||
tty.msg(msg)
|
||||
|
||||
|
||||
def run_standalone_tests(**kwargs):
|
||||
"""Run stand-alone tests on the current spec.
|
||||
|
||||
@ -2095,8 +2136,9 @@ def run_standalone_tests(**kwargs):
|
||||
|
||||
test_args = [
|
||||
"spack",
|
||||
"-d",
|
||||
"-v",
|
||||
"--color=always",
|
||||
"--backtrace",
|
||||
"--verbose",
|
||||
"test",
|
||||
"run",
|
||||
]
|
||||
|
@ -234,7 +234,8 @@ def parse_specs(args, **kwargs):
|
||||
msg = e.message
|
||||
if e.long_message:
|
||||
msg += e.long_message
|
||||
if unquoted_flags:
|
||||
# Unquoted flags will be read as a variant or hash
|
||||
if unquoted_flags and ("variant" in msg or "hash" in msg):
|
||||
msg += "\n\n"
|
||||
msg += unquoted_flags.report()
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user