Compare commits
1 Commits
e4s-23.11
...
packages/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17c3d6ab19 |
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -5,13 +5,3 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
# Requirements to build documentation
|
|
||||||
- package-ecosystem: "pip"
|
|
||||||
directory: "/lib/spack/docs"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
# Requirements to run style checks
|
|
||||||
- package-ecosystem: "pip"
|
|
||||||
directory: "/.github/workflows/style"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
|
|||||||
15
.github/workflows/audit.yaml
vendored
15
.github/workflows/audit.yaml
vendored
@@ -17,13 +17,10 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
# Run audits on all the packages in the built-in repository
|
# Run audits on all the packages in the built-in repository
|
||||||
package-audits:
|
package-audits:
|
||||||
runs-on: ${{ matrix.operating_system }}
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
operating_system: ["ubuntu-latest", "macos-latest"]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # @v2
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b # @v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{inputs.python_version}}
|
python-version: ${{inputs.python_version}}
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
@@ -34,7 +31,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. share/spack/setup-env.sh
|
. share/spack/setup-env.sh
|
||||||
coverage run $(which spack) audit packages
|
coverage run $(which spack) audit packages
|
||||||
coverage run $(which spack) audit externals
|
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage xml
|
coverage xml
|
||||||
- name: Package audits (without coverage)
|
- name: Package audits (without coverage)
|
||||||
@@ -42,8 +38,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. share/spack/setup-env.sh
|
. share/spack/setup-env.sh
|
||||||
$(which spack) audit packages
|
$(which spack) audit packages
|
||||||
$(which spack) audit externals
|
- uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2 # @v2.1.0
|
||||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # @v2.1.0
|
|
||||||
if: ${{ inputs.with_coverage == 'true' }}
|
if: ${{ inputs.with_coverage == 'true' }}
|
||||||
with:
|
with:
|
||||||
flags: unittests,audits
|
flags: unittests,linux,audits
|
||||||
|
|||||||
40
.github/workflows/bootstrap.yml
vendored
40
.github/workflows/bootstrap.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
make patch unzip which xz python3 python3-devel tree \
|
make patch unzip which xz python3 python3-devel tree \
|
||||||
cmake bison bison-devel libstdc++-static
|
cmake bison bison-devel libstdc++-static
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup non-root user
|
- name: Setup non-root user
|
||||||
@@ -42,8 +42,8 @@ jobs:
|
|||||||
shell: runuser -u spack-test -- bash {0}
|
shell: runuser -u spack-test -- bash {0}
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack bootstrap disable github-actions-v0.5
|
|
||||||
spack bootstrap disable github-actions-v0.4
|
spack bootstrap disable github-actions-v0.4
|
||||||
|
spack bootstrap disable github-actions-v0.3
|
||||||
spack external find cmake bison
|
spack external find cmake bison
|
||||||
spack -d solve zlib
|
spack -d solve zlib
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
make patch unzip xz-utils python3 python3-dev tree \
|
make patch unzip xz-utils python3 python3-dev tree \
|
||||||
cmake bison
|
cmake bison
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup non-root user
|
- name: Setup non-root user
|
||||||
@@ -80,8 +80,8 @@ jobs:
|
|||||||
shell: runuser -u spack-test -- bash {0}
|
shell: runuser -u spack-test -- bash {0}
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack bootstrap disable github-actions-v0.5
|
|
||||||
spack bootstrap disable github-actions-v0.4
|
spack bootstrap disable github-actions-v0.4
|
||||||
|
spack bootstrap disable github-actions-v0.3
|
||||||
spack external find cmake bison
|
spack external find cmake bison
|
||||||
spack -d solve zlib
|
spack -d solve zlib
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
bzip2 curl file g++ gcc gfortran git gnupg2 gzip \
|
bzip2 curl file g++ gcc gfortran git gnupg2 gzip \
|
||||||
make patch unzip xz-utils python3 python3-dev tree
|
make patch unzip xz-utils python3 python3-dev tree
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup non-root user
|
- name: Setup non-root user
|
||||||
@@ -133,7 +133,7 @@ jobs:
|
|||||||
make patch unzip which xz python3 python3-devel tree \
|
make patch unzip which xz python3 python3-devel tree \
|
||||||
cmake bison
|
cmake bison
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup repo
|
- name: Setup repo
|
||||||
@@ -145,8 +145,8 @@ jobs:
|
|||||||
- name: Bootstrap clingo
|
- name: Bootstrap clingo
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack bootstrap disable github-actions-v0.5
|
|
||||||
spack bootstrap disable github-actions-v0.4
|
spack bootstrap disable github-actions-v0.4
|
||||||
|
spack bootstrap disable github-actions-v0.3
|
||||||
spack external find cmake bison
|
spack external find cmake bison
|
||||||
spack -d solve zlib
|
spack -d solve zlib
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
@@ -158,13 +158,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
brew install cmake bison@2.7 tree
|
brew install cmake bison@2.7 tree
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
- name: Bootstrap clingo
|
- name: Bootstrap clingo
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
export PATH=/usr/local/opt/bison@2.7/bin:$PATH
|
export PATH=/usr/local/opt/bison@2.7/bin:$PATH
|
||||||
spack bootstrap disable github-actions-v0.5
|
|
||||||
spack bootstrap disable github-actions-v0.4
|
spack bootstrap disable github-actions-v0.4
|
||||||
|
spack bootstrap disable github-actions-v0.3
|
||||||
spack external find --not-buildable cmake bison
|
spack external find --not-buildable cmake bison
|
||||||
spack -d solve zlib
|
spack -d solve zlib
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
@@ -179,11 +179,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
brew install tree
|
brew install tree
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
- name: Bootstrap clingo
|
- name: Bootstrap clingo
|
||||||
run: |
|
run: |
|
||||||
set -ex
|
set -ex
|
||||||
for ver in '3.7' '3.8' '3.9' '3.10' '3.11' ; do
|
for ver in '3.6' '3.7' '3.8' '3.9' '3.10' ; do
|
||||||
not_found=1
|
not_found=1
|
||||||
ver_dir="$(find $RUNNER_TOOL_CACHE/Python -wholename "*/${ver}.*/*/bin" | grep . || true)"
|
ver_dir="$(find $RUNNER_TOOL_CACHE/Python -wholename "*/${ver}.*/*/bin" | grep . || true)"
|
||||||
echo "Testing $ver_dir"
|
echo "Testing $ver_dir"
|
||||||
@@ -204,7 +204,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup repo
|
- name: Setup repo
|
||||||
@@ -214,7 +214,7 @@ jobs:
|
|||||||
- name: Bootstrap clingo
|
- name: Bootstrap clingo
|
||||||
run: |
|
run: |
|
||||||
set -ex
|
set -ex
|
||||||
for ver in '3.7' '3.8' '3.9' '3.10' '3.11' ; do
|
for ver in '3.6' '3.7' '3.8' '3.9' '3.10' ; do
|
||||||
not_found=1
|
not_found=1
|
||||||
ver_dir="$(find $RUNNER_TOOL_CACHE/Python -wholename "*/${ver}.*/*/bin" | grep . || true)"
|
ver_dir="$(find $RUNNER_TOOL_CACHE/Python -wholename "*/${ver}.*/*/bin" | grep . || true)"
|
||||||
echo "Testing $ver_dir"
|
echo "Testing $ver_dir"
|
||||||
@@ -247,7 +247,7 @@ jobs:
|
|||||||
bzip2 curl file g++ gcc patchelf gfortran git gzip \
|
bzip2 curl file g++ gcc patchelf gfortran git gzip \
|
||||||
make patch unzip xz-utils python3 python3-dev tree
|
make patch unzip xz-utils python3 python3-dev tree
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup non-root user
|
- name: Setup non-root user
|
||||||
@@ -265,7 +265,6 @@ jobs:
|
|||||||
shell: runuser -u spack-test -- bash {0}
|
shell: runuser -u spack-test -- bash {0}
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack bootstrap disable github-actions-v0.4
|
|
||||||
spack bootstrap disable spack-install
|
spack bootstrap disable spack-install
|
||||||
spack -d gpg list
|
spack -d gpg list
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
@@ -284,7 +283,7 @@ jobs:
|
|||||||
make patch unzip xz-utils python3 python3-dev tree \
|
make patch unzip xz-utils python3 python3-dev tree \
|
||||||
gawk
|
gawk
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup non-root user
|
- name: Setup non-root user
|
||||||
@@ -303,8 +302,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack solve zlib
|
spack solve zlib
|
||||||
spack bootstrap disable github-actions-v0.5
|
|
||||||
spack bootstrap disable github-actions-v0.4
|
spack bootstrap disable github-actions-v0.4
|
||||||
|
spack bootstrap disable github-actions-v0.3
|
||||||
spack -d gpg list
|
spack -d gpg list
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
|
|
||||||
@@ -317,11 +316,10 @@ jobs:
|
|||||||
# Remove GnuPG since we want to bootstrap it
|
# Remove GnuPG since we want to bootstrap it
|
||||||
sudo rm -rf /usr/local/bin/gpg
|
sudo rm -rf /usr/local/bin/gpg
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
- name: Bootstrap GnuPG
|
- name: Bootstrap GnuPG
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack bootstrap disable github-actions-v0.4
|
|
||||||
spack bootstrap disable spack-install
|
spack bootstrap disable spack-install
|
||||||
spack -d gpg list
|
spack -d gpg list
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
@@ -335,13 +333,13 @@ jobs:
|
|||||||
# Remove GnuPG since we want to bootstrap it
|
# Remove GnuPG since we want to bootstrap it
|
||||||
sudo rm -rf /usr/local/bin/gpg
|
sudo rm -rf /usr/local/bin/gpg
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
- name: Bootstrap GnuPG
|
- name: Bootstrap GnuPG
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack solve zlib
|
spack solve zlib
|
||||||
spack bootstrap disable github-actions-v0.5
|
|
||||||
spack bootstrap disable github-actions-v0.4
|
spack bootstrap disable github-actions-v0.4
|
||||||
|
spack bootstrap disable github-actions-v0.3
|
||||||
spack -d gpg list
|
spack -d gpg list
|
||||||
tree ~/.spack/bootstrap/store/
|
tree ~/.spack/bootstrap/store/
|
||||||
|
|
||||||
|
|||||||
16
.github/workflows/build-containers.yml
vendored
16
.github/workflows/build-containers.yml
vendored
@@ -49,14 +49,14 @@ jobs:
|
|||||||
[almalinux8, 'linux/amd64,linux/arm64,linux/ppc64le', 'almalinux:8'],
|
[almalinux8, 'linux/amd64,linux/arm64,linux/ppc64le', 'almalinux:8'],
|
||||||
[almalinux9, 'linux/amd64,linux/arm64,linux/ppc64le', 'almalinux:9'],
|
[almalinux9, 'linux/amd64,linux/arm64,linux/ppc64le', 'almalinux:9'],
|
||||||
[rockylinux8, 'linux/amd64,linux/arm64', 'rockylinux:8'],
|
[rockylinux8, 'linux/amd64,linux/arm64', 'rockylinux:8'],
|
||||||
[rockylinux9, 'linux/amd64,linux/arm64', 'rockylinux:9'],
|
[rockylinux9, 'linux/amd64,linux/arm64,linux/ppc64le', 'rockylinux:9'],
|
||||||
[fedora37, 'linux/amd64,linux/arm64,linux/ppc64le', 'fedora:37'],
|
[fedora37, 'linux/amd64,linux/arm64,linux/ppc64le', 'fedora:37'],
|
||||||
[fedora38, 'linux/amd64,linux/arm64,linux/ppc64le', 'fedora:38']]
|
[fedora38, 'linux/amd64,linux/arm64,linux/ppc64le', 'fedora:38']]
|
||||||
name: Build ${{ matrix.dockerfile[0] }}
|
name: Build ${{ matrix.dockerfile[0] }}
|
||||||
if: github.repository == 'spack/spack'
|
if: github.repository == 'spack/spack'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
|
|
||||||
- name: Set Container Tag Normal (Nightly)
|
- name: Set Container Tag Normal (Nightly)
|
||||||
run: |
|
run: |
|
||||||
@@ -86,19 +86,19 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload Dockerfile
|
- name: Upload Dockerfile
|
||||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
|
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
|
||||||
with:
|
with:
|
||||||
name: dockerfiles
|
name: dockerfiles
|
||||||
path: dockerfiles
|
path: dockerfiles
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # @v1
|
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # @v1
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # @v1
|
uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # @v1
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # @v1
|
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # @v1
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -106,13 +106,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Log in to DockerHub
|
- name: Log in to DockerHub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # @v1
|
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # @v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build & Deploy ${{ matrix.dockerfile[0] }}
|
- name: Build & Deploy ${{ matrix.dockerfile[0] }}
|
||||||
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # @v2
|
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # @v2
|
||||||
with:
|
with:
|
||||||
context: dockerfiles/${{ matrix.dockerfile[0] }}
|
context: dockerfiles/${{ matrix.dockerfile[0] }}
|
||||||
platforms: ${{ matrix.dockerfile[1] }}
|
platforms: ${{ matrix.dockerfile[1] }}
|
||||||
|
|||||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
core: ${{ steps.filter.outputs.core }}
|
core: ${{ steps.filter.outputs.core }}
|
||||||
packages: ${{ steps.filter.outputs.packages }}
|
packages: ${{ steps.filter.outputs.packages }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|||||||
31
.github/workflows/nightly-win-builds.yml
vendored
31
.github/workflows/nightly-win-builds.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
name: Windows Paraview Nightly
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 2 * * *' # Run at 2 am
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell:
|
|
||||||
powershell Invoke-Expression -Command "./share/spack/qa/windows_test_setup.ps1"; {0}
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-paraview-deps:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- name: Install Python packages
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip six pywin32 setuptools coverage
|
|
||||||
- name: Build Test
|
|
||||||
run: |
|
|
||||||
spack compiler find
|
|
||||||
spack external find cmake ninja win-sdk win-wdk wgl msmpi
|
|
||||||
spack -d install -y --cdash-upload-url https://cdash.spack.io/submit.php?project=Spack+on+Windows --cdash-track Nightly --only dependencies paraview
|
|
||||||
exit 0
|
|
||||||
7
.github/workflows/style/requirements.txt
vendored
7
.github/workflows/style/requirements.txt
vendored
@@ -1,7 +0,0 @@
|
|||||||
black==23.10.1
|
|
||||||
clingo==5.6.2
|
|
||||||
flake8==6.1.0
|
|
||||||
isort==5.12.0
|
|
||||||
mypy==1.6.1
|
|
||||||
types-six==1.16.21.9
|
|
||||||
vermin==1.5.2
|
|
||||||
35
.github/workflows/unit_tests.yaml
vendored
35
.github/workflows/unit_tests.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
|
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
|
||||||
concretizer: ['clingo']
|
concretizer: ['clingo']
|
||||||
on_develop:
|
on_develop:
|
||||||
- ${{ github.ref == 'refs/heads/develop' }}
|
- ${{ github.ref == 'refs/heads/develop' }}
|
||||||
@@ -45,16 +45,12 @@ jobs:
|
|||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
concretizer: 'clingo'
|
concretizer: 'clingo'
|
||||||
on_develop: false
|
on_develop: false
|
||||||
- python-version: '3.11'
|
|
||||||
os: ubuntu-latest
|
|
||||||
concretizer: 'clingo'
|
|
||||||
on_develop: false
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # @v2
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b # @v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install System packages
|
- name: Install System packages
|
||||||
@@ -91,17 +87,17 @@ jobs:
|
|||||||
UNIT_TEST_COVERAGE: ${{ matrix.python-version == '3.11' }}
|
UNIT_TEST_COVERAGE: ${{ matrix.python-version == '3.11' }}
|
||||||
run: |
|
run: |
|
||||||
share/spack/qa/run-unit-tests
|
share/spack/qa/run-unit-tests
|
||||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
- uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2
|
||||||
with:
|
with:
|
||||||
flags: unittests,linux,${{ matrix.concretizer }}
|
flags: unittests,linux,${{ matrix.concretizer }}
|
||||||
# Test shell integration
|
# Test shell integration
|
||||||
shell:
|
shell:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # @v2
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b # @v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
- name: Install System packages
|
- name: Install System packages
|
||||||
@@ -122,7 +118,7 @@ jobs:
|
|||||||
COVERAGE: true
|
COVERAGE: true
|
||||||
run: |
|
run: |
|
||||||
share/spack/qa/run-shell-tests
|
share/spack/qa/run-shell-tests
|
||||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
- uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2
|
||||||
with:
|
with:
|
||||||
flags: shelltests,linux
|
flags: shelltests,linux
|
||||||
|
|
||||||
@@ -137,7 +133,7 @@ jobs:
|
|||||||
dnf install -y \
|
dnf install -y \
|
||||||
bzip2 curl file gcc-c++ gcc gcc-gfortran git gnupg2 gzip \
|
bzip2 curl file gcc-c++ gcc gcc-gfortran git gnupg2 gzip \
|
||||||
make patch tcl unzip which xz
|
make patch tcl unzip which xz
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
- name: Setup repo and non-root user
|
- name: Setup repo and non-root user
|
||||||
run: |
|
run: |
|
||||||
git --version
|
git --version
|
||||||
@@ -156,10 +152,10 @@ jobs:
|
|||||||
clingo-cffi:
|
clingo-cffi:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # @v2
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b # @v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
- name: Install System packages
|
- name: Install System packages
|
||||||
@@ -169,7 +165,6 @@ jobs:
|
|||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip setuptools pytest coverage[toml] pytest-cov clingo pytest-xdist
|
pip install --upgrade pip setuptools pytest coverage[toml] pytest-cov clingo pytest-xdist
|
||||||
pip install --upgrade flake8 "isort>=4.3.5" "mypy>=0.900" "click" "black"
|
|
||||||
- name: Setup git configuration
|
- name: Setup git configuration
|
||||||
run: |
|
run: |
|
||||||
# Need this for the git tests to succeed.
|
# Need this for the git tests to succeed.
|
||||||
@@ -181,7 +176,7 @@ jobs:
|
|||||||
SPACK_TEST_SOLVER: clingo
|
SPACK_TEST_SOLVER: clingo
|
||||||
run: |
|
run: |
|
||||||
share/spack/qa/run-unit-tests
|
share/spack/qa/run-unit-tests
|
||||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # @v2.1.0
|
- uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2 # @v2.1.0
|
||||||
with:
|
with:
|
||||||
flags: unittests,linux,clingo
|
flags: unittests,linux,clingo
|
||||||
# Run unit tests on MacOS
|
# Run unit tests on MacOS
|
||||||
@@ -189,12 +184,12 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.11"]
|
python-version: ["3.10"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # @v2
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b # @v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
@@ -216,6 +211,6 @@ jobs:
|
|||||||
$(which spack) solve zlib
|
$(which spack) solve zlib
|
||||||
common_args=(--dist loadfile --tx '4*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python' -x)
|
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 --cov-report=xml:coverage.xml "${common_args[@]}"
|
$(which spack) unit-test --cov --cov-config=pyproject.toml --cov-report=xml:coverage.xml "${common_args[@]}"
|
||||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
- uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2
|
||||||
with:
|
with:
|
||||||
flags: unittests,macos
|
flags: unittests,macos
|
||||||
|
|||||||
18
.github/workflows/valid-style.yml
vendored
18
.github/workflows/valid-style.yml
vendored
@@ -18,15 +18,15 @@ jobs:
|
|||||||
validate:
|
validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b # @v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
- name: Install Python Packages
|
- name: Install Python Packages
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip setuptools
|
pip install --upgrade pip
|
||||||
pip install -r .github/workflows/style/requirements.txt
|
pip install --upgrade vermin
|
||||||
- name: vermin (Spack's Core)
|
- name: vermin (Spack's Core)
|
||||||
run: vermin --backport importlib --backport argparse --violations --backport typing -t=3.6- -vvv lib/spack/spack/ lib/spack/llnl/ bin/
|
run: vermin --backport importlib --backport argparse --violations --backport typing -t=3.6- -vvv lib/spack/spack/ lib/spack/llnl/ bin/
|
||||||
- name: vermin (Repositories)
|
- name: vermin (Repositories)
|
||||||
@@ -35,17 +35,16 @@ jobs:
|
|||||||
style:
|
style:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b # @v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip setuptools
|
python3 -m pip install --upgrade pip setuptools types-six black==23.1.0 mypy isort clingo flake8
|
||||||
pip install -r .github/workflows/style/requirements.txt
|
|
||||||
- name: Setup git configuration
|
- name: Setup git configuration
|
||||||
run: |
|
run: |
|
||||||
# Need this for the git tests to succeed.
|
# Need this for the git tests to succeed.
|
||||||
@@ -69,7 +68,7 @@ jobs:
|
|||||||
dnf install -y \
|
dnf install -y \
|
||||||
bzip2 curl file gcc-c++ gcc gcc-gfortran git gnupg2 gzip \
|
bzip2 curl file gcc-c++ gcc gcc-gfortran git gnupg2 gzip \
|
||||||
make patch tcl unzip which xz
|
make patch tcl unzip which xz
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # @v2
|
||||||
- name: Setup repo and non-root user
|
- name: Setup repo and non-root user
|
||||||
run: |
|
run: |
|
||||||
git --version
|
git --version
|
||||||
@@ -82,7 +81,6 @@ jobs:
|
|||||||
shell: runuser -u spack-test -- bash {0}
|
shell: runuser -u spack-test -- bash {0}
|
||||||
run: |
|
run: |
|
||||||
source share/spack/setup-env.sh
|
source share/spack/setup-env.sh
|
||||||
spack debug report
|
|
||||||
spack -d bootstrap now --dev
|
spack -d bootstrap now --dev
|
||||||
spack style -t black
|
spack style -t black
|
||||||
spack unit-test -V
|
spack unit-test -V
|
||||||
|
|||||||
19
.github/workflows/windows_python.yml
vendored
19
.github/workflows/windows_python.yml
vendored
@@ -15,10 +15,10 @@ jobs:
|
|||||||
unit-tests:
|
unit-tests:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
@@ -33,16 +33,16 @@ jobs:
|
|||||||
./share/spack/qa/validate_last_exit.ps1
|
./share/spack/qa/validate_last_exit.ps1
|
||||||
coverage combine -a
|
coverage combine -a
|
||||||
coverage xml
|
coverage xml
|
||||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
- uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2
|
||||||
with:
|
with:
|
||||||
flags: unittests,windows
|
flags: unittests,windows
|
||||||
unit-tests-cmd:
|
unit-tests-cmd:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
@@ -57,16 +57,16 @@ jobs:
|
|||||||
./share/spack/qa/validate_last_exit.ps1
|
./share/spack/qa/validate_last_exit.ps1
|
||||||
coverage combine -a
|
coverage combine -a
|
||||||
coverage xml
|
coverage xml
|
||||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
- uses: codecov/codecov-action@894ff025c7b54547a9a2a1e9f228beae737ad3c2
|
||||||
with:
|
with:
|
||||||
flags: unittests,windows
|
flags: unittests,windows
|
||||||
build-abseil:
|
build-abseil:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236
|
- uses: actions/setup-python@57ded4d7d5e986d7296eab16560982c6dd7c923b
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
@@ -75,5 +75,6 @@ jobs:
|
|||||||
- name: Build Test
|
- name: Build Test
|
||||||
run: |
|
run: |
|
||||||
spack compiler find
|
spack compiler find
|
||||||
spack -d external find cmake ninja
|
spack external find cmake
|
||||||
|
spack external find ninja
|
||||||
spack -d install abseil-cpp
|
spack -d install abseil-cpp
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
build:
|
|
||||||
os: "ubuntu-22.04"
|
|
||||||
apt_packages:
|
|
||||||
- graphviz
|
|
||||||
tools:
|
|
||||||
python: "3.11"
|
|
||||||
|
|
||||||
sphinx:
|
sphinx:
|
||||||
configuration: lib/spack/docs/conf.py
|
configuration: lib/spack/docs/conf.py
|
||||||
fail_on_warning: true
|
fail_on_warning: true
|
||||||
|
|
||||||
python:
|
python:
|
||||||
|
version: 3.7
|
||||||
install:
|
install:
|
||||||
- requirements: lib/spack/docs/requirements.txt
|
- requirements: lib/spack/docs/requirements.txt
|
||||||
|
|||||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,51 +1,3 @@
|
|||||||
# v0.20.3 (2023-10-31)
|
|
||||||
|
|
||||||
## Bugfixes
|
|
||||||
|
|
||||||
- Fix a bug where `spack mirror set-url` would drop configured connection info (reverts #34210)
|
|
||||||
- Fix a minor issue with package hash computation for Python 3.12 (#40328)
|
|
||||||
|
|
||||||
|
|
||||||
# v0.20.2 (2023-10-03)
|
|
||||||
|
|
||||||
## Features in this release
|
|
||||||
|
|
||||||
Spack now supports Python 3.12 (#40155)
|
|
||||||
|
|
||||||
## Bugfixes
|
|
||||||
|
|
||||||
- Improve escaping in Tcl module files (#38375)
|
|
||||||
- Make repo cache work on repositories with zero mtime (#39214)
|
|
||||||
- Ignore errors for newer, incompatible buildcache version (#40279)
|
|
||||||
- Print an error when git is required, but missing (#40254)
|
|
||||||
- Ensure missing build dependencies get installed when using `spack install --overwrite` (#40252)
|
|
||||||
- Fix an issue where Spack freezes when the build process unexpectedly exits (#39015)
|
|
||||||
- Fix a bug where installation failures cause an unrelated `NameError` to be thrown (#39017)
|
|
||||||
- Fix an issue where Spack package versions would be incorrectly derived from git tags (#39414)
|
|
||||||
- Fix a bug triggered when file locking fails internally (#39188)
|
|
||||||
- Prevent "spack external find" to error out when a directory cannot be accessed (#38755)
|
|
||||||
- Fix multiple performance regressions in environments (#38771)
|
|
||||||
- Add more ignored modules to `pyproject.toml` for `mypy` (#38769)
|
|
||||||
|
|
||||||
|
|
||||||
# v0.20.1 (2023-07-10)
|
|
||||||
|
|
||||||
## Spack Bugfixes
|
|
||||||
|
|
||||||
- Spec removed from an environment where not actually removed if `--force` was not given (#37877)
|
|
||||||
- Speed-up module file generation (#37739)
|
|
||||||
- Hotfix for a few recipes that treat CMake as a link dependency (#35816)
|
|
||||||
- Fix re-running stand-alone test a second time, which was getting a trailing spurious failure (#37840)
|
|
||||||
- Fixed reading JSON manifest on Cray, reporting non-concrete specs (#37909)
|
|
||||||
- Fixed a few bugs when generating Dockerfiles from Spack (#37766,#37769)
|
|
||||||
- Fixed a few long-standing bugs when generating module files (#36678,#38347,#38465,#38455)
|
|
||||||
- Fixed issues with building Python extensions using an external Python (#38186)
|
|
||||||
- Fixed compiler removal from command line (#38057)
|
|
||||||
- Show external status as [e] (#33792)
|
|
||||||
- Backported `archspec` fixes (#37793)
|
|
||||||
- Improved a few error messages (#37791)
|
|
||||||
|
|
||||||
|
|
||||||
# v0.20.0 (2023-05-21)
|
# v0.20.0 (2023-05-21)
|
||||||
|
|
||||||
`v0.20.0` is a major feature release.
|
`v0.20.0` is a major feature release.
|
||||||
|
|||||||
54
CITATION.cff
54
CITATION.cff
@@ -27,53 +27,12 @@
|
|||||||
# And here's the CITATION.cff format:
|
# And here's the CITATION.cff format:
|
||||||
#
|
#
|
||||||
cff-version: 1.2.0
|
cff-version: 1.2.0
|
||||||
type: software
|
|
||||||
message: "If you are referencing Spack in a publication, please cite the paper below."
|
message: "If you are referencing Spack in a publication, please cite the paper below."
|
||||||
title: "The Spack Package Manager: Bringing Order to HPC Software Chaos"
|
|
||||||
abstract: >-
|
|
||||||
Large HPC centers spend considerable time supporting software for thousands of users, but the complexity of HPC software is quickly outpacing the capabilities of existing software management tools.
|
|
||||||
Scientific applications require specific versions of compilers, MPI, and other dependency libraries, so using a single, standard software stack is infeasible.
|
|
||||||
However, managing many configurations is difficult because the configuration space is combinatorial in size.
|
|
||||||
We introduce Spack, a tool used at Lawrence Livermore National Laboratory to manage this complexity.
|
|
||||||
Spack provides a novel, re- cursive specification syntax to invoke parametric builds of packages and dependencies.
|
|
||||||
It allows any number of builds to coexist on the same system, and it ensures that installed packages can find their dependencies, regardless of the environment.
|
|
||||||
We show through real-world use cases that Spack supports diverse and demanding applications, bringing order to HPC software chaos.
|
|
||||||
preferred-citation:
|
preferred-citation:
|
||||||
title: "The Spack Package Manager: Bringing Order to HPC Software Chaos"
|
|
||||||
type: conference-paper
|
type: conference-paper
|
||||||
url: "https://tgamblin.github.io/pubs/spack-sc15.pdf"
|
doi: "10.1145/2807591.2807623"
|
||||||
|
url: "https://github.com/spack/spack"
|
||||||
authors:
|
authors:
|
||||||
- family-names: "Gamblin"
|
|
||||||
given-names: "Todd"
|
|
||||||
- family-names: "LeGendre"
|
|
||||||
given-names: "Matthew"
|
|
||||||
- family-names: "Collette"
|
|
||||||
given-names: "Michael R."
|
|
||||||
- family-names: "Lee"
|
|
||||||
given-names: "Gregory L."
|
|
||||||
- family-names: "Moody"
|
|
||||||
given-names: "Adam"
|
|
||||||
- family-names: "de Supinski"
|
|
||||||
given-names: "Bronis R."
|
|
||||||
- family-names: "Futral"
|
|
||||||
given-names: "Scott"
|
|
||||||
conference:
|
|
||||||
name: "Supercomputing 2015 (SC’15)"
|
|
||||||
city: "Austin"
|
|
||||||
region: "Texas"
|
|
||||||
country: "US"
|
|
||||||
date-start: 2015-11-15
|
|
||||||
date-end: 2015-11-20
|
|
||||||
month: 11
|
|
||||||
year: 2015
|
|
||||||
identifiers:
|
|
||||||
- description: "The concept DOI of the work."
|
|
||||||
type: doi
|
|
||||||
value: 10.1145/2807591.2807623
|
|
||||||
- description: "The DOE Document Release Number of the work"
|
|
||||||
type: other
|
|
||||||
value: "LLNL-CONF-669890"
|
|
||||||
authors:
|
|
||||||
- family-names: "Gamblin"
|
- family-names: "Gamblin"
|
||||||
given-names: "Todd"
|
given-names: "Todd"
|
||||||
- family-names: "LeGendre"
|
- family-names: "LeGendre"
|
||||||
@@ -88,3 +47,12 @@ authors:
|
|||||||
given-names: "Bronis R."
|
given-names: "Bronis R."
|
||||||
- family-names: "Futral"
|
- family-names: "Futral"
|
||||||
given-names: "Scott"
|
given-names: "Scott"
|
||||||
|
title: "The Spack Package Manager: Bringing Order to HPC Software Chaos"
|
||||||
|
conference:
|
||||||
|
name: "Supercomputing 2015 (SC’15)"
|
||||||
|
city: "Austin"
|
||||||
|
region: "Texas"
|
||||||
|
country: "USA"
|
||||||
|
month: November 15-20
|
||||||
|
year: 2015
|
||||||
|
notes: LLNL-CONF-669890
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
[](https://spack.readthedocs.io)
|
[](https://spack.readthedocs.io)
|
||||||
[](https://github.com/psf/black)
|
[](https://github.com/psf/black)
|
||||||
[](https://slack.spack.io)
|
[](https://slack.spack.io)
|
||||||
[](https://matrix.to/#/#spack-space:matrix.org)
|
|
||||||
|
|
||||||
Spack is a multi-platform package manager that builds and installs
|
Spack is a multi-platform package manager that builds and installs
|
||||||
multiple versions and configurations of software. It works on Linux,
|
multiple versions and configurations of software. It works on Linux,
|
||||||
@@ -63,10 +62,7 @@ Resources:
|
|||||||
|
|
||||||
* **Slack workspace**: [spackpm.slack.com](https://spackpm.slack.com).
|
* **Slack workspace**: [spackpm.slack.com](https://spackpm.slack.com).
|
||||||
To get an invitation, visit [slack.spack.io](https://slack.spack.io).
|
To get an invitation, visit [slack.spack.io](https://slack.spack.io).
|
||||||
* **Matrix space**: [#spack-space:matrix.org](https://matrix.to/#/#spack-space:matrix.org):
|
* [**Github Discussions**](https://github.com/spack/spack/discussions): not just for discussions, also Q&A.
|
||||||
[bridged](https://github.com/matrix-org/matrix-appservice-slack#matrix-appservice-slack) to Slack.
|
|
||||||
* [**Github Discussions**](https://github.com/spack/spack/discussions):
|
|
||||||
not just for discussions, but also Q&A.
|
|
||||||
* **Mailing list**: [groups.google.com/d/forum/spack](https://groups.google.com/d/forum/spack)
|
* **Mailing list**: [groups.google.com/d/forum/spack](https://groups.google.com/d/forum/spack)
|
||||||
* **Twitter**: [@spackpm](https://twitter.com/spackpm). Be sure to
|
* **Twitter**: [@spackpm](https://twitter.com/spackpm). Be sure to
|
||||||
`@mention` us!
|
`@mention` us!
|
||||||
|
|||||||
32
SECURITY.md
32
SECURITY.md
@@ -2,26 +2,24 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
We provide security updates for `develop` and for the last two
|
We provide security updates for the following releases.
|
||||||
stable (`0.x`) release series of Spack. Security updates will be
|
|
||||||
made available as patch (`0.x.1`, `0.x.2`, etc.) releases.
|
|
||||||
|
|
||||||
For more on Spack's release structure, see
|
For more on Spack's release structure, see
|
||||||
[`README.md`](https://github.com/spack/spack#releases).
|
[`README.md`](https://github.com/spack/spack#releases).
|
||||||
|
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| develop | :white_check_mark: |
|
||||||
|
| 0.19.x | :white_check_mark: |
|
||||||
|
| 0.18.x | :white_check_mark: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
You can report a vulnerability using GitHub's private reporting
|
To report a vulnerability or other security
|
||||||
feature:
|
issue, email maintainers@spack.io.
|
||||||
|
|
||||||
1. Go to [github.com/spack/spack/security](https://github.com/spack/spack/security).
|
You can expect to hear back within two days.
|
||||||
2. Click "Report a vulnerability" in the upper right corner of that page.
|
If your security issue is accepted, we will do
|
||||||
3. Fill out the form and submit your draft security advisory.
|
our best to release a fix within a week. If
|
||||||
|
fixing the issue will take longer than this,
|
||||||
More details are available in
|
we will discuss timeline options with you.
|
||||||
[GitHub's docs](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability).
|
|
||||||
|
|
||||||
You can expect to hear back about security issues within two days.
|
|
||||||
If your security issue is accepted, we will do our best to release
|
|
||||||
a fix within a week. If fixing the issue will take longer than
|
|
||||||
this, we will discuss timeline options with you.
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ exit 1
|
|||||||
# Line above is a shell no-op, and ends a python multi-line comment.
|
# Line above is a shell no-op, and ends a python multi-line comment.
|
||||||
# The code above runs this file with our preferred python interpreter.
|
# The code above runs this file with our preferred python interpreter.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
::
|
::
|
||||||
@echo off
|
@echo off
|
||||||
|
|
||||||
set spack="%SPACK_ROOT%"\bin\spack
|
set spack=%SPACK_ROOT%\bin\spack
|
||||||
|
|
||||||
::#######################################################################
|
::#######################################################################
|
||||||
:: This is a wrapper around the spack command that forwards calls to
|
:: This is a wrapper around the spack command that forwards calls to
|
||||||
@@ -51,43 +51,65 @@ setlocal enabledelayedexpansion
|
|||||||
:: subcommands will never start with '-'
|
:: subcommands will never start with '-'
|
||||||
:: everything after the subcommand is an arg
|
:: everything after the subcommand is an arg
|
||||||
|
|
||||||
|
:: we cannot allow batch "for" loop to directly process CL args
|
||||||
|
:: a number of batch reserved characters are commonly passed to
|
||||||
|
:: spack and allowing batch's "for" method to process the raw inputs
|
||||||
|
:: results in a large number of formatting issues
|
||||||
|
:: instead, treat the entire CLI as one string
|
||||||
|
:: and split by space manually
|
||||||
|
:: capture cl args in variable named cl_args
|
||||||
|
set cl_args=%*
|
||||||
:process_cl_args
|
:process_cl_args
|
||||||
rem Set first cl argument (denoted by %1) to be processed
|
rem tokens=1* returns the first processed token produced
|
||||||
set t=%1
|
rem by tokenizing the input string cl_args on spaces into
|
||||||
rem shift moves all cl positional arguments left by one
|
rem the named variable %%g
|
||||||
rem meaning %2 is now %1, this allows us to iterate over each
|
rem While this make look like a for loop, it only
|
||||||
rem argument
|
rem executes a single time for each of the cl args
|
||||||
shift
|
rem the actual iterative loop is performed by the
|
||||||
rem assign next "first" cl argument to cl_args, will be null when
|
rem goto process_cl_args stanza
|
||||||
rem there are now further arguments to process
|
rem we are simply leveraging the "for" method's string
|
||||||
set cl_args=%1
|
rem tokenization
|
||||||
if "!t:~0,1!" == "-" (
|
for /f "tokens=1*" %%g in ("%cl_args%") do (
|
||||||
if defined _sp_subcommand (
|
set t=%%~g
|
||||||
rem We already have a subcommand, processing args now
|
rem remainder of string is composed into %%h
|
||||||
|
rem these are the cl args yet to be processed
|
||||||
|
rem assign cl_args var to only the args to be processed
|
||||||
|
rem effectively discarding the current arg %%g
|
||||||
|
rem this will be nul when we have no further tokens to process
|
||||||
|
set cl_args=%%h
|
||||||
|
rem process the first space delineated cl arg
|
||||||
|
rem of this iteration
|
||||||
|
if "!t:~0,1!" == "-" (
|
||||||
|
if defined _sp_subcommand (
|
||||||
|
rem We already have a subcommand, processing args now
|
||||||
|
if not defined _sp_args (
|
||||||
|
set "_sp_args=!t!"
|
||||||
|
) else (
|
||||||
|
set "_sp_args=!_sp_args! !t!"
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
if not defined _sp_flags (
|
||||||
|
set "_sp_flags=!t!"
|
||||||
|
shift
|
||||||
|
) else (
|
||||||
|
set "_sp_flags=!_sp_flags! !t!"
|
||||||
|
shift
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) else if not defined _sp_subcommand (
|
||||||
|
set "_sp_subcommand=!t!"
|
||||||
|
shift
|
||||||
|
) else (
|
||||||
if not defined _sp_args (
|
if not defined _sp_args (
|
||||||
set "_sp_args=!t!"
|
set "_sp_args=!t!"
|
||||||
|
shift
|
||||||
) else (
|
) else (
|
||||||
set "_sp_args=!_sp_args! !t!"
|
set "_sp_args=!_sp_args! !t!"
|
||||||
|
shift
|
||||||
)
|
)
|
||||||
) else (
|
|
||||||
if not defined _sp_flags (
|
|
||||||
set "_sp_flags=!t!"
|
|
||||||
) else (
|
|
||||||
set "_sp_flags=!_sp_flags! !t!"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) else if not defined _sp_subcommand (
|
|
||||||
set "_sp_subcommand=!t!"
|
|
||||||
) else (
|
|
||||||
if not defined _sp_args (
|
|
||||||
set "_sp_args=!t!"
|
|
||||||
) else (
|
|
||||||
set "_sp_args=!_sp_args! !t!"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
rem if this is not nil, we have more tokens to process
|
||||||
rem if this is not nu;ll, we have more tokens to process
|
|
||||||
rem start above process again with remaining unprocessed cl args
|
rem start above process again with remaining unprocessed cl args
|
||||||
if defined cl_args goto :process_cl_args
|
if defined cl_args goto :process_cl_args
|
||||||
|
|
||||||
@@ -192,7 +214,7 @@ goto :end_switch
|
|||||||
if defined _sp_args (
|
if defined _sp_args (
|
||||||
if NOT "%_sp_args%"=="%_sp_args:--help=%" (
|
if NOT "%_sp_args%"=="%_sp_args:--help=%" (
|
||||||
goto :default_case
|
goto :default_case
|
||||||
) else if NOT "%_sp_args%"=="%_sp_args:-h=%" (
|
) else if NOT "%_sp_args%"=="%_sp_args: -h=%" (
|
||||||
goto :default_case
|
goto :default_case
|
||||||
) else if NOT "%_sp_args%"=="%_sp_args:--bat=%" (
|
) else if NOT "%_sp_args%"=="%_sp_args:--bat=%" (
|
||||||
goto :default_case
|
goto :default_case
|
||||||
|
|||||||
146
bin/spack.ps1
146
bin/spack.ps1
@@ -1,146 +0,0 @@
|
|||||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
|
||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
# #######################################################################
|
|
||||||
|
|
||||||
function Compare-CommonArgs {
|
|
||||||
$CMDArgs = $args[0]
|
|
||||||
# These aruments take precedence and call for no futher parsing of arguments
|
|
||||||
# invoke actual Spack entrypoint with that context and exit after
|
|
||||||
"--help", "-h", "--version", "-V" | ForEach-Object {
|
|
||||||
$arg_opt = $_
|
|
||||||
if(($CMDArgs) -and ([bool]($CMDArgs.Where({$_ -eq $arg_opt})))) {
|
|
||||||
return $true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
|
|
||||||
function Read-SpackArgs {
|
|
||||||
$SpackCMD_params = @()
|
|
||||||
$SpackSubCommand = $NULL
|
|
||||||
$SpackSubCommandArgs = @()
|
|
||||||
$args_ = $args[0]
|
|
||||||
$args_ | ForEach-Object {
|
|
||||||
if (!$SpackSubCommand) {
|
|
||||||
if($_.SubString(0,1) -eq "-")
|
|
||||||
{
|
|
||||||
$SpackCMD_params += $_
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$SpackSubCommand = $_
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$SpackSubCommandArgs += $_
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $SpackCMD_params, $SpackSubCommand, $SpackSubCommandArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
function Set-SpackEnv {
|
|
||||||
# This method is responsible
|
|
||||||
# for processing the return from $(spack <command>)
|
|
||||||
# which are returned as System.Object[]'s containing
|
|
||||||
# a list of env commands
|
|
||||||
# Invoke-Expression can only handle one command at a time
|
|
||||||
# so we iterate over the list to invoke the env modification
|
|
||||||
# expressions one at a time
|
|
||||||
foreach($envop in $args[0]){
|
|
||||||
Invoke-Expression $envop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function Invoke-SpackCD {
|
|
||||||
if (Compare-CommonArgs $SpackSubCommandArgs) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" cd -h
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$LOC = $(python "$Env:SPACK_ROOT/bin/spack" location $SpackSubCommandArgs)
|
|
||||||
if (($NULL -ne $LOC)){
|
|
||||||
if ( Test-Path -Path $LOC){
|
|
||||||
Set-Location $LOC
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Invoke-SpackEnv {
|
|
||||||
if (Compare-CommonArgs $SpackSubCommandArgs[0]) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" env -h
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$SubCommandSubCommand = $SpackSubCommandArgs[0]
|
|
||||||
$SubCommandSubCommandArgs = $SpackSubCommandArgs[1..$SpackSubCommandArgs.Count]
|
|
||||||
switch ($SubCommandSubCommand) {
|
|
||||||
"activate" {
|
|
||||||
if (Compare-CommonArgs $SubCommandSubCommandArgs) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" env activate $SubCommandSubCommandArgs
|
|
||||||
}
|
|
||||||
elseif ([bool]($SubCommandSubCommandArgs.Where({$_ -eq "--pwsh"}))) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" env activate $SubCommandSubCommandArgs
|
|
||||||
}
|
|
||||||
elseif (!$SubCommandSubCommandArgs) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" env activate $SubCommandSubCommandArgs
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$SpackEnv = $(python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params env activate "--pwsh" $SubCommandSubCommandArgs)
|
|
||||||
Set-SpackEnv $SpackEnv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"deactivate" {
|
|
||||||
if ([bool]($SubCommandSubCommandArgs.Where({$_ -eq "--pwsh"}))) {
|
|
||||||
python"$Env:SPACK_ROOT/bin/spack" env deactivate $SubCommandSubCommandArgs
|
|
||||||
}
|
|
||||||
elseif($SubCommandSubCommandArgs) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" env deactivate -h
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$SpackEnv = $(python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params env deactivate "--pwsh")
|
|
||||||
Set-SpackEnv $SpackEnv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default {python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Invoke-SpackLoad {
|
|
||||||
if (Compare-CommonArgs $SpackSubCommandArgs) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs
|
|
||||||
}
|
|
||||||
elseif ([bool]($SpackSubCommandArgs.Where({($_ -eq "--pwsh") -or ($_ -eq "--list")}))) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$SpackEnv = $(python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params $SpackSubCommand "--pwsh" $SpackSubCommandArgs)
|
|
||||||
Set-SpackEnv $SpackEnv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$SpackCMD_params, $SpackSubCommand, $SpackSubCommandArgs = Read-SpackArgs $args
|
|
||||||
|
|
||||||
if (Compare-CommonArgs $SpackCMD_params) {
|
|
||||||
python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs
|
|
||||||
exit $LASTEXITCODE
|
|
||||||
}
|
|
||||||
|
|
||||||
# Process Spack commands with special conditions
|
|
||||||
# all other commands are piped directly to Spack
|
|
||||||
switch($SpackSubCommand)
|
|
||||||
{
|
|
||||||
"cd" {Invoke-SpackCD}
|
|
||||||
"env" {Invoke-SpackEnv}
|
|
||||||
"load" {Invoke-SpackLoad}
|
|
||||||
"unload" {Invoke-SpackLoad}
|
|
||||||
default {python "$Env:SPACK_ROOT/bin/spack" $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs}
|
|
||||||
}
|
|
||||||
@@ -9,15 +9,15 @@ bootstrap:
|
|||||||
# may not be able to bootstrap all the software that Spack needs,
|
# may not be able to bootstrap all the software that Spack needs,
|
||||||
# depending on its type.
|
# depending on its type.
|
||||||
sources:
|
sources:
|
||||||
- name: 'github-actions-v0.5'
|
|
||||||
metadata: $spack/share/spack/bootstrap/github-actions-v0.5
|
|
||||||
- name: 'github-actions-v0.4'
|
- name: 'github-actions-v0.4'
|
||||||
metadata: $spack/share/spack/bootstrap/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: 'spack-install'
|
- name: 'spack-install'
|
||||||
metadata: $spack/share/spack/bootstrap/spack-install
|
metadata: $spack/share/spack/bootstrap/spack-install
|
||||||
trusted:
|
trusted:
|
||||||
# By default we trust bootstrapping from sources and from binaries
|
# By default we trust bootstrapping from sources and from binaries
|
||||||
# produced on Github via the workflow
|
# produced on Github via the workflow
|
||||||
github-actions-v0.5: true
|
|
||||||
github-actions-v0.4: true
|
github-actions-v0.4: true
|
||||||
|
github-actions-v0.3: true
|
||||||
spack-install: true
|
spack-install: true
|
||||||
|
|||||||
@@ -36,9 +36,3 @@ concretizer:
|
|||||||
# on each root spec, allowing different versions and variants of the same package in
|
# on each root spec, allowing different versions and variants of the same package in
|
||||||
# an environment.
|
# an environment.
|
||||||
unify: true
|
unify: true
|
||||||
# Option to deal with possible duplicate nodes (i.e. different nodes from the same package) in the DAG.
|
|
||||||
duplicates:
|
|
||||||
# "none": allows a single node for any package in the DAG.
|
|
||||||
# "minimal": allows the duplication of 'build-tools' nodes only (e.g. py-setuptools, cmake etc.)
|
|
||||||
# "full" (experimental): allows separation of the entire build-tool stack (e.g. the entire "cmake" subDAG)
|
|
||||||
strategy: minimal
|
|
||||||
|
|||||||
@@ -216,11 +216,10 @@ config:
|
|||||||
# manipulation by unprivileged user (e.g. AFS)
|
# manipulation by unprivileged user (e.g. AFS)
|
||||||
allow_sgid: true
|
allow_sgid: true
|
||||||
|
|
||||||
# Whether to show status information during building and installing packages.
|
# Whether to set the terminal title to display status information during
|
||||||
# This gives information about Spack's current progress as well as the current
|
# building and installing packages. This gives information about Spack's
|
||||||
# and total number of packages. Information is shown both in the terminal
|
# current progress as well as the current and total number of packages.
|
||||||
# title and inline.
|
terminal_title: false
|
||||||
install_status: true
|
|
||||||
|
|
||||||
# Number of seconds a buildcache's index.json is cached locally before probing
|
# Number of seconds a buildcache's index.json is cached locally before probing
|
||||||
# for updates, within a single Spack invocation. Defaults to 10 minutes.
|
# for updates, within a single Spack invocation. Defaults to 10 minutes.
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
mirrors:
|
mirrors:
|
||||||
spack-public:
|
spack-public: https://mirror.spack.io
|
||||||
binary: false
|
|
||||||
url: https://mirror.spack.io
|
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ packages:
|
|||||||
pbs: [openpbs, torque]
|
pbs: [openpbs, torque]
|
||||||
pil: [py-pillow]
|
pil: [py-pillow]
|
||||||
pkgconfig: [pkgconf, pkg-config]
|
pkgconfig: [pkgconf, pkg-config]
|
||||||
qmake: [qt-base, qt]
|
|
||||||
rpc: [libtirpc]
|
rpc: [libtirpc]
|
||||||
scalapack: [netlib-scalapack, amdscalapack]
|
scalapack: [netlib-scalapack, amdscalapack]
|
||||||
sycl: [hipsycl]
|
sycl: [hipsycl]
|
||||||
@@ -60,7 +59,6 @@ packages:
|
|||||||
xxd: [xxd-standalone, vim]
|
xxd: [xxd-standalone, vim]
|
||||||
yacc: [bison, byacc]
|
yacc: [bison, byacc]
|
||||||
ziglang: [zig]
|
ziglang: [zig]
|
||||||
zlib-api: [zlib-ng+compat, zlib]
|
|
||||||
permissions:
|
permissions:
|
||||||
read: world
|
read: world
|
||||||
write: user
|
write: user
|
||||||
|
|||||||
1
lib/spack/docs/.gitignore
vendored
1
lib/spack/docs/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
package_list.html
|
||||||
command_index.rst
|
command_index.rst
|
||||||
spack*.rst
|
spack*.rst
|
||||||
llnl*.rst
|
llnl*.rst
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
|
||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
# We use our own extension of the default style with a few modifications
|
|
||||||
from pygments.styles.default import DefaultStyle
|
|
||||||
from pygments.token import Generic
|
|
||||||
|
|
||||||
|
|
||||||
class SpackStyle(DefaultStyle):
|
|
||||||
styles = DefaultStyle.styles.copy()
|
|
||||||
background_color = "#f4f4f8"
|
|
||||||
styles[Generic.Output] = "#355"
|
|
||||||
styles[Generic.Prompt] = "bold #346ec9"
|
|
||||||
@@ -45,8 +45,7 @@ Listing available packages
|
|||||||
|
|
||||||
To install software with Spack, you need to know what software is
|
To install software with Spack, you need to know what software is
|
||||||
available. You can see a list of available package names at the
|
available. You can see a list of available package names at the
|
||||||
`packages.spack.io <https://packages.spack.io>`_ website, or
|
:ref:`package-list` webpage, or using the ``spack list`` command.
|
||||||
using the ``spack list`` command.
|
|
||||||
|
|
||||||
.. _cmd-spack-list:
|
.. _cmd-spack-list:
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ can install:
|
|||||||
:ellipsis: 10
|
:ellipsis: 10
|
||||||
|
|
||||||
There are thousands of them, so we've truncated the output above, but you
|
There are thousands of them, so we've truncated the output above, but you
|
||||||
can find a `full list here <https://packages.spack.io>`_.
|
can find a :ref:`full list here <package-list>`.
|
||||||
Packages are listed by name in alphabetical order.
|
Packages are listed by name in alphabetical order.
|
||||||
A pattern to match with no wildcards, ``*`` or ``?``,
|
A pattern to match with no wildcards, ``*`` or ``?``,
|
||||||
will be treated as though it started and ended with
|
will be treated as though it started and ended with
|
||||||
|
|||||||
@@ -48,10 +48,14 @@ Here is an example where a build cache is created in a local directory named
|
|||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ spack buildcache push ./spack-cache ninja
|
$ spack buildcache push --allow-root ./spack-cache ninja
|
||||||
==> Pushing binary packages to file:///home/spackuser/spack/spack-cache/build_cache
|
==> Pushing binary packages to file:///home/spackuser/spack/spack-cache/build_cache
|
||||||
|
|
||||||
Note that ``ninja`` must be installed locally for this to work.
|
Not that ``ninja`` must be installed locally for this to work.
|
||||||
|
|
||||||
|
We're using the ``--allow-root`` flag to tell Spack that is OK when any of
|
||||||
|
the binaries we're pushing contain references to the local Spack install
|
||||||
|
directory.
|
||||||
|
|
||||||
Once you have a build cache, you can add it as a mirror, discussed next.
|
Once you have a build cache, you can add it as a mirror, discussed next.
|
||||||
|
|
||||||
@@ -143,7 +147,7 @@ and then install from it exclusively, you would do:
|
|||||||
|
|
||||||
$ spack mirror add E4S https://cache.e4s.io
|
$ spack mirror add E4S https://cache.e4s.io
|
||||||
$ spack buildcache keys --install --trust
|
$ spack buildcache keys --install --trust
|
||||||
$ spack install --use-buildcache only <package>
|
$ spack install --use-buildache only <package>
|
||||||
|
|
||||||
We use ``--install`` and ``--trust`` to say that we are installing keys to our
|
We use ``--install`` and ``--trust`` to say that we are installing keys to our
|
||||||
keyring, and trusting all downloaded keys.
|
keyring, and trusting all downloaded keys.
|
||||||
@@ -156,131 +160,6 @@ List of popular build caches
|
|||||||
* `Extreme-scale Scientific Software Stack (E4S) <https://e4s-project.github.io/>`_: `build cache <https://oaciss.uoregon.edu/e4s/inventory.html>`_
|
* `Extreme-scale Scientific Software Stack (E4S) <https://e4s-project.github.io/>`_: `build cache <https://oaciss.uoregon.edu/e4s/inventory.html>`_
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
OCI / Docker V2 registries as build cache
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
Spack can also use OCI or Docker V2 registries such as Dockerhub, Quay.io,
|
|
||||||
Github Packages, GitLab Container Registry, JFrog Artifactory, and others
|
|
||||||
as build caches. This is a convenient way to share binaries using public
|
|
||||||
infrastructure, or to cache Spack built binaries in Github Actions and
|
|
||||||
GitLab CI.
|
|
||||||
|
|
||||||
To get started, configure an OCI mirror using ``oci://`` as the scheme,
|
|
||||||
and optionally specify a username and password (or personal access token):
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ spack mirror add --oci-username username --oci-password password my_registry oci://example.com/my_image
|
|
||||||
|
|
||||||
Spack follows the naming conventions of Docker, with Dockerhub as the default
|
|
||||||
registry. To use Dockerhub, you can omit the registry domain:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ spack mirror add --oci-username username --oci-password password my_registry oci://username/my_image
|
|
||||||
|
|
||||||
From here, you can use the mirror as any other build cache:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ spack buildcache push my_registry <specs...> # push to the registry
|
|
||||||
$ spack install <specs...> # install from the registry
|
|
||||||
|
|
||||||
A unique feature of buildcaches on top of OCI registries is that it's incredibly
|
|
||||||
easy to generate get a runnable container image with the binaries installed. This
|
|
||||||
is a great way to make applications available to users without requiring them to
|
|
||||||
install Spack -- all you need is Docker, Podman or any other OCI-compatible container
|
|
||||||
runtime.
|
|
||||||
|
|
||||||
To produce container images, all you need to do is add the ``--base-image`` flag
|
|
||||||
when pushing to the build cache:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ spack buildcache push --base-image ubuntu:20.04 my_registry ninja
|
|
||||||
Pushed to example.com/my_image:ninja-1.11.1-yxferyhmrjkosgta5ei6b4lqf6bxbscz.spack
|
|
||||||
|
|
||||||
$ docker run -it example.com/my_image:ninja-1.11.1-yxferyhmrjkosgta5ei6b4lqf6bxbscz.spack
|
|
||||||
root@e4c2b6f6b3f4:/# ninja --version
|
|
||||||
1.11.1
|
|
||||||
|
|
||||||
If ``--base-image`` is not specified, distroless images are produced. In practice,
|
|
||||||
you won't be able to run these as containers, since they don't come with libc and
|
|
||||||
other system dependencies. However, they are still compatible with tools like
|
|
||||||
``skopeo``, ``podman``, and ``docker`` for pulling and pushing.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
The docker ``overlayfs2`` storage driver is limited to 128 layers, above which a
|
|
||||||
``max depth exceeded`` error may be produced when pulling the image. There
|
|
||||||
are `alternative drivers <https://docs.docker.com/storage/storagedriver/>`_.
|
|
||||||
|
|
||||||
------------------------------------
|
|
||||||
Using a buildcache in GitHub Actions
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
GitHub Actions is a popular CI/CD platform for building and testing software,
|
|
||||||
but each CI job has limited resources, making from source builds too slow for
|
|
||||||
many applications. Spack build caches can be used to share binaries between CI
|
|
||||||
runs, speeding up CI significantly.
|
|
||||||
|
|
||||||
A typical workflow is to include a ``spack.yaml`` environment in your repository
|
|
||||||
that specifies the packages to install:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
spack:
|
|
||||||
specs: [pkg-x, pkg-y]
|
|
||||||
packages:
|
|
||||||
all:
|
|
||||||
require: target=x86_64_v2
|
|
||||||
mirrors:
|
|
||||||
github_packages: oci://ghcr.io/<user>/<repo>
|
|
||||||
|
|
||||||
And a GitHub action that sets up Spack, installs packages from the build cache
|
|
||||||
or from sources, and pushes newly built binaries to the build cache:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
name: Install Spack packages
|
|
||||||
|
|
||||||
on: push
|
|
||||||
|
|
||||||
env:
|
|
||||||
SPACK_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
example:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install Spack
|
|
||||||
run: |
|
|
||||||
git clone --depth=1 https://github.com/spack/spack.git
|
|
||||||
echo "$PWD/spack/bin/" >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Concretize
|
|
||||||
run: spack -e . concretize
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
run: spack -e . install --no-check-signature --fail-fast
|
|
||||||
|
|
||||||
- name: Push to buildcache
|
|
||||||
run: |
|
|
||||||
spack -e . mirror set --oci-username <user> --oci-password "${{ secrets.GITHUB_TOKEN }}" github_packages
|
|
||||||
spack -e . buildcache push --base-image ubuntu:22.04 --unsigned --update-index github_packages
|
|
||||||
if: always()
|
|
||||||
|
|
||||||
The first time this action runs, it will build the packages from source and
|
|
||||||
push them to the build cache. Subsequent runs will pull the binaries from the
|
|
||||||
build cache. The concretizer will ensure that prebuilt binaries are favored
|
|
||||||
over source builds.
|
|
||||||
|
|
||||||
The build cache entries appear in the GitHub Packages section of your repository,
|
|
||||||
and contain instructions for pulling and running them with ``docker`` or ``podman``.
|
|
||||||
|
|
||||||
----------
|
----------
|
||||||
Relocation
|
Relocation
|
||||||
----------
|
----------
|
||||||
|
|||||||
@@ -32,14 +32,9 @@ can't be found. You can readily check if any prerequisite for using Spack is mis
|
|||||||
|
|
||||||
Spack will take care of bootstrapping any missing dependency marked as [B]. Dependencies marked as [-] are instead required to be found on the system.
|
Spack will take care of bootstrapping any missing dependency marked as [B]. Dependencies marked as [-] are instead required to be found on the system.
|
||||||
|
|
||||||
% echo $?
|
|
||||||
1
|
|
||||||
|
|
||||||
In the case of the output shown above Spack detected that both ``clingo`` and ``gnupg``
|
In the case of the output shown above Spack detected that both ``clingo`` and ``gnupg``
|
||||||
are missing and it's giving detailed information on why they are needed and whether
|
are missing and it's giving detailed information on why they are needed and whether
|
||||||
they can be bootstrapped. The return code of this command summarizes the results, if any
|
they can be bootstrapped. Running a command that concretize a spec, like:
|
||||||
dependencies are missing the return code is ``1``, otherwise ``0``. Running a command that
|
|
||||||
concretizes a spec, like:
|
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
@@ -49,7 +44,7 @@ concretizes a spec, like:
|
|||||||
==> Installing "clingo-bootstrap@spack%apple-clang@12.0.0~docs~ipo+python build_type=Release arch=darwin-catalina-x86_64" from a buildcache
|
==> Installing "clingo-bootstrap@spack%apple-clang@12.0.0~docs~ipo+python build_type=Release arch=darwin-catalina-x86_64" from a buildcache
|
||||||
[ ... ]
|
[ ... ]
|
||||||
|
|
||||||
automatically triggers the bootstrapping of clingo from pre-built binaries as expected.
|
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
|
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:
|
might be useful to setup containers or other similar environments:
|
||||||
|
|||||||
@@ -3,103 +3,6 @@
|
|||||||
|
|
||||||
SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
|
||||||
.. _concretizer-options:
|
|
||||||
|
|
||||||
==========================================
|
|
||||||
Concretization Settings (concretizer.yaml)
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
The ``concretizer.yaml`` configuration file allows to customize aspects of the
|
|
||||||
algorithm used to select the dependencies you install. The default configuration
|
|
||||||
is the following:
|
|
||||||
|
|
||||||
.. literalinclude:: _spack_root/etc/spack/defaults/concretizer.yaml
|
|
||||||
:language: yaml
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
Reuse already installed packages
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
The ``reuse`` attribute controls whether Spack will prefer to use installed packages (``true``), or
|
|
||||||
whether it will do a "fresh" installation and prefer the latest settings from
|
|
||||||
``package.py`` files and ``packages.yaml`` (``false``).
|
|
||||||
You can use:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
% spack install --reuse <spec>
|
|
||||||
|
|
||||||
to enable reuse for a single installation, and you can use:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
spack install --fresh <spec>
|
|
||||||
|
|
||||||
to do a fresh install if ``reuse`` is enabled by default.
|
|
||||||
``reuse: true`` is the default.
|
|
||||||
|
|
||||||
------------------------------------------
|
|
||||||
Selection of the target microarchitectures
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
The options under the ``targets`` attribute control which targets are considered during a solve.
|
|
||||||
Currently the options in this section are only configurable from the ``concretizer.yaml`` file
|
|
||||||
and there are no corresponding command line arguments to enable them for a single solve.
|
|
||||||
|
|
||||||
The ``granularity`` option can take two possible values: ``microarchitectures`` and ``generic``.
|
|
||||||
If set to:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
concretizer:
|
|
||||||
targets:
|
|
||||||
granularity: microarchitectures
|
|
||||||
|
|
||||||
Spack will consider all the microarchitectures known to ``archspec`` to label nodes for
|
|
||||||
compatibility. If instead the option is set to:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
concretizer:
|
|
||||||
targets:
|
|
||||||
granularity: generic
|
|
||||||
|
|
||||||
Spack will consider only generic microarchitectures. For instance, when running on an
|
|
||||||
Haswell node, Spack will consider ``haswell`` as the best target in the former case and
|
|
||||||
``x86_64_v3`` as the best target in the latter case.
|
|
||||||
|
|
||||||
The ``host_compatible`` option is a Boolean option that determines whether or not the
|
|
||||||
microarchitectures considered during the solve are constrained to be compatible with the
|
|
||||||
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.
|
|
||||||
|
|
||||||
---------------
|
|
||||||
Duplicate nodes
|
|
||||||
---------------
|
|
||||||
|
|
||||||
The ``duplicates`` attribute controls whether the DAG can contain multiple configurations of
|
|
||||||
the same package. This is mainly relevant for build dependencies, which may have their version
|
|
||||||
pinned by some nodes, and thus be required at different versions by different nodes in the same
|
|
||||||
DAG.
|
|
||||||
|
|
||||||
The ``strategy`` option controls how the solver deals with duplicates. If the value is ``none``,
|
|
||||||
then a single configuration per package is allowed in the DAG. This means, for instance, that only
|
|
||||||
a single ``cmake`` or a single ``py-setuptools`` version is allowed. The result would be a slightly
|
|
||||||
faster concretization, at the expense of making a few specs unsolvable.
|
|
||||||
|
|
||||||
If the value is ``minimal`` Spack will allow packages tagged as ``build-tools`` to have duplicates.
|
|
||||||
This allows, for instance, to concretize specs whose nodes require different, and incompatible, ranges
|
|
||||||
of some build tool. For instance, in the figure below the latest `py-shapely` requires a newer `py-setuptools`,
|
|
||||||
while `py-numpy` still needs an older version:
|
|
||||||
|
|
||||||
.. figure:: images/shapely_duplicates.svg
|
|
||||||
:scale: 70 %
|
|
||||||
:align: center
|
|
||||||
|
|
||||||
Up to Spack v0.20 ``duplicates:strategy:none`` was the default (and only) behavior. From Spack v0.21 the
|
|
||||||
default behavior is ``duplicates:strategy:minimal``.
|
|
||||||
|
|
||||||
.. _build-settings:
|
.. _build-settings:
|
||||||
|
|
||||||
================================
|
================================
|
||||||
@@ -329,6 +232,76 @@ Specific limitations include:
|
|||||||
then Spack will not add a new external entry (``spack config blame packages``
|
then Spack will not add a new external entry (``spack config blame packages``
|
||||||
can help locate all external entries).
|
can help locate all external entries).
|
||||||
|
|
||||||
|
.. _concretizer-options:
|
||||||
|
|
||||||
|
----------------------
|
||||||
|
Concretizer options
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
``packages.yaml`` gives the concretizer preferences for specific packages,
|
||||||
|
but you can also use ``concretizer.yaml`` to customize aspects of the
|
||||||
|
algorithm it uses to select the dependencies you install:
|
||||||
|
|
||||||
|
.. literalinclude:: _spack_root/etc/spack/defaults/concretizer.yaml
|
||||||
|
:language: yaml
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Reuse already installed packages
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``reuse`` attribute controls whether Spack will prefer to use installed packages (``true``), or
|
||||||
|
whether it will do a "fresh" installation and prefer the latest settings from
|
||||||
|
``package.py`` files and ``packages.yaml`` (``false``).
|
||||||
|
You can use:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
% spack install --reuse <spec>
|
||||||
|
|
||||||
|
to enable reuse for a single installation, and you can use:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
spack install --fresh <spec>
|
||||||
|
|
||||||
|
to do a fresh install if ``reuse`` is enabled by default.
|
||||||
|
``reuse: true`` is the default.
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Selection of the target microarchitectures
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The options under the ``targets`` attribute control which targets are considered during a solve.
|
||||||
|
Currently the options in this section are only configurable from the ``concretizer.yaml`` file
|
||||||
|
and there are no corresponding command line arguments to enable them for a single solve.
|
||||||
|
|
||||||
|
The ``granularity`` option can take two possible values: ``microarchitectures`` and ``generic``.
|
||||||
|
If set to:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
concretizer:
|
||||||
|
targets:
|
||||||
|
granularity: microarchitectures
|
||||||
|
|
||||||
|
Spack will consider all the microarchitectures known to ``archspec`` to label nodes for
|
||||||
|
compatibility. If instead the option is set to:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
concretizer:
|
||||||
|
targets:
|
||||||
|
granularity: generic
|
||||||
|
|
||||||
|
Spack will consider only generic microarchitectures. For instance, when running on an
|
||||||
|
Haswell node, Spack will consider ``haswell`` as the best target in the former case and
|
||||||
|
``x86_64_v3`` as the best target in the latter case.
|
||||||
|
|
||||||
|
The ``host_compatible`` option is a Boolean option that determines whether or not the
|
||||||
|
microarchitectures considered during the solve are constrained to be compatible with the
|
||||||
|
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-requirements:
|
.. _package-requirements:
|
||||||
|
|
||||||
--------------------
|
--------------------
|
||||||
|
|||||||
@@ -127,9 +127,9 @@ check out a commit from the ``master`` branch, you would want to add:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
depends_on("autoconf", type="build", when="@master")
|
depends_on('autoconf', type='build', when='@master')
|
||||||
depends_on("automake", type="build", when="@master")
|
depends_on('automake', type='build', when='@master')
|
||||||
depends_on("libtool", type="build", when="@master")
|
depends_on('libtool', type='build', when='@master')
|
||||||
|
|
||||||
It is typically redundant to list the ``m4`` macro processor package as a
|
It is typically redundant to list the ``m4`` macro processor package as a
|
||||||
dependency, since ``autoconf`` already depends on it.
|
dependency, since ``autoconf`` already depends on it.
|
||||||
@@ -145,7 +145,7 @@ example, the ``bash`` shell is used to run the ``autogen.sh`` script.
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def autoreconf(self, spec, prefix):
|
def autoreconf(self, spec, prefix):
|
||||||
which("bash")("autogen.sh")
|
which('bash')('autogen.sh')
|
||||||
|
|
||||||
"""""""""""""""""""""""""""""""""""""""
|
"""""""""""""""""""""""""""""""""""""""
|
||||||
patching configure or Makefile.in files
|
patching configure or Makefile.in files
|
||||||
@@ -186,9 +186,9 @@ To opt out of this feature, use the following setting:
|
|||||||
To enable it conditionally on different architectures, define a property and
|
To enable it conditionally on different architectures, define a property and
|
||||||
make the package depend on ``gnuconfig`` as a build dependency:
|
make the package depend on ``gnuconfig`` as a build dependency:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block
|
||||||
|
|
||||||
depends_on("gnuconfig", when="@1.0:")
|
depends_on('gnuconfig', when='@1.0:')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patch_config_files(self):
|
def patch_config_files(self):
|
||||||
@@ -230,7 +230,7 @@ version, this can be done like so:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def force_autoreconf(self):
|
def force_autoreconf(self):
|
||||||
return self.version == Version("1.2.3")
|
return self.version == Version('1.2.3')
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Finding configure flags
|
Finding configure flags
|
||||||
@@ -278,22 +278,13 @@ function like so:
|
|||||||
def configure_args(self):
|
def configure_args(self):
|
||||||
args = []
|
args = []
|
||||||
|
|
||||||
if self.spec.satisfies("+mpi"):
|
if '+mpi' in self.spec:
|
||||||
args.append("--enable-mpi")
|
args.append('--enable-mpi')
|
||||||
else:
|
else:
|
||||||
args.append("--disable-mpi")
|
args.append('--disable-mpi')
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
Alternatively, you can use the :ref:`enable_or_disable <autotools_enable_or_disable>` helper:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def configure_args(self):
|
|
||||||
return [self.enable_or_disable("mpi")]
|
|
||||||
|
|
||||||
|
|
||||||
Note that we are explicitly disabling MPI support if it is not
|
Note that we are explicitly disabling MPI support if it is not
|
||||||
requested. This is important, as many Autotools packages will enable
|
requested. This is important, as many Autotools packages will enable
|
||||||
options by default if the dependencies are found, and disable them
|
options by default if the dependencies are found, and disable them
|
||||||
@@ -304,11 +295,9 @@ and `here <https://wiki.gentoo.org/wiki/Project:Quality_Assurance/Automagic_depe
|
|||||||
for a rationale as to why these so-called "automagic" dependencies
|
for a rationale as to why these so-called "automagic" dependencies
|
||||||
are a problem.
|
are a problem.
|
||||||
|
|
||||||
.. note::
|
By default, Autotools installs packages to ``/usr``. We don't want this,
|
||||||
|
so Spack automatically adds ``--prefix=/path/to/installation/prefix``
|
||||||
By default, Autotools installs packages to ``/usr``. We don't want this,
|
to your list of ``configure_args``. You don't need to add this yourself.
|
||||||
so Spack automatically adds ``--prefix=/path/to/installation/prefix``
|
|
||||||
to your list of ``configure_args``. You don't need to add this yourself.
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
Helper functions
|
Helper functions
|
||||||
@@ -319,8 +308,6 @@ You may have noticed that most of the Autotools flags are of the form
|
|||||||
``--without-baz``. Since these flags are so common, Spack provides a
|
``--without-baz``. Since these flags are so common, Spack provides a
|
||||||
couple of helper functions to make your life easier.
|
couple of helper functions to make your life easier.
|
||||||
|
|
||||||
.. _autotools_enable_or_disable:
|
|
||||||
|
|
||||||
"""""""""""""""""
|
"""""""""""""""""
|
||||||
enable_or_disable
|
enable_or_disable
|
||||||
"""""""""""""""""
|
"""""""""""""""""
|
||||||
@@ -332,11 +319,11 @@ typically used to enable or disable some feature within the package.
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
variant(
|
variant(
|
||||||
"memchecker",
|
'memchecker',
|
||||||
default=False,
|
default=False,
|
||||||
description="Memchecker support for debugging [degrades performance]"
|
description='Memchecker support for debugging [degrades performance]'
|
||||||
)
|
)
|
||||||
config_args.extend(self.enable_or_disable("memchecker"))
|
config_args.extend(self.enable_or_disable('memchecker'))
|
||||||
|
|
||||||
In this example, specifying the variant ``+memchecker`` will generate
|
In this example, specifying the variant ``+memchecker`` will generate
|
||||||
the following configuration options:
|
the following configuration options:
|
||||||
@@ -356,15 +343,15 @@ the ``with_or_without`` method.
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
variant(
|
variant(
|
||||||
"schedulers",
|
'schedulers',
|
||||||
values=disjoint_sets(
|
values=disjoint_sets(
|
||||||
("auto",), ("alps", "lsf", "tm", "slurm", "sge", "loadleveler")
|
('auto',), ('alps', 'lsf', 'tm', 'slurm', 'sge', 'loadleveler')
|
||||||
).with_non_feature_values("auto", "none"),
|
).with_non_feature_values('auto', 'none'),
|
||||||
description="List of schedulers for which support is enabled; "
|
description="List of schedulers for which support is enabled; "
|
||||||
"'auto' lets openmpi determine",
|
"'auto' lets openmpi determine",
|
||||||
)
|
)
|
||||||
if not spec.satisfies("schedulers=auto"):
|
if 'schedulers=auto' not in spec:
|
||||||
config_args.extend(self.with_or_without("schedulers"))
|
config_args.extend(self.with_or_without('schedulers'))
|
||||||
|
|
||||||
In this example, specifying the variant ``schedulers=slurm,sge`` will
|
In this example, specifying the variant ``schedulers=slurm,sge`` will
|
||||||
generate the following configuration options:
|
generate the following configuration options:
|
||||||
@@ -389,16 +376,16 @@ generated, using the ``activation_value`` argument to
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
variant(
|
variant(
|
||||||
"fabrics",
|
'fabrics',
|
||||||
values=disjoint_sets(
|
values=disjoint_sets(
|
||||||
("auto",), ("psm", "psm2", "verbs", "mxm", "ucx", "libfabric")
|
('auto',), ('psm', 'psm2', 'verbs', 'mxm', 'ucx', 'libfabric')
|
||||||
).with_non_feature_values("auto", "none"),
|
).with_non_feature_values('auto', 'none'),
|
||||||
description="List of fabrics that are enabled; "
|
description="List of fabrics that are enabled; "
|
||||||
"'auto' lets openmpi determine",
|
"'auto' lets openmpi determine",
|
||||||
)
|
)
|
||||||
if not spec.satisfies("fabrics=auto"):
|
if 'fabrics=auto' not in spec:
|
||||||
config_args.extend(self.with_or_without("fabrics",
|
config_args.extend(self.with_or_without('fabrics',
|
||||||
activation_value="prefix"))
|
activation_value='prefix'))
|
||||||
|
|
||||||
``activation_value`` accepts a callable that generates the configure
|
``activation_value`` accepts a callable that generates the configure
|
||||||
parameter value given the variant value; but the special value
|
parameter value given the variant value; but the special value
|
||||||
@@ -422,16 +409,16 @@ When Spack variants and configure flags do not correspond one-to-one, the
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
variant("debug_tools", default=False)
|
variant('debug_tools', default=False)
|
||||||
config_args += self.enable_or_disable("debug-tools", variant="debug_tools")
|
config_args += self.enable_or_disable('debug-tools', variant='debug_tools')
|
||||||
|
|
||||||
Or when one variant controls multiple flags:
|
Or when one variant controls multiple flags:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
variant("debug_tools", default=False)
|
variant('debug_tools', default=False)
|
||||||
config_args += self.with_or_without("memchecker", variant="debug_tools")
|
config_args += self.with_or_without('memchecker', variant='debug_tools')
|
||||||
config_args += self.with_or_without("profiler", variant="debug_tools")
|
config_args += self.with_or_without('profiler', variant='debug_tools')
|
||||||
|
|
||||||
|
|
||||||
""""""""""""""""""""
|
""""""""""""""""""""
|
||||||
@@ -445,8 +432,8 @@ For example:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
variant("profiler", when="@2.0:")
|
variant('profiler', when='@2.0:')
|
||||||
config_args += self.with_or_without("profiler")
|
config_args += self.with_or_without('profiler')
|
||||||
|
|
||||||
will neither add ``--with-profiler`` nor ``--without-profiler`` when the version is
|
will neither add ``--with-profiler`` nor ``--without-profiler`` when the version is
|
||||||
below ``2.0``.
|
below ``2.0``.
|
||||||
@@ -465,10 +452,10 @@ the variant values require atypical behavior.
|
|||||||
def with_or_without_verbs(self, activated):
|
def with_or_without_verbs(self, activated):
|
||||||
# Up through version 1.6, this option was named --with-openib.
|
# Up through version 1.6, this option was named --with-openib.
|
||||||
# In version 1.7, it was renamed to be --with-verbs.
|
# In version 1.7, it was renamed to be --with-verbs.
|
||||||
opt = "verbs" if self.spec.satisfies("@1.7:") else "openib"
|
opt = 'verbs' if self.spec.satisfies('@1.7:') else 'openib'
|
||||||
if not activated:
|
if not activated:
|
||||||
return f"--without-{opt}"
|
return '--without-{0}'.format(opt)
|
||||||
return f"--with-{opt}={self.spec['rdma-core'].prefix}"
|
return '--with-{0}={1}'.format(opt, self.spec['rdma-core'].prefix)
|
||||||
|
|
||||||
Defining ``with_or_without_verbs`` overrides the behavior of a
|
Defining ``with_or_without_verbs`` overrides the behavior of a
|
||||||
``fabrics=verbs`` variant, changing the configure-time option to
|
``fabrics=verbs`` variant, changing the configure-time option to
|
||||||
@@ -492,7 +479,7 @@ do this like so:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
configure_directory = "src"
|
configure_directory = 'src'
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Building out of source
|
Building out of source
|
||||||
@@ -504,7 +491,7 @@ This can be done using the ``build_directory`` variable:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
build_directory = "spack-build"
|
build_directory = 'spack-build'
|
||||||
|
|
||||||
By default, Spack will build the package in the same directory that
|
By default, Spack will build the package in the same directory that
|
||||||
contains the ``configure`` script
|
contains the ``configure`` script
|
||||||
@@ -527,8 +514,8 @@ library or build the documentation, you can add these like so:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
build_targets = ["all", "docs"]
|
build_targets = ['all', 'docs']
|
||||||
install_targets = ["install", "docs"]
|
install_targets = ['install', 'docs']
|
||||||
|
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
Testing
|
Testing
|
||||||
|
|||||||
@@ -9,32 +9,9 @@
|
|||||||
Bundle
|
Bundle
|
||||||
------
|
------
|
||||||
|
|
||||||
``BundlePackage`` represents a set of packages that are expected to work
|
``BundlePackage`` represents a set of packages that are expected to work well
|
||||||
well together, such as a collection of commonly used software libraries.
|
together, such as a collection of commonly used software libraries. The
|
||||||
The associated software is specified as dependencies.
|
associated software is specified as bundle dependencies.
|
||||||
|
|
||||||
If it makes sense, variants, conflicts, and requirements can be added to
|
|
||||||
the package. :ref:`Variants <variants>` ensure that common build options
|
|
||||||
are consistent across the packages supporting them. :ref:`Conflicts
|
|
||||||
and requirements <packaging_conflicts>` prevent attempts to build with known
|
|
||||||
bugs or limitations.
|
|
||||||
|
|
||||||
For example, if ``MyBundlePackage`` is known to only build on ``linux``,
|
|
||||||
it could use the ``require`` directive as follows:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
require("platform=linux", msg="MyBundlePackage only builds on linux")
|
|
||||||
|
|
||||||
Spack has a number of built-in bundle packages, such as:
|
|
||||||
|
|
||||||
* `AmdAocl <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/amd-aocl/package.py>`_
|
|
||||||
* `EcpProxyApps <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/ecp-proxy-apps/package.py>`_
|
|
||||||
* `Libc <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/libc/package.py>`_
|
|
||||||
* `Xsdk <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/xsdk/package.py>`_
|
|
||||||
|
|
||||||
where ``Xsdk`` also inherits from ``CudaPackage`` and ``RocmPackage`` and
|
|
||||||
``Libc`` is a virtual bundle package for the C standard library.
|
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ A typical usage of these methods may look something like this:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def initconfig_mpi_entries(self):
|
def initconfig_mpi_entries(self)
|
||||||
# Get existing MPI configurations
|
# Get existing MPI configurations
|
||||||
entries = super(self, Foo).initconfig_mpi_entries()
|
entries = super(self, Foo).initconfig_mpi_entries()
|
||||||
|
|
||||||
@@ -95,25 +95,25 @@ A typical usage of these methods may look something like this:
|
|||||||
# This spec has an MPI variant, and we need to enable MPI when it is on.
|
# This spec has an MPI variant, and we need to enable MPI when it is on.
|
||||||
# This hypothetical package controls MPI with the ``FOO_MPI`` option to
|
# This hypothetical package controls MPI with the ``FOO_MPI`` option to
|
||||||
# cmake.
|
# cmake.
|
||||||
if self.spec.satisfies("+mpi"):
|
if '+mpi' in self.spec:
|
||||||
entries.append(cmake_cache_option("FOO_MPI", True, "enable mpi"))
|
entries.append(cmake_cache_option('FOO_MPI', True, "enable mpi"))
|
||||||
else:
|
else:
|
||||||
entries.append(cmake_cache_option("FOO_MPI", False, "disable mpi"))
|
entries.append(cmake_cache_option('FOO_MPI', False, "disable mpi"))
|
||||||
|
|
||||||
def initconfig_package_entries(self):
|
def initconfig_package_entries(self):
|
||||||
# Package specific options
|
# Package specific options
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
entries.append("#Entries for build options")
|
entries.append('#Entries for build options')
|
||||||
|
|
||||||
bar_on = self.spec.satisfies("+bar")
|
bar_on = '+bar' in self.spec
|
||||||
entries.append(cmake_cache_option("FOO_BAR", bar_on, "toggle bar"))
|
entries.append(cmake_cache_option('FOO_BAR', bar_on, 'toggle bar'))
|
||||||
|
|
||||||
entries.append("#Entries for dependencies")
|
entries.append('#Entries for dependencies')
|
||||||
|
|
||||||
if self.spec["blas"].name == "baz": # baz is our blas provider
|
if self.spec['blas'].name == 'baz': # baz is our blas provider
|
||||||
entries.append(cmake_cache_string("FOO_BLAS", "baz", "Use baz"))
|
entries.append(cmake_cache_string('FOO_BLAS', 'baz', 'Use baz'))
|
||||||
entries.append(cmake_cache_path("BAZ_PREFIX", self.spec["baz"].prefix))
|
entries.append(cmake_cache_path('BAZ_PREFIX', self.spec['baz'].prefix))
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
External documentation
|
External documentation
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ to terminate such build attempts with a suitable message:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
conflicts("cuda_arch=none", when="+cuda",
|
conflicts('cuda_arch=none', when='+cuda',
|
||||||
msg="CUDA architecture is required")
|
msg='CUDA architecture is required')
|
||||||
|
|
||||||
Similarly, if your software does not support all versions of the property,
|
Similarly, if your software does not support all versions of the property,
|
||||||
you could add ``conflicts`` to your package for those versions. For example,
|
you could add ``conflicts`` to your package for those versions. For example,
|
||||||
@@ -66,13 +66,13 @@ custom message should a user attempt such a build:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
unsupported_cuda_archs = [
|
unsupported_cuda_archs = [
|
||||||
"10", "11", "12", "13",
|
'10', '11', '12', '13',
|
||||||
"20", "21",
|
'20', '21',
|
||||||
"30", "32", "35", "37"
|
'30', '32', '35', '37'
|
||||||
]
|
]
|
||||||
for value in unsupported_cuda_archs:
|
for value in unsupported_cuda_archs:
|
||||||
conflicts(f"cuda_arch={value}", when="+cuda",
|
conflicts('cuda_arch={0}'.format(value), when='+cuda',
|
||||||
msg=f"CUDA architecture {value} is not supported")
|
msg='CUDA architecture {0} is not supported'.format(value))
|
||||||
|
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
Methods
|
Methods
|
||||||
@@ -107,16 +107,16 @@ class of your package. For example, you can add it to your
|
|||||||
spec = self.spec
|
spec = self.spec
|
||||||
args = []
|
args = []
|
||||||
...
|
...
|
||||||
if spec.satisfies("+cuda"):
|
if '+cuda' in spec:
|
||||||
# Set up the cuda macros needed by the build
|
# Set up the cuda macros needed by the build
|
||||||
args.append("-DWITH_CUDA=ON")
|
args.append('-DWITH_CUDA=ON')
|
||||||
cuda_arch_list = spec.variants["cuda_arch"].value
|
cuda_arch_list = spec.variants['cuda_arch'].value
|
||||||
cuda_arch = cuda_arch_list[0]
|
cuda_arch = cuda_arch_list[0]
|
||||||
if cuda_arch != "none":
|
if cuda_arch != 'none':
|
||||||
args.append(f"-DCUDA_FLAGS=-arch=sm_{cuda_arch}")
|
args.append('-DCUDA_FLAGS=-arch=sm_{0}'.format(cuda_arch))
|
||||||
else:
|
else:
|
||||||
# Ensure build with cuda is disabled
|
# Ensure build with cuda is disabled
|
||||||
args.append("-DWITH_CUDA=OFF")
|
args.append('-DWITH_CUDA=OFF')
|
||||||
...
|
...
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ You will need to customize options as needed for your build.
|
|||||||
|
|
||||||
This example also illustrates how to check for the ``cuda`` variant using
|
This example also illustrates how to check for the ``cuda`` variant using
|
||||||
``self.spec`` and how to retrieve the ``cuda_arch`` variant's value, which
|
``self.spec`` and how to retrieve the ``cuda_arch`` variant's value, which
|
||||||
is a list, using ``self.spec.variants["cuda_arch"].value``.
|
is a list, using ``self.spec.variants['cuda_arch'].value``.
|
||||||
|
|
||||||
With over 70 packages using ``CudaPackage`` as of January 2021 there are
|
With over 70 packages using ``CudaPackage`` as of January 2021 there are
|
||||||
lots of examples to choose from to get more ideas for using this package.
|
lots of examples to choose from to get more ideas for using this package.
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ If you look at the ``perl`` package, you'll see:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
phases = ["configure", "build", "install"]
|
phases = ['configure', 'build', 'install']
|
||||||
|
|
||||||
Similarly, ``cmake`` defines:
|
Similarly, ``cmake`` defines:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
phases = ["bootstrap", "build", "install"]
|
phases = ['bootstrap', 'build', 'install']
|
||||||
|
|
||||||
If we look at the ``cmake`` example, this tells Spack's ``PackageBase``
|
If we look at the ``cmake`` example, this tells Spack's ``PackageBase``
|
||||||
class to run the ``bootstrap``, ``build``, and ``install`` functions
|
class to run the ``bootstrap``, ``build``, and ``install`` functions
|
||||||
@@ -78,7 +78,7 @@ If we look at ``perl``, we see that it defines a ``configure`` method:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def configure(self, spec, prefix):
|
def configure(self, spec, prefix):
|
||||||
configure = Executable("./Configure")
|
configure = Executable('./Configure')
|
||||||
configure(*self.configure_args())
|
configure(*self.configure_args())
|
||||||
|
|
||||||
There is also a corresponding ``configure_args`` function that handles
|
There is also a corresponding ``configure_args`` function that handles
|
||||||
@@ -92,7 +92,7 @@ phases are pretty simple:
|
|||||||
make()
|
make()
|
||||||
|
|
||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
make("install")
|
make('install')
|
||||||
|
|
||||||
The ``cmake`` package looks very similar, but with a ``bootstrap``
|
The ``cmake`` package looks very similar, but with a ``bootstrap``
|
||||||
function instead of ``configure``:
|
function instead of ``configure``:
|
||||||
@@ -100,14 +100,14 @@ function instead of ``configure``:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def bootstrap(self, spec, prefix):
|
def bootstrap(self, spec, prefix):
|
||||||
bootstrap = Executable("./bootstrap")
|
bootstrap = Executable('./bootstrap')
|
||||||
bootstrap(*self.bootstrap_args())
|
bootstrap(*self.bootstrap_args())
|
||||||
|
|
||||||
def build(self, spec, prefix):
|
def build(self, spec, prefix):
|
||||||
make()
|
make()
|
||||||
|
|
||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
make("install")
|
make('install')
|
||||||
|
|
||||||
Again, there is a ``boostrap_args`` function that determines the
|
Again, there is a ``boostrap_args`` function that determines the
|
||||||
correct bootstrap flags to use.
|
correct bootstrap flags to use.
|
||||||
@@ -128,16 +128,16 @@ before or after a particular phase. For example, in ``perl``, we see:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@run_after("install")
|
@run_after('install')
|
||||||
def install_cpanm(self):
|
def install_cpanm(self):
|
||||||
spec = self.spec
|
spec = self.spec
|
||||||
|
|
||||||
if spec.satisfies("+cpanm"):
|
if '+cpanm' in spec:
|
||||||
with working_dir(join_path("cpanm", "cpanm")):
|
with working_dir(join_path('cpanm', 'cpanm')):
|
||||||
perl = spec["perl"].command
|
perl = spec['perl'].command
|
||||||
perl("Makefile.PL")
|
perl('Makefile.PL')
|
||||||
make()
|
make()
|
||||||
make("install")
|
make('install')
|
||||||
|
|
||||||
This extra step automatically installs ``cpanm`` in addition to the
|
This extra step automatically installs ``cpanm`` in addition to the
|
||||||
base Perl installation.
|
base Perl installation.
|
||||||
@@ -174,10 +174,10 @@ In the ``perl`` package, we can see:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@run_after("build")
|
@run_after('build')
|
||||||
@on_package_attributes(run_tests=True)
|
@on_package_attributes(run_tests=True)
|
||||||
def test(self):
|
def test(self):
|
||||||
make("test")
|
make('test')
|
||||||
|
|
||||||
As you can guess, this runs ``make test`` *after* building the package,
|
As you can guess, this runs ``make test`` *after* building the package,
|
||||||
if and only if testing is requested. Again, this is not specific to
|
if and only if testing is requested. Again, this is not specific to
|
||||||
@@ -189,7 +189,7 @@ custom build systems, it can be added to existing build systems as well.
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@run_after("install")
|
@run_after('install')
|
||||||
@on_package_attributes(run_tests=True)
|
@on_package_attributes(run_tests=True)
|
||||||
|
|
||||||
works as expected. However, if you reverse the ordering:
|
works as expected. However, if you reverse the ordering:
|
||||||
@@ -197,7 +197,7 @@ custom build systems, it can be added to existing build systems as well.
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@on_package_attributes(run_tests=True)
|
@on_package_attributes(run_tests=True)
|
||||||
@run_after("install")
|
@run_after('install')
|
||||||
|
|
||||||
the tests will always be run regardless of whether or not
|
the tests will always be run regardless of whether or not
|
||||||
``--test=root`` is requested. See https://github.com/spack/spack/issues/3833
|
``--test=root`` is requested. See https://github.com/spack/spack/issues/3833
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ use Spack to build packages with the tools.
|
|||||||
The Spack Python class ``IntelOneapiPackage`` is a base class that is
|
The Spack Python class ``IntelOneapiPackage`` is a base class that is
|
||||||
used by ``IntelOneapiCompilers``, ``IntelOneapiMkl``,
|
used by ``IntelOneapiCompilers``, ``IntelOneapiMkl``,
|
||||||
``IntelOneapiTbb`` and other classes to implement the oneAPI
|
``IntelOneapiTbb`` and other classes to implement the oneAPI
|
||||||
packages. Search for ``oneAPI`` at `<packages.spack.io>`_ for the full
|
packages. See the :ref:`package-list` for the full list of available
|
||||||
list of available oneAPI packages, or use::
|
oneAPI packages or use::
|
||||||
|
|
||||||
spack list -d oneAPI
|
spack list -d oneAPI
|
||||||
|
|
||||||
@@ -76,55 +76,6 @@ To build with with ``icx``, do ::
|
|||||||
|
|
||||||
spack install patchelf%oneapi
|
spack install patchelf%oneapi
|
||||||
|
|
||||||
|
|
||||||
Using oneAPI Spack environment
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
In this example, we build lammps with ``icx`` using Spack environment for oneAPI packages created by Intel. The
|
|
||||||
compilers are installed with Spack like in example above.
|
|
||||||
|
|
||||||
Install the oneAPI compilers::
|
|
||||||
|
|
||||||
spack install intel-oneapi-compilers
|
|
||||||
|
|
||||||
Add the compilers to your ``compilers.yaml`` so Spack can use them::
|
|
||||||
|
|
||||||
spack compiler add `spack location -i intel-oneapi-compilers`/compiler/latest/linux/bin/intel64
|
|
||||||
spack compiler add `spack location -i intel-oneapi-compilers`/compiler/latest/linux/bin
|
|
||||||
|
|
||||||
Verify that the compilers are available::
|
|
||||||
|
|
||||||
spack compiler list
|
|
||||||
|
|
||||||
Clone `spack-configs <https://github.com/spack/spack-configs>`_ repo and activate Intel oneAPI CPU environment::
|
|
||||||
|
|
||||||
git clone https://github.com/spack/spack-configs
|
|
||||||
spack env activate spack-configs/INTEL/CPU
|
|
||||||
spack concretize -f
|
|
||||||
|
|
||||||
`Intel oneAPI CPU environment <https://github.com/spack/spack-configs/blob/main/INTEL/CPU/spack.yaml>`_ contains applications tested and validated by Intel, this list is constantly extended. And currently it supports:
|
|
||||||
|
|
||||||
- `Devito <https://www.devitoproject.org/>`_
|
|
||||||
- `GROMACS <https://www.gromacs.org/>`_
|
|
||||||
- `HPCG <https://www.hpcg-benchmark.org/>`_
|
|
||||||
- `HPL <https://netlib.org/benchmark/hpl/>`_
|
|
||||||
- `LAMMPS <https://www.lammps.org/#gsc.tab=0>`_
|
|
||||||
- `OpenFOAM <https://www.openfoam.com/>`_
|
|
||||||
- `Quantum Espresso <https://www.quantum-espresso.org/>`_
|
|
||||||
- `STREAM <https://www.cs.virginia.edu/stream/>`_
|
|
||||||
- `WRF <https://github.com/wrf-model/WRF>`_
|
|
||||||
|
|
||||||
To build lammps with oneAPI compiler from this environment just run::
|
|
||||||
|
|
||||||
spack install lammps
|
|
||||||
|
|
||||||
Compiled binaries can be find using::
|
|
||||||
|
|
||||||
spack cd -i lammps
|
|
||||||
|
|
||||||
You can do the same for all other applications from this environment.
|
|
||||||
|
|
||||||
|
|
||||||
Using oneAPI MPI to Satisfy a Virtual Dependence
|
Using oneAPI MPI to Satisfy a Virtual Dependence
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ using GNU Make, you should add a dependency on ``gmake``:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
depends_on("gmake", type="build")
|
depends_on('gmake', type='build')
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -93,8 +93,8 @@ there are any other variables you need to set, you can do this in the
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def edit(self, spec, prefix):
|
def edit(self, spec, prefix):
|
||||||
env["PREFIX"] = prefix
|
env['PREFIX'] = prefix
|
||||||
env["BLASLIB"] = spec["blas"].libs.ld_flags
|
env['BLASLIB'] = spec['blas'].libs.ld_flags
|
||||||
|
|
||||||
|
|
||||||
`cbench <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/cbench/package.py>`_
|
`cbench <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/cbench/package.py>`_
|
||||||
@@ -113,7 +113,7 @@ you can do this like so:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
build_targets = ["CC=cc"]
|
build_targets = ['CC=cc']
|
||||||
|
|
||||||
|
|
||||||
If you do need access to the spec, you can create a property like so:
|
If you do need access to the spec, you can create a property like so:
|
||||||
@@ -125,8 +125,8 @@ If you do need access to the spec, you can create a property like so:
|
|||||||
spec = self.spec
|
spec = self.spec
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"CC=cc",
|
'CC=cc',
|
||||||
f"BLASLIB={spec['blas'].libs.ld_flags}",
|
'BLASLIB={0}'.format(spec['blas'].libs.ld_flags),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -145,12 +145,12 @@ and a ``filter_file`` method to help with this. For example:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def edit(self, spec, prefix):
|
def edit(self, spec, prefix):
|
||||||
makefile = FileFilter("Makefile")
|
makefile = FileFilter('Makefile')
|
||||||
|
|
||||||
makefile.filter(r"^\s*CC\s*=.*", f"CC = {spack_cc}")
|
makefile.filter(r'^\s*CC\s*=.*', 'CC = ' + spack_cc)
|
||||||
makefile.filter(r"^\s*CXX\s*=.*", f"CXX = {spack_cxx}")
|
makefile.filter(r'^\s*CXX\s*=.*', 'CXX = ' + spack_cxx)
|
||||||
makefile.filter(r"^\s*F77\s*=.*", f"F77 = {spack_f77}")
|
makefile.filter(r'^\s*F77\s*=.*', 'F77 = ' + spack_f77)
|
||||||
makefile.filter(r"^\s*FC\s*=.*", f"FC = {spack_fc}")
|
makefile.filter(r'^\s*FC\s*=.*', 'FC = ' + spack_fc)
|
||||||
|
|
||||||
|
|
||||||
`stream <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/stream/package.py>`_
|
`stream <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/stream/package.py>`_
|
||||||
@@ -181,16 +181,16 @@ well for storing variables:
|
|||||||
|
|
||||||
def edit(self, spec, prefix):
|
def edit(self, spec, prefix):
|
||||||
config = {
|
config = {
|
||||||
"CC": "cc",
|
'CC': 'cc',
|
||||||
"MAKE": "make",
|
'MAKE': 'make',
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.satisfies("+blas"):
|
if '+blas' in spec:
|
||||||
config["BLAS_LIBS"] = spec["blas"].libs.joined()
|
config['BLAS_LIBS'] = spec['blas'].libs.joined()
|
||||||
|
|
||||||
with open("make.inc", "w") as inc:
|
with open('make.inc', 'w') as inc:
|
||||||
for key in config:
|
for key in config:
|
||||||
inc.write(f"{key} = {config[key]}\n")
|
inc.write('{0} = {1}\n'.format(key, config[key]))
|
||||||
|
|
||||||
|
|
||||||
`elk <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/elk/package.py>`_
|
`elk <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/elk/package.py>`_
|
||||||
@@ -204,14 +204,14 @@ them in a list:
|
|||||||
|
|
||||||
def edit(self, spec, prefix):
|
def edit(self, spec, prefix):
|
||||||
config = [
|
config = [
|
||||||
f"INSTALL_DIR = {prefix}",
|
'INSTALL_DIR = {0}'.format(prefix),
|
||||||
"INCLUDE_DIR = $(INSTALL_DIR)/include",
|
'INCLUDE_DIR = $(INSTALL_DIR)/include',
|
||||||
"LIBRARY_DIR = $(INSTALL_DIR)/lib",
|
'LIBRARY_DIR = $(INSTALL_DIR)/lib',
|
||||||
]
|
]
|
||||||
|
|
||||||
with open("make.inc", "w") as inc:
|
with open('make.inc', 'w') as inc:
|
||||||
for var in config:
|
for var in config:
|
||||||
inc.write(f"{var}\n")
|
inc.write('{0}\n'.format(var))
|
||||||
|
|
||||||
|
|
||||||
`hpl <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/hpl/package.py>`_
|
`hpl <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/hpl/package.py>`_
|
||||||
@@ -284,7 +284,7 @@ can tell Spack where to locate it like so:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
build_directory = "src"
|
build_directory = 'src'
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -299,8 +299,8 @@ install the package:
|
|||||||
|
|
||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
mkdir(prefix.bin)
|
mkdir(prefix.bin)
|
||||||
install("foo", prefix.bin)
|
install('foo', prefix.bin)
|
||||||
install_tree("lib", prefix.lib)
|
install_tree('lib', prefix.lib)
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -152,16 +152,16 @@ set. Once set, ``pypi`` will be used to define the ``homepage``,
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
homepage = "https://pypi.org/project/setuptools/"
|
homepage = 'https://pypi.org/project/setuptools/'
|
||||||
url = "https://pypi.org/packages/source/s/setuptools/setuptools-49.2.0.zip"
|
url = 'https://pypi.org/packages/source/s/setuptools/setuptools-49.2.0.zip'
|
||||||
list_url = "https://pypi.org/simple/setuptools/"
|
list_url = 'https://pypi.org/simple/setuptools/'
|
||||||
|
|
||||||
|
|
||||||
is equivalent to:
|
is equivalent to:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
pypi = "setuptools/setuptools-49.2.0.zip"
|
pypi = 'setuptools/setuptools-49.2.0.zip'
|
||||||
|
|
||||||
|
|
||||||
If a package has a different homepage listed on PyPI, you can
|
If a package has a different homepage listed on PyPI, you can
|
||||||
@@ -208,7 +208,7 @@ dependencies to your package:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
depends_on("py-setuptools@42:", type="build")
|
depends_on('py-setuptools@42:', type='build')
|
||||||
|
|
||||||
|
|
||||||
Note that ``py-wheel`` is already listed as a build dependency in the
|
Note that ``py-wheel`` is already listed as a build dependency in the
|
||||||
@@ -232,7 +232,7 @@ Look for dependencies under the following keys:
|
|||||||
* ``dependencies`` under ``[project]``
|
* ``dependencies`` under ``[project]``
|
||||||
|
|
||||||
These packages are required for building and installation. You can
|
These packages are required for building and installation. You can
|
||||||
add them with ``type=("build", "run")``.
|
add them with ``type=('build', 'run')``.
|
||||||
|
|
||||||
* ``[project.optional-dependencies]``
|
* ``[project.optional-dependencies]``
|
||||||
|
|
||||||
@@ -279,12 +279,12 @@ distutils library, and has almost the exact same API. In addition to
|
|||||||
* ``setup_requires``
|
* ``setup_requires``
|
||||||
|
|
||||||
These packages are usually only needed at build-time, so you can
|
These packages are usually only needed at build-time, so you can
|
||||||
add them with ``type="build"``.
|
add them with ``type='build'``.
|
||||||
|
|
||||||
* ``install_requires``
|
* ``install_requires``
|
||||||
|
|
||||||
These packages are required for building and installation. You can
|
These packages are required for building and installation. You can
|
||||||
add them with ``type=("build", "run")``.
|
add them with ``type=('build', 'run')``.
|
||||||
|
|
||||||
* ``extras_require``
|
* ``extras_require``
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ distutils library, and has almost the exact same API. In addition to
|
|||||||
|
|
||||||
These are packages that are required to run the unit tests for the
|
These are packages that are required to run the unit tests for the
|
||||||
package. These dependencies can be specified using the
|
package. These dependencies can be specified using the
|
||||||
``type="test"`` dependency type. However, the PyPI tarballs rarely
|
``type='test'`` dependency type. However, the PyPI tarballs rarely
|
||||||
contain unit tests, so there is usually no reason to add these.
|
contain unit tests, so there is usually no reason to add these.
|
||||||
|
|
||||||
See https://setuptools.pypa.io/en/latest/userguide/dependency_management.html
|
See https://setuptools.pypa.io/en/latest/userguide/dependency_management.html
|
||||||
@@ -321,7 +321,7 @@ older versions of flit may use the following keys:
|
|||||||
* ``requires`` under ``[tool.flit.metadata]``
|
* ``requires`` under ``[tool.flit.metadata]``
|
||||||
|
|
||||||
These packages are required for building and installation. You can
|
These packages are required for building and installation. You can
|
||||||
add them with ``type=("build", "run")``.
|
add them with ``type=('build', 'run')``.
|
||||||
|
|
||||||
* ``[tool.flit.metadata.requires-extra]``
|
* ``[tool.flit.metadata.requires-extra]``
|
||||||
|
|
||||||
@@ -434,12 +434,12 @@ the BLAS/LAPACK library you want pkg-config to search for:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
depends_on("py-pip@22.1:", type="build")
|
depends_on('py-pip@22.1:', type='build')
|
||||||
|
|
||||||
def config_settings(self, spec, prefix):
|
def config_settings(self, spec, prefix):
|
||||||
return {
|
return {
|
||||||
"blas": spec["blas"].libs.names[0],
|
'blas': spec['blas'].libs.names[0],
|
||||||
"lapack": spec["lapack"].libs.names[0],
|
'lapack': spec['lapack'].libs.names[0],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -463,10 +463,10 @@ has an optional dependency on ``libyaml`` that can be enabled like so:
|
|||||||
|
|
||||||
def global_options(self, spec, prefix):
|
def global_options(self, spec, prefix):
|
||||||
options = []
|
options = []
|
||||||
if spec.satisfies("+libyaml"):
|
if '+libyaml' in spec:
|
||||||
options.append("--with-libyaml")
|
options.append('--with-libyaml')
|
||||||
else:
|
else:
|
||||||
options.append("--without-libyaml")
|
options.append('--without-libyaml')
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
@@ -492,10 +492,10 @@ allows you to specify the directories to search for ``libyaml``:
|
|||||||
|
|
||||||
def install_options(self, spec, prefix):
|
def install_options(self, spec, prefix):
|
||||||
options = []
|
options = []
|
||||||
if spec.satisfies("+libyaml"):
|
if '+libyaml' in spec:
|
||||||
options.extend([
|
options.extend([
|
||||||
spec["libyaml"].libs.search_flags,
|
spec['libyaml'].libs.search_flags,
|
||||||
spec["libyaml"].headers.include_flags,
|
spec['libyaml'].headers.include_flags,
|
||||||
])
|
])
|
||||||
return options
|
return options
|
||||||
|
|
||||||
@@ -556,7 +556,7 @@ detected are wrong, you can provide the names yourself by overriding
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import_modules = ["six"]
|
import_modules = ['six']
|
||||||
|
|
||||||
|
|
||||||
Sometimes the list of module names to import depends on how the
|
Sometimes the list of module names to import depends on how the
|
||||||
@@ -571,9 +571,9 @@ This can be expressed like so:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def import_modules(self):
|
def import_modules(self):
|
||||||
modules = ["yaml"]
|
modules = ['yaml']
|
||||||
if self.spec.satisfies("+libyaml"):
|
if '+libyaml' in self.spec:
|
||||||
modules.append("yaml.cyaml")
|
modules.append('yaml.cyaml')
|
||||||
return modules
|
return modules
|
||||||
|
|
||||||
|
|
||||||
@@ -586,14 +586,14 @@ Instead of defining the ``import_modules`` explicitly, only the subset
|
|||||||
of module names to be skipped can be defined by using ``skip_modules``.
|
of module names to be skipped can be defined by using ``skip_modules``.
|
||||||
If a defined module has submodules, they are skipped as well, e.g.,
|
If a defined module has submodules, they are skipped as well, e.g.,
|
||||||
in case the ``plotting`` modules should be excluded from the
|
in case the ``plotting`` modules should be excluded from the
|
||||||
automatically detected ``import_modules`` ``["nilearn", "nilearn.surface",
|
automatically detected ``import_modules`` ``['nilearn', 'nilearn.surface',
|
||||||
"nilearn.plotting", "nilearn.plotting.data"]`` set:
|
'nilearn.plotting', 'nilearn.plotting.data']`` set:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
skip_modules = ["nilearn.plotting"]
|
skip_modules = ['nilearn.plotting']
|
||||||
|
|
||||||
This will set ``import_modules`` to ``["nilearn", "nilearn.surface"]``
|
This will set ``import_modules`` to ``['nilearn', 'nilearn.surface']``
|
||||||
|
|
||||||
Import tests can be run during the installation using ``spack install
|
Import tests can be run during the installation using ``spack install
|
||||||
--test=root`` or at any time after the installation using
|
--test=root`` or at any time after the installation using
|
||||||
@@ -612,11 +612,11 @@ after the ``install`` phase:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@run_after("install")
|
@run_after('install')
|
||||||
@on_package_attributes(run_tests=True)
|
@on_package_attributes(run_tests=True)
|
||||||
def install_test(self):
|
def install_test(self):
|
||||||
with working_dir("spack-test", create=True):
|
with working_dir('spack-test', create=True):
|
||||||
python("-c", "import numpy; numpy.test('full', verbose=2)")
|
python('-c', 'import numpy; numpy.test("full", verbose=2)')
|
||||||
|
|
||||||
|
|
||||||
when testing is enabled during the installation (i.e., ``spack install
|
when testing is enabled during the installation (i.e., ``spack install
|
||||||
@@ -638,7 +638,7 @@ provides Python bindings in a ``python`` directory, you can use:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
build_directory = "python"
|
build_directory = 'python'
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -81,27 +81,28 @@ class of your package. For example, you can add it to your
|
|||||||
class MyRocmPackage(CMakePackage, ROCmPackage):
|
class MyRocmPackage(CMakePackage, ROCmPackage):
|
||||||
...
|
...
|
||||||
# Ensure +rocm and amdgpu_targets are passed to dependencies
|
# Ensure +rocm and amdgpu_targets are passed to dependencies
|
||||||
depends_on("mydeppackage", when="+rocm")
|
depends_on('mydeppackage', when='+rocm')
|
||||||
for val in ROCmPackage.amdgpu_targets:
|
for val in ROCmPackage.amdgpu_targets:
|
||||||
depends_on(f"mydeppackage amdgpu_target={val}",
|
depends_on('mydeppackage amdgpu_target={0}'.format(val),
|
||||||
when=f"amdgpu_target={val}")
|
when='amdgpu_target={0}'.format(val))
|
||||||
...
|
...
|
||||||
|
|
||||||
def cmake_args(self):
|
def cmake_args(self):
|
||||||
spec = self.spec
|
spec = self.spec
|
||||||
args = []
|
args = []
|
||||||
...
|
...
|
||||||
if spec.satisfies("+rocm"):
|
if '+rocm' in spec:
|
||||||
# Set up the hip macros needed by the build
|
# Set up the hip macros needed by the build
|
||||||
args.extend([
|
args.extend([
|
||||||
"-DENABLE_HIP=ON",
|
'-DENABLE_HIP=ON',
|
||||||
f"-DHIP_ROOT_DIR={spec['hip'].prefix}"])
|
'-DHIP_ROOT_DIR={0}'.format(spec['hip'].prefix)])
|
||||||
rocm_archs = spec.variants["amdgpu_target"].value
|
rocm_archs = spec.variants['amdgpu_target'].value
|
||||||
if "none" not in rocm_archs:
|
if 'none' not in rocm_archs:
|
||||||
args.append(f"-DHIP_HIPCC_FLAGS=--amdgpu-target={','.join(rocm_archs}")
|
args.append('-DHIP_HIPCC_FLAGS=--amdgpu-target={0}'
|
||||||
|
.format(",".join(rocm_archs)))
|
||||||
else:
|
else:
|
||||||
# Ensure build with hip is disabled
|
# Ensure build with hip is disabled
|
||||||
args.append("-DENABLE_HIP=OFF")
|
args.append('-DENABLE_HIP=OFF')
|
||||||
...
|
...
|
||||||
return args
|
return args
|
||||||
...
|
...
|
||||||
@@ -113,7 +114,7 @@ build.
|
|||||||
|
|
||||||
This example also illustrates how to check for the ``rocm`` variant using
|
This example also illustrates how to check for the ``rocm`` variant using
|
||||||
``self.spec`` and how to retrieve the ``amdgpu_target`` variant's value
|
``self.spec`` and how to retrieve the ``amdgpu_target`` variant's value
|
||||||
using ``self.spec.variants["amdgpu_target"].value``.
|
using ``self.spec.variants['amdgpu_target'].value``.
|
||||||
|
|
||||||
All five packages using ``ROCmPackage`` as of January 2021 also use the
|
All five packages using ``ROCmPackage`` as of January 2021 also use the
|
||||||
:ref:`CudaPackage <cudapackage>`. So it is worth looking at those packages
|
:ref:`CudaPackage <cudapackage>`. So it is worth looking at those packages
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ overridden like so:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
scons("check")
|
scons('check')
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
@@ -88,7 +88,7 @@ base class already contains:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
depends_on("scons", type="build")
|
depends_on('scons', type='build')
|
||||||
|
|
||||||
|
|
||||||
If you want to specify a particular version requirement, you can override
|
If you want to specify a particular version requirement, you can override
|
||||||
@@ -96,7 +96,7 @@ this in your package:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
depends_on("scons@2.3.0:", type="build")
|
depends_on('scons@2.3.0:', type='build')
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -238,14 +238,14 @@ the package build phase. This is done by overriding ``build_args`` like so:
|
|||||||
|
|
||||||
def build_args(self, spec, prefix):
|
def build_args(self, spec, prefix):
|
||||||
args = [
|
args = [
|
||||||
f"PREFIX={prefix}",
|
'PREFIX={0}'.format(prefix),
|
||||||
f"ZLIB={spec['zlib'].prefix}",
|
'ZLIB={0}'.format(spec['zlib'].prefix),
|
||||||
]
|
]
|
||||||
|
|
||||||
if spec.satisfies("+debug"):
|
if '+debug' in spec:
|
||||||
args.append("DEBUG=yes")
|
args.append('DEBUG=yes')
|
||||||
else:
|
else:
|
||||||
args.append("DEBUG=no")
|
args.append('DEBUG=no')
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@@ -275,8 +275,8 @@ environment variables. For example, cantera has the following option:
|
|||||||
* env_vars: [ string ]
|
* env_vars: [ string ]
|
||||||
Environment variables to propagate through to SCons. Either the
|
Environment variables to propagate through to SCons. Either the
|
||||||
string "all" or a comma separated list of variable names, e.g.
|
string "all" or a comma separated list of variable names, e.g.
|
||||||
"LD_LIBRARY_PATH,HOME".
|
'LD_LIBRARY_PATH,HOME'.
|
||||||
- default: "LD_LIBRARY_PATH,PYTHONPATH"
|
- default: 'LD_LIBRARY_PATH,PYTHONPATH'
|
||||||
|
|
||||||
|
|
||||||
In the case of cantera, using ``env_vars=all`` allows us to use
|
In the case of cantera, using ``env_vars=all`` allows us to use
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ By default, these phases run:
|
|||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ sip-build --verbose --target-dir ...
|
$ python configure.py --bindir ... --destdir ...
|
||||||
$ make
|
$ make
|
||||||
$ make install
|
$ make install
|
||||||
|
|
||||||
@@ -41,30 +41,30 @@ By default, these phases run:
|
|||||||
Important files
|
Important files
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Each SIP package comes with a custom configuration file written in Python.
|
Each SIP package comes with a custom ``configure.py`` build script,
|
||||||
For newer packages, this is called ``project.py``, while in older packages,
|
written in Python. This script contains instructions to build the project.
|
||||||
it may be called ``configure.py``. This script contains instructions to build
|
|
||||||
the project.
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Build system dependencies
|
Build system dependencies
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
``SIPPackage`` requires several dependencies. Python and SIP are needed at build-time
|
``SIPPackage`` requires several dependencies. Python is needed to run
|
||||||
to run the aforementioned configure script. Python is also needed at run-time to
|
the ``configure.py`` build script, and to run the resulting Python
|
||||||
actually use the installed Python library. And as we are building Python bindings
|
libraries. Qt is needed to provide the ``qmake`` command. SIP is also
|
||||||
for C/C++ libraries, Python is also needed as a link dependency. All of these
|
needed to build the package. All of these dependencies are automatically
|
||||||
dependencies are automatically added via the base class.
|
added via the base class
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
extends("python", type=("build", "link", "run"))
|
extends('python')
|
||||||
depends_on("py-sip", type="build")
|
|
||||||
|
|
||||||
|
depends_on('qt', type='build')
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
depends_on('py-sip', type='build')
|
||||||
Passing arguments to ``sip-build``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Passing arguments to ``configure.py``
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Each phase comes with a ``<phase_args>`` function that can be used to pass
|
Each phase comes with a ``<phase_args>`` function that can be used to pass
|
||||||
arguments to that particular phase. For example, if you need to pass
|
arguments to that particular phase. For example, if you need to pass
|
||||||
@@ -72,11 +72,11 @@ arguments to the configure phase, you can use:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def configure_args(self):
|
def configure_args(self, spec, prefix):
|
||||||
return ["--no-python-dbus"]
|
return ['--no-python-dbus']
|
||||||
|
|
||||||
|
|
||||||
A list of valid options can be found by running ``sip-build --help``.
|
A list of valid options can be found by running ``python configure.py --help``.
|
||||||
|
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
Testing
|
Testing
|
||||||
|
|||||||
@@ -48,6 +48,9 @@
|
|||||||
os.environ["COLIFY_SIZE"] = "25x120"
|
os.environ["COLIFY_SIZE"] = "25x120"
|
||||||
os.environ["COLUMNS"] = "120"
|
os.environ["COLUMNS"] = "120"
|
||||||
|
|
||||||
|
# Generate full package list if needed
|
||||||
|
subprocess.call(["spack", "list", "--format=html", "--update=package_list.html"])
|
||||||
|
|
||||||
# Generate a command index if an update is needed
|
# Generate a command index if an update is needed
|
||||||
subprocess.call(
|
subprocess.call(
|
||||||
[
|
[
|
||||||
@@ -94,7 +97,9 @@ class PatchedPythonDomain(PythonDomain):
|
|||||||
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
|
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
|
||||||
if "refspecific" in node:
|
if "refspecific" in node:
|
||||||
del node["refspecific"]
|
del node["refspecific"]
|
||||||
return super().resolve_xref(env, fromdocname, builder, typ, target, node, contnode)
|
return super(PatchedPythonDomain, self).resolve_xref(
|
||||||
|
env, fromdocname, builder, typ, target, node, contnode
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -144,6 +149,7 @@ def setup(sphinx):
|
|||||||
# Get nice vector graphics
|
# Get nice vector graphics
|
||||||
graphviz_output_format = "svg"
|
graphviz_output_format = "svg"
|
||||||
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ["_templates"]
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
@@ -204,7 +210,6 @@ def setup(sphinx):
|
|||||||
("py:class", "clingo.Control"),
|
("py:class", "clingo.Control"),
|
||||||
("py:class", "six.moves.urllib.parse.ParseResult"),
|
("py:class", "six.moves.urllib.parse.ParseResult"),
|
||||||
("py:class", "TextIO"),
|
("py:class", "TextIO"),
|
||||||
("py:class", "hashlib._Hash"),
|
|
||||||
# Spack classes that are private and we don't want to expose
|
# Spack classes that are private and we don't want to expose
|
||||||
("py:class", "spack.provider_index._IndexBase"),
|
("py:class", "spack.provider_index._IndexBase"),
|
||||||
("py:class", "spack.repo._PrependFileLoader"),
|
("py:class", "spack.repo._PrependFileLoader"),
|
||||||
@@ -212,8 +217,6 @@ def setup(sphinx):
|
|||||||
# Spack classes that intersphinx is unable to resolve
|
# Spack classes that intersphinx is unable to resolve
|
||||||
("py:class", "spack.version.StandardVersion"),
|
("py:class", "spack.version.StandardVersion"),
|
||||||
("py:class", "spack.spec.DependencySpec"),
|
("py:class", "spack.spec.DependencySpec"),
|
||||||
("py:class", "spack.spec.InstallStatus"),
|
|
||||||
("py:class", "spack.spec.SpecfileReaderBase"),
|
|
||||||
("py:class", "spack.install_test.Pb"),
|
("py:class", "spack.install_test.Pb"),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -230,8 +233,30 @@ def setup(sphinx):
|
|||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
# show_authors = False
|
# show_authors = False
|
||||||
sys.path.append("./_pygments")
|
|
||||||
pygments_style = "style.SpackStyle"
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
# We use our own extension of the default style with a few modifications
|
||||||
|
from pygments.style import Style
|
||||||
|
from pygments.styles.default import DefaultStyle
|
||||||
|
from pygments.token import Comment, Generic, Text
|
||||||
|
|
||||||
|
|
||||||
|
class SpackStyle(DefaultStyle):
|
||||||
|
styles = DefaultStyle.styles.copy()
|
||||||
|
background_color = "#f4f4f8"
|
||||||
|
styles[Generic.Output] = "#355"
|
||||||
|
styles[Generic.Prompt] = "bold #346ec9"
|
||||||
|
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
dist = pkg_resources.Distribution(__file__)
|
||||||
|
sys.path.append(".") # make 'conf' module findable
|
||||||
|
ep = pkg_resources.EntryPoint.parse("spack = conf:SpackStyle", dist=dist)
|
||||||
|
dist._ep_map = {"pygments.styles": {"plugin1": ep}}
|
||||||
|
pkg_resources.working_set.add(dist)
|
||||||
|
|
||||||
|
pygments_style = "spack"
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
# modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
@@ -316,15 +341,16 @@ def setup(sphinx):
|
|||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = "Spackdoc"
|
htmlhelp_basename = "Spackdoc"
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
# 'papersize': 'letterpaper',
|
#'papersize': 'letterpaper',
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
# 'pointsize': '10pt',
|
#'pointsize': '10pt',
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
# 'preamble': '',
|
#'preamble': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
|||||||
@@ -292,13 +292,12 @@ It is also worth noting that:
|
|||||||
non_bindable_shared_objects = ["libinterface.so"]
|
non_bindable_shared_objects = ["libinterface.so"]
|
||||||
|
|
||||||
----------------------
|
----------------------
|
||||||
``install_status``
|
``terminal_title``
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
When set to ``true``, Spack will show information about its current progress
|
By setting this option to ``true``, Spack will update the terminal's title to
|
||||||
as well as the current and total package numbers. Progress is shown both
|
provide information about its current progress as well as the current and
|
||||||
in the terminal title and inline. Setting it to ``false`` will not show any
|
total package numbers.
|
||||||
progress information.
|
|
||||||
|
|
||||||
To work properly, this requires your terminal to reset its title after
|
To work properly, this requires your terminal to reset its title after
|
||||||
Spack has finished its work, otherwise Spack's status information will
|
Spack has finished its work, otherwise Spack's status information will
|
||||||
|
|||||||
@@ -212,12 +212,18 @@ under the ``container`` attribute of environments:
|
|||||||
final:
|
final:
|
||||||
- libgomp
|
- libgomp
|
||||||
|
|
||||||
|
# Extra instructions
|
||||||
|
extra_instructions:
|
||||||
|
final: |
|
||||||
|
RUN echo 'export PS1="\[$(tput bold)\]\[$(tput setaf 1)\][gromacs]\[$(tput setaf 2)\]\u\[$(tput sgr0)\]:\w $ "' >> ~/.bashrc
|
||||||
|
|
||||||
# Labels for the image
|
# Labels for the image
|
||||||
labels:
|
labels:
|
||||||
app: "gromacs"
|
app: "gromacs"
|
||||||
mpi: "mpich"
|
mpi: "mpich"
|
||||||
|
|
||||||
A detailed description of the options available can be found in the :ref:`container_config_options` section.
|
A detailed description of the options available can be found in the
|
||||||
|
:ref:`container_config_options` section.
|
||||||
|
|
||||||
-------------------
|
-------------------
|
||||||
Setting Base Images
|
Setting Base Images
|
||||||
@@ -519,13 +525,6 @@ the example below:
|
|||||||
COPY data /share/myapp/data
|
COPY data /share/myapp/data
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
The Dockerfile is generated by running:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ spack -e /opt/environment containerize
|
|
||||||
|
|
||||||
Note that the environment must be active for spack to read the template.
|
|
||||||
The recipe that gets generated contains the two extra instruction that we added in our template extension:
|
The recipe that gets generated contains the two extra instruction that we added in our template extension:
|
||||||
|
|
||||||
.. code-block:: Dockerfile
|
.. code-block:: Dockerfile
|
||||||
@@ -637,7 +636,7 @@ to customize the generation of container recipes:
|
|||||||
- No
|
- No
|
||||||
* - ``os_packages:command``
|
* - ``os_packages:command``
|
||||||
- Tool used to manage system packages
|
- Tool used to manage system packages
|
||||||
- ``apt``, ``yum``, ``dnf``, ``dnf_epel``, ``zypper``, ``apk``, ``yum_amazon``
|
- ``apt``, ``yum``, ``zypper``, ``apk``, ``yum_amazon``
|
||||||
- Only with custom base images
|
- Only with custom base images
|
||||||
* - ``os_packages:update``
|
* - ``os_packages:update``
|
||||||
- Whether or not to update the list of available packages
|
- Whether or not to update the list of available packages
|
||||||
|
|||||||
@@ -310,11 +310,53 @@ Once all of the dependencies are installed, you can try building the documentati
|
|||||||
$ make clean
|
$ make clean
|
||||||
$ make
|
$ make
|
||||||
|
|
||||||
If you see any warning or error messages, you will have to correct those before your PR
|
If you see any warning or error messages, you will have to correct those before
|
||||||
is accepted. If you are editing the documentation, you should be running the
|
your PR is accepted.
|
||||||
documentation tests to make sure there are no errors. Documentation changes can result
|
|
||||||
in some obfuscated warning messages. If you don't understand what they mean, feel free
|
If you are editing the documentation, you should obviously be running the
|
||||||
to ask when you submit your PR.
|
documentation tests. But even if you are simply adding a new package, your
|
||||||
|
changes could cause the documentation tests to fail:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
package_list.rst:8745: WARNING: Block quote ends without a blank line; unexpected unindent.
|
||||||
|
|
||||||
|
At first, this error message will mean nothing to you, since you didn't edit
|
||||||
|
that file. Until you look at line 8745 of the file in question:
|
||||||
|
|
||||||
|
.. code-block:: rst
|
||||||
|
|
||||||
|
Description:
|
||||||
|
NetCDF is a set of software libraries and self-describing, machine-
|
||||||
|
independent data formats that support the creation, access, and sharing
|
||||||
|
of array-oriented scientific data.
|
||||||
|
|
||||||
|
Our documentation includes :ref:`a list of all Spack packages <package-list>`.
|
||||||
|
If you add a new package, its docstring is added to this page. The problem in
|
||||||
|
this case was that the docstring looked like:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Netcdf(Package):
|
||||||
|
"""
|
||||||
|
NetCDF is a set of software libraries and self-describing,
|
||||||
|
machine-independent data formats that support the creation,
|
||||||
|
access, and sharing of array-oriented scientific data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Docstrings cannot start with a newline character, or else Sphinx will complain.
|
||||||
|
Instead, they should look like:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Netcdf(Package):
|
||||||
|
"""NetCDF is a set of software libraries and self-describing,
|
||||||
|
machine-independent data formats that support the creation,
|
||||||
|
access, and sharing of array-oriented scientific data."""
|
||||||
|
|
||||||
|
Documentation changes can result in much more obfuscated warning messages.
|
||||||
|
If you don't understand what they mean, feel free to ask when you submit
|
||||||
|
your PR.
|
||||||
|
|
||||||
--------
|
--------
|
||||||
Coverage
|
Coverage
|
||||||
|
|||||||
@@ -916,9 +916,9 @@ function, as shown in the example below:
|
|||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
projections:
|
projections:
|
||||||
zlib: "{name}-{version}"
|
zlib: {name}-{version}
|
||||||
^mpi: "{name}-{version}/{^mpi.name}-{^mpi.version}-{compiler.name}-{compiler.version}"
|
^mpi: {name}-{version}/{^mpi.name}-{^mpi.version}-{compiler.name}-{compiler.version}
|
||||||
all: "{name}-{version}/{compiler.name}-{compiler.version}"
|
all: {name}-{version}/{compiler.name}-{compiler.version}
|
||||||
|
|
||||||
The entries in the projections configuration file must all be either
|
The entries in the projections configuration file must all be either
|
||||||
specs or the keyword ``all``. For each spec, the projection used will
|
specs or the keyword ``all``. For each spec, the projection used will
|
||||||
@@ -1132,11 +1132,11 @@ index once every package is pushed. Note how this target uses the generated
|
|||||||
example/push/%: example/install/%
|
example/push/%: example/install/%
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(info About to push $(SPEC) to a buildcache)
|
$(info About to push $(SPEC) to a buildcache)
|
||||||
$(SPACK) -e . buildcache push --allow-root --only=package $(BUILDCACHE_DIR) /$(HASH)
|
$(SPACK) -e . buildcache create --allow-root --only=package --directory $(BUILDCACHE_DIR) /$(HASH)
|
||||||
@touch $@
|
@touch $@
|
||||||
|
|
||||||
push: $(addprefix example/push/,$(example/SPACK_PACKAGE_IDS))
|
push: $(addprefix example/push/,$(example/SPACK_PACKAGE_IDS))
|
||||||
$(info Updating the buildcache index)
|
$(info Updating the buildcache index)
|
||||||
$(SPACK) -e . buildcache update-index $(BUILDCACHE_DIR)
|
$(SPACK) -e . buildcache update-index --directory $(BUILDCACHE_DIR)
|
||||||
$(info Done!)
|
$(info Done!)
|
||||||
@touch $@
|
@touch $@
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ installed, but you know that new compilers have been added to your
|
|||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ module load gcc/4.9.0
|
$ module load gcc-4.9.0
|
||||||
$ spack compiler find
|
$ spack compiler find
|
||||||
==> Added 1 new compiler to ~/.spack/linux/compilers.yaml
|
==> Added 1 new compiler to ~/.spack/linux/compilers.yaml
|
||||||
gcc@4.9.0
|
gcc@4.9.0
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
.. Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
|
||||||
Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
|
|
||||||
==========================
|
|
||||||
Using External GPU Support
|
|
||||||
==========================
|
|
||||||
|
|
||||||
Many packages come with a ``+cuda`` or ``+rocm`` variant. With no added
|
|
||||||
configuration Spack will download and install the needed components.
|
|
||||||
It may be preferable to use existing system support: the following sections
|
|
||||||
help with using a system installation of GPU libraries.
|
|
||||||
|
|
||||||
-----------------------------------
|
|
||||||
Using an External ROCm Installation
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
Spack breaks down ROCm into many separate component packages. The following
|
|
||||||
is an example ``packages.yaml`` that organizes a consistent set of ROCm
|
|
||||||
components for use by dependent packages:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
packages:
|
|
||||||
all:
|
|
||||||
compiler: [rocmcc@=5.3.0]
|
|
||||||
variants: amdgpu_target=gfx90a
|
|
||||||
hip:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: hip@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/hip
|
|
||||||
hsa-rocr-dev:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: hsa-rocr-dev@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/
|
|
||||||
llvm-amdgpu:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: llvm-amdgpu@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/llvm/
|
|
||||||
comgr:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: comgr@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/
|
|
||||||
hipsparse:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: hipsparse@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/
|
|
||||||
hipblas:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: hipblas@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/
|
|
||||||
rocblas:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: rocblas@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/
|
|
||||||
rocprim:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: rocprim@5.3.0
|
|
||||||
prefix: /opt/rocm-5.3.0/rocprim/
|
|
||||||
|
|
||||||
This is in combination with the following compiler definition:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
compilers:
|
|
||||||
- compiler:
|
|
||||||
spec: rocmcc@=5.3.0
|
|
||||||
paths:
|
|
||||||
cc: /opt/rocm-5.3.0/bin/amdclang
|
|
||||||
cxx: /opt/rocm-5.3.0/bin/amdclang++
|
|
||||||
f77: null
|
|
||||||
fc: /opt/rocm-5.3.0/bin/amdflang
|
|
||||||
operating_system: rhel8
|
|
||||||
target: x86_64
|
|
||||||
|
|
||||||
This includes the following considerations:
|
|
||||||
|
|
||||||
- Each of the listed externals specifies ``buildable: false`` to force Spack
|
|
||||||
to use only the externals we defined.
|
|
||||||
- ``spack external find`` can automatically locate some of the ``hip``/``rocm``
|
|
||||||
packages, but not all of them, and furthermore not in a manner that
|
|
||||||
guarantees a complementary set if multiple ROCm installations are available.
|
|
||||||
- The ``prefix`` is the same for several components, but note that others
|
|
||||||
require listing one of the subdirectories as a prefix.
|
|
||||||
|
|
||||||
-----------------------------------
|
|
||||||
Using an External CUDA Installation
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
CUDA is split into fewer components and is simpler to specify:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
packages:
|
|
||||||
all:
|
|
||||||
variants:
|
|
||||||
- cuda_arch=70
|
|
||||||
cuda:
|
|
||||||
buildable: false
|
|
||||||
externals:
|
|
||||||
- spec: cuda@11.0.2
|
|
||||||
prefix: /opt/cuda/cuda-11.0.2/
|
|
||||||
|
|
||||||
where ``/opt/cuda/cuda-11.0.2/lib/`` contains ``libcudart.so``.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 296 KiB |
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 108 KiB |
@@ -54,16 +54,9 @@ or refer to the full manual below.
|
|||||||
features
|
features
|
||||||
getting_started
|
getting_started
|
||||||
basic_usage
|
basic_usage
|
||||||
|
Tutorial: Spack 101 <https://spack-tutorial.readthedocs.io>
|
||||||
replace_conda_homebrew
|
replace_conda_homebrew
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: Links
|
|
||||||
|
|
||||||
Tutorial (spack-tutorial.rtfd.io) <https://spack-tutorial.readthedocs.io>
|
|
||||||
Packages (packages.spack.io) <https://packages.spack.io>
|
|
||||||
Binaries (binaries.spack.io) <https://cache.spack.io>
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:caption: Reference
|
:caption: Reference
|
||||||
@@ -79,11 +72,10 @@ or refer to the full manual below.
|
|||||||
repositories
|
repositories
|
||||||
binary_caches
|
binary_caches
|
||||||
command_index
|
command_index
|
||||||
|
package_list
|
||||||
chain
|
chain
|
||||||
extensions
|
extensions
|
||||||
pipelines
|
pipelines
|
||||||
signing
|
|
||||||
gpu_configuration
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|||||||
@@ -35,27 +35,27 @@ showing lots of installed packages:
|
|||||||
$ module avail
|
$ module avail
|
||||||
|
|
||||||
--------------------------------------------------------------- ~/spack/share/spack/modules/linux-ubuntu14-x86_64 ---------------------------------------------------------------
|
--------------------------------------------------------------- ~/spack/share/spack/modules/linux-ubuntu14-x86_64 ---------------------------------------------------------------
|
||||||
autoconf/2.69-gcc-4.8-qextxkq hwloc/1.11.6-gcc-6.3.0-akcisez m4/1.4.18-gcc-4.8-ev2znoc openblas/0.2.19-gcc-6.3.0-dhkmed6 py-setuptools/34.2.0-gcc-6.3.0-fadur4s
|
autoconf-2.69-gcc-4.8-qextxkq hwloc-1.11.6-gcc-6.3.0-akcisez m4-1.4.18-gcc-4.8-ev2znoc openblas-0.2.19-gcc-6.3.0-dhkmed6 py-setuptools-34.2.0-gcc-6.3.0-fadur4s
|
||||||
automake/1.15-gcc-4.8-maqvukj isl/0.18-gcc-4.8-afi6taq m4/1.4.18-gcc-6.3.0-uppywnz openmpi/2.1.0-gcc-6.3.0-go2s4z5 py-six/1.10.0-gcc-6.3.0-p4dhkaw
|
automake-1.15-gcc-4.8-maqvukj isl-0.18-gcc-4.8-afi6taq m4-1.4.18-gcc-6.3.0-uppywnz openmpi-2.1.0-gcc-6.3.0-go2s4z5 py-six-1.10.0-gcc-6.3.0-p4dhkaw
|
||||||
binutils/2.28-gcc-4.8-5s7c6rs libiconv/1.15-gcc-4.8-at46wg3 mawk/1.3.4-gcc-4.8-acjez57 openssl/1.0.2k-gcc-4.8-dkls5tk python/2.7.13-gcc-6.3.0-tyehea7
|
binutils-2.28-gcc-4.8-5s7c6rs libiconv-1.15-gcc-4.8-at46wg3 mawk-1.3.4-gcc-4.8-acjez57 openssl-1.0.2k-gcc-4.8-dkls5tk python-2.7.13-gcc-6.3.0-tyehea7
|
||||||
bison/3.0.4-gcc-4.8-ek4luo5 libpciaccess/0.13.4-gcc-6.3.0-gmufnvh mawk/1.3.4-gcc-6.3.0-ostdoms openssl/1.0.2k-gcc-6.3.0-gxgr5or readline/7.0-gcc-4.8-xhufqhn
|
bison-3.0.4-gcc-4.8-ek4luo5 libpciaccess-0.13.4-gcc-6.3.0-gmufnvh mawk-1.3.4-gcc-6.3.0-ostdoms openssl-1.0.2k-gcc-6.3.0-gxgr5or readline-7.0-gcc-4.8-xhufqhn
|
||||||
bzip2/1.0.6-gcc-4.8-iffrxzn libsigsegv/2.11-gcc-4.8-pp2cvte mpc/1.0.3-gcc-4.8-g5mztc5 pcre/8.40-gcc-4.8-r5pbrxb readline/7.0-gcc-6.3.0-zzcyicg
|
bzip2-1.0.6-gcc-4.8-iffrxzn libsigsegv-2.11-gcc-4.8-pp2cvte mpc-1.0.3-gcc-4.8-g5mztc5 pcre-8.40-gcc-4.8-r5pbrxb readline-7.0-gcc-6.3.0-zzcyicg
|
||||||
bzip2/1.0.6-gcc-6.3.0-bequudr libsigsegv/2.11-gcc-6.3.0-7enifnh mpfr/3.1.5-gcc-4.8-o7xm7az perl/5.24.1-gcc-4.8-dg5j65u sqlite/3.8.5-gcc-6.3.0-6zoruzj
|
bzip2-1.0.6-gcc-6.3.0-bequudr libsigsegv-2.11-gcc-6.3.0-7enifnh mpfr-3.1.5-gcc-4.8-o7xm7az perl-5.24.1-gcc-4.8-dg5j65u sqlite-3.8.5-gcc-6.3.0-6zoruzj
|
||||||
cmake/3.7.2-gcc-6.3.0-fowuuby libtool/2.4.6-gcc-4.8-7a523za mpich/3.2-gcc-6.3.0-dmvd3aw perl/5.24.1-gcc-6.3.0-6uzkpt6 tar/1.29-gcc-4.8-wse2ass
|
cmake-3.7.2-gcc-6.3.0-fowuuby libtool-2.4.6-gcc-4.8-7a523za mpich-3.2-gcc-6.3.0-dmvd3aw perl-5.24.1-gcc-6.3.0-6uzkpt6 tar-1.29-gcc-4.8-wse2ass
|
||||||
curl/7.53.1-gcc-4.8-3fz46n6 libtool/2.4.6-gcc-6.3.0-n7zmbzt ncurses/6.0-gcc-4.8-dcpe7ia pkg-config/0.29.2-gcc-4.8-ib33t75 tcl/8.6.6-gcc-4.8-tfxzqbr
|
curl-7.53.1-gcc-4.8-3fz46n6 libtool-2.4.6-gcc-6.3.0-n7zmbzt ncurses-6.0-gcc-4.8-dcpe7ia pkg-config-0.29.2-gcc-4.8-ib33t75 tcl-8.6.6-gcc-4.8-tfxzqbr
|
||||||
expat/2.2.0-gcc-4.8-mrv6bd4 libxml2/2.9.4-gcc-4.8-ryzxnsu ncurses/6.0-gcc-6.3.0-ucbhcdy pkg-config/0.29.2-gcc-6.3.0-jpgubk3 util-macros/1.19.1-gcc-6.3.0-xorz2x2
|
expat-2.2.0-gcc-4.8-mrv6bd4 libxml2-2.9.4-gcc-4.8-ryzxnsu ncurses-6.0-gcc-6.3.0-ucbhcdy pkg-config-0.29.2-gcc-6.3.0-jpgubk3 util-macros-1.19.1-gcc-6.3.0-xorz2x2
|
||||||
flex/2.6.3-gcc-4.8-yf345oo libxml2/2.9.4-gcc-6.3.0-rltzsdh netlib-lapack/3.6.1-gcc-6.3.0-js33dog py-appdirs/1.4.0-gcc-6.3.0-jxawmw7 xz/5.2.3-gcc-4.8-mew4log
|
flex-2.6.3-gcc-4.8-yf345oo libxml2-2.9.4-gcc-6.3.0-rltzsdh netlib-lapack-3.6.1-gcc-6.3.0-js33dog py-appdirs-1.4.0-gcc-6.3.0-jxawmw7 xz-5.2.3-gcc-4.8-mew4log
|
||||||
gcc/6.3.0-gcc-4.8-24puqve lmod/7.4.1-gcc-4.8-je4srhr netlib-scalapack/2.0.2-gcc-6.3.0-5aidk4l py-numpy/1.12.0-gcc-6.3.0-oemmoeu xz/5.2.3-gcc-6.3.0-3vqeuvb
|
gcc-6.3.0-gcc-4.8-24puqve lmod-7.4.1-gcc-4.8-je4srhr netlib-scalapack-2.0.2-gcc-6.3.0-5aidk4l py-numpy-1.12.0-gcc-6.3.0-oemmoeu xz-5.2.3-gcc-6.3.0-3vqeuvb
|
||||||
gettext/0.19.8.1-gcc-4.8-yymghlh lua/5.3.4-gcc-4.8-im75yaz netlib-scalapack/2.0.2-gcc-6.3.0-hjsemcn py-packaging/16.8-gcc-6.3.0-i2n3dtl zip/3.0-gcc-4.8-rwar22d
|
gettext-0.19.8.1-gcc-4.8-yymghlh lua-5.3.4-gcc-4.8-im75yaz netlib-scalapack-2.0.2-gcc-6.3.0-hjsemcn py-packaging-16.8-gcc-6.3.0-i2n3dtl zip-3.0-gcc-4.8-rwar22d
|
||||||
gmp/6.1.2-gcc-4.8-5ub2wu5 lua-luafilesystem/1_6_3-gcc-4.8-wkey3nl netlib-scalapack/2.0.2-gcc-6.3.0-jva724b py-pyparsing/2.1.10-gcc-6.3.0-tbo6gmw zlib/1.2.11-gcc-4.8-pgxsxv7
|
gmp-6.1.2-gcc-4.8-5ub2wu5 lua-luafilesystem-1_6_3-gcc-4.8-wkey3nl netlib-scalapack-2.0.2-gcc-6.3.0-jva724b py-pyparsing-2.1.10-gcc-6.3.0-tbo6gmw zlib-1.2.11-gcc-4.8-pgxsxv7
|
||||||
help2man/1.47.4-gcc-4.8-kcnqmau lua-luaposix/33.4.0-gcc-4.8-mdod2ry netlib-scalapack/2.0.2-gcc-6.3.0-rgqfr6d py-scipy/0.19.0-gcc-6.3.0-kr7nat4 zlib/1.2.11-gcc-6.3.0-7cqp6cj
|
help2man-1.47.4-gcc-4.8-kcnqmau lua-luaposix-33.4.0-gcc-4.8-mdod2ry netlib-scalapack-2.0.2-gcc-6.3.0-rgqfr6d py-scipy-0.19.0-gcc-6.3.0-kr7nat4 zlib-1.2.11-gcc-6.3.0-7cqp6cj
|
||||||
|
|
||||||
The names should look familiar, as they resemble the output from ``spack find``.
|
The names should look familiar, as they resemble the output from ``spack find``.
|
||||||
For example, you could type the following command to load the ``cmake`` module:
|
For example, you could type the following command to load the ``cmake`` module:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ module load cmake/3.7.2-gcc-6.3.0-fowuuby
|
$ module load cmake-3.7.2-gcc-6.3.0-fowuuby
|
||||||
|
|
||||||
Neither of these is particularly pretty, easy to remember, or easy to
|
Neither of these is particularly pretty, easy to remember, or easy to
|
||||||
type. Luckily, Spack offers many facilities for customizing the module
|
type. Luckily, Spack offers many facilities for customizing the module
|
||||||
@@ -275,12 +275,10 @@ of the installed software. For instance, in the snippet below:
|
|||||||
set:
|
set:
|
||||||
BAR: 'bar'
|
BAR: 'bar'
|
||||||
# This anonymous spec selects any package that
|
# This anonymous spec selects any package that
|
||||||
# depends on mpi. The double colon at the
|
# depends on openmpi. The double colon at the
|
||||||
# end clears the set of rules that matched so far.
|
# end clears the set of rules that matched so far.
|
||||||
^mpi::
|
^openmpi::
|
||||||
environment:
|
environment:
|
||||||
prepend_path:
|
|
||||||
PATH: '{^mpi.prefix}/bin'
|
|
||||||
set:
|
set:
|
||||||
BAR: 'baz'
|
BAR: 'baz'
|
||||||
# Selects any zlib package
|
# Selects any zlib package
|
||||||
@@ -295,9 +293,7 @@ of the installed software. For instance, in the snippet below:
|
|||||||
- FOOBAR
|
- FOOBAR
|
||||||
|
|
||||||
you are instructing Spack to set the environment variable ``BAR=bar`` for every module,
|
you are instructing Spack to set the environment variable ``BAR=bar`` for every module,
|
||||||
unless the associated spec satisfies the abstract dependency ``^mpi`` in which case
|
unless the associated spec satisfies ``^openmpi`` in which case ``BAR=baz``.
|
||||||
``BAR=baz``, and the directory containing the respective MPI executables is prepended
|
|
||||||
to the ``PATH`` variable.
|
|
||||||
In addition in any spec that satisfies ``zlib`` the value ``foo`` will be
|
In addition in any spec that satisfies ``zlib`` the value ``foo`` will be
|
||||||
prepended to ``LD_LIBRARY_PATH`` and in any spec that satisfies ``zlib%gcc@4.8``
|
prepended to ``LD_LIBRARY_PATH`` and in any spec that satisfies ``zlib%gcc@4.8``
|
||||||
the variable ``FOOBAR`` will be unset.
|
the variable ``FOOBAR`` will be unset.
|
||||||
@@ -400,30 +396,28 @@ that are already in the Lmod hierarchy.
|
|||||||
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Tcl and Lua modules also allow for explicit conflicts between modulefiles.
|
Tcl modules
|
||||||
|
Tcl modules also allow for explicit conflicts between modulefiles.
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
default:
|
default:
|
||||||
enable:
|
enable:
|
||||||
- tcl
|
- tcl
|
||||||
tcl:
|
tcl:
|
||||||
projections:
|
projections:
|
||||||
all: '{name}/{version}-{compiler.name}-{compiler.version}'
|
all: '{name}/{version}-{compiler.name}-{compiler.version}'
|
||||||
all:
|
all:
|
||||||
conflict:
|
conflict:
|
||||||
- '{name}'
|
- '{name}'
|
||||||
- 'intel/14.0.1'
|
- 'intel/14.0.1'
|
||||||
|
|
||||||
will create module files that will conflict with ``intel/14.0.1`` and with the
|
will create module files that will conflict with ``intel/14.0.1`` and with the
|
||||||
base directory of the same module, effectively preventing the possibility to
|
base directory of the same module, effectively preventing the possibility to
|
||||||
load two or more versions of the same software at the same time. The tokens
|
load two or more versions of the same software at the same time. The tokens
|
||||||
that are available for use in this directive are the same understood by the
|
that are available for use in this directive are the same understood by
|
||||||
:meth:`~spack.spec.Spec.format` method.
|
the :meth:`~spack.spec.Spec.format` method.
|
||||||
|
|
||||||
For Lmod and Environment Modules versions prior 4.2, it is important to
|
|
||||||
express the conflict on both modulefiles conflicting with each other.
|
|
||||||
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -785,35 +779,35 @@ cut-and-pasted into a shell script. For example:
|
|||||||
|
|
||||||
$ spack module tcl loads --dependencies py-numpy git
|
$ spack module tcl loads --dependencies py-numpy git
|
||||||
# bzip2@1.0.6%gcc@4.9.3=linux-x86_64
|
# bzip2@1.0.6%gcc@4.9.3=linux-x86_64
|
||||||
module load bzip2/1.0.6-gcc-4.9.3-ktnrhkrmbbtlvnagfatrarzjojmkvzsx
|
module load bzip2-1.0.6-gcc-4.9.3-ktnrhkrmbbtlvnagfatrarzjojmkvzsx
|
||||||
# ncurses@6.0%gcc@4.9.3=linux-x86_64
|
# ncurses@6.0%gcc@4.9.3=linux-x86_64
|
||||||
module load ncurses/6.0-gcc-4.9.3-kaazyneh3bjkfnalunchyqtygoe2mncv
|
module load ncurses-6.0-gcc-4.9.3-kaazyneh3bjkfnalunchyqtygoe2mncv
|
||||||
# zlib@1.2.8%gcc@4.9.3=linux-x86_64
|
# zlib@1.2.8%gcc@4.9.3=linux-x86_64
|
||||||
module load zlib/1.2.8-gcc-4.9.3-v3ufwaahjnviyvgjcelo36nywx2ufj7z
|
module load zlib-1.2.8-gcc-4.9.3-v3ufwaahjnviyvgjcelo36nywx2ufj7z
|
||||||
# sqlite@3.8.5%gcc@4.9.3=linux-x86_64
|
# sqlite@3.8.5%gcc@4.9.3=linux-x86_64
|
||||||
module load sqlite/3.8.5-gcc-4.9.3-a3eediswgd5f3rmto7g3szoew5nhehbr
|
module load sqlite-3.8.5-gcc-4.9.3-a3eediswgd5f3rmto7g3szoew5nhehbr
|
||||||
# readline@6.3%gcc@4.9.3=linux-x86_64
|
# readline@6.3%gcc@4.9.3=linux-x86_64
|
||||||
module load readline/6.3-gcc-4.9.3-se6r3lsycrwxyhreg4lqirp6xixxejh3
|
module load readline-6.3-gcc-4.9.3-se6r3lsycrwxyhreg4lqirp6xixxejh3
|
||||||
# python@3.5.1%gcc@4.9.3=linux-x86_64
|
# python@3.5.1%gcc@4.9.3=linux-x86_64
|
||||||
module load python/3.5.1-gcc-4.9.3-5q5rsrtjld4u6jiicuvtnx52m7tfhegi
|
module load python-3.5.1-gcc-4.9.3-5q5rsrtjld4u6jiicuvtnx52m7tfhegi
|
||||||
# py-setuptools@20.5%gcc@4.9.3=linux-x86_64
|
# py-setuptools@20.5%gcc@4.9.3=linux-x86_64
|
||||||
module load py-setuptools/20.5-gcc-4.9.3-4qr2suj6p6glepnedmwhl4f62x64wxw2
|
module load py-setuptools-20.5-gcc-4.9.3-4qr2suj6p6glepnedmwhl4f62x64wxw2
|
||||||
# py-nose@1.3.7%gcc@4.9.3=linux-x86_64
|
# py-nose@1.3.7%gcc@4.9.3=linux-x86_64
|
||||||
module load py-nose/1.3.7-gcc-4.9.3-pwhtjw2dvdvfzjwuuztkzr7b4l6zepli
|
module load py-nose-1.3.7-gcc-4.9.3-pwhtjw2dvdvfzjwuuztkzr7b4l6zepli
|
||||||
# openblas@0.2.17%gcc@4.9.3+shared=linux-x86_64
|
# openblas@0.2.17%gcc@4.9.3+shared=linux-x86_64
|
||||||
module load openblas/0.2.17-gcc-4.9.3-pw6rmlom7apfsnjtzfttyayzc7nx5e7y
|
module load openblas-0.2.17-gcc-4.9.3-pw6rmlom7apfsnjtzfttyayzc7nx5e7y
|
||||||
# py-numpy@1.11.0%gcc@4.9.3+blas+lapack=linux-x86_64
|
# py-numpy@1.11.0%gcc@4.9.3+blas+lapack=linux-x86_64
|
||||||
module load py-numpy/1.11.0-gcc-4.9.3-mulodttw5pcyjufva4htsktwty4qd52r
|
module load py-numpy-1.11.0-gcc-4.9.3-mulodttw5pcyjufva4htsktwty4qd52r
|
||||||
# curl@7.47.1%gcc@4.9.3=linux-x86_64
|
# curl@7.47.1%gcc@4.9.3=linux-x86_64
|
||||||
module load curl/7.47.1-gcc-4.9.3-ohz3fwsepm3b462p5lnaquv7op7naqbi
|
module load curl-7.47.1-gcc-4.9.3-ohz3fwsepm3b462p5lnaquv7op7naqbi
|
||||||
# autoconf@2.69%gcc@4.9.3=linux-x86_64
|
# autoconf@2.69%gcc@4.9.3=linux-x86_64
|
||||||
module load autoconf/2.69-gcc-4.9.3-bkibjqhgqm5e3o423ogfv2y3o6h2uoq4
|
module load autoconf-2.69-gcc-4.9.3-bkibjqhgqm5e3o423ogfv2y3o6h2uoq4
|
||||||
# cmake@3.5.0%gcc@4.9.3~doc+ncurses+openssl~qt=linux-x86_64
|
# cmake@3.5.0%gcc@4.9.3~doc+ncurses+openssl~qt=linux-x86_64
|
||||||
module load cmake/3.5.0-gcc-4.9.3-x7xnsklmgwla3ubfgzppamtbqk5rwn7t
|
module load cmake-3.5.0-gcc-4.9.3-x7xnsklmgwla3ubfgzppamtbqk5rwn7t
|
||||||
# expat@2.1.0%gcc@4.9.3=linux-x86_64
|
# expat@2.1.0%gcc@4.9.3=linux-x86_64
|
||||||
module load expat/2.1.0-gcc-4.9.3-6pkz2ucnk2e62imwakejjvbv6egncppd
|
module load expat-2.1.0-gcc-4.9.3-6pkz2ucnk2e62imwakejjvbv6egncppd
|
||||||
# git@2.8.0-rc2%gcc@4.9.3+curl+expat=linux-x86_64
|
# git@2.8.0-rc2%gcc@4.9.3+curl+expat=linux-x86_64
|
||||||
module load git/2.8.0-rc2-gcc-4.9.3-3bib4hqtnv5xjjoq5ugt3inblt4xrgkd
|
module load git-2.8.0-rc2-gcc-4.9.3-3bib4hqtnv5xjjoq5ugt3inblt4xrgkd
|
||||||
|
|
||||||
The script may be further edited by removing unnecessary modules.
|
The script may be further edited by removing unnecessary modules.
|
||||||
|
|
||||||
@@ -832,12 +826,12 @@ For example, consider the following on one system:
|
|||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ module avail
|
$ module avail
|
||||||
linux-SuSE11-x86_64/antlr/2.7.7-gcc-5.3.0-bdpl46y
|
linux-SuSE11-x86_64/antlr-2.7.7-gcc-5.3.0-bdpl46y
|
||||||
|
|
||||||
$ spack module tcl loads antlr # WRONG!
|
$ spack module tcl loads antlr # WRONG!
|
||||||
# antlr@2.7.7%gcc@5.3.0~csharp+cxx~java~python arch=linux-SuSE11-x86_64
|
# antlr@2.7.7%gcc@5.3.0~csharp+cxx~java~python arch=linux-SuSE11-x86_64
|
||||||
module load antlr/2.7.7-gcc-5.3.0-bdpl46y
|
module load antlr-2.7.7-gcc-5.3.0-bdpl46y
|
||||||
|
|
||||||
$ spack module tcl loads --prefix linux-SuSE11-x86_64/ antlr
|
$ spack module tcl loads --prefix linux-SuSE11-x86_64/ antlr
|
||||||
# antlr@2.7.7%gcc@5.3.0~csharp+cxx~java~python arch=linux-SuSE11-x86_64
|
# antlr@2.7.7%gcc@5.3.0~csharp+cxx~java~python arch=linux-SuSE11-x86_64
|
||||||
module load linux-SuSE11-x86_64/antlr/2.7.7-gcc-5.3.0-bdpl46y
|
module load linux-SuSE11-x86_64/antlr-2.7.7-gcc-5.3.0-bdpl46y
|
||||||
|
|||||||
17
lib/spack/docs/package_list.rst
Normal file
17
lib/spack/docs/package_list.rst
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.. Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||||
|
Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
.. _package-list:
|
||||||
|
|
||||||
|
============
|
||||||
|
Package List
|
||||||
|
============
|
||||||
|
|
||||||
|
This is a list of things you can install using Spack. It is
|
||||||
|
automatically generated based on the packages in this Spack
|
||||||
|
version.
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: package_list.html
|
||||||
@@ -121,7 +121,7 @@ Since v0.19, Spack supports two ways of writing a package recipe. The most comm
|
|||||||
|
|
||||||
def url_for_version(self, version):
|
def url_for_version(self, version):
|
||||||
if version >= Version("2.1.1"):
|
if version >= Version("2.1.1"):
|
||||||
return super().url_for_version(version)
|
return super(Openjpeg, self).url_for_version(version)
|
||||||
url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz"
|
url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz"
|
||||||
return url_fmt.format(version)
|
return url_fmt.format(version)
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ builder class explicitly. Using the same example as above, this reads:
|
|||||||
|
|
||||||
def url_for_version(self, version):
|
def url_for_version(self, version):
|
||||||
if version >= Version("2.1.1"):
|
if version >= Version("2.1.1"):
|
||||||
return super().url_for_version(version)
|
return super(Openjpeg, self).url_for_version(version)
|
||||||
url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz"
|
url_fmt = "https://github.com/uclouvain/openjpeg/archive/version.{0}.tar.gz"
|
||||||
return url_fmt.format(version)
|
return url_fmt.format(version)
|
||||||
|
|
||||||
@@ -363,42 +363,6 @@ one of these::
|
|||||||
If Spack finds none of these variables set, it will look for ``vim``, ``vi``, ``emacs``,
|
If Spack finds none of these variables set, it will look for ``vim``, ``vi``, ``emacs``,
|
||||||
``nano``, and ``notepad``, in that order.
|
``nano``, and ``notepad``, in that order.
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^
|
|
||||||
Bundling software
|
|
||||||
^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
If you have a collection of software expected to work well together with
|
|
||||||
no source code of its own, you can create a :ref:`BundlePackage <bundlepackage>`.
|
|
||||||
Examples where bundle packages can be useful include defining suites of
|
|
||||||
applications (e.g, `EcpProxyApps
|
|
||||||
<https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/ecp-proxy-apps/package.py>`_), commonly used libraries
|
|
||||||
(e.g., `AmdAocl <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/amd-aocl/package.py>`_),
|
|
||||||
and software development kits (e.g., `EcpDataVisSdk <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/ecp-data-vis-sdk/package.py>`_).
|
|
||||||
|
|
||||||
These versioned packages primarily consist of dependencies on the associated
|
|
||||||
software packages. They can include :ref:`variants <variants>` to ensure
|
|
||||||
common build options are consistently applied to dependencies. Known build
|
|
||||||
failures, such as not building on a platform or when certain compilers or
|
|
||||||
variants are used, can be flagged with :ref:`conflicts <packaging_conflicts>`.
|
|
||||||
Build requirements, such as only building with specific compilers, can similarly
|
|
||||||
be flagged with :ref:`requires <packaging_conflicts>`.
|
|
||||||
|
|
||||||
The ``spack create --template bundle`` command will create a skeleton
|
|
||||||
``BundlePackage`` ``package.py`` for you:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ spack create --template bundle --name coolsdk
|
|
||||||
|
|
||||||
Now you can fill in the basic package documentation, version(s), and software
|
|
||||||
package dependencies along with any other relevant customizations.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Remember that bundle packages have no software of their own so there
|
|
||||||
is nothing to download.
|
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Non-downloadable software
|
Non-downloadable software
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -646,16 +610,7 @@ add a line like this in the package class:
|
|||||||
version("8.2.0", md5="1c9f62f0778697a09d36121ead88e08e")
|
version("8.2.0", md5="1c9f62f0778697a09d36121ead88e08e")
|
||||||
version("8.1.2", md5="d47dd09ed7ae6e7fd6f9a816d7f5fdf6")
|
version("8.1.2", md5="d47dd09ed7ae6e7fd6f9a816d7f5fdf6")
|
||||||
|
|
||||||
.. note::
|
Versions should be listed in descending order, from newest to oldest.
|
||||||
|
|
||||||
By convention, we list versions in descending order, from newest to oldest.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
:ref:`Bundle packages <bundlepackage>` do not have source code so
|
|
||||||
there is nothing to fetch. Consequently, their version directives
|
|
||||||
consist solely of the version name (e.g., ``version("202309")``).
|
|
||||||
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
Date Versions
|
Date Versions
|
||||||
@@ -1549,7 +1504,7 @@ its value:
|
|||||||
|
|
||||||
def configure_args(self):
|
def configure_args(self):
|
||||||
...
|
...
|
||||||
if self.spec.satisfies("+shared"):
|
if "+shared" in self.spec:
|
||||||
extra_args.append("--enable-shared")
|
extra_args.append("--enable-shared")
|
||||||
else:
|
else:
|
||||||
extra_args.append("--disable-shared")
|
extra_args.append("--disable-shared")
|
||||||
@@ -1636,7 +1591,7 @@ Within a package recipe a multi-valued variant is tested using a ``key=value`` s
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if spec.satisfies("languages=jit"):
|
if "languages=jit" in spec:
|
||||||
options.append("--enable-host-shared")
|
options.append("--enable-host-shared")
|
||||||
|
|
||||||
"""""""""""""""""""""""""""""""""""""""""""
|
"""""""""""""""""""""""""""""""""""""""""""
|
||||||
@@ -2288,7 +2243,7 @@ looks like this:
|
|||||||
url = "http://www.openssl.org/source/openssl-1.0.1h.tar.gz"
|
url = "http://www.openssl.org/source/openssl-1.0.1h.tar.gz"
|
||||||
|
|
||||||
version("1.0.1h", md5="8d6d684a9430d5cc98a62a5d8fbda8cf")
|
version("1.0.1h", md5="8d6d684a9430d5cc98a62a5d8fbda8cf")
|
||||||
depends_on("zlib-api")
|
depends_on("zlib")
|
||||||
|
|
||||||
parallel = False
|
parallel = False
|
||||||
|
|
||||||
@@ -2557,10 +2512,9 @@ Conditional dependencies
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
You may have a package that only requires a dependency under certain
|
You may have a package that only requires a dependency under certain
|
||||||
conditions. For example, you may have a package with optional MPI support.
|
conditions. For example, you may have a package that has optional MPI support,
|
||||||
You would then provide a variant to reflect that the feature is optional
|
- MPI is only a dependency when you want to enable MPI support for the
|
||||||
and specify the MPI dependency only applies when MPI support is enabled.
|
package. In that case, you could say something like:
|
||||||
In that case, you could say something like:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -2568,39 +2522,13 @@ In that case, you could say something like:
|
|||||||
|
|
||||||
depends_on("mpi", when="+mpi")
|
depends_on("mpi", when="+mpi")
|
||||||
|
|
||||||
|
``when`` can include constraints on the variant, version, compiler, etc. and
|
||||||
|
the :mod:`syntax<spack.spec>` is the same as for Specs written on the command
|
||||||
|
line.
|
||||||
|
|
||||||
Suppose the above package also has, since version 3, optional `Trilinos`
|
If a dependency/feature of a package isn't typically used, you can save time
|
||||||
support and you want them both to build either with or without MPI. Further
|
by making it conditional (since Spack will not build the dependency unless it
|
||||||
suppose you require a version of `Trilinos` no older than 12.6. In that case,
|
is required for the Spec).
|
||||||
the `trilinos` variant and dependency directives would be:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
variant("trilinos", default=False, description="Enable Trilinos support")
|
|
||||||
|
|
||||||
depends_on("trilinos@12.6:", when="@3: +trilinos")
|
|
||||||
depends_on("trilinos@12.6: +mpi", when="@3: +trilinos +mpi")
|
|
||||||
|
|
||||||
|
|
||||||
Alternatively, you could use the `when` context manager to equivalently specify
|
|
||||||
the `trilinos` variant dependencies as follows:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
with when("@3: +trilinos"):
|
|
||||||
depends_on("trilinos@12.6:")
|
|
||||||
depends_on("trilinos +mpi", when="+mpi")
|
|
||||||
|
|
||||||
|
|
||||||
The argument to ``when`` in either case can include any Spec constraints that
|
|
||||||
are supported on the command line using the same :ref:`syntax <sec-specs>`.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If a dependency isn't typically used, you can save time by making it
|
|
||||||
conditional since Spack will not build the dependency unless it is
|
|
||||||
required for the Spec.
|
|
||||||
|
|
||||||
|
|
||||||
.. _dependency_dependency_patching:
|
.. _dependency_dependency_patching:
|
||||||
|
|
||||||
@@ -2688,6 +2616,60 @@ appear in the package file (or in this case, in the list).
|
|||||||
right version. If two packages depend on ``binutils`` patched *the
|
right version. If two packages depend on ``binutils`` patched *the
|
||||||
same* way, they can both use a single installation of ``binutils``.
|
same* way, they can both use a single installation of ``binutils``.
|
||||||
|
|
||||||
|
.. _setup-dependent-environment:
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Influence how dependents are built or run
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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.builder.Builder.setup_dependent_build_environment>`
|
||||||
|
methods.
|
||||||
|
The Qt package, for instance, uses this call:
|
||||||
|
|
||||||
|
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/qt/package.py
|
||||||
|
:pyobject: Qt.setup_dependent_build_environment
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
to set the ``QTDIR`` environment variable so that packages
|
||||||
|
that depend on a particular Qt installation will find it.
|
||||||
|
Another good example of how a dependency can influence
|
||||||
|
the build environment of dependents is the Python package:
|
||||||
|
|
||||||
|
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/python/package.py
|
||||||
|
:pyobject: Python.setup_dependent_build_environment
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
In the method above it is ensured that any package that depends on Python
|
||||||
|
will have the ``PYTHONPATH``, ``PYTHONHOME`` and ``PATH`` environment
|
||||||
|
variables set appropriately before starting the installation. To make things
|
||||||
|
even simpler the ``python setup.py`` command is also inserted into the module
|
||||||
|
scope of dependents by overriding a third method called
|
||||||
|
:meth:`setup_dependent_package <spack.package_base.PackageBase.setup_dependent_package>`
|
||||||
|
:
|
||||||
|
|
||||||
|
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/python/package.py
|
||||||
|
:pyobject: Python.setup_dependent_package
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
This allows most python packages to have a very simple install procedure,
|
||||||
|
like the following:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def install(self, spec, prefix):
|
||||||
|
setup_py("install", "--prefix={0}".format(prefix))
|
||||||
|
|
||||||
|
Finally the Python package takes also care of the modifications to ``PYTHONPATH``
|
||||||
|
to allow dependencies to run correctly:
|
||||||
|
|
||||||
|
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/python/package.py
|
||||||
|
:pyobject: Python.setup_dependent_run_environment
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
|
||||||
.. _packaging_conflicts:
|
.. _packaging_conflicts:
|
||||||
|
|
||||||
@@ -2696,7 +2678,7 @@ Conflicts and requirements
|
|||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Sometimes packages have known bugs, or limitations, that would prevent them
|
Sometimes packages have known bugs, or limitations, that would prevent them
|
||||||
from building e.g. against other dependencies or with certain compilers. Spack
|
to build e.g. against other dependencies or with certain compilers. Spack
|
||||||
makes it possible to express such constraints with the ``conflicts`` directive.
|
makes it possible to express such constraints with the ``conflicts`` directive.
|
||||||
|
|
||||||
Adding the following to a package:
|
Adding the following to a package:
|
||||||
@@ -2832,70 +2814,6 @@ variant(s) are selected. This may be accomplished with conditional
|
|||||||
extends("python", when="+python")
|
extends("python", when="+python")
|
||||||
...
|
...
|
||||||
|
|
||||||
.. _setup-environment:
|
|
||||||
|
|
||||||
--------------------------------------------
|
|
||||||
Runtime and build time environment variables
|
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
Spack provides a few methods to help package authors set up the required environment variables for
|
|
||||||
their package. Environment variables typically depend on how the package is used: variables that
|
|
||||||
make sense during the build phase may not be needed at runtime, and vice versa. Further, sometimes
|
|
||||||
it makes sense to let a dependency set the environment variables for its dependents. To allow all
|
|
||||||
this, Spack provides four different methods that can be overridden in a package:
|
|
||||||
|
|
||||||
1. :meth:`setup_build_environment <spack.builder.Builder.setup_build_environment>`
|
|
||||||
2. :meth:`setup_run_environment <spack.package_base.PackageBase.setup_run_environment>`
|
|
||||||
3. :meth:`setup_dependent_build_environment <spack.builder.Builder.setup_dependent_build_environment>`
|
|
||||||
4. :meth:`setup_dependent_run_environment <spack.package_base.PackageBase.setup_dependent_run_environment>`
|
|
||||||
|
|
||||||
The Qt package, for instance, uses this call:
|
|
||||||
|
|
||||||
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/qt/package.py
|
|
||||||
:pyobject: Qt.setup_dependent_build_environment
|
|
||||||
:linenos:
|
|
||||||
|
|
||||||
to set the ``QTDIR`` environment variable so that packages that depend on a particular Qt
|
|
||||||
installation will find it.
|
|
||||||
|
|
||||||
The following diagram will give you an idea when each of these methods is called in a build
|
|
||||||
context:
|
|
||||||
|
|
||||||
.. image:: images/setup_env.png
|
|
||||||
:align: center
|
|
||||||
|
|
||||||
Notice that ``setup_dependent_run_environment`` can be called multiple times, once for each
|
|
||||||
dependent package, whereas ``setup_run_environment`` is called only once for the package itself.
|
|
||||||
This means that the former should only be used if the environment variables depend on the dependent
|
|
||||||
package, whereas the latter should be used if the environment variables depend only on the package
|
|
||||||
itself.
|
|
||||||
|
|
||||||
--------------------------------
|
|
||||||
Setting package module variables
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Apart from modifying environment variables of the dependent package, you can also define Python
|
|
||||||
variables to be used by the dependent. This is done by implementing
|
|
||||||
:meth:`setup_dependent_package <spack.package_base.PackageBase.setup_dependent_package>`. An
|
|
||||||
example of this can be found in the ``Python`` package:
|
|
||||||
|
|
||||||
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/python/package.py
|
|
||||||
:pyobject: Python.setup_dependent_package
|
|
||||||
:linenos:
|
|
||||||
|
|
||||||
This allows Python packages to directly use these variables:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def install(self, spec, prefix):
|
|
||||||
...
|
|
||||||
install("script.py", python_platlib)
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
We recommend using ``setup_dependent_package`` sparingly, as it is not always clear where
|
|
||||||
global variables are coming from when editing a ``package.py`` file.
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
Views
|
Views
|
||||||
-----
|
-----
|
||||||
@@ -3153,7 +3071,7 @@ follows:
|
|||||||
# The library provided by the bar virtual package
|
# The library provided by the bar virtual package
|
||||||
@property
|
@property
|
||||||
def bar_libs(self):
|
def bar_libs(self):
|
||||||
return find_libraries("libFooBar", root=self.home, recursive=True)
|
return find_libraries("libFooBar", root=sef.home, recursive=True)
|
||||||
|
|
||||||
# The baz virtual package home
|
# The baz virtual package home
|
||||||
@property
|
@property
|
||||||
@@ -3538,7 +3456,7 @@ need to override methods like ``configure_args``:
|
|||||||
|
|
||||||
def configure_args(self):
|
def configure_args(self):
|
||||||
args = ["--enable-cxx"] + self.enable_or_disable("libs")
|
args = ["--enable-cxx"] + self.enable_or_disable("libs")
|
||||||
if self.spec.satisfies("libs=static"):
|
if "libs=static" in self.spec:
|
||||||
args.append("--with-pic")
|
args.append("--with-pic")
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@@ -3672,8 +3590,7 @@ regardless of the build system. The arguments for the phase are:
|
|||||||
The arguments ``spec`` and ``prefix`` are passed only for convenience, as they always
|
The arguments ``spec`` and ``prefix`` are passed only for convenience, as they always
|
||||||
correspond to ``self.spec`` and ``self.spec.prefix`` respectively.
|
correspond to ``self.spec`` and ``self.spec.prefix`` respectively.
|
||||||
|
|
||||||
If the ``package.py`` has build instructions in a separate
|
If the ``package.py`` encodes builders explicitly, the signature for a phase changes slightly:
|
||||||
:ref:`builder class <multiple_build_systems>`, the signature for a phase changes slightly:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -3683,6 +3600,56 @@ If the ``package.py`` has build instructions in a separate
|
|||||||
|
|
||||||
In this case the package is passed as the second argument, and ``self`` is the builder instance.
|
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
|
Mixin base classes
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
@@ -3729,106 +3696,6 @@ for instance:
|
|||||||
|
|
||||||
In the example above ``Cp2k`` inherits all the conflicts and variants that ``CudaPackage`` defines.
|
In the example above ``Cp2k`` inherits all the conflicts and variants that ``CudaPackage`` defines.
|
||||||
|
|
||||||
.. _multiple_build_systems:
|
|
||||||
|
|
||||||
----------------------
|
|
||||||
Multiple build systems
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
There are cases where a package 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 by splitting the build instructions into separate builder classes.
|
|
||||||
|
|
||||||
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 Example(CMakePackage, AutotoolsPackage):
|
|
||||||
|
|
||||||
variant("my_feature", default=True)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Additional build instructions are split into separate builder classes:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class CMakeBuilder(spack.build_systems.cmake.CMakeBuilder):
|
|
||||||
def cmake_args(self):
|
|
||||||
return [
|
|
||||||
self.define_from_variant("MY_FEATURE", "my_feature")
|
|
||||||
]
|
|
||||||
|
|
||||||
class AutotoolsBuilder(spack.build_systems.autotools.AutotoolsBuilder):
|
|
||||||
def configure_args(self):
|
|
||||||
return self.with_or_without("my-feature", variant="my_feature")
|
|
||||||
|
|
||||||
In this example, ``spack install example +feature build_sytem=cmake`` will
|
|
||||||
pick the ``CMakeBuilder`` and invoke ``cmake -DMY_FEATURE:BOOL=ON``.
|
|
||||||
|
|
||||||
Similarly, ``spack install example +feature build_system=autotools`` will pick
|
|
||||||
the ``AutotoolsBuilder`` and invoke ``./configure --with-my-feature``.
|
|
||||||
|
|
||||||
Dependencies are always specified in the package class. When some dependencies
|
|
||||||
depend on the choice of the build system, it is possible to use when conditions as
|
|
||||||
usual:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class Example(CMakePackage, AutotoolsPackage):
|
|
||||||
|
|
||||||
build_system("cmake", "autotools", default="cmake")
|
|
||||||
|
|
||||||
# Runtime dependencies
|
|
||||||
depends_on("ncurses")
|
|
||||||
depends_on("libxml2")
|
|
||||||
|
|
||||||
# Lowerbounds for cmake only apply when using cmake as the build system
|
|
||||||
with when("build_system=cmake"):
|
|
||||||
depends_on("cmake@3.18:", when="@2.0:", type="build")
|
|
||||||
depends_on("cmake@3:", type="build")
|
|
||||||
|
|
||||||
# Specify extra build dependencies used only in the configure script
|
|
||||||
with when("build_system=autotools"):
|
|
||||||
depends_on("perl", type="build")
|
|
||||||
depends_on("pkgconfig", type="build")
|
|
||||||
|
|
||||||
Very often projects switch from one build system to another, or add support
|
|
||||||
for a new build system from a certain version, which means that the choice
|
|
||||||
of the build system typically depends on a version range. Those situations can
|
|
||||||
be handled by using conditional values in the ``build_system`` directive:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class Example(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``.
|
|
||||||
|
|
||||||
The ``build_system`` can be used as an ordinary variant, which also means that it can
|
|
||||||
be used in ``depends_on`` statements. This can be useful when a package *requires* that
|
|
||||||
its dependency has a CMake config file, meaning that the dependent can only build when the
|
|
||||||
dependency is built with CMake, and not Autotools. In that case, you can force the choice
|
|
||||||
of the build system in the dependent:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class Dependent(CMakePackage):
|
|
||||||
|
|
||||||
depends_on("example build_system=cmake")
|
|
||||||
|
|
||||||
|
|
||||||
.. _install-environment:
|
.. _install-environment:
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -4401,7 +4268,7 @@ for supported features, for instance:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
if spec.satisfies("target=avx512"):
|
if "avx512" in spec.target:
|
||||||
args.append("--with-avx512")
|
args.append("--with-avx512")
|
||||||
|
|
||||||
The snippet above will append the ``--with-avx512`` item to a list of arguments only if the corresponding
|
The snippet above will append the ``--with-avx512`` item to a list of arguments only if the corresponding
|
||||||
@@ -4906,17 +4773,17 @@ For example, running:
|
|||||||
|
|
||||||
results in spack checking that the installation created the following **file**:
|
results in spack checking that the installation created the following **file**:
|
||||||
|
|
||||||
* ``self.prefix.bin.reframe``
|
* ``self.prefix/bin/reframe``
|
||||||
|
|
||||||
and the following **directories**:
|
and the following **directories**:
|
||||||
|
|
||||||
* ``self.prefix.bin``
|
* ``self.prefix/bin``
|
||||||
* ``self.prefix.config``
|
* ``self.prefix/config``
|
||||||
* ``self.prefix.docs``
|
* ``self.prefix/docs``
|
||||||
* ``self.prefix.reframe``
|
* ``self.prefix/reframe``
|
||||||
* ``self.prefix.tutorials``
|
* ``self.prefix/tutorials``
|
||||||
* ``self.prefix.unittests``
|
* ``self.prefix/unittests``
|
||||||
* ``self.prefix.cscs-checks``
|
* ``self.prefix/cscs-checks``
|
||||||
|
|
||||||
If **any** of these paths are missing, then Spack considers the installation
|
If **any** of these paths are missing, then Spack considers the installation
|
||||||
to have failed.
|
to have failed.
|
||||||
@@ -5060,7 +4927,7 @@ installed executable. The check is implemented as follows:
|
|||||||
@on_package_attributes(run_tests=True)
|
@on_package_attributes(run_tests=True)
|
||||||
def check_list(self):
|
def check_list(self):
|
||||||
with working_dir(self.stage.source_path):
|
with working_dir(self.stage.source_path):
|
||||||
reframe = Executable(self.prefix.bin.reframe)
|
reframe = Executable(join_path(self.prefix, "bin", "reframe"))
|
||||||
reframe("-l")
|
reframe("-l")
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
@@ -5280,8 +5147,8 @@ embedded test parts.
|
|||||||
for example in ["ex1", "ex2"]:
|
for example in ["ex1", "ex2"]:
|
||||||
with test_part(
|
with test_part(
|
||||||
self,
|
self,
|
||||||
f"test_example_{example}",
|
"test_example_{0}".format(example),
|
||||||
purpose=f"run installed {example}",
|
purpose="run installed {0}".format(example),
|
||||||
):
|
):
|
||||||
exe = which(join_path(self.prefix.bin, example))
|
exe = which(join_path(self.prefix.bin, example))
|
||||||
exe()
|
exe()
|
||||||
@@ -5359,10 +5226,11 @@ Below illustrates using this feature to compile an example.
|
|||||||
...
|
...
|
||||||
cxx = which(os.environ["CXX"])
|
cxx = which(os.environ["CXX"])
|
||||||
cxx(
|
cxx(
|
||||||
f"-L{self.prefix.lib}",
|
"-L{0}".format(self.prefix.lib),
|
||||||
f"-I{self.prefix.include}",
|
"-I{0}".format(self.prefix.include),
|
||||||
f"{exe}.cpp",
|
"{0}.cpp".format(exe),
|
||||||
"-o", exe
|
"-o",
|
||||||
|
exe
|
||||||
)
|
)
|
||||||
cxx_example = which(exe)
|
cxx_example = which(exe)
|
||||||
cxx_example()
|
cxx_example()
|
||||||
@@ -5379,14 +5247,14 @@ Saving build-time files
|
|||||||
We highly recommend re-using build-time test sources and pared down
|
We highly recommend re-using build-time test sources and pared down
|
||||||
input files for testing installed software. These files are easier
|
input files for testing installed software. These files are easier
|
||||||
to keep synchronized with software capabilities since they reside
|
to keep synchronized with software capabilities since they reside
|
||||||
within the software's repository.
|
within the software's repository.
|
||||||
|
|
||||||
If that is not possible, you can add test-related files to the package
|
If that is not possible, you can add test-related files to the package
|
||||||
repository (see :ref:`adding custom files <cache_custom_files>`). It
|
repository (see :ref:`adding custom files <cache_custom_files>`). It
|
||||||
will be important to maintain them so they work across listed or supported
|
will be important to maintain them so they work across listed or supported
|
||||||
versions of the package.
|
versions of the package.
|
||||||
|
|
||||||
You can use the ``cache_extra_test_sources`` helper to copy directories
|
You can use the ``cache_extra_test_sources`` method to copy directories
|
||||||
and or files from the source build stage directory to the package's
|
and or files from the source build stage directory to the package's
|
||||||
installation directory.
|
installation directory.
|
||||||
|
|
||||||
@@ -5394,15 +5262,10 @@ The signature for ``cache_extra_test_sources`` is:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def cache_extra_test_sources(pkg, srcs):
|
def cache_extra_test_sources(self, srcs):
|
||||||
|
|
||||||
where each argument has the following meaning:
|
|
||||||
|
|
||||||
* ``pkg`` is an instance of the package for the spec under test.
|
|
||||||
|
|
||||||
* ``srcs`` is a string *or* a list of strings corresponding to the
|
|
||||||
paths of subdirectories and or files needed for stand-alone testing.
|
|
||||||
|
|
||||||
|
where ``srcs`` is a string *or* a list of strings corresponding to the
|
||||||
|
paths of subdirectories and or files needed for stand-alone testing.
|
||||||
The paths must be relative to the staged source directory. Contents of
|
The paths must be relative to the staged source directory. Contents of
|
||||||
subdirectories and files are copied to a special test cache subdirectory
|
subdirectories and files are copied to a special test cache subdirectory
|
||||||
of the installation prefix. They are automatically copied to the appropriate
|
of the installation prefix. They are automatically copied to the appropriate
|
||||||
@@ -5423,18 +5286,21 @@ and using ``foo.c`` in a test method is illustrated below.
|
|||||||
srcs = ["tests",
|
srcs = ["tests",
|
||||||
join_path("examples", "foo.c"),
|
join_path("examples", "foo.c"),
|
||||||
join_path("examples", "bar.c")]
|
join_path("examples", "bar.c")]
|
||||||
cache_extra_test_sources(self, srcs)
|
self.cache_extra_test_sources(srcs)
|
||||||
|
|
||||||
def test_foo(self):
|
def test_foo(self):
|
||||||
exe = "foo"
|
exe = "foo"
|
||||||
src_dir = self.test_suite.current_test_cache_dir.examples
|
src_dir = join_path(
|
||||||
|
self.test_suite.current_test_cache_dir, "examples"
|
||||||
|
)
|
||||||
with working_dir(src_dir):
|
with working_dir(src_dir):
|
||||||
cc = which(os.environ["CC"])
|
cc = which(os.environ["CC"])
|
||||||
cc(
|
cc(
|
||||||
f"-L{self.prefix.lib}",
|
"-L{0}".format(self.prefix.lib),
|
||||||
f"-I{self.prefix.include}",
|
"-I{0}".format(self.prefix.include),
|
||||||
f"{exe}.c",
|
"{0}.c".format(exe),
|
||||||
"-o", exe
|
"-o",
|
||||||
|
exe
|
||||||
)
|
)
|
||||||
foo = which(exe)
|
foo = which(exe)
|
||||||
foo()
|
foo()
|
||||||
@@ -5460,9 +5326,9 @@ the files using the ``self.test_suite.current_test_cache_dir`` property.
|
|||||||
In our example above, test methods can use the following paths to reference
|
In our example above, test methods can use the following paths to reference
|
||||||
the copy of each entry listed in ``srcs``, respectively:
|
the copy of each entry listed in ``srcs``, respectively:
|
||||||
|
|
||||||
* ``self.test_suite.current_test_cache_dir.tests``
|
* ``join_path(self.test_suite.current_test_cache_dir, "tests")``
|
||||||
* ``join_path(self.test_suite.current_test_cache_dir.examples, "foo.c")``
|
* ``join_path(self.test_suite.current_test_cache_dir, "examples", "foo.c")``
|
||||||
* ``join_path(self.test_suite.current_test_cache_dir.examples, "bar.c")``
|
* ``join_path(self.test_suite.current_test_cache_dir, "examples", "bar.c")``
|
||||||
|
|
||||||
.. admonition:: Library packages should build stand-alone tests
|
.. admonition:: Library packages should build stand-alone tests
|
||||||
|
|
||||||
@@ -5481,7 +5347,7 @@ the copy of each entry listed in ``srcs``, respectively:
|
|||||||
If one or more of the copied files needs to be modified to reference
|
If one or more of the copied files needs to be modified to reference
|
||||||
the installed software, it is recommended that those changes be made
|
the installed software, it is recommended that those changes be made
|
||||||
to the cached files **once** in the ``copy_test_sources`` method and
|
to the cached files **once** in the ``copy_test_sources`` method and
|
||||||
***after** the call to ``cache_extra_test_sources()``. This will
|
***after** the call to ``self.cache_extra_test_sources()``. This will
|
||||||
reduce the amount of unnecessary work in the test method **and** avoid
|
reduce the amount of unnecessary work in the test method **and** avoid
|
||||||
problems testing in shared instances and facility deployments.
|
problems testing in shared instances and facility deployments.
|
||||||
|
|
||||||
@@ -5528,7 +5394,7 @@ property as shown below.
|
|||||||
"""build and run custom-example"""
|
"""build and run custom-example"""
|
||||||
data_dir = self.test_suite.current_test_data_dir
|
data_dir = self.test_suite.current_test_data_dir
|
||||||
exe = "custom-example"
|
exe = "custom-example"
|
||||||
src = datadir.join(f"{exe}.cpp")
|
src = datadir.join("{0}.cpp".format(exe))
|
||||||
...
|
...
|
||||||
# TODO: Build custom-example using src and exe
|
# TODO: Build custom-example using src and exe
|
||||||
...
|
...
|
||||||
@@ -5544,7 +5410,7 @@ Reading expected output from a file
|
|||||||
|
|
||||||
The helper function ``get_escaped_text_output`` is available for packages
|
The helper function ``get_escaped_text_output`` is available for packages
|
||||||
to retrieve and properly format the text from a file that contains the
|
to retrieve and properly format the text from a file that contains the
|
||||||
expected output from running an executable that may contain special
|
expected output from running an executable that may contain special
|
||||||
characters.
|
characters.
|
||||||
|
|
||||||
The signature for ``get_escaped_text_output`` is:
|
The signature for ``get_escaped_text_output`` is:
|
||||||
@@ -5578,7 +5444,7 @@ added to the package's ``test`` subdirectory.
|
|||||||
db_filename, ".dump", output=str.split, error=str.split
|
db_filename, ".dump", output=str.split, error=str.split
|
||||||
)
|
)
|
||||||
for exp in expected:
|
for exp in expected:
|
||||||
assert re.search(exp, out), f"Expected '{exp}' in output"
|
assert re.search(exp, out), "Expected '{0}' in output".format(exp)
|
||||||
|
|
||||||
If the file was instead copied from the ``tests`` subdirectory of the staged
|
If the file was instead copied from the ``tests`` subdirectory of the staged
|
||||||
source code, the path would be obtained as shown below.
|
source code, the path would be obtained as shown below.
|
||||||
@@ -5591,7 +5457,7 @@ source code, the path would be obtained as shown below.
|
|||||||
db_filename = test_cache_dir.join("packages.db")
|
db_filename = test_cache_dir.join("packages.db")
|
||||||
|
|
||||||
Alternatively, if the file was copied to the ``share/tests`` subdirectory
|
Alternatively, if the file was copied to the ``share/tests`` subdirectory
|
||||||
as part of the installation process, the test could access the path as
|
as part of the installation process, the test could access the path as
|
||||||
follows:
|
follows:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
@@ -5628,12 +5494,9 @@ Invoking the method is the equivalent of:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
errors = []
|
|
||||||
for check in expected:
|
for check in expected:
|
||||||
if not re.search(check, actual):
|
if not re.search(check, actual):
|
||||||
errors.append(f"Expected '{check}' in output '{actual}'")
|
raise RuntimeError("Expected '{0}' in output '{1}'".format(check, actual))
|
||||||
if errors:
|
|
||||||
raise RuntimeError("\n ".join(errors))
|
|
||||||
|
|
||||||
|
|
||||||
.. _accessing-files:
|
.. _accessing-files:
|
||||||
@@ -5673,7 +5536,7 @@ repository, and installation.
|
|||||||
- ``self.test_suite.test_dir_for_spec(self.spec)``
|
- ``self.test_suite.test_dir_for_spec(self.spec)``
|
||||||
* - Current Spec's Build-time Files
|
* - Current Spec's Build-time Files
|
||||||
- ``self.test_suite.current_test_cache_dir``
|
- ``self.test_suite.current_test_cache_dir``
|
||||||
- ``join_path(self.test_suite.current_test_cache_dir.examples, "foo.c")``
|
- ``join_path(self.test_suite.current_test_cache_dir, "examples", "foo.c")``
|
||||||
* - Current Spec's Custom Test Files
|
* - Current Spec's Custom Test Files
|
||||||
- ``self.test_suite.current_test_data_dir``
|
- ``self.test_suite.current_test_data_dir``
|
||||||
- ``join_path(self.test_suite.current_test_data_dir, "hello.f90")``
|
- ``join_path(self.test_suite.current_test_data_dir, "hello.f90")``
|
||||||
@@ -5688,7 +5551,7 @@ Inheriting stand-alone tests
|
|||||||
Stand-alone tests defined in parent (.e.g., :ref:`build-systems`) and
|
Stand-alone tests defined in parent (.e.g., :ref:`build-systems`) and
|
||||||
virtual (e.g., :ref:`virtual-dependencies`) packages are executed by
|
virtual (e.g., :ref:`virtual-dependencies`) packages are executed by
|
||||||
packages that inherit from or provide interface implementations for those
|
packages that inherit from or provide interface implementations for those
|
||||||
packages, respectively.
|
packages, respectively.
|
||||||
|
|
||||||
The table below summarizes the stand-alone tests that will be executed along
|
The table below summarizes the stand-alone tests that will be executed along
|
||||||
with those implemented in the package itself.
|
with those implemented in the package itself.
|
||||||
@@ -5758,7 +5621,7 @@ for ``openmpi``:
|
|||||||
SKIPPED: test_version_oshcc: oshcc is not installed
|
SKIPPED: test_version_oshcc: oshcc is not installed
|
||||||
...
|
...
|
||||||
==> [2023-03-10-16:04:02.215227] Completed testing
|
==> [2023-03-10-16:04:02.215227] Completed testing
|
||||||
==> [2023-03-10-16:04:02.215597]
|
==> [2023-03-10-16:04:02.215597]
|
||||||
======================== SUMMARY: openmpi-4.1.4-ubmrigj ========================
|
======================== SUMMARY: openmpi-4.1.4-ubmrigj ========================
|
||||||
Openmpi::test_bin_mpirun .. PASSED
|
Openmpi::test_bin_mpirun .. PASSED
|
||||||
Openmpi::test_bin_ompi_info .. PASSED
|
Openmpi::test_bin_ompi_info .. PASSED
|
||||||
@@ -6208,7 +6071,7 @@ in the extra attributes can implement this method like this:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def validate_detected_spec(cls, spec, extra_attributes):
|
def validate_detected_spec(cls, spec, extra_attributes):
|
||||||
"""Check that "compilers" is in the extra attributes."""
|
"""Check that "compilers" is in the extra attributes."""
|
||||||
msg = ("the extra attribute 'compilers' must be set for "
|
msg = ("the extra attribute "compilers" must be set for "
|
||||||
"the detected spec '{0}'".format(spec))
|
"the detected spec '{0}'".format(spec))
|
||||||
assert "compilers" in extra_attributes, msg
|
assert "compilers" in extra_attributes, msg
|
||||||
|
|
||||||
@@ -6284,100 +6147,7 @@ follows:
|
|||||||
"foo-package@{0}".format(version_str)
|
"foo-package@{0}".format(version_str)
|
||||||
)
|
)
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
.. _package-lifecycle:
|
||||||
Add detection tests to packages
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
To ensure that software is detected correctly for multiple configurations
|
|
||||||
and on different systems users can write a ``detection_test.yaml`` file and
|
|
||||||
put it in the package directory alongside the ``package.py`` file.
|
|
||||||
This YAML file contains enough information for Spack to mock an environment
|
|
||||||
and try to check if the detection logic yields the results that are expected.
|
|
||||||
|
|
||||||
As a general rule, attributes at the top-level of ``detection_test.yaml``
|
|
||||||
represent search mechanisms and they each map to a list of tests that should confirm
|
|
||||||
the validity of the package's detection logic.
|
|
||||||
|
|
||||||
The detection tests can be run with the following command:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ spack audit externals
|
|
||||||
|
|
||||||
Errors that have been detected are reported to screen.
|
|
||||||
|
|
||||||
""""""""""""""""""""""""""
|
|
||||||
Tests for PATH inspections
|
|
||||||
""""""""""""""""""""""""""
|
|
||||||
|
|
||||||
Detection tests insisting on ``PATH`` inspections are listed under
|
|
||||||
the ``paths`` attribute:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
paths:
|
|
||||||
- layout:
|
|
||||||
- executables:
|
|
||||||
- "bin/clang-3.9"
|
|
||||||
- "bin/clang++-3.9"
|
|
||||||
script: |
|
|
||||||
echo "clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)"
|
|
||||||
echo "Target: x86_64-pc-linux-gnu"
|
|
||||||
echo "Thread model: posix"
|
|
||||||
echo "InstalledDir: /usr/bin"
|
|
||||||
results:
|
|
||||||
- spec: 'llvm@3.9.1 +clang~lld~lldb'
|
|
||||||
|
|
||||||
Each test is performed by first creating a temporary directory structure as
|
|
||||||
specified in the corresponding ``layout`` and by then running
|
|
||||||
package detection and checking that the outcome matches the expected
|
|
||||||
``results``. The exact details on how to specify both the ``layout`` and the
|
|
||||||
``results`` are reported in the table below:
|
|
||||||
|
|
||||||
.. list-table:: Test based on PATH inspections
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - Option Name
|
|
||||||
- Description
|
|
||||||
- Allowed Values
|
|
||||||
- Required Field
|
|
||||||
* - ``layout``
|
|
||||||
- Specifies the filesystem tree used for the test
|
|
||||||
- List of objects
|
|
||||||
- Yes
|
|
||||||
* - ``layout:[0]:executables``
|
|
||||||
- Relative paths for the mock executables to be created
|
|
||||||
- List of strings
|
|
||||||
- Yes
|
|
||||||
* - ``layout:[0]:script``
|
|
||||||
- Mock logic for the executable
|
|
||||||
- Any valid shell script
|
|
||||||
- Yes
|
|
||||||
* - ``results``
|
|
||||||
- List of expected results
|
|
||||||
- List of objects (empty if no result is expected)
|
|
||||||
- Yes
|
|
||||||
* - ``results:[0]:spec``
|
|
||||||
- A spec that is expected from detection
|
|
||||||
- Any valid spec
|
|
||||||
- Yes
|
|
||||||
|
|
||||||
"""""""""""""""""""""""""""""""
|
|
||||||
Reuse tests from other packages
|
|
||||||
"""""""""""""""""""""""""""""""
|
|
||||||
|
|
||||||
When using a custom repository, it is possible to customize a package that already exists in ``builtin``
|
|
||||||
and reuse its external tests. To do so, just write a ``detection_tests.yaml`` alongside the customized
|
|
||||||
``package.py`` with an ``includes`` attribute. For instance the ``detection_tests.yaml`` for
|
|
||||||
``myrepo.llvm`` might look like:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
includes:
|
|
||||||
- "builtin.llvm"
|
|
||||||
|
|
||||||
This YAML file instructs Spack to run the detection tests defined in ``builtin.llvm`` in addition to
|
|
||||||
those locally defined in the file.
|
|
||||||
|
|
||||||
-----------------------------
|
-----------------------------
|
||||||
Style guidelines for packages
|
Style guidelines for packages
|
||||||
@@ -6836,63 +6606,3 @@ To achieve backward compatibility with the single-class format Spack creates in
|
|||||||
Overall the role of the adapter is to route access to attributes of methods first through the ``*Package``
|
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
|
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.
|
the adapter role is to "emulate" a method resolution order like the one represented by the red arrows.
|
||||||
|
|
||||||
------------------------------
|
|
||||||
Specifying License Information
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
Most of the software in Spack is open source, and most open source software is released
|
|
||||||
under one or more `common open source licenses <https://opensource.org/licenses/>`_.
|
|
||||||
Specifying the license that a package is released under in a project's
|
|
||||||
`package.py` is good practice. To specify a license, find the `SPDX identifier
|
|
||||||
<https://spdx.org/licenses/>`_ for a project and then add it using the license
|
|
||||||
directive:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
license("<SPDX Identifier HERE>")
|
|
||||||
|
|
||||||
For example, the SPDX ID for the Apache Software License, version 2.0 is ``Apache-2.0``,
|
|
||||||
so you'd write:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
license("Apache-2.0")
|
|
||||||
|
|
||||||
Or, for a dual-licensed package like Spack, you would use an `SPDX Expression
|
|
||||||
<https://spdx.github.io/spdx-spec/v2-draft/SPDX-license-expressions/>`_ with both of its
|
|
||||||
licenses:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
license("Apache-2.0 OR MIT")
|
|
||||||
|
|
||||||
Note that specifying a license without a when clause makes it apply to all
|
|
||||||
versions and variants of the package, which might not actually be the case.
|
|
||||||
For example, a project might have switched licenses at some point or have
|
|
||||||
certain build configurations that include files that are licensed differently.
|
|
||||||
Spack itself used to be under the ``LGPL-2.1`` license, until it was relicensed
|
|
||||||
in version ``0.12`` in 2018.
|
|
||||||
|
|
||||||
You can specify when a ``license()`` directive applies using with a ``when=``
|
|
||||||
clause, just like other directives. For example, to specify that a specific
|
|
||||||
license identifier should only apply to versions up to ``0.11``, but another
|
|
||||||
license should apply for later versions, you could write:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
license("LGPL-2.1", when="@:0.11")
|
|
||||||
license("Apache-2.0 OR MIT", when="@0.12:")
|
|
||||||
|
|
||||||
Note that unlike for most other directives, the ``when=`` constraints in the
|
|
||||||
``license()`` directive can't intersect. Spack needs to be able to resolve
|
|
||||||
exactly one license identifier expression for any given version. To specify
|
|
||||||
*multiple* licenses, use SPDX expressions and operators as above. The operators
|
|
||||||
you probably care most about are:
|
|
||||||
|
|
||||||
* ``OR``: user chooses one license to adhere to; and
|
|
||||||
* ``AND``: user has to adhere to all the licenses.
|
|
||||||
|
|
||||||
You may also care about `license exceptions
|
|
||||||
<https://spdx.org/licenses/exceptions-index.html>`_ that use the ``WITH`` operator,
|
|
||||||
e.g. ``Apache-2.0 WITH LLVM-exception``.
|
|
||||||
|
|||||||
@@ -213,16 +213,6 @@ pipeline jobs.
|
|||||||
``spack ci generate``
|
``spack ci generate``
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Throughout this documentation, references to the "mirror" mean the target
|
|
||||||
mirror which is checked for the presence of up-to-date specs, and where
|
|
||||||
any scheduled jobs should push built binary packages. In the past, this
|
|
||||||
defaulted to the mirror at index 0 in the mirror configs, and could be
|
|
||||||
overridden using the ``--buildcache-destination`` argument. Starting with
|
|
||||||
Spack 0.23, ``spack ci generate`` will require you to identify this mirror
|
|
||||||
by the name "buildcache-destination". While you can configure any number
|
|
||||||
of mirrors as sources for your pipelines, you will need to identify the
|
|
||||||
destination mirror by name.
|
|
||||||
|
|
||||||
Concretizes the specs in the active environment, stages them (as described in
|
Concretizes the specs in the active environment, stages them (as described in
|
||||||
:ref:`staging_algorithm`), and writes the resulting ``.gitlab-ci.yml`` to disk.
|
:ref:`staging_algorithm`), and writes the resulting ``.gitlab-ci.yml`` to disk.
|
||||||
During concretization of the environment, ``spack ci generate`` also writes a
|
During concretization of the environment, ``spack ci generate`` also writes a
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
=====================================
|
=====================================
|
||||||
Spack for Homebrew/Conda Users
|
Using Spack to Replace Homebrew/Conda
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
Spack is an incredibly powerful package manager, designed for supercomputers
|
Spack is an incredibly powerful package manager, designed for supercomputers
|
||||||
@@ -191,18 +191,18 @@ The ``--fresh`` flag tells Spack to use the latest version of every package
|
|||||||
where possible instead of trying to optimize for reuse of existing installed
|
where possible instead of trying to optimize for reuse of existing installed
|
||||||
packages.
|
packages.
|
||||||
|
|
||||||
The ``--force`` flag in addition tells Spack to overwrite its previous
|
The ``--force`` flag in addition tells Spack to overwrite its previous
|
||||||
concretization decisions, allowing you to choose a new version of Python.
|
concretization decisions, allowing you to choose a new version of Python.
|
||||||
If any of the new packages like Bash are already installed, ``spack install``
|
If any of the new packages like Bash are already installed, ``spack install``
|
||||||
won't re-install them, it will keep the symlinks in place.
|
won't re-install them, it will keep the symlinks in place.
|
||||||
|
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
Updating & Cleaning Up Old Packages
|
Updating & Cleaning Up Old Packages
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
If you're looking to mimic the behavior of Homebrew, you may also want to
|
If you're looking to mimic the behavior of Homebrew, you may also want to
|
||||||
clean up out-of-date packages from your environment after an upgrade. To
|
clean up out-of-date packages from your environment after an upgrade. To
|
||||||
upgrade your entire software stack within an environment and clean up old
|
upgrade your entire software stack within an environment and clean up old
|
||||||
package versions, simply run the following commands:
|
package versions, simply run the following commands:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
@@ -212,9 +212,9 @@ package versions, simply run the following commands:
|
|||||||
$ spack concretize --fresh --force
|
$ spack concretize --fresh --force
|
||||||
$ spack install
|
$ spack install
|
||||||
$ spack gc
|
$ spack gc
|
||||||
|
|
||||||
Running ``spack mark -i --all`` tells Spack to mark all of the existing
|
Running ``spack mark -i --all`` tells Spack to mark all of the existing
|
||||||
packages within an environment as "implicitly" installed. This tells
|
packages within an environment as "implicitly" installed. This tells
|
||||||
spack's garbage collection system that these packages should be cleaned up.
|
spack's garbage collection system that these packages should be cleaned up.
|
||||||
|
|
||||||
Don't worry however, this will not remove your entire environment.
|
Don't worry however, this will not remove your entire environment.
|
||||||
@@ -223,8 +223,8 @@ a fresh concretization and will re-mark any packages that should remain
|
|||||||
installed as "explicitly" installed.
|
installed as "explicitly" installed.
|
||||||
|
|
||||||
**Note:** if you use multiple spack environments you should re-run ``spack install``
|
**Note:** if you use multiple spack environments you should re-run ``spack install``
|
||||||
in each of your environments prior to running ``spack gc`` to prevent spack
|
in each of your environments prior to running ``spack gc`` to prevent spack
|
||||||
from uninstalling any shared packages that are no longer required by the
|
from uninstalling any shared packages that are no longer required by the
|
||||||
environment you just upgraded.
|
environment you just upgraded.
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
sphinx==7.2.6
|
# These dependencies should be installed using pip in order
|
||||||
sphinxcontrib-programoutput==0.17
|
# to build the documentation.
|
||||||
sphinx_design==0.5.0
|
|
||||||
sphinx-rtd-theme==1.3.0
|
sphinx>=3.4,!=4.1.2,!=5.1.0
|
||||||
python-levenshtein==0.23.0
|
sphinxcontrib-programoutput
|
||||||
docutils==0.18.1
|
sphinx-design
|
||||||
pygments==2.16.1
|
sphinx-rtd-theme
|
||||||
urllib3==2.0.7
|
python-levenshtein
|
||||||
pytest==7.4.3
|
# Restrict to docutils <0.17 to workaround a list rendering issue in sphinx.
|
||||||
isort==5.12.0
|
# https://stackoverflow.com/questions/67542699
|
||||||
black==23.10.1
|
docutils <0.17
|
||||||
flake8==6.1.0
|
pygments <2.13
|
||||||
mypy==1.6.1
|
urllib3 <2
|
||||||
|
|||||||
@@ -1,478 +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)
|
|
||||||
|
|
||||||
.. _signing:
|
|
||||||
|
|
||||||
=====================
|
|
||||||
Spack Package Signing
|
|
||||||
=====================
|
|
||||||
|
|
||||||
The goal of package signing in Spack is to provide data integrity
|
|
||||||
assurances around official packages produced by the automated Spack CI
|
|
||||||
pipelines. These assurances directly address the security of Spack’s
|
|
||||||
software supply chain by explaining why a security-conscious user can
|
|
||||||
be reasonably justified in the belief that packages installed via Spack
|
|
||||||
have an uninterrupted auditable trail back to change management
|
|
||||||
decisions judged to be appropriate by the Spack maintainers. This is
|
|
||||||
achieved through cryptographic signing of packages built by Spack CI
|
|
||||||
pipelines based on code that has been transparently reviewed and
|
|
||||||
approved on GitHub. This document describes the signing process for
|
|
||||||
interested users.
|
|
||||||
|
|
||||||
.. _risks:
|
|
||||||
|
|
||||||
------------------------------
|
|
||||||
Risks, Impact and Threat Model
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
This document addresses the approach taken to safeguard Spack’s
|
|
||||||
reputation with regard to the integrity of the package data produced by
|
|
||||||
Spack’s CI pipelines. It does not address issues of data confidentiality
|
|
||||||
(Spack is intended to be largely open source) or availability (efforts
|
|
||||||
are described elsewhere). With that said the main reputational risk can
|
|
||||||
be broadly categorized as a loss of faith in the data integrity due to a
|
|
||||||
breach of the private key used to sign packages. Remediation of a
|
|
||||||
private key breach would require republishing the public key with a
|
|
||||||
revocation certificate, generating a new signing key, an assessment and
|
|
||||||
potential rebuild/resigning of all packages since the key was breached,
|
|
||||||
and finally direct intervention by every spack user to update their copy
|
|
||||||
of Spack’s public keys used for local verification.
|
|
||||||
|
|
||||||
The primary threat model used in mitigating the risks of these stated
|
|
||||||
impacts is one of individual error not malicious intent or insider
|
|
||||||
threat. The primary objective is to avoid the above impacts by making a
|
|
||||||
private key breach nearly impossible due to oversight or configuration
|
|
||||||
error. Obvious and straightforward measures are taken to mitigate issues
|
|
||||||
of malicious interference in data integrity and insider threats but
|
|
||||||
these attack vectors are not systematically addressed. It should be hard
|
|
||||||
to exfiltrate the private key intentionally, and almost impossible to
|
|
||||||
leak the key by accident.
|
|
||||||
|
|
||||||
.. _overview:
|
|
||||||
|
|
||||||
-----------------
|
|
||||||
Pipeline Overview
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Spack pipelines build software through progressive stages where packages
|
|
||||||
in later stages nominally depend on packages built in earlier stages.
|
|
||||||
For both technical and design reasons these dependencies are not
|
|
||||||
implemented through the default GitLab artifacts mechanism; instead
|
|
||||||
built packages are uploaded to AWS S3 mirrors (buckets) where they are
|
|
||||||
retrieved by subsequent stages in the pipeline. Two broad categories of
|
|
||||||
pipelines exist: Pull Request (PR) pipelines and Develop/Release
|
|
||||||
pipelines.
|
|
||||||
|
|
||||||
- PR pipelines are launched in response to pull requests made by
|
|
||||||
trusted and untrusted users. Packages built on these pipelines upload
|
|
||||||
code to quarantined AWS S3 locations which cache the built packages
|
|
||||||
for the purposes of review and iteration on the changes proposed in
|
|
||||||
the pull request. Packages built on PR pipelines can come from
|
|
||||||
untrusted users so signing of these pipelines is not implemented.
|
|
||||||
Jobs in these pipelines are executed via normal GitLab runners both
|
|
||||||
within the AWS GitLab infrastructure and at affiliated institutions.
|
|
||||||
- Develop and Release pipelines **sign** the packages they produce and carry
|
|
||||||
strong integrity assurances that trace back to auditable change management
|
|
||||||
decisions. These pipelines only run after members from a trusted group of
|
|
||||||
reviewers verify that the proposed changes in a pull request are appropriate.
|
|
||||||
Once the PR is merged, or a release is cut, a pipeline is run on protected
|
|
||||||
GitLab runners which provide access to the required signing keys within the
|
|
||||||
job. Intermediary keys are used to sign packages in each stage of the
|
|
||||||
pipeline as they are built and a final job officially signs each package
|
|
||||||
external to any specific packages’ build environment. An intermediate key
|
|
||||||
exists in the AWS infrastructure and for each affiliated instritution that
|
|
||||||
maintains protected runners. The runners that execute these pipelines
|
|
||||||
exclusively accept jobs from protected branches meaning the intermediate keys
|
|
||||||
are never exposed to unreviewed code and the official keys are never exposed
|
|
||||||
to any specific build environment.
|
|
||||||
|
|
||||||
.. _key_architecture:
|
|
||||||
|
|
||||||
----------------
|
|
||||||
Key Architecture
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Spack’s CI process uses public-key infrastructure (PKI) based on GNU Privacy
|
|
||||||
Guard (gpg) keypairs to sign public releases of spack package metadata, also
|
|
||||||
called specs. Two classes of GPG keys are involved in the process to reduce the
|
|
||||||
impact of an individual private key compromise, these key classes are the
|
|
||||||
*Intermediate CI Key* and *Reputational Key*. Each of these keys has signing
|
|
||||||
sub-keys that are used exclusively for signing packages. This can be confusing
|
|
||||||
so for the purpose of this explanation we’ll refer to Root and Signing keys.
|
|
||||||
Each key has a private and a public component as well as one or more identities
|
|
||||||
and zero or more signatures.
|
|
||||||
|
|
||||||
-------------------
|
|
||||||
Intermediate CI Key
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
The Intermediate key class is used to sign and verify packages between stages
|
|
||||||
within a develop or release pipeline. An intermediate key exists for the AWS
|
|
||||||
infrastructure as well as each affiliated institution that maintains protected
|
|
||||||
runners. These intermediate keys are made available to the GitLab execution
|
|
||||||
environment building the package so that the package’s dependencies may be
|
|
||||||
verified by the Signing Intermediate CI Public Key and the final package may be
|
|
||||||
signed by the Signing Intermediate CI Private Key.
|
|
||||||
|
|
||||||
|
|
||||||
+---------------------------------------------------------------------------------------------------------+
|
|
||||||
| **Intermediate CI Key (GPG)** |
|
|
||||||
+==================================================+======================================================+
|
|
||||||
| Root Intermediate CI Private Key (RSA 4096)# | Root Intermediate CI Public Key (RSA 4096) |
|
|
||||||
+--------------------------------------------------+------------------------------------------------------+
|
|
||||||
| Signing Intermediate CI Private Key (RSA 4096) | Signing Intermediate CI Public Key (RSA 4096) |
|
|
||||||
+--------------------------------------------------+------------------------------------------------------+
|
|
||||||
| Identity: “Intermediate CI Key <maintainers@spack.io>” |
|
|
||||||
+---------------------------------------------------------------------------------------------------------+
|
|
||||||
| Signatures: None |
|
|
||||||
+---------------------------------------------------------------------------------------------------------+
|
|
||||||
|
|
||||||
|
|
||||||
The *Root intermediate CI Private Key*\ Is stripped out of the GPG key and
|
|
||||||
stored offline completely separate from Spack’s infrastructure. This allows the
|
|
||||||
core development team to append revocation certificates to the GPG key and
|
|
||||||
issue new sub-keys for use in the pipeline. It is our expectation that this
|
|
||||||
will happen on a semi regular basis. A corollary of this is that *this key
|
|
||||||
should not be used to verify package integrity outside the internal CI process.*
|
|
||||||
|
|
||||||
----------------
|
|
||||||
Reputational Key
|
|
||||||
----------------
|
|
||||||
|
|
||||||
The Reputational Key is the public facing key used to sign complete groups of
|
|
||||||
development and release packages. Only one key pair exsits in this class of
|
|
||||||
keys. In contrast to the Intermediate CI Key the Reputational Key *should* be
|
|
||||||
used to verify package integrity. At the end of develop and release pipeline a
|
|
||||||
final pipeline job pulls down all signed package metadata built by the pipeline,
|
|
||||||
verifies they were signed with an Intermediate CI Key, then strips the
|
|
||||||
Intermediate CI Key signature from the package and re-signs them with the
|
|
||||||
Signing Reputational Private Key. The officially signed packages are then
|
|
||||||
uploaded back to the AWS S3 mirror. Please note that separating use of the
|
|
||||||
reputational key into this final job is done to prevent leakage of the key in a
|
|
||||||
spack package. Because the Signing Reputational Private Key is never exposed to
|
|
||||||
a build job it cannot accidentally end up in any built package.
|
|
||||||
|
|
||||||
|
|
||||||
+---------------------------------------------------------------------------------------------------------+
|
|
||||||
| **Reputational Key (GPG)** |
|
|
||||||
+==================================================+======================================================+
|
|
||||||
| Root Reputational Private Key (RSA 4096)# | Root Reputational Public Key (RSA 4096) |
|
|
||||||
+--------------------------------------------------+------------------------------------------------------+
|
|
||||||
| Signing Reputational Private Key (RSA 4096) | Signing Reputational Public Key (RSA 4096) |
|
|
||||||
+--------------------------------------------------+------------------------------------------------------+
|
|
||||||
| Identity: “Spack Project <maintainers@spack.io>” |
|
|
||||||
+---------------------------------------------------------------------------------------------------------+
|
|
||||||
| Signatures: Signed by core development team [#f1]_ |
|
|
||||||
+---------------------------------------------------------------------------------------------------------+
|
|
||||||
|
|
||||||
The Root Reputational Private Key is stripped out of the GPG key and stored
|
|
||||||
offline completely separate from Spack’s infrastructure. This allows the core
|
|
||||||
development team to append revocation certificates to the GPG key in the
|
|
||||||
unlikely event that the Signing Reputation Private Key is compromised. In
|
|
||||||
general it is the expectation that rotating this key will happen infrequently if
|
|
||||||
at all. This should allow relatively transparent verification for the end-user
|
|
||||||
community without needing deep familiarity with GnuPG or Public Key
|
|
||||||
Infrastructure.
|
|
||||||
|
|
||||||
|
|
||||||
.. _build_cache_format:
|
|
||||||
|
|
||||||
------------------
|
|
||||||
Build Cache Format
|
|
||||||
------------------
|
|
||||||
|
|
||||||
A binary package consists of a metadata file unambiguously defining the
|
|
||||||
built package (and including other details such as how to relocate it)
|
|
||||||
and the installation directory of the package stored as a compressed
|
|
||||||
archive file. The metadata files can either be unsigned, in which case
|
|
||||||
the contents are simply the json-serialized concrete spec plus metadata,
|
|
||||||
or they can be signed, in which case the json-serialized concrete spec
|
|
||||||
plus metadata is wrapped in a gpg cleartext signature. Built package
|
|
||||||
metadata files are named to indicate the operating system and
|
|
||||||
architecture for which the package was built as well as the compiler
|
|
||||||
used to build it and the packages name and version. For example::
|
|
||||||
|
|
||||||
linux-ubuntu18.04-haswell-gcc-7.5.0-zlib-1.2.12-llv2ysfdxnppzjrt5ldybb5c52qbmoow.spec.json.sig
|
|
||||||
|
|
||||||
would contain the concrete spec and binary metadata for a binary package
|
|
||||||
of ``zlib@1.2.12``, built for the ``ubuntu`` operating system and ``haswell``
|
|
||||||
architecture. The id of the built package exists in the name of the file
|
|
||||||
as well (after the package name and version) and in this case begins
|
|
||||||
with ``llv2ys``. The id distinguishes a particular built package from all
|
|
||||||
other built packages with the same os/arch, compiler, name, and version.
|
|
||||||
Below is an example of a signed binary package metadata file. Such a
|
|
||||||
file would live in the ``build_cache`` directory of a binary mirror::
|
|
||||||
|
|
||||||
-----BEGIN PGP SIGNED MESSAGE-----
|
|
||||||
Hash: SHA512
|
|
||||||
|
|
||||||
{
|
|
||||||
"spec": {
|
|
||||||
<concrete-spec-contents-omitted>
|
|
||||||
},
|
|
||||||
|
|
||||||
"buildcache_layout_version": 1,
|
|
||||||
"binary_cache_checksum": {
|
|
||||||
"hash_algorithm": "sha256",
|
|
||||||
"hash": "4f1e46452c35a5e61bcacca205bae1bfcd60a83a399af201a29c95b7cc3e1423"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-----BEGIN PGP SIGNATURE-----
|
|
||||||
iQGzBAEBCgAdFiEETZn0sLle8jIrdAPLx/P+voVcifMFAmKAGvwACgkQx/P+voVc
|
|
||||||
ifNoVgv/VrhA+wurVs5GB9PhmMA1m5U/AfXZb4BElDRwpT8ZcTPIv5X8xtv60eyn
|
|
||||||
4EOneGVbZoMThVxgev/NKARorGmhFXRqhWf+jknJZ1dicpqn/qpv34rELKUpgXU+
|
|
||||||
QDQ4d1P64AIdTczXe2GI9ZvhOo6+bPvK7LIsTkBbtWmopkomVxF0LcMuxAVIbA6b
|
|
||||||
887yBvVO0VGlqRnkDW7nXx49r3AG2+wDcoU1f8ep8QtjOcMNaPTPJ0UnjD0VQGW6
|
|
||||||
4ZFaGZWzdo45MY6tF3o5mqM7zJkVobpoW3iUz6J5tjz7H/nMlGgMkUwY9Kxp2PVH
|
|
||||||
qoj6Zip3LWplnl2OZyAY+vflPFdFh12Xpk4FG7Sxm/ux0r+l8tCAPvtw+G38a5P7
|
|
||||||
QEk2JBr8qMGKASmnRlJUkm1vwz0a95IF3S9YDfTAA2vz6HH3PtsNLFhtorfx8eBi
|
|
||||||
Wn5aPJAGEPOawEOvXGGbsH4cDEKPeN0n6cy1k92uPEmBLDVsdnur8q42jk5c2Qyx
|
|
||||||
j3DXty57
|
|
||||||
=3gvm
|
|
||||||
-----END PGP SIGNATURE-----
|
|
||||||
|
|
||||||
If a user has trusted the public key associated with the private key
|
|
||||||
used to sign the above spec file, the signature can be verified with
|
|
||||||
gpg, as follows::
|
|
||||||
|
|
||||||
$ gpg –verify linux-ubuntu18.04-haswell-gcc-7.5.0-zlib-1.2.12-llv2ysfdxnppzjrt5ldybb5c52qbmoow.spec.json.sig
|
|
||||||
|
|
||||||
The metadata (regardless whether signed or unsigned) contains the checksum
|
|
||||||
of the ``.spack`` file containing the actual installation. The checksum should
|
|
||||||
be compared to a checksum computed locally on the ``.spack`` file to ensure the
|
|
||||||
contents have not changed since the binary spec plus metadata were signed. The
|
|
||||||
``.spack`` files are actually tarballs containing the compressed archive of the
|
|
||||||
install tree. These files, along with the metadata files, live within the
|
|
||||||
``build_cache`` directory of the mirror, and together are organized as follows::
|
|
||||||
|
|
||||||
build_cache/
|
|
||||||
# unsigned metadata (for indexing, contains sha256 of .spack file)
|
|
||||||
<arch>-<compiler>-<name>-<ver>-24zvipcqgg2wyjpvdq2ajy5jnm564hen.spec.json
|
|
||||||
# clearsigned metadata (same as above, but signed)
|
|
||||||
<arch>-<compiler>-<name>-<ver>-24zvipcqgg2wyjpvdq2ajy5jnm564hen.spec.json.sig
|
|
||||||
<arch>/
|
|
||||||
<compiler>/
|
|
||||||
<name>-<ver>/
|
|
||||||
# tar.gz-compressed prefix (may support more compression formats later)
|
|
||||||
<arch>-<compiler>-<name>-<ver>-24zvipcqgg2wyjpvdq2ajy5jnm564hen.spack
|
|
||||||
|
|
||||||
Uncompressing and extracting the ``.spack`` file results in the install tree.
|
|
||||||
This is in contrast to previous versions of spack, where the ``.spack`` file
|
|
||||||
contained a (duplicated) metadata file, a signature file and a nested tarball
|
|
||||||
containing the install tree.
|
|
||||||
|
|
||||||
.. _internal_implementation:
|
|
||||||
|
|
||||||
-----------------------
|
|
||||||
Internal Implementation
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
The technical implementation of the pipeline signing process includes components
|
|
||||||
defined in Amazon Web Services, the Kubernetes cluster, at affilicated
|
|
||||||
institutions, and the GitLab/GitLab Runner deployment. We present the techincal
|
|
||||||
implementation in two interdependent sections. The first addresses how secrets
|
|
||||||
are managed through the lifecycle of a develop or release pipeline. The second
|
|
||||||
section describes how Gitlab Runner and pipelines are configured and managed to
|
|
||||||
support secure automated signing.
|
|
||||||
|
|
||||||
Secrets Management
|
|
||||||
^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
As stated above the Root Private Keys (intermediate and reputational)
|
|
||||||
are stripped from the GPG keys and stored outside Spack’s
|
|
||||||
infrastructure.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
**TODO**
|
|
||||||
- Explanation here about where and how access is handled for these keys.
|
|
||||||
- Both Root private keys are protected with strong passwords
|
|
||||||
- Who has access to these and how?
|
|
||||||
|
|
||||||
**Intermediate CI Key**
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Multiple intermediate CI signing keys exist, one Intermediate CI Key for jobs
|
|
||||||
run in AWS, and one key for each affiliated institution (e.g. Univerity of
|
|
||||||
Oregon). Here we describe how the Intermediate CI Key is managed in AWS:
|
|
||||||
|
|
||||||
The Intermediate CI Key (including the Signing Intermediate CI Private Key is
|
|
||||||
exported as an ASCII armored file and stored in a Kubernetes secret called
|
|
||||||
``spack-intermediate-ci-signing-key``. For convenience sake, this same secret
|
|
||||||
contains an ASCII-armored export of just the *public* components of the
|
|
||||||
Reputational Key. This secret also contains the *public* components of each of
|
|
||||||
the affiliated institutions' Intermediate CI Key. These are potentially needed
|
|
||||||
to verify dependent packages which may have been found in the public mirror or
|
|
||||||
built by a protected job running on an affiliated institution's infrastrcuture
|
|
||||||
in an earlier stage of the pipeline.
|
|
||||||
|
|
||||||
Procedurally the ``spack-intermediate-ci-signing-key`` secret is used in
|
|
||||||
the following way:
|
|
||||||
|
|
||||||
1. A ``large-arm-prot`` or ``large-x86-prot`` protected runner picks up
|
|
||||||
a job tagged ``protected`` from a protected GitLab branch. (See
|
|
||||||
`Protected Runners and Reserved Tags <#_8bawjmgykv0b>`__).
|
|
||||||
2. Based on its configuration, the runner creates a job Pod in the
|
|
||||||
pipeline namespace and mounts the spack-intermediate-ci-signing-key
|
|
||||||
Kubernetes secret into the build container
|
|
||||||
3. The Intermediate CI Key, affiliated institutions' public key and the
|
|
||||||
Reputational Public Key are imported into a keyring by the ``spack gpg …``
|
|
||||||
sub-command. This is initiated by the job’s build script which is created by
|
|
||||||
the generate job at the beginning of the pipeline.
|
|
||||||
4. Assuming the package has dependencies those specs are verified using
|
|
||||||
the keyring.
|
|
||||||
5. The package is built and the spec.json is generated
|
|
||||||
6. The spec.json is signed by the keyring and uploaded to the mirror’s
|
|
||||||
build cache.
|
|
||||||
|
|
||||||
**Reputational Key**
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Because of the increased impact to end users in the case of a private
|
|
||||||
key breach, the Reputational Key is managed separately from the
|
|
||||||
Intermediate CI Keys and has additional controls. First, the Reputational
|
|
||||||
Key was generated outside of Spack’s infrastructure and has been signed
|
|
||||||
by the core development team. The Reputational Key (along with the
|
|
||||||
Signing Reputational Private Key) was then ASCII armor exported to a
|
|
||||||
file. Unlike the Intermediate CI Key this exported file is not stored as
|
|
||||||
a base64 encoded secret in Kubernetes. Instead\ *the key file
|
|
||||||
itself*\ is encrypted and stored in Kubernetes as the
|
|
||||||
``spack-signing-key-encrypted`` secret in the pipeline namespace.
|
|
||||||
|
|
||||||
The encryption of the exported Reputational Key (including the Signing
|
|
||||||
Reputational Private Key) is handled by `AWS Key Management Store (KMS) data
|
|
||||||
keys
|
|
||||||
<https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#data-keys>`__.
|
|
||||||
The private key material is decrypted and imported at the time of signing into a
|
|
||||||
memory mounted temporary directory holding the keychain. The signing job uses
|
|
||||||
the `AWS Encryption SDK
|
|
||||||
<https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/crypto-cli.html>`__
|
|
||||||
(i.e. ``aws-encryption-cli``) to decrypt the Reputational Key. Permission to
|
|
||||||
decrypt the key is granted to the job Pod through a Kubernetes service account
|
|
||||||
specifically used for this, and only this, function. Finally, for convenience
|
|
||||||
sake, this same secret contains an ASCII-armored export of the *public*
|
|
||||||
components of the Intermediate CI Keys and the Reputational Key. This allows the
|
|
||||||
signing script to verify that packages were built by the pipeline (both on AWS
|
|
||||||
or at affiliated institutions), or signed previously as a part of a different
|
|
||||||
pipeline. This is is done *before* importing decrypting and importing the
|
|
||||||
Signing Reputational Private Key material and officially signing the packages.
|
|
||||||
|
|
||||||
Procedurally the ``spack-singing-key-encrypted`` secret is used in the
|
|
||||||
following way:
|
|
||||||
|
|
||||||
1. The ``spack-package-signing-gitlab-runner`` protected runner picks
|
|
||||||
up a job tagged ``notary`` from a protected GitLab branch (See
|
|
||||||
`Protected Runners and Reserved Tags <#_8bawjmgykv0b>`__).
|
|
||||||
2. Based on its configuration, the runner creates a job pod in the
|
|
||||||
pipeline namespace. The job is run in a stripped down purpose-built
|
|
||||||
image ``ghcr.io/spack/notary:latest`` Docker image. The runner is
|
|
||||||
configured to only allow running jobs with this image.
|
|
||||||
3. The runner also mounts the ``spack-signing-key-encrypted`` secret to
|
|
||||||
a path on disk. Note that this becomes several files on disk, the
|
|
||||||
public components of the Intermediate CI Keys, the public components
|
|
||||||
of the Reputational CI, and an AWS KMS encrypted file containing the
|
|
||||||
Singing Reputational Private Key.
|
|
||||||
4. In addition to the secret, the runner creates a tmpfs memory mounted
|
|
||||||
directory where the GnuPG keyring will be created to verify, and
|
|
||||||
then resign the package specs.
|
|
||||||
5. The job script syncs all spec.json.sig files from the build cache to
|
|
||||||
a working directory in the job’s execution environment.
|
|
||||||
6. The job script then runs the ``sign.sh`` script built into the
|
|
||||||
notary Docker image.
|
|
||||||
7. The ``sign.sh`` script imports the public components of the
|
|
||||||
Reputational and Intermediate CI Keys and uses them to verify good
|
|
||||||
signatures on the spec.json.sig files. If any signed spec does not
|
|
||||||
verify the job immediately fails.
|
|
||||||
8. Assuming all specs are verified, the ``sign.sh`` script then unpacks
|
|
||||||
the spec json data from the signed file in preparation for being
|
|
||||||
re-signed with the Reputational Key.
|
|
||||||
9. The private components of the Reputational Key are decrypted to
|
|
||||||
standard out using ``aws-encryption-cli`` directly into a ``gpg
|
|
||||||
–import …`` statement which imports the key into the
|
|
||||||
keyring mounted in-memory.
|
|
||||||
10. The private key is then used to sign each of the json specs and the
|
|
||||||
keyring is removed from disk.
|
|
||||||
11. The re-signed json specs are resynced to the AWS S3 Mirror and the
|
|
||||||
public signing of the packages for the develop or release pipeline
|
|
||||||
that created them is complete.
|
|
||||||
|
|
||||||
Non service-account access to the private components of the Reputational
|
|
||||||
Key that are managed through access to the symmetric secret in KMS used
|
|
||||||
to encrypt the data key (which in turn is used to encrypt the GnuPG key
|
|
||||||
- See:\ `Encryption SDK
|
|
||||||
Documentation <https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/crypto-cli-examples.html#cli-example-encrypt-file>`__).
|
|
||||||
A small trusted subset of the core development team are the only
|
|
||||||
individuals with access to this symmetric key.
|
|
||||||
|
|
||||||
.. _protected_runners:
|
|
||||||
|
|
||||||
Protected Runners and Reserved Tags
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Spack has a large number of Gitlab Runners operating in its build farm.
|
|
||||||
These include runners deployed in the AWS Kubernetes cluster as well as
|
|
||||||
runners deployed at affiliated institutions. The majority of runners are
|
|
||||||
shared runners that operate across projects in gitlab.spack.io. These
|
|
||||||
runners pick up jobs primarily from the spack/spack project and execute
|
|
||||||
them in PR pipelines.
|
|
||||||
|
|
||||||
A small number of runners operating on AWS and at affiliated institutions are
|
|
||||||
registered as specific *protected* runners on the spack/spack project. In
|
|
||||||
addition to protected runners there are protected branches on the spack/spack
|
|
||||||
project. These are the ``develop`` branch, any release branch (i.e. managed with
|
|
||||||
the ``releases/v*`` wildcard) and any tag branch (managed with the ``v*``
|
|
||||||
wildcard) Finally Spack’s pipeline generation code reserves certain tags to make
|
|
||||||
sure jobs are routed to the correct runners, these tags are ``public``,
|
|
||||||
``protected``, and ``notary``. Understanding how all this works together to
|
|
||||||
protect secrets and provide integrity assurances can be a little confusing so
|
|
||||||
lets break these down:
|
|
||||||
|
|
||||||
- **Protected Branches**- Protected branches in Spack prevent anyone
|
|
||||||
other than Maintainers in GitLab from pushing code. In the case of
|
|
||||||
Spack the only Maintainer level entity pushing code to protected
|
|
||||||
branches is Spack bot. Protecting branches also marks them in such a
|
|
||||||
way that Protected Runners will only run jobs from those branches
|
|
||||||
- **Protected Runners**- Protected Runners only run jobs from protected
|
|
||||||
branches. Because protected runners have access to secrets, it's critical
|
|
||||||
that they not run Jobs from untrusted code (i.e. PR branches). If they did it
|
|
||||||
would be possible for a PR branch to tag a job in such a way that a protected
|
|
||||||
runner executed that job and mounted secrets into a code execution
|
|
||||||
environment that had not been reviewed by Spack maintainers. Note however
|
|
||||||
that in the absence of tagging used to route jobs, public runners *could* run
|
|
||||||
jobs from protected branches. No secrets would be at risk of being breached
|
|
||||||
because non-protected runners do not have access to those secrets; lack of
|
|
||||||
secrets would, however, cause the jobs to fail.
|
|
||||||
- **Reserved Tags**- To mitigate the issue of public runners picking up
|
|
||||||
protected jobs Spack uses a small set of “reserved” job tags (Note that these
|
|
||||||
are *job* tags not git tags). These tags are “public”, “private”, and
|
|
||||||
“notary.” The majority of jobs executed in Spack’s GitLab instance are
|
|
||||||
executed via a ``generate`` job. The generate job code systematically ensures
|
|
||||||
that no user defined configuration sets these tags. Instead, the ``generate``
|
|
||||||
job sets these tags based on rules related to the branch where this pipeline
|
|
||||||
originated. If the job is a part of a pipeline on a PR branch it sets the
|
|
||||||
``public`` tag. If the job is part of a pipeline on a protected branch it
|
|
||||||
sets the ``protected`` tag. Finally if the job is the package signing job and
|
|
||||||
it is running on a pipeline that is part of a protected branch then it sets
|
|
||||||
the ``notary`` tag.
|
|
||||||
|
|
||||||
Protected Runners are configured to only run jobs from protected branches. Only
|
|
||||||
jobs running in pipelines on protected branches are tagged with ``protected`` or
|
|
||||||
``notary`` tags. This tightly couples jobs on protected branches to protected
|
|
||||||
runners that provide access to the secrets required to sign the built packages.
|
|
||||||
The secrets are can **only** be accessed via:
|
|
||||||
|
|
||||||
1. Runners under direct control of the core development team.
|
|
||||||
2. Runners under direct control of trusted maintainers at affiliated institutions.
|
|
||||||
3. By code running the automated pipeline that has been reviewed by the
|
|
||||||
Spack maintainers and judged to be appropriate.
|
|
||||||
|
|
||||||
Other attempts (either through malicious intent or incompetence) can at
|
|
||||||
worst grab jobs intended for protected runners which will cause those
|
|
||||||
jobs to fail alerting both Spack maintainers and the core development
|
|
||||||
team.
|
|
||||||
|
|
||||||
.. [#f1]
|
|
||||||
The Reputational Key has also cross signed core development team
|
|
||||||
keys.
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
Name, Supported Versions, Notes, Requirement Reason
|
Name, Supported Versions, Notes, Requirement Reason
|
||||||
Python, 3.6--3.12, , Interpreter for Spack
|
Python, 3.6--3.11, , Interpreter for Spack
|
||||||
C/C++ Compilers, , , Building software
|
C/C++ Compilers, , , Building software
|
||||||
|
make, , , Build software
|
||||||
patch, , , Build software
|
patch, , , Build software
|
||||||
|
bash, , , Compiler wrappers
|
||||||
tar, , , Extract/create archives
|
tar, , , Extract/create archives
|
||||||
gzip, , , Compress/Decompress archives
|
gzip, , , Compress/Decompress archives
|
||||||
unzip, , , Compress/Decompress archives
|
unzip, , , Compress/Decompress archives
|
||||||
|
|||||||
|
428
lib/spack/env/cc
vendored
428
lib/spack/env/cc
vendored
@@ -416,14 +416,30 @@ input_command="$*"
|
|||||||
# The lists are all bell-separated to be as flexible as possible, as their
|
# The lists are all bell-separated to be as flexible as possible, as their
|
||||||
# contents may come from the command line, from ' '-separated lists,
|
# contents may come from the command line, from ' '-separated lists,
|
||||||
# ':'-separated lists, etc.
|
# ':'-separated lists, etc.
|
||||||
|
include_dirs_list=""
|
||||||
|
lib_dirs_list=""
|
||||||
|
rpath_dirs_list=""
|
||||||
|
system_include_dirs_list=""
|
||||||
|
system_lib_dirs_list=""
|
||||||
|
system_rpath_dirs_list=""
|
||||||
|
isystem_system_include_dirs_list=""
|
||||||
|
isystem_include_dirs_list=""
|
||||||
|
libs_list=""
|
||||||
|
other_args_list=""
|
||||||
|
|
||||||
|
# Global state for keeping track of -Wl,-rpath -Wl,/path
|
||||||
|
wl_expect_rpath=no
|
||||||
|
|
||||||
|
# Same, but for -Xlinker -rpath -Xlinker /path
|
||||||
|
xlinker_expect_rpath=no
|
||||||
|
|
||||||
parse_Wl() {
|
parse_Wl() {
|
||||||
while [ $# -ne 0 ]; do
|
while [ $# -ne 0 ]; do
|
||||||
if [ "$wl_expect_rpath" = yes ]; then
|
if [ "$wl_expect_rpath" = yes ]; then
|
||||||
if system_dir "$1"; then
|
if system_dir "$1"; then
|
||||||
append return_system_rpath_dirs_list "$1"
|
append system_rpath_dirs_list "$1"
|
||||||
else
|
else
|
||||||
append return_rpath_dirs_list "$1"
|
append rpath_dirs_list "$1"
|
||||||
fi
|
fi
|
||||||
wl_expect_rpath=no
|
wl_expect_rpath=no
|
||||||
else
|
else
|
||||||
@@ -433,9 +449,9 @@ parse_Wl() {
|
|||||||
if [ -z "$arg" ]; then
|
if [ -z "$arg" ]; then
|
||||||
shift; continue
|
shift; continue
|
||||||
elif system_dir "$arg"; then
|
elif system_dir "$arg"; then
|
||||||
append return_system_rpath_dirs_list "$arg"
|
append system_rpath_dirs_list "$arg"
|
||||||
else
|
else
|
||||||
append return_rpath_dirs_list "$arg"
|
append rpath_dirs_list "$arg"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
--rpath=*)
|
--rpath=*)
|
||||||
@@ -443,9 +459,9 @@ parse_Wl() {
|
|||||||
if [ -z "$arg" ]; then
|
if [ -z "$arg" ]; then
|
||||||
shift; continue
|
shift; continue
|
||||||
elif system_dir "$arg"; then
|
elif system_dir "$arg"; then
|
||||||
append return_system_rpath_dirs_list "$arg"
|
append system_rpath_dirs_list "$arg"
|
||||||
else
|
else
|
||||||
append return_rpath_dirs_list "$arg"
|
append rpath_dirs_list "$arg"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
-rpath|--rpath)
|
-rpath|--rpath)
|
||||||
@@ -459,7 +475,7 @@ parse_Wl() {
|
|||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
append return_other_args_list "-Wl,$1"
|
append other_args_list "-Wl,$1"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
@@ -467,210 +483,177 @@ parse_Wl() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
categorize_arguments() {
|
|
||||||
|
|
||||||
unset IFS
|
while [ $# -ne 0 ]; do
|
||||||
|
|
||||||
return_other_args_list=""
|
# an RPATH to be added after the case statement.
|
||||||
return_isystem_was_used=""
|
rp=""
|
||||||
return_isystem_system_include_dirs_list=""
|
|
||||||
return_isystem_include_dirs_list=""
|
|
||||||
return_system_include_dirs_list=""
|
|
||||||
return_include_dirs_list=""
|
|
||||||
return_system_lib_dirs_list=""
|
|
||||||
return_lib_dirs_list=""
|
|
||||||
return_system_rpath_dirs_list=""
|
|
||||||
return_rpath_dirs_list=""
|
|
||||||
|
|
||||||
# Global state for keeping track of -Wl,-rpath -Wl,/path
|
# Multiple consecutive spaces in the command line can
|
||||||
wl_expect_rpath=no
|
# result in blank arguments
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
shift
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
# Same, but for -Xlinker -rpath -Xlinker /path
|
if [ -n "${SPACK_COMPILER_FLAGS_KEEP}" ] ; then
|
||||||
xlinker_expect_rpath=no
|
# NOTE: the eval is required to allow `|` alternatives inside the variable
|
||||||
|
eval "\
|
||||||
while [ $# -ne 0 ]; do
|
case \"\$1\" in
|
||||||
|
$SPACK_COMPILER_FLAGS_KEEP)
|
||||||
# an RPATH to be added after the case statement.
|
append other_args_list \"\$1\"
|
||||||
rp=""
|
|
||||||
|
|
||||||
# Multiple consecutive spaces in the command line can
|
|
||||||
# result in blank arguments
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
shift
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${SPACK_COMPILER_FLAGS_KEEP}" ] ; then
|
|
||||||
# NOTE: the eval is required to allow `|` alternatives inside the variable
|
|
||||||
eval "\
|
|
||||||
case \"\$1\" in
|
|
||||||
$SPACK_COMPILER_FLAGS_KEEP)
|
|
||||||
append return_other_args_list \"\$1\"
|
|
||||||
shift
|
|
||||||
continue
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
# the replace list is a space-separated list of pipe-separated pairs,
|
|
||||||
# the first in each pair is the original prefix to be matched, the
|
|
||||||
# second is the replacement prefix
|
|
||||||
if [ -n "${SPACK_COMPILER_FLAGS_REPLACE}" ] ; then
|
|
||||||
for rep in ${SPACK_COMPILER_FLAGS_REPLACE} ; do
|
|
||||||
before=${rep%|*}
|
|
||||||
after=${rep#*|}
|
|
||||||
eval "\
|
|
||||||
stripped=\"\${1##$before}\"
|
|
||||||
"
|
|
||||||
if [ "$stripped" = "$1" ] ; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
replaced="$after$stripped"
|
|
||||||
|
|
||||||
# it matched, remove it
|
|
||||||
shift
|
shift
|
||||||
|
continue
|
||||||
if [ -z "$replaced" ] ; then
|
|
||||||
# completely removed, continue OUTER loop
|
|
||||||
continue 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# re-build argument list with replacement
|
|
||||||
set -- "$replaced" "$@"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
-isystem*)
|
|
||||||
arg="${1#-isystem}"
|
|
||||||
return_isystem_was_used=true
|
|
||||||
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
|
||||||
if system_dir "$arg"; then
|
|
||||||
append return_isystem_system_include_dirs_list "$arg"
|
|
||||||
else
|
|
||||||
append return_isystem_include_dirs_list "$arg"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
-I*)
|
|
||||||
arg="${1#-I}"
|
|
||||||
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
|
||||||
if system_dir "$arg"; then
|
|
||||||
append return_system_include_dirs_list "$arg"
|
|
||||||
else
|
|
||||||
append return_include_dirs_list "$arg"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
-L*)
|
|
||||||
arg="${1#-L}"
|
|
||||||
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
|
||||||
if system_dir "$arg"; then
|
|
||||||
append return_system_lib_dirs_list "$arg"
|
|
||||||
else
|
|
||||||
append return_lib_dirs_list "$arg"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
-l*)
|
|
||||||
# -loopopt=0 is generated erroneously in autoconf <= 2.69,
|
|
||||||
# and passed by ifx to the linker, which confuses it with a
|
|
||||||
# library. Filter it out.
|
|
||||||
# TODO: generalize filtering of args with an env var, so that
|
|
||||||
# TODO: we do not have to special case this here.
|
|
||||||
if { [ "$mode" = "ccld" ] || [ $mode = "ld" ]; } \
|
|
||||||
&& [ "$1" != "${1#-loopopt}" ]; then
|
|
||||||
shift
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
arg="${1#-l}"
|
|
||||||
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
|
||||||
append return_other_args_list "-l$arg"
|
|
||||||
;;
|
|
||||||
-Wl,*)
|
|
||||||
IFS=,
|
|
||||||
if ! parse_Wl ${1#-Wl,}; then
|
|
||||||
append return_other_args_list "$1"
|
|
||||||
fi
|
|
||||||
unset IFS
|
|
||||||
;;
|
|
||||||
-Xlinker)
|
|
||||||
shift
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
# -Xlinker without value: let the compiler error about it.
|
|
||||||
append return_other_args_list -Xlinker
|
|
||||||
xlinker_expect_rpath=no
|
|
||||||
break
|
|
||||||
elif [ "$xlinker_expect_rpath" = yes ]; then
|
|
||||||
# Register the path of -Xlinker -rpath <other args> -Xlinker <path>
|
|
||||||
if system_dir "$1"; then
|
|
||||||
append return_system_rpath_dirs_list "$1"
|
|
||||||
else
|
|
||||||
append return_rpath_dirs_list "$1"
|
|
||||||
fi
|
|
||||||
xlinker_expect_rpath=no
|
|
||||||
else
|
|
||||||
case "$1" in
|
|
||||||
-rpath=*)
|
|
||||||
arg="${1#-rpath=}"
|
|
||||||
if system_dir "$arg"; then
|
|
||||||
append return_system_rpath_dirs_list "$arg"
|
|
||||||
else
|
|
||||||
append return_rpath_dirs_list "$arg"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
--rpath=*)
|
|
||||||
arg="${1#--rpath=}"
|
|
||||||
if system_dir "$arg"; then
|
|
||||||
append return_system_rpath_dirs_list "$arg"
|
|
||||||
else
|
|
||||||
append return_rpath_dirs_list "$arg"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
-rpath|--rpath)
|
|
||||||
xlinker_expect_rpath=yes
|
|
||||||
;;
|
|
||||||
"$dtags_to_strip")
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
append return_other_args_list -Xlinker
|
|
||||||
append return_other_args_list "$1"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
"$dtags_to_strip")
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
append return_other_args_list "$1"
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
"
|
||||||
done
|
fi
|
||||||
|
# the replace list is a space-separated list of pipe-separated pairs,
|
||||||
|
# the first in each pair is the original prefix to be matched, the
|
||||||
|
# second is the replacement prefix
|
||||||
|
if [ -n "${SPACK_COMPILER_FLAGS_REPLACE}" ] ; then
|
||||||
|
for rep in ${SPACK_COMPILER_FLAGS_REPLACE} ; do
|
||||||
|
before=${rep%|*}
|
||||||
|
after=${rep#*|}
|
||||||
|
eval "\
|
||||||
|
stripped=\"\${1##$before}\"
|
||||||
|
"
|
||||||
|
if [ "$stripped" = "$1" ] ; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
# We found `-Xlinker -rpath` but no matching value `-Xlinker /path`. Just append
|
replaced="$after$stripped"
|
||||||
# `-Xlinker -rpath` again and let the compiler or linker handle the error during arg
|
|
||||||
# parsing.
|
# it matched, remove it
|
||||||
if [ "$xlinker_expect_rpath" = yes ]; then
|
shift
|
||||||
append return_other_args_list -Xlinker
|
|
||||||
append return_other_args_list -rpath
|
if [ -z "$replaced" ] ; then
|
||||||
|
# completely removed, continue OUTER loop
|
||||||
|
continue 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# re-build argument list with replacement
|
||||||
|
set -- "$replaced" "$@"
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Same, but for -Wl flags.
|
case "$1" in
|
||||||
if [ "$wl_expect_rpath" = yes ]; then
|
-isystem*)
|
||||||
append return_other_args_list -Wl,-rpath
|
arg="${1#-isystem}"
|
||||||
fi
|
isystem_was_used=true
|
||||||
}
|
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
||||||
|
if system_dir "$arg"; then
|
||||||
|
append isystem_system_include_dirs_list "$arg"
|
||||||
|
else
|
||||||
|
append isystem_include_dirs_list "$arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-I*)
|
||||||
|
arg="${1#-I}"
|
||||||
|
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
||||||
|
if system_dir "$arg"; then
|
||||||
|
append system_include_dirs_list "$arg"
|
||||||
|
else
|
||||||
|
append include_dirs_list "$arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-L*)
|
||||||
|
arg="${1#-L}"
|
||||||
|
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
||||||
|
if system_dir "$arg"; then
|
||||||
|
append system_lib_dirs_list "$arg"
|
||||||
|
else
|
||||||
|
append lib_dirs_list "$arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-l*)
|
||||||
|
# -loopopt=0 is generated erroneously in autoconf <= 2.69,
|
||||||
|
# and passed by ifx to the linker, which confuses it with a
|
||||||
|
# library. Filter it out.
|
||||||
|
# TODO: generalize filtering of args with an env var, so that
|
||||||
|
# TODO: we do not have to special case this here.
|
||||||
|
if { [ "$mode" = "ccld" ] || [ $mode = "ld" ]; } \
|
||||||
|
&& [ "$1" != "${1#-loopopt}" ]; then
|
||||||
|
shift
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
arg="${1#-l}"
|
||||||
|
if [ -z "$arg" ]; then shift; arg="$1"; fi
|
||||||
|
append other_args_list "-l$arg"
|
||||||
|
;;
|
||||||
|
-Wl,*)
|
||||||
|
IFS=,
|
||||||
|
if ! parse_Wl ${1#-Wl,}; then
|
||||||
|
append other_args_list "$1"
|
||||||
|
fi
|
||||||
|
unset IFS
|
||||||
|
;;
|
||||||
|
-Xlinker)
|
||||||
|
shift
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
# -Xlinker without value: let the compiler error about it.
|
||||||
|
append other_args_list -Xlinker
|
||||||
|
xlinker_expect_rpath=no
|
||||||
|
break
|
||||||
|
elif [ "$xlinker_expect_rpath" = yes ]; then
|
||||||
|
# Register the path of -Xlinker -rpath <other args> -Xlinker <path>
|
||||||
|
if system_dir "$1"; then
|
||||||
|
append system_rpath_dirs_list "$1"
|
||||||
|
else
|
||||||
|
append rpath_dirs_list "$1"
|
||||||
|
fi
|
||||||
|
xlinker_expect_rpath=no
|
||||||
|
else
|
||||||
|
case "$1" in
|
||||||
|
-rpath=*)
|
||||||
|
arg="${1#-rpath=}"
|
||||||
|
if system_dir "$arg"; then
|
||||||
|
append system_rpath_dirs_list "$arg"
|
||||||
|
else
|
||||||
|
append rpath_dirs_list "$arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--rpath=*)
|
||||||
|
arg="${1#--rpath=}"
|
||||||
|
if system_dir "$arg"; then
|
||||||
|
append system_rpath_dirs_list "$arg"
|
||||||
|
else
|
||||||
|
append rpath_dirs_list "$arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-rpath|--rpath)
|
||||||
|
xlinker_expect_rpath=yes
|
||||||
|
;;
|
||||||
|
"$dtags_to_strip")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
append other_args_list -Xlinker
|
||||||
|
append other_args_list "$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"$dtags_to_strip")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
append other_args_list "$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
categorize_arguments "$@"
|
# We found `-Xlinker -rpath` but no matching value `-Xlinker /path`. Just append
|
||||||
include_dirs_list="$return_include_dirs_list"
|
# `-Xlinker -rpath` again and let the compiler or linker handle the error during arg
|
||||||
lib_dirs_list="$return_lib_dirs_list"
|
# parsing.
|
||||||
rpath_dirs_list="$return_rpath_dirs_list"
|
if [ "$xlinker_expect_rpath" = yes ]; then
|
||||||
system_include_dirs_list="$return_system_include_dirs_list"
|
append other_args_list -Xlinker
|
||||||
system_lib_dirs_list="$return_system_lib_dirs_list"
|
append other_args_list -rpath
|
||||||
system_rpath_dirs_list="$return_system_rpath_dirs_list"
|
fi
|
||||||
isystem_was_used="$return_isystem_was_used"
|
|
||||||
isystem_system_include_dirs_list="$return_isystem_system_include_dirs_list"
|
# Same, but for -Wl flags.
|
||||||
isystem_include_dirs_list="$return_isystem_include_dirs_list"
|
if [ "$wl_expect_rpath" = yes ]; then
|
||||||
other_args_list="$return_other_args_list"
|
append other_args_list -Wl,-rpath
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Add flags from Spack's cppflags, cflags, cxxflags, fcflags, fflags, and
|
# Add flags from Spack's cppflags, cflags, cxxflags, fcflags, fflags, and
|
||||||
@@ -690,14 +673,12 @@ elif [ "$SPACK_ADD_DEBUG_FLAGS" = "custom" ]; then
|
|||||||
extend flags_list SPACK_DEBUG_FLAGS
|
extend flags_list SPACK_DEBUG_FLAGS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
spack_flags_list=""
|
|
||||||
|
|
||||||
# Fortran flags come before CPPFLAGS
|
# Fortran flags come before CPPFLAGS
|
||||||
case "$mode" in
|
case "$mode" in
|
||||||
cc|ccld)
|
cc|ccld)
|
||||||
case $lang_flags in
|
case $lang_flags in
|
||||||
F)
|
F)
|
||||||
extend spack_flags_list SPACK_FFLAGS
|
extend flags_list SPACK_FFLAGS
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
@@ -706,7 +687,7 @@ esac
|
|||||||
# C preprocessor flags come before any C/CXX flags
|
# C preprocessor flags come before any C/CXX flags
|
||||||
case "$mode" in
|
case "$mode" in
|
||||||
cpp|as|cc|ccld)
|
cpp|as|cc|ccld)
|
||||||
extend spack_flags_list SPACK_CPPFLAGS
|
extend flags_list SPACK_CPPFLAGS
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -716,10 +697,10 @@ case "$mode" in
|
|||||||
cc|ccld)
|
cc|ccld)
|
||||||
case $lang_flags in
|
case $lang_flags in
|
||||||
C)
|
C)
|
||||||
extend spack_flags_list SPACK_CFLAGS
|
extend flags_list SPACK_CFLAGS
|
||||||
;;
|
;;
|
||||||
CXX)
|
CXX)
|
||||||
extend spack_flags_list SPACK_CXXFLAGS
|
extend flags_list SPACK_CXXFLAGS
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -731,25 +712,10 @@ esac
|
|||||||
# Linker flags
|
# Linker flags
|
||||||
case "$mode" in
|
case "$mode" in
|
||||||
ld|ccld)
|
ld|ccld)
|
||||||
extend spack_flags_list SPACK_LDFLAGS
|
extend flags_list SPACK_LDFLAGS
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
IFS="$lsep"
|
|
||||||
categorize_arguments $spack_flags_list
|
|
||||||
unset IFS
|
|
||||||
spack_flags_include_dirs_list="$return_include_dirs_list"
|
|
||||||
spack_flags_lib_dirs_list="$return_lib_dirs_list"
|
|
||||||
spack_flags_rpath_dirs_list="$return_rpath_dirs_list"
|
|
||||||
spack_flags_system_include_dirs_list="$return_system_include_dirs_list"
|
|
||||||
spack_flags_system_lib_dirs_list="$return_system_lib_dirs_list"
|
|
||||||
spack_flags_system_rpath_dirs_list="$return_system_rpath_dirs_list"
|
|
||||||
spack_flags_isystem_was_used="$return_isystem_was_used"
|
|
||||||
spack_flags_isystem_system_include_dirs_list="$return_isystem_system_include_dirs_list"
|
|
||||||
spack_flags_isystem_include_dirs_list="$return_isystem_include_dirs_list"
|
|
||||||
spack_flags_other_args_list="$return_other_args_list"
|
|
||||||
|
|
||||||
|
|
||||||
# On macOS insert headerpad_max_install_names linker flag
|
# On macOS insert headerpad_max_install_names linker flag
|
||||||
if [ "$mode" = ld ] || [ "$mode" = ccld ]; then
|
if [ "$mode" = ld ] || [ "$mode" = ccld ]; then
|
||||||
if [ "${SPACK_SHORT_SPEC#*darwin}" != "${SPACK_SHORT_SPEC}" ]; then
|
if [ "${SPACK_SHORT_SPEC#*darwin}" != "${SPACK_SHORT_SPEC}" ]; then
|
||||||
@@ -775,8 +741,6 @@ if [ "$mode" = ccld ] || [ "$mode" = ld ]; then
|
|||||||
extend lib_dirs_list SPACK_LINK_DIRS
|
extend lib_dirs_list SPACK_LINK_DIRS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
libs_list=""
|
|
||||||
|
|
||||||
# add RPATHs if we're in in any linking mode
|
# add RPATHs if we're in in any linking mode
|
||||||
case "$mode" in
|
case "$mode" in
|
||||||
ld|ccld)
|
ld|ccld)
|
||||||
@@ -805,16 +769,12 @@ args_list="$flags_list"
|
|||||||
|
|
||||||
# Insert include directories just prior to any system include directories
|
# Insert include directories just prior to any system include directories
|
||||||
# NOTE: adding ${lsep} to the prefix here turns every added element into two
|
# NOTE: adding ${lsep} to the prefix here turns every added element into two
|
||||||
extend args_list spack_flags_include_dirs_list "-I"
|
|
||||||
extend args_list include_dirs_list "-I"
|
extend args_list include_dirs_list "-I"
|
||||||
extend args_list spack_flags_isystem_include_dirs_list "-isystem${lsep}"
|
|
||||||
extend args_list isystem_include_dirs_list "-isystem${lsep}"
|
extend args_list isystem_include_dirs_list "-isystem${lsep}"
|
||||||
|
|
||||||
case "$mode" in
|
case "$mode" in
|
||||||
cpp|cc|as|ccld)
|
cpp|cc|as|ccld)
|
||||||
if [ "$spack_flags_isystem_was_used" = "true" ]; then
|
if [ "$isystem_was_used" = "true" ]; then
|
||||||
extend args_list SPACK_INCLUDE_DIRS "-isystem${lsep}"
|
|
||||||
elif [ "$isystem_was_used" = "true" ]; then
|
|
||||||
extend args_list SPACK_INCLUDE_DIRS "-isystem${lsep}"
|
extend args_list SPACK_INCLUDE_DIRS "-isystem${lsep}"
|
||||||
else
|
else
|
||||||
extend args_list SPACK_INCLUDE_DIRS "-I"
|
extend args_list SPACK_INCLUDE_DIRS "-I"
|
||||||
@@ -822,15 +782,11 @@ case "$mode" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
extend args_list spack_flags_system_include_dirs_list -I
|
|
||||||
extend args_list system_include_dirs_list -I
|
extend args_list system_include_dirs_list -I
|
||||||
extend args_list spack_flags_isystem_system_include_dirs_list "-isystem${lsep}"
|
|
||||||
extend args_list isystem_system_include_dirs_list "-isystem${lsep}"
|
extend args_list isystem_system_include_dirs_list "-isystem${lsep}"
|
||||||
|
|
||||||
# Library search paths
|
# Library search paths
|
||||||
extend args_list spack_flags_lib_dirs_list "-L"
|
|
||||||
extend args_list lib_dirs_list "-L"
|
extend args_list lib_dirs_list "-L"
|
||||||
extend args_list spack_flags_system_lib_dirs_list "-L"
|
|
||||||
extend args_list system_lib_dirs_list "-L"
|
extend args_list system_lib_dirs_list "-L"
|
||||||
|
|
||||||
# RPATHs arguments
|
# RPATHs arguments
|
||||||
@@ -839,25 +795,20 @@ case "$mode" in
|
|||||||
if [ -n "$dtags_to_add" ] ; then
|
if [ -n "$dtags_to_add" ] ; then
|
||||||
append args_list "$linker_arg$dtags_to_add"
|
append args_list "$linker_arg$dtags_to_add"
|
||||||
fi
|
fi
|
||||||
extend args_list spack_flags_rpath_dirs_list "$rpath"
|
|
||||||
extend args_list rpath_dirs_list "$rpath"
|
extend args_list rpath_dirs_list "$rpath"
|
||||||
extend args_list spack_flags_system_rpath_dirs_list "$rpath"
|
|
||||||
extend args_list system_rpath_dirs_list "$rpath"
|
extend args_list system_rpath_dirs_list "$rpath"
|
||||||
;;
|
;;
|
||||||
ld)
|
ld)
|
||||||
if [ -n "$dtags_to_add" ] ; then
|
if [ -n "$dtags_to_add" ] ; then
|
||||||
append args_list "$dtags_to_add"
|
append args_list "$dtags_to_add"
|
||||||
fi
|
fi
|
||||||
extend args_list spack_flags_rpath_dirs_list "-rpath${lsep}"
|
|
||||||
extend args_list rpath_dirs_list "-rpath${lsep}"
|
extend args_list rpath_dirs_list "-rpath${lsep}"
|
||||||
extend args_list spack_flags_system_rpath_dirs_list "-rpath${lsep}"
|
|
||||||
extend args_list system_rpath_dirs_list "-rpath${lsep}"
|
extend args_list system_rpath_dirs_list "-rpath${lsep}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Other arguments from the input command
|
# Other arguments from the input command
|
||||||
extend args_list other_args_list
|
extend args_list other_args_list
|
||||||
extend args_list spack_flags_other_args_list
|
|
||||||
|
|
||||||
# Inject SPACK_LDLIBS, if supplied
|
# Inject SPACK_LDLIBS, if supplied
|
||||||
extend args_list libs_list "-l"
|
extend args_list libs_list "-l"
|
||||||
@@ -913,4 +864,3 @@ fi
|
|||||||
# Execute the full command, preserving spaces with IFS set
|
# Execute the full command, preserving spaces with IFS set
|
||||||
# to the alarm bell separator.
|
# to the alarm bell separator.
|
||||||
IFS="$lsep"; exec $full_command_list
|
IFS="$lsep"; exec $full_command_list
|
||||||
|
|
||||||
|
|||||||
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
|
* Homepage: https://pypi.python.org/pypi/archspec
|
||||||
* Usage: Labeling, comparison and detection of microarchitectures
|
* Usage: Labeling, comparison and detection of microarchitectures
|
||||||
* Version: 0.2.1 (commit df43a1834460bf94516136951c4729a3100603ec)
|
* Version: 0.2.1 (commit 9e1117bd8a2f0581bced161f2a2e8d6294d0300b)
|
||||||
|
|
||||||
astunparse
|
astunparse
|
||||||
----------------
|
----------------
|
||||||
|
|||||||
2
lib/spack/external/archspec/__init__.py
vendored
2
lib/spack/external/archspec/__init__.py
vendored
@@ -1,2 +1,2 @@
|
|||||||
"""Init file to avoid namespace packages"""
|
"""Init file to avoid namespace packages"""
|
||||||
__version__ = "0.2.1"
|
__version__ = "0.2.0"
|
||||||
|
|||||||
@@ -79,18 +79,14 @@ def __init__(self, name, parents, vendor, features, compilers, generation=0):
|
|||||||
self.features = features
|
self.features = features
|
||||||
self.compilers = compilers
|
self.compilers = compilers
|
||||||
self.generation = generation
|
self.generation = generation
|
||||||
# Cache the ancestor computation
|
|
||||||
self._ancestors = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ancestors(self):
|
def ancestors(self):
|
||||||
"""All the ancestors of this microarchitecture."""
|
"""All the ancestors of this microarchitecture."""
|
||||||
if self._ancestors is None:
|
value = self.parents[:]
|
||||||
value = self.parents[:]
|
for parent in self.parents:
|
||||||
for parent in self.parents:
|
value.extend(a for a in parent.ancestors if a not in value)
|
||||||
value.extend(a for a in parent.ancestors if a not in value)
|
return value
|
||||||
self._ancestors = value
|
|
||||||
return self._ancestors
|
|
||||||
|
|
||||||
def _to_set(self):
|
def _to_set(self):
|
||||||
"""Returns a set of the nodes in this microarchitecture DAG."""
|
"""Returns a set of the nodes in this microarchitecture DAG."""
|
||||||
|
|||||||
@@ -145,13 +145,6 @@
|
|||||||
"flags": "-march={name} -mtune=generic -mcx16 -msahf -mpopcnt -msse3 -msse4.1 -msse4.2 -mssse3"
|
"flags": "-march={name} -mtune=generic -mcx16 -msahf -mpopcnt -msse3 -msse4.1 -msse4.2 -mssse3"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"intel": [
|
|
||||||
{
|
|
||||||
"versions": "16.0:",
|
|
||||||
"name": "corei7",
|
|
||||||
"flags": "-march={name} -mtune=generic -mpopcnt"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"oneapi": [
|
"oneapi": [
|
||||||
{
|
{
|
||||||
"versions": "2021.2.0:",
|
"versions": "2021.2.0:",
|
||||||
@@ -224,13 +217,6 @@
|
|||||||
"flags": "-march={name} -mtune=generic -mcx16 -msahf -mpopcnt -msse3 -msse4.1 -msse4.2 -mssse3 -mavx -mavx2 -mbmi -mbmi2 -mf16c -mfma -mlzcnt -mmovbe -mxsave"
|
"flags": "-march={name} -mtune=generic -mcx16 -msahf -mpopcnt -msse3 -msse4.1 -msse4.2 -mssse3 -mavx -mavx2 -mbmi -mbmi2 -mf16c -mfma -mlzcnt -mmovbe -mxsave"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"intel": [
|
|
||||||
{
|
|
||||||
"versions": "16.0:",
|
|
||||||
"name": "core-avx2",
|
|
||||||
"flags": "-march={name} -mtune={name} -fma -mf16c"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"oneapi": [
|
"oneapi": [
|
||||||
{
|
{
|
||||||
"versions": "2021.2.0:",
|
"versions": "2021.2.0:",
|
||||||
@@ -314,13 +300,6 @@
|
|||||||
"flags": "-march={name} -mtune=generic -mcx16 -msahf -mpopcnt -msse3 -msse4.1 -msse4.2 -mssse3 -mavx -mavx2 -mbmi -mbmi2 -mf16c -mfma -mlzcnt -mmovbe -mxsave -mavx512f -mavx512bw -mavx512cd -mavx512dq -mavx512vl"
|
"flags": "-march={name} -mtune=generic -mcx16 -msahf -mpopcnt -msse3 -msse4.1 -msse4.2 -mssse3 -mavx -mavx2 -mbmi -mbmi2 -mf16c -mfma -mlzcnt -mmovbe -mxsave -mavx512f -mavx512bw -mavx512cd -mavx512dq -mavx512vl"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"intel": [
|
|
||||||
{
|
|
||||||
"versions": "16.0:",
|
|
||||||
"name": "skylake-avx512",
|
|
||||||
"flags": "-march={name} -mtune={name}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"oneapi": [
|
"oneapi": [
|
||||||
{
|
{
|
||||||
"versions": "2021.2.0:",
|
"versions": "2021.2.0:",
|
||||||
@@ -1433,92 +1412,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sapphirerapids": {
|
|
||||||
"from": [
|
|
||||||
"icelake"
|
|
||||||
],
|
|
||||||
"vendor": "GenuineIntel",
|
|
||||||
"features": [
|
|
||||||
"mmx",
|
|
||||||
"sse",
|
|
||||||
"sse2",
|
|
||||||
"ssse3",
|
|
||||||
"sse4_1",
|
|
||||||
"sse4_2",
|
|
||||||
"popcnt",
|
|
||||||
"aes",
|
|
||||||
"pclmulqdq",
|
|
||||||
"avx",
|
|
||||||
"rdrand",
|
|
||||||
"f16c",
|
|
||||||
"movbe",
|
|
||||||
"fma",
|
|
||||||
"avx2",
|
|
||||||
"bmi1",
|
|
||||||
"bmi2",
|
|
||||||
"rdseed",
|
|
||||||
"adx",
|
|
||||||
"clflushopt",
|
|
||||||
"xsavec",
|
|
||||||
"xsaveopt",
|
|
||||||
"avx512f",
|
|
||||||
"avx512vl",
|
|
||||||
"avx512bw",
|
|
||||||
"avx512dq",
|
|
||||||
"avx512cd",
|
|
||||||
"avx512vbmi",
|
|
||||||
"avx512ifma",
|
|
||||||
"sha_ni",
|
|
||||||
"clwb",
|
|
||||||
"rdpid",
|
|
||||||
"gfni",
|
|
||||||
"avx512_vbmi2",
|
|
||||||
"avx512_vpopcntdq",
|
|
||||||
"avx512_bitalg",
|
|
||||||
"avx512_vnni",
|
|
||||||
"vpclmulqdq",
|
|
||||||
"vaes",
|
|
||||||
"avx512_bf16",
|
|
||||||
"cldemote",
|
|
||||||
"movdir64b",
|
|
||||||
"movdiri",
|
|
||||||
"pdcm",
|
|
||||||
"serialize",
|
|
||||||
"waitpkg"
|
|
||||||
],
|
|
||||||
"compilers": {
|
|
||||||
"gcc": [
|
|
||||||
{
|
|
||||||
"versions": "11.0:",
|
|
||||||
"flags": "-march={name} -mtune={name}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clang": [
|
|
||||||
{
|
|
||||||
"versions": "12.0:",
|
|
||||||
"flags": "-march={name} -mtune={name}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"intel": [
|
|
||||||
{
|
|
||||||
"versions": "2021.2:",
|
|
||||||
"flags": "-march={name} -mtune={name}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"oneapi": [
|
|
||||||
{
|
|
||||||
"versions": "2021.2:",
|
|
||||||
"flags": "-march={name} -mtune={name}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dpcpp": [
|
|
||||||
{
|
|
||||||
"versions": "2021.2:",
|
|
||||||
"flags": "-march={name} -mtune={name}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"k10": {
|
"k10": {
|
||||||
"from": ["x86_64"],
|
"from": ["x86_64"],
|
||||||
"vendor": "AuthenticAMD",
|
"vendor": "AuthenticAMD",
|
||||||
@@ -2172,6 +2065,8 @@
|
|||||||
"pku",
|
"pku",
|
||||||
"gfni",
|
"gfni",
|
||||||
"flush_l1d",
|
"flush_l1d",
|
||||||
|
"erms",
|
||||||
|
"avic",
|
||||||
"avx512f",
|
"avx512f",
|
||||||
"avx512dq",
|
"avx512dq",
|
||||||
"avx512ifma",
|
"avx512ifma",
|
||||||
@@ -2188,12 +2083,12 @@
|
|||||||
"compilers": {
|
"compilers": {
|
||||||
"gcc": [
|
"gcc": [
|
||||||
{
|
{
|
||||||
"versions": "10.3:12.2",
|
"versions": "10.3:13.0",
|
||||||
"name": "znver3",
|
"name": "znver3",
|
||||||
"flags": "-march={name} -mtune={name} -mavx512f -mavx512dq -mavx512ifma -mavx512cd -mavx512bw -mavx512vl -mavx512vbmi -mavx512vbmi2 -mavx512vnni -mavx512bitalg"
|
"flags": "-march={name} -mtune={name} -mavx512f -mavx512dq -mavx512ifma -mavx512cd -mavx512bw -mavx512vl -mavx512vbmi -mavx512vbmi2 -mavx512vnni -mavx512bitalg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"versions": "12.3:",
|
"versions": "13.1:",
|
||||||
"name": "znver4",
|
"name": "znver4",
|
||||||
"flags": "-march={name} -mtune={name}"
|
"flags": "-march={name} -mtune={name}"
|
||||||
}
|
}
|
||||||
|
|||||||
7
lib/spack/external/ctest_log_parser.py
vendored
7
lib/spack/external/ctest_log_parser.py
vendored
@@ -65,6 +65,9 @@
|
|||||||
up to date with CTest, just make sure the ``*_matches`` and
|
up to date with CTest, just make sure the ``*_matches`` and
|
||||||
``*_exceptions`` lists are kept up to date with CTest's build handler.
|
``*_exceptions`` lists are kept up to date with CTest's build handler.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
@@ -208,7 +211,7 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class LogEvent:
|
class LogEvent(object):
|
||||||
"""Class representing interesting events (e.g., errors) in a build log."""
|
"""Class representing interesting events (e.g., errors) in a build log."""
|
||||||
def __init__(self, text, line_no,
|
def __init__(self, text, line_no,
|
||||||
source_file=None, source_line_no=None,
|
source_file=None, source_line_no=None,
|
||||||
@@ -345,7 +348,7 @@ def _parse_unpack(args):
|
|||||||
return _parse(*args)
|
return _parse(*args)
|
||||||
|
|
||||||
|
|
||||||
class CTestLogParser:
|
class CTestLogParser(object):
|
||||||
"""Log file parser that extracts errors and warnings."""
|
"""Log file parser that extracts errors and warnings."""
|
||||||
def __init__(self, profile=False):
|
def __init__(self, profile=False):
|
||||||
# whether to record timing information
|
# whether to record timing information
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
|
||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
"""Path primitives that just require Python standard library."""
|
|
||||||
import functools
|
|
||||||
import sys
|
|
||||||
from typing import List, Optional
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
|
|
||||||
class Path:
|
|
||||||
"""Enum to identify the path-style."""
|
|
||||||
|
|
||||||
unix: int = 0
|
|
||||||
windows: int = 1
|
|
||||||
platform_path: int = windows if sys.platform == "win32" else unix
|
|
||||||
|
|
||||||
|
|
||||||
def format_os_path(path: str, mode: int = Path.unix) -> str:
|
|
||||||
"""Formats the input path to use consistent, platform specific separators.
|
|
||||||
|
|
||||||
Absolute paths are converted between drive letters and a prepended '/' as per platform
|
|
||||||
requirement.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
path: the path to be normalized, must be a string or expose the replace method.
|
|
||||||
mode: the path file separator style to normalize the passed path to.
|
|
||||||
Default is unix style, i.e. '/'
|
|
||||||
"""
|
|
||||||
if not path:
|
|
||||||
return path
|
|
||||||
if mode == Path.windows:
|
|
||||||
path = path.replace("/", "\\")
|
|
||||||
else:
|
|
||||||
path = path.replace("\\", "/")
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_posix_path(path: str) -> str:
|
|
||||||
"""Converts the input path to POSIX style."""
|
|
||||||
return format_os_path(path, mode=Path.unix)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_windows_path(path: str) -> str:
|
|
||||||
"""Converts the input path to Windows style."""
|
|
||||||
return format_os_path(path, mode=Path.windows)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_platform_path(path: str) -> str:
|
|
||||||
"""Converts the input path to the current platform's native style."""
|
|
||||||
return format_os_path(path, mode=Path.platform_path)
|
|
||||||
|
|
||||||
|
|
||||||
def path_to_os_path(*parameters: str) -> List[str]:
|
|
||||||
"""Takes an arbitrary number of positional parameters, converts each argument of type
|
|
||||||
string to use a normalized filepath separator, and returns a list of all values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _is_url(path_or_url: str) -> bool:
|
|
||||||
if "\\" in path_or_url:
|
|
||||||
return False
|
|
||||||
url_tuple = urlparse(path_or_url)
|
|
||||||
return bool(url_tuple.scheme) and len(url_tuple.scheme) > 1
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for item in parameters:
|
|
||||||
if isinstance(item, str) and not _is_url(item):
|
|
||||||
item = convert_to_platform_path(item)
|
|
||||||
result.append(item)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def system_path_filter(_func=None, arg_slice: Optional[slice] = None):
|
|
||||||
"""Filters function arguments to account for platform path separators.
|
|
||||||
Optional slicing range can be specified to select specific arguments
|
|
||||||
|
|
||||||
This decorator takes all (or a slice) of a method's positional arguments
|
|
||||||
and normalizes usage of filepath separators on a per platform basis.
|
|
||||||
|
|
||||||
Note: `**kwargs`, urls, and any type that is not a string are ignored
|
|
||||||
so in such cases where path normalization is required, that should be
|
|
||||||
handled by calling path_to_os_path directly as needed.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
arg_slice: a slice object specifying the slice of arguments
|
|
||||||
in the decorated method over which filepath separators are
|
|
||||||
normalized
|
|
||||||
"""
|
|
||||||
|
|
||||||
def holder_func(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def path_filter_caller(*args, **kwargs):
|
|
||||||
args = list(args)
|
|
||||||
if arg_slice:
|
|
||||||
args[arg_slice] = path_to_os_path(*args[arg_slice])
|
|
||||||
else:
|
|
||||||
args = path_to_os_path(*args)
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
return path_filter_caller
|
|
||||||
|
|
||||||
if _func:
|
|
||||||
return holder_func(_func)
|
|
||||||
return holder_func
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
|
||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
"""String manipulation functions that do not have other dependencies than Python
|
|
||||||
standard library
|
|
||||||
"""
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
def comma_list(sequence: List[str], article: str = "") -> str:
|
|
||||||
if type(sequence) is not list:
|
|
||||||
sequence = list(sequence)
|
|
||||||
|
|
||||||
if not sequence:
|
|
||||||
return ""
|
|
||||||
if len(sequence) == 1:
|
|
||||||
return sequence[0]
|
|
||||||
|
|
||||||
out = ", ".join(str(s) for s in sequence[:-1])
|
|
||||||
if len(sequence) != 2:
|
|
||||||
out += "," # oxford comma
|
|
||||||
out += " "
|
|
||||||
if article:
|
|
||||||
out += article + " "
|
|
||||||
out += str(sequence[-1])
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def comma_or(sequence: List[str]) -> str:
|
|
||||||
"""Return a string with all the elements of the input joined by comma, but the last
|
|
||||||
one (which is joined by 'or').
|
|
||||||
"""
|
|
||||||
return comma_list(sequence, "or")
|
|
||||||
|
|
||||||
|
|
||||||
def comma_and(sequence: List[str]) -> str:
|
|
||||||
"""Return a string with all the elements of the input joined by comma, but the last
|
|
||||||
one (which is joined by 'and').
|
|
||||||
"""
|
|
||||||
return comma_list(sequence, "and")
|
|
||||||
|
|
||||||
|
|
||||||
def quote(sequence: List[str], q: str = "'") -> List[str]:
|
|
||||||
"""Quotes each item in the input list with the quote character passed as second argument."""
|
|
||||||
return [f"{q}{e}{q}" for e in sequence]
|
|
||||||
|
|
||||||
|
|
||||||
def plural(n: int, singular: str, plural: Optional[str] = None, show_n: bool = True) -> str:
|
|
||||||
"""Pluralize <singular> word by adding an s if n != 1.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
n: number of things there are
|
|
||||||
singular: singular form of word
|
|
||||||
plural: optional plural form, for when it's not just singular + 's'
|
|
||||||
show_n: whether to include n in the result string (default True)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
"1 thing" if n == 1 or "n things" if n != 1
|
|
||||||
"""
|
|
||||||
number = f"{n} " if show_n else ""
|
|
||||||
if n == 1:
|
|
||||||
return f"{number}{singular}"
|
|
||||||
elif plural is not None:
|
|
||||||
return f"{number}{plural}"
|
|
||||||
else:
|
|
||||||
return f"{number}{singular}s"
|
|
||||||
@@ -1,459 +0,0 @@
|
|||||||
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
|
||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
"""URL primitives that just require Python standard library."""
|
|
||||||
import itertools
|
|
||||||
import os.path
|
|
||||||
import re
|
|
||||||
from typing import Optional, Set, Tuple
|
|
||||||
from urllib.parse import urlsplit, urlunsplit
|
|
||||||
|
|
||||||
# Archive extensions allowed in Spack
|
|
||||||
PREFIX_EXTENSIONS = ("tar", "TAR")
|
|
||||||
EXTENSIONS = ("gz", "bz2", "xz", "Z")
|
|
||||||
NO_TAR_EXTENSIONS = ("zip", "tgz", "tbz2", "tbz", "txz")
|
|
||||||
|
|
||||||
# Add PREFIX_EXTENSIONS and EXTENSIONS last so that .tar.gz is matched *before* .tar or .gz
|
|
||||||
ALLOWED_ARCHIVE_TYPES = (
|
|
||||||
tuple(".".join(ext) for ext in itertools.product(PREFIX_EXTENSIONS, EXTENSIONS))
|
|
||||||
+ PREFIX_EXTENSIONS
|
|
||||||
+ EXTENSIONS
|
|
||||||
+ NO_TAR_EXTENSIONS
|
|
||||||
)
|
|
||||||
CONTRACTION_MAP = {"tgz": "tar.gz", "txz": "tar.xz", "tbz": "tar.bz2", "tbz2": "tar.bz2"}
|
|
||||||
|
|
||||||
|
|
||||||
def find_list_urls(url: str) -> Set[str]:
|
|
||||||
r"""Find good list URLs for the supplied URL.
|
|
||||||
|
|
||||||
By default, returns the dirname of the archive path.
|
|
||||||
|
|
||||||
Provides special treatment for the following websites, which have a
|
|
||||||
unique list URL different from the dirname of the download URL:
|
|
||||||
|
|
||||||
========= =======================================================
|
|
||||||
GitHub https://github.com/<repo>/<name>/releases
|
|
||||||
GitLab https://gitlab.\*/<repo>/<name>/tags
|
|
||||||
BitBucket https://bitbucket.org/<repo>/<name>/downloads/?tab=tags
|
|
||||||
CRAN https://\*.r-project.org/src/contrib/Archive/<name>
|
|
||||||
PyPI https://pypi.org/simple/<name>/
|
|
||||||
LuaRocks https://luarocks.org/modules/<repo>/<name>
|
|
||||||
========= =======================================================
|
|
||||||
|
|
||||||
Note: this function is called by `spack versions`, `spack checksum`,
|
|
||||||
and `spack create`, but not by `spack fetch` or `spack install`.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
url (str): The download URL for the package
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
set: One or more list URLs for the package
|
|
||||||
"""
|
|
||||||
|
|
||||||
url_types = [
|
|
||||||
# GitHub
|
|
||||||
# e.g. https://github.com/llnl/callpath/archive/v1.0.1.tar.gz
|
|
||||||
(r"(.*github\.com/[^/]+/[^/]+)", lambda m: m.group(1) + "/releases"),
|
|
||||||
# GitLab API endpoint
|
|
||||||
# e.g. https://gitlab.dkrz.de/api/v4/projects/k202009%2Flibaec/repository/archive.tar.gz?sha=v1.0.2
|
|
||||||
(
|
|
||||||
r"(.*gitlab[^/]+)/api/v4/projects/([^/]+)%2F([^/]+)",
|
|
||||||
lambda m: m.group(1) + "/" + m.group(2) + "/" + m.group(3) + "/tags",
|
|
||||||
),
|
|
||||||
# GitLab non-API endpoint
|
|
||||||
# e.g. https://gitlab.dkrz.de/k202009/libaec/uploads/631e85bcf877c2dcaca9b2e6d6526339/libaec-1.0.0.tar.gz
|
|
||||||
(r"(.*gitlab[^/]+/(?!api/v4/projects)[^/]+/[^/]+)", lambda m: m.group(1) + "/tags"),
|
|
||||||
# BitBucket
|
|
||||||
# e.g. https://bitbucket.org/eigen/eigen/get/3.3.3.tar.bz2
|
|
||||||
(r"(.*bitbucket.org/[^/]+/[^/]+)", lambda m: m.group(1) + "/downloads/?tab=tags"),
|
|
||||||
# CRAN
|
|
||||||
# e.g. https://cran.r-project.org/src/contrib/Rcpp_0.12.9.tar.gz
|
|
||||||
# e.g. https://cloud.r-project.org/src/contrib/rgl_0.98.1.tar.gz
|
|
||||||
(
|
|
||||||
r"(.*\.r-project\.org/src/contrib)/([^_]+)",
|
|
||||||
lambda m: m.group(1) + "/Archive/" + m.group(2),
|
|
||||||
),
|
|
||||||
# PyPI
|
|
||||||
# e.g. https://pypi.io/packages/source/n/numpy/numpy-1.19.4.zip
|
|
||||||
# e.g. https://www.pypi.io/packages/source/n/numpy/numpy-1.19.4.zip
|
|
||||||
# e.g. https://pypi.org/packages/source/n/numpy/numpy-1.19.4.zip
|
|
||||||
# e.g. https://pypi.python.org/packages/source/n/numpy/numpy-1.19.4.zip
|
|
||||||
# e.g. https://files.pythonhosted.org/packages/source/n/numpy/numpy-1.19.4.zip
|
|
||||||
# e.g. https://pypi.io/packages/py2.py3/o/opencensus-context/opencensus_context-0.1.1-py2.py3-none-any.whl
|
|
||||||
(
|
|
||||||
r"(?:pypi|pythonhosted)[^/]+/packages/[^/]+/./([^/]+)",
|
|
||||||
lambda m: "https://pypi.org/simple/" + m.group(1) + "/",
|
|
||||||
),
|
|
||||||
# LuaRocks
|
|
||||||
# e.g. https://luarocks.org/manifests/gvvaughan/lpeg-1.0.2-1.src.rock
|
|
||||||
# e.g. https://luarocks.org/manifests/openresty/lua-cjson-2.1.0-1.src.rock
|
|
||||||
(
|
|
||||||
r"luarocks[^/]+/(?:modules|manifests)/(?P<org>[^/]+)/"
|
|
||||||
+ r"(?P<name>.+?)-[0-9.-]*\.src\.rock",
|
|
||||||
lambda m: "https://luarocks.org/modules/"
|
|
||||||
+ m.group("org")
|
|
||||||
+ "/"
|
|
||||||
+ m.group("name")
|
|
||||||
+ "/",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
list_urls = {os.path.dirname(url)}
|
|
||||||
|
|
||||||
for pattern, fun in url_types:
|
|
||||||
match = re.search(pattern, url)
|
|
||||||
if match:
|
|
||||||
list_urls.add(fun(match))
|
|
||||||
|
|
||||||
return list_urls
|
|
||||||
|
|
||||||
|
|
||||||
def strip_query_and_fragment(url: str) -> Tuple[str, str]:
|
|
||||||
"""Strips query and fragment from a url, then returns the base url and the suffix.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url: URL to be stripped
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: when there is any error parsing the URL
|
|
||||||
"""
|
|
||||||
components = urlsplit(url)
|
|
||||||
stripped = components[:3] + (None, None)
|
|
||||||
|
|
||||||
query, frag = components[3:5]
|
|
||||||
suffix = ""
|
|
||||||
if query:
|
|
||||||
suffix += "?" + query
|
|
||||||
if frag:
|
|
||||||
suffix += "#" + frag
|
|
||||||
|
|
||||||
return urlunsplit(stripped), suffix
|
|
||||||
|
|
||||||
|
|
||||||
SOURCEFORGE_RE = re.compile(r"(.*(?:sourceforge\.net|sf\.net)/.*)(/download)$")
|
|
||||||
|
|
||||||
|
|
||||||
def split_url_on_sourceforge_suffix(url: str) -> Tuple[str, ...]:
|
|
||||||
"""If the input is a sourceforge URL, returns base URL and "/download" suffix. Otherwise,
|
|
||||||
returns the input URL and an empty string.
|
|
||||||
"""
|
|
||||||
match = SOURCEFORGE_RE.search(url)
|
|
||||||
if match is not None:
|
|
||||||
return match.groups()
|
|
||||||
return url, ""
|
|
||||||
|
|
||||||
|
|
||||||
def has_extension(path_or_url: str, ext: str) -> bool:
|
|
||||||
"""Returns true if the extension in input is present in path, false otherwise."""
|
|
||||||
prefix, _ = split_url_on_sourceforge_suffix(path_or_url)
|
|
||||||
if not ext.startswith(r"\."):
|
|
||||||
ext = rf"\.{ext}$"
|
|
||||||
|
|
||||||
if re.search(ext, prefix):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def extension_from_path(path_or_url: Optional[str]) -> Optional[str]:
|
|
||||||
"""Tries to match an allowed archive extension to the input. Returns the first match,
|
|
||||||
or None if no match was found.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: if the input is None
|
|
||||||
"""
|
|
||||||
if path_or_url is None:
|
|
||||||
raise ValueError("Can't call extension() on None")
|
|
||||||
|
|
||||||
for t in ALLOWED_ARCHIVE_TYPES:
|
|
||||||
if has_extension(path_or_url, t):
|
|
||||||
return t
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def remove_extension(path_or_url: str, *, extension: str) -> str:
|
|
||||||
"""Returns the input with the extension removed"""
|
|
||||||
suffix = rf"\.{extension}$"
|
|
||||||
return re.sub(suffix, "", path_or_url)
|
|
||||||
|
|
||||||
|
|
||||||
def check_and_remove_ext(path: str, *, extension: str) -> str:
|
|
||||||
"""Returns the input path with the extension removed, if the extension is present in path.
|
|
||||||
Otherwise, returns the input unchanged.
|
|
||||||
"""
|
|
||||||
if not has_extension(path, extension):
|
|
||||||
return path
|
|
||||||
path, _ = split_url_on_sourceforge_suffix(path)
|
|
||||||
return remove_extension(path, extension=extension)
|
|
||||||
|
|
||||||
|
|
||||||
def strip_extension(path_or_url: str, *, extension: Optional[str] = None) -> str:
|
|
||||||
"""If a path contains the extension in input, returns the path stripped of the extension.
|
|
||||||
Otherwise, returns the input path.
|
|
||||||
|
|
||||||
If extension is None, attempts to strip any allowed extension from path.
|
|
||||||
"""
|
|
||||||
if extension is None:
|
|
||||||
for t in ALLOWED_ARCHIVE_TYPES:
|
|
||||||
if has_extension(path_or_url, ext=t):
|
|
||||||
extension = t
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return path_or_url
|
|
||||||
|
|
||||||
return check_and_remove_ext(path_or_url, extension=extension)
|
|
||||||
|
|
||||||
|
|
||||||
def split_url_extension(url: str) -> Tuple[str, ...]:
|
|
||||||
"""Some URLs have a query string, e.g.:
|
|
||||||
|
|
||||||
1. https://github.com/losalamos/CLAMR/blob/packages/PowerParser_v2.0.7.tgz?raw=true
|
|
||||||
2. http://www.apache.org/dyn/closer.cgi?path=/cassandra/1.2.0/apache-cassandra-1.2.0-rc2-bin.tar.gz
|
|
||||||
3. https://gitlab.kitware.com/vtk/vtk/repository/archive.tar.bz2?ref=v7.0.0
|
|
||||||
|
|
||||||
In (1), the query string needs to be stripped to get at the
|
|
||||||
extension, but in (2) & (3), the filename is IN a single final query
|
|
||||||
argument.
|
|
||||||
|
|
||||||
This strips the URL into three pieces: ``prefix``, ``ext``, and ``suffix``.
|
|
||||||
The suffix contains anything that was stripped off the URL to
|
|
||||||
get at the file extension. In (1), it will be ``'?raw=true'``, but
|
|
||||||
in (2), it will be empty. In (3) the suffix is a parameter that follows
|
|
||||||
after the file extension, e.g.:
|
|
||||||
|
|
||||||
1. ``('https://github.com/losalamos/CLAMR/blob/packages/PowerParser_v2.0.7', '.tgz', '?raw=true')``
|
|
||||||
2. ``('http://www.apache.org/dyn/closer.cgi?path=/cassandra/1.2.0/apache-cassandra-1.2.0-rc2-bin', '.tar.gz', None)``
|
|
||||||
3. ``('https://gitlab.kitware.com/vtk/vtk/repository/archive', '.tar.bz2', '?ref=v7.0.0')``
|
|
||||||
"""
|
|
||||||
# Strip off sourceforge download suffix.
|
|
||||||
# e.g. https://sourceforge.net/projects/glew/files/glew/2.0.0/glew-2.0.0.tgz/download
|
|
||||||
prefix, suffix = split_url_on_sourceforge_suffix(url)
|
|
||||||
|
|
||||||
ext = extension_from_path(prefix)
|
|
||||||
if ext is not None:
|
|
||||||
prefix = strip_extension(prefix)
|
|
||||||
return prefix, ext, suffix
|
|
||||||
|
|
||||||
try:
|
|
||||||
prefix, suf = strip_query_and_fragment(prefix)
|
|
||||||
except ValueError:
|
|
||||||
# FIXME: tty.debug("Got error parsing path %s" % path)
|
|
||||||
# Ignore URL parse errors here
|
|
||||||
return url, ""
|
|
||||||
|
|
||||||
ext = extension_from_path(prefix)
|
|
||||||
prefix = strip_extension(prefix)
|
|
||||||
suffix = suf + suffix
|
|
||||||
if ext is None:
|
|
||||||
ext = ""
|
|
||||||
|
|
||||||
return prefix, ext, suffix
|
|
||||||
|
|
||||||
|
|
||||||
def strip_version_suffixes(path_or_url: str) -> str:
|
|
||||||
"""Some tarballs contain extraneous information after the version:
|
|
||||||
|
|
||||||
* ``bowtie2-2.2.5-source``
|
|
||||||
* ``libevent-2.0.21-stable``
|
|
||||||
* ``cuda_8.0.44_linux.run``
|
|
||||||
|
|
||||||
These strings are not part of the version number and should be ignored.
|
|
||||||
This function strips those suffixes off and returns the remaining string.
|
|
||||||
The goal is that the version is always the last thing in ``path``:
|
|
||||||
|
|
||||||
* ``bowtie2-2.2.5``
|
|
||||||
* ``libevent-2.0.21``
|
|
||||||
* ``cuda_8.0.44``
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path_or_url: The filename or URL for the package
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The ``path`` with any extraneous suffixes removed
|
|
||||||
"""
|
|
||||||
# NOTE: This could be done with complicated regexes in parse_version_offset
|
|
||||||
# NOTE: The problem is that we would have to add these regexes to the end
|
|
||||||
# NOTE: of every single version regex. Easier to just strip them off
|
|
||||||
# NOTE: permanently
|
|
||||||
|
|
||||||
suffix_regexes = [
|
|
||||||
# Download type
|
|
||||||
r"[Ii]nstall",
|
|
||||||
r"all",
|
|
||||||
r"code",
|
|
||||||
r"[Ss]ources?",
|
|
||||||
r"file",
|
|
||||||
r"full",
|
|
||||||
r"single",
|
|
||||||
r"with[a-zA-Z_-]+",
|
|
||||||
r"rock",
|
|
||||||
r"src(_0)?",
|
|
||||||
r"public",
|
|
||||||
r"bin",
|
|
||||||
r"binary",
|
|
||||||
r"run",
|
|
||||||
r"[Uu]niversal",
|
|
||||||
r"jar",
|
|
||||||
r"complete",
|
|
||||||
r"dynamic",
|
|
||||||
r"oss",
|
|
||||||
r"gem",
|
|
||||||
r"tar",
|
|
||||||
r"sh",
|
|
||||||
# Download version
|
|
||||||
r"release",
|
|
||||||
r"bin",
|
|
||||||
r"stable",
|
|
||||||
r"[Ff]inal",
|
|
||||||
r"rel",
|
|
||||||
r"orig",
|
|
||||||
r"dist",
|
|
||||||
r"\+",
|
|
||||||
# License
|
|
||||||
r"gpl",
|
|
||||||
# Arch
|
|
||||||
# Needs to come before and after OS, appears in both orders
|
|
||||||
r"ia32",
|
|
||||||
r"intel",
|
|
||||||
r"amd64",
|
|
||||||
r"linux64",
|
|
||||||
r"x64",
|
|
||||||
r"64bit",
|
|
||||||
r"x86[_-]64",
|
|
||||||
r"i586_64",
|
|
||||||
r"x86",
|
|
||||||
r"i[36]86",
|
|
||||||
r"ppc64(le)?",
|
|
||||||
r"armv?(7l|6l|64)",
|
|
||||||
# Other
|
|
||||||
r"cpp",
|
|
||||||
r"gtk",
|
|
||||||
r"incubating",
|
|
||||||
# OS
|
|
||||||
r"[Ll]inux(_64)?",
|
|
||||||
r"LINUX",
|
|
||||||
r"[Uu]ni?x",
|
|
||||||
r"[Ss]un[Oo][Ss]",
|
|
||||||
r"[Mm]ac[Oo][Ss][Xx]?",
|
|
||||||
r"[Oo][Ss][Xx]",
|
|
||||||
r"[Dd]arwin(64)?",
|
|
||||||
r"[Aa]pple",
|
|
||||||
r"[Ww]indows",
|
|
||||||
r"[Ww]in(64|32)?",
|
|
||||||
r"[Cc]ygwin(64|32)?",
|
|
||||||
r"[Mm]ingw",
|
|
||||||
r"centos",
|
|
||||||
# Arch
|
|
||||||
# Needs to come before and after OS, appears in both orders
|
|
||||||
r"ia32",
|
|
||||||
r"intel",
|
|
||||||
r"amd64",
|
|
||||||
r"linux64",
|
|
||||||
r"x64",
|
|
||||||
r"64bit",
|
|
||||||
r"x86[_-]64",
|
|
||||||
r"i586_64",
|
|
||||||
r"x86",
|
|
||||||
r"i[36]86",
|
|
||||||
r"ppc64(le)?",
|
|
||||||
r"armv?(7l|6l|64)?",
|
|
||||||
# PyPI
|
|
||||||
r"[._-]py[23].*\.whl",
|
|
||||||
r"[._-]cp[23].*\.whl",
|
|
||||||
r"[._-]win.*\.exe",
|
|
||||||
]
|
|
||||||
|
|
||||||
for regex in suffix_regexes:
|
|
||||||
# Remove the suffix from the end of the path
|
|
||||||
# This may be done multiple times
|
|
||||||
path_or_url = re.sub(r"[._-]?" + regex + "$", "", path_or_url)
|
|
||||||
|
|
||||||
return path_or_url
|
|
||||||
|
|
||||||
|
|
||||||
def expand_contracted_extension(extension: str) -> str:
|
|
||||||
"""Returns the expanded version of a known contracted extension.
|
|
||||||
|
|
||||||
This function maps extensions like ".tgz" to ".tar.gz". On unknown extensions,
|
|
||||||
return the input unmodified.
|
|
||||||
"""
|
|
||||||
extension = extension.strip(".")
|
|
||||||
return CONTRACTION_MAP.get(extension, extension)
|
|
||||||
|
|
||||||
|
|
||||||
def expand_contracted_extension_in_path(
|
|
||||||
path_or_url: str, *, extension: Optional[str] = None
|
|
||||||
) -> str:
|
|
||||||
"""Returns the input path or URL with any contraction extension expanded.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path_or_url: path or URL to be expanded
|
|
||||||
extension: if specified, only attempt to expand that extension
|
|
||||||
"""
|
|
||||||
extension = extension or extension_from_path(path_or_url)
|
|
||||||
if extension is None:
|
|
||||||
return path_or_url
|
|
||||||
|
|
||||||
expanded = expand_contracted_extension(extension)
|
|
||||||
if expanded != extension:
|
|
||||||
return re.sub(rf"{extension}", rf"{expanded}", path_or_url)
|
|
||||||
return path_or_url
|
|
||||||
|
|
||||||
|
|
||||||
def compression_ext_from_compressed_archive(extension: str) -> Optional[str]:
|
|
||||||
"""Returns compression extension for a compressed archive"""
|
|
||||||
extension = expand_contracted_extension(extension)
|
|
||||||
for ext in [*EXTENSIONS]:
|
|
||||||
if ext in extension:
|
|
||||||
return ext
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def strip_compression_extension(path_or_url: str, ext: Optional[str] = None) -> str:
|
|
||||||
"""Strips the compression extension from the input, and returns it. For instance,
|
|
||||||
"foo.tgz" becomes "foo.tar".
|
|
||||||
|
|
||||||
If no extension is given, try a default list of extensions.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path_or_url: input to be stripped
|
|
||||||
ext: if given, extension to be stripped
|
|
||||||
"""
|
|
||||||
if not extension_from_path(path_or_url):
|
|
||||||
return path_or_url
|
|
||||||
|
|
||||||
expanded_path = expand_contracted_extension_in_path(path_or_url)
|
|
||||||
candidates = [ext] if ext is not None else EXTENSIONS
|
|
||||||
for current_extension in candidates:
|
|
||||||
modified_path = check_and_remove_ext(expanded_path, extension=current_extension)
|
|
||||||
if modified_path != expanded_path:
|
|
||||||
return modified_path
|
|
||||||
return expanded_path
|
|
||||||
|
|
||||||
|
|
||||||
def allowed_archive(path_or_url: str) -> bool:
|
|
||||||
"""Returns true if the input is a valid archive, False otherwise."""
|
|
||||||
return (
|
|
||||||
False if not path_or_url else any(path_or_url.endswith(t) for t in ALLOWED_ARCHIVE_TYPES)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def determine_url_file_extension(path: str) -> str:
|
|
||||||
"""This returns the type of archive a URL refers to. This is
|
|
||||||
sometimes confusing because of URLs like:
|
|
||||||
|
|
||||||
(1) https://github.com/petdance/ack/tarball/1.93_02
|
|
||||||
|
|
||||||
Where the URL doesn't actually contain the filename. We need
|
|
||||||
to know what type it is so that we can appropriately name files
|
|
||||||
in mirrors.
|
|
||||||
"""
|
|
||||||
match = re.search(r"github.com/.+/(zip|tar)ball/", path)
|
|
||||||
if match:
|
|
||||||
if match.group(1) == "zip":
|
|
||||||
return "zip"
|
|
||||||
elif match.group(1) == "tar":
|
|
||||||
return "tar.gz"
|
|
||||||
|
|
||||||
prefix, ext, suffix = split_url_extension(path)
|
|
||||||
return ext
|
|
||||||
@@ -3,42 +3,33 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
import abc
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import errno
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
|
||||||
from typing import IO, Any, Iterable, List, Optional, Sequence, Tuple, Union
|
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command(object):
|
||||||
"""Parsed representation of a command from argparse.
|
"""Parsed representation of a command from argparse.
|
||||||
|
|
||||||
This is a single command from an argparse parser. ``ArgparseWriter`` creates these and returns
|
This is a single command from an argparse parser. ``ArgparseWriter``
|
||||||
them from ``parse()``, and it passes one of these to each call to ``format()`` so that we can
|
creates these and returns them from ``parse()``, and it passes one of
|
||||||
take an action for a single command.
|
these to each call to ``format()`` so that we can take an action for
|
||||||
|
a single command.
|
||||||
|
|
||||||
|
Parts of a Command:
|
||||||
|
- prog: command name (str)
|
||||||
|
- description: command description (str)
|
||||||
|
- usage: command usage (str)
|
||||||
|
- positionals: list of positional arguments (list)
|
||||||
|
- optionals: list of optional arguments (list)
|
||||||
|
- subcommands: list of subcommand parsers (list)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, prog, description, usage, positionals, optionals, subcommands):
|
||||||
self,
|
|
||||||
prog: str,
|
|
||||||
description: Optional[str],
|
|
||||||
usage: str,
|
|
||||||
positionals: List[Tuple[str, Optional[Iterable[Any]], Union[int, str, None], str]],
|
|
||||||
optionals: List[Tuple[Sequence[str], List[str], str, Union[int, str, None], str]],
|
|
||||||
subcommands: List[Tuple[ArgumentParser, str, str]],
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a new Command instance.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prog: Program name.
|
|
||||||
description: Command description.
|
|
||||||
usage: Command usage.
|
|
||||||
positionals: List of positional arguments.
|
|
||||||
optionals: List of optional arguments.
|
|
||||||
subcommands: List of subcommand parsers.
|
|
||||||
"""
|
|
||||||
self.prog = prog
|
self.prog = prog
|
||||||
self.description = description
|
self.description = description
|
||||||
self.usage = usage
|
self.usage = usage
|
||||||
@@ -47,34 +38,35 @@ def __init__(
|
|||||||
self.subcommands = subcommands
|
self.subcommands = subcommands
|
||||||
|
|
||||||
|
|
||||||
# NOTE: The only reason we subclass argparse.HelpFormatter is to get access to self._expand_help(),
|
# NOTE: The only reason we subclass argparse.HelpFormatter is to get access
|
||||||
# ArgparseWriter is not intended to be used as a formatter_class.
|
# to self._expand_help(), ArgparseWriter is not intended to be used as a
|
||||||
class ArgparseWriter(argparse.HelpFormatter, abc.ABC):
|
# formatter_class.
|
||||||
"""Analyze an argparse ArgumentParser for easy generation of help."""
|
class ArgparseWriter(argparse.HelpFormatter):
|
||||||
|
"""Analyzes an argparse ArgumentParser for easy generation of help."""
|
||||||
|
|
||||||
def __init__(self, prog: str, out: IO = sys.stdout, aliases: bool = False) -> None:
|
def __init__(self, prog, out=None, aliases=False):
|
||||||
"""Initialize a new ArgparseWriter instance.
|
"""Initializes a new ArgparseWriter instance.
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
prog: Program name.
|
prog (str): the program name
|
||||||
out: File object to write to.
|
out (file object): the file to write to (default sys.stdout)
|
||||||
aliases: Whether or not to include subparsers for aliases.
|
aliases (bool): whether or not to include subparsers for aliases
|
||||||
"""
|
"""
|
||||||
super().__init__(prog)
|
super(ArgparseWriter, self).__init__(prog)
|
||||||
self.level = 0
|
self.level = 0
|
||||||
self.prog = prog
|
self.prog = prog
|
||||||
self.out = out
|
self.out = sys.stdout if out is None else out
|
||||||
self.aliases = aliases
|
self.aliases = aliases
|
||||||
|
|
||||||
def parse(self, parser: ArgumentParser, prog: str) -> Command:
|
def parse(self, parser, prog):
|
||||||
"""Parse the parser object and return the relavent components.
|
"""Parses the parser object and returns the relavent components.
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
parser: Command parser.
|
parser (argparse.ArgumentParser): the parser
|
||||||
prog: Program name.
|
prog (str): the command name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Information about the command from the parser.
|
(Command) information about the command from the parser
|
||||||
"""
|
"""
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
|
||||||
@@ -88,7 +80,8 @@ def parse(self, parser: ArgumentParser, prog: str) -> Command:
|
|||||||
groups = parser._mutually_exclusive_groups
|
groups = parser._mutually_exclusive_groups
|
||||||
usage = fmt._format_usage(None, actions, groups, "").strip()
|
usage = fmt._format_usage(None, actions, groups, "").strip()
|
||||||
|
|
||||||
# Go through actions and split them into optionals, positionals, and subcommands
|
# Go through actions and split them into optionals, positionals,
|
||||||
|
# and subcommands
|
||||||
optionals = []
|
optionals = []
|
||||||
positionals = []
|
positionals = []
|
||||||
subcommands = []
|
subcommands = []
|
||||||
@@ -96,97 +89,74 @@ def parse(self, parser: ArgumentParser, prog: str) -> Command:
|
|||||||
if action.option_strings:
|
if action.option_strings:
|
||||||
flags = action.option_strings
|
flags = action.option_strings
|
||||||
dest_flags = fmt._format_action_invocation(action)
|
dest_flags = fmt._format_action_invocation(action)
|
||||||
nargs = action.nargs
|
help = self._expand_help(action) if action.help else ""
|
||||||
help = (
|
help = help.replace("\n", " ")
|
||||||
self._expand_help(action)
|
optionals.append((flags, dest_flags, help))
|
||||||
if action.help and action.help != argparse.SUPPRESS
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
help = help.split("\n")[0]
|
|
||||||
|
|
||||||
if action.choices is not None:
|
|
||||||
dest = [str(choice) for choice in action.choices]
|
|
||||||
else:
|
|
||||||
dest = [action.dest]
|
|
||||||
|
|
||||||
optionals.append((flags, dest, dest_flags, nargs, help))
|
|
||||||
elif isinstance(action, argparse._SubParsersAction):
|
elif isinstance(action, argparse._SubParsersAction):
|
||||||
for subaction in action._choices_actions:
|
for subaction in action._choices_actions:
|
||||||
subparser = action._name_parser_map[subaction.dest]
|
subparser = action._name_parser_map[subaction.dest]
|
||||||
help = (
|
subcommands.append((subparser, subaction.dest))
|
||||||
self._expand_help(subaction)
|
|
||||||
if subaction.help and action.help != argparse.SUPPRESS
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
help = help.split("\n")[0]
|
|
||||||
subcommands.append((subparser, subaction.dest, help))
|
|
||||||
|
|
||||||
# Look for aliases of the form 'name (alias, ...)'
|
# Look for aliases of the form 'name (alias, ...)'
|
||||||
if self.aliases and isinstance(subaction.metavar, str):
|
if self.aliases:
|
||||||
match = re.match(r"(.*) \((.*)\)", subaction.metavar)
|
match = re.match(r"(.*) \((.*)\)", subaction.metavar)
|
||||||
if match:
|
if match:
|
||||||
aliases = match.group(2).split(", ")
|
aliases = match.group(2).split(", ")
|
||||||
for alias in aliases:
|
for alias in aliases:
|
||||||
subparser = action._name_parser_map[alias]
|
subparser = action._name_parser_map[alias]
|
||||||
help = (
|
subcommands.append((subparser, alias))
|
||||||
self._expand_help(subaction)
|
|
||||||
if subaction.help and action.help != argparse.SUPPRESS
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
help = help.split("\n")[0]
|
|
||||||
subcommands.append((subparser, alias, help))
|
|
||||||
else:
|
else:
|
||||||
args = fmt._format_action_invocation(action)
|
args = fmt._format_action_invocation(action)
|
||||||
help = (
|
help = self._expand_help(action) if action.help else ""
|
||||||
self._expand_help(action)
|
help = help.replace("\n", " ")
|
||||||
if action.help and action.help != argparse.SUPPRESS
|
positionals.append((args, help))
|
||||||
else ""
|
|
||||||
)
|
|
||||||
help = help.split("\n")[0]
|
|
||||||
positionals.append((args, action.choices, action.nargs, help))
|
|
||||||
|
|
||||||
return Command(prog, description, usage, positionals, optionals, subcommands)
|
return Command(prog, description, usage, positionals, optionals, subcommands)
|
||||||
|
|
||||||
@abc.abstractmethod
|
def format(self, cmd):
|
||||||
def format(self, cmd: Command) -> str:
|
"""Returns the string representation of a single node in the
|
||||||
"""Return the string representation of a single node in the parser tree.
|
parser tree.
|
||||||
|
|
||||||
Override this in subclasses to define how each subcommand should be displayed.
|
Override this in subclasses to define how each subcommand
|
||||||
|
should be displayed.
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
cmd: Parsed information about a command or subcommand.
|
(Command): parsed information about a command or subcommand
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
String representation of this subcommand.
|
str: the string representation of this subcommand
|
||||||
"""
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def _write(self, parser: ArgumentParser, prog: str, level: int = 0) -> None:
|
def _write(self, parser, prog, level=0):
|
||||||
"""Recursively write a parser.
|
"""Recursively writes a parser.
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
parser: Command parser.
|
parser (argparse.ArgumentParser): the parser
|
||||||
prog: Program name.
|
prog (str): the command name
|
||||||
level: Current level.
|
level (int): the current level
|
||||||
"""
|
"""
|
||||||
self.level = level
|
self.level = level
|
||||||
|
|
||||||
cmd = self.parse(parser, prog)
|
cmd = self.parse(parser, prog)
|
||||||
self.out.write(self.format(cmd))
|
self.out.write(self.format(cmd))
|
||||||
|
|
||||||
for subparser, prog, help in cmd.subcommands:
|
for subparser, prog in cmd.subcommands:
|
||||||
self._write(subparser, prog, level=level + 1)
|
self._write(subparser, prog, level=level + 1)
|
||||||
|
|
||||||
def write(self, parser: ArgumentParser) -> None:
|
def write(self, parser):
|
||||||
"""Write out details about an ArgumentParser.
|
"""Write out details about an ArgumentParser.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parser: Command parser.
|
parser (argparse.ArgumentParser): the parser
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self._write(parser, self.prog)
|
self._write(parser, self.prog)
|
||||||
except BrokenPipeError:
|
except IOError as e:
|
||||||
# Swallow pipe errors
|
# Swallow pipe errors
|
||||||
pass
|
# Raises IOError in Python 2 and BrokenPipeError in Python 3
|
||||||
|
if e.errno != errno.EPIPE:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
_rst_levels = ["=", "-", "^", "~", ":", "`"]
|
_rst_levels = ["=", "-", "^", "~", ":", "`"]
|
||||||
@@ -195,33 +165,21 @@ def write(self, parser: ArgumentParser) -> None:
|
|||||||
class ArgparseRstWriter(ArgparseWriter):
|
class ArgparseRstWriter(ArgparseWriter):
|
||||||
"""Write argparse output as rst sections."""
|
"""Write argparse output as rst sections."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, prog, out=None, aliases=False, rst_levels=_rst_levels):
|
||||||
self,
|
"""Create a new ArgparseRstWriter.
|
||||||
prog: str,
|
|
||||||
out: IO = sys.stdout,
|
|
||||||
aliases: bool = False,
|
|
||||||
rst_levels: Sequence[str] = _rst_levels,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a new ArgparseRstWriter instance.
|
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
prog: Program name.
|
prog (str): program name
|
||||||
out: File object to write to.
|
out (file object): file to write to
|
||||||
aliases: Whether or not to include subparsers for aliases.
|
aliases (bool): whether or not to include subparsers for aliases
|
||||||
rst_levels: List of characters for rst section headings.
|
rst_levels (list of str): list of characters
|
||||||
|
for rst section headings
|
||||||
"""
|
"""
|
||||||
super().__init__(prog, out, aliases)
|
out = sys.stdout if out is None else out
|
||||||
|
super(ArgparseRstWriter, self).__init__(prog, out, aliases)
|
||||||
self.rst_levels = rst_levels
|
self.rst_levels = rst_levels
|
||||||
|
|
||||||
def format(self, cmd: Command) -> str:
|
def format(self, cmd):
|
||||||
"""Return the string representation of a single node in the parser tree.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cmd: Parsed information about a command or subcommand.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
String representation of a node.
|
|
||||||
"""
|
|
||||||
string = io.StringIO()
|
string = io.StringIO()
|
||||||
string.write(self.begin_command(cmd.prog))
|
string.write(self.begin_command(cmd.prog))
|
||||||
|
|
||||||
@@ -232,13 +190,13 @@ def format(self, cmd: Command) -> str:
|
|||||||
|
|
||||||
if cmd.positionals:
|
if cmd.positionals:
|
||||||
string.write(self.begin_positionals())
|
string.write(self.begin_positionals())
|
||||||
for args, choices, nargs, help in cmd.positionals:
|
for args, help in cmd.positionals:
|
||||||
string.write(self.positional(args, help))
|
string.write(self.positional(args, help))
|
||||||
string.write(self.end_positionals())
|
string.write(self.end_positionals())
|
||||||
|
|
||||||
if cmd.optionals:
|
if cmd.optionals:
|
||||||
string.write(self.begin_optionals())
|
string.write(self.begin_optionals())
|
||||||
for flags, dest, dest_flags, nargs, help in cmd.optionals:
|
for flags, dest_flags, help in cmd.optionals:
|
||||||
string.write(self.optional(dest_flags, help))
|
string.write(self.optional(dest_flags, help))
|
||||||
string.write(self.end_optionals())
|
string.write(self.end_optionals())
|
||||||
|
|
||||||
@@ -247,15 +205,7 @@ def format(self, cmd: Command) -> str:
|
|||||||
|
|
||||||
return string.getvalue()
|
return string.getvalue()
|
||||||
|
|
||||||
def begin_command(self, prog: str) -> str:
|
def begin_command(self, prog):
|
||||||
"""Text to print before a command.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prog: Program name.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Text before a command.
|
|
||||||
"""
|
|
||||||
return """
|
return """
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -268,26 +218,10 @@ def begin_command(self, prog: str) -> str:
|
|||||||
prog.replace(" ", "-"), prog, self.rst_levels[self.level] * len(prog)
|
prog.replace(" ", "-"), prog, self.rst_levels[self.level] * len(prog)
|
||||||
)
|
)
|
||||||
|
|
||||||
def description(self, description: str) -> str:
|
def description(self, description):
|
||||||
"""Description of a command.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
description: Command description.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Description of a command.
|
|
||||||
"""
|
|
||||||
return description + "\n\n"
|
return description + "\n\n"
|
||||||
|
|
||||||
def usage(self, usage: str) -> str:
|
def usage(self, usage):
|
||||||
"""Example usage of a command.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
usage: Command usage.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Usage of a command.
|
|
||||||
"""
|
|
||||||
return """\
|
return """\
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
@@ -297,24 +231,10 @@ def usage(self, usage: str) -> str:
|
|||||||
usage
|
usage
|
||||||
)
|
)
|
||||||
|
|
||||||
def begin_positionals(self) -> str:
|
def begin_positionals(self):
|
||||||
"""Text to print before positional arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Positional arguments header.
|
|
||||||
"""
|
|
||||||
return "\n**Positional arguments**\n\n"
|
return "\n**Positional arguments**\n\n"
|
||||||
|
|
||||||
def positional(self, name: str, help: str) -> str:
|
def positional(self, name, help):
|
||||||
"""Description of a positional argument.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: Argument name.
|
|
||||||
help: Help text.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Positional argument description.
|
|
||||||
"""
|
|
||||||
return """\
|
return """\
|
||||||
{0}
|
{0}
|
||||||
{1}
|
{1}
|
||||||
@@ -323,32 +243,13 @@ def positional(self, name: str, help: str) -> str:
|
|||||||
name, help
|
name, help
|
||||||
)
|
)
|
||||||
|
|
||||||
def end_positionals(self) -> str:
|
def end_positionals(self):
|
||||||
"""Text to print after positional arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Positional arguments footer.
|
|
||||||
"""
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def begin_optionals(self) -> str:
|
def begin_optionals(self):
|
||||||
"""Text to print before optional arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Optional arguments header.
|
|
||||||
"""
|
|
||||||
return "\n**Optional arguments**\n\n"
|
return "\n**Optional arguments**\n\n"
|
||||||
|
|
||||||
def optional(self, opts: str, help: str) -> str:
|
def optional(self, opts, help):
|
||||||
"""Description of an optional argument.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
opts: Optional argument.
|
|
||||||
help: Help text.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Optional argument description.
|
|
||||||
"""
|
|
||||||
return """\
|
return """\
|
||||||
``{0}``
|
``{0}``
|
||||||
{1}
|
{1}
|
||||||
@@ -357,23 +258,10 @@ def optional(self, opts: str, help: str) -> str:
|
|||||||
opts, help
|
opts, help
|
||||||
)
|
)
|
||||||
|
|
||||||
def end_optionals(self) -> str:
|
def end_optionals(self):
|
||||||
"""Text to print after optional arguments.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Optional arguments footer.
|
|
||||||
"""
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def begin_subcommands(self, subcommands: List[Tuple[ArgumentParser, str, str]]) -> str:
|
def begin_subcommands(self, subcommands):
|
||||||
"""Table with links to other subcommands.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
subcommands: List of subcommands.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Subcommand linking text.
|
|
||||||
"""
|
|
||||||
string = """
|
string = """
|
||||||
**Subcommands**
|
**Subcommands**
|
||||||
|
|
||||||
@@ -382,8 +270,116 @@ def begin_subcommands(self, subcommands: List[Tuple[ArgumentParser, str, str]])
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for cmd, _, _ in subcommands:
|
for cmd, _ in subcommands:
|
||||||
prog = re.sub(r"^[^ ]* ", "", cmd.prog)
|
prog = re.sub(r"^[^ ]* ", "", cmd.prog)
|
||||||
string += " * :ref:`{0} <{1}>`\n".format(prog, cmd.prog.replace(" ", "-"))
|
string += " * :ref:`{0} <{1}>`\n".format(prog, cmd.prog.replace(" ", "-"))
|
||||||
|
|
||||||
return string + "\n"
|
return string + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
class ArgparseCompletionWriter(ArgparseWriter):
|
||||||
|
"""Write argparse output as shell programmable tab completion functions."""
|
||||||
|
|
||||||
|
def format(self, cmd):
|
||||||
|
"""Returns the string representation of a single node in the
|
||||||
|
parser tree.
|
||||||
|
|
||||||
|
Override this in subclasses to define how each subcommand
|
||||||
|
should be displayed.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
(Command): parsed information about a command or subcommand
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the string representation of this subcommand
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert cmd.optionals # we should always at least have -h, --help
|
||||||
|
assert not (cmd.positionals and cmd.subcommands) # one or the other
|
||||||
|
|
||||||
|
# We only care about the arguments/flags, not the help messages
|
||||||
|
positionals = []
|
||||||
|
if cmd.positionals:
|
||||||
|
positionals, _ = zip(*cmd.positionals)
|
||||||
|
optionals, _, _ = zip(*cmd.optionals)
|
||||||
|
subcommands = []
|
||||||
|
if cmd.subcommands:
|
||||||
|
_, subcommands = zip(*cmd.subcommands)
|
||||||
|
|
||||||
|
# Flatten lists of lists
|
||||||
|
optionals = [x for xx in optionals for x in xx]
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.start_function(cmd.prog)
|
||||||
|
+ self.body(positionals, optionals, subcommands)
|
||||||
|
+ self.end_function(cmd.prog)
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_function(self, prog):
|
||||||
|
"""Returns the syntax needed to begin a function definition.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
prog (str): the command name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the function definition beginning
|
||||||
|
"""
|
||||||
|
name = prog.replace("-", "_").replace(" ", "_")
|
||||||
|
return "\n_{0}() {{".format(name)
|
||||||
|
|
||||||
|
def end_function(self, prog=None):
|
||||||
|
"""Returns the syntax needed to end a function definition.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
prog (str or None): the command name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the function definition ending
|
||||||
|
"""
|
||||||
|
return "}\n"
|
||||||
|
|
||||||
|
def body(self, positionals, optionals, subcommands):
|
||||||
|
"""Returns the body of the function.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
positionals (list): list of positional arguments
|
||||||
|
optionals (list): list of optional arguments
|
||||||
|
subcommands (list): list of subcommand parsers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the function body
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def positionals(self, positionals):
|
||||||
|
"""Returns the syntax for reporting positional arguments.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
positionals (list): list of positional arguments
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the syntax for positional arguments
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def optionals(self, optionals):
|
||||||
|
"""Returns the syntax for reporting optional flags.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
optionals (list): list of optional arguments
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the syntax for optional flags
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def subcommands(self, subcommands):
|
||||||
|
"""Returns the syntax for reporting subcommands.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
subcommands (list): list of subcommand parsers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the syntax for subcommand parsers
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import numbers
|
import numbers
|
||||||
import os
|
import os
|
||||||
import pathlib
|
|
||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -19,17 +18,14 @@
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from itertools import accumulate
|
|
||||||
from typing import Callable, Iterable, List, Match, Optional, Tuple, Union
|
from typing import Callable, Iterable, List, Match, Optional, Tuple, Union
|
||||||
|
|
||||||
import llnl.util.symlink
|
|
||||||
from llnl.util import tty
|
from llnl.util import tty
|
||||||
from llnl.util.lang import dedupe, memoized
|
from llnl.util.lang import dedupe, memoized
|
||||||
from llnl.util.symlink import islink, readlink, resolve_link_target_relative_to_the_link, symlink
|
from llnl.util.symlink import islink, symlink
|
||||||
|
|
||||||
from spack.util.executable import Executable, which
|
from spack.util.executable import Executable, which
|
||||||
|
from spack.util.path import path_to_os_path, system_path_filter
|
||||||
from ..path import path_to_os_path, system_path_filter
|
|
||||||
|
|
||||||
if sys.platform != "win32":
|
if sys.platform != "win32":
|
||||||
import grp
|
import grp
|
||||||
@@ -105,7 +101,7 @@ def _nop(args, ns=None, follow_symlinks=None):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# follow symlinks (aka don't not follow symlinks)
|
# follow symlinks (aka don't not follow symlinks)
|
||||||
follow = follow_symlinks or not (islink(src) and islink(dst))
|
follow = follow_symlinks or not (os.path.islink(src) and os.path.islink(dst))
|
||||||
if follow:
|
if follow:
|
||||||
# use the real function if it exists
|
# use the real function if it exists
|
||||||
def lookup(name):
|
def lookup(name):
|
||||||
@@ -156,37 +152,6 @@ def lookup(name):
|
|||||||
shutil.copystat = copystat
|
shutil.copystat = copystat
|
||||||
|
|
||||||
|
|
||||||
def polite_path(components: Iterable[str]):
|
|
||||||
"""
|
|
||||||
Given a list of strings which are intended to be path components,
|
|
||||||
generate a path, and format each component to avoid generating extra
|
|
||||||
path entries.
|
|
||||||
|
|
||||||
For example all "/", "\", and ":" characters will be replaced with
|
|
||||||
"_". Other characters like "=" will also be replaced.
|
|
||||||
"""
|
|
||||||
return os.path.join(*[polite_filename(x) for x in components])
|
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
|
||||||
def _polite_antipattern():
|
|
||||||
# A regex of all the characters we don't want in a filename
|
|
||||||
return re.compile(r"[^A-Za-z0-9_.-]")
|
|
||||||
|
|
||||||
|
|
||||||
def polite_filename(filename: str) -> str:
|
|
||||||
"""
|
|
||||||
Replace generally problematic filename characters with underscores.
|
|
||||||
|
|
||||||
This differs from sanitize_filename in that it is more aggressive in
|
|
||||||
changing characters in the name. For example it removes "=" which can
|
|
||||||
confuse path parsing in external tools.
|
|
||||||
"""
|
|
||||||
# This character set applies for both Windows and Linux. It does not
|
|
||||||
# account for reserved filenames in Windows.
|
|
||||||
return _polite_antipattern().sub("_", filename)
|
|
||||||
|
|
||||||
|
|
||||||
def getuid():
|
def getuid():
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import ctypes
|
import ctypes
|
||||||
@@ -204,7 +169,7 @@ def rename(src, dst):
|
|||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
# Windows path existence checks will sometimes fail on junctions/links/symlinks
|
# Windows path existence checks will sometimes fail on junctions/links/symlinks
|
||||||
# so check for that case
|
# so check for that case
|
||||||
if os.path.exists(dst) or islink(dst):
|
if os.path.exists(dst) or os.path.islink(dst):
|
||||||
os.remove(dst)
|
os.remove(dst)
|
||||||
os.rename(src, dst)
|
os.rename(src, dst)
|
||||||
|
|
||||||
@@ -368,7 +333,8 @@ def groupid_to_group(x):
|
|||||||
|
|
||||||
if string:
|
if string:
|
||||||
regex = re.escape(regex)
|
regex = re.escape(regex)
|
||||||
for filename in path_to_os_path(*filenames):
|
filenames = path_to_os_path(*filenames)
|
||||||
|
for filename in filenames:
|
||||||
msg = 'FILTER FILE: {0} [replacing "{1}"]'
|
msg = 'FILTER FILE: {0} [replacing "{1}"]'
|
||||||
tty.debug(msg.format(filename, regex))
|
tty.debug(msg.format(filename, regex))
|
||||||
|
|
||||||
@@ -436,7 +402,7 @@ def groupid_to_group(x):
|
|||||||
os.remove(backup_filename)
|
os.remove(backup_filename)
|
||||||
|
|
||||||
|
|
||||||
class FileFilter:
|
class FileFilter(object):
|
||||||
"""Convenience class for calling ``filter_file`` a lot."""
|
"""Convenience class for calling ``filter_file`` a lot."""
|
||||||
|
|
||||||
def __init__(self, *filenames):
|
def __init__(self, *filenames):
|
||||||
@@ -600,7 +566,7 @@ def set_install_permissions(path):
|
|||||||
# If this points to a file maintained in a Spack prefix, it is assumed that
|
# If this points to a file maintained in a Spack prefix, it is assumed that
|
||||||
# this function will be invoked on the target. If the file is outside a
|
# this function will be invoked on the target. If the file is outside a
|
||||||
# Spack-maintained prefix, the permissions should not be modified.
|
# Spack-maintained prefix, the permissions should not be modified.
|
||||||
if islink(path):
|
if os.path.islink(path):
|
||||||
return
|
return
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
os.chmod(path, 0o755)
|
os.chmod(path, 0o755)
|
||||||
@@ -644,8 +610,6 @@ def chgrp(path, group, follow_symlinks=True):
|
|||||||
gid = grp.getgrnam(group).gr_gid
|
gid = grp.getgrnam(group).gr_gid
|
||||||
else:
|
else:
|
||||||
gid = group
|
gid = group
|
||||||
if os.stat(path).st_gid == gid:
|
|
||||||
return
|
|
||||||
if follow_symlinks:
|
if follow_symlinks:
|
||||||
os.chown(path, -1, gid)
|
os.chown(path, -1, gid)
|
||||||
else:
|
else:
|
||||||
@@ -669,7 +633,7 @@ def chmod_x(entry, perms):
|
|||||||
@system_path_filter
|
@system_path_filter
|
||||||
def copy_mode(src, dest):
|
def copy_mode(src, dest):
|
||||||
"""Set the mode of dest to that of src unless it is a link."""
|
"""Set the mode of dest to that of src unless it is a link."""
|
||||||
if islink(dest):
|
if os.path.islink(dest):
|
||||||
return
|
return
|
||||||
src_mode = os.stat(src).st_mode
|
src_mode = os.stat(src).st_mode
|
||||||
dest_mode = os.stat(dest).st_mode
|
dest_mode = os.stat(dest).st_mode
|
||||||
@@ -755,12 +719,26 @@ def install(src, dest):
|
|||||||
copy(src, dest, _permissions=True)
|
copy(src, dest, _permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
@system_path_filter
|
||||||
|
def resolve_link_target_relative_to_the_link(link):
|
||||||
|
"""
|
||||||
|
os.path.isdir uses os.path.exists, which for links will check
|
||||||
|
the existence of the link target. If the link target is relative to
|
||||||
|
the link, we need to construct a pathname that is valid from
|
||||||
|
our cwd (which may not be the same as the link's directory)
|
||||||
|
"""
|
||||||
|
target = os.readlink(link)
|
||||||
|
if os.path.isabs(target):
|
||||||
|
return target
|
||||||
|
link_dir = os.path.dirname(os.path.abspath(link))
|
||||||
|
return os.path.join(link_dir, target)
|
||||||
|
|
||||||
|
|
||||||
@system_path_filter
|
@system_path_filter
|
||||||
def copy_tree(
|
def copy_tree(
|
||||||
src: str,
|
src: str,
|
||||||
dest: str,
|
dest: str,
|
||||||
symlinks: bool = True,
|
symlinks: bool = True,
|
||||||
allow_broken_symlinks: bool = sys.platform != "win32",
|
|
||||||
ignore: Optional[Callable[[str], bool]] = None,
|
ignore: Optional[Callable[[str], bool]] = None,
|
||||||
_permissions: bool = False,
|
_permissions: bool = False,
|
||||||
):
|
):
|
||||||
@@ -783,8 +761,6 @@ def copy_tree(
|
|||||||
src (str): the directory to copy
|
src (str): the directory to copy
|
||||||
dest (str): the destination directory
|
dest (str): the destination directory
|
||||||
symlinks (bool): whether or not to preserve symlinks
|
symlinks (bool): whether or not to preserve symlinks
|
||||||
allow_broken_symlinks (bool): whether or not to allow broken (dangling) symlinks,
|
|
||||||
On Windows, setting this to True will raise an exception. Defaults to true on unix.
|
|
||||||
ignore (typing.Callable): function indicating which files to ignore
|
ignore (typing.Callable): function indicating which files to ignore
|
||||||
_permissions (bool): for internal use only
|
_permissions (bool): for internal use only
|
||||||
|
|
||||||
@@ -792,8 +768,6 @@ def copy_tree(
|
|||||||
IOError: if *src* does not match any files or directories
|
IOError: if *src* does not match any files or directories
|
||||||
ValueError: if *src* is a parent directory of *dest*
|
ValueError: if *src* is a parent directory of *dest*
|
||||||
"""
|
"""
|
||||||
if allow_broken_symlinks and sys.platform == "win32":
|
|
||||||
raise llnl.util.symlink.SymlinkError("Cannot allow broken symlinks on Windows!")
|
|
||||||
if _permissions:
|
if _permissions:
|
||||||
tty.debug("Installing {0} to {1}".format(src, dest))
|
tty.debug("Installing {0} to {1}".format(src, dest))
|
||||||
else:
|
else:
|
||||||
@@ -807,11 +781,6 @@ def copy_tree(
|
|||||||
if not files:
|
if not files:
|
||||||
raise IOError("No such file or directory: '{0}'".format(src))
|
raise IOError("No such file or directory: '{0}'".format(src))
|
||||||
|
|
||||||
# For Windows hard-links and junctions, the source path must exist to make a symlink. Add
|
|
||||||
# all symlinks to this list while traversing the tree, then when finished, make all
|
|
||||||
# symlinks at the end.
|
|
||||||
links = []
|
|
||||||
|
|
||||||
for src in files:
|
for src in files:
|
||||||
abs_src = os.path.abspath(src)
|
abs_src = os.path.abspath(src)
|
||||||
if not abs_src.endswith(os.path.sep):
|
if not abs_src.endswith(os.path.sep):
|
||||||
@@ -834,7 +803,7 @@ def copy_tree(
|
|||||||
ignore=ignore,
|
ignore=ignore,
|
||||||
follow_nonexisting=True,
|
follow_nonexisting=True,
|
||||||
):
|
):
|
||||||
if islink(s):
|
if os.path.islink(s):
|
||||||
link_target = resolve_link_target_relative_to_the_link(s)
|
link_target = resolve_link_target_relative_to_the_link(s)
|
||||||
if symlinks:
|
if symlinks:
|
||||||
target = os.readlink(s)
|
target = os.readlink(s)
|
||||||
@@ -848,9 +817,7 @@ def escaped_path(path):
|
|||||||
tty.debug("Redirecting link {0} to {1}".format(target, new_target))
|
tty.debug("Redirecting link {0} to {1}".format(target, new_target))
|
||||||
target = new_target
|
target = new_target
|
||||||
|
|
||||||
links.append((target, d, s))
|
symlink(target, d)
|
||||||
continue
|
|
||||||
|
|
||||||
elif os.path.isdir(link_target):
|
elif os.path.isdir(link_target):
|
||||||
mkdirp(d)
|
mkdirp(d)
|
||||||
else:
|
else:
|
||||||
@@ -865,17 +832,9 @@ def escaped_path(path):
|
|||||||
set_install_permissions(d)
|
set_install_permissions(d)
|
||||||
copy_mode(s, d)
|
copy_mode(s, d)
|
||||||
|
|
||||||
for target, d, s in links:
|
|
||||||
symlink(target, d, allow_broken_symlinks=allow_broken_symlinks)
|
|
||||||
if _permissions:
|
|
||||||
set_install_permissions(d)
|
|
||||||
copy_mode(s, d)
|
|
||||||
|
|
||||||
|
|
||||||
@system_path_filter
|
@system_path_filter
|
||||||
def install_tree(
|
def install_tree(src, dest, symlinks=True, ignore=None):
|
||||||
src, dest, symlinks=True, ignore=None, allow_broken_symlinks=sys.platform != "win32"
|
|
||||||
):
|
|
||||||
"""Recursively install an entire directory tree rooted at *src*.
|
"""Recursively install an entire directory tree rooted at *src*.
|
||||||
|
|
||||||
Same as :py:func:`copy_tree` with the addition of setting proper
|
Same as :py:func:`copy_tree` with the addition of setting proper
|
||||||
@@ -886,21 +845,12 @@ def install_tree(
|
|||||||
dest (str): the destination directory
|
dest (str): the destination directory
|
||||||
symlinks (bool): whether or not to preserve symlinks
|
symlinks (bool): whether or not to preserve symlinks
|
||||||
ignore (typing.Callable): function indicating which files to ignore
|
ignore (typing.Callable): function indicating which files to ignore
|
||||||
allow_broken_symlinks (bool): whether or not to allow broken (dangling) symlinks,
|
|
||||||
On Windows, setting this to True will raise an exception.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
IOError: if *src* does not match any files or directories
|
IOError: if *src* does not match any files or directories
|
||||||
ValueError: if *src* is a parent directory of *dest*
|
ValueError: if *src* is a parent directory of *dest*
|
||||||
"""
|
"""
|
||||||
copy_tree(
|
copy_tree(src, dest, symlinks=symlinks, ignore=ignore, _permissions=True)
|
||||||
src,
|
|
||||||
dest,
|
|
||||||
symlinks=symlinks,
|
|
||||||
allow_broken_symlinks=allow_broken_symlinks,
|
|
||||||
ignore=ignore,
|
|
||||||
_permissions=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@system_path_filter
|
@system_path_filter
|
||||||
@@ -1304,12 +1254,7 @@ def traverse_tree(
|
|||||||
Keyword Arguments:
|
Keyword Arguments:
|
||||||
order (str): Whether to do pre- or post-order traversal. Accepted
|
order (str): Whether to do pre- or post-order traversal. Accepted
|
||||||
values are 'pre' and 'post'
|
values are 'pre' and 'post'
|
||||||
ignore (typing.Callable): function indicating which files to ignore. This will also
|
ignore (typing.Callable): function indicating which files to ignore
|
||||||
ignore symlinks if they point to an ignored file (regardless of whether the symlink
|
|
||||||
is explicitly ignored); note this only supports one layer of indirection (i.e. if
|
|
||||||
you have x -> y -> z, and z is ignored but x/y are not, then y would be ignored
|
|
||||||
but not x). To avoid this, make sure the ignore function also ignores the symlink
|
|
||||||
paths too.
|
|
||||||
follow_nonexisting (bool): Whether to descend into directories in
|
follow_nonexisting (bool): Whether to descend into directories in
|
||||||
``src`` that do not exit in ``dest``. Default is True
|
``src`` that do not exit in ``dest``. Default is True
|
||||||
follow_links (bool): Whether to descend into symlinks in ``src``
|
follow_links (bool): Whether to descend into symlinks in ``src``
|
||||||
@@ -1336,24 +1281,11 @@ def traverse_tree(
|
|||||||
dest_child = os.path.join(dest_path, f)
|
dest_child = os.path.join(dest_path, f)
|
||||||
rel_child = os.path.join(rel_path, f)
|
rel_child = os.path.join(rel_path, f)
|
||||||
|
|
||||||
# If the source path is a link and the link's source is ignored, then ignore the link too,
|
|
||||||
# but only do this if the ignore is defined.
|
|
||||||
if ignore is not None:
|
|
||||||
if islink(source_child) and not follow_links:
|
|
||||||
target = readlink(source_child)
|
|
||||||
all_parents = accumulate(target.split(os.sep), lambda x, y: os.path.join(x, y))
|
|
||||||
if any(map(ignore, all_parents)):
|
|
||||||
tty.warn(
|
|
||||||
f"Skipping {source_path} because the source or a part of the source's "
|
|
||||||
f"path is included in the ignores."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Treat as a directory
|
# Treat as a directory
|
||||||
# TODO: for symlinks, os.path.isdir looks for the link target. If the
|
# TODO: for symlinks, os.path.isdir looks for the link target. If the
|
||||||
# target is relative to the link, then that may not resolve properly
|
# target is relative to the link, then that may not resolve properly
|
||||||
# relative to our cwd - see resolve_link_target_relative_to_the_link
|
# relative to our cwd - see resolve_link_target_relative_to_the_link
|
||||||
if os.path.isdir(source_child) and (follow_links or not islink(source_child)):
|
if os.path.isdir(source_child) and (follow_links or not os.path.islink(source_child)):
|
||||||
# When follow_nonexisting isn't set, don't descend into dirs
|
# When follow_nonexisting isn't set, don't descend into dirs
|
||||||
# in source that do not exist in dest
|
# in source that do not exist in dest
|
||||||
if follow_nonexisting or os.path.exists(dest_child):
|
if follow_nonexisting or os.path.exists(dest_child):
|
||||||
@@ -1379,11 +1311,7 @@ def traverse_tree(
|
|||||||
|
|
||||||
def lexists_islink_isdir(path):
|
def lexists_islink_isdir(path):
|
||||||
"""Computes the tuple (lexists(path), islink(path), isdir(path)) in a minimal
|
"""Computes the tuple (lexists(path), islink(path), isdir(path)) in a minimal
|
||||||
number of stat calls on unix. Use os.path and symlink.islink methods for windows."""
|
number of stat calls."""
|
||||||
if sys.platform == "win32":
|
|
||||||
if not os.path.lexists(path):
|
|
||||||
return False, False, False
|
|
||||||
return os.path.lexists(path), islink(path), os.path.isdir(path)
|
|
||||||
# First try to lstat, so we know if it's a link or not.
|
# First try to lstat, so we know if it's a link or not.
|
||||||
try:
|
try:
|
||||||
lst = os.lstat(path)
|
lst = os.lstat(path)
|
||||||
@@ -1408,7 +1336,7 @@ def lexists_islink_isdir(path):
|
|||||||
return True, is_link, is_dir
|
return True, is_link, is_dir
|
||||||
|
|
||||||
|
|
||||||
class BaseDirectoryVisitor:
|
class BaseDirectoryVisitor(object):
|
||||||
"""Base class and interface for :py:func:`visit_directory_tree`."""
|
"""Base class and interface for :py:func:`visit_directory_tree`."""
|
||||||
|
|
||||||
def visit_file(self, root, rel_path, depth):
|
def visit_file(self, root, rel_path, depth):
|
||||||
@@ -1598,7 +1526,7 @@ def remove_if_dead_link(path):
|
|||||||
Parameters:
|
Parameters:
|
||||||
path (str): The potential dead link
|
path (str): The potential dead link
|
||||||
"""
|
"""
|
||||||
if islink(path) and not os.path.exists(path):
|
if os.path.islink(path) and not os.path.exists(path):
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
|
|
||||||
|
|
||||||
@@ -1657,7 +1585,7 @@ def remove_linked_tree(path):
|
|||||||
kwargs["onerror"] = readonly_file_handler(ignore_errors=True)
|
kwargs["onerror"] = readonly_file_handler(ignore_errors=True)
|
||||||
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
if islink(path):
|
if os.path.islink(path):
|
||||||
shutil.rmtree(os.path.realpath(path), **kwargs)
|
shutil.rmtree(os.path.realpath(path), **kwargs)
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
else:
|
else:
|
||||||
@@ -1824,14 +1752,9 @@ def find(root, files, recursive=True):
|
|||||||
files = [files]
|
files = [files]
|
||||||
|
|
||||||
if recursive:
|
if recursive:
|
||||||
tty.debug(f"Find (recursive): {root} {str(files)}")
|
return _find_recursive(root, files)
|
||||||
result = _find_recursive(root, files)
|
|
||||||
else:
|
else:
|
||||||
tty.debug(f"Find (not recursive): {root} {str(files)}")
|
return _find_non_recursive(root, files)
|
||||||
result = _find_non_recursive(root, files)
|
|
||||||
|
|
||||||
tty.debug(f"Find complete: {root} {str(files)}")
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@system_path_filter
|
@system_path_filter
|
||||||
@@ -1967,7 +1890,7 @@ class HeaderList(FileList):
|
|||||||
include_regex = re.compile(r"(.*?)(\binclude\b)(.*)")
|
include_regex = re.compile(r"(.*?)(\binclude\b)(.*)")
|
||||||
|
|
||||||
def __init__(self, files):
|
def __init__(self, files):
|
||||||
super().__init__(files)
|
super(HeaderList, self).__init__(files)
|
||||||
|
|
||||||
self._macro_definitions = []
|
self._macro_definitions = []
|
||||||
self._directories = None
|
self._directories = None
|
||||||
@@ -1993,7 +1916,7 @@ def _default_directories(self):
|
|||||||
"""Default computation of directories based on the list of
|
"""Default computation of directories based on the list of
|
||||||
header files.
|
header files.
|
||||||
"""
|
"""
|
||||||
dir_list = super().directories
|
dir_list = super(HeaderList, self).directories
|
||||||
values = []
|
values = []
|
||||||
for d in dir_list:
|
for d in dir_list:
|
||||||
# If the path contains a subdirectory named 'include' then stop
|
# If the path contains a subdirectory named 'include' then stop
|
||||||
@@ -2429,7 +2352,7 @@ def find_all_libraries(root, recursive=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WindowsSimulatedRPath:
|
class WindowsSimulatedRPath(object):
|
||||||
"""Class representing Windows filesystem rpath analog
|
"""Class representing Windows filesystem rpath analog
|
||||||
|
|
||||||
One instance of this class is associated with a package (only on Windows)
|
One instance of this class is associated with a package (only on Windows)
|
||||||
@@ -2458,7 +2381,7 @@ def library_dependents(self):
|
|||||||
"""
|
"""
|
||||||
Set of directories where package binaries/libraries are located.
|
Set of directories where package binaries/libraries are located.
|
||||||
"""
|
"""
|
||||||
return set([pathlib.Path(self.pkg.prefix.bin)]) | self._additional_library_dependents
|
return set([self.pkg.prefix.bin]) | self._additional_library_dependents
|
||||||
|
|
||||||
def add_library_dependent(self, *dest):
|
def add_library_dependent(self, *dest):
|
||||||
"""
|
"""
|
||||||
@@ -2471,9 +2394,9 @@ def add_library_dependent(self, *dest):
|
|||||||
"""
|
"""
|
||||||
for pth in dest:
|
for pth in dest:
|
||||||
if os.path.isfile(pth):
|
if os.path.isfile(pth):
|
||||||
self._additional_library_dependents.add(pathlib.Path(pth).parent)
|
self._additional_library_dependents.add(os.path.dirname)
|
||||||
else:
|
else:
|
||||||
self._additional_library_dependents.add(pathlib.Path(pth))
|
self._additional_library_dependents.add(pth)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rpaths(self):
|
def rpaths(self):
|
||||||
@@ -2486,7 +2409,7 @@ def rpaths(self):
|
|||||||
dependent_libs.extend(list(find_all_shared_libraries(path, recursive=True)))
|
dependent_libs.extend(list(find_all_shared_libraries(path, recursive=True)))
|
||||||
for extra_path in self._addl_rpaths:
|
for extra_path in self._addl_rpaths:
|
||||||
dependent_libs.extend(list(find_all_shared_libraries(extra_path, recursive=True)))
|
dependent_libs.extend(list(find_all_shared_libraries(extra_path, recursive=True)))
|
||||||
return set([pathlib.Path(x) for x in dependent_libs])
|
return set(dependent_libs)
|
||||||
|
|
||||||
def add_rpath(self, *paths):
|
def add_rpath(self, *paths):
|
||||||
"""
|
"""
|
||||||
@@ -2502,7 +2425,7 @@ def add_rpath(self, *paths):
|
|||||||
"""
|
"""
|
||||||
self._addl_rpaths = self._addl_rpaths | set(paths)
|
self._addl_rpaths = self._addl_rpaths | set(paths)
|
||||||
|
|
||||||
def _link(self, path: pathlib.Path, dest_dir: pathlib.Path):
|
def _link(self, path, dest_dir):
|
||||||
"""Perform link step of simulated rpathing, installing
|
"""Perform link step of simulated rpathing, installing
|
||||||
simlinks of file in path to the dest_dir
|
simlinks of file in path to the dest_dir
|
||||||
location. This method deliberately prevents
|
location. This method deliberately prevents
|
||||||
@@ -2510,35 +2433,27 @@ def _link(self, path: pathlib.Path, dest_dir: pathlib.Path):
|
|||||||
This is because it is both meaningless from an rpath
|
This is because it is both meaningless from an rpath
|
||||||
perspective, and will cause an error when Developer
|
perspective, and will cause an error when Developer
|
||||||
mode is not enabled"""
|
mode is not enabled"""
|
||||||
|
file_name = os.path.basename(path)
|
||||||
def report_already_linked():
|
dest_file = os.path.join(dest_dir, file_name)
|
||||||
# We have either already symlinked or we are encoutering a naming clash
|
if os.path.exists(dest_dir) and not dest_file == path:
|
||||||
# either way, we don't want to overwrite existing libraries
|
|
||||||
already_linked = islink(str(dest_file))
|
|
||||||
tty.debug(
|
|
||||||
"Linking library %s to %s failed, " % (str(path), str(dest_file))
|
|
||||||
+ "already linked."
|
|
||||||
if already_linked
|
|
||||||
else "library with name %s already exists at location %s."
|
|
||||||
% (str(file_name), str(dest_dir))
|
|
||||||
)
|
|
||||||
|
|
||||||
file_name = path.name
|
|
||||||
dest_file = dest_dir / file_name
|
|
||||||
if not dest_file.exists() and dest_dir.exists() and not dest_file == path:
|
|
||||||
try:
|
try:
|
||||||
symlink(str(path), str(dest_file))
|
symlink(path, dest_file)
|
||||||
# For py2 compatibility, we have to catch the specific Windows error code
|
# For py2 compatibility, we have to catch the specific Windows error code
|
||||||
# associate with trying to create a file that already exists (winerror 183)
|
# associate with trying to create a file that already exists (winerror 183)
|
||||||
# Catch OSErrors missed by the SymlinkError checks
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if sys.platform == "win32" and (e.winerror == 183 or e.errno == errno.EEXIST):
|
if sys.platform == "win32" and (e.winerror == 183 or e.errno == errno.EEXIST):
|
||||||
report_already_linked()
|
# 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_dir)
|
||||||
|
)
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
# catch errors we raise ourselves from Spack
|
|
||||||
except llnl.util.symlink.AlreadyExistsError:
|
|
||||||
report_already_linked()
|
|
||||||
|
|
||||||
def establish_link(self):
|
def establish_link(self):
|
||||||
"""
|
"""
|
||||||
@@ -2771,7 +2686,7 @@ def remove_directory_contents(dir):
|
|||||||
"""Remove all contents of a directory."""
|
"""Remove all contents of a directory."""
|
||||||
if os.path.exists(dir):
|
if os.path.exists(dir):
|
||||||
for entry in [os.path.join(dir, entry) for entry in os.listdir(dir)]:
|
for entry in [os.path.join(dir, entry) for entry in os.listdir(dir)]:
|
||||||
if os.path.isfile(entry) or islink(entry):
|
if os.path.isfile(entry) or os.path.islink(entry):
|
||||||
os.unlink(entry)
|
os.unlink(entry)
|
||||||
else:
|
else:
|
||||||
shutil.rmtree(entry)
|
shutil.rmtree(entry)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
import collections.abc
|
import collections.abc
|
||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
@@ -766,10 +768,10 @@ def pretty_seconds(seconds):
|
|||||||
|
|
||||||
class RequiredAttributeError(ValueError):
|
class RequiredAttributeError(ValueError):
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super().__init__(message)
|
super(RequiredAttributeError, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
class ObjectWrapper:
|
class ObjectWrapper(object):
|
||||||
"""Base class that wraps an object. Derived classes can add new behavior
|
"""Base class that wraps an object. Derived classes can add new behavior
|
||||||
while staying undercover.
|
while staying undercover.
|
||||||
|
|
||||||
@@ -796,7 +798,7 @@ def __init__(self, wrapped_object):
|
|||||||
self.__dict__ = wrapped_object.__dict__
|
self.__dict__ = wrapped_object.__dict__
|
||||||
|
|
||||||
|
|
||||||
class Singleton:
|
class Singleton(object):
|
||||||
"""Simple wrapper for lazily initialized singleton objects."""
|
"""Simple wrapper for lazily initialized singleton objects."""
|
||||||
|
|
||||||
def __init__(self, factory):
|
def __init__(self, factory):
|
||||||
@@ -821,7 +823,7 @@ def __getattr__(self, name):
|
|||||||
# 'instance'/'_instance' to be defined or it will enter an infinite
|
# 'instance'/'_instance' to be defined or it will enter an infinite
|
||||||
# loop, so protect against that here.
|
# loop, so protect against that here.
|
||||||
if name in ["_instance", "instance"]:
|
if name in ["_instance", "instance"]:
|
||||||
raise AttributeError(f"cannot create {name}")
|
raise AttributeError()
|
||||||
return getattr(self.instance, name)
|
return getattr(self.instance, name)
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
@@ -843,6 +845,27 @@ def __repr__(self):
|
|||||||
return repr(self.instance)
|
return repr(self.instance)
|
||||||
|
|
||||||
|
|
||||||
|
class LazyReference(object):
|
||||||
|
"""Lazily evaluated reference to part of a singleton."""
|
||||||
|
|
||||||
|
def __init__(self, ref_function):
|
||||||
|
self.ref_function = ref_function
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name == "ref_function":
|
||||||
|
raise AttributeError()
|
||||||
|
return getattr(self.ref_function(), name)
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
return self.ref_function()[name]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.ref_function())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self.ref_function())
|
||||||
|
|
||||||
|
|
||||||
def load_module_from_file(module_name, module_path):
|
def load_module_from_file(module_name, module_path):
|
||||||
"""Loads a python module from the path of the corresponding file.
|
"""Loads a python module from the path of the corresponding file.
|
||||||
|
|
||||||
@@ -920,7 +943,7 @@ def _wrapper(args):
|
|||||||
return _wrapper
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
class Devnull:
|
class Devnull(object):
|
||||||
"""Null stream with less overhead than ``os.devnull``.
|
"""Null stream with less overhead than ``os.devnull``.
|
||||||
|
|
||||||
See https://stackoverflow.com/a/2929954.
|
See https://stackoverflow.com/a/2929954.
|
||||||
@@ -1037,7 +1060,7 @@ def __str__(self):
|
|||||||
return str(self.data)
|
return str(self.data)
|
||||||
|
|
||||||
|
|
||||||
class GroupedExceptionHandler:
|
class GroupedExceptionHandler(object):
|
||||||
"""A generic mechanism to coalesce multiple exceptions and preserve tracebacks."""
|
"""A generic mechanism to coalesce multiple exceptions and preserve tracebacks."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -1068,7 +1091,7 @@ def grouped_message(self, with_tracebacks: bool = True) -> str:
|
|||||||
return "due to the following failures:\n{0}".format("\n".join(each_exception_message))
|
return "due to the following failures:\n{0}".format("\n".join(each_exception_message))
|
||||||
|
|
||||||
|
|
||||||
class GroupedExceptionForwarder:
|
class GroupedExceptionForwarder(object):
|
||||||
"""A contextmanager to capture exceptions and forward them to a
|
"""A contextmanager to capture exceptions and forward them to a
|
||||||
GroupedExceptionHandler."""
|
GroupedExceptionHandler."""
|
||||||
|
|
||||||
@@ -1088,7 +1111,7 @@ def __exit__(self, exc_type, exc_value, tb):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class classproperty:
|
class classproperty(object):
|
||||||
"""Non-data descriptor to evaluate a class-level property. The function that performs
|
"""Non-data descriptor to evaluate a class-level property. The function that performs
|
||||||
the evaluation is injected at creation time and take an instance (could be None) and
|
the evaluation is injected at creation time and take an instance (could be None) and
|
||||||
an owner (i.e. the class that originated the instance)
|
an owner (i.e. the class that originated the instance)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
"""LinkTree class for setting up trees of symbolic links."""
|
"""LinkTree class for setting up trees of symbolic links."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import filecmp
|
import filecmp
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -285,7 +287,7 @@ def visit_symlinked_file(self, root, rel_path, depth):
|
|||||||
self.visit_file(root, rel_path, depth)
|
self.visit_file(root, rel_path, depth)
|
||||||
|
|
||||||
|
|
||||||
class LinkTree:
|
class LinkTree(object):
|
||||||
"""Class to create trees of symbolic links from a source directory.
|
"""Class to create trees of symbolic links from a source directory.
|
||||||
|
|
||||||
LinkTree objects are constructed with a source root. Their
|
LinkTree objects are constructed with a source root. Their
|
||||||
@@ -430,12 +432,12 @@ class MergeConflictError(Exception):
|
|||||||
|
|
||||||
class ConflictingSpecsError(MergeConflictError):
|
class ConflictingSpecsError(MergeConflictError):
|
||||||
def __init__(self, spec_1, spec_2):
|
def __init__(self, spec_1, spec_2):
|
||||||
super().__init__(spec_1, spec_2)
|
super(MergeConflictError, self).__init__(spec_1, spec_2)
|
||||||
|
|
||||||
|
|
||||||
class SingleMergeConflictError(MergeConflictError):
|
class SingleMergeConflictError(MergeConflictError):
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
super().__init__("Package merge blocked by file: %s" % path)
|
super(MergeConflictError, self).__init__("Package merge blocked by file: %s" % path)
|
||||||
|
|
||||||
|
|
||||||
class MergeConflictSummary(MergeConflictError):
|
class MergeConflictSummary(MergeConflictError):
|
||||||
@@ -450,4 +452,4 @@ def __init__(self, conflicts):
|
|||||||
msg += "\n `{0}` and `{1}` both project to `{2}`".format(
|
msg += "\n `{0}` and `{1}` both project to `{2}`".format(
|
||||||
conflict.src_a, conflict.src_b, conflict.dst
|
conflict.src_a, conflict.src_b, conflict.dst
|
||||||
)
|
)
|
||||||
super().__init__(msg)
|
super(MergeConflictSummary, self).__init__(msg)
|
||||||
|
|||||||
@@ -9,12 +9,11 @@
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from types import TracebackType
|
|
||||||
from typing import IO, Any, Callable, ContextManager, Dict, Generator, Optional, Tuple, Type, Union
|
|
||||||
|
|
||||||
from llnl.util import lang, tty
|
import llnl.util.tty as tty
|
||||||
|
from llnl.util.lang import pretty_seconds
|
||||||
|
|
||||||
from ..string import plural
|
import spack.util.string
|
||||||
|
|
||||||
if sys.platform != "win32":
|
if sys.platform != "win32":
|
||||||
import fcntl
|
import fcntl
|
||||||
@@ -35,15 +34,12 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
ReleaseFnType = Optional[Callable[[], bool]]
|
#: A useful replacement for functions that should return True when not provided
|
||||||
|
#: for example.
|
||||||
|
true_fn = lambda: True
|
||||||
|
|
||||||
|
|
||||||
def true_fn() -> bool:
|
class OpenFile(object):
|
||||||
"""A function that always returns True."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class OpenFile:
|
|
||||||
"""Record for keeping track of open lockfiles (with reference counting).
|
"""Record for keeping track of open lockfiles (with reference counting).
|
||||||
|
|
||||||
There's really only one ``OpenFile`` per inode, per process, but we record the
|
There's really only one ``OpenFile`` per inode, per process, but we record the
|
||||||
@@ -52,12 +48,12 @@ class OpenFile:
|
|||||||
file descriptors as well in the future.
|
file descriptors as well in the future.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fh: IO) -> None:
|
def __init__(self, fh):
|
||||||
self.fh = fh
|
self.fh = fh
|
||||||
self.refs = 0
|
self.refs = 0
|
||||||
|
|
||||||
|
|
||||||
class OpenFileTracker:
|
class OpenFileTracker(object):
|
||||||
"""Track open lockfiles, to minimize number of open file descriptors.
|
"""Track open lockfiles, to minimize number of open file descriptors.
|
||||||
|
|
||||||
The ``fcntl`` locks that Spack uses are associated with an inode and a process.
|
The ``fcntl`` locks that Spack uses are associated with an inode and a process.
|
||||||
@@ -82,11 +78,11 @@ class OpenFileTracker:
|
|||||||
work in Python and assume the GIL.
|
work in Python and assume the GIL.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self):
|
||||||
"""Create a new ``OpenFileTracker``."""
|
"""Create a new ``OpenFileTracker``."""
|
||||||
self._descriptors: Dict[Any, OpenFile] = {}
|
self._descriptors = {}
|
||||||
|
|
||||||
def get_fh(self, path: str) -> IO:
|
def get_fh(self, path):
|
||||||
"""Get a filehandle for a lockfile.
|
"""Get a filehandle for a lockfile.
|
||||||
|
|
||||||
This routine will open writable files for read/write even if you're asking
|
This routine will open writable files for read/write even if you're asking
|
||||||
@@ -94,7 +90,7 @@ def get_fh(self, path: str) -> IO:
|
|||||||
(write) lock later if requested.
|
(write) lock later if requested.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
path: path to lock file we want a filehandle for
|
path (str): path to lock file we want a filehandle for
|
||||||
"""
|
"""
|
||||||
# Open writable files as 'r+' so we can upgrade to write later
|
# Open writable files as 'r+' so we can upgrade to write later
|
||||||
os_mode, fh_mode = (os.O_RDWR | os.O_CREAT), "r+"
|
os_mode, fh_mode = (os.O_RDWR | os.O_CREAT), "r+"
|
||||||
@@ -143,7 +139,7 @@ def get_fh(self, path: str) -> IO:
|
|||||||
def release_by_stat(self, stat):
|
def release_by_stat(self, stat):
|
||||||
key = (stat.st_dev, stat.st_ino, os.getpid())
|
key = (stat.st_dev, stat.st_ino, os.getpid())
|
||||||
open_file = self._descriptors.get(key)
|
open_file = self._descriptors.get(key)
|
||||||
assert open_file, "Attempted to close non-existing inode: %s" % stat.st_ino
|
assert open_file, "Attempted to close non-existing inode: %s" % stat.st_inode
|
||||||
|
|
||||||
open_file.refs -= 1
|
open_file.refs -= 1
|
||||||
if not open_file.refs:
|
if not open_file.refs:
|
||||||
@@ -161,7 +157,7 @@ def purge(self):
|
|||||||
|
|
||||||
#: Open file descriptors for locks in this process. Used to prevent one process
|
#: Open file descriptors for locks in this process. Used to prevent one process
|
||||||
#: from opening the sam file many times for different byte range locks
|
#: from opening the sam file many times for different byte range locks
|
||||||
FILE_TRACKER = OpenFileTracker()
|
file_tracker = OpenFileTracker()
|
||||||
|
|
||||||
|
|
||||||
def _attempts_str(wait_time, nattempts):
|
def _attempts_str(wait_time, nattempts):
|
||||||
@@ -169,11 +165,11 @@ def _attempts_str(wait_time, nattempts):
|
|||||||
if nattempts <= 1:
|
if nattempts <= 1:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
attempts = plural(nattempts, "attempt")
|
attempts = spack.util.string.plural(nattempts, "attempt")
|
||||||
return " after {} and {}".format(lang.pretty_seconds(wait_time), attempts)
|
return " after {} and {}".format(pretty_seconds(wait_time), attempts)
|
||||||
|
|
||||||
|
|
||||||
class LockType:
|
class LockType(object):
|
||||||
READ = 0
|
READ = 0
|
||||||
WRITE = 1
|
WRITE = 1
|
||||||
|
|
||||||
@@ -192,11 +188,11 @@ def to_module(tid):
|
|||||||
return lock
|
return lock
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_valid(op: int) -> bool:
|
def is_valid(op):
|
||||||
return op == LockType.READ or op == LockType.WRITE
|
return op == LockType.READ or op == LockType.WRITE
|
||||||
|
|
||||||
|
|
||||||
class Lock:
|
class Lock(object):
|
||||||
"""This is an implementation of a filesystem lock using Python's lockf.
|
"""This is an implementation of a filesystem lock using Python's lockf.
|
||||||
|
|
||||||
In Python, ``lockf`` actually calls ``fcntl``, so this should work with
|
In Python, ``lockf`` actually calls ``fcntl``, so this should work with
|
||||||
@@ -211,16 +207,7 @@ class Lock:
|
|||||||
overlapping byte ranges in the same file).
|
overlapping byte ranges in the same file).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, path, start=0, length=0, default_timeout=None, debug=False, desc=""):
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
*,
|
|
||||||
start: int = 0,
|
|
||||||
length: int = 0,
|
|
||||||
default_timeout: Optional[float] = None,
|
|
||||||
debug: bool = False,
|
|
||||||
desc: str = "",
|
|
||||||
) -> None:
|
|
||||||
"""Construct a new lock on the file at ``path``.
|
"""Construct a new lock on the file at ``path``.
|
||||||
|
|
||||||
By default, the lock applies to the whole file. Optionally,
|
By default, the lock applies to the whole file. Optionally,
|
||||||
@@ -233,17 +220,17 @@ def __init__(
|
|||||||
beginning of the file.
|
beginning of the file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path: path to the lock
|
path (str): path to the lock
|
||||||
start: optional byte offset at which the lock starts
|
start (int): optional byte offset at which the lock starts
|
||||||
length: optional number of bytes to lock
|
length (int): optional number of bytes to lock
|
||||||
default_timeout: seconds to wait for lock attempts,
|
default_timeout (int): number of seconds to wait for lock attempts,
|
||||||
where None means to wait indefinitely
|
where None means to wait indefinitely
|
||||||
debug: debug mode specific to locking
|
debug (bool): debug mode specific to locking
|
||||||
desc: optional debug message lock description, which is
|
desc (str): optional debug message lock description, which is
|
||||||
helpful for distinguishing between different Spack locks.
|
helpful for distinguishing between different Spack locks.
|
||||||
"""
|
"""
|
||||||
self.path = path
|
self.path = path
|
||||||
self._file: Optional[IO] = None
|
self._file = None
|
||||||
self._reads = 0
|
self._reads = 0
|
||||||
self._writes = 0
|
self._writes = 0
|
||||||
|
|
||||||
@@ -255,7 +242,7 @@ def __init__(
|
|||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
|
||||||
# optional debug description
|
# optional debug description
|
||||||
self.desc = f" ({desc})" if desc else ""
|
self.desc = " ({0})".format(desc) if desc else ""
|
||||||
|
|
||||||
# If the user doesn't set a default timeout, or if they choose
|
# If the user doesn't set a default timeout, or if they choose
|
||||||
# None, 0, etc. then lock attempts will not time out (unless the
|
# None, 0, etc. then lock attempts will not time out (unless the
|
||||||
@@ -263,15 +250,11 @@ def __init__(
|
|||||||
self.default_timeout = default_timeout or None
|
self.default_timeout = default_timeout or None
|
||||||
|
|
||||||
# PID and host of lock holder (only used in debug mode)
|
# PID and host of lock holder (only used in debug mode)
|
||||||
self.pid: Optional[int] = None
|
self.pid = self.old_pid = None
|
||||||
self.old_pid: Optional[int] = None
|
self.host = self.old_host = None
|
||||||
self.host: Optional[str] = None
|
|
||||||
self.old_host: Optional[str] = None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _poll_interval_generator(
|
def _poll_interval_generator(_wait_times=None):
|
||||||
_wait_times: Optional[Tuple[float, float, float]] = None
|
|
||||||
) -> Generator[float, None, None]:
|
|
||||||
"""This implements a backoff scheme for polling a contended resource
|
"""This implements a backoff scheme for polling a contended resource
|
||||||
by suggesting a succession of wait times between polls.
|
by suggesting a succession of wait times between polls.
|
||||||
|
|
||||||
@@ -294,21 +277,21 @@ def _poll_interval_generator(
|
|||||||
num_requests += 1
|
num_requests += 1
|
||||||
yield wait_time
|
yield wait_time
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self):
|
||||||
"""Formal representation of the lock."""
|
"""Formal representation of the lock."""
|
||||||
rep = "{0}(".format(self.__class__.__name__)
|
rep = "{0}(".format(self.__class__.__name__)
|
||||||
for attr, value in self.__dict__.items():
|
for attr, value in self.__dict__.items():
|
||||||
rep += "{0}={1}, ".format(attr, value.__repr__())
|
rep += "{0}={1}, ".format(attr, value.__repr__())
|
||||||
return "{0})".format(rep.strip(", "))
|
return "{0})".format(rep.strip(", "))
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self):
|
||||||
"""Readable string (with key fields) of the lock."""
|
"""Readable string (with key fields) of the lock."""
|
||||||
location = "{0}[{1}:{2}]".format(self.path, self._start, self._length)
|
location = "{0}[{1}:{2}]".format(self.path, self._start, self._length)
|
||||||
timeout = "timeout={0}".format(self.default_timeout)
|
timeout = "timeout={0}".format(self.default_timeout)
|
||||||
activity = "#reads={0}, #writes={1}".format(self._reads, self._writes)
|
activity = "#reads={0}, #writes={1}".format(self._reads, self._writes)
|
||||||
return "({0}, {1}, {2})".format(location, timeout, activity)
|
return "({0}, {1}, {2})".format(location, timeout, activity)
|
||||||
|
|
||||||
def _lock(self, op: int, timeout: Optional[float] = None) -> Tuple[float, int]:
|
def _lock(self, op, timeout=None):
|
||||||
"""This takes a lock using POSIX locks (``fcntl.lockf``).
|
"""This takes a lock using POSIX locks (``fcntl.lockf``).
|
||||||
|
|
||||||
The lock is implemented as a spin lock using a nonblocking call
|
The lock is implemented as a spin lock using a nonblocking call
|
||||||
@@ -327,7 +310,7 @@ def _lock(self, op: int, timeout: Optional[float] = None) -> Tuple[float, int]:
|
|||||||
# Create file and parent directories if they don't exist.
|
# Create file and parent directories if they don't exist.
|
||||||
if self._file is None:
|
if self._file is None:
|
||||||
self._ensure_parent_directory()
|
self._ensure_parent_directory()
|
||||||
self._file = FILE_TRACKER.get_fh(self.path)
|
self._file = file_tracker.get_fh(self.path)
|
||||||
|
|
||||||
if LockType.to_module(op) == fcntl.LOCK_EX and self._file.mode == "r":
|
if LockType.to_module(op) == fcntl.LOCK_EX and self._file.mode == "r":
|
||||||
# Attempt to upgrade to write lock w/a read-only file.
|
# Attempt to upgrade to write lock w/a read-only file.
|
||||||
@@ -336,7 +319,7 @@ def _lock(self, op: int, timeout: Optional[float] = None) -> Tuple[float, int]:
|
|||||||
|
|
||||||
self._log_debug(
|
self._log_debug(
|
||||||
"{} locking [{}:{}]: timeout {}".format(
|
"{} locking [{}:{}]: timeout {}".format(
|
||||||
op_str.lower(), self._start, self._length, lang.pretty_seconds(timeout or 0)
|
op_str.lower(), self._start, self._length, pretty_seconds(timeout or 0)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -360,20 +343,15 @@ def _lock(self, op: int, timeout: Optional[float] = None) -> Tuple[float, int]:
|
|||||||
total_wait_time = time.time() - start_time
|
total_wait_time = time.time() - start_time
|
||||||
raise LockTimeoutError(op_str.lower(), self.path, total_wait_time, num_attempts)
|
raise LockTimeoutError(op_str.lower(), self.path, total_wait_time, num_attempts)
|
||||||
|
|
||||||
def _poll_lock(self, op: int) -> bool:
|
def _poll_lock(self, op):
|
||||||
"""Attempt to acquire the lock in a non-blocking manner. Return whether
|
"""Attempt to acquire the lock in a non-blocking manner. Return whether
|
||||||
the locking attempt succeeds
|
the locking attempt succeeds
|
||||||
"""
|
"""
|
||||||
assert self._file is not None, "cannot poll a lock without the file being set"
|
|
||||||
module_op = LockType.to_module(op)
|
module_op = LockType.to_module(op)
|
||||||
try:
|
try:
|
||||||
# Try to get the lock (will raise if not available.)
|
# Try to get the lock (will raise if not available.)
|
||||||
fcntl.lockf(
|
fcntl.lockf(
|
||||||
self._file.fileno(),
|
self._file, module_op | fcntl.LOCK_NB, self._length, self._start, os.SEEK_SET
|
||||||
module_op | fcntl.LOCK_NB,
|
|
||||||
self._length,
|
|
||||||
self._start,
|
|
||||||
os.SEEK_SET,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# help for debugging distributed locking
|
# help for debugging distributed locking
|
||||||
@@ -399,7 +377,7 @@ def _poll_lock(self, op: int) -> bool:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _ensure_parent_directory(self) -> str:
|
def _ensure_parent_directory(self):
|
||||||
parent = os.path.dirname(self.path)
|
parent = os.path.dirname(self.path)
|
||||||
|
|
||||||
# relative paths to lockfiles in the current directory have no parent
|
# relative paths to lockfiles in the current directory have no parent
|
||||||
@@ -418,22 +396,20 @@ def _ensure_parent_directory(self) -> str:
|
|||||||
raise
|
raise
|
||||||
return parent
|
return parent
|
||||||
|
|
||||||
def _read_log_debug_data(self) -> None:
|
def _read_log_debug_data(self):
|
||||||
"""Read PID and host data out of the file if it is there."""
|
"""Read PID and host data out of the file if it is there."""
|
||||||
assert self._file is not None, "cannot read debug log without the file being set"
|
|
||||||
self.old_pid = self.pid
|
self.old_pid = self.pid
|
||||||
self.old_host = self.host
|
self.old_host = self.host
|
||||||
|
|
||||||
line = self._file.read()
|
line = self._file.read()
|
||||||
if line:
|
if line:
|
||||||
pid, host = line.strip().split(",")
|
pid, host = line.strip().split(",")
|
||||||
_, _, pid = pid.rpartition("=")
|
_, _, self.pid = pid.rpartition("=")
|
||||||
_, _, self.host = host.rpartition("=")
|
_, _, self.host = host.rpartition("=")
|
||||||
self.pid = int(pid)
|
self.pid = int(self.pid)
|
||||||
|
|
||||||
def _write_log_debug_data(self) -> None:
|
def _write_log_debug_data(self):
|
||||||
"""Write PID and host data to the file, recording old values."""
|
"""Write PID and host data to the file, recording old values."""
|
||||||
assert self._file is not None, "cannot write debug log without the file being set"
|
|
||||||
self.old_pid = self.pid
|
self.old_pid = self.pid
|
||||||
self.old_host = self.host
|
self.old_host = self.host
|
||||||
|
|
||||||
@@ -447,21 +423,20 @@ def _write_log_debug_data(self) -> None:
|
|||||||
self._file.flush()
|
self._file.flush()
|
||||||
os.fsync(self._file.fileno())
|
os.fsync(self._file.fileno())
|
||||||
|
|
||||||
def _unlock(self) -> None:
|
def _unlock(self):
|
||||||
"""Releases a lock using POSIX locks (``fcntl.lockf``)
|
"""Releases a lock using POSIX locks (``fcntl.lockf``)
|
||||||
|
|
||||||
Releases the lock regardless of mode. Note that read locks may
|
Releases the lock regardless of mode. Note that read locks may
|
||||||
be masquerading as write locks, but this removes either.
|
be masquerading as write locks, but this removes either.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
assert self._file is not None, "cannot unlock without the file being set"
|
fcntl.lockf(self._file, fcntl.LOCK_UN, self._length, self._start, os.SEEK_SET)
|
||||||
fcntl.lockf(self._file.fileno(), fcntl.LOCK_UN, self._length, self._start, os.SEEK_SET)
|
file_tracker.release_by_fh(self._file)
|
||||||
FILE_TRACKER.release_by_fh(self._file)
|
|
||||||
self._file = None
|
self._file = None
|
||||||
self._reads = 0
|
self._reads = 0
|
||||||
self._writes = 0
|
self._writes = 0
|
||||||
|
|
||||||
def acquire_read(self, timeout: Optional[float] = None) -> bool:
|
def acquire_read(self, timeout=None):
|
||||||
"""Acquires a recursive, shared lock for reading.
|
"""Acquires a recursive, shared lock for reading.
|
||||||
|
|
||||||
Read and write locks can be acquired and released in arbitrary
|
Read and write locks can be acquired and released in arbitrary
|
||||||
@@ -486,7 +461,7 @@ def acquire_read(self, timeout: Optional[float] = None) -> bool:
|
|||||||
self._reads += 1
|
self._reads += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def acquire_write(self, timeout: Optional[float] = None) -> bool:
|
def acquire_write(self, timeout=None):
|
||||||
"""Acquires a recursive, exclusive lock for writing.
|
"""Acquires a recursive, exclusive lock for writing.
|
||||||
|
|
||||||
Read and write locks can be acquired and released in arbitrary
|
Read and write locks can be acquired and released in arbitrary
|
||||||
@@ -516,7 +491,7 @@ def acquire_write(self, timeout: Optional[float] = None) -> bool:
|
|||||||
self._writes += 1
|
self._writes += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_write_locked(self) -> bool:
|
def is_write_locked(self):
|
||||||
"""Check if the file is write locked
|
"""Check if the file is write locked
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
@@ -533,7 +508,7 @@ def is_write_locked(self) -> bool:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def downgrade_write_to_read(self, timeout: Optional[float] = None) -> None:
|
def downgrade_write_to_read(self, timeout=None):
|
||||||
"""
|
"""
|
||||||
Downgrade from an exclusive write lock to a shared read.
|
Downgrade from an exclusive write lock to a shared read.
|
||||||
|
|
||||||
@@ -552,7 +527,7 @@ def downgrade_write_to_read(self, timeout: Optional[float] = None) -> None:
|
|||||||
else:
|
else:
|
||||||
raise LockDowngradeError(self.path)
|
raise LockDowngradeError(self.path)
|
||||||
|
|
||||||
def upgrade_read_to_write(self, timeout: Optional[float] = None) -> None:
|
def upgrade_read_to_write(self, timeout=None):
|
||||||
"""
|
"""
|
||||||
Attempts to upgrade from a shared read lock to an exclusive write.
|
Attempts to upgrade from a shared read lock to an exclusive write.
|
||||||
|
|
||||||
@@ -571,7 +546,7 @@ def upgrade_read_to_write(self, timeout: Optional[float] = None) -> None:
|
|||||||
else:
|
else:
|
||||||
raise LockUpgradeError(self.path)
|
raise LockUpgradeError(self.path)
|
||||||
|
|
||||||
def release_read(self, release_fn: ReleaseFnType = None) -> bool:
|
def release_read(self, release_fn=None):
|
||||||
"""Releases a read lock.
|
"""Releases a read lock.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -607,7 +582,7 @@ def release_read(self, release_fn: ReleaseFnType = None) -> bool:
|
|||||||
self._reads -= 1
|
self._reads -= 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def release_write(self, release_fn: ReleaseFnType = None) -> bool:
|
def release_write(self, release_fn=None):
|
||||||
"""Releases a write lock.
|
"""Releases a write lock.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -648,65 +623,65 @@ def release_write(self, release_fn: ReleaseFnType = None) -> bool:
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self):
|
||||||
if self._reads == 0 and self._writes == 0:
|
if self._reads == 0 and self._writes == 0:
|
||||||
os.unlink(self.path)
|
os.unlink(self.path)
|
||||||
else:
|
else:
|
||||||
raise LockError("Attempting to cleanup active lock.")
|
raise LockError("Attempting to cleanup active lock.")
|
||||||
|
|
||||||
def _get_counts_desc(self) -> str:
|
def _get_counts_desc(self):
|
||||||
return (
|
return (
|
||||||
"(reads {0}, writes {1})".format(self._reads, self._writes) if tty.is_verbose() else ""
|
"(reads {0}, writes {1})".format(self._reads, self._writes) if tty.is_verbose() else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
def _log_acquired(self, locktype, wait_time, nattempts) -> None:
|
def _log_acquired(self, locktype, wait_time, nattempts):
|
||||||
attempts_part = _attempts_str(wait_time, nattempts)
|
attempts_part = _attempts_str(wait_time, nattempts)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
desc = "Acquired at %s" % now.strftime("%H:%M:%S.%f")
|
desc = "Acquired at %s" % now.strftime("%H:%M:%S.%f")
|
||||||
self._log_debug(self._status_msg(locktype, "{0}{1}".format(desc, attempts_part)))
|
self._log_debug(self._status_msg(locktype, "{0}{1}".format(desc, attempts_part)))
|
||||||
|
|
||||||
def _log_acquiring(self, locktype) -> None:
|
def _log_acquiring(self, locktype):
|
||||||
self._log_debug(self._status_msg(locktype, "Acquiring"), level=3)
|
self._log_debug(self._status_msg(locktype, "Acquiring"), level=3)
|
||||||
|
|
||||||
def _log_debug(self, *args, **kwargs) -> None:
|
def _log_debug(self, *args, **kwargs):
|
||||||
"""Output lock debug messages."""
|
"""Output lock debug messages."""
|
||||||
kwargs["level"] = kwargs.get("level", 2)
|
kwargs["level"] = kwargs.get("level", 2)
|
||||||
tty.debug(*args, **kwargs)
|
tty.debug(*args, **kwargs)
|
||||||
|
|
||||||
def _log_downgraded(self, wait_time, nattempts) -> None:
|
def _log_downgraded(self, wait_time, nattempts):
|
||||||
attempts_part = _attempts_str(wait_time, nattempts)
|
attempts_part = _attempts_str(wait_time, nattempts)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
desc = "Downgraded at %s" % now.strftime("%H:%M:%S.%f")
|
desc = "Downgraded at %s" % now.strftime("%H:%M:%S.%f")
|
||||||
self._log_debug(self._status_msg("READ LOCK", "{0}{1}".format(desc, attempts_part)))
|
self._log_debug(self._status_msg("READ LOCK", "{0}{1}".format(desc, attempts_part)))
|
||||||
|
|
||||||
def _log_downgrading(self) -> None:
|
def _log_downgrading(self):
|
||||||
self._log_debug(self._status_msg("WRITE LOCK", "Downgrading"), level=3)
|
self._log_debug(self._status_msg("WRITE LOCK", "Downgrading"), level=3)
|
||||||
|
|
||||||
def _log_released(self, locktype) -> None:
|
def _log_released(self, locktype):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
desc = "Released at %s" % now.strftime("%H:%M:%S.%f")
|
desc = "Released at %s" % now.strftime("%H:%M:%S.%f")
|
||||||
self._log_debug(self._status_msg(locktype, desc))
|
self._log_debug(self._status_msg(locktype, desc))
|
||||||
|
|
||||||
def _log_releasing(self, locktype) -> None:
|
def _log_releasing(self, locktype):
|
||||||
self._log_debug(self._status_msg(locktype, "Releasing"), level=3)
|
self._log_debug(self._status_msg(locktype, "Releasing"), level=3)
|
||||||
|
|
||||||
def _log_upgraded(self, wait_time, nattempts) -> None:
|
def _log_upgraded(self, wait_time, nattempts):
|
||||||
attempts_part = _attempts_str(wait_time, nattempts)
|
attempts_part = _attempts_str(wait_time, nattempts)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
desc = "Upgraded at %s" % now.strftime("%H:%M:%S.%f")
|
desc = "Upgraded at %s" % now.strftime("%H:%M:%S.%f")
|
||||||
self._log_debug(self._status_msg("WRITE LOCK", "{0}{1}".format(desc, attempts_part)))
|
self._log_debug(self._status_msg("WRITE LOCK", "{0}{1}".format(desc, attempts_part)))
|
||||||
|
|
||||||
def _log_upgrading(self) -> None:
|
def _log_upgrading(self):
|
||||||
self._log_debug(self._status_msg("READ LOCK", "Upgrading"), level=3)
|
self._log_debug(self._status_msg("READ LOCK", "Upgrading"), level=3)
|
||||||
|
|
||||||
def _status_msg(self, locktype: str, status: str) -> str:
|
def _status_msg(self, locktype, status):
|
||||||
status_desc = "[{0}] {1}".format(status, self._get_counts_desc())
|
status_desc = "[{0}] {1}".format(status, self._get_counts_desc())
|
||||||
return "{0}{1.desc}: {1.path}[{1._start}:{1._length}] {2}".format(
|
return "{0}{1.desc}: {1.path}[{1._start}:{1._length}] {2}".format(
|
||||||
locktype, self, status_desc
|
locktype, self, status_desc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LockTransaction:
|
class LockTransaction(object):
|
||||||
"""Simple nested transaction context manager that uses a file lock.
|
"""Simple nested transaction context manager that uses a file lock.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -734,13 +709,7 @@ class LockTransaction:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, lock, acquire=None, release=None, timeout=None):
|
||||||
self,
|
|
||||||
lock: Lock,
|
|
||||||
acquire: Union[ReleaseFnType, ContextManager] = None,
|
|
||||||
release: Union[ReleaseFnType, ContextManager] = None,
|
|
||||||
timeout: Optional[float] = None,
|
|
||||||
) -> None:
|
|
||||||
self._lock = lock
|
self._lock = lock
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._acquire_fn = acquire
|
self._acquire_fn = acquire
|
||||||
@@ -755,20 +724,15 @@ def __enter__(self):
|
|||||||
else:
|
else:
|
||||||
return self._as
|
return self._as
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(self, type, value, traceback):
|
||||||
self,
|
|
||||||
exc_type: Optional[Type[BaseException]],
|
|
||||||
exc_value: Optional[BaseException],
|
|
||||||
traceback: Optional[TracebackType],
|
|
||||||
) -> bool:
|
|
||||||
suppress = False
|
suppress = False
|
||||||
|
|
||||||
def release_fn():
|
def release_fn():
|
||||||
if self._release_fn is not None:
|
if self._release_fn is not None:
|
||||||
return self._release_fn(exc_type, exc_value, traceback)
|
return self._release_fn(type, value, traceback)
|
||||||
|
|
||||||
if self._as and hasattr(self._as, "__exit__"):
|
if self._as and hasattr(self._as, "__exit__"):
|
||||||
if self._as.__exit__(exc_type, exc_value, traceback):
|
if self._as.__exit__(type, value, traceback):
|
||||||
suppress = True
|
suppress = True
|
||||||
|
|
||||||
if self._exit(release_fn):
|
if self._exit(release_fn):
|
||||||
@@ -776,12 +740,6 @@ def release_fn():
|
|||||||
|
|
||||||
return suppress
|
return suppress
|
||||||
|
|
||||||
def _enter(self) -> bool:
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def _exit(self, release_fn: ReleaseFnType) -> bool:
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
|
|
||||||
class ReadTransaction(LockTransaction):
|
class ReadTransaction(LockTransaction):
|
||||||
"""LockTransaction context manager that does a read and releases it."""
|
"""LockTransaction context manager that does a read and releases it."""
|
||||||
@@ -812,7 +770,7 @@ class LockDowngradeError(LockError):
|
|||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
msg = "Cannot downgrade lock from write to read on file: %s" % path
|
msg = "Cannot downgrade lock from write to read on file: %s" % path
|
||||||
super().__init__(msg)
|
super(LockDowngradeError, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
class LockLimitError(LockError):
|
class LockLimitError(LockError):
|
||||||
@@ -824,10 +782,10 @@ class LockTimeoutError(LockError):
|
|||||||
|
|
||||||
def __init__(self, lock_type, path, time, attempts):
|
def __init__(self, lock_type, path, time, attempts):
|
||||||
fmt = "Timed out waiting for a {} lock after {}.\n Made {} {} on file: {}"
|
fmt = "Timed out waiting for a {} lock after {}.\n Made {} {} on file: {}"
|
||||||
super().__init__(
|
super(LockTimeoutError, self).__init__(
|
||||||
fmt.format(
|
fmt.format(
|
||||||
lock_type,
|
lock_type,
|
||||||
lang.pretty_seconds(time),
|
pretty_seconds(time),
|
||||||
attempts,
|
attempts,
|
||||||
"attempt" if attempts == 1 else "attempts",
|
"attempt" if attempts == 1 else "attempts",
|
||||||
path,
|
path,
|
||||||
@@ -840,7 +798,7 @@ class LockUpgradeError(LockError):
|
|||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
msg = "Cannot upgrade lock from read to write on file: %s" % path
|
msg = "Cannot upgrade lock from read to write on file: %s" % path
|
||||||
super().__init__(msg)
|
super(LockUpgradeError, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
class LockPermissionError(LockError):
|
class LockPermissionError(LockError):
|
||||||
@@ -852,7 +810,7 @@ class LockROFileError(LockPermissionError):
|
|||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
msg = "Can't take write lock on read-only file: %s" % path
|
msg = "Can't take write lock on read-only file: %s" % path
|
||||||
super().__init__(msg)
|
super(LockROFileError, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
class CantCreateLockError(LockPermissionError):
|
class CantCreateLockError(LockPermissionError):
|
||||||
@@ -861,4 +819,4 @@ class CantCreateLockError(LockPermissionError):
|
|||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
msg = "cannot create lock '%s': " % path
|
msg = "cannot create lock '%s': " % path
|
||||||
msg += "file does not exist and location is not writable"
|
msg += "file does not exist and location is not writable"
|
||||||
super().__init__(msg)
|
super(LockError, self).__init__(msg)
|
||||||
|
|||||||
@@ -2,189 +2,77 @@
|
|||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
import errno
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from os.path import exists, join
|
||||||
|
|
||||||
from llnl.util import lang, tty
|
from llnl.util import lang
|
||||||
|
|
||||||
from ..path import system_path_filter
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
from win32file import CreateHardLink
|
from win32file import CreateHardLink
|
||||||
|
|
||||||
is_windows = sys.platform == "win32"
|
|
||||||
|
|
||||||
|
def symlink(real_path, link_path):
|
||||||
def symlink(source_path: str, link_path: str, allow_broken_symlinks: bool = not is_windows):
|
|
||||||
"""
|
"""
|
||||||
Create a link.
|
Create a symbolic link.
|
||||||
|
|
||||||
On non-Windows and Windows with System Administrator
|
On Windows, use junctions if os.symlink fails.
|
||||||
privleges this will be a normal symbolic link via
|
|
||||||
os.symlink.
|
|
||||||
|
|
||||||
On Windows without privledges the link will be a
|
|
||||||
junction for a directory and a hardlink for a file.
|
|
||||||
On Windows the various link types are:
|
|
||||||
|
|
||||||
Symbolic Link: A link to a file or directory on the
|
|
||||||
same or different volume (drive letter) or even to
|
|
||||||
a remote file or directory (using UNC in its path).
|
|
||||||
Need System Administrator privileges to make these.
|
|
||||||
|
|
||||||
Hard Link: A link to a file on the same volume (drive
|
|
||||||
letter) only. Every file (file's data) has at least 1
|
|
||||||
hard link (file's name). But when this method creates
|
|
||||||
a new hard link there will be 2. Deleting all hard
|
|
||||||
links effectively deletes the file. Don't need System
|
|
||||||
Administrator privileges.
|
|
||||||
|
|
||||||
Junction: A link to a directory on the same or different
|
|
||||||
volume (drive letter) but not to a remote directory. Don't
|
|
||||||
need System Administrator privileges.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
source_path (str): The real file or directory that the link points to.
|
|
||||||
Must be absolute OR relative to the link.
|
|
||||||
link_path (str): The path where the link will exist.
|
|
||||||
allow_broken_symlinks (bool): On Linux or Mac, don't raise an exception if the source_path
|
|
||||||
doesn't exist. This will still raise an exception on Windows.
|
|
||||||
"""
|
"""
|
||||||
source_path = os.path.normpath(source_path)
|
if sys.platform != "win32":
|
||||||
win_source_path = source_path
|
os.symlink(real_path, link_path)
|
||||||
link_path = os.path.normpath(link_path)
|
elif _win32_can_symlink():
|
||||||
|
# Windows requires target_is_directory=True when the target is a dir.
|
||||||
# Never allow broken links on Windows.
|
os.symlink(real_path, link_path, target_is_directory=os.path.isdir(real_path))
|
||||||
if sys.platform == "win32" and allow_broken_symlinks:
|
|
||||||
raise ValueError("allow_broken_symlinks parameter cannot be True on Windows.")
|
|
||||||
|
|
||||||
if not allow_broken_symlinks:
|
|
||||||
# Perform basic checks to make sure symlinking will succeed
|
|
||||||
if os.path.lexists(link_path):
|
|
||||||
raise AlreadyExistsError(
|
|
||||||
f"Link path ({link_path}) already exists. Cannot create link."
|
|
||||||
)
|
|
||||||
|
|
||||||
if not os.path.exists(source_path):
|
|
||||||
if os.path.isabs(source_path) and not allow_broken_symlinks:
|
|
||||||
# An absolute source path that does not exist will result in a broken link.
|
|
||||||
raise SymlinkError(
|
|
||||||
f"Source path ({source_path}) is absolute but does not exist. Resulting "
|
|
||||||
f"link would be broken so not making link."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# os.symlink can create a link when the given source path is relative to
|
|
||||||
# the link path. Emulate this behavior and check to see if the source exists
|
|
||||||
# relative to the link path ahead of link creation to prevent broken
|
|
||||||
# links from being made.
|
|
||||||
link_parent_dir = os.path.dirname(link_path)
|
|
||||||
relative_path = os.path.join(link_parent_dir, source_path)
|
|
||||||
if os.path.exists(relative_path):
|
|
||||||
# In order to work on windows, the source path needs to be modified to be
|
|
||||||
# relative because hardlink/junction dont resolve relative paths the same
|
|
||||||
# way as os.symlink. This is ignored on other operating systems.
|
|
||||||
win_source_path = relative_path
|
|
||||||
elif not allow_broken_symlinks:
|
|
||||||
raise SymlinkError(
|
|
||||||
f"The source path ({source_path}) is not relative to the link path "
|
|
||||||
f"({link_path}). Resulting link would be broken so not making link."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create the symlink
|
|
||||||
if sys.platform == "win32" and not _windows_can_symlink():
|
|
||||||
_windows_create_link(win_source_path, link_path)
|
|
||||||
else:
|
else:
|
||||||
os.symlink(source_path, link_path, target_is_directory=os.path.isdir(source_path))
|
try:
|
||||||
|
# Try to use junctions
|
||||||
|
_win32_junction(real_path, link_path)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.EEXIST:
|
||||||
|
# EEXIST error indicates that file we're trying to "link"
|
||||||
|
# is already present, don't bother trying to copy which will also fail
|
||||||
|
# just raise
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# If all else fails, fall back to copying files
|
||||||
|
shutil.copyfile(real_path, link_path)
|
||||||
|
|
||||||
|
|
||||||
def islink(path: str) -> bool:
|
def islink(path):
|
||||||
"""Override os.islink to give correct answer for spack logic.
|
return os.path.islink(path) or _win32_is_junction(path)
|
||||||
|
|
||||||
For Non-Windows: a link can be determined with the os.path.islink method.
|
|
||||||
Windows-only methods will return false for other operating systems.
|
|
||||||
|
|
||||||
For Windows: spack considers symlinks, hard links, and junctions to
|
|
||||||
all be links, so if any of those are True, return True.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path (str): path to check if it is a link.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool - whether the path is any kind link or not.
|
|
||||||
"""
|
|
||||||
return any([os.path.islink(path), _windows_is_junction(path), _windows_is_hardlink(path)])
|
|
||||||
|
|
||||||
|
|
||||||
def _windows_is_hardlink(path: str) -> bool:
|
# '_win32' functions based on
|
||||||
"""Determines if a path is a windows hard link. This is accomplished
|
# https://github.com/Erotemic/ubelt/blob/master/ubelt/util_links.py
|
||||||
by looking at the number of links using os.stat. A non-hard-linked file
|
def _win32_junction(path, link):
|
||||||
will have a st_nlink value of 1, whereas a hard link will have a value
|
# junctions require absolute paths
|
||||||
larger than 1. Note that both the original and hard-linked file will
|
if not os.path.isabs(link):
|
||||||
return True because they share the same inode.
|
link = os.path.abspath(link)
|
||||||
|
|
||||||
Args:
|
# os.symlink will fail if link exists, emulate the behavior here
|
||||||
path (str): Windows path to check for a hard link
|
if exists(link):
|
||||||
|
raise OSError(errno.EEXIST, "File exists: %s -> %s" % (link, path))
|
||||||
|
|
||||||
Returns:
|
if not os.path.isabs(path):
|
||||||
bool - Whether the path is a hard link or not.
|
parent = os.path.join(link, os.pardir)
|
||||||
"""
|
path = os.path.join(parent, path)
|
||||||
if sys.platform != "win32" or os.path.islink(path) or not os.path.exists(path):
|
path = os.path.abspath(path)
|
||||||
return False
|
|
||||||
|
|
||||||
return os.stat(path).st_nlink > 1
|
CreateHardLink(link, path)
|
||||||
|
|
||||||
|
|
||||||
def _windows_is_junction(path: str) -> bool:
|
|
||||||
"""Determines if a path is a windows junction. A junction can be
|
|
||||||
determined using a bitwise AND operation between the file's
|
|
||||||
attribute bitmask and the known junction bitmask (0x400).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path (str): A non-file path
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool - whether the path is a junction or not.
|
|
||||||
"""
|
|
||||||
if sys.platform != "win32" or os.path.islink(path) or os.path.isfile(path):
|
|
||||||
return False
|
|
||||||
|
|
||||||
import ctypes.wintypes
|
|
||||||
|
|
||||||
get_file_attributes = ctypes.windll.kernel32.GetFileAttributesW # type: ignore[attr-defined]
|
|
||||||
get_file_attributes.argtypes = (ctypes.wintypes.LPWSTR,)
|
|
||||||
get_file_attributes.restype = ctypes.wintypes.DWORD
|
|
||||||
|
|
||||||
invalid_file_attributes = 0xFFFFFFFF
|
|
||||||
reparse_point = 0x400
|
|
||||||
file_attr = get_file_attributes(str(path))
|
|
||||||
|
|
||||||
if file_attr == invalid_file_attributes:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return file_attr & reparse_point > 0
|
|
||||||
|
|
||||||
|
|
||||||
@lang.memoized
|
@lang.memoized
|
||||||
def _windows_can_symlink() -> bool:
|
def _win32_can_symlink():
|
||||||
"""
|
|
||||||
Determines if windows is able to make a symlink depending on
|
|
||||||
the system configuration and the level of the user's permissions.
|
|
||||||
"""
|
|
||||||
if sys.platform != "win32":
|
|
||||||
tty.warn("windows_can_symlink method can't be used on non-Windows OS.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
tempdir = tempfile.mkdtemp()
|
tempdir = tempfile.mkdtemp()
|
||||||
|
|
||||||
dpath = os.path.join(tempdir, "dpath")
|
dpath = join(tempdir, "dpath")
|
||||||
fpath = os.path.join(tempdir, "fpath.txt")
|
fpath = join(tempdir, "fpath.txt")
|
||||||
|
|
||||||
dlink = os.path.join(tempdir, "dlink")
|
dlink = join(tempdir, "dlink")
|
||||||
flink = os.path.join(tempdir, "flink.txt")
|
flink = join(tempdir, "flink.txt")
|
||||||
|
|
||||||
import llnl.util.filesystem as fs
|
import llnl.util.filesystem as fs
|
||||||
|
|
||||||
@@ -208,140 +96,24 @@ def _windows_can_symlink() -> bool:
|
|||||||
return can_symlink_directories and can_symlink_files
|
return can_symlink_directories and can_symlink_files
|
||||||
|
|
||||||
|
|
||||||
def _windows_create_link(source: str, link: str):
|
def _win32_is_junction(path):
|
||||||
"""
|
"""
|
||||||
Attempts to create a Hard Link or Junction as an alternative
|
Determines if a path is a win32 junction
|
||||||
to a symbolic link. This is called when symbolic links cannot
|
|
||||||
be created.
|
|
||||||
"""
|
"""
|
||||||
if sys.platform != "win32":
|
if os.path.islink(path):
|
||||||
raise SymlinkError("windows_create_link method can't be used on non-Windows OS.")
|
return False
|
||||||
elif os.path.isdir(source):
|
|
||||||
_windows_create_junction(source=source, link=link)
|
|
||||||
elif os.path.isfile(source):
|
|
||||||
_windows_create_hard_link(path=source, link=link)
|
|
||||||
else:
|
|
||||||
raise SymlinkError(
|
|
||||||
f"Cannot create link from {source}. It is neither a file nor a directory."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import ctypes.wintypes
|
||||||
|
|
||||||
def _windows_create_junction(source: str, link: str):
|
GetFileAttributes = ctypes.windll.kernel32.GetFileAttributesW
|
||||||
"""Duly verify that the path and link are eligible to create a junction,
|
GetFileAttributes.argtypes = (ctypes.wintypes.LPWSTR,)
|
||||||
then create the junction.
|
GetFileAttributes.restype = ctypes.wintypes.DWORD
|
||||||
"""
|
|
||||||
if sys.platform != "win32":
|
|
||||||
raise SymlinkError("windows_create_junction method can't be used on non-Windows OS.")
|
|
||||||
elif not os.path.exists(source):
|
|
||||||
raise SymlinkError("Source path does not exist, cannot create a junction.")
|
|
||||||
elif os.path.lexists(link):
|
|
||||||
raise AlreadyExistsError("Link path already exists, cannot create a junction.")
|
|
||||||
elif not os.path.isdir(source):
|
|
||||||
raise SymlinkError("Source path is not a directory, cannot create a junction.")
|
|
||||||
|
|
||||||
import subprocess
|
INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
|
||||||
|
FILE_ATTRIBUTE_REPARSE_POINT = 0x400
|
||||||
|
|
||||||
cmd = ["cmd", "/C", "mklink", "/J", link, source]
|
res = GetFileAttributes(path)
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
return res != INVALID_FILE_ATTRIBUTES and bool(res & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||||
out, err = proc.communicate()
|
|
||||||
tty.debug(out.decode())
|
|
||||||
if proc.returncode != 0:
|
|
||||||
err = err.decode()
|
|
||||||
tty.error(err)
|
|
||||||
raise SymlinkError("Make junction command returned a non-zero return code.", err)
|
|
||||||
|
|
||||||
|
return False
|
||||||
def _windows_create_hard_link(path: str, link: str):
|
|
||||||
"""Duly verify that the path and link are eligible to create a hard
|
|
||||||
link, then create the hard link.
|
|
||||||
"""
|
|
||||||
if sys.platform != "win32":
|
|
||||||
raise SymlinkError("windows_create_hard_link method can't be used on non-Windows OS.")
|
|
||||||
elif not os.path.exists(path):
|
|
||||||
raise SymlinkError(f"File path {path} does not exist. Cannot create hard link.")
|
|
||||||
elif os.path.lexists(link):
|
|
||||||
raise AlreadyExistsError(f"Link path ({link}) already exists. Cannot create hard link.")
|
|
||||||
elif not os.path.isfile(path):
|
|
||||||
raise SymlinkError(f"File path ({link}) is not a file. Cannot create hard link.")
|
|
||||||
else:
|
|
||||||
tty.debug(f"Creating hard link {link} pointing to {path}")
|
|
||||||
CreateHardLink(link, path)
|
|
||||||
|
|
||||||
|
|
||||||
def readlink(path: str):
|
|
||||||
"""Spack utility to override of os.readlink method to work cross platform"""
|
|
||||||
if _windows_is_hardlink(path):
|
|
||||||
return _windows_read_hard_link(path)
|
|
||||||
elif _windows_is_junction(path):
|
|
||||||
return _windows_read_junction(path)
|
|
||||||
else:
|
|
||||||
return os.readlink(path)
|
|
||||||
|
|
||||||
|
|
||||||
def _windows_read_hard_link(link: str) -> str:
|
|
||||||
"""Find all of the files that point to the same inode as the link"""
|
|
||||||
if sys.platform != "win32":
|
|
||||||
raise SymlinkError("Can't read hard link on non-Windows OS.")
|
|
||||||
link = os.path.abspath(link)
|
|
||||||
fsutil_cmd = ["fsutil", "hardlink", "list", link]
|
|
||||||
proc = subprocess.Popen(fsutil_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
||||||
out, err = proc.communicate()
|
|
||||||
if proc.returncode != 0:
|
|
||||||
raise SymlinkError(f"An error occurred while reading hard link: {err.decode()}")
|
|
||||||
|
|
||||||
# fsutil response does not include the drive name, so append it back to each linked file.
|
|
||||||
drive, link_tail = os.path.splitdrive(os.path.abspath(link))
|
|
||||||
links = set([os.path.join(drive, p) for p in out.decode().splitlines()])
|
|
||||||
links.remove(link)
|
|
||||||
if len(links) == 1:
|
|
||||||
return links.pop()
|
|
||||||
elif len(links) > 1:
|
|
||||||
# TODO: How best to handle the case where 3 or more paths point to a single inode?
|
|
||||||
raise SymlinkError(f"Found multiple paths pointing to the same inode {links}")
|
|
||||||
else:
|
|
||||||
raise SymlinkError("Cannot determine hard link source path.")
|
|
||||||
|
|
||||||
|
|
||||||
def _windows_read_junction(link: str):
|
|
||||||
"""Find the path that a junction points to."""
|
|
||||||
if sys.platform != "win32":
|
|
||||||
raise SymlinkError("Can't read junction on non-Windows OS.")
|
|
||||||
|
|
||||||
link = os.path.abspath(link)
|
|
||||||
link_basename = os.path.basename(link)
|
|
||||||
link_parent = os.path.dirname(link)
|
|
||||||
fsutil_cmd = ["dir", "/a:l", link_parent]
|
|
||||||
proc = subprocess.Popen(fsutil_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
||||||
out, err = proc.communicate()
|
|
||||||
if proc.returncode != 0:
|
|
||||||
raise SymlinkError(f"An error occurred while reading junction: {err.decode()}")
|
|
||||||
matches = re.search(rf"<JUNCTION>\s+{link_basename} \[(.*)]", out.decode())
|
|
||||||
if matches:
|
|
||||||
return matches.group(1)
|
|
||||||
else:
|
|
||||||
raise SymlinkError("Could not find junction path.")
|
|
||||||
|
|
||||||
|
|
||||||
@system_path_filter
|
|
||||||
def resolve_link_target_relative_to_the_link(link):
|
|
||||||
"""
|
|
||||||
os.path.isdir uses os.path.exists, which for links will check
|
|
||||||
the existence of the link target. If the link target is relative to
|
|
||||||
the link, we need to construct a pathname that is valid from
|
|
||||||
our cwd (which may not be the same as the link's directory)
|
|
||||||
"""
|
|
||||||
target = readlink(link)
|
|
||||||
if os.path.isabs(target):
|
|
||||||
return target
|
|
||||||
link_dir = os.path.dirname(os.path.abspath(link))
|
|
||||||
return os.path.join(link_dir, target)
|
|
||||||
|
|
||||||
|
|
||||||
class SymlinkError(RuntimeError):
|
|
||||||
"""Exception class for errors raised while creating symlinks,
|
|
||||||
junctions and hard links
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class AlreadyExistsError(SymlinkError):
|
|
||||||
"""Link path already exists."""
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
@@ -12,7 +14,6 @@
|
|||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sys import platform as _platform
|
from sys import platform as _platform
|
||||||
from typing import NoReturn
|
|
||||||
|
|
||||||
if _platform != "win32":
|
if _platform != "win32":
|
||||||
import fcntl
|
import fcntl
|
||||||
@@ -211,7 +212,6 @@ def info(message, *args, **kwargs):
|
|||||||
stream.write(line + "\n")
|
stream.write(line + "\n")
|
||||||
else:
|
else:
|
||||||
stream.write(indent + _output_filter(str(arg)) + "\n")
|
stream.write(indent + _output_filter(str(arg)) + "\n")
|
||||||
stream.flush()
|
|
||||||
|
|
||||||
|
|
||||||
def verbose(message, *args, **kwargs):
|
def verbose(message, *args, **kwargs):
|
||||||
@@ -246,7 +246,7 @@ def warn(message, *args, **kwargs):
|
|||||||
info("Warning: " + str(message), *args, **kwargs)
|
info("Warning: " + str(message), *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def die(message, *args, **kwargs) -> NoReturn:
|
def die(message, *args, **kwargs):
|
||||||
kwargs.setdefault("countback", 4)
|
kwargs.setdefault("countback", 4)
|
||||||
error(message, *args, **kwargs)
|
error(message, *args, **kwargs)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"""
|
"""
|
||||||
Routines for printing columnar output. See ``colify()`` for more information.
|
Routines for printing columnar output. See ``colify()`` for more information.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import division, unicode_literals
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -59,6 +59,8 @@
|
|||||||
|
|
||||||
To output an @, use '@@'. To output a } inside braces, use '}}'.
|
To output an @, use '@@'. To output a } inside braces, use '}}'.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
@@ -68,7 +70,7 @@ class ColorParseError(Exception):
|
|||||||
"""Raised when a color format fails to parse."""
|
"""Raised when a color format fails to parse."""
|
||||||
|
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super().__init__(message)
|
super(ColorParseError, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
# Text styles for ansi codes
|
# Text styles for ansi codes
|
||||||
@@ -203,7 +205,7 @@ def color_when(value):
|
|||||||
set_color_when(old_value)
|
set_color_when(old_value)
|
||||||
|
|
||||||
|
|
||||||
class match_to_ansi:
|
class match_to_ansi(object):
|
||||||
def __init__(self, color=True, enclose=False):
|
def __init__(self, color=True, enclose=False):
|
||||||
self.color = _color_when_value(color)
|
self.color = _color_when_value(color)
|
||||||
self.enclose = enclose
|
self.enclose = enclose
|
||||||
@@ -319,7 +321,7 @@ def cescape(string):
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
class ColorStream:
|
class ColorStream(object):
|
||||||
def __init__(self, stream, color=None):
|
def __init__(self, stream, color=None):
|
||||||
self._stream = stream
|
self._stream = stream
|
||||||
self._color = color
|
self._color = color
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
"""Utility classes for logging the output of blocks of code.
|
"""Utility classes for logging the output of blocks of code.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import ctypes
|
import ctypes
|
||||||
import errno
|
import errno
|
||||||
@@ -65,7 +67,7 @@ def _strip(line):
|
|||||||
return _escape.sub("", line)
|
return _escape.sub("", line)
|
||||||
|
|
||||||
|
|
||||||
class keyboard_input:
|
class keyboard_input(object):
|
||||||
"""Context manager to disable line editing and echoing.
|
"""Context manager to disable line editing and echoing.
|
||||||
|
|
||||||
Use this with ``sys.stdin`` for keyboard input, e.g.::
|
Use this with ``sys.stdin`` for keyboard input, e.g.::
|
||||||
@@ -242,7 +244,7 @@ def __exit__(self, exc_type, exception, traceback):
|
|||||||
signal.signal(signum, old_handler)
|
signal.signal(signum, old_handler)
|
||||||
|
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered(object):
|
||||||
"""Wrapper for Python streams that forces them to be unbuffered.
|
"""Wrapper for Python streams that forces them to be unbuffered.
|
||||||
|
|
||||||
This is implemented by forcing a flush after each write.
|
This is implemented by forcing a flush after each write.
|
||||||
@@ -287,7 +289,7 @@ def _file_descriptors_work(*streams):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class FileWrapper:
|
class FileWrapper(object):
|
||||||
"""Represents a file. Can be an open stream, a path to a file (not opened
|
"""Represents a file. Can be an open stream, a path to a file (not opened
|
||||||
yet), or neither. When unwrapped, it returns an open file (or file-like)
|
yet), or neither. When unwrapped, it returns an open file (or file-like)
|
||||||
object.
|
object.
|
||||||
@@ -329,7 +331,7 @@ def close(self):
|
|||||||
self.file.close()
|
self.file.close()
|
||||||
|
|
||||||
|
|
||||||
class MultiProcessFd:
|
class MultiProcessFd(object):
|
||||||
"""Return an object which stores a file descriptor and can be passed as an
|
"""Return an object which stores a file descriptor and can be passed as an
|
||||||
argument to a function run with ``multiprocessing.Process``, such that
|
argument to a function run with ``multiprocessing.Process``, such that
|
||||||
the file descriptor is available in the subprocess."""
|
the file descriptor is available in the subprocess."""
|
||||||
@@ -429,7 +431,7 @@ def log_output(*args, **kwargs):
|
|||||||
return nixlog(*args, **kwargs)
|
return nixlog(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class nixlog:
|
class nixlog(object):
|
||||||
"""
|
"""
|
||||||
Under the hood, we spawn a daemon and set up a pipe between this
|
Under the hood, we spawn a daemon and set up a pipe between this
|
||||||
process and the daemon. The daemon writes our output to both the
|
process and the daemon. The daemon writes our output to both the
|
||||||
@@ -750,7 +752,7 @@ def close(self):
|
|||||||
os.close(self.saved_stream)
|
os.close(self.saved_stream)
|
||||||
|
|
||||||
|
|
||||||
class winlog:
|
class winlog(object):
|
||||||
"""
|
"""
|
||||||
Similar to nixlog, with underlying
|
Similar to nixlog, with underlying
|
||||||
functionality ported to support Windows.
|
functionality ported to support Windows.
|
||||||
@@ -780,7 +782,7 @@ def __enter__(self):
|
|||||||
raise RuntimeError("file argument must be set by __init__ ")
|
raise RuntimeError("file argument must be set by __init__ ")
|
||||||
|
|
||||||
# Open both write and reading on logfile
|
# Open both write and reading on logfile
|
||||||
if isinstance(self.logfile, io.StringIO):
|
if type(self.logfile) == io.StringIO:
|
||||||
self._ioflag = True
|
self._ioflag = True
|
||||||
# cannot have two streams on tempfile, so we must make our own
|
# cannot have two streams on tempfile, so we must make our own
|
||||||
sys.stdout = self.logfile
|
sys.stdout = self.logfile
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
Note: The functionality in this module is unsupported on Windows
|
Note: The functionality in this module is unsupported on Windows
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProcessController:
|
class ProcessController(object):
|
||||||
"""Wrapper around some fundamental process control operations.
|
"""Wrapper around some fundamental process control operations.
|
||||||
|
|
||||||
This allows one process (the controller) to drive another (the
|
This allows one process (the controller) to drive another (the
|
||||||
@@ -155,7 +157,7 @@ def wait_running(self):
|
|||||||
self.wait(lambda: "T" not in self.proc_status())
|
self.wait(lambda: "T" not in self.proc_status())
|
||||||
|
|
||||||
|
|
||||||
class PseudoShell:
|
class PseudoShell(object):
|
||||||
"""Sets up controller and minion processes with a PTY.
|
"""Sets up controller and minion processes with a PTY.
|
||||||
|
|
||||||
You can create a ``PseudoShell`` if you want to test how some
|
You can create a ``PseudoShell`` if you want to test how some
|
||||||
|
|||||||
@@ -8,18 +8,16 @@
|
|||||||
from llnl.util.lang import memoized
|
from llnl.util.lang import memoized
|
||||||
|
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.version
|
|
||||||
from spack.compilers.clang import Clang
|
from spack.compilers.clang import Clang
|
||||||
|
from spack.spec import CompilerSpec
|
||||||
from spack.util.executable import Executable, ProcessError
|
from spack.util.executable import Executable, ProcessError
|
||||||
|
|
||||||
|
|
||||||
class ABI:
|
class ABI(object):
|
||||||
"""This class provides methods to test ABI compatibility between specs.
|
"""This class provides methods to test ABI compatibility between specs.
|
||||||
The current implementation is rather rough and could be improved."""
|
The current implementation is rather rough and could be improved."""
|
||||||
|
|
||||||
def architecture_compatible(
|
def architecture_compatible(self, target, constraint):
|
||||||
self, target: spack.spec.Spec, constraint: spack.spec.Spec
|
|
||||||
) -> bool:
|
|
||||||
"""Return true if architecture of target spec is ABI compatible
|
"""Return true if architecture of target spec is ABI compatible
|
||||||
to the architecture of constraint spec. If either the target
|
to the architecture of constraint spec. If either the target
|
||||||
or constraint specs have no architecture, target is also defined
|
or constraint specs have no architecture, target is also defined
|
||||||
@@ -36,7 +34,7 @@ def _gcc_get_libstdcxx_version(self, version):
|
|||||||
a compiler's libstdc++ or libgcc_s"""
|
a compiler's libstdc++ or libgcc_s"""
|
||||||
from spack.build_environment import dso_suffix
|
from spack.build_environment import dso_suffix
|
||||||
|
|
||||||
spec = spack.spec.CompilerSpec("gcc", version)
|
spec = CompilerSpec("gcc", version)
|
||||||
compilers = spack.compilers.compilers_for_spec(spec)
|
compilers = spack.compilers.compilers_for_spec(spec)
|
||||||
if not compilers:
|
if not compilers:
|
||||||
return None
|
return None
|
||||||
@@ -79,20 +77,16 @@ def _gcc_compiler_compare(self, pversion, cversion):
|
|||||||
return False
|
return False
|
||||||
return plib == clib
|
return plib == clib
|
||||||
|
|
||||||
def _intel_compiler_compare(
|
def _intel_compiler_compare(self, pversion, cversion):
|
||||||
self, pversion: spack.version.ClosedOpenRange, cversion: spack.version.ClosedOpenRange
|
|
||||||
) -> bool:
|
|
||||||
"""Returns true iff the intel version pversion and cversion
|
"""Returns true iff the intel version pversion and cversion
|
||||||
are ABI compatible"""
|
are ABI compatible"""
|
||||||
|
|
||||||
# Test major and minor versions. Ignore build version.
|
# Test major and minor versions. Ignore build version.
|
||||||
pv = pversion.lo
|
if len(pversion.version) < 2 or len(cversion.version) < 2:
|
||||||
cv = cversion.lo
|
return False
|
||||||
return pv.up_to(2) == cv.up_to(2)
|
return pversion.version[:2] == cversion.version[:2]
|
||||||
|
|
||||||
def compiler_compatible(
|
def compiler_compatible(self, parent, child, **kwargs):
|
||||||
self, parent: spack.spec.Spec, child: spack.spec.Spec, loose: bool = False
|
|
||||||
) -> bool:
|
|
||||||
"""Return true if compilers for parent and child are ABI compatible."""
|
"""Return true if compilers for parent and child are ABI compatible."""
|
||||||
if not parent.compiler or not child.compiler:
|
if not parent.compiler or not child.compiler:
|
||||||
return True
|
return True
|
||||||
@@ -101,7 +95,7 @@ def compiler_compatible(
|
|||||||
# Different compiler families are assumed ABI incompatible
|
# Different compiler families are assumed ABI incompatible
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if loose:
|
if kwargs.get("loose", False):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# TODO: Can we move the specialized ABI matching stuff
|
# TODO: Can we move the specialized ABI matching stuff
|
||||||
@@ -122,10 +116,9 @@ def compiler_compatible(
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def compatible(
|
def compatible(self, target, constraint, **kwargs):
|
||||||
self, target: spack.spec.Spec, constraint: spack.spec.Spec, loose: bool = False
|
|
||||||
) -> bool:
|
|
||||||
"""Returns true if target spec is ABI compatible to constraint spec"""
|
"""Returns true if target spec is ABI compatible to constraint spec"""
|
||||||
|
loosematch = kwargs.get("loose", False)
|
||||||
return self.architecture_compatible(target, constraint) and self.compiler_compatible(
|
return self.architecture_compatible(target, constraint) and self.compiler_compatible(
|
||||||
target, constraint, loose=loose
|
target, constraint, loose=loosematch
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,13 +38,10 @@ def _search_duplicate_compilers(error_cls):
|
|||||||
import ast
|
import ast
|
||||||
import collections
|
import collections
|
||||||
import collections.abc
|
import collections.abc
|
||||||
import glob
|
|
||||||
import inspect
|
import inspect
|
||||||
import itertools
|
import itertools
|
||||||
import pathlib
|
|
||||||
import pickle
|
import pickle
|
||||||
import re
|
import re
|
||||||
import warnings
|
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
import llnl.util.lang
|
import llnl.util.lang
|
||||||
@@ -63,7 +60,7 @@ def _search_duplicate_compilers(error_cls):
|
|||||||
GROUPS = collections.defaultdict(list)
|
GROUPS = collections.defaultdict(list)
|
||||||
|
|
||||||
|
|
||||||
class Error:
|
class Error(object):
|
||||||
"""Information on an error reported in a test."""
|
"""Information on an error reported in a test."""
|
||||||
|
|
||||||
def __init__(self, summary, details):
|
def __init__(self, summary, details):
|
||||||
@@ -289,7 +286,7 @@ def _check_build_test_callbacks(pkgs, error_cls):
|
|||||||
"""Ensure stand-alone test method is not included in build-time callbacks"""
|
"""Ensure stand-alone test method is not included in build-time callbacks"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
test_callbacks = getattr(pkg_cls, "build_time_test_callbacks", None)
|
test_callbacks = getattr(pkg_cls, "build_time_test_callbacks", None)
|
||||||
|
|
||||||
# TODO (post-34236): "test*"->"test_*" once remove deprecated methods
|
# TODO (post-34236): "test*"->"test_*" once remove deprecated methods
|
||||||
@@ -307,48 +304,33 @@ def _check_build_test_callbacks(pkgs, error_cls):
|
|||||||
|
|
||||||
@package_directives
|
@package_directives
|
||||||
def _check_patch_urls(pkgs, error_cls):
|
def _check_patch_urls(pkgs, error_cls):
|
||||||
"""Ensure that patches fetched from GitHub and GitLab have stable sha256
|
"""Ensure that patches fetched from GitHub have stable sha256 hashes."""
|
||||||
hashes."""
|
|
||||||
github_patch_url_re = (
|
github_patch_url_re = (
|
||||||
r"^https?://(?:patch-diff\.)?github(?:usercontent)?\.com/"
|
r"^https?://(?:patch-diff\.)?github(?:usercontent)?\.com/"
|
||||||
r".+/.+/(?:commit|pull)/[a-fA-F0-9]+\.(?:patch|diff)"
|
".+/.+/(?:commit|pull)/[a-fA-F0-9]*.(?:patch|diff)"
|
||||||
)
|
|
||||||
# Only .diff URLs have stable/full hashes:
|
|
||||||
# https://forum.gitlab.com/t/patches-with-full-index/29313
|
|
||||||
gitlab_patch_url_re = (
|
|
||||||
r"^https?://(?:.+)?gitlab(?:.+)/"
|
|
||||||
r".+/.+/-/(?:commit|merge_requests)/[a-fA-F0-9]+\.(?:patch|diff)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
for condition, patches in pkg_cls.patches.items():
|
for condition, patches in pkg_cls.patches.items():
|
||||||
for patch in patches:
|
for patch in patches:
|
||||||
if not isinstance(patch, spack.patch.UrlPatch):
|
if not isinstance(patch, spack.patch.UrlPatch):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if re.match(github_patch_url_re, patch.url):
|
if not re.match(github_patch_url_re, patch.url):
|
||||||
full_index_arg = "?full_index=1"
|
continue
|
||||||
if not patch.url.endswith(full_index_arg):
|
|
||||||
errors.append(
|
full_index_arg = "?full_index=1"
|
||||||
error_cls(
|
if not patch.url.endswith(full_index_arg):
|
||||||
"patch URL in package {0} must end with {1}".format(
|
errors.append(
|
||||||
pkg_cls.name, full_index_arg
|
error_cls(
|
||||||
),
|
"patch URL in package {0} must end with {1}".format(
|
||||||
[patch.url],
|
pkg_cls.name, full_index_arg
|
||||||
)
|
),
|
||||||
)
|
[patch.url],
|
||||||
elif re.match(gitlab_patch_url_re, patch.url):
|
|
||||||
if not patch.url.endswith(".diff"):
|
|
||||||
errors.append(
|
|
||||||
error_cls(
|
|
||||||
"patch URL in package {0} must end with .diff".format(
|
|
||||||
pkg_cls.name
|
|
||||||
),
|
|
||||||
[patch.url],
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@@ -360,7 +342,7 @@ def _search_for_reserved_attributes_names_in_packages(pkgs, error_cls):
|
|||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
name_definitions = collections.defaultdict(list)
|
name_definitions = collections.defaultdict(list)
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
|
|
||||||
for cls_item in inspect.getmro(pkg_cls):
|
for cls_item in inspect.getmro(pkg_cls):
|
||||||
for name in RESERVED_NAMES:
|
for name in RESERVED_NAMES:
|
||||||
@@ -401,7 +383,7 @@ def _ensure_packages_are_pickeleable(pkgs, error_cls):
|
|||||||
"""Ensure that package objects are pickleable"""
|
"""Ensure that package objects are pickleable"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
pkg = pkg_cls(spack.spec.Spec(pkg_name))
|
pkg = pkg_cls(spack.spec.Spec(pkg_name))
|
||||||
try:
|
try:
|
||||||
pickle.dumps(pkg)
|
pickle.dumps(pkg)
|
||||||
@@ -442,7 +424,7 @@ def _ensure_all_versions_can_produce_a_fetcher(pkgs, error_cls):
|
|||||||
"""Ensure all versions in a package can produce a fetcher"""
|
"""Ensure all versions in a package can produce a fetcher"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
pkg = pkg_cls(spack.spec.Spec(pkg_name))
|
pkg = pkg_cls(spack.spec.Spec(pkg_name))
|
||||||
try:
|
try:
|
||||||
spack.fetch_strategy.check_pkg_attributes(pkg)
|
spack.fetch_strategy.check_pkg_attributes(pkg)
|
||||||
@@ -467,7 +449,7 @@ def _ensure_docstring_and_no_fixme(pkgs, error_cls):
|
|||||||
]
|
]
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
details = []
|
details = []
|
||||||
filename = spack.repo.PATH.filename_for_package_name(pkg_name)
|
filename = spack.repo.path.filename_for_package_name(pkg_name)
|
||||||
with open(filename, "r") as package_file:
|
with open(filename, "r") as package_file:
|
||||||
for i, line in enumerate(package_file):
|
for i, line in enumerate(package_file):
|
||||||
pattern = next((r for r in fixme_regexes if r.search(line)), None)
|
pattern = next((r for r in fixme_regexes if r.search(line)), None)
|
||||||
@@ -479,7 +461,7 @@ def _ensure_docstring_and_no_fixme(pkgs, error_cls):
|
|||||||
error_msg = "Package '{}' contains boilerplate that need to be removed"
|
error_msg = "Package '{}' contains boilerplate that need to be removed"
|
||||||
errors.append(error_cls(error_msg.format(pkg_name), details))
|
errors.append(error_cls(error_msg.format(pkg_name), details))
|
||||||
|
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
if not pkg_cls.__doc__:
|
if not pkg_cls.__doc__:
|
||||||
error_msg = "Package '{}' miss a docstring"
|
error_msg = "Package '{}' miss a docstring"
|
||||||
errors.append(error_cls(error_msg.format(pkg_name), []))
|
errors.append(error_cls(error_msg.format(pkg_name), []))
|
||||||
@@ -492,7 +474,7 @@ def _ensure_all_packages_use_sha256_checksums(pkgs, error_cls):
|
|||||||
"""Ensure no packages use md5 checksums"""
|
"""Ensure no packages use md5 checksums"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
if pkg_cls.manual_download:
|
if pkg_cls.manual_download:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -529,7 +511,7 @@ def _ensure_env_methods_are_ported_to_builders(pkgs, error_cls):
|
|||||||
"""Ensure that methods modifying the build environment are ported to builder classes."""
|
"""Ensure that methods modifying the build environment are ported to builder classes."""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
buildsystem_variant, _ = pkg_cls.variants["build_system"]
|
buildsystem_variant, _ = pkg_cls.variants["build_system"]
|
||||||
buildsystem_names = [getattr(x, "value", x) for x in buildsystem_variant.values]
|
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]
|
builder_cls_names = [spack.builder.BUILDER_CLS[x].__name__ for x in buildsystem_names]
|
||||||
@@ -556,7 +538,7 @@ def _linting_package_file(pkgs, error_cls):
|
|||||||
"""Check for correctness of links"""
|
"""Check for correctness of links"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
|
|
||||||
# Does the homepage have http, and if so, does https work?
|
# Does the homepage have http, and if so, does https work?
|
||||||
if pkg_cls.homepage.startswith("http://"):
|
if pkg_cls.homepage.startswith("http://"):
|
||||||
@@ -580,7 +562,7 @@ def _unknown_variants_in_directives(pkgs, error_cls):
|
|||||||
"""Report unknown or wrong variants in directives for this package"""
|
"""Report unknown or wrong variants in directives for this package"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
|
|
||||||
# Check "conflicts" directive
|
# Check "conflicts" directive
|
||||||
for conflict, triggers in pkg_cls.conflicts.items():
|
for conflict, triggers in pkg_cls.conflicts.items():
|
||||||
@@ -646,15 +628,15 @@ def _unknown_variants_in_dependencies(pkgs, error_cls):
|
|||||||
"""Report unknown dependencies and wrong variants for dependencies"""
|
"""Report unknown dependencies and wrong variants for dependencies"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
filename = spack.repo.PATH.filename_for_package_name(pkg_name)
|
filename = spack.repo.path.filename_for_package_name(pkg_name)
|
||||||
for dependency_name, dependency_data in pkg_cls.dependencies.items():
|
for dependency_name, dependency_data in pkg_cls.dependencies.items():
|
||||||
# No need to analyze virtual packages
|
# No need to analyze virtual packages
|
||||||
if spack.repo.PATH.is_virtual(dependency_name):
|
if spack.repo.path.is_virtual(dependency_name):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dependency_pkg_cls = spack.repo.PATH.get_pkg_class(dependency_name)
|
dependency_pkg_cls = spack.repo.path.get_pkg_class(dependency_name)
|
||||||
except spack.repo.UnknownPackageError:
|
except spack.repo.UnknownPackageError:
|
||||||
# This dependency is completely missing, so report
|
# This dependency is completely missing, so report
|
||||||
# and continue the analysis
|
# and continue the analysis
|
||||||
@@ -693,7 +675,7 @@ def _ensure_variant_defaults_are_parsable(pkgs, error_cls):
|
|||||||
"""Ensures that variant defaults are present and parsable from cli"""
|
"""Ensures that variant defaults are present and parsable from cli"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
for variant_name, entry in pkg_cls.variants.items():
|
for variant_name, entry in pkg_cls.variants.items():
|
||||||
variant, _ = entry
|
variant, _ = entry
|
||||||
default_is_parsable = (
|
default_is_parsable = (
|
||||||
@@ -727,53 +709,27 @@ def _ensure_variant_defaults_are_parsable(pkgs, error_cls):
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
@package_directives
|
|
||||||
def _ensure_variants_have_descriptions(pkgs, error_cls):
|
|
||||||
"""Ensures that all variants have a description."""
|
|
||||||
errors = []
|
|
||||||
for pkg_name in pkgs:
|
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
|
||||||
for variant_name, entry in pkg_cls.variants.items():
|
|
||||||
variant, _ = entry
|
|
||||||
if not variant.description:
|
|
||||||
error_msg = "Variant '{}' in package '{}' is missing a description"
|
|
||||||
errors.append(error_cls(error_msg.format(variant_name, pkg_name), []))
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
@package_directives
|
@package_directives
|
||||||
def _version_constraints_are_satisfiable_by_some_version_in_repo(pkgs, error_cls):
|
def _version_constraints_are_satisfiable_by_some_version_in_repo(pkgs, error_cls):
|
||||||
"""Report if version constraints used in directives are not satisfiable"""
|
"""Report if version constraints used in directives are not satisfiable"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||||
filename = spack.repo.PATH.filename_for_package_name(pkg_name)
|
filename = spack.repo.path.filename_for_package_name(pkg_name)
|
||||||
dependencies_to_check = []
|
dependencies_to_check = []
|
||||||
for dependency_name, dependency_data in pkg_cls.dependencies.items():
|
for dependency_name, dependency_data in pkg_cls.dependencies.items():
|
||||||
# Skip virtual dependencies for the time being, check on
|
# Skip virtual dependencies for the time being, check on
|
||||||
# their versions can be added later
|
# their versions can be added later
|
||||||
if spack.repo.PATH.is_virtual(dependency_name):
|
if spack.repo.path.is_virtual(dependency_name):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dependencies_to_check.extend([edge.spec for edge in dependency_data.values()])
|
dependencies_to_check.extend([edge.spec for edge in dependency_data.values()])
|
||||||
|
|
||||||
host_architecture = spack.spec.ArchSpec.default_arch()
|
|
||||||
for s in dependencies_to_check:
|
for s in dependencies_to_check:
|
||||||
dependency_pkg_cls = None
|
dependency_pkg_cls = None
|
||||||
try:
|
try:
|
||||||
dependency_pkg_cls = spack.repo.PATH.get_pkg_class(s.name)
|
dependency_pkg_cls = spack.repo.path.get_pkg_class(s.name)
|
||||||
# Some packages have hacks that might cause failures on some platform
|
assert any(v.intersects(s.versions) for v in list(dependency_pkg_cls.versions))
|
||||||
# Allow to explicitly set conditions to skip version checks in that case
|
|
||||||
skip_conditions = getattr(dependency_pkg_cls, "skip_version_audit", [])
|
|
||||||
skip_version_check = False
|
|
||||||
for condition in skip_conditions:
|
|
||||||
if host_architecture.satisfies(spack.spec.Spec(condition).architecture):
|
|
||||||
skip_version_check = True
|
|
||||||
break
|
|
||||||
assert skip_version_check or any(
|
|
||||||
v.intersects(s.versions) for v in list(dependency_pkg_cls.versions)
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
summary = (
|
summary = (
|
||||||
"{0}: dependency on {1} cannot be satisfied " "by known versions of {1.name}"
|
"{0}: dependency on {1} cannot be satisfied " "by known versions of {1.name}"
|
||||||
@@ -805,7 +761,7 @@ def _analyze_variants_in_directive(pkg, constraint, directive, error_cls):
|
|||||||
except variant_exceptions as e:
|
except variant_exceptions as e:
|
||||||
summary = pkg.name + ': wrong variant in "{0}" directive'
|
summary = pkg.name + ': wrong variant in "{0}" directive'
|
||||||
summary = summary.format(directive)
|
summary = summary.format(directive)
|
||||||
filename = spack.repo.PATH.filename_for_package_name(pkg.name)
|
filename = spack.repo.path.filename_for_package_name(pkg.name)
|
||||||
|
|
||||||
error_msg = str(e).strip()
|
error_msg = str(e).strip()
|
||||||
if isinstance(e, KeyError):
|
if isinstance(e, KeyError):
|
||||||
@@ -816,76 +772,3 @@ def _analyze_variants_in_directive(pkg, constraint, directive, error_cls):
|
|||||||
errors.append(err)
|
errors.append(err)
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
#: Sanity checks on package directives
|
|
||||||
external_detection = AuditClass(
|
|
||||||
group="externals",
|
|
||||||
tag="PKG-EXTERNALS",
|
|
||||||
description="Sanity checks for external software detection",
|
|
||||||
kwargs=("pkgs",),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def packages_with_detection_tests():
|
|
||||||
"""Return the list of packages with a corresponding detection_test.yaml file."""
|
|
||||||
import spack.config
|
|
||||||
import spack.util.path
|
|
||||||
|
|
||||||
to_be_tested = []
|
|
||||||
for current_repo in spack.repo.PATH.repos:
|
|
||||||
namespace = current_repo.namespace
|
|
||||||
packages_dir = pathlib.PurePath(current_repo.packages_path)
|
|
||||||
pattern = packages_dir / "**" / "detection_test.yaml"
|
|
||||||
pkgs_with_tests = [
|
|
||||||
f"{namespace}.{str(pathlib.PurePath(x).parent.name)}" for x in glob.glob(str(pattern))
|
|
||||||
]
|
|
||||||
to_be_tested.extend(pkgs_with_tests)
|
|
||||||
|
|
||||||
return to_be_tested
|
|
||||||
|
|
||||||
|
|
||||||
@external_detection
|
|
||||||
def _test_detection_by_executable(pkgs, error_cls):
|
|
||||||
"""Test drive external detection for packages"""
|
|
||||||
import spack.detection
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Filter the packages and retain only the ones with detection tests
|
|
||||||
pkgs_with_tests = packages_with_detection_tests()
|
|
||||||
selected_pkgs = []
|
|
||||||
for current_package in pkgs_with_tests:
|
|
||||||
_, unqualified_name = spack.repo.partition_package_name(current_package)
|
|
||||||
# Check for both unqualified name and qualified name
|
|
||||||
if unqualified_name in pkgs or current_package in pkgs:
|
|
||||||
selected_pkgs.append(current_package)
|
|
||||||
selected_pkgs.sort()
|
|
||||||
|
|
||||||
if not selected_pkgs:
|
|
||||||
summary = "No detection test to run"
|
|
||||||
details = [f' "{p}" has no detection test' for p in pkgs]
|
|
||||||
warnings.warn("\n".join([summary] + details))
|
|
||||||
return errors
|
|
||||||
|
|
||||||
for pkg_name in selected_pkgs:
|
|
||||||
for idx, test_runner in enumerate(
|
|
||||||
spack.detection.detection_tests(pkg_name, spack.repo.PATH)
|
|
||||||
):
|
|
||||||
specs = test_runner.execute()
|
|
||||||
expected_specs = test_runner.expected_specs
|
|
||||||
|
|
||||||
not_detected = set(expected_specs) - set(specs)
|
|
||||||
if not_detected:
|
|
||||||
summary = pkg_name + ": cannot detect some specs"
|
|
||||||
details = [f'"{s}" was not detected [test_id={idx}]' for s in sorted(not_detected)]
|
|
||||||
errors.append(error_cls(summary=summary, details=details))
|
|
||||||
|
|
||||||
not_expected = set(specs) - set(expected_specs)
|
|
||||||
if not_expected:
|
|
||||||
summary = pkg_name + ": detected unexpected specs"
|
|
||||||
msg = '"{0}" was detected, but was not expected [test_id={1}]'
|
|
||||||
details = [msg.format(s, idx) for s in sorted(not_expected)]
|
|
||||||
errors.append(error_cls(summary=summary, details=details))
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
"""Function and classes needed to bootstrap Spack itself."""
|
"""Function and classes needed to bootstrap Spack itself."""
|
||||||
|
|
||||||
from .config import ensure_bootstrap_configuration, is_bootstrapping, store_path
|
from .config import ensure_bootstrap_configuration, is_bootstrapping
|
||||||
from .core import all_core_root_specs, ensure_core_dependencies, ensure_patchelf_in_path_or_raise
|
from .core import all_core_root_specs, ensure_core_dependencies, ensure_patchelf_in_path_or_raise
|
||||||
from .environment import BootstrapEnvironment, ensure_environment_dependencies
|
from .environment import BootstrapEnvironment, ensure_environment_dependencies
|
||||||
from .status import status_message
|
from .status import status_message
|
||||||
@@ -18,5 +18,4 @@
|
|||||||
"ensure_environment_dependencies",
|
"ensure_environment_dependencies",
|
||||||
"BootstrapEnvironment",
|
"BootstrapEnvironment",
|
||||||
"status_message",
|
"status_message",
|
||||||
"store_path",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ def _try_import_from_store(
|
|||||||
# We have to run as part of this python interpreter
|
# We have to run as part of this python interpreter
|
||||||
query_spec += " ^" + spec_for_current_python()
|
query_spec += " ^" + spec_for_current_python()
|
||||||
|
|
||||||
installed_specs = spack.store.STORE.db.query(query_spec, installed=True)
|
installed_specs = spack.store.db.query(query_spec, installed=True)
|
||||||
|
|
||||||
for candidate_spec in installed_specs:
|
for candidate_spec in installed_specs:
|
||||||
pkg = candidate_spec["python"].package
|
pkg = candidate_spec["python"].package
|
||||||
@@ -183,7 +183,7 @@ def _executables_in_store(
|
|||||||
executables_str = ", ".join(executables)
|
executables_str = ", ".join(executables)
|
||||||
msg = "[BOOTSTRAP EXECUTABLES {0}] Try installed specs with query '{1}'"
|
msg = "[BOOTSTRAP EXECUTABLES {0}] Try installed specs with query '{1}'"
|
||||||
tty.debug(msg.format(executables_str, query_spec))
|
tty.debug(msg.format(executables_str, query_spec))
|
||||||
installed_specs = spack.store.STORE.db.query(query_spec, installed=True)
|
installed_specs = spack.store.db.query(query_spec, installed=True)
|
||||||
if installed_specs:
|
if installed_specs:
|
||||||
for concrete_spec in installed_specs:
|
for concrete_spec in installed_specs:
|
||||||
bin_dir = concrete_spec.prefix.bin
|
bin_dir = concrete_spec.prefix.bin
|
||||||
|
|||||||
@@ -124,9 +124,9 @@ def _read_and_sanitize_configuration() -> Dict[str, Any]:
|
|||||||
def _bootstrap_config_scopes() -> Sequence["spack.config.ConfigScope"]:
|
def _bootstrap_config_scopes() -> Sequence["spack.config.ConfigScope"]:
|
||||||
tty.debug("[BOOTSTRAP CONFIG SCOPE] name=_builtin")
|
tty.debug("[BOOTSTRAP CONFIG SCOPE] name=_builtin")
|
||||||
config_scopes: MutableSequence["spack.config.ConfigScope"] = [
|
config_scopes: MutableSequence["spack.config.ConfigScope"] = [
|
||||||
spack.config.InternalConfigScope("_builtin", spack.config.CONFIG_DEFAULTS)
|
spack.config.InternalConfigScope("_builtin", spack.config.config_defaults)
|
||||||
]
|
]
|
||||||
configuration_paths = (spack.config.CONFIGURATION_DEFAULTS_PATH, ("bootstrap", _config_path()))
|
configuration_paths = (spack.config.configuration_defaults_path, ("bootstrap", _config_path()))
|
||||||
for name, path in configuration_paths:
|
for name, path in configuration_paths:
|
||||||
platform = spack.platforms.host().name
|
platform = spack.platforms.host().name
|
||||||
platform_scope = spack.config.ConfigScope(
|
platform_scope = spack.config.ConfigScope(
|
||||||
@@ -150,19 +150,18 @@ def _add_compilers_if_missing() -> None:
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _ensure_bootstrap_configuration() -> Generator:
|
def _ensure_bootstrap_configuration() -> Generator:
|
||||||
spack.store.ensure_singleton_created()
|
|
||||||
bootstrap_store_path = store_path()
|
bootstrap_store_path = store_path()
|
||||||
user_configuration = _read_and_sanitize_configuration()
|
user_configuration = _read_and_sanitize_configuration()
|
||||||
with spack.environment.no_active_environment():
|
with spack.environment.no_active_environment():
|
||||||
with spack.platforms.prevent_cray_detection(), spack.platforms.use_platform(
|
with spack.platforms.prevent_cray_detection(), spack.platforms.use_platform(
|
||||||
spack.platforms.real_host()
|
spack.platforms.real_host()
|
||||||
), spack.repo.use_repositories(spack.paths.packages_path):
|
), spack.repo.use_repositories(spack.paths.packages_path), spack.store.use_store(
|
||||||
|
bootstrap_store_path
|
||||||
|
):
|
||||||
# Default configuration scopes excluding command line
|
# Default configuration scopes excluding command line
|
||||||
# and builtin but accounting for platform specific scopes
|
# and builtin but accounting for platform specific scopes
|
||||||
config_scopes = _bootstrap_config_scopes()
|
config_scopes = _bootstrap_config_scopes()
|
||||||
with spack.config.use_configuration(*config_scopes), spack.store.use_store(
|
with spack.config.use_configuration(*config_scopes):
|
||||||
bootstrap_store_path, extra_data={"padded_length": 0}
|
|
||||||
):
|
|
||||||
# We may need to compile code from sources, so ensure we
|
# We may need to compile code from sources, so ensure we
|
||||||
# have compilers for the current platform
|
# have compilers for the current platform
|
||||||
_add_compilers_if_missing()
|
_add_compilers_if_missing()
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ def _install_and_test(
|
|||||||
with spack.config.override(self.mirror_scope):
|
with spack.config.override(self.mirror_scope):
|
||||||
# This index is currently needed to get the compiler used to build some
|
# This index is currently needed to get the compiler used to build some
|
||||||
# specs that we know by dag hash.
|
# specs that we know by dag hash.
|
||||||
spack.binary_distribution.BINARY_INDEX.regenerate_spec_cache()
|
spack.binary_distribution.binary_index.regenerate_spec_cache()
|
||||||
index = spack.binary_distribution.update_cache_and_get_specs()
|
index = spack.binary_distribution.update_cache_and_get_specs()
|
||||||
|
|
||||||
if not index:
|
if not index:
|
||||||
@@ -228,7 +228,7 @@ def _install_and_test(
|
|||||||
if not abstract_spec.intersects(candidate_spec):
|
if not abstract_spec.intersects(candidate_spec):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if python_spec is not None and not abstract_spec.intersects(f"^{python_spec}"):
|
if python_spec is not None and python_spec not in abstract_spec:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for _, pkg_hash, pkg_sha256 in item["binaries"]:
|
for _, pkg_hash, pkg_sha256 in item["binaries"]:
|
||||||
@@ -446,11 +446,16 @@ def ensure_executables_in_path_or_raise(
|
|||||||
current_bootstrapper.last_search["spec"],
|
current_bootstrapper.last_search["spec"],
|
||||||
current_bootstrapper.last_search["command"],
|
current_bootstrapper.last_search["command"],
|
||||||
)
|
)
|
||||||
cmd.add_default_envmod(
|
env_mods = spack.util.environment.EnvironmentModifications()
|
||||||
spack.user_environment.environment_modifications_for_specs(
|
for dep in concrete_spec.traverse(
|
||||||
concrete_spec, set_package_py_globals=False
|
root=True, order="post", deptype=("link", "run")
|
||||||
|
):
|
||||||
|
env_mods.extend(
|
||||||
|
spack.user_environment.environment_modifications_for_spec(
|
||||||
|
dep, set_package_py_globals=False
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
cmd.add_default_envmod(env_mods)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
assert exception_handler, (
|
assert exception_handler, (
|
||||||
@@ -471,22 +476,15 @@ def ensure_executables_in_path_or_raise(
|
|||||||
def _add_externals_if_missing() -> None:
|
def _add_externals_if_missing() -> None:
|
||||||
search_list = [
|
search_list = [
|
||||||
# clingo
|
# clingo
|
||||||
"cmake",
|
spack.repo.path.get_pkg_class("cmake"),
|
||||||
"bison",
|
spack.repo.path.get_pkg_class("bison"),
|
||||||
# GnuPG
|
# GnuPG
|
||||||
"gawk",
|
spack.repo.path.get_pkg_class("gawk"),
|
||||||
# develop deps
|
|
||||||
"git",
|
|
||||||
]
|
]
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
search_list.append("winbison")
|
search_list.append(spack.repo.path.get_pkg_class("winbison"))
|
||||||
externals = spack.detection.by_path(search_list)
|
detected_packages = spack.detection.by_executable(search_list)
|
||||||
# System git is typically deprecated, so mark as non-buildable to force it as external
|
spack.detection.update_configuration(detected_packages, scope="bootstrap")
|
||||||
non_buildable_externals = {k: externals.pop(k) for k in ("git",) if k in externals}
|
|
||||||
spack.detection.update_configuration(externals, scope="bootstrap", buildable=True)
|
|
||||||
spack.detection.update_configuration(
|
|
||||||
non_buildable_externals, scope="bootstrap", buildable=False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def clingo_root_spec() -> str:
|
def clingo_root_spec() -> str:
|
||||||
|
|||||||
@@ -15,15 +15,14 @@
|
|||||||
|
|
||||||
from llnl.util import tty
|
from llnl.util import tty
|
||||||
|
|
||||||
|
import spack.build_environment
|
||||||
import spack.environment
|
import spack.environment
|
||||||
import spack.tengine
|
import spack.tengine
|
||||||
import spack.util.cpus
|
|
||||||
import spack.util.executable
|
import spack.util.executable
|
||||||
from spack.environment import depfile
|
from spack.environment import depfile
|
||||||
|
|
||||||
from ._common import _root_spec
|
from ._common import _root_spec
|
||||||
from .config import root_path, spec_for_current_python, store_path
|
from .config import root_path, spec_for_current_python, store_path
|
||||||
from .core import _add_externals_if_missing
|
|
||||||
|
|
||||||
|
|
||||||
class BootstrapEnvironment(spack.environment.Environment):
|
class BootstrapEnvironment(spack.environment.Environment):
|
||||||
@@ -137,7 +136,7 @@ def _install_with_depfile(self) -> None:
|
|||||||
"-C",
|
"-C",
|
||||||
str(self.environment_root()),
|
str(self.environment_root()),
|
||||||
"-j",
|
"-j",
|
||||||
str(spack.util.cpus.determine_number_of_jobs(parallel=True)),
|
str(spack.build_environment.determine_number_of_jobs(parallel=True)),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -176,17 +175,16 @@ def black_root_spec() -> str:
|
|||||||
|
|
||||||
def flake8_root_spec() -> str:
|
def flake8_root_spec() -> str:
|
||||||
"""Return the root spec used to bootstrap flake8"""
|
"""Return the root spec used to bootstrap flake8"""
|
||||||
return _root_spec("py-flake8@3.8.2:")
|
return _root_spec("py-flake8")
|
||||||
|
|
||||||
|
|
||||||
def pytest_root_spec() -> str:
|
def pytest_root_spec() -> str:
|
||||||
"""Return the root spec used to bootstrap flake8"""
|
"""Return the root spec used to bootstrap flake8"""
|
||||||
return _root_spec("py-pytest@6.2.4:")
|
return _root_spec("py-pytest")
|
||||||
|
|
||||||
|
|
||||||
def ensure_environment_dependencies() -> None:
|
def ensure_environment_dependencies() -> None:
|
||||||
"""Ensure Spack dependencies from the bootstrap environment are installed and ready to use"""
|
"""Ensure Spack dependencies from the bootstrap environment are installed and ready to use"""
|
||||||
_add_externals_if_missing()
|
|
||||||
with BootstrapEnvironment() as env:
|
with BootstrapEnvironment() as env:
|
||||||
env.update_installations()
|
env.update_installations()
|
||||||
env.update_syspath_and_environ()
|
env.update_syspath_and_environ()
|
||||||
|
|||||||
@@ -40,15 +40,11 @@
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import types
|
import types
|
||||||
from collections import defaultdict
|
|
||||||
from enum import Flag, auto
|
|
||||||
from itertools import chain
|
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
from llnl.string import plural
|
|
||||||
from llnl.util.filesystem import join_path
|
from llnl.util.filesystem import join_path
|
||||||
from llnl.util.lang import dedupe, stable_partition
|
from llnl.util.lang import dedupe
|
||||||
from llnl.util.symlink import symlink
|
from llnl.util.symlink import symlink
|
||||||
from llnl.util.tty.color import cescape, colorize
|
from llnl.util.tty.color import cescape, colorize
|
||||||
from llnl.util.tty.log import MultiProcessFd
|
from llnl.util.tty.log import MultiProcessFd
|
||||||
@@ -58,37 +54,35 @@
|
|||||||
import spack.build_systems.python
|
import spack.build_systems.python
|
||||||
import spack.builder
|
import spack.builder
|
||||||
import spack.config
|
import spack.config
|
||||||
import spack.deptypes as dt
|
|
||||||
import spack.main
|
import spack.main
|
||||||
import spack.package_base
|
import spack.package_base
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.platforms
|
import spack.platforms
|
||||||
import spack.repo
|
import spack.repo
|
||||||
import spack.schema.environment
|
import spack.schema.environment
|
||||||
import spack.spec
|
|
||||||
import spack.store
|
import spack.store
|
||||||
import spack.subprocess_context
|
import spack.subprocess_context
|
||||||
import spack.user_environment
|
import spack.user_environment
|
||||||
import spack.util.path
|
import spack.util.path
|
||||||
import spack.util.pattern
|
import spack.util.pattern
|
||||||
from spack import traverse
|
|
||||||
from spack.context import Context
|
|
||||||
from spack.error import NoHeadersError, NoLibrariesError
|
from spack.error import NoHeadersError, NoLibrariesError
|
||||||
from spack.install_test import spack_install_test_log
|
from spack.install_test import spack_install_test_log
|
||||||
from spack.installer import InstallError
|
from spack.installer import InstallError
|
||||||
from spack.util.cpus import determine_number_of_jobs
|
from spack.util.cpus import cpus_available
|
||||||
from spack.util.environment import (
|
from spack.util.environment import (
|
||||||
SYSTEM_DIRS,
|
SYSTEM_DIRS,
|
||||||
EnvironmentModifications,
|
EnvironmentModifications,
|
||||||
env_flag,
|
env_flag,
|
||||||
filter_system_paths,
|
filter_system_paths,
|
||||||
get_path,
|
get_path,
|
||||||
|
inspect_path,
|
||||||
is_system_path,
|
is_system_path,
|
||||||
validate,
|
validate,
|
||||||
)
|
)
|
||||||
from spack.util.executable import Executable
|
from spack.util.executable import Executable
|
||||||
from spack.util.log_parse import make_log_context, parse_log_events
|
from spack.util.log_parse import make_log_context, parse_log_events
|
||||||
from spack.util.module_cmd import load_module, module, path_from_modules
|
from spack.util.module_cmd import load_module, module, path_from_modules
|
||||||
|
from spack.util.string import plural
|
||||||
|
|
||||||
#
|
#
|
||||||
# This can be set by the user to globally disable parallel builds.
|
# This can be set by the user to globally disable parallel builds.
|
||||||
@@ -115,6 +109,7 @@
|
|||||||
SPACK_CCACHE_BINARY = "SPACK_CCACHE_BINARY"
|
SPACK_CCACHE_BINARY = "SPACK_CCACHE_BINARY"
|
||||||
SPACK_SYSTEM_DIRS = "SPACK_SYSTEM_DIRS"
|
SPACK_SYSTEM_DIRS = "SPACK_SYSTEM_DIRS"
|
||||||
|
|
||||||
|
|
||||||
# Platform-specific library suffix.
|
# Platform-specific library suffix.
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
dso_suffix = "dylib"
|
dso_suffix = "dylib"
|
||||||
@@ -153,7 +148,7 @@ class MakeExecutable(Executable):
|
|||||||
|
|
||||||
def __init__(self, name, jobs, **kwargs):
|
def __init__(self, name, jobs, **kwargs):
|
||||||
supports_jobserver = kwargs.pop("supports_jobserver", True)
|
supports_jobserver = kwargs.pop("supports_jobserver", True)
|
||||||
super().__init__(name, **kwargs)
|
super(MakeExecutable, self).__init__(name, **kwargs)
|
||||||
self.supports_jobserver = supports_jobserver
|
self.supports_jobserver = supports_jobserver
|
||||||
self.jobs = jobs
|
self.jobs = jobs
|
||||||
|
|
||||||
@@ -180,7 +175,7 @@ def __call__(self, *args, **kwargs):
|
|||||||
if jobs_env_jobs is not None:
|
if jobs_env_jobs is not None:
|
||||||
kwargs["extra_env"] = {jobs_env: str(jobs_env_jobs)}
|
kwargs["extra_env"] = {jobs_env: str(jobs_env_jobs)}
|
||||||
|
|
||||||
return super().__call__(*args, **kwargs)
|
return super(MakeExecutable, self).__call__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def _on_cray():
|
def _on_cray():
|
||||||
@@ -411,13 +406,19 @@ def set_compiler_environment_variables(pkg, env):
|
|||||||
|
|
||||||
|
|
||||||
def set_wrapper_variables(pkg, env):
|
def set_wrapper_variables(pkg, env):
|
||||||
"""Set environment variables used by the Spack compiler wrapper (which have the prefix
|
"""Set environment variables used by the Spack compiler wrapper
|
||||||
`SPACK_`) and also add the compiler wrappers to PATH.
|
(which have the prefix `SPACK_`) and also add the compiler wrappers
|
||||||
|
to PATH.
|
||||||
|
|
||||||
This determines the injected -L/-I/-rpath options; each of these specifies a search order and
|
This determines the injected -L/-I/-rpath options; each
|
||||||
this function computes these options in a manner that is intended to match the DAG traversal
|
of these specifies a search order and this function computes these
|
||||||
order in `SetupContext`. TODO: this is not the case yet, we're using post order, SetupContext
|
options in a manner that is intended to match the DAG traversal order
|
||||||
is using topo order."""
|
in `modifications_from_dependencies`: that method uses a post-order
|
||||||
|
traversal so that `PrependPath` actions from dependencies take lower
|
||||||
|
precedence; we use a post-order traversal here to match the visitation
|
||||||
|
order of `modifications_from_dependencies` (so we are visiting the
|
||||||
|
lowest priority packages first).
|
||||||
|
"""
|
||||||
# Set environment variables if specified for
|
# Set environment variables if specified for
|
||||||
# the given compiler
|
# the given compiler
|
||||||
compiler = pkg.compiler
|
compiler = pkg.compiler
|
||||||
@@ -536,42 +537,78 @@ def update_compiler_args_for_dep(dep):
|
|||||||
env.set(SPACK_RPATH_DIRS, ":".join(rpath_dirs))
|
env.set(SPACK_RPATH_DIRS, ":".join(rpath_dirs))
|
||||||
|
|
||||||
|
|
||||||
def set_package_py_globals(pkg, context: Context = Context.BUILD):
|
def determine_number_of_jobs(
|
||||||
|
parallel=False, command_line=None, config_default=None, max_cpus=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Packages that require sequential builds need 1 job. Otherwise we use the
|
||||||
|
number of jobs set on the command line. If not set, then we use the config
|
||||||
|
defaults (which is usually set through the builtin config scope), but we
|
||||||
|
cap to the number of CPUs available to avoid oversubscription.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
parallel (bool or None): true when package supports parallel builds
|
||||||
|
command_line (int or None): command line override
|
||||||
|
config_default (int or None): config default number of jobs
|
||||||
|
max_cpus (int or None): maximum number of CPUs available. When None, this
|
||||||
|
value is automatically determined.
|
||||||
|
"""
|
||||||
|
if not parallel:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if command_line is None and "command_line" in spack.config.scopes():
|
||||||
|
command_line = spack.config.get("config:build_jobs", scope="command_line")
|
||||||
|
|
||||||
|
if command_line is not None:
|
||||||
|
return command_line
|
||||||
|
|
||||||
|
max_cpus = max_cpus or cpus_available()
|
||||||
|
|
||||||
|
# in some rare cases _builtin config may not be set, so default to max 16
|
||||||
|
config_default = config_default or spack.config.get("config:build_jobs", 16)
|
||||||
|
|
||||||
|
return min(max_cpus, config_default)
|
||||||
|
|
||||||
|
|
||||||
|
def set_module_variables_for_package(pkg):
|
||||||
"""Populate the Python module of a package with some useful global names.
|
"""Populate the Python module of a package with some useful global names.
|
||||||
This makes things easier for package writers.
|
This makes things easier for package writers.
|
||||||
"""
|
"""
|
||||||
|
# Put a marker on this module so that it won't execute the body of this
|
||||||
|
# function again, since it is not needed
|
||||||
|
marker = "_set_run_already_called"
|
||||||
|
if getattr(pkg.module, marker, False):
|
||||||
|
return
|
||||||
|
|
||||||
module = ModuleChangePropagator(pkg)
|
module = ModuleChangePropagator(pkg)
|
||||||
|
|
||||||
|
jobs = determine_number_of_jobs(parallel=pkg.parallel)
|
||||||
|
|
||||||
m = module
|
m = module
|
||||||
|
m.make_jobs = jobs
|
||||||
|
|
||||||
if context == Context.BUILD:
|
# TODO: make these build deps that can be installed if not found.
|
||||||
jobs = determine_number_of_jobs(parallel=pkg.parallel)
|
m.make = MakeExecutable("make", jobs)
|
||||||
m.make_jobs = jobs
|
m.ninja = MakeExecutable("ninja", jobs, supports_jobserver=False)
|
||||||
|
# TODO: johnwparent: add package or builder support to define these build tools
|
||||||
|
# for now there is no entrypoint for builders to define these on their
|
||||||
|
# own
|
||||||
|
if sys.platform == "win32":
|
||||||
|
m.nmake = Executable("nmake")
|
||||||
|
m.msbuild = Executable("msbuild")
|
||||||
|
# analog to configure for win32
|
||||||
|
m.cscript = Executable("cscript")
|
||||||
|
|
||||||
# TODO: make these build deps that can be installed if not found.
|
# Find the configure script in the archive path
|
||||||
m.make = MakeExecutable("make", jobs)
|
# Don't use which for this; we want to find it in the current dir.
|
||||||
m.gmake = MakeExecutable("gmake", jobs)
|
m.configure = Executable("./configure")
|
||||||
m.ninja = MakeExecutable("ninja", jobs, supports_jobserver=False)
|
|
||||||
# TODO: johnwparent: add package or builder support to define these build tools
|
|
||||||
# for now there is no entrypoint for builders to define these on their
|
|
||||||
# own
|
|
||||||
if sys.platform == "win32":
|
|
||||||
m.nmake = Executable("nmake")
|
|
||||||
m.msbuild = Executable("msbuild")
|
|
||||||
# analog to configure for win32
|
|
||||||
m.cscript = Executable("cscript")
|
|
||||||
|
|
||||||
# Find the configure script in the archive path
|
# Standard CMake arguments
|
||||||
# Don't use which for this; we want to find it in the current dir.
|
m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg)
|
||||||
m.configure = Executable("./configure")
|
m.std_meson_args = spack.build_systems.meson.MesonBuilder.std_args(pkg)
|
||||||
|
m.std_pip_args = spack.build_systems.python.PythonPipBuilder.std_args(pkg)
|
||||||
|
|
||||||
# Standard CMake arguments
|
# Put spack compiler paths in module scope.
|
||||||
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. (Some packages use it
|
|
||||||
# in setup_run_environment etc, so don't put it context == build)
|
|
||||||
link_dir = spack.paths.build_env_path
|
link_dir = spack.paths.build_env_path
|
||||||
m.spack_cc = os.path.join(link_dir, pkg.compiler.link_paths["cc"])
|
m.spack_cc = os.path.join(link_dir, pkg.compiler.link_paths["cc"])
|
||||||
m.spack_cxx = os.path.join(link_dir, pkg.compiler.link_paths["cxx"])
|
m.spack_cxx = os.path.join(link_dir, pkg.compiler.link_paths["cxx"])
|
||||||
@@ -595,6 +632,9 @@ def static_to_shared_library(static_lib, shared_lib=None, **kwargs):
|
|||||||
|
|
||||||
m.static_to_shared_library = static_to_shared_library
|
m.static_to_shared_library = static_to_shared_library
|
||||||
|
|
||||||
|
# Put a marker on this module so that it won't execute the body of this
|
||||||
|
# function again, since it is not needed
|
||||||
|
setattr(m, marker, True)
|
||||||
module.propagate_changes_to_mro()
|
module.propagate_changes_to_mro()
|
||||||
|
|
||||||
|
|
||||||
@@ -720,15 +760,12 @@ def load_external_modules(pkg):
|
|||||||
load_module(external_module)
|
load_module(external_module)
|
||||||
|
|
||||||
|
|
||||||
def setup_package(pkg, dirty, context: Context = Context.BUILD):
|
def setup_package(pkg, dirty, context="build"):
|
||||||
"""Execute all environment setup routines."""
|
"""Execute all environment setup routines."""
|
||||||
if context not in (Context.BUILD, Context.TEST):
|
if context not in ["build", "test"]:
|
||||||
raise ValueError(f"'context' must be Context.BUILD or Context.TEST - got {context}")
|
raise ValueError("'context' must be one of ['build', 'test'] - got: {0}".format(context))
|
||||||
|
|
||||||
# First populate the package.py's module with the relevant globals that could be used in any
|
set_module_variables_for_package(pkg)
|
||||||
# of the setup_* functions.
|
|
||||||
setup_context = SetupContext(pkg.spec, context=context)
|
|
||||||
setup_context.set_all_package_py_globals()
|
|
||||||
|
|
||||||
# Keep track of env changes from packages separately, since we want to
|
# Keep track of env changes from packages separately, since we want to
|
||||||
# issue warnings when packages make "suspicious" modifications.
|
# issue warnings when packages make "suspicious" modifications.
|
||||||
@@ -736,15 +773,13 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD):
|
|||||||
env_mods = EnvironmentModifications()
|
env_mods = EnvironmentModifications()
|
||||||
|
|
||||||
# setup compilers for build contexts
|
# setup compilers for build contexts
|
||||||
need_compiler = context == Context.BUILD or (
|
need_compiler = context == "build" or (context == "test" and pkg.test_requires_compiler)
|
||||||
context == Context.TEST and pkg.test_requires_compiler
|
|
||||||
)
|
|
||||||
if need_compiler:
|
if need_compiler:
|
||||||
set_compiler_environment_variables(pkg, env_mods)
|
set_compiler_environment_variables(pkg, env_mods)
|
||||||
set_wrapper_variables(pkg, env_mods)
|
set_wrapper_variables(pkg, env_mods)
|
||||||
|
|
||||||
tty.debug("setup_package: grabbing modifications from dependencies")
|
tty.debug("setup_package: grabbing modifications from dependencies")
|
||||||
env_mods.extend(setup_context.get_env_modifications())
|
env_mods.extend(modifications_from_dependencies(pkg.spec, context, custom_mods_only=False))
|
||||||
tty.debug("setup_package: collected all modifications from dependencies")
|
tty.debug("setup_package: collected all modifications from dependencies")
|
||||||
|
|
||||||
# architecture specific setup
|
# architecture specific setup
|
||||||
@@ -752,13 +787,28 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD):
|
|||||||
target = platform.target(pkg.spec.architecture.target)
|
target = platform.target(pkg.spec.architecture.target)
|
||||||
platform.setup_platform_environment(pkg, env_mods)
|
platform.setup_platform_environment(pkg, env_mods)
|
||||||
|
|
||||||
if context == Context.TEST:
|
if context == "build":
|
||||||
env_mods.prepend_path("PATH", ".")
|
tty.debug("setup_package: setup build environment for root")
|
||||||
elif context == Context.BUILD and not dirty and not env_mods.is_unset("CPATH"):
|
builder = spack.builder.create(pkg)
|
||||||
tty.debug(
|
builder.setup_build_environment(env_mods)
|
||||||
"A dependency has updated CPATH, this may lead pkg-config to assume that the package "
|
|
||||||
"is part of the system includes and omit it when invoked with '--cflags'."
|
if (not dirty) and (not env_mods.is_unset("CPATH")):
|
||||||
|
tty.debug(
|
||||||
|
"A dependency has updated CPATH, this may lead pkg-"
|
||||||
|
"config to assume that the package is part of the system"
|
||||||
|
" includes and omit it when invoked with '--cflags'."
|
||||||
|
)
|
||||||
|
elif context == "test":
|
||||||
|
tty.debug("setup_package: setup test environment for root")
|
||||||
|
env_mods.extend(
|
||||||
|
inspect_path(
|
||||||
|
pkg.spec.prefix,
|
||||||
|
spack.user_environment.prefix_inspections(pkg.spec.platform),
|
||||||
|
exclude=is_system_path,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
pkg.setup_run_environment(env_mods)
|
||||||
|
env_mods.prepend_path("PATH", ".")
|
||||||
|
|
||||||
# First apply the clean environment changes
|
# First apply the clean environment changes
|
||||||
env_base.apply_modifications()
|
env_base.apply_modifications()
|
||||||
@@ -796,250 +846,158 @@ def setup_package(pkg, dirty, context: Context = Context.BUILD):
|
|||||||
return env_base
|
return env_base
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentVisitor:
|
def _make_runnable(pkg, env):
|
||||||
def __init__(self, *roots: spack.spec.Spec, context: Context):
|
# Helper method which prepends a Package's bin/ prefix to the PATH
|
||||||
# For the roots (well, marked specs) we follow different edges
|
# environment variable
|
||||||
# than for their deps, depending on the context.
|
prefix = pkg.prefix
|
||||||
self.root_hashes = set(s.dag_hash() for s in roots)
|
|
||||||
|
|
||||||
if context == Context.BUILD:
|
for dirname in ["bin", "bin64"]:
|
||||||
# Drop direct run deps in build context
|
bin_dir = os.path.join(prefix, dirname)
|
||||||
# We don't really distinguish between install and build time test deps,
|
if os.path.isdir(bin_dir):
|
||||||
# so we include them here as build-time test deps.
|
env.prepend_path("PATH", bin_dir)
|
||||||
self.root_depflag = dt.BUILD | dt.TEST | dt.LINK
|
|
||||||
elif context == Context.TEST:
|
|
||||||
# This is more of an extended run environment
|
|
||||||
self.root_depflag = dt.TEST | dt.RUN | dt.LINK
|
|
||||||
elif context == Context.RUN:
|
|
||||||
self.root_depflag = dt.RUN | dt.LINK
|
|
||||||
|
|
||||||
def neighbors(self, item):
|
|
||||||
spec = item.edge.spec
|
|
||||||
if spec.dag_hash() in self.root_hashes:
|
|
||||||
depflag = self.root_depflag
|
|
||||||
else:
|
|
||||||
depflag = dt.LINK | dt.RUN
|
|
||||||
return traverse.sort_edges(spec.edges_to_dependencies(depflag=depflag))
|
|
||||||
|
|
||||||
|
|
||||||
class UseMode(Flag):
|
def modifications_from_dependencies(
|
||||||
#: Entrypoint spec (a spec to be built; an env root, etc)
|
spec, context, custom_mods_only=True, set_package_py_globals=True
|
||||||
ROOT = auto()
|
):
|
||||||
|
"""Returns the environment modifications that are required by
|
||||||
|
the dependencies of a spec and also applies modifications
|
||||||
|
to this spec's package at module scope, if need be.
|
||||||
|
|
||||||
#: A spec used at runtime, but no executables in PATH
|
Environment modifications include:
|
||||||
RUNTIME = auto()
|
|
||||||
|
|
||||||
#: A spec used at runtime, with executables in PATH
|
- Updating PATH so that executables can be found
|
||||||
RUNTIME_EXECUTABLE = auto()
|
- Updating CMAKE_PREFIX_PATH and PKG_CONFIG_PATH so that their respective
|
||||||
|
tools can find Spack-built dependencies
|
||||||
|
- Running custom package environment modifications
|
||||||
|
|
||||||
#: A spec that's a direct build or test dep
|
Custom package modifications can conflict with the default PATH changes
|
||||||
BUILDTIME_DIRECT = auto()
|
we make (specifically for the PATH, CMAKE_PREFIX_PATH, and PKG_CONFIG_PATH
|
||||||
|
environment variables), so this applies changes in a fixed order:
|
||||||
|
|
||||||
#: A spec that should be visible in search paths in a build env.
|
- All modifications (custom and default) from external deps first
|
||||||
BUILDTIME = auto()
|
- All modifications from non-external deps afterwards
|
||||||
|
|
||||||
#: Flag is set when the (node, mode) is finalized
|
With that order, `PrependPath` actions from non-external default
|
||||||
ADDED = auto()
|
environment modifications will take precedence over custom modifications
|
||||||
|
from external packages.
|
||||||
|
|
||||||
|
A secondary constraint is that custom and default modifications are
|
||||||
|
grouped on a per-package basis: combined with the post-order traversal this
|
||||||
|
means that default modifications of dependents can override custom
|
||||||
|
modifications of dependencies (again, this would only occur for PATH,
|
||||||
|
CMAKE_PREFIX_PATH, or PKG_CONFIG_PATH).
|
||||||
|
|
||||||
def effective_deptypes(
|
Args:
|
||||||
*specs: spack.spec.Spec, context: Context = Context.BUILD
|
spec (spack.spec.Spec): spec for which we want the modifications
|
||||||
) -> List[Tuple[spack.spec.Spec, UseMode]]:
|
context (str): either 'build' for build-time modifications or 'run'
|
||||||
"""Given a list of input specs and a context, return a list of tuples of
|
for run-time modifications
|
||||||
all specs that contribute to (environment) modifications, together with
|
custom_mods_only (bool): if True returns only custom modifications, if False
|
||||||
a flag specifying in what way they do so. The list is ordered topologically
|
returns custom and default modifications
|
||||||
from root to leaf, meaning that environment modifications should be applied
|
set_package_py_globals (bool): whether or not to set the global variables in the
|
||||||
in reverse so that dependents override dependencies, not the other way around."""
|
package.py files (this may be problematic when using buildcaches that have
|
||||||
visitor = traverse.TopoVisitor(
|
been built on a different but compatible OS)
|
||||||
EnvironmentVisitor(*specs, context=context),
|
"""
|
||||||
key=lambda x: x.dag_hash(),
|
if context not in ["build", "run", "test"]:
|
||||||
root=True,
|
raise ValueError(
|
||||||
all_edges=True,
|
"Expecting context to be one of ['build', 'run', 'test'], " "got: {0}".format(context)
|
||||||
)
|
|
||||||
traverse.traverse_depth_first_with_visitor(traverse.with_artificial_edges(specs), visitor)
|
|
||||||
|
|
||||||
# Dictionary with "no mode" as default value, so it's easy to write modes[x] |= flag.
|
|
||||||
use_modes = defaultdict(lambda: UseMode(0))
|
|
||||||
nodes_with_type = []
|
|
||||||
|
|
||||||
for edge in visitor.edges:
|
|
||||||
parent, child, depflag = edge.parent, edge.spec, edge.depflag
|
|
||||||
|
|
||||||
# Mark the starting point
|
|
||||||
if parent is None:
|
|
||||||
use_modes[child] = UseMode.ROOT
|
|
||||||
continue
|
|
||||||
|
|
||||||
parent_mode = use_modes[parent]
|
|
||||||
|
|
||||||
# Nothing to propagate.
|
|
||||||
if not parent_mode:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Dependending on the context, include particular deps from the root.
|
|
||||||
if UseMode.ROOT & parent_mode:
|
|
||||||
if context == Context.BUILD:
|
|
||||||
if (dt.BUILD | dt.TEST) & depflag:
|
|
||||||
use_modes[child] |= UseMode.BUILDTIME_DIRECT
|
|
||||||
if dt.LINK & depflag:
|
|
||||||
use_modes[child] |= UseMode.BUILDTIME
|
|
||||||
|
|
||||||
elif context == Context.TEST:
|
|
||||||
if (dt.RUN | dt.TEST) & depflag:
|
|
||||||
use_modes[child] |= UseMode.RUNTIME_EXECUTABLE
|
|
||||||
elif dt.LINK & depflag:
|
|
||||||
use_modes[child] |= UseMode.RUNTIME
|
|
||||||
|
|
||||||
elif context == Context.RUN:
|
|
||||||
if dt.RUN & depflag:
|
|
||||||
use_modes[child] |= UseMode.RUNTIME_EXECUTABLE
|
|
||||||
elif dt.LINK & depflag:
|
|
||||||
use_modes[child] |= UseMode.RUNTIME
|
|
||||||
|
|
||||||
# Propagate RUNTIME and RUNTIME_EXECUTABLE through link and run deps.
|
|
||||||
if (UseMode.RUNTIME | UseMode.RUNTIME_EXECUTABLE | UseMode.BUILDTIME_DIRECT) & parent_mode:
|
|
||||||
if dt.LINK & depflag:
|
|
||||||
use_modes[child] |= UseMode.RUNTIME
|
|
||||||
if dt.RUN & depflag:
|
|
||||||
use_modes[child] |= UseMode.RUNTIME_EXECUTABLE
|
|
||||||
|
|
||||||
# Propagate BUILDTIME through link deps.
|
|
||||||
if UseMode.BUILDTIME & parent_mode:
|
|
||||||
if dt.LINK & depflag:
|
|
||||||
use_modes[child] |= UseMode.BUILDTIME
|
|
||||||
|
|
||||||
# Finalize the spec; the invariant is that all in-edges are processed
|
|
||||||
# before out-edges, meaning that parent is done.
|
|
||||||
if not (UseMode.ADDED & parent_mode):
|
|
||||||
use_modes[parent] |= UseMode.ADDED
|
|
||||||
nodes_with_type.append((parent, parent_mode))
|
|
||||||
|
|
||||||
# Attach the leaf nodes, since we only added nodes with out-edges.
|
|
||||||
for spec, parent_mode in use_modes.items():
|
|
||||||
if parent_mode and not (UseMode.ADDED & parent_mode):
|
|
||||||
nodes_with_type.append((spec, parent_mode))
|
|
||||||
|
|
||||||
return nodes_with_type
|
|
||||||
|
|
||||||
|
|
||||||
class SetupContext:
|
|
||||||
"""This class encapsulates the logic to determine environment modifications, and is used as
|
|
||||||
well to set globals in modules of package.py."""
|
|
||||||
|
|
||||||
def __init__(self, *specs: spack.spec.Spec, context: Context) -> None:
|
|
||||||
"""Construct a ModificationsFromDag object.
|
|
||||||
Args:
|
|
||||||
specs: single root spec for build/test context, possibly more for run context
|
|
||||||
context: build, run, or test"""
|
|
||||||
if (context == Context.BUILD or context == Context.TEST) and not len(specs) == 1:
|
|
||||||
raise ValueError("Cannot setup build environment for multiple specs")
|
|
||||||
specs_with_type = effective_deptypes(*specs, context=context)
|
|
||||||
|
|
||||||
self.specs = specs
|
|
||||||
self.context = context
|
|
||||||
self.external: List[Tuple[spack.spec.Spec, UseMode]]
|
|
||||||
self.nonexternal: List[Tuple[spack.spec.Spec, UseMode]]
|
|
||||||
# Reverse so we go from leaf to root
|
|
||||||
self.nodes_in_subdag = set(id(s) for s, _ in specs_with_type)
|
|
||||||
|
|
||||||
# Split into non-external and external, maintaining topo order per group.
|
|
||||||
self.external, self.nonexternal = stable_partition(
|
|
||||||
reversed(specs_with_type), lambda t: t[0].external
|
|
||||||
)
|
)
|
||||||
self.should_be_runnable = UseMode.BUILDTIME_DIRECT | UseMode.RUNTIME_EXECUTABLE
|
|
||||||
self.should_setup_run_env = (
|
|
||||||
UseMode.BUILDTIME_DIRECT | UseMode.RUNTIME | UseMode.RUNTIME_EXECUTABLE
|
|
||||||
)
|
|
||||||
self.should_setup_dependent_build_env = UseMode.BUILDTIME | UseMode.BUILDTIME_DIRECT
|
|
||||||
self.should_setup_build_env = UseMode.ROOT if context == Context.BUILD else UseMode(0)
|
|
||||||
|
|
||||||
if context == Context.RUN or context == Context.TEST:
|
env = EnvironmentModifications()
|
||||||
self.should_be_runnable |= UseMode.ROOT
|
|
||||||
self.should_setup_run_env |= UseMode.ROOT
|
|
||||||
|
|
||||||
# Everything that calls setup_run_environment and setup_dependent_* needs globals set.
|
# Note: see computation of 'custom_mod_deps' and 'exe_deps' later in this
|
||||||
self.should_set_package_py_globals = (
|
# function; these sets form the building blocks of those collections.
|
||||||
self.should_setup_dependent_build_env | self.should_setup_run_env | UseMode.ROOT
|
build_deps = set(spec.dependencies(deptype=("build", "test")))
|
||||||
)
|
link_deps = set(spec.traverse(root=False, deptype="link"))
|
||||||
# In a build context, the root and direct build deps need build-specific globals set.
|
build_link_deps = build_deps | link_deps
|
||||||
self.needs_build_context = UseMode.ROOT | UseMode.BUILDTIME_DIRECT
|
build_and_supporting_deps = set()
|
||||||
|
for build_dep in build_deps:
|
||||||
|
build_and_supporting_deps.update(build_dep.traverse(deptype="run"))
|
||||||
|
run_and_supporting_deps = set(spec.traverse(root=False, deptype=("run", "link")))
|
||||||
|
test_and_supporting_deps = set()
|
||||||
|
for test_dep in set(spec.dependencies(deptype="test")):
|
||||||
|
test_and_supporting_deps.update(test_dep.traverse(deptype="run"))
|
||||||
|
|
||||||
def set_all_package_py_globals(self):
|
# All dependencies that might have environment modifications to apply
|
||||||
"""Set the globals in modules of package.py files."""
|
custom_mod_deps = set()
|
||||||
for dspec, flag in chain(self.external, self.nonexternal):
|
if context == "build":
|
||||||
pkg = dspec.package
|
custom_mod_deps.update(build_and_supporting_deps)
|
||||||
|
# Tests may be performed after build
|
||||||
|
custom_mod_deps.update(test_and_supporting_deps)
|
||||||
|
else:
|
||||||
|
# test/run context
|
||||||
|
custom_mod_deps.update(run_and_supporting_deps)
|
||||||
|
if context == "test":
|
||||||
|
custom_mod_deps.update(test_and_supporting_deps)
|
||||||
|
custom_mod_deps.update(link_deps)
|
||||||
|
|
||||||
if self.should_set_package_py_globals & flag:
|
# Determine 'exe_deps': the set of packages with binaries we want to use
|
||||||
if self.context == Context.BUILD and self.needs_build_context & flag:
|
if context == "build":
|
||||||
set_package_py_globals(pkg, context=Context.BUILD)
|
exe_deps = build_and_supporting_deps | test_and_supporting_deps
|
||||||
else:
|
elif context == "run":
|
||||||
# This includes runtime dependencies, also runtime deps of direct build deps.
|
exe_deps = set(spec.traverse(deptype="run"))
|
||||||
set_package_py_globals(pkg, context=Context.RUN)
|
elif context == "test":
|
||||||
|
exe_deps = test_and_supporting_deps
|
||||||
|
|
||||||
for spec in dspec.dependents():
|
def default_modifications_for_dep(dep):
|
||||||
# Note: some specs have dependents that are unreachable from the root, so avoid
|
if dep in build_link_deps and not is_system_path(dep.prefix) and context == "build":
|
||||||
# setting globals for those.
|
prefix = dep.prefix
|
||||||
if id(spec) not in self.nodes_in_subdag:
|
|
||||||
continue
|
|
||||||
dependent_module = ModuleChangePropagator(spec.package)
|
|
||||||
pkg.setup_dependent_package(dependent_module, spec)
|
|
||||||
dependent_module.propagate_changes_to_mro()
|
|
||||||
|
|
||||||
def get_env_modifications(self) -> EnvironmentModifications:
|
env.prepend_path("CMAKE_PREFIX_PATH", prefix)
|
||||||
"""Returns the environment variable modifications for the given input specs and context.
|
|
||||||
Environment modifications include:
|
|
||||||
- Updating PATH for packages that are required at runtime
|
|
||||||
- Updating CMAKE_PREFIX_PATH and PKG_CONFIG_PATH so that their respective
|
|
||||||
tools can find Spack-built dependencies (when context=build)
|
|
||||||
- Running custom package environment modifications: setup_run_environment,
|
|
||||||
setup_dependent_run_environment, setup_build_environment,
|
|
||||||
setup_dependent_build_environment.
|
|
||||||
|
|
||||||
The (partial) order imposed on the specs is externals first, then topological
|
for directory in ("lib", "lib64", "share"):
|
||||||
from leaf to root. That way externals cannot contribute search paths that would shadow
|
pcdir = os.path.join(prefix, directory, "pkgconfig")
|
||||||
Spack's prefixes, and dependents override variables set by dependencies."""
|
if os.path.isdir(pcdir):
|
||||||
env = EnvironmentModifications()
|
env.prepend_path("PKG_CONFIG_PATH", pcdir)
|
||||||
for dspec, flag in chain(self.external, self.nonexternal):
|
|
||||||
tty.debug(f"Adding env modifications for {dspec.name}")
|
|
||||||
pkg = dspec.package
|
|
||||||
|
|
||||||
if self.should_setup_dependent_build_env & flag:
|
if dep in exe_deps and not is_system_path(dep.prefix):
|
||||||
self._make_buildtime_detectable(dspec, env)
|
_make_runnable(dep, env)
|
||||||
|
|
||||||
for root in self.specs: # there is only one root in build context
|
def add_modifications_for_dep(dep):
|
||||||
spack.builder.create(pkg).setup_dependent_build_environment(env, root)
|
tty.debug("Adding env modifications for {0}".format(dep.name))
|
||||||
|
# Some callers of this function only want the custom modifications.
|
||||||
|
# For callers that want both custom and default modifications, we want
|
||||||
|
# to perform the default modifications here (this groups custom
|
||||||
|
# and default modifications together on a per-package basis).
|
||||||
|
if not custom_mods_only:
|
||||||
|
default_modifications_for_dep(dep)
|
||||||
|
|
||||||
if self.should_setup_build_env & flag:
|
# Perform custom modifications here (PrependPath actions performed in
|
||||||
spack.builder.create(pkg).setup_build_environment(env)
|
# the custom method override the default environment modifications
|
||||||
|
# we do to help the build, namely for PATH, CMAKE_PREFIX_PATH, and
|
||||||
|
# PKG_CONFIG_PATH)
|
||||||
|
if dep in custom_mod_deps:
|
||||||
|
dpkg = dep.package
|
||||||
|
if set_package_py_globals:
|
||||||
|
set_module_variables_for_package(dpkg)
|
||||||
|
|
||||||
if self.should_be_runnable & flag:
|
current_module = ModuleChangePropagator(spec.package)
|
||||||
self._make_runnable(dspec, env)
|
dpkg.setup_dependent_package(current_module, spec)
|
||||||
|
current_module.propagate_changes_to_mro()
|
||||||
|
|
||||||
if self.should_setup_run_env & flag:
|
if context == "build":
|
||||||
for spec in dspec.dependents(deptype=dt.LINK | dt.RUN):
|
builder = spack.builder.create(dpkg)
|
||||||
if id(spec) in self.nodes_in_subdag:
|
builder.setup_dependent_build_environment(env, spec)
|
||||||
pkg.setup_dependent_run_environment(env, spec)
|
else:
|
||||||
pkg.setup_run_environment(env)
|
dpkg.setup_dependent_run_environment(env, spec)
|
||||||
return env
|
tty.debug("Added env modifications for {0}".format(dep.name))
|
||||||
|
|
||||||
def _make_buildtime_detectable(self, dep: spack.spec.Spec, env: EnvironmentModifications):
|
# Note that we want to perform environment modifications in a fixed order.
|
||||||
if is_system_path(dep.prefix):
|
# The Spec.traverse method provides this: i.e. in addition to
|
||||||
return
|
# the post-order semantics, it also guarantees a fixed traversal order
|
||||||
|
# among dependencies which are not constrained by post-order semantics.
|
||||||
|
for dspec in spec.traverse(root=False, order="post"):
|
||||||
|
if dspec.external:
|
||||||
|
add_modifications_for_dep(dspec)
|
||||||
|
|
||||||
env.prepend_path("CMAKE_PREFIX_PATH", dep.prefix)
|
for dspec in spec.traverse(root=False, order="post"):
|
||||||
for d in ("lib", "lib64", "share"):
|
# Default env modifications for non-external packages can override
|
||||||
pcdir = os.path.join(dep.prefix, d, "pkgconfig")
|
# custom modifications of external packages (this can only occur
|
||||||
if os.path.isdir(pcdir):
|
# for modifications to PATH, CMAKE_PREFIX_PATH, and PKG_CONFIG_PATH)
|
||||||
env.prepend_path("PKG_CONFIG_PATH", pcdir)
|
if not dspec.external:
|
||||||
|
add_modifications_for_dep(dspec)
|
||||||
|
|
||||||
def _make_runnable(self, dep: spack.spec.Spec, env: EnvironmentModifications):
|
return env
|
||||||
if is_system_path(dep.prefix):
|
|
||||||
return
|
|
||||||
|
|
||||||
for d in ("bin", "bin64"):
|
|
||||||
bin_dir = os.path.join(dep.prefix, d)
|
|
||||||
if os.path.isdir(bin_dir):
|
|
||||||
env.prepend_path("PATH", bin_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def get_cmake_prefix_path(pkg):
|
def get_cmake_prefix_path(pkg):
|
||||||
@@ -1069,9 +1027,9 @@ def get_cmake_prefix_path(pkg):
|
|||||||
|
|
||||||
|
|
||||||
def _setup_pkg_and_run(
|
def _setup_pkg_and_run(
|
||||||
serialized_pkg, function, kwargs, write_pipe, input_multiprocess_fd, jsfd1, jsfd2
|
serialized_pkg, function, kwargs, child_pipe, input_multiprocess_fd, jsfd1, jsfd2
|
||||||
):
|
):
|
||||||
context: str = kwargs.get("context", "build")
|
context = kwargs.get("context", "build")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# We are in the child process. Python sets sys.stdin to
|
# We are in the child process. Python sets sys.stdin to
|
||||||
@@ -1087,15 +1045,15 @@ def _setup_pkg_and_run(
|
|||||||
if not kwargs.get("fake", False):
|
if not kwargs.get("fake", False):
|
||||||
kwargs["unmodified_env"] = os.environ.copy()
|
kwargs["unmodified_env"] = os.environ.copy()
|
||||||
kwargs["env_modifications"] = setup_package(
|
kwargs["env_modifications"] = setup_package(
|
||||||
pkg, dirty=kwargs.get("dirty", False), context=Context.from_string(context)
|
pkg, dirty=kwargs.get("dirty", False), context=context
|
||||||
)
|
)
|
||||||
return_value = function(pkg, kwargs)
|
return_value = function(pkg, kwargs)
|
||||||
write_pipe.send(return_value)
|
child_pipe.send(return_value)
|
||||||
|
|
||||||
except StopPhase as e:
|
except StopPhase as e:
|
||||||
# Do not create a full ChildError from this, it's not an error
|
# Do not create a full ChildError from this, it's not an error
|
||||||
# it's a control statement.
|
# it's a control statement.
|
||||||
write_pipe.send(e)
|
child_pipe.send(e)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
# catch ANYTHING that goes wrong in the child process
|
# catch ANYTHING that goes wrong in the child process
|
||||||
exc_type, exc, tb = sys.exc_info()
|
exc_type, exc, tb = sys.exc_info()
|
||||||
@@ -1144,10 +1102,10 @@ def _setup_pkg_and_run(
|
|||||||
context,
|
context,
|
||||||
package_context,
|
package_context,
|
||||||
)
|
)
|
||||||
write_pipe.send(ce)
|
child_pipe.send(ce)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
write_pipe.close()
|
child_pipe.close()
|
||||||
if input_multiprocess_fd is not None:
|
if input_multiprocess_fd is not None:
|
||||||
input_multiprocess_fd.close()
|
input_multiprocess_fd.close()
|
||||||
|
|
||||||
@@ -1191,7 +1149,7 @@ def child_fun():
|
|||||||
For more information on `multiprocessing` child process creation
|
For more information on `multiprocessing` child process creation
|
||||||
mechanisms, see https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
|
mechanisms, see https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
|
||||||
"""
|
"""
|
||||||
read_pipe, write_pipe = multiprocessing.Pipe(duplex=False)
|
parent_pipe, child_pipe = multiprocessing.Pipe()
|
||||||
input_multiprocess_fd = None
|
input_multiprocess_fd = None
|
||||||
jobserver_fd1 = None
|
jobserver_fd1 = None
|
||||||
jobserver_fd2 = None
|
jobserver_fd2 = None
|
||||||
@@ -1216,7 +1174,7 @@ def child_fun():
|
|||||||
serialized_pkg,
|
serialized_pkg,
|
||||||
function,
|
function,
|
||||||
kwargs,
|
kwargs,
|
||||||
write_pipe,
|
child_pipe,
|
||||||
input_multiprocess_fd,
|
input_multiprocess_fd,
|
||||||
jobserver_fd1,
|
jobserver_fd1,
|
||||||
jobserver_fd2,
|
jobserver_fd2,
|
||||||
@@ -1225,12 +1183,6 @@ def child_fun():
|
|||||||
|
|
||||||
p.start()
|
p.start()
|
||||||
|
|
||||||
# We close the writable end of the pipe now to be sure that p is the
|
|
||||||
# only process which owns a handle for it. This ensures that when p
|
|
||||||
# closes its handle for the writable end, read_pipe.recv() will
|
|
||||||
# promptly report the readable end as being ready.
|
|
||||||
write_pipe.close()
|
|
||||||
|
|
||||||
except InstallError as e:
|
except InstallError as e:
|
||||||
e.pkg = pkg
|
e.pkg = pkg
|
||||||
raise
|
raise
|
||||||
@@ -1240,16 +1192,7 @@ def child_fun():
|
|||||||
if input_multiprocess_fd is not None:
|
if input_multiprocess_fd is not None:
|
||||||
input_multiprocess_fd.close()
|
input_multiprocess_fd.close()
|
||||||
|
|
||||||
def exitcode_msg(p):
|
child_result = parent_pipe.recv()
|
||||||
typ = "exit" if p.exitcode >= 0 else "signal"
|
|
||||||
return f"{typ} {abs(p.exitcode)}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
child_result = read_pipe.recv()
|
|
||||||
except EOFError:
|
|
||||||
p.join()
|
|
||||||
raise InstallError(f"The process has stopped unexpectedly ({exitcode_msg(p)})")
|
|
||||||
|
|
||||||
p.join()
|
p.join()
|
||||||
|
|
||||||
# If returns a StopPhase, raise it
|
# If returns a StopPhase, raise it
|
||||||
@@ -1269,10 +1212,6 @@ def exitcode_msg(p):
|
|||||||
child_result.print_context()
|
child_result.print_context()
|
||||||
raise child_result
|
raise child_result
|
||||||
|
|
||||||
# Fallback. Usually caught beforehand in EOFError above.
|
|
||||||
if p.exitcode != 0:
|
|
||||||
raise InstallError(f"The process failed unexpectedly ({exitcode_msg(p)})")
|
|
||||||
|
|
||||||
return child_result
|
return child_result
|
||||||
|
|
||||||
|
|
||||||
@@ -1317,8 +1256,9 @@ def make_stack(tb, stack=None):
|
|||||||
func = getattr(obj, tb.tb_frame.f_code.co_name, "")
|
func = getattr(obj, tb.tb_frame.f_code.co_name, "")
|
||||||
if func:
|
if func:
|
||||||
typename, *_ = func.__qualname__.partition(".")
|
typename, *_ = func.__qualname__.partition(".")
|
||||||
if isinstance(obj, CONTEXT_BASES) and typename not in basenames:
|
|
||||||
break
|
if isinstance(obj, CONTEXT_BASES) and typename not in basenames:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1392,7 +1332,7 @@ class ChildError(InstallError):
|
|||||||
build_errors = [("spack.util.executable", "ProcessError")]
|
build_errors = [("spack.util.executable", "ProcessError")]
|
||||||
|
|
||||||
def __init__(self, msg, module, classname, traceback_string, log_name, log_type, context):
|
def __init__(self, msg, module, classname, traceback_string, log_name, log_type, context):
|
||||||
super().__init__(msg)
|
super(ChildError, self).__init__(msg)
|
||||||
self.module = module
|
self.module = module
|
||||||
self.name = classname
|
self.name = classname
|
||||||
self.traceback = traceback_string
|
self.traceback = traceback_string
|
||||||
@@ -1433,7 +1373,7 @@ def long_message(self):
|
|||||||
test_log = join_path(os.path.dirname(self.log_name), spack_install_test_log)
|
test_log = join_path(os.path.dirname(self.log_name), spack_install_test_log)
|
||||||
if os.path.isfile(test_log):
|
if os.path.isfile(test_log):
|
||||||
out.write("\nSee test log for details:\n")
|
out.write("\nSee test log for details:\n")
|
||||||
out.write(" {0}\n".format(test_log))
|
out.write(" {0}n".format(test_log))
|
||||||
|
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ def check_paths(path_list, filetype, predicate):
|
|||||||
check_paths(pkg.sanity_check_is_file, "file", os.path.isfile)
|
check_paths(pkg.sanity_check_is_file, "file", os.path.isfile)
|
||||||
check_paths(pkg.sanity_check_is_dir, "directory", os.path.isdir)
|
check_paths(pkg.sanity_check_is_dir, "directory", os.path.isdir)
|
||||||
|
|
||||||
ignore_file = llnl.util.lang.match_predicate(spack.store.STORE.layout.hidden_file_regexes)
|
ignore_file = llnl.util.lang.match_predicate(spack.store.layout.hidden_file_regexes)
|
||||||
if all(map(ignore_file, os.listdir(pkg.prefix))):
|
if all(map(ignore_file, os.listdir(pkg.prefix))):
|
||||||
msg = "Install failed for {0}. Nothing was installed!"
|
msg = "Install failed for {0}. Nothing was installed!"
|
||||||
raise spack.installer.InstallError(msg.format(pkg.name))
|
raise spack.installer.InstallError(msg.format(pkg.name))
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ class AutotoolsPackage(spack.package_base.PackageBase):
|
|||||||
depends_on("gnuconfig", type="build", when="target=ppc64le:")
|
depends_on("gnuconfig", type="build", when="target=ppc64le:")
|
||||||
depends_on("gnuconfig", type="build", when="target=aarch64:")
|
depends_on("gnuconfig", type="build", when="target=aarch64:")
|
||||||
depends_on("gnuconfig", type="build", when="target=riscv64:")
|
depends_on("gnuconfig", type="build", when="target=riscv64:")
|
||||||
depends_on("gmake", type="build")
|
|
||||||
conflicts("platform=windows")
|
conflicts("platform=windows")
|
||||||
|
|
||||||
def flags_to_build_system_args(self, flags):
|
def flags_to_build_system_args(self, flags):
|
||||||
@@ -56,8 +55,7 @@ def flags_to_build_system_args(self, flags):
|
|||||||
setattr(self, "configure_flag_args", [])
|
setattr(self, "configure_flag_args", [])
|
||||||
for flag, values in flags.items():
|
for flag, values in flags.items():
|
||||||
if values:
|
if values:
|
||||||
var_name = "LIBS" if flag == "ldlibs" else flag.upper()
|
values_str = "{0}={1}".format(flag.upper(), " ".join(values))
|
||||||
values_str = "{0}={1}".format(var_name, " ".join(values))
|
|
||||||
self.configure_flag_args.append(values_str)
|
self.configure_flag_args.append(values_str)
|
||||||
# Spack's fflags are meant for both F77 and FC, therefore we
|
# Spack's fflags are meant for both F77 and FC, therefore we
|
||||||
# additionaly set FCFLAGS if required.
|
# additionaly set FCFLAGS if required.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
import collections.abc
|
|
||||||
import os
|
import os
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
@@ -14,24 +13,21 @@
|
|||||||
from .cmake import CMakeBuilder, CMakePackage
|
from .cmake import CMakeBuilder, CMakePackage
|
||||||
|
|
||||||
|
|
||||||
def cmake_cache_path(name, value, comment="", force=False):
|
def cmake_cache_path(name, value, comment=""):
|
||||||
"""Generate a string for a cmake cache variable"""
|
"""Generate a string for a cmake cache variable"""
|
||||||
force_str = " FORCE" if force else ""
|
return 'set({0} "{1}" CACHE PATH "{2}")\n'.format(name, value, comment)
|
||||||
return 'set({0} "{1}" CACHE PATH "{2}"{3})\n'.format(name, value, comment, force_str)
|
|
||||||
|
|
||||||
|
|
||||||
def cmake_cache_string(name, value, comment="", force=False):
|
def cmake_cache_string(name, value, comment=""):
|
||||||
"""Generate a string for a cmake cache variable"""
|
"""Generate a string for a cmake cache variable"""
|
||||||
force_str = " FORCE" if force else ""
|
return 'set({0} "{1}" CACHE STRING "{2}")\n'.format(name, value, comment)
|
||||||
return 'set({0} "{1}" CACHE STRING "{2}"{3})\n'.format(name, value, comment, force_str)
|
|
||||||
|
|
||||||
|
|
||||||
def cmake_cache_option(name, boolean_value, comment="", force=False):
|
def cmake_cache_option(name, boolean_value, comment=""):
|
||||||
"""Generate a string for a cmake configuration option"""
|
"""Generate a string for a cmake configuration option"""
|
||||||
|
|
||||||
value = "ON" if boolean_value else "OFF"
|
value = "ON" if boolean_value else "OFF"
|
||||||
force_str = " FORCE" if force else ""
|
return 'set({0} {1} CACHE BOOL "{2}")\n'.format(name, value, comment)
|
||||||
return 'set({0} {1} CACHE BOOL "{2}"{3})\n'.format(name, value, comment, force_str)
|
|
||||||
|
|
||||||
|
|
||||||
class CachedCMakeBuilder(CMakeBuilder):
|
class CachedCMakeBuilder(CMakeBuilder):
|
||||||
@@ -67,34 +63,6 @@ def cache_name(self):
|
|||||||
def cache_path(self):
|
def cache_path(self):
|
||||||
return os.path.join(self.pkg.stage.source_path, self.cache_name)
|
return os.path.join(self.pkg.stage.source_path, self.cache_name)
|
||||||
|
|
||||||
# Implement a version of the define_from_variant for Cached packages
|
|
||||||
def define_cmake_cache_from_variant(self, cmake_var, variant=None, comment=""):
|
|
||||||
"""Return a Cached CMake field from the given variant's value.
|
|
||||||
See define_from_variant in lib/spack/spack/build_systems/cmake.py package
|
|
||||||
"""
|
|
||||||
|
|
||||||
if variant is None:
|
|
||||||
variant = cmake_var.lower()
|
|
||||||
|
|
||||||
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.pkg.spec.variants:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
value = self.pkg.spec.variants[variant].value
|
|
||||||
field = None
|
|
||||||
if isinstance(value, bool):
|
|
||||||
field = cmake_cache_option(cmake_var, value, comment)
|
|
||||||
else:
|
|
||||||
if isinstance(value, collections.abc.Sequence) and not isinstance(value, str):
|
|
||||||
value = ";".join(str(v) for v in value)
|
|
||||||
else:
|
|
||||||
value = str(value)
|
|
||||||
field = cmake_cache_string(cmake_var, value, comment)
|
|
||||||
|
|
||||||
return field
|
|
||||||
|
|
||||||
def initconfig_compiler_entries(self):
|
def initconfig_compiler_entries(self):
|
||||||
# This will tell cmake to use the Spack compiler wrappers when run
|
# This will tell cmake to use the Spack compiler wrappers when run
|
||||||
# through Spack, but use the underlying compiler when run outside of
|
# through Spack, but use the underlying compiler when run outside of
|
||||||
@@ -227,58 +195,26 @@ def initconfig_hardware_entries(self):
|
|||||||
"#------------------{0}\n".format("-" * 60),
|
"#------------------{0}\n".format("-" * 60),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Provide standard CMake arguments for dependent CachedCMakePackages
|
|
||||||
if spec.satisfies("^cuda"):
|
if spec.satisfies("^cuda"):
|
||||||
entries.append("#------------------{0}".format("-" * 30))
|
entries.append("#------------------{0}".format("-" * 30))
|
||||||
entries.append("# Cuda")
|
entries.append("# Cuda")
|
||||||
entries.append("#------------------{0}\n".format("-" * 30))
|
entries.append("#------------------{0}\n".format("-" * 30))
|
||||||
|
|
||||||
cudatoolkitdir = spec["cuda"].prefix
|
cudatoolkitdir = spec["cuda"].prefix
|
||||||
entries.append(cmake_cache_path("CUDAToolkit_ROOT", cudatoolkitdir))
|
|
||||||
entries.append(cmake_cache_path("CMAKE_CUDA_COMPILER", "${CUDAToolkit_ROOT}/bin/nvcc"))
|
|
||||||
entries.append(cmake_cache_path("CMAKE_CUDA_HOST_COMPILER", "${CMAKE_CXX_COMPILER}"))
|
|
||||||
# Include the deprecated CUDA_TOOLKIT_ROOT_DIR for supporting BLT packages
|
|
||||||
entries.append(cmake_cache_path("CUDA_TOOLKIT_ROOT_DIR", cudatoolkitdir))
|
entries.append(cmake_cache_path("CUDA_TOOLKIT_ROOT_DIR", cudatoolkitdir))
|
||||||
|
cudacompiler = "${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc"
|
||||||
archs = spec.variants["cuda_arch"].value
|
entries.append(cmake_cache_path("CMAKE_CUDA_COMPILER", cudacompiler))
|
||||||
if archs[0] != "none":
|
entries.append(cmake_cache_path("CMAKE_CUDA_HOST_COMPILER", "${CMAKE_CXX_COMPILER}"))
|
||||||
arch_str = ";".join(archs)
|
|
||||||
entries.append(
|
|
||||||
cmake_cache_string("CMAKE_CUDA_ARCHITECTURES", "{0}".format(arch_str))
|
|
||||||
)
|
|
||||||
|
|
||||||
if "+rocm" in spec:
|
|
||||||
entries.append("#------------------{0}".format("-" * 30))
|
|
||||||
entries.append("# ROCm")
|
|
||||||
entries.append("#------------------{0}\n".format("-" * 30))
|
|
||||||
|
|
||||||
# Explicitly setting HIP_ROOT_DIR may be a patch that is no longer necessary
|
|
||||||
entries.append(cmake_cache_path("HIP_ROOT_DIR", "{0}".format(spec["hip"].prefix)))
|
|
||||||
entries.append(
|
|
||||||
cmake_cache_path("HIP_CXX_COMPILER", "{0}".format(self.spec["hip"].hipcc))
|
|
||||||
)
|
|
||||||
archs = self.spec.variants["amdgpu_target"].value
|
|
||||||
if archs[0] != "none":
|
|
||||||
arch_str = ";".join(archs)
|
|
||||||
entries.append(
|
|
||||||
cmake_cache_string("CMAKE_HIP_ARCHITECTURES", "{0}".format(arch_str))
|
|
||||||
)
|
|
||||||
entries.append(cmake_cache_string("AMDGPU_TARGETS", "{0}".format(arch_str)))
|
|
||||||
entries.append(cmake_cache_string("GPU_TARGETS", "{0}".format(arch_str)))
|
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
def std_initconfig_entries(self):
|
def std_initconfig_entries(self):
|
||||||
cmake_prefix_path_env = os.environ["CMAKE_PREFIX_PATH"]
|
|
||||||
cmake_prefix_path = cmake_prefix_path_env.replace(os.pathsep, ";")
|
|
||||||
return [
|
return [
|
||||||
"#------------------{0}".format("-" * 60),
|
"#------------------{0}".format("-" * 60),
|
||||||
"# !!!! This is a generated file, edit at own risk !!!!",
|
"# !!!! This is a generated file, edit at own risk !!!!",
|
||||||
"#------------------{0}".format("-" * 60),
|
"#------------------{0}".format("-" * 60),
|
||||||
"# CMake executable path: {0}".format(self.pkg.spec["cmake"].command.path),
|
"# CMake executable path: {0}".format(self.pkg.spec["cmake"].command.path),
|
||||||
"#------------------{0}\n".format("-" * 60),
|
"#------------------{0}\n".format("-" * 60),
|
||||||
cmake_cache_path("CMAKE_PREFIX_PATH", cmake_prefix_path),
|
|
||||||
self.define_cmake_cache_from_variant("CMAKE_BUILD_TYPE", "build_type"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def initconfig_package_entries(self):
|
def initconfig_package_entries(self):
|
||||||
@@ -301,7 +237,7 @@ def initconfig(self, pkg, spec, prefix):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def std_cmake_args(self):
|
def std_cmake_args(self):
|
||||||
args = super().std_cmake_args
|
args = super(CachedCMakeBuilder, self).std_cmake_args
|
||||||
args.extend(["-C", self.cache_path])
|
args.extend(["-C", self.cache_path])
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
import collections.abc
|
import collections.abc
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import pathlib
|
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -16,6 +15,7 @@
|
|||||||
import spack.build_environment
|
import spack.build_environment
|
||||||
import spack.builder
|
import spack.builder
|
||||||
import spack.package_base
|
import spack.package_base
|
||||||
|
import spack.util.path
|
||||||
from spack.directives import build_system, conflicts, depends_on, variant
|
from spack.directives import build_system, conflicts, depends_on, variant
|
||||||
from spack.multimethod import when
|
from spack.multimethod import when
|
||||||
|
|
||||||
@@ -142,10 +142,10 @@ def flags_to_build_system_args(self, flags):
|
|||||||
# We specify for each of them.
|
# We specify for each of them.
|
||||||
if flags["ldflags"]:
|
if flags["ldflags"]:
|
||||||
ldflags = " ".join(flags["ldflags"])
|
ldflags = " ".join(flags["ldflags"])
|
||||||
|
ld_string = "-DCMAKE_{0}_LINKER_FLAGS={1}"
|
||||||
# cmake has separate linker arguments for types of builds.
|
# cmake has separate linker arguments for types of builds.
|
||||||
self.cmake_flag_args.append(f"-DCMAKE_EXE_LINKER_FLAGS={ldflags}")
|
for type in ["EXE", "MODULE", "SHARED", "STATIC"]:
|
||||||
self.cmake_flag_args.append(f"-DCMAKE_MODULE_LINKER_FLAGS={ldflags}")
|
self.cmake_flag_args.append(ld_string.format(type, ldflags))
|
||||||
self.cmake_flag_args.append(f"-DCMAKE_SHARED_LINKER_FLAGS={ldflags}")
|
|
||||||
|
|
||||||
# CMake has libs options separated by language. Apply ours to each.
|
# CMake has libs options separated by language. Apply ours to each.
|
||||||
if flags["ldlibs"]:
|
if flags["ldlibs"]:
|
||||||
@@ -248,8 +248,7 @@ def std_cmake_args(self):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def std_args(pkg, generator=None):
|
def std_args(pkg, generator=None):
|
||||||
"""Computes the standard cmake arguments for a generic package"""
|
"""Computes the standard cmake arguments for a generic package"""
|
||||||
default_generator = "Ninja" if sys.platform == "win32" else "Unix Makefiles"
|
generator = generator or "Unix Makefiles"
|
||||||
generator = generator or default_generator
|
|
||||||
valid_primary_generators = ["Unix Makefiles", "Ninja"]
|
valid_primary_generators = ["Unix Makefiles", "Ninja"]
|
||||||
primary_generator = _extract_primary_generator(generator)
|
primary_generator = _extract_primary_generator(generator)
|
||||||
if primary_generator not in valid_primary_generators:
|
if primary_generator not in valid_primary_generators:
|
||||||
@@ -272,8 +271,9 @@ def std_args(pkg, generator=None):
|
|||||||
args = [
|
args = [
|
||||||
"-G",
|
"-G",
|
||||||
generator,
|
generator,
|
||||||
define("CMAKE_INSTALL_PREFIX", pathlib.Path(pkg.prefix).as_posix()),
|
define("CMAKE_INSTALL_PREFIX", pkg.prefix),
|
||||||
define("CMAKE_BUILD_TYPE", build_type),
|
define("CMAKE_BUILD_TYPE", build_type),
|
||||||
|
define("BUILD_TESTING", pkg.run_tests),
|
||||||
]
|
]
|
||||||
|
|
||||||
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
||||||
@@ -296,46 +296,8 @@ def std_args(pkg, generator=None):
|
|||||||
define("CMAKE_PREFIX_PATH", spack.build_environment.get_cmake_prefix_path(pkg)),
|
define("CMAKE_PREFIX_PATH", spack.build_environment.get_cmake_prefix_path(pkg)),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def define_cuda_architectures(pkg):
|
|
||||||
"""Returns the str ``-DCMAKE_CUDA_ARCHITECTURES:STRING=(expanded cuda_arch)``.
|
|
||||||
|
|
||||||
``cuda_arch`` is variant composed of a list of target CUDA architectures and
|
|
||||||
it is declared in the cuda package.
|
|
||||||
|
|
||||||
This method is no-op for cmake<3.18 and when ``cuda_arch`` variant is not set.
|
|
||||||
|
|
||||||
"""
|
|
||||||
cmake_flag = str()
|
|
||||||
if "cuda_arch" in pkg.spec.variants and pkg.spec.satisfies("^cmake@3.18:"):
|
|
||||||
cmake_flag = CMakeBuilder.define(
|
|
||||||
"CMAKE_CUDA_ARCHITECTURES", pkg.spec.variants["cuda_arch"].value
|
|
||||||
)
|
|
||||||
|
|
||||||
return cmake_flag
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def define_hip_architectures(pkg):
|
|
||||||
"""Returns the str ``-DCMAKE_HIP_ARCHITECTURES:STRING=(expanded amdgpu_target)``.
|
|
||||||
|
|
||||||
``amdgpu_target`` is variant composed of a list of the target HIP
|
|
||||||
architectures and it is declared in the rocm package.
|
|
||||||
|
|
||||||
This method is no-op for cmake<3.18 and when ``amdgpu_target`` variant is
|
|
||||||
not set.
|
|
||||||
|
|
||||||
"""
|
|
||||||
cmake_flag = str()
|
|
||||||
if "amdgpu_target" in pkg.spec.variants and pkg.spec.satisfies("^cmake@3.21:"):
|
|
||||||
cmake_flag = CMakeBuilder.define(
|
|
||||||
"CMAKE_HIP_ARCHITECTURES", pkg.spec.variants["amdgpu_target"].value
|
|
||||||
)
|
|
||||||
|
|
||||||
return cmake_flag
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def define(cmake_var, value):
|
def define(cmake_var, value):
|
||||||
"""Return a CMake command line argument that defines a variable.
|
"""Return a CMake command line argument that defines a variable.
|
||||||
@@ -450,6 +412,7 @@ def cmake_args(self):
|
|||||||
|
|
||||||
* CMAKE_INSTALL_PREFIX
|
* CMAKE_INSTALL_PREFIX
|
||||||
* CMAKE_BUILD_TYPE
|
* CMAKE_BUILD_TYPE
|
||||||
|
* BUILD_TESTING
|
||||||
|
|
||||||
which will be set automatically.
|
which will be set automatically.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -102,10 +102,11 @@ def cuda_flags(arch_list):
|
|||||||
|
|
||||||
depends_on("cuda@11.0:", when="cuda_arch=80")
|
depends_on("cuda@11.0:", when="cuda_arch=80")
|
||||||
depends_on("cuda@11.1:", when="cuda_arch=86")
|
depends_on("cuda@11.1:", when="cuda_arch=86")
|
||||||
depends_on("cuda@11.4:", when="cuda_arch=87")
|
|
||||||
depends_on("cuda@11.8:", when="cuda_arch=89")
|
|
||||||
|
|
||||||
depends_on("cuda@12.0:", when="cuda_arch=90")
|
depends_on("cuda@11.4:", when="cuda_arch=87")
|
||||||
|
|
||||||
|
depends_on("cuda@11.8:", when="cuda_arch=89")
|
||||||
|
depends_on("cuda@11.8:", when="cuda_arch=90")
|
||||||
|
|
||||||
# From the NVIDIA install guide we know of conflicts for particular
|
# From the NVIDIA install guide we know of conflicts for particular
|
||||||
# platforms (linux, darwin), architectures (x86, powerpc) and compilers
|
# platforms (linux, darwin), architectures (x86, powerpc) and compilers
|
||||||
@@ -154,7 +155,7 @@ def cuda_flags(arch_list):
|
|||||||
conflicts("%pgi@:15.3,15.5:", when="+cuda ^cuda@7.5 target=x86_64:")
|
conflicts("%pgi@:15.3,15.5:", when="+cuda ^cuda@7.5 target=x86_64:")
|
||||||
conflicts("%pgi@:16.2,16.0:16.3", when="+cuda ^cuda@8 target=x86_64:")
|
conflicts("%pgi@:16.2,16.0:16.3", when="+cuda ^cuda@8 target=x86_64:")
|
||||||
conflicts("%pgi@:15,18:", when="+cuda ^cuda@9.0:9.1 target=x86_64:")
|
conflicts("%pgi@:15,18:", when="+cuda ^cuda@9.0:9.1 target=x86_64:")
|
||||||
conflicts("%pgi@:16,19:", when="+cuda ^cuda@9.2.88:10.0 target=x86_64:")
|
conflicts("%pgi@:16,19:", when="+cuda ^cuda@9.2.88:10 target=x86_64:")
|
||||||
conflicts("%pgi@:17,20:", when="+cuda ^cuda@10.1.105:10.2.89 target=x86_64:")
|
conflicts("%pgi@:17,20:", when="+cuda ^cuda@10.1.105:10.2.89 target=x86_64:")
|
||||||
conflicts("%pgi@:17,21:", when="+cuda ^cuda@11.0.2:11.1.0 target=x86_64:")
|
conflicts("%pgi@:17,21:", when="+cuda ^cuda@11.0.2:11.1.0 target=x86_64:")
|
||||||
conflicts("%clang@:3.4", when="+cuda ^cuda@:7.5 target=x86_64:")
|
conflicts("%clang@:3.4", when="+cuda ^cuda@:7.5 target=x86_64:")
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
import spack.builder
|
import spack.builder
|
||||||
import spack.package_base
|
import spack.package_base
|
||||||
from spack.directives import build_system, conflicts, depends_on
|
from spack.directives import build_system, conflicts
|
||||||
from spack.multimethod import when
|
|
||||||
|
|
||||||
from ._checks import (
|
from ._checks import (
|
||||||
BaseBuilder,
|
BaseBuilder,
|
||||||
@@ -30,10 +29,7 @@ class MakefilePackage(spack.package_base.PackageBase):
|
|||||||
legacy_buildsystem = "makefile"
|
legacy_buildsystem = "makefile"
|
||||||
|
|
||||||
build_system("makefile")
|
build_system("makefile")
|
||||||
|
conflicts("platform=windows", when="build_system=makefile")
|
||||||
with when("build_system=makefile"):
|
|
||||||
conflicts("platform=windows")
|
|
||||||
depends_on("gmake", type="build")
|
|
||||||
|
|
||||||
|
|
||||||
@spack.builder.builder("makefile")
|
@spack.builder.builder("makefile")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
import spack.builder
|
import spack.builder
|
||||||
import spack.package_base
|
import spack.package_base
|
||||||
from spack.directives import build_system, conflicts, depends_on, variant
|
from spack.directives import build_system, depends_on, variant
|
||||||
from spack.multimethod import when
|
from spack.multimethod import when
|
||||||
|
|
||||||
from ._checks import BaseBuilder, execute_build_time_tests
|
from ._checks import BaseBuilder, execute_build_time_tests
|
||||||
@@ -47,13 +47,6 @@ class MesonPackage(spack.package_base.PackageBase):
|
|||||||
variant("strip", default=False, description="Strip targets on install")
|
variant("strip", default=False, description="Strip targets on install")
|
||||||
depends_on("meson", type="build")
|
depends_on("meson", type="build")
|
||||||
depends_on("ninja", type="build")
|
depends_on("ninja", type="build")
|
||||||
# Python detection in meson requires distutils to be importable, but distutils no longer
|
|
||||||
# exists in Python 3.12. In Spack, we can't use setuptools as distutils replacement,
|
|
||||||
# because the distutils-precedence.pth startup file that setuptools ships with is not run
|
|
||||||
# when setuptools is in PYTHONPATH; it has to be in system site-packages. In a future meson
|
|
||||||
# release, the distutils requirement will be dropped, so this conflict can be relaxed.
|
|
||||||
# We have patches to make it work with meson 1.1 and above.
|
|
||||||
conflicts("^python@3.12:", when="^meson@:1.0")
|
|
||||||
|
|
||||||
def flags_to_build_system_args(self, flags):
|
def flags_to_build_system_args(self, flags):
|
||||||
"""Produces a list of all command line arguments to pass the specified
|
"""Produces a list of all command line arguments to pass the specified
|
||||||
@@ -216,5 +209,5 @@ def install(self, pkg, spec, prefix):
|
|||||||
def check(self):
|
def check(self):
|
||||||
"""Search Meson-generated files for the target ``test`` and run it if found."""
|
"""Search Meson-generated files for the target ``test`` and run it if found."""
|
||||||
with fs.working_dir(self.build_directory):
|
with fs.working_dir(self.build_directory):
|
||||||
self.pkg._if_ninja_target_execute("test")
|
self._if_ninja_target_execute("test")
|
||||||
self.pkg._if_ninja_target_execute("check")
|
self._if_ninja_target_execute("check")
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ def makefile_root(self):
|
|||||||
return self.stage.source_path
|
return self.stage.source_path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def makefile_name(self):
|
def nmakefile_name(self):
|
||||||
"""Name of the current makefile. This is currently an empty value.
|
"""Name of the current makefile. This is currently an empty value.
|
||||||
If a project defines this value, it will be used with the /f argument
|
If a project defines this value, it will be used with the /f argument
|
||||||
to provide nmake an explicit makefile. This is usefule in scenarios where
|
to provide nmake an explicit makefile. This is usefule in scenarios where
|
||||||
@@ -126,8 +126,8 @@ def build(self, pkg, spec, prefix):
|
|||||||
"""Run "nmake" on the build targets specified by the builder."""
|
"""Run "nmake" on the build targets specified by the builder."""
|
||||||
opts = self.std_nmake_args
|
opts = self.std_nmake_args
|
||||||
opts += self.nmake_args()
|
opts += self.nmake_args()
|
||||||
if self.makefile_name:
|
if self.nmakefile_name:
|
||||||
opts.append("/F{}".format(self.makefile_name))
|
opts.append("/f {}".format(self.nmakefile_name))
|
||||||
with fs.working_dir(self.build_directory):
|
with fs.working_dir(self.build_directory):
|
||||||
inspect.getmodule(self.pkg).nmake(
|
inspect.getmodule(self.pkg).nmake(
|
||||||
*opts, *self.build_targets, ignore_quotes=self.ignore_quotes
|
*opts, *self.build_targets, ignore_quotes=self.ignore_quotes
|
||||||
@@ -139,8 +139,8 @@ def install(self, pkg, spec, prefix):
|
|||||||
opts = self.std_nmake_args
|
opts = self.std_nmake_args
|
||||||
opts += self.nmake_args()
|
opts += self.nmake_args()
|
||||||
opts += self.nmake_install_args()
|
opts += self.nmake_install_args()
|
||||||
if self.makefile_name:
|
if self.nmakefile_name:
|
||||||
opts.append("/F{}".format(self.makefile_name))
|
opts.append("/f {}".format(self.nmakefile_name))
|
||||||
opts.append(self.define("PREFIX", prefix))
|
opts.append(self.define("PREFIX", prefix))
|
||||||
with fs.working_dir(self.build_directory):
|
with fs.working_dir(self.build_directory):
|
||||||
inspect.getmodule(self.pkg).nmake(
|
inspect.getmodule(self.pkg).nmake(
|
||||||
|
|||||||
@@ -61,11 +61,6 @@ def component_prefix(self):
|
|||||||
"""Path to component <prefix>/<component>/<version>."""
|
"""Path to component <prefix>/<component>/<version>."""
|
||||||
return self.prefix.join(join_path(self.component_dir, self.spec.version))
|
return self.prefix.join(join_path(self.component_dir, self.spec.version))
|
||||||
|
|
||||||
@property
|
|
||||||
def env_script_args(self):
|
|
||||||
"""Additional arguments to pass to vars.sh script."""
|
|
||||||
return ()
|
|
||||||
|
|
||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
self.install_component(basename(self.url_for_version(spec.version)))
|
self.install_component(basename(self.url_for_version(spec.version)))
|
||||||
|
|
||||||
@@ -126,10 +121,10 @@ def setup_run_environment(self, env):
|
|||||||
$ source {prefix}/{component}/{version}/env/vars.sh
|
$ source {prefix}/{component}/{version}/env/vars.sh
|
||||||
"""
|
"""
|
||||||
# Only if environment modifications are desired (default is +envmods)
|
# Only if environment modifications are desired (default is +envmods)
|
||||||
if "~envmods" not in self.spec:
|
if "+envmods" in self.spec:
|
||||||
env.extend(
|
env.extend(
|
||||||
EnvironmentModifications.from_sourcing_file(
|
EnvironmentModifications.from_sourcing_file(
|
||||||
join_path(self.component_prefix, "env", "vars.sh"), *self.env_script_args
|
join_path(self.component_prefix, "env", "vars.sh")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,7 +175,7 @@ def libs(self):
|
|||||||
return find_libraries("*", root=lib_path, shared=True, recursive=True)
|
return find_libraries("*", root=lib_path, shared=True, recursive=True)
|
||||||
|
|
||||||
|
|
||||||
class IntelOneApiStaticLibraryList:
|
class IntelOneApiStaticLibraryList(object):
|
||||||
"""Provides ld_flags when static linking is needed
|
"""Provides ld_flags when static linking is needed
|
||||||
|
|
||||||
Oneapi puts static and dynamic libraries in the same directory, so
|
Oneapi puts static and dynamic libraries in the same directory, so
|
||||||
|
|||||||
@@ -16,38 +16,20 @@
|
|||||||
|
|
||||||
import spack.builder
|
import spack.builder
|
||||||
import spack.config
|
import spack.config
|
||||||
import spack.deptypes as dt
|
|
||||||
import spack.detection
|
import spack.detection
|
||||||
import spack.multimethod
|
import spack.multimethod
|
||||||
import spack.package_base
|
import spack.package_base
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.store
|
import spack.store
|
||||||
from spack.directives import build_system, depends_on, extends, maintainers
|
from spack.directives import build_system, depends_on, extends, maintainers
|
||||||
from spack.error import NoHeadersError, NoLibrariesError
|
from spack.error import NoHeadersError, NoLibrariesError, SpecError
|
||||||
from spack.install_test import test_part
|
from spack.version import Version
|
||||||
|
|
||||||
from ._checks import BaseBuilder, execute_install_time_tests
|
from ._checks import BaseBuilder, execute_install_time_tests
|
||||||
|
|
||||||
|
|
||||||
def _flatten_dict(dictionary):
|
|
||||||
"""Iterable that yields KEY=VALUE paths through a dictionary.
|
|
||||||
Args:
|
|
||||||
dictionary: Possibly nested dictionary of arbitrary keys and values.
|
|
||||||
Yields:
|
|
||||||
A single path through the dictionary.
|
|
||||||
"""
|
|
||||||
for key, item in dictionary.items():
|
|
||||||
if isinstance(item, dict):
|
|
||||||
# Recursive case
|
|
||||||
for value in _flatten_dict(item):
|
|
||||||
yield f"{key}={value}"
|
|
||||||
else:
|
|
||||||
# Base case
|
|
||||||
yield f"{key}={item}"
|
|
||||||
|
|
||||||
|
|
||||||
class PythonExtension(spack.package_base.PackageBase):
|
class PythonExtension(spack.package_base.PackageBase):
|
||||||
maintainers("adamjstewart")
|
maintainers("adamjstewart", "pradyunsg")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def import_modules(self):
|
def import_modules(self):
|
||||||
@@ -185,106 +167,18 @@ def remove_files_from_view(self, view, merge_map):
|
|||||||
|
|
||||||
view.remove_files(to_remove)
|
view.remove_files(to_remove)
|
||||||
|
|
||||||
def test_imports(self):
|
def test(self):
|
||||||
"""Attempts to import modules of the installed package."""
|
"""Attempts to import modules of the installed package."""
|
||||||
|
|
||||||
# Make sure we are importing the installed modules,
|
# Make sure we are importing the installed modules,
|
||||||
# not the ones in the source directory
|
# not the ones in the source directory
|
||||||
python = inspect.getmodule(self).python
|
|
||||||
for module in self.import_modules:
|
for module in self.import_modules:
|
||||||
with test_part(
|
self.run_test(
|
||||||
self,
|
inspect.getmodule(self).python.path,
|
||||||
f"test_imports_{module}",
|
["-c", "import {0}".format(module)],
|
||||||
purpose=f"checking import of {module}",
|
purpose="checking import of {0}".format(module),
|
||||||
work_dir="spack-test",
|
work_dir="spack-test",
|
||||||
):
|
)
|
||||||
python("-c", f"import {module}")
|
|
||||||
|
|
||||||
def update_external_dependencies(self, extendee_spec=None):
|
|
||||||
"""
|
|
||||||
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 extendee_spec:
|
|
||||||
python = extendee_spec
|
|
||||||
elif "python" in self.spec.root:
|
|
||||||
python = self.spec.root["python"]
|
|
||||||
else:
|
|
||||||
python = self.get_external_python_for_prefix()
|
|
||||||
if not python.concrete:
|
|
||||||
repo = spack.repo.PATH.repo_for_pkg(python)
|
|
||||||
python.namespace = repo.namespace
|
|
||||||
|
|
||||||
# Ensure architecture information is present
|
|
||||||
if not python.architecture:
|
|
||||||
host_platform = spack.platforms.host()
|
|
||||||
host_os = host_platform.operating_system("default_os")
|
|
||||||
host_target = host_platform.target("default_target")
|
|
||||||
python.architecture = spack.spec.ArchSpec(
|
|
||||||
(str(host_platform), str(host_os), str(host_target))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if not python.architecture.platform:
|
|
||||||
python.architecture.platform = spack.platforms.host()
|
|
||||||
if not python.architecture.os:
|
|
||||||
python.architecture.os = "default_os"
|
|
||||||
if not python.architecture.target:
|
|
||||||
python.architecture.target = archspec.cpu.host().family.name
|
|
||||||
|
|
||||||
# Ensure compiler information is present
|
|
||||||
if not python.compiler:
|
|
||||||
python.compiler = self.spec.compiler
|
|
||||||
|
|
||||||
python.external_path = self.spec.external_path
|
|
||||||
python._mark_concrete()
|
|
||||||
self.spec.add_dependency_edge(python, depflag=dt.BUILD | dt.LINK | dt.RUN, virtuals=())
|
|
||||||
|
|
||||||
def get_external_python_for_prefix(self):
|
|
||||||
"""
|
|
||||||
For an external package that extends python, find the most likely spec for the python
|
|
||||||
it depends on.
|
|
||||||
|
|
||||||
First search: an "installed" external that shares a prefix with this package
|
|
||||||
Second search: a configured external that shares a prefix with this package
|
|
||||||
Third search: search this prefix for a python package
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
spack.spec.Spec: The external Spec for python most likely to be compatible with self.spec
|
|
||||||
"""
|
|
||||||
python_externals_installed = [
|
|
||||||
s for s in spack.store.STORE.db.query("python") if s.prefix == self.spec.external_path
|
|
||||||
]
|
|
||||||
if python_externals_installed:
|
|
||||||
return python_externals_installed[0]
|
|
||||||
|
|
||||||
python_external_config = spack.config.get("packages:python:externals", [])
|
|
||||||
python_externals_configured = [
|
|
||||||
spack.spec.parse_with_version_concrete(item["spec"])
|
|
||||||
for item in python_external_config
|
|
||||||
if item["prefix"] == self.spec.external_path
|
|
||||||
]
|
|
||||||
if python_externals_configured:
|
|
||||||
return python_externals_configured[0]
|
|
||||||
|
|
||||||
python_externals_detection = spack.detection.by_path(
|
|
||||||
["python"], path_hints=[self.spec.external_path]
|
|
||||||
)
|
|
||||||
|
|
||||||
python_externals_detected = [
|
|
||||||
d.spec
|
|
||||||
for d in python_externals_detection.get("python", [])
|
|
||||||
if d.prefix == self.spec.external_path
|
|
||||||
]
|
|
||||||
if python_externals_detected:
|
|
||||||
return python_externals_detected[0]
|
|
||||||
|
|
||||||
raise StopIteration("No external python could be detected for %s to depend on" % self.spec)
|
|
||||||
|
|
||||||
|
|
||||||
class PythonPackage(PythonExtension):
|
class PythonPackage(PythonExtension):
|
||||||
@@ -331,16 +225,99 @@ def list_url(cls):
|
|||||||
name = cls.pypi.split("/")[0]
|
name = cls.pypi.split("/")[0]
|
||||||
return "https://pypi.org/simple/" + name + "/"
|
return "https://pypi.org/simple/" + name + "/"
|
||||||
|
|
||||||
|
def update_external_dependencies(self, extendee_spec=None):
|
||||||
|
"""
|
||||||
|
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 extendee_spec:
|
||||||
|
python = extendee_spec
|
||||||
|
elif "python" in self.spec.root:
|
||||||
|
python = self.spec.root["python"]
|
||||||
|
else:
|
||||||
|
python = self.get_external_python_for_prefix()
|
||||||
|
if not python.concrete:
|
||||||
|
repo = spack.repo.path.repo_for_pkg(python)
|
||||||
|
python.namespace = repo.namespace
|
||||||
|
|
||||||
|
# Ensure architecture information is present
|
||||||
|
if not python.architecture:
|
||||||
|
host_platform = spack.platforms.host()
|
||||||
|
host_os = host_platform.operating_system("default_os")
|
||||||
|
host_target = host_platform.target("default_target")
|
||||||
|
python.architecture = spack.spec.ArchSpec(
|
||||||
|
(str(host_platform), str(host_os), str(host_target))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if not python.architecture.platform:
|
||||||
|
python.architecture.platform = spack.platforms.host()
|
||||||
|
if not python.architecture.os:
|
||||||
|
python.architecture.os = "default_os"
|
||||||
|
if not python.architecture.target:
|
||||||
|
python.architecture.target = archspec.cpu.host().family.name
|
||||||
|
|
||||||
|
# Ensure compiler information is present
|
||||||
|
if not python.compiler:
|
||||||
|
python.compiler = self.spec.compiler
|
||||||
|
|
||||||
|
python.external_path = self.spec.external_path
|
||||||
|
python._mark_concrete()
|
||||||
|
self.spec.add_dependency_edge(python, deptypes=("build", "link", "run"))
|
||||||
|
|
||||||
|
def get_external_python_for_prefix(self):
|
||||||
|
"""
|
||||||
|
For an external package that extends python, find the most likely spec for the python
|
||||||
|
it depends on.
|
||||||
|
|
||||||
|
First search: an "installed" external that shares a prefix with this package
|
||||||
|
Second search: a configured external that shares a prefix with this package
|
||||||
|
Third search: search this prefix for a python package
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
spack.spec.Spec: The external Spec for python most likely to be compatible with self.spec
|
||||||
|
"""
|
||||||
|
python_externals_installed = [
|
||||||
|
s for s in spack.store.db.query("python") if s.prefix == self.spec.external_path
|
||||||
|
]
|
||||||
|
if python_externals_installed:
|
||||||
|
return python_externals_installed[0]
|
||||||
|
|
||||||
|
python_external_config = spack.config.get("packages:python:externals", [])
|
||||||
|
python_externals_configured = [
|
||||||
|
spack.spec.parse_with_version_concrete(item["spec"])
|
||||||
|
for item in python_external_config
|
||||||
|
if item["prefix"] == self.spec.external_path
|
||||||
|
]
|
||||||
|
if python_externals_configured:
|
||||||
|
return python_externals_configured[0]
|
||||||
|
|
||||||
|
python_externals_detection = spack.detection.by_executable(
|
||||||
|
[spack.repo.path.get_pkg_class("python")], path_hints=[self.spec.external_path]
|
||||||
|
)
|
||||||
|
|
||||||
|
python_externals_detected = [
|
||||||
|
d.spec
|
||||||
|
for d in python_externals_detection.get("python", [])
|
||||||
|
if d.prefix == self.spec.external_path
|
||||||
|
]
|
||||||
|
if python_externals_detected:
|
||||||
|
return python_externals_detected[0]
|
||||||
|
|
||||||
|
raise StopIteration("No external python could be detected for %s to depend on" % self.spec)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def headers(self):
|
def headers(self):
|
||||||
"""Discover header files in platlib."""
|
"""Discover header files in platlib."""
|
||||||
|
|
||||||
# Remove py- prefix in package name
|
|
||||||
name = self.spec.name[3:]
|
|
||||||
|
|
||||||
# Headers may be in either location
|
# Headers may be in either location
|
||||||
include = self.prefix.join(self.spec["python"].package.include).join(name)
|
include = self.prefix.join(self.spec["python"].package.include)
|
||||||
platlib = self.prefix.join(self.spec["python"].package.platlib).join(name)
|
platlib = self.prefix.join(self.spec["python"].package.platlib)
|
||||||
headers = fs.find_all_headers(include) + fs.find_all_headers(platlib)
|
headers = fs.find_all_headers(include) + fs.find_all_headers(platlib)
|
||||||
|
|
||||||
if headers:
|
if headers:
|
||||||
@@ -354,14 +331,13 @@ def libs(self):
|
|||||||
"""Discover libraries in platlib."""
|
"""Discover libraries in platlib."""
|
||||||
|
|
||||||
# Remove py- prefix in package name
|
# Remove py- prefix in package name
|
||||||
name = self.spec.name[3:]
|
library = "lib" + self.spec.name[3:].replace("-", "?")
|
||||||
|
root = self.prefix.join(self.spec["python"].package.platlib)
|
||||||
|
|
||||||
root = self.prefix.join(self.spec["python"].package.platlib).join(name)
|
for shared in [True, False]:
|
||||||
|
libs = fs.find_libraries(library, root, shared=shared, recursive=True)
|
||||||
libs = fs.find_all_libraries(root, recursive=True)
|
if libs:
|
||||||
|
return libs
|
||||||
if libs:
|
|
||||||
return libs
|
|
||||||
|
|
||||||
msg = "Unable to recursively locate {} libraries in {}"
|
msg = "Unable to recursively locate {} libraries in {}"
|
||||||
raise NoLibrariesError(msg.format(self.spec.name, root))
|
raise NoLibrariesError(msg.format(self.spec.name, root))
|
||||||
@@ -378,7 +354,7 @@ class PythonPipBuilder(BaseBuilder):
|
|||||||
legacy_long_methods = ("install_options", "global_options", "config_settings")
|
legacy_long_methods = ("install_options", "global_options", "config_settings")
|
||||||
|
|
||||||
#: Names associated with package attributes in the old build-system format
|
#: Names associated with package attributes in the old build-system format
|
||||||
legacy_attributes = ("archive_files", "build_directory", "install_time_test_callbacks")
|
legacy_attributes = ("build_directory", "install_time_test_callbacks")
|
||||||
|
|
||||||
#: Callback names for install-time test
|
#: Callback names for install-time test
|
||||||
install_time_test_callbacks = ["test"]
|
install_time_test_callbacks = ["test"]
|
||||||
@@ -422,24 +398,20 @@ def build_directory(self):
|
|||||||
|
|
||||||
def config_settings(self, spec, prefix):
|
def config_settings(self, spec, prefix):
|
||||||
"""Configuration settings to be passed to the PEP 517 build backend.
|
"""Configuration settings to be passed to the PEP 517 build backend.
|
||||||
|
Requires pip 22.1+, which requires Python 3.7+.
|
||||||
Requires pip 22.1 or newer for keys that appear only a single time,
|
|
||||||
or pip 23.1 or newer if the same key appears multiple times.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
spec (spack.spec.Spec): build spec
|
spec (spack.spec.Spec): build spec
|
||||||
prefix (spack.util.prefix.Prefix): installation prefix
|
prefix (spack.util.prefix.Prefix): installation prefix
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Possibly nested dictionary of KEY, VALUE settings
|
dict: dictionary of KEY, VALUE settings
|
||||||
"""
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def install_options(self, spec, prefix):
|
def install_options(self, spec, prefix):
|
||||||
"""Extra arguments to be supplied to the setup.py install command.
|
"""Extra arguments to be supplied to the setup.py install command.
|
||||||
|
|
||||||
Requires pip 23.0 or older.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
spec (spack.spec.Spec): build spec
|
spec (spack.spec.Spec): build spec
|
||||||
prefix (spack.util.prefix.Prefix): installation prefix
|
prefix (spack.util.prefix.Prefix): installation prefix
|
||||||
@@ -453,8 +425,6 @@ def global_options(self, spec, prefix):
|
|||||||
"""Extra global options to be supplied to the setup.py call before the install
|
"""Extra global options to be supplied to the setup.py call before the install
|
||||||
or bdist_wheel command.
|
or bdist_wheel command.
|
||||||
|
|
||||||
Deprecated in pip 23.1.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
spec (spack.spec.Spec): build spec
|
spec (spack.spec.Spec): build spec
|
||||||
prefix (spack.util.prefix.Prefix): installation prefix
|
prefix (spack.util.prefix.Prefix): installation prefix
|
||||||
@@ -467,28 +437,29 @@ def global_options(self, spec, prefix):
|
|||||||
def install(self, pkg, spec, prefix):
|
def install(self, pkg, spec, prefix):
|
||||||
"""Install everything from build directory."""
|
"""Install everything from build directory."""
|
||||||
|
|
||||||
args = PythonPipBuilder.std_args(pkg) + [f"--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"):
|
||||||
|
raise SpecError(
|
||||||
|
"'{}' package uses 'config_settings' which is only supported by "
|
||||||
|
"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 setting in _flatten_dict(self.config_settings(spec, prefix)):
|
|
||||||
args.append(f"--config-settings={setting}")
|
|
||||||
for option in self.install_options(spec, prefix):
|
for option in self.install_options(spec, prefix):
|
||||||
args.append(f"--install-option={option}")
|
args.append("--install-option=" + option)
|
||||||
for option in self.global_options(spec, prefix):
|
for option in self.global_options(spec, prefix):
|
||||||
args.append(f"--global-option={option}")
|
args.append("--global-option=" + option)
|
||||||
|
|
||||||
if pkg.stage.archive_file and pkg.stage.archive_file.endswith(".whl"):
|
if pkg.stage.archive_file and pkg.stage.archive_file.endswith(".whl"):
|
||||||
args.append(pkg.stage.archive_file)
|
args.append(pkg.stage.archive_file)
|
||||||
else:
|
else:
|
||||||
args.append(".")
|
args.append(".")
|
||||||
|
|
||||||
pip = spec["python"].command
|
pip = inspect.getmodule(pkg).pip
|
||||||
# Hide user packages, since we don't have build isolation. This is
|
|
||||||
# necessary because pip / setuptools may run hooks from arbitrary
|
|
||||||
# packages during the build. There is no equivalent variable to hide
|
|
||||||
# system packages, so this is not reliable for external Python.
|
|
||||||
pip.add_default_env("PYTHONNOUSERSITE", "1")
|
|
||||||
pip.add_default_arg("-m")
|
|
||||||
pip.add_default_arg("pip")
|
|
||||||
with fs.working_dir(self.build_directory):
|
with fs.working_dir(self.build_directory):
|
||||||
pip(*args)
|
pip(*args)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class QMakePackage(spack.package_base.PackageBase):
|
|||||||
|
|
||||||
build_system("qmake")
|
build_system("qmake")
|
||||||
|
|
||||||
depends_on("qmake", type="build", when="build_system=qmake")
|
depends_on("qt", type="build", when="build_system=qmake")
|
||||||
|
|
||||||
|
|
||||||
@spack.builder.builder("qmake")
|
@spack.builder.builder("qmake")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import llnl.util.lang as lang
|
import llnl.util.lang as lang
|
||||||
|
|
||||||
from spack.directives import extends
|
from spack.directives import extends, maintainers
|
||||||
|
|
||||||
from .generic import GenericBuilder, Package
|
from .generic import GenericBuilder, Package
|
||||||
|
|
||||||
@@ -71,6 +71,8 @@ class RPackage(Package):
|
|||||||
|
|
||||||
GenericBuilder = RBuilder
|
GenericBuilder = RBuilder
|
||||||
|
|
||||||
|
maintainers("glennpj")
|
||||||
|
|
||||||
#: This attribute is used in UI queries that need to know the build
|
#: This attribute is used in UI queries that need to know the build
|
||||||
#: system base class
|
#: system base class
|
||||||
build_system_class = "RPackage"
|
build_system_class = "RPackage"
|
||||||
|
|||||||
@@ -10,10 +10,9 @@
|
|||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
|
||||||
import spack.builder
|
import spack.builder
|
||||||
from spack.build_environment import SPACK_NO_PARALLEL_MAKE
|
from spack.build_environment import SPACK_NO_PARALLEL_MAKE, determine_number_of_jobs
|
||||||
from spack.directives import build_system, extends, maintainers
|
from spack.directives import build_system, extends, maintainers
|
||||||
from spack.package_base import PackageBase
|
from spack.package_base import PackageBase
|
||||||
from spack.util.cpus import determine_number_of_jobs
|
|
||||||
from spack.util.environment import env_flag
|
from spack.util.environment import env_flag
|
||||||
from spack.util.executable import Executable, ProcessError
|
from spack.util.executable import Executable, ProcessError
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ class RacketBuilder(spack.builder.Builder):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def subdirectory(self):
|
def subdirectory(self):
|
||||||
if self.pkg.racket_name:
|
if self.racket_name:
|
||||||
return "pkgs/{0}".format(self.pkg.racket_name)
|
return "pkgs/{0}".format(self.pkg.racket_name)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ def install(self, pkg, spec, prefix):
|
|||||||
"--copy",
|
"--copy",
|
||||||
"-i",
|
"-i",
|
||||||
"-j",
|
"-j",
|
||||||
str(determine_number_of_jobs(parallel=parallel)),
|
str(determine_number_of_jobs(parallel)),
|
||||||
"--",
|
"--",
|
||||||
os.getcwd(),
|
os.getcwd(),
|
||||||
]
|
]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user