Compare commits
519 Commits
e4s-24.02
...
docs/no-se
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6cb5700b1c | ||
![]() |
763f444d63 | ||
![]() |
6614c4322d | ||
![]() |
983422facf | ||
![]() |
d0bdd66238 | ||
![]() |
3a50d32299 | ||
![]() |
50ee3624c0 | ||
![]() |
2840cb54b5 | ||
![]() |
5c482d0d7e | ||
![]() |
f3ad990b55 | ||
![]() |
977603cd96 | ||
![]() |
1f919255fd | ||
![]() |
5140a9b6a3 | ||
![]() |
1732ad0af4 | ||
![]() |
e7510ae292 | ||
![]() |
0c224ba4a7 | ||
![]() |
86b4a867ef | ||
![]() |
6049e5f6eb | ||
![]() |
0339690a59 | ||
![]() |
2bae1b7e00 | ||
![]() |
ae5b605f99 | ||
![]() |
35898d94d2 | ||
![]() |
7e00bd5014 | ||
![]() |
1f3aefb0a3 | ||
![]() |
d4601d0e53 | ||
![]() |
935660e3d5 | ||
![]() |
17bfc41841 | ||
![]() |
49b38e3a78 | ||
![]() |
66f078ff84 | ||
![]() |
304a63507a | ||
![]() |
c7afc0eb5f | ||
![]() |
57cde78c56 | ||
![]() |
b01f6308d5 | ||
![]() |
13709bb7b7 | ||
![]() |
661ae1f230 | ||
![]() |
287e1039f5 | ||
![]() |
015dc4ee2e | ||
![]() |
46165982b1 | ||
![]() |
c9a111946e | ||
![]() |
62160021c1 | ||
![]() |
3290e2c189 | ||
![]() |
2a9fc3452a | ||
![]() |
2ea8a2e6ae | ||
![]() |
fe4a4ddcf9 | ||
![]() |
c45714fd3c | ||
![]() |
523d12d9a8 | ||
![]() |
5340e0184d | ||
![]() |
bca7698138 | ||
![]() |
5c26ce5385 | ||
![]() |
02137dda17 | ||
![]() |
4abac88895 | ||
![]() |
79c2a55e00 | ||
![]() |
71c169293c | ||
![]() |
bcc5ded205 | ||
![]() |
379a5d8fa0 | ||
![]() |
d8c2782949 | ||
![]() |
6dde6ca887 | ||
![]() |
8f8c262fb3 | ||
![]() |
93b8e771b6 | ||
![]() |
48088ee24a | ||
![]() |
c7df258ca6 | ||
![]() |
b8e8fa2dcd | ||
![]() |
8e885f4eb2 | ||
![]() |
116308fa17 | ||
![]() |
5eb4d858cb | ||
![]() |
8dd5f36b68 | ||
![]() |
e3ce3ab266 | ||
![]() |
0618cb98d1 | ||
![]() |
3b4a27ce7b | ||
![]() |
07afbd5619 | ||
![]() |
5d8cd207ec | ||
![]() |
3990589b08 | ||
![]() |
38d821461c | ||
![]() |
80b13a0059 | ||
![]() |
ab101d33be | ||
![]() |
cc742126ef | ||
![]() |
a1f21436a4 | ||
![]() |
95fdc92c1f | ||
![]() |
6680c6b72e | ||
![]() |
74b6bf14b8 | ||
![]() |
7c315fc14b | ||
![]() |
f51c9fc6c3 | ||
![]() |
3e713bb0fa | ||
![]() |
55bbb10984 | ||
![]() |
6e37f873f5 | ||
![]() |
d49cbecf8c | ||
![]() |
fe07645e3b | ||
![]() |
c5b8d5c92a | ||
![]() |
2278816cb3 | ||
![]() |
4bd305b6d3 | ||
![]() |
a26e0ff999 | ||
![]() |
4550fb83c0 | ||
![]() |
5b96c2e89f | ||
![]() |
18c8406091 | ||
![]() |
bccefa14cb | ||
![]() |
20fc5a174a | ||
![]() |
3abcb408d1 | ||
![]() |
a0d97d9294 | ||
![]() |
0979a6a875 | ||
![]() |
98de8e3257 | ||
![]() |
23b299c086 | ||
![]() |
adcd3364c9 | ||
![]() |
a2908fb9f7 | ||
![]() |
f514e72011 | ||
![]() |
b61d964eb8 | ||
![]() |
2066eda3cd | ||
![]() |
d8b186a381 | ||
![]() |
d258aec099 | ||
![]() |
3d1d5f755f | ||
![]() |
90778873d1 | ||
![]() |
1af57e13fb | ||
![]() |
28d25affcc | ||
![]() |
3ebaf33915 | ||
![]() |
e8d981b405 | ||
![]() |
02f222f6a3 | ||
![]() |
8345c6fb85 | ||
![]() |
3f23634c46 | ||
![]() |
d5766431a0 | ||
![]() |
1388bfe47d | ||
![]() |
579dec3b35 | ||
![]() |
9608ed9dbd | ||
![]() |
42b739d6d5 | ||
![]() |
91a0c71ed1 | ||
![]() |
6ee6fbe56b | ||
![]() |
be4eae3fa8 | ||
![]() |
ad70b88d5f | ||
![]() |
c1d230f25f | ||
![]() |
4bc52fc1a3 | ||
![]() |
7d728822f0 | ||
![]() |
e96640d2b9 | ||
![]() |
e196978c7c | ||
![]() |
de3d1e6c66 | ||
![]() |
2d8e0825fe | ||
![]() |
d5c06c4e2c | ||
![]() |
0d92b07dbd | ||
![]() |
6cfcbc0167 | ||
![]() |
f9e9fab2da | ||
![]() |
b3f790c259 | ||
![]() |
b5b5130bed | ||
![]() |
d670a2a5ce | ||
![]() |
0ee3a3c401 | ||
![]() |
ad6dc0d103 | ||
![]() |
6e373b46f8 | ||
![]() |
abe617c4ea | ||
![]() |
a0e80b23b9 | ||
![]() |
4d051eb6ff | ||
![]() |
0d89083cb5 | ||
![]() |
23e586fd85 | ||
![]() |
c2b116175b | ||
![]() |
a1f90620c3 | ||
![]() |
668879141f | ||
![]() |
267defd9d3 | ||
![]() |
603d3f2283 | ||
![]() |
02bfbbe269 | ||
![]() |
44d08d2c24 | ||
![]() |
c6faab10aa | ||
![]() |
baa203f115 | ||
![]() |
575a33846f | ||
![]() |
79df065730 | ||
![]() |
953ee2d0ca | ||
![]() |
6796b581ed | ||
![]() |
f49a5519b7 | ||
![]() |
e07a6f51dc | ||
![]() |
cb73d71cf1 | ||
![]() |
5949fc2c88 | ||
![]() |
fd10cfdebf | ||
![]() |
32506e222d | ||
![]() |
a7d5cc6d68 | ||
![]() |
222241f232 | ||
![]() |
aa1820eb5c | ||
![]() |
16ea5f68ba | ||
![]() |
aeec515b4f | ||
![]() |
6e2ec2950b | ||
![]() |
fe5772898d | ||
![]() |
384ddf8e93 | ||
![]() |
32c2e240f8 | ||
![]() |
b88c4792a7 | ||
![]() |
ac92e94b00 | ||
![]() |
4a01865f7b | ||
![]() |
025165e22e | ||
![]() |
cda9bc3e1d | ||
![]() |
caf21dda42 | ||
![]() |
e1779a2884 | ||
![]() |
f55a018fd9 | ||
![]() |
f5964e1dde | ||
![]() |
23e0fe2e21 | ||
![]() |
44438e6171 | ||
![]() |
af8e000a93 | ||
![]() |
375b82a593 | ||
![]() |
2030e2b089 | ||
![]() |
34aba94148 | ||
![]() |
43c909e19c | ||
![]() |
7c011d304f | ||
![]() |
1546fc7e5f | ||
![]() |
75a134f085 | ||
![]() |
d0c4675a9b | ||
![]() |
0507c3c63d | ||
![]() |
59caa93571 | ||
![]() |
b4b53a9a9f | ||
![]() |
be1cfffa45 | ||
![]() |
75b7109222 | ||
![]() |
c56cf8c0d2 | ||
![]() |
5c3df6e8ca | ||
![]() |
79087d08d9 | ||
![]() |
d31e503e5b | ||
![]() |
55b62c2168 | ||
![]() |
6c3511ee1d | ||
![]() |
d19fa74909 | ||
![]() |
a2ad2d1c9f | ||
![]() |
55863bd680 | ||
![]() |
7d4dcd52d9 | ||
![]() |
5e4e72ddd2 | ||
![]() |
447c48e2fd | ||
![]() |
3be4f4db86 | ||
![]() |
ca97a0fefb | ||
![]() |
59f56327fe | ||
![]() |
e4c871489a | ||
![]() |
c048101d90 | ||
![]() |
e0304bf509 | ||
![]() |
0e2a9fe26a | ||
![]() |
d1e01d5646 | ||
![]() |
7b04910f84 | ||
![]() |
0664a2cdb2 | ||
![]() |
fc38fe1c69 | ||
![]() |
29cb1d0ce0 | ||
![]() |
ce777e3c89 | ||
![]() |
bd44cedd0d | ||
![]() |
316a9a5d11 | ||
![]() |
4a04989bbb | ||
![]() |
2c4b529896 | ||
![]() |
e37c099ddb | ||
![]() |
4d7898a669 | ||
![]() |
91b0528abf | ||
![]() |
8ee3073350 | ||
![]() |
cb3c014a43 | ||
![]() |
2a01e9679a | ||
![]() |
519deac544 | ||
![]() |
c33a8dc223 | ||
![]() |
742e2fc7e4 | ||
![]() |
e90b616428 | ||
![]() |
3d037c5150 | ||
![]() |
fd60e97784 | ||
![]() |
c2cda6bc48 | ||
![]() |
1cd95a4bb7 | ||
![]() |
727eaf3c82 | ||
![]() |
d014671bcb | ||
![]() |
4edb073a20 | ||
![]() |
5d2b9514db | ||
![]() |
2491855678 | ||
![]() |
b23038db53 | ||
![]() |
6d68dcf13c | ||
![]() |
fbf6db035b | ||
![]() |
1a43fc1e62 | ||
![]() |
8210853276 | ||
![]() |
e8bf6ab352 | ||
![]() |
0aa91b99ed | ||
![]() |
d93b035f14 | ||
![]() |
adb0757f72 | ||
![]() |
2369a8f4e5 | ||
![]() |
a6844d26e0 | ||
![]() |
95c663224d | ||
![]() |
6dc01f0d94 | ||
![]() |
2d3eeecef8 | ||
![]() |
167a168a63 | ||
![]() |
10af235895 | ||
![]() |
5d6dec5a5c | ||
![]() |
6ae0696e2f | ||
![]() |
9688e91a6b | ||
![]() |
c026943c8e | ||
![]() |
662ab2d4c5 | ||
![]() |
722b00bbfb | ||
![]() |
6dbb56ba36 | ||
![]() |
2e5bdd2679 | ||
![]() |
97ec167452 | ||
![]() |
2b8dcc0f57 | ||
![]() |
6312658888 | ||
![]() |
213818ffb5 | ||
![]() |
aed5d1a88d | ||
![]() |
8b94128625 | ||
![]() |
4d91fbdf0f | ||
![]() |
d7aa9d38fa | ||
![]() |
b3369ac669 | ||
![]() |
e68fde6f4e | ||
![]() |
f0e49a54c0 | ||
![]() |
2c67571726 | ||
![]() |
fae6d3780f | ||
![]() |
34ba8e9527 | ||
![]() |
686d1bc1ea | ||
![]() |
9b42f9ab18 | ||
![]() |
1d508e1be3 | ||
![]() |
41f735a4ee | ||
![]() |
e9e6eb613b | ||
![]() |
6dd19594ab | ||
![]() |
4f0a8fce52 | ||
![]() |
7ff3b17f14 | ||
![]() |
f71669175f | ||
![]() |
27b72b7691 | ||
![]() |
c5e0270ef0 | ||
![]() |
c59d2d5b1c | ||
![]() |
a832d31ccd | ||
![]() |
47a8bde4da | ||
![]() |
13050a9fb3 | ||
![]() |
753e8b53d3 | ||
![]() |
af49f81ec5 | ||
![]() |
db1a7406ca | ||
![]() |
ecc9145d2c | ||
![]() |
7090983c67 | ||
![]() |
09fdea959f | ||
![]() |
e419e4ca93 | ||
![]() |
9e8f6e8d54 | ||
![]() |
753d69856a | ||
![]() |
6d28caefc7 | ||
![]() |
7b9eac02ff | ||
![]() |
138f8ba6e2 | ||
![]() |
046f9e89a1 | ||
![]() |
2cca64d01d | ||
![]() |
9aed13adb9 | ||
![]() |
85b6c344bd | ||
![]() |
9359c9b9db | ||
![]() |
d43f62cc5f | ||
![]() |
a30d4612f5 | ||
![]() |
e32009a7e3 | ||
![]() |
b0f3489d68 | ||
![]() |
9099e4c233 | ||
![]() |
a1b895e547 | ||
![]() |
df1ff0affa | ||
![]() |
95e828f3d8 | ||
![]() |
a28c6caac0 | ||
![]() |
514260d8cb | ||
![]() |
642ec1918f | ||
![]() |
bdae9f776b | ||
![]() |
5c86a3cca2 | ||
![]() |
ea53008604 | ||
![]() |
01ea8f46e7 | ||
![]() |
ff1e700b56 | ||
![]() |
5efa723289 | ||
![]() |
4985f87a52 | ||
![]() |
ae000f963c | ||
![]() |
0960f691a1 | ||
![]() |
713b19cac7 | ||
![]() |
05f1f07e51 | ||
![]() |
955a01dfa4 | ||
![]() |
928ee7569c | ||
![]() |
8190903821 | ||
![]() |
473347df41 | ||
![]() |
1ef52d7c8e | ||
![]() |
561fe13bad | ||
![]() |
c15ed38cef | ||
![]() |
fa4568d9c9 | ||
![]() |
ef4f78a6cd | ||
![]() |
da36d069db | ||
![]() |
6ff3e17a7d | ||
![]() |
bb200be57d | ||
![]() |
a238563fdb | ||
![]() |
bfc6f1d2a9 | ||
![]() |
99b8a08366 | ||
![]() |
dba5ae939d | ||
![]() |
7a4aa823d1 | ||
![]() |
89e387cb67 | ||
![]() |
19c46de69f | ||
![]() |
e35fbfab77 | ||
![]() |
478203dc68 | ||
![]() |
5713ffd143 | ||
![]() |
1711b6dee1 | ||
![]() |
7fbd4afaaa | ||
![]() |
f396dbcb4f | ||
![]() |
57fe3430fd | ||
![]() |
8428bef040 | ||
![]() |
47c91c9163 | ||
![]() |
6c57360eac | ||
![]() |
624292d685 | ||
![]() |
55ecc47dce | ||
![]() |
f2125882c5 | ||
![]() |
d23cb39a3f | ||
![]() |
7f7d5b899a | ||
![]() |
c44e854d05 | ||
![]() |
8fa8dbc269 | ||
![]() |
e59303d4ff | ||
![]() |
494d943a24 | ||
![]() |
d227da5554 | ||
![]() |
714590426f | ||
![]() |
9ffe179934 | ||
![]() |
9c4e44a0ad | ||
![]() |
1ef69a8bfb | ||
![]() |
55db090206 | ||
![]() |
f8ce84860c | ||
![]() |
795360fe48 | ||
![]() |
3d3d075496 | ||
![]() |
35630c219d | ||
![]() |
5ef9bb7752 | ||
![]() |
5bd5a219a6 | ||
![]() |
c1450d26ff | ||
![]() |
10d826597a | ||
![]() |
00cbcd5dbc | ||
![]() |
6bd8fda597 | ||
![]() |
8b88255b43 | ||
![]() |
bcbd78cea9 | ||
![]() |
22ad28bb17 | ||
![]() |
74bd8a9cf7 | ||
![]() |
023a6be67d | ||
![]() |
922a1983f3 | ||
![]() |
2758fc7e14 | ||
![]() |
3541fcae01 | ||
![]() |
eea06de6df | ||
![]() |
29658eddcc | ||
![]() |
2fc0d05a55 | ||
![]() |
faf64f1a26 | ||
![]() |
d340523d68 | ||
![]() |
089fa12ff8 | ||
![]() |
d9528819a3 | ||
![]() |
e3d5ca2997 | ||
![]() |
8fc76ab325 | ||
![]() |
e77678bd82 | ||
![]() |
dafd8dbe87 | ||
![]() |
5034919d23 | ||
![]() |
2bcfb72d38 | ||
![]() |
f27bff81ba | ||
![]() |
5c49bb45c7 | ||
![]() |
97fb9565ee | ||
![]() |
72eaca23fe | ||
![]() |
1f11b3844a | ||
![]() |
e129a6f47a | ||
![]() |
d983ac35fe | ||
![]() |
2dcf4f709b | ||
![]() |
a171fe3565 | ||
![]() |
c9aeab58e6 | ||
![]() |
517dac6ff8 | ||
![]() |
376653ec3d | ||
![]() |
28eea2994f | ||
![]() |
6d55caabe8 | ||
![]() |
d8260907df | ||
![]() |
9474f4bb33 | ||
![]() |
698b71e2fd | ||
![]() |
749301d133 | ||
![]() |
a20c0de6d8 | ||
![]() |
ad2ae63745 | ||
![]() |
d56ca4f107 | ||
![]() |
e09c3ddec2 | ||
![]() |
1882920c97 | ||
![]() |
b70cb60b65 | ||
![]() |
6a1e64f531 | ||
![]() |
5217b20901 | ||
![]() |
ed37969925 | ||
![]() |
32230e6520 | ||
![]() |
7a712a11b9 | ||
![]() |
e63d8e6163 | ||
![]() |
461a9093cd | ||
![]() |
8edb6ff22a | ||
![]() |
1f44be83af | ||
![]() |
1be078d01d | ||
![]() |
c07cde3308 | ||
![]() |
dda8b1a5b8 | ||
![]() |
ba45277640 | ||
![]() |
f03ae39fd1 | ||
![]() |
62145122be | ||
![]() |
44e33c3eb9 | ||
![]() |
ed5ed3e31e | ||
![]() |
cc866efba1 | ||
![]() |
f550262efb | ||
![]() |
890ec8d71c | ||
![]() |
62ed5ee318 | ||
![]() |
0a10ff70bc | ||
![]() |
0718e3459a | ||
![]() |
7ec93a496d | ||
![]() |
2170386ad9 | ||
![]() |
6c48effbf5 | ||
![]() |
97e3da0e3e | ||
![]() |
d6ff81ab4d | ||
![]() |
6dfc2e7075 | ||
![]() |
80e36d47c2 | ||
![]() |
e1826f89d4 | ||
![]() |
23df20fe6d | ||
![]() |
10ef7a0466 | ||
![]() |
7eba7d4028 | ||
![]() |
b09d741aed | ||
![]() |
594069961a | ||
![]() |
9eb445f0a2 | ||
![]() |
e791f61c52 | ||
![]() |
b074e18402 | ||
![]() |
5b6a289d30 | ||
![]() |
8e9bce44cc | ||
![]() |
7242238a25 | ||
![]() |
ef26ee3f1f | ||
![]() |
584ff9933a | ||
![]() |
bb07776606 | ||
![]() |
07df50fbdc | ||
![]() |
fc731f28cb | ||
![]() |
6474d7ef00 | ||
![]() |
0b23bbbbb0 | ||
![]() |
21406b119c | ||
![]() |
2b51980904 | ||
![]() |
1865e228c4 | ||
![]() |
179a1e423e | ||
![]() |
bd8de5bf2d | ||
![]() |
7c8c7eedca | ||
![]() |
8c1957c03e | ||
![]() |
803ad69eb1 | ||
![]() |
29d784e5fa | ||
![]() |
58b2201710 | ||
![]() |
02605d577b | ||
![]() |
42de252c12 | ||
![]() |
6c3c06a571 | ||
![]() |
6a4573ce5a | ||
![]() |
e77128dfa2 | ||
![]() |
19df8e45ec | ||
![]() |
4c7a1f541c | ||
![]() |
295e36efa3 | ||
![]() |
3f47cc8d00 | ||
![]() |
4006020d78 | ||
![]() |
6d4fa96aad | ||
![]() |
85def2bfc7 | ||
![]() |
266bbad8cd | ||
![]() |
1e3b7a6df1 | ||
![]() |
00fe864321 | ||
![]() |
3df720e909 | ||
![]() |
02a6ec7b3c | ||
![]() |
d3c1f7a872 | ||
![]() |
84568b3454 | ||
![]() |
2721b4c10d |
6
.github/pull_request_template.md
vendored
Normal file
6
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<!--
|
||||
Remember that `spackbot` can help with your PR in multiple ways:
|
||||
- `@spackbot help` shows all the commands that are currently available
|
||||
- `@spackbot fix style` tries to push a commit to fix style issues in this PR
|
||||
- `@spackbot re-run pipeline` runs the pipelines again, if you have write access to the repository
|
||||
-->
|
2
.github/workflows/audit.yaml
vendored
2
.github/workflows/audit.yaml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
. share/spack/setup-env.sh
|
||||
$(which spack) audit packages
|
||||
$(which spack) audit externals
|
||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # @v2.1.0
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1 # @v2.1.0
|
||||
if: ${{ inputs.with_coverage == 'true' }}
|
||||
with:
|
||||
flags: unittests,audits
|
||||
|
6
.github/workflows/build-containers.yml
vendored
6
.github/workflows/build-containers.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # @v2
|
||||
|
||||
- uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c
|
||||
- uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
|
||||
id: docker_meta
|
||||
with:
|
||||
images: |
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226
|
||||
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d
|
||||
@@ -118,7 +118,5 @@ jobs:
|
||||
context: dockerfiles/${{ matrix.dockerfile[0] }}
|
||||
platforms: ${{ matrix.dockerfile[1] }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# For pull requests it's not necessary to checkout the code
|
||||
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50
|
||||
- uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a
|
||||
id: filter
|
||||
with:
|
||||
# See https://github.com/dorny/paths-filter/issues/56 for the syntax used below
|
||||
|
4
.github/workflows/style/requirements.txt
vendored
4
.github/workflows/style/requirements.txt
vendored
@@ -1,5 +1,5 @@
|
||||
black==23.12.1
|
||||
clingo==5.6.2
|
||||
black==24.2.0
|
||||
clingo==5.7.1
|
||||
flake8==7.0.0
|
||||
isort==5.13.2
|
||||
mypy==1.8.0
|
||||
|
8
.github/workflows/unit_tests.yaml
vendored
8
.github/workflows/unit_tests.yaml
vendored
@@ -91,7 +91,7 @@ jobs:
|
||||
UNIT_TEST_COVERAGE: ${{ matrix.python-version == '3.11' }}
|
||||
run: |
|
||||
share/spack/qa/run-unit-tests
|
||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1
|
||||
with:
|
||||
flags: unittests,linux,${{ matrix.concretizer }}
|
||||
# Test shell integration
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
COVERAGE: true
|
||||
run: |
|
||||
share/spack/qa/run-shell-tests
|
||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1
|
||||
with:
|
||||
flags: shelltests,linux
|
||||
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
SPACK_TEST_SOLVER: clingo
|
||||
run: |
|
||||
share/spack/qa/run-unit-tests
|
||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # @v2.1.0
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1 # @v2.1.0
|
||||
with:
|
||||
flags: unittests,linux,clingo
|
||||
# Run unit tests on MacOS
|
||||
@@ -216,6 +216,6 @@ jobs:
|
||||
$(which spack) solve zlib
|
||||
common_args=(--dist loadfile --tx '4*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python' -x)
|
||||
$(which spack) unit-test --verbose --cov --cov-config=pyproject.toml --cov-report=xml:coverage.xml "${common_args[@]}"
|
||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1
|
||||
with:
|
||||
flags: unittests,macos
|
||||
|
4
.github/workflows/windows_python.yml
vendored
4
.github/workflows/windows_python.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
./share/spack/qa/validate_last_exit.ps1
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1
|
||||
with:
|
||||
flags: unittests,windows
|
||||
unit-tests-cmd:
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
./share/spack/qa/validate_last_exit.ps1
|
||||
coverage combine -a
|
||||
coverage xml
|
||||
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
|
||||
- uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1
|
||||
with:
|
||||
flags: unittests,windows
|
||||
build-abseil:
|
||||
|
@@ -59,6 +59,7 @@ upload:
|
||||
apidoc:
|
||||
sphinx-apidoc -f -T -o . ../spack
|
||||
sphinx-apidoc -f -T -o . ../llnl
|
||||
./nosearch-api-docs # set :nosearch: at top of each file
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
|
@@ -1130,6 +1130,10 @@ A version specifier can also be a list of ranges and specific versions,
|
||||
separated by commas. For example, ``@1.0:1.5,=1.7.1`` matches any version
|
||||
in the range ``1.0:1.5`` and the specific version ``1.7.1``.
|
||||
|
||||
^^^^^^^^^^^^
|
||||
Git versions
|
||||
^^^^^^^^^^^^
|
||||
|
||||
For packages with a ``git`` attribute, ``git`` references
|
||||
may be specified instead of a numerical version i.e. branches, tags
|
||||
and commits. Spack will stage and build based off the ``git``
|
||||
|
@@ -199,6 +199,7 @@ def setup(sphinx):
|
||||
("py:class", "contextlib.contextmanager"),
|
||||
("py:class", "module"),
|
||||
("py:class", "_io.BufferedReader"),
|
||||
("py:class", "_io.BytesIO"),
|
||||
("py:class", "unittest.case.TestCase"),
|
||||
("py:class", "_frozen_importlib_external.SourceFileLoader"),
|
||||
("py:class", "clingo.Control"),
|
||||
|
@@ -357,91 +357,23 @@ If there is a hook that you would like and is missing, you can propose to add a
|
||||
``pre_install(spec)``
|
||||
"""""""""""""""""""""
|
||||
|
||||
A ``pre_install`` hook is run within an install subprocess, directly before
|
||||
the install starts. It expects a single argument of a spec, and is run in
|
||||
a multiprocessing subprocess. Note that if you see ``pre_install`` functions associated with packages these are not hooks
|
||||
as we have defined them here, but rather callback functions associated with
|
||||
a package install.
|
||||
A ``pre_install`` hook is run within the install subprocess, directly before the install starts.
|
||||
It expects a single argument of a spec.
|
||||
|
||||
|
||||
""""""""""""""""""""""
|
||||
``post_install(spec)``
|
||||
""""""""""""""""""""""
|
||||
"""""""""""""""""""""""""""""""""""""
|
||||
``post_install(spec, explicit=None)``
|
||||
"""""""""""""""""""""""""""""""""""""
|
||||
|
||||
A ``post_install`` hook is run within an install subprocess, directly after
|
||||
the install finishes, but before the build stage is removed. If you
|
||||
write one of these hooks, you should expect it to accept a spec as the only
|
||||
argument. This is run in a multiprocessing subprocess. This ``post_install`` is
|
||||
also seen in packages, but in this context not related to the hooks described
|
||||
here.
|
||||
A ``post_install`` hook is run within the install subprocess, directly after the install finishes,
|
||||
but before the build stage is removed and the spec is registered in the database. It expects two
|
||||
arguments: spec and an optional boolean indicating whether this spec is being installed explicitly.
|
||||
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
``pre_uninstall(spec)`` and ``post_uninstall(spec)``
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
""""""""""""""""""""""""""
|
||||
``on_install_start(spec)``
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
This hook is run at the beginning of ``lib/spack/spack/installer.py``,
|
||||
in the install function of a ``PackageInstaller``,
|
||||
and importantly is not part of a build process, but before it. This is when
|
||||
we have just newly grabbed the task, and are preparing to install. If you
|
||||
write a hook of this type, you should provide the spec to it.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_install_start(spec):
|
||||
"""On start of an install, we want to...
|
||||
"""
|
||||
print('on_install_start')
|
||||
|
||||
|
||||
""""""""""""""""""""""""""""
|
||||
``on_install_success(spec)``
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
This hook is run on a successful install, and is also run inside the build
|
||||
process, akin to ``post_install``. The main difference is that this hook
|
||||
is run outside of the context of the stage directory, meaning after the
|
||||
build stage has been removed and the user is alerted that the install was
|
||||
successful. If you need to write a hook that is run on success of a particular
|
||||
phase, you should use ``on_phase_success``.
|
||||
|
||||
""""""""""""""""""""""""""""
|
||||
``on_install_failure(spec)``
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
This hook is run given an install failure that happens outside of the build
|
||||
subprocess, but somewhere in ``installer.py`` when something else goes wrong.
|
||||
If you need to write a hook that is relevant to a failure within a build
|
||||
process, you would want to instead use ``on_phase_failure``.
|
||||
|
||||
|
||||
"""""""""""""""""""""""""""
|
||||
``on_install_cancel(spec)``
|
||||
"""""""""""""""""""""""""""
|
||||
|
||||
The same, but triggered if a spec install is cancelled for any reason.
|
||||
|
||||
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""
|
||||
``on_phase_success(pkg, phase_name, log_file)``
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
This hook is run within the install subprocess, and specifically when a phase
|
||||
successfully finishes. Since we are interested in the package, the name of
|
||||
the phase, and any output from it, we require:
|
||||
|
||||
- **pkg**: the package variable, which also has the attached spec at ``pkg.spec``
|
||||
- **phase_name**: the name of the phase that was successful (e.g., configure)
|
||||
- **log_file**: the path to the file with output, in case you need to inspect or otherwise interact with it.
|
||||
|
||||
"""""""""""""""""""""""""""""""""""""""""""""
|
||||
``on_phase_error(pkg, phase_name, log_file)``
|
||||
"""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
In the case of an error during a phase, we might want to trigger some event
|
||||
with a hook, and this is the purpose of this particular hook. Akin to
|
||||
``on_phase_success`` we require the same variables - the package that failed,
|
||||
the name of the phase, and the log file where we might find errors.
|
||||
These hooks are currently used for cleaning up module files after uninstall.
|
||||
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@@ -416,6 +416,23 @@ that git clone if ``foo`` is in the environment.
|
||||
Further development on ``foo`` can be tested by reinstalling the environment,
|
||||
and eventually committed and pushed to the upstream git repo.
|
||||
|
||||
If the package being developed supports out-of-source builds then users can use the
|
||||
``--build_directory`` flag to control the location and name of the build directory.
|
||||
This is a shortcut to set the ``package_attributes:build_directory`` in the
|
||||
``packages`` configuration (see :ref:`assigning-package-attributes`).
|
||||
The supplied location will become the build-directory for that package in all future builds.
|
||||
|
||||
.. warning::
|
||||
Potential pitfalls of setting the build directory
|
||||
Spack does not check for out-of-source build compatibility with the packages and
|
||||
so the onerous of making sure the package supports out-of-source builds is on
|
||||
the user.
|
||||
For example, most ``autotool`` and ``makefile`` packages do not support out-of-source builds
|
||||
while all ``CMake`` packages do.
|
||||
Understanding these nuances are on the software developers and we strongly encourage
|
||||
developers to only redirect the build directory if they understand their package's
|
||||
build-system.
|
||||
|
||||
^^^^^^^
|
||||
Loading
|
||||
^^^^^^^
|
||||
@@ -472,11 +489,11 @@ a ``packages.yaml`` file) could contain:
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
...
|
||||
# ...
|
||||
packages:
|
||||
all:
|
||||
compiler: [intel]
|
||||
...
|
||||
# ...
|
||||
|
||||
This configuration sets the default compiler for all packages to
|
||||
``intel``.
|
||||
@@ -822,7 +839,7 @@ directories.
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
...
|
||||
# ...
|
||||
view:
|
||||
mpis:
|
||||
root: /path/to/view
|
||||
@@ -866,7 +883,7 @@ automatically named ``default``, so that
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
...
|
||||
# ...
|
||||
view: True
|
||||
|
||||
is equivalent to
|
||||
@@ -874,7 +891,7 @@ is equivalent to
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
...
|
||||
# ...
|
||||
view:
|
||||
default:
|
||||
root: .spack-env/view
|
||||
@@ -884,7 +901,7 @@ and
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
...
|
||||
# ...
|
||||
view: /path/to/view
|
||||
|
||||
is equivalent to
|
||||
@@ -892,7 +909,7 @@ is equivalent to
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
...
|
||||
# ...
|
||||
view:
|
||||
default:
|
||||
root: /path/to/view
|
||||
|
@@ -623,7 +623,7 @@ Fortran.
|
||||
|
||||
compilers:
|
||||
- compiler:
|
||||
...
|
||||
# ...
|
||||
paths:
|
||||
cc: /usr/bin/clang
|
||||
cxx: /usr/bin/clang++
|
||||
|
@@ -10,7 +10,7 @@ Modules (modules.yaml)
|
||||
======================
|
||||
|
||||
The use of module systems to manage user environment in a controlled way
|
||||
is a common practice at HPC centers that is often embraced also by
|
||||
is a common practice at HPC centers that is sometimes embraced also by
|
||||
individual programmers on their development machines. To support this
|
||||
common practice Spack integrates with `Environment Modules
|
||||
<http://modules.sourceforge.net/>`_ and `Lmod
|
||||
@@ -21,14 +21,38 @@ Modules are one of several ways you can use Spack packages. For other
|
||||
options that may fit your use case better, you should also look at
|
||||
:ref:`spack load <spack-load>` and :ref:`environments <environments>`.
|
||||
|
||||
----------------------------
|
||||
Using module files via Spack
|
||||
----------------------------
|
||||
-----------
|
||||
Quick start
|
||||
-----------
|
||||
|
||||
If you have installed a supported module system you should be able to
|
||||
run ``module avail`` to see what module
|
||||
files have been installed. Here is sample output of those programs,
|
||||
showing lots of installed packages:
|
||||
In the current version of Spack, module files are not generated by default. To get started, you
|
||||
can generate module files for all currently installed packages by running either
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack module tcl refresh
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack module lmod refresh
|
||||
|
||||
Spack can also generate module files for all future installations automatically through the
|
||||
following configuration:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack config add modules:default:enable:[tcl]
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ spack config add modules:default:enable:[lmod]
|
||||
|
||||
Assuming you have a module system installed, you should now be able to use the ``module`` command
|
||||
to interact with them:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@@ -65,33 +89,17 @@ scheme used at your site.
|
||||
Module file customization
|
||||
-------------------------
|
||||
|
||||
Module files are generated by post-install hooks after the successful
|
||||
installation of a package.
|
||||
|
||||
.. note::
|
||||
|
||||
Spack only generates modulefiles when a package is installed. If
|
||||
you attempt to install a package and it is already installed, Spack
|
||||
will not regenerate modulefiles for the package. This may lead to
|
||||
inconsistent modulefiles if the Spack module configuration has
|
||||
changed since the package was installed, either by editing a file
|
||||
or changing scopes or environments.
|
||||
|
||||
Later in this section there is a subsection on :ref:`regenerating
|
||||
modules <cmd-spack-module-refresh>` that will allow you to bring
|
||||
your modules to a consistent state.
|
||||
|
||||
The table below summarizes the essential information associated with
|
||||
the different file formats that can be generated by Spack:
|
||||
|
||||
|
||||
+-----------------------------+--------------------+-------------------------------+----------------------------------------------+----------------------+
|
||||
| | **Hook name** | **Default root directory** | **Default template file** | **Compatible tools** |
|
||||
+=============================+====================+===============================+==============================================+======================+
|
||||
| **Tcl - Non-Hierarchical** | ``tcl`` | share/spack/modules | share/spack/templates/modules/modulefile.tcl | Env. Modules/Lmod |
|
||||
+-----------------------------+--------------------+-------------------------------+----------------------------------------------+----------------------+
|
||||
| **Lua - Hierarchical** | ``lmod`` | share/spack/lmod | share/spack/templates/modules/modulefile.lua | Lmod |
|
||||
+-----------------------------+--------------------+-------------------------------+----------------------------------------------+----------------------+
|
||||
+-----------+--------------+------------------------------+----------------------------------------------+----------------------+
|
||||
| | Hierarchical | **Default root directory** | **Default template file** | **Compatible tools** |
|
||||
+===========+==============+==============================+==============================================+======================+
|
||||
| ``tcl`` | No | share/spack/modules | share/spack/templates/modules/modulefile.tcl | Env. Modules/Lmod |
|
||||
+-----------+--------------+------------------------------+----------------------------------------------+----------------------+
|
||||
| ``lmod`` | Yes | share/spack/lmod | share/spack/templates/modules/modulefile.lua | Lmod |
|
||||
+-----------+--------------+------------------------------+----------------------------------------------+----------------------+
|
||||
|
||||
|
||||
Spack ships with sensible defaults for the generation of module files, but
|
||||
@@ -102,7 +110,7 @@ In general you can override or extend the default behavior by:
|
||||
2. writing specific rules in the ``modules.yaml`` configuration file
|
||||
3. writing your own templates to override or extend the defaults
|
||||
|
||||
The former method let you express changes in the run-time environment
|
||||
The former method lets you express changes in the run-time environment
|
||||
that are needed to use the installed software properly, e.g. injecting variables
|
||||
from language interpreters into their extensions. The latter two instead permit to
|
||||
fine tune the filesystem layout, content and creation of module files to meet
|
||||
@@ -110,79 +118,62 @@ site specific conventions.
|
||||
|
||||
.. _overide-api-calls-in-package-py:
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Override API calls in ``package.py``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Setting environment variables dynamically in ``package.py``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are two methods that you can override in any ``package.py`` to affect the
|
||||
content of the module files generated by Spack. The first one:
|
||||
There are two methods that you can implement in any ``package.py`` to dynamically affect the
|
||||
content of the module files generated by Spack. The most important one is
|
||||
``setup_run_environment``, which can be used to set environment variables in the module file that
|
||||
depend on the spec:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def setup_run_environment(self, env):
|
||||
pass
|
||||
if self.spec.satisfies("+foo"):
|
||||
env.set("FOO", "bar")
|
||||
|
||||
can alter the content of the module file associated with the same package where it is overridden.
|
||||
The second method:
|
||||
The second, less commonly used, is ``setup_dependent_run_environment(self, env, dependent_spec)``,
|
||||
which allows a dependency to set variables in the module file of its dependents. This is typically
|
||||
used in packages like ``python``, ``r``, or ``perl`` to prepend the dependent's prefix to the
|
||||
search path of the interpreter (``PYTHONPATH``, ``R_LIBS``, ``PERL5LIB`` resp.), so it can locate
|
||||
the packages at runtime.
|
||||
|
||||
For example, a simplified version of the ``python`` package could look like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def setup_dependent_run_environment(self, env, dependent_spec):
|
||||
pass
|
||||
if dependent_spec.package.extends(self.spec):
|
||||
env.prepend_path("PYTHONPATH", dependent_spec.prefix.lib.python)
|
||||
|
||||
can instead inject run-time environment modifications in the module files of packages
|
||||
that depend on it. In both cases you need to fill ``env`` with the desired
|
||||
list of environment modifications.
|
||||
|
||||
.. admonition:: The ``r`` package and callback APIs
|
||||
|
||||
An example in which it is crucial to override both methods
|
||||
is given by the ``r`` package. This package installs libraries and headers
|
||||
in non-standard locations and it is possible to prepend the appropriate directory
|
||||
to the corresponding environment variables:
|
||||
|
||||
================== =================================
|
||||
LD_LIBRARY_PATH ``self.prefix/rlib/R/lib``
|
||||
PKG_CONFIG_PATH ``self.prefix/rlib/pkgconfig``
|
||||
================== =================================
|
||||
|
||||
with the following snippet:
|
||||
|
||||
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/r/package.py
|
||||
:pyobject: R.setup_run_environment
|
||||
|
||||
The ``r`` package also knows which environment variable should be modified
|
||||
to make language extensions provided by other packages available, and modifies
|
||||
it appropriately in the override of the second method:
|
||||
|
||||
.. literalinclude:: _spack_root/var/spack/repos/builtin/packages/r/package.py
|
||||
:pyobject: R.setup_dependent_run_environment
|
||||
and would make any package that ``extends("python")`` have its library directory added to the
|
||||
``PYTHONPATH`` environment variable in the module file. It's much more convenient to set this
|
||||
variable here, than to repeat it in every Python extension's ``setup_run_environment`` method.
|
||||
|
||||
.. _modules-yaml:
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Write a configuration file
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
The ``modules.yaml`` config file and module sets
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The configuration files that control module generation behavior
|
||||
are named ``modules.yaml``. The default configuration:
|
||||
The configuration files that control module generation behavior are named ``modules.yaml``. The
|
||||
default configuration looks like this:
|
||||
|
||||
.. literalinclude:: _spack_root/etc/spack/defaults/modules.yaml
|
||||
:language: yaml
|
||||
|
||||
activates the hooks to generate ``tcl`` module files and inspects
|
||||
the installation folder of each package for the presence of a set of subdirectories
|
||||
(``bin``, ``man``, ``share/man``, etc.). If any is found its full path is prepended
|
||||
to the environment variables listed below the folder name.
|
||||
You can define one or more **module sets**, each of which can be configured separately with regard
|
||||
to install location, naming scheme, inclusion and exclusion, autoloading, et cetera.
|
||||
|
||||
Spack modules can be configured for multiple module sets. The default
|
||||
module set is named ``default``. All Spack commands which operate on
|
||||
modules default to apply the ``default`` module set, but can be
|
||||
applied to any module set in the configuration.
|
||||
The default module set is aptly named ``default``. All
|
||||
:ref:`Spack commands that operate on modules <maintaining-module-files>` apply to the ``default``
|
||||
module set, unless another module set is specified explicitly (with the ``--name`` flag).
|
||||
|
||||
"""""""""""""""""""""""""
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Changing the modules root
|
||||
"""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As shown in the table above, the default module root for ``lmod`` is
|
||||
``$spack/share/spack/lmod`` and the default root for ``tcl`` is
|
||||
@@ -198,7 +189,7 @@ set by changing the ``roots`` key of the configuration.
|
||||
my_custom_lmod_modules:
|
||||
roots:
|
||||
lmod: /path/to/install/custom/lmod/modules
|
||||
...
|
||||
# ...
|
||||
|
||||
This configuration will create two module sets. The default module set
|
||||
will install its ``tcl`` modules to ``/path/to/install/tcl/modules``
|
||||
@@ -224,25 +215,32 @@ location could be confusing to users of your modules. In the next
|
||||
section, we will discuss enabling and disabling module types (module
|
||||
file generators) for each module set.
|
||||
|
||||
""""""""""""""""""""
|
||||
Activate other hooks
|
||||
""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Automatically generating module files
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Any other module file generator shipped with Spack can be activated adding it to the
|
||||
list under the ``enable`` key in the module file. Currently the only generator that
|
||||
is not active by default is ``lmod``, which produces hierarchical lua module files.
|
||||
|
||||
Each module system can then be configured separately. In fact, you should list configuration
|
||||
options that affect a particular type of module files under a top level key corresponding
|
||||
to the generator being customized:
|
||||
Spack can be configured to automatically generate module files as part of package installation.
|
||||
This is done by adding the desired module systems to the ``enable`` list.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
modules:
|
||||
default:
|
||||
enable:
|
||||
- tcl
|
||||
- lmod
|
||||
- tcl
|
||||
- lmod
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Configuring ``tcl`` and ``lmod`` modules
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can configure the behavior of either module system separately, under a key corresponding to
|
||||
the generator being customized:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
modules:
|
||||
default:
|
||||
tcl:
|
||||
# contains environment modules specific customizations
|
||||
lmod:
|
||||
@@ -253,16 +251,70 @@ either change the layout of the module files on the filesystem, or they will aff
|
||||
their content. For the latter point it is possible to use anonymous specs
|
||||
to fine tune the set of packages on which the modifications should be applied.
|
||||
|
||||
.. _autoloading-dependencies:
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Autoloading and hiding dependencies
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A module file should set the variables that are needed for an application to work. But since an
|
||||
application often has many dependencies, where should all the environment variables for those be
|
||||
set? In Spack the rule is that each package sets the runtime variables that are needed by the
|
||||
package itself, and no more. This way, dependencies can be loaded standalone too, and duplication
|
||||
of environment variables is avoided.
|
||||
|
||||
That means however that if you want to use an application, you need to load the modules for all its
|
||||
dependencies. Of course this is not something you would want users to do manually.
|
||||
|
||||
Since Spack knows the dependency graph of every package, it can easily generate module files that
|
||||
automatically load the modules for its dependencies recursively. It is enabled by default for both
|
||||
Lmod and Environment Modules under the ``autoload: direct`` config option. The former system has
|
||||
builtin support through the ``depends_on`` function, the latter simply uses a ``module load``
|
||||
statement. Both module systems (at least in newer versions) do reference counting, so that if a
|
||||
module is loaded by two different modules, it will only be unloaded after the others are.
|
||||
|
||||
The ``autoload`` key accepts the values ``none``, ``direct``, and ``all``. To disable it, use
|
||||
``none``, and to enable, it's best to stick to ``direct``, which only autoloads the direct link and
|
||||
run type dependencies, relying on recursive autoloading to load the rest.
|
||||
|
||||
A common complaint about autoloading is the large number of modules that are visible to the user.
|
||||
Spack has a solution for this as well: ``hide_implicits: true``. This ensures that only those
|
||||
packages you've explicitly installed are exposed by ``module avail``, but still allows for
|
||||
autoloading of hidden dependencies. Lmod should support hiding implicits in general, while
|
||||
Environment Modules requires version 4.7 or higher.
|
||||
|
||||
.. note::
|
||||
If supported by your module system, we highly encourage the following configuration that enables
|
||||
autoloading and hiding of implicits. It ensures all runtime variables are set correctly,
|
||||
including those for dependencies, without overwhelming the user with a large number of available
|
||||
modules. Further, it makes it easier to get readable module names without collisions, see the
|
||||
section below on :ref:`modules-projections`.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
modules:
|
||||
default:
|
||||
tcl:
|
||||
hide_implicits: true
|
||||
all:
|
||||
autoload: direct
|
||||
lmod:
|
||||
hide_implicits: true
|
||||
all:
|
||||
autoload: direct
|
||||
|
||||
.. _anonymous_specs:
|
||||
|
||||
""""""""""""""""""""""""""""
|
||||
Selection by anonymous specs
|
||||
""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Setting environment variables for selected packages in config
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In the configuration file you can use *anonymous specs* (i.e. specs
|
||||
that **are not required to have a root package** and are thus used just
|
||||
to express constraints) to apply certain modifications on a selected set
|
||||
of the installed software. For instance, in the snippet below:
|
||||
In the configuration file you can filter particular specs, and make further changes to the
|
||||
environment variables that go into their module files. This is very powerful when you want to avoid
|
||||
:ref:`modifying the package itself <overide-api-calls-in-package-py>`, or when you want to set
|
||||
certain variables on multiple selected packages at once.
|
||||
|
||||
For instance, in the snippet below:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
@@ -305,12 +357,28 @@ the variable ``FOOBAR`` will be unset.
|
||||
.. note::
|
||||
Order does matter
|
||||
The modifications associated with the ``all`` keyword are always evaluated
|
||||
first, no matter where they appear in the configuration file. All the other
|
||||
spec constraints are instead evaluated top to bottom.
|
||||
first, no matter where they appear in the configuration file. All the other changes to
|
||||
environment variables for matching specs are evaluated from top to bottom.
|
||||
|
||||
""""""""""""""""""""""""""""""""""""""""""""
|
||||
.. warning::
|
||||
|
||||
As general advice, it's often better to set as few unnecessary variables as possible. For
|
||||
example, the following seemingly innocent and potentially useful configuration
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
all:
|
||||
environment:
|
||||
set:
|
||||
"{name}_ROOT": "{prefix}"
|
||||
|
||||
sets ``BINUTILS_ROOT`` to its prefix in modules for ``binutils``, which happens to break
|
||||
the ``gcc`` compiler: it uses this variable as its default search path for certain object
|
||||
files and libraries, and by merely setting it, everything fails to link.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Exclude or include specific module files
|
||||
""""""""""""""""""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can use anonymous specs also to prevent module files from being written or
|
||||
to force them to be written. Consider the case where you want to hide from users
|
||||
@@ -330,14 +398,19 @@ you will prevent the generation of module files for any package that
|
||||
is compiled with ``gcc@4.4.7``, with the only exception of any ``gcc``
|
||||
or any ``llvm`` installation.
|
||||
|
||||
It is safe to combine ``exclude`` and ``autoload``
|
||||
:ref:`mentioned above <autoloading-dependencies>`. When ``exclude`` prevents a module file to be
|
||||
generated for a dependency, the ``autoload`` feature will simply not generate a statement to load
|
||||
it.
|
||||
|
||||
|
||||
.. _modules-projections:
|
||||
|
||||
"""""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Customize the naming of modules
|
||||
"""""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The names of environment modules generated by spack are not always easy to
|
||||
The names of environment modules generated by Spack are not always easy to
|
||||
fully comprehend due to the long hash in the name. There are three module
|
||||
configuration options to help with that. The first is a global setting to
|
||||
adjust the hash length. It can be set anywhere from 0 to 32 and has a default
|
||||
@@ -353,6 +426,13 @@ shows how to set hash length in the module file names:
|
||||
tcl:
|
||||
hash_length: 7
|
||||
|
||||
.. tip::
|
||||
|
||||
Using ``hide_implicits: true`` (see :ref:`autoloading-dependencies`) vastly reduces the number
|
||||
modules exposed to the user. The hidden modules always contain the hash in their name, and are
|
||||
not influenced by the ``hash_length`` setting. Hidden implicits thus make it easier to use a
|
||||
short hash length or no hash at all, without risking name conflicts.
|
||||
|
||||
To help make module names more readable, and to help alleviate name conflicts
|
||||
with a short hash, one can use the ``suffixes`` option in the modules
|
||||
configuration file. This option will add strings to modules that match a spec.
|
||||
@@ -365,12 +445,12 @@ For instance, the following config options,
|
||||
tcl:
|
||||
all:
|
||||
suffixes:
|
||||
^python@2.7.12: 'python-2.7.12'
|
||||
^python@3.12: 'python-3.12'
|
||||
^openblas: 'openblas'
|
||||
|
||||
will add a ``python-2.7.12`` version string to any packages compiled with
|
||||
python matching the spec, ``python@2.7.12``. This is useful to know which
|
||||
version of python a set of python extensions is associated with. Likewise, the
|
||||
will add a ``python-3.12`` version string to any packages compiled with
|
||||
Python matching the spec, ``python@3.12``. This is useful to know which
|
||||
version of Python a set of Python extensions is associated with. Likewise, the
|
||||
``openblas`` string is attached to any program that has openblas in the spec,
|
||||
most likely via the ``+blas`` variant specification.
|
||||
|
||||
@@ -468,41 +548,11 @@ that are already in the Lmod hierarchy.
|
||||
For hierarchies that are deeper than three layers ``lmod spider`` may have some issues.
|
||||
See `this discussion on the Lmod project <https://github.com/TACC/Lmod/issues/114>`_.
|
||||
|
||||
""""""""""""""""""""""
|
||||
Select default modules
|
||||
""""""""""""""""""""""
|
||||
|
||||
By default, when multiple modules of the same name share a directory,
|
||||
the highest version number will be the default module. This behavior
|
||||
of the ``module`` command can be overridden with a symlink named
|
||||
``default`` to the desired default module. If you wish to configure
|
||||
default modules with Spack, add a ``defaults`` key to your modules
|
||||
configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
modules:
|
||||
my-module-set:
|
||||
tcl:
|
||||
defaults:
|
||||
- gcc@10.2.1
|
||||
- hdf5@1.2.10+mpi+hl%gcc
|
||||
|
||||
These defaults may be arbitrarily specific. For any package that
|
||||
satisfies a default, Spack will generate the module file in the
|
||||
appropriate path, and will generate a default symlink to the module
|
||||
file as well.
|
||||
|
||||
.. warning::
|
||||
If Spack is configured to generate multiple default packages in the
|
||||
same directory, the last modulefile to be generated will be the
|
||||
default module.
|
||||
|
||||
.. _customize-env-modifications:
|
||||
|
||||
"""""""""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Customize environment modifications
|
||||
"""""""""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can control which prefixes in a Spack package are added to
|
||||
environment variables with the ``prefix_inspections`` section; this
|
||||
@@ -600,9 +650,9 @@ stack to users who are likely to inspect the modules to find full
|
||||
paths to software, when it is desirable to present the users with a
|
||||
simpler set of paths than those generated by the Spack install tree.
|
||||
|
||||
""""""""""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Filter out environment modifications
|
||||
""""""""""""""""""""""""""""""""""""
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Modifications to certain environment variables in module files are there by
|
||||
default, for instance because they are generated by prefix inspections.
|
||||
@@ -622,49 +672,37 @@ do so by using the ``exclude_env_vars``:
|
||||
The configuration above will generate module files that will not contain
|
||||
modifications to either ``CPATH`` or ``LIBRARY_PATH``.
|
||||
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Select default modules
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. _autoloading-dependencies:
|
||||
|
||||
"""""""""""""""""""""
|
||||
Autoload dependencies
|
||||
"""""""""""""""""""""
|
||||
|
||||
Often it is required for a module to have its (transient) dependencies loaded as well.
|
||||
One example where this is useful is when one package needs to use executables provided
|
||||
by its dependency; when the dependency is autoloaded, the executable will be in the
|
||||
PATH. Similarly for scripting languages such as Python, packages and their dependencies
|
||||
have to be loaded together.
|
||||
|
||||
Autoloading is enabled by default for Lmod and Environment Modules. The former
|
||||
has builtin support for through the ``depends_on`` function. The latter uses
|
||||
``module load`` statement to load and track dependencies.
|
||||
|
||||
Autoloading can also be enabled conditionally:
|
||||
By default, when multiple modules of the same name share a directory,
|
||||
the highest version number will be the default module. This behavior
|
||||
of the ``module`` command can be overridden with a symlink named
|
||||
``default`` to the desired default module. If you wish to configure
|
||||
default modules with Spack, add a ``defaults`` key to your modules
|
||||
configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
modules:
|
||||
default:
|
||||
tcl:
|
||||
all:
|
||||
autoload: none
|
||||
^python:
|
||||
autoload: direct
|
||||
modules:
|
||||
my-module-set:
|
||||
tcl:
|
||||
defaults:
|
||||
- gcc@10.2.1
|
||||
- hdf5@1.2.10+mpi+hl%gcc
|
||||
|
||||
The configuration file above will produce module files that will
|
||||
load their direct dependencies if the package installed depends on ``python``.
|
||||
The allowed values for the ``autoload`` statement are either ``none``,
|
||||
``direct`` or ``all``.
|
||||
These defaults may be arbitrarily specific. For any package that
|
||||
satisfies a default, Spack will generate the module file in the
|
||||
appropriate path, and will generate a default symlink to the module
|
||||
file as well.
|
||||
|
||||
.. note::
|
||||
Tcl prerequisites
|
||||
In the ``tcl`` section of the configuration file it is possible to use
|
||||
the ``prerequisites`` directive that accepts the same values as
|
||||
``autoload``. It will produce module files that have a ``prereq``
|
||||
statement, which autoloads dependencies on Environment Modules when its
|
||||
``auto_handling`` configuration option is enabled. If Environment Modules
|
||||
is installed with Spack, ``auto_handling`` is enabled by default starting
|
||||
version 4.2. Otherwise it is enabled by default since version 5.0.
|
||||
.. warning::
|
||||
If Spack is configured to generate multiple default packages in the
|
||||
same directory, the last modulefile to be generated will be the
|
||||
default module.
|
||||
|
||||
.. _maintaining-module-files:
|
||||
|
||||
------------------------
|
||||
Maintaining Module Files
|
||||
|
6
lib/spack/docs/nosearch-api-docs
Executable file
6
lib/spack/docs/nosearch-api-docs
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Set :nosearch: at top of each api doc file
|
||||
for filename in {spack,llnl}.*.rst; do
|
||||
$(echo ":nosearch:"; cat $filename) > $filename
|
||||
done
|
@@ -647,6 +647,8 @@ manually placed files within the install prefix are owned by the
|
||||
assigned group. If no group is assigned, Spack will allow the OS
|
||||
default behavior to go as expected.
|
||||
|
||||
.. _assigning-package-attributes:
|
||||
|
||||
----------------------------
|
||||
Assigning Package Attributes
|
||||
----------------------------
|
||||
@@ -657,10 +659,11 @@ You can assign class-level attributes in the configuration:
|
||||
|
||||
packages:
|
||||
mpileaks:
|
||||
# Override existing attributes
|
||||
url: http://www.somewhereelse.com/mpileaks-1.0.tar.gz
|
||||
# ... or add new ones
|
||||
x: 1
|
||||
package_attributes:
|
||||
# Override existing attributes
|
||||
url: http://www.somewhereelse.com/mpileaks-1.0.tar.gz
|
||||
# ... or add new ones
|
||||
x: 1
|
||||
|
||||
Attributes set this way will be accessible to any method executed
|
||||
in the package.py file (e.g. the ``install()`` method). Values for these
|
||||
|
@@ -810,7 +810,7 @@ generated by ``spack ci generate``. You also want your generated rebuild jobs
|
||||
.. code-block:: yaml
|
||||
|
||||
spack:
|
||||
...
|
||||
# ...
|
||||
ci:
|
||||
pipeline-gen:
|
||||
- build-job:
|
||||
|
@@ -17,7 +17,7 @@ experimental software separately from the built-in repository. Spack
|
||||
allows you to configure local repositories using either the
|
||||
``repos.yaml`` or the ``spack repo`` command.
|
||||
|
||||
A package repository a directory structured like this::
|
||||
A package repository is a directory structured like this::
|
||||
|
||||
repo/
|
||||
repo.yaml
|
||||
|
@@ -2,12 +2,12 @@ sphinx==7.2.6
|
||||
sphinxcontrib-programoutput==0.17
|
||||
sphinx_design==0.5.0
|
||||
sphinx-rtd-theme==2.0.0
|
||||
python-levenshtein==0.23.0
|
||||
python-levenshtein==0.25.0
|
||||
docutils==0.20.1
|
||||
pygments==2.17.2
|
||||
urllib3==2.1.0
|
||||
pytest==7.4.4
|
||||
urllib3==2.2.1
|
||||
pytest==8.0.2
|
||||
isort==5.13.2
|
||||
black==23.12.1
|
||||
black==24.2.0
|
||||
flake8==7.0.0
|
||||
mypy==1.8.0
|
||||
|
@@ -171,7 +171,7 @@ def polite_path(components: Iterable[str]):
|
||||
@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_.-]")
|
||||
return re.compile(r"[^A-Za-z0-9_+.-]")
|
||||
|
||||
|
||||
def polite_filename(filename: str) -> str:
|
||||
@@ -920,29 +920,35 @@ def get_filetype(path_name):
|
||||
return output.strip()
|
||||
|
||||
|
||||
@system_path_filter
|
||||
def is_nonsymlink_exe_with_shebang(path):
|
||||
"""
|
||||
Returns whether the path is an executable script with a shebang.
|
||||
Return False when the path is a *symlink* to an executable script.
|
||||
"""
|
||||
def has_shebang(path):
|
||||
"""Returns whether a path has a shebang line. Returns False if the file cannot be opened."""
|
||||
try:
|
||||
st = os.lstat(path)
|
||||
# Should not be a symlink
|
||||
if stat.S_ISLNK(st.st_mode):
|
||||
return False
|
||||
|
||||
# Should be executable
|
||||
if not st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
|
||||
return False
|
||||
|
||||
# Should start with a shebang
|
||||
with open(path, "rb") as f:
|
||||
return f.read(2) == b"#!"
|
||||
except (IOError, OSError):
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
@system_path_filter
|
||||
def is_nonsymlink_exe_with_shebang(path):
|
||||
"""Returns whether the path is an executable regular file with a shebang. Returns False too
|
||||
when the path is a symlink to a script, and also when the file cannot be opened."""
|
||||
try:
|
||||
st = os.lstat(path)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
# Should not be a symlink
|
||||
if stat.S_ISLNK(st.st_mode):
|
||||
return False
|
||||
|
||||
# Should be executable
|
||||
if not st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
|
||||
return False
|
||||
|
||||
return has_shebang(path)
|
||||
|
||||
|
||||
@system_path_filter(arg_slice=slice(1))
|
||||
def chgrp_if_not_world_writable(path, group):
|
||||
"""chgrp path to group if path is not world writable"""
|
||||
@@ -1234,6 +1240,47 @@ def get_single_file(directory):
|
||||
return fnames[0]
|
||||
|
||||
|
||||
@system_path_filter
|
||||
def windows_sfn(path: os.PathLike):
|
||||
"""Returns 8.3 Filename (SFN) representation of
|
||||
path
|
||||
|
||||
8.3 Filenames (SFN or short filename) is a file
|
||||
naming convention used prior to Win95 that Windows
|
||||
still (and will continue to) support. This convention
|
||||
caps filenames at 8 characters, and most importantly
|
||||
does not allow for spaces in addition to other specifications.
|
||||
The scheme is generally the same as a normal Windows
|
||||
file scheme, but all spaces are removed and the filename
|
||||
is capped at 6 characters. The remaining characters are
|
||||
replaced with ~N where N is the number file in a directory
|
||||
that a given file represents i.e. Program Files and Program Files (x86)
|
||||
would be PROGRA~1 and PROGRA~2 respectively.
|
||||
Further, all file/directory names are all caps (although modern Windows
|
||||
is case insensitive in practice).
|
||||
Conversion is accomplished by fileapi.h GetShortPathNameW
|
||||
|
||||
Returns paths in 8.3 Filename form
|
||||
|
||||
Note: this method is a no-op on Linux
|
||||
|
||||
Args:
|
||||
path: Path to be transformed into SFN (8.3 filename) format
|
||||
"""
|
||||
# This should not be run-able on linux/macos
|
||||
if sys.platform != "win32":
|
||||
return path
|
||||
path = str(path)
|
||||
import ctypes
|
||||
|
||||
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
# stub Windows types TCHAR[LENGTH]
|
||||
TCHAR_arr = ctypes.c_wchar * len(path)
|
||||
ret_str = TCHAR_arr()
|
||||
k32.GetShortPathNameW(path, ret_str, len(path))
|
||||
return ret_str.value
|
||||
|
||||
|
||||
@contextmanager
|
||||
def temp_cwd():
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
@@ -1377,120 +1424,89 @@ def traverse_tree(
|
||||
yield (source_path, dest_path)
|
||||
|
||||
|
||||
def lexists_islink_isdir(path):
|
||||
"""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."""
|
||||
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.
|
||||
try:
|
||||
lst = os.lstat(path)
|
||||
except (IOError, OSError):
|
||||
return False, False, False
|
||||
|
||||
is_link = stat.S_ISLNK(lst.st_mode)
|
||||
|
||||
# Check whether file is a dir.
|
||||
if not is_link:
|
||||
is_dir = stat.S_ISDIR(lst.st_mode)
|
||||
return True, is_link, is_dir
|
||||
|
||||
# Check whether symlink points to a dir.
|
||||
try:
|
||||
st = os.stat(path)
|
||||
is_dir = stat.S_ISDIR(st.st_mode)
|
||||
except (IOError, OSError):
|
||||
# Dangling symlink (i.e. it lexists but not exists)
|
||||
is_dir = False
|
||||
|
||||
return True, is_link, is_dir
|
||||
|
||||
|
||||
class BaseDirectoryVisitor:
|
||||
"""Base class and interface for :py:func:`visit_directory_tree`."""
|
||||
|
||||
def visit_file(self, root, rel_path, depth):
|
||||
def visit_file(self, root: str, rel_path: str, depth: int) -> None:
|
||||
"""Handle the non-symlink file at ``os.path.join(root, rel_path)``
|
||||
|
||||
Parameters:
|
||||
root (str): root directory
|
||||
rel_path (str): relative path to current file from ``root``
|
||||
root: root directory
|
||||
rel_path: relative path to current file from ``root``
|
||||
depth (int): depth of current file from the ``root`` directory"""
|
||||
pass
|
||||
|
||||
def visit_symlinked_file(self, root, rel_path, depth):
|
||||
"""Handle the symlink to a file at ``os.path.join(root, rel_path)``.
|
||||
Note: ``rel_path`` is the location of the symlink, not to what it is
|
||||
pointing to. The symlink may be dangling.
|
||||
def visit_symlinked_file(self, root: str, rel_path: str, depth) -> None:
|
||||
"""Handle the symlink to a file at ``os.path.join(root, rel_path)``. Note: ``rel_path`` is
|
||||
the location of the symlink, not to what it is pointing to. The symlink may be dangling.
|
||||
|
||||
Parameters:
|
||||
root (str): root directory
|
||||
rel_path (str): relative path to current symlink from ``root``
|
||||
depth (int): depth of current symlink from the ``root`` directory"""
|
||||
root: root directory
|
||||
rel_path: relative path to current symlink from ``root``
|
||||
depth: depth of current symlink from the ``root`` directory"""
|
||||
pass
|
||||
|
||||
def before_visit_dir(self, root, rel_path, depth):
|
||||
def before_visit_dir(self, root: str, rel_path: str, depth: int) -> bool:
|
||||
"""Return True from this function to recurse into the directory at
|
||||
os.path.join(root, rel_path). Return False in order not to recurse further.
|
||||
|
||||
Parameters:
|
||||
root (str): root directory
|
||||
rel_path (str): relative path to current directory from ``root``
|
||||
depth (int): depth of current directory from the ``root`` directory
|
||||
root: root directory
|
||||
rel_path: relative path to current directory from ``root``
|
||||
depth: depth of current directory from the ``root`` directory
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when the directory should be recursed into. ``False`` when
|
||||
not"""
|
||||
return False
|
||||
|
||||
def before_visit_symlinked_dir(self, root, rel_path, depth):
|
||||
"""Return ``True`` to recurse into the symlinked directory and ``False`` in
|
||||
order not to. Note: ``rel_path`` is the path to the symlink itself.
|
||||
Following symlinked directories blindly can cause infinite recursion due to
|
||||
cycles.
|
||||
def before_visit_symlinked_dir(self, root: str, rel_path: str, depth: int) -> bool:
|
||||
"""Return ``True`` to recurse into the symlinked directory and ``False`` in order not to.
|
||||
Note: ``rel_path`` is the path to the symlink itself. Following symlinked directories
|
||||
blindly can cause infinite recursion due to cycles.
|
||||
|
||||
Parameters:
|
||||
root (str): root directory
|
||||
rel_path (str): relative path to current symlink from ``root``
|
||||
depth (int): depth of current symlink from the ``root`` directory
|
||||
root: root directory
|
||||
rel_path: relative path to current symlink from ``root``
|
||||
depth: depth of current symlink from the ``root`` directory
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when the directory should be recursed into. ``False`` when
|
||||
not"""
|
||||
return False
|
||||
|
||||
def after_visit_dir(self, root, rel_path, depth):
|
||||
"""Called after recursion into ``rel_path`` finished. This function is not
|
||||
called when ``rel_path`` was not recursed into.
|
||||
def after_visit_dir(self, root: str, rel_path: str, depth: int) -> None:
|
||||
"""Called after recursion into ``rel_path`` finished. This function is not called when
|
||||
``rel_path`` was not recursed into.
|
||||
|
||||
Parameters:
|
||||
root (str): root directory
|
||||
rel_path (str): relative path to current directory from ``root``
|
||||
depth (int): depth of current directory from the ``root`` directory"""
|
||||
root: root directory
|
||||
rel_path: relative path to current directory from ``root``
|
||||
depth: depth of current directory from the ``root`` directory"""
|
||||
pass
|
||||
|
||||
def after_visit_symlinked_dir(self, root, rel_path, depth):
|
||||
"""Called after recursion into ``rel_path`` finished. This function is not
|
||||
called when ``rel_path`` was not recursed into.
|
||||
def after_visit_symlinked_dir(self, root: str, rel_path: str, depth: int) -> None:
|
||||
"""Called after recursion into ``rel_path`` finished. This function is not called when
|
||||
``rel_path`` was not recursed into.
|
||||
|
||||
Parameters:
|
||||
root (str): root directory
|
||||
rel_path (str): relative path to current symlink from ``root``
|
||||
depth (int): depth of current symlink from the ``root`` directory"""
|
||||
root: root directory
|
||||
rel_path: relative path to current symlink from ``root``
|
||||
depth: depth of current symlink from the ``root`` directory"""
|
||||
pass
|
||||
|
||||
|
||||
def visit_directory_tree(root, visitor, rel_path="", depth=0):
|
||||
"""Recurses the directory root depth-first through a visitor pattern using the
|
||||
interface from :py:class:`BaseDirectoryVisitor`
|
||||
def visit_directory_tree(
|
||||
root: str, visitor: BaseDirectoryVisitor, rel_path: str = "", depth: int = 0
|
||||
):
|
||||
"""Recurses the directory root depth-first through a visitor pattern using the interface from
|
||||
:py:class:`BaseDirectoryVisitor`
|
||||
|
||||
Parameters:
|
||||
root (str): path of directory to recurse into
|
||||
visitor (BaseDirectoryVisitor): what visitor to use
|
||||
rel_path (str): current relative path from the root
|
||||
depth (str): current depth from the root
|
||||
root: path of directory to recurse into
|
||||
visitor: what visitor to use
|
||||
rel_path: current relative path from the root
|
||||
depth: current depth from the root
|
||||
"""
|
||||
dir = os.path.join(root, rel_path)
|
||||
dir_entries = sorted(os.scandir(dir), key=lambda d: d.name)
|
||||
@@ -1498,26 +1514,19 @@ def visit_directory_tree(root, visitor, rel_path="", depth=0):
|
||||
for f in dir_entries:
|
||||
rel_child = os.path.join(rel_path, f.name)
|
||||
islink = f.is_symlink()
|
||||
# On Windows, symlinks to directories are distinct from
|
||||
# symlinks to files, and it is possible to create a
|
||||
# broken symlink to a directory (e.g. using os.symlink
|
||||
# without `target_is_directory=True`), invoking `isdir`
|
||||
# on a symlink on Windows that is broken in this manner
|
||||
# will result in an error. In this case we can work around
|
||||
# the issue by reading the target and resolving the
|
||||
# directory ourselves
|
||||
# On Windows, symlinks to directories are distinct from symlinks to files, and it is
|
||||
# possible to create a broken symlink to a directory (e.g. using os.symlink without
|
||||
# `target_is_directory=True`), invoking `isdir` on a symlink on Windows that is broken in
|
||||
# this manner will result in an error. In this case we can work around the issue by reading
|
||||
# the target and resolving the directory ourselves
|
||||
try:
|
||||
isdir = f.is_dir()
|
||||
except OSError as e:
|
||||
if sys.platform == "win32" and hasattr(e, "winerror") and e.winerror == 5 and islink:
|
||||
# if path is a symlink, determine destination and
|
||||
# evaluate file vs directory
|
||||
# if path is a symlink, determine destination and evaluate file vs directory
|
||||
link_target = resolve_link_target_relative_to_the_link(f)
|
||||
# link_target might be relative but
|
||||
# resolve_link_target_relative_to_the_link
|
||||
# will ensure that if so, that it is relative
|
||||
# to the CWD and therefore
|
||||
# makes sense
|
||||
# link_target might be relative but resolve_link_target_relative_to_the_link
|
||||
# will ensure that if so, that it is relative to the CWD and therefore makes sense
|
||||
isdir = os.path.isdir(link_target)
|
||||
else:
|
||||
raise e
|
||||
|
@@ -8,7 +8,7 @@
|
||||
import filecmp
|
||||
import os
|
||||
import shutil
|
||||
from collections import OrderedDict
|
||||
from typing import Callable, Dict, List, Optional, Tuple
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import BaseDirectoryVisitor, mkdirp, touch, traverse_tree
|
||||
@@ -51,32 +51,32 @@ class SourceMergeVisitor(BaseDirectoryVisitor):
|
||||
- A list of merge conflicts in dst/
|
||||
"""
|
||||
|
||||
def __init__(self, ignore=None):
|
||||
def __init__(self, ignore: Optional[Callable[[str], bool]] = None):
|
||||
self.ignore = ignore if ignore is not None else lambda f: False
|
||||
|
||||
# When mapping <src root> to <dst root>/<projection>, we need
|
||||
# to prepend the <projection> bit to the relative path in the
|
||||
# destination dir.
|
||||
self.projection = ""
|
||||
# When mapping <src root> to <dst root>/<projection>, we need to prepend the <projection>
|
||||
# bit to the relative path in the destination dir.
|
||||
self.projection: str = ""
|
||||
|
||||
# When a file blocks another file, the conflict can sometimes
|
||||
# be resolved / ignored (e.g. <prefix>/LICENSE or
|
||||
# or <site-packages>/<namespace>/__init__.py conflicts can be
|
||||
# ignored).
|
||||
self.file_conflicts = []
|
||||
# Two files f and g conflict if they are not os.path.samefile(f, g) and they are both
|
||||
# projected to the same destination file. These conflicts are not necessarily fatal, and
|
||||
# can be resolved or ignored. For example <prefix>/LICENSE or
|
||||
# <site-packages>/<namespace>/__init__.py conflicts can be ignored).
|
||||
self.file_conflicts: List[MergeConflict] = []
|
||||
|
||||
# When we have to create a dir where a file is, or a file
|
||||
# where a dir is, we have fatal errors, listed here.
|
||||
self.fatal_conflicts = []
|
||||
# When we have to create a dir where a file is, or a file where a dir is, we have fatal
|
||||
# errors, listed here.
|
||||
self.fatal_conflicts: List[MergeConflict] = []
|
||||
|
||||
# What directories we have to make; this is an ordered set,
|
||||
# so that we have a fast lookup and can run mkdir in order.
|
||||
self.directories = OrderedDict()
|
||||
# What directories we have to make; this is an ordered dict, so that we have a fast lookup
|
||||
# and can run mkdir in order.
|
||||
self.directories: Dict[str, Tuple[str, str]] = {}
|
||||
|
||||
# Files to link. Maps dst_rel to (src_root, src_rel)
|
||||
self.files = OrderedDict()
|
||||
# Files to link. Maps dst_rel to (src_root, src_rel). This is an ordered dict, where files
|
||||
# are guaranteed to be grouped by src_root in the order they were visited.
|
||||
self.files: Dict[str, Tuple[str, str]] = {}
|
||||
|
||||
def before_visit_dir(self, root, rel_path, depth):
|
||||
def before_visit_dir(self, root: str, rel_path: str, depth: int) -> bool:
|
||||
"""
|
||||
Register a directory if dst / rel_path is not blocked by a file or ignored.
|
||||
"""
|
||||
@@ -104,7 +104,7 @@ def before_visit_dir(self, root, rel_path, depth):
|
||||
self.directories[proj_rel_path] = (root, rel_path)
|
||||
return True
|
||||
|
||||
def before_visit_symlinked_dir(self, root, rel_path, depth):
|
||||
def before_visit_symlinked_dir(self, root: str, rel_path: str, depth: int) -> bool:
|
||||
"""
|
||||
Replace symlinked dirs with actual directories when possible in low depths,
|
||||
otherwise handle it as a file (i.e. we link to the symlink).
|
||||
@@ -136,40 +136,56 @@ def before_visit_symlinked_dir(self, root, rel_path, depth):
|
||||
self.visit_file(root, rel_path, depth)
|
||||
return False
|
||||
|
||||
def visit_file(self, root, rel_path, depth):
|
||||
def visit_file(self, root: str, rel_path: str, depth: int, *, symlink: bool = False) -> None:
|
||||
proj_rel_path = os.path.join(self.projection, rel_path)
|
||||
|
||||
if self.ignore(rel_path):
|
||||
pass
|
||||
elif proj_rel_path in self.directories:
|
||||
# Can't create a file where a dir is; fatal error
|
||||
src_a_root, src_a_relpath = self.directories[proj_rel_path]
|
||||
self.fatal_conflicts.append(
|
||||
MergeConflict(
|
||||
dst=proj_rel_path,
|
||||
src_a=os.path.join(src_a_root, src_a_relpath),
|
||||
src_a=os.path.join(*self.directories[proj_rel_path]),
|
||||
src_b=os.path.join(root, rel_path),
|
||||
)
|
||||
)
|
||||
elif proj_rel_path in self.files:
|
||||
# In some cases we can resolve file-file conflicts
|
||||
src_a_root, src_a_relpath = self.files[proj_rel_path]
|
||||
self.file_conflicts.append(
|
||||
MergeConflict(
|
||||
dst=proj_rel_path,
|
||||
src_a=os.path.join(src_a_root, src_a_relpath),
|
||||
src_b=os.path.join(root, rel_path),
|
||||
# When two files project to the same path, they conflict iff they are distinct.
|
||||
# If they are the same (i.e. one links to the other), register regular files rather
|
||||
# than symlinks. The reason is that in copy-type views, we need a copy of the actual
|
||||
# file, not the symlink.
|
||||
|
||||
src_a = os.path.join(*self.files[proj_rel_path])
|
||||
src_b = os.path.join(root, rel_path)
|
||||
|
||||
try:
|
||||
samefile = os.path.samefile(src_a, src_b)
|
||||
except OSError:
|
||||
samefile = False
|
||||
|
||||
if not samefile:
|
||||
# Distinct files produce a conflict.
|
||||
self.file_conflicts.append(
|
||||
MergeConflict(dst=proj_rel_path, src_a=src_a, src_b=src_b)
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if not symlink:
|
||||
# Remove the link in favor of the actual file. The del is necessary to maintain the
|
||||
# order of the files dict, which is grouped by root.
|
||||
del self.files[proj_rel_path]
|
||||
self.files[proj_rel_path] = (root, rel_path)
|
||||
|
||||
else:
|
||||
# Otherwise register this file to be linked.
|
||||
self.files[proj_rel_path] = (root, rel_path)
|
||||
|
||||
def visit_symlinked_file(self, root, rel_path, depth):
|
||||
def visit_symlinked_file(self, root: str, rel_path: str, depth: int) -> None:
|
||||
# Treat symlinked files as ordinary files (without "dereferencing")
|
||||
self.visit_file(root, rel_path, depth)
|
||||
self.visit_file(root, rel_path, depth, symlink=True)
|
||||
|
||||
def set_projection(self, projection):
|
||||
def set_projection(self, projection: str) -> None:
|
||||
self.projection = os.path.normpath(projection)
|
||||
|
||||
# Todo, is this how to check in general for empty projection?
|
||||
@@ -197,24 +213,19 @@ def set_projection(self, projection):
|
||||
|
||||
|
||||
class DestinationMergeVisitor(BaseDirectoryVisitor):
|
||||
"""DestinatinoMergeVisitor takes a SourceMergeVisitor
|
||||
and:
|
||||
"""DestinatinoMergeVisitor takes a SourceMergeVisitor and:
|
||||
|
||||
a. registers additional conflicts when merging
|
||||
to the destination prefix
|
||||
b. removes redundant mkdir operations when
|
||||
directories already exist in the destination
|
||||
prefix.
|
||||
a. registers additional conflicts when merging to the destination prefix
|
||||
b. removes redundant mkdir operations when directories already exist in the destination prefix.
|
||||
|
||||
This also makes sure that symlinked directories
|
||||
in the target prefix will never be merged with
|
||||
This also makes sure that symlinked directories in the target prefix will never be merged with
|
||||
directories in the sources directories.
|
||||
"""
|
||||
|
||||
def __init__(self, source_merge_visitor):
|
||||
def __init__(self, source_merge_visitor: SourceMergeVisitor):
|
||||
self.src = source_merge_visitor
|
||||
|
||||
def before_visit_dir(self, root, rel_path, depth):
|
||||
def before_visit_dir(self, root: str, rel_path: str, depth: int) -> bool:
|
||||
# If destination dir is a file in a src dir, add a conflict,
|
||||
# and don't traverse deeper
|
||||
if rel_path in self.src.files:
|
||||
@@ -236,7 +247,7 @@ def before_visit_dir(self, root, rel_path, depth):
|
||||
# don't descend into it.
|
||||
return False
|
||||
|
||||
def before_visit_symlinked_dir(self, root, rel_path, depth):
|
||||
def before_visit_symlinked_dir(self, root: str, rel_path: str, depth: int) -> bool:
|
||||
"""
|
||||
Symlinked directories in the destination prefix should
|
||||
be seen as files; we should not accidentally merge
|
||||
@@ -262,7 +273,7 @@ def before_visit_symlinked_dir(self, root, rel_path, depth):
|
||||
# Never descend into symlinked target dirs.
|
||||
return False
|
||||
|
||||
def visit_file(self, root, rel_path, depth):
|
||||
def visit_file(self, root: str, rel_path: str, depth: int) -> None:
|
||||
# Can't merge a file if target already exists
|
||||
if rel_path in self.src.directories:
|
||||
src_a_root, src_a_relpath = self.src.directories[rel_path]
|
||||
@@ -280,7 +291,7 @@ def visit_file(self, root, rel_path, depth):
|
||||
)
|
||||
)
|
||||
|
||||
def visit_symlinked_file(self, root, rel_path, depth):
|
||||
def visit_symlinked_file(self, root: str, rel_path: str, depth: int) -> None:
|
||||
# Treat symlinked files as ordinary files (without "dereferencing")
|
||||
self.visit_file(root, rel_path, depth)
|
||||
|
||||
|
@@ -189,6 +189,7 @@ def _windows_can_symlink() -> bool:
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
fs.touchp(fpath)
|
||||
fs.mkdirp(dpath)
|
||||
|
||||
try:
|
||||
os.symlink(dpath, dlink)
|
||||
|
@@ -244,7 +244,7 @@ def _search_duplicate_specs_in_externals(error_cls):
|
||||
+ lines
|
||||
+ ["as they might result in non-deterministic hashes"]
|
||||
)
|
||||
except TypeError:
|
||||
except (TypeError, AttributeError):
|
||||
details = []
|
||||
|
||||
errors.append(error_cls(summary=error_msg, details=details))
|
||||
@@ -292,12 +292,6 @@ def _avoid_mismatched_variants(error_cls):
|
||||
errors = []
|
||||
packages_yaml = spack.config.CONFIG.get_config("packages")
|
||||
|
||||
def make_error(config_data, summary):
|
||||
s = io.StringIO()
|
||||
s.write("Occurring in the following file:\n")
|
||||
syaml.dump_config(config_data, stream=s, blame=True)
|
||||
return error_cls(summary=summary, details=[s.getvalue()])
|
||||
|
||||
for pkg_name in packages_yaml:
|
||||
# 'all:' must be more forgiving, since it is setting defaults for everything
|
||||
if pkg_name == "all" or "variants" not in packages_yaml[pkg_name]:
|
||||
@@ -317,7 +311,7 @@ def make_error(config_data, summary):
|
||||
f"Setting a preference for the '{pkg_name}' package to the "
|
||||
f"non-existing variant '{variant.name}'"
|
||||
)
|
||||
errors.append(make_error(preferences, summary))
|
||||
errors.append(_make_config_error(preferences, summary, error_cls=error_cls))
|
||||
continue
|
||||
|
||||
# Variant cannot accept this value
|
||||
@@ -329,11 +323,41 @@ def make_error(config_data, summary):
|
||||
f"Setting the variant '{variant.name}' of the '{pkg_name}' package "
|
||||
f"to the invalid value '{str(variant)}'"
|
||||
)
|
||||
errors.append(make_error(preferences, summary))
|
||||
errors.append(_make_config_error(preferences, summary, error_cls=error_cls))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@config_packages
|
||||
def _wrongly_named_spec(error_cls):
|
||||
"""Warns if the wrong name is used for an external spec"""
|
||||
errors = []
|
||||
packages_yaml = spack.config.CONFIG.get_config("packages")
|
||||
for pkg_name in packages_yaml:
|
||||
if pkg_name == "all":
|
||||
continue
|
||||
|
||||
externals = packages_yaml[pkg_name].get("externals", [])
|
||||
is_virtual = spack.repo.PATH.is_virtual(pkg_name)
|
||||
for entry in externals:
|
||||
spec = spack.spec.Spec(entry["spec"])
|
||||
regular_pkg_is_wrong = not is_virtual and pkg_name != spec.name
|
||||
virtual_pkg_is_wrong = is_virtual and not any(
|
||||
p.name == spec.name for p in spack.repo.PATH.providers_for(pkg_name)
|
||||
)
|
||||
if regular_pkg_is_wrong or virtual_pkg_is_wrong:
|
||||
summary = f"Wrong external spec detected for '{pkg_name}': {spec}"
|
||||
errors.append(_make_config_error(entry, summary, error_cls=error_cls))
|
||||
return errors
|
||||
|
||||
|
||||
def _make_config_error(config_data, summary, error_cls):
|
||||
s = io.StringIO()
|
||||
s.write("Occurring in the following file:\n")
|
||||
syaml.dump_config(config_data, stream=s, blame=True)
|
||||
return error_cls(summary=summary, details=[s.getvalue()])
|
||||
|
||||
|
||||
#: Sanity checks on package directives
|
||||
package_directives = AuditClass(
|
||||
group="packages",
|
||||
@@ -772,13 +796,33 @@ def check_virtual_with_variants(spec, msg):
|
||||
except spack.repo.UnknownPackageError:
|
||||
# This dependency is completely missing, so report
|
||||
# and continue the analysis
|
||||
summary = (
|
||||
f"{pkg_name}: unknown package '{dep_name}' in " "'depends_on' directive"
|
||||
)
|
||||
summary = f"{pkg_name}: unknown package '{dep_name}' in 'depends_on' directive"
|
||||
details = [f" in {filename}"]
|
||||
errors.append(error_cls(summary=summary, details=details))
|
||||
continue
|
||||
|
||||
# Check for self-referential specs similar to:
|
||||
#
|
||||
# depends_on("foo@X.Y", when="^foo+bar")
|
||||
#
|
||||
# That would allow clingo to choose whether to have foo@X.Y+bar in the graph.
|
||||
problematic_edges = [
|
||||
x for x in when.edges_to_dependencies(dep_name) if not x.virtuals
|
||||
]
|
||||
if problematic_edges and not dep.patches:
|
||||
summary = (
|
||||
f"{pkg_name}: dependency on '{dep.spec}' when '{when}' is self-referential"
|
||||
)
|
||||
details = [
|
||||
(
|
||||
f" please specify better using '^[virtuals=...] {dep_name}', or "
|
||||
f"substitute with an equivalent condition on '{pkg_name}'"
|
||||
),
|
||||
f" in {filename}",
|
||||
]
|
||||
errors.append(error_cls(summary=summary, details=details))
|
||||
continue
|
||||
|
||||
# check variants
|
||||
dependency_variants = dep.spec.variants
|
||||
for name, value in dependency_variants.items():
|
||||
|
@@ -1541,7 +1541,7 @@ def fetch_url_to_mirror(url):
|
||||
response = spack.oci.opener.urlopen(
|
||||
urllib.request.Request(
|
||||
url=ref.manifest_url(),
|
||||
headers={"Accept": "application/vnd.oci.image.manifest.v1+json"},
|
||||
headers={"Accept": ", ".join(spack.oci.oci.manifest_content_type)},
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
|
@@ -542,7 +542,7 @@ def verify_patchelf(patchelf: "spack.util.executable.Executable") -> bool:
|
||||
return version >= spack.version.Version("0.13.1")
|
||||
|
||||
|
||||
def ensure_patchelf_in_path_or_raise() -> None:
|
||||
def ensure_patchelf_in_path_or_raise() -> spack.util.executable.Executable:
|
||||
"""Ensure patchelf is in the PATH or raise."""
|
||||
# The old concretizer is not smart and we're doing its job: if the latest patchelf
|
||||
# does not concretize because the compiler doesn't support C++17, we try to
|
||||
|
@@ -146,7 +146,7 @@ def mypy_root_spec() -> str:
|
||||
|
||||
def black_root_spec() -> str:
|
||||
"""Return the root spec used to bootstrap black"""
|
||||
return _root_spec("py-black@:23.1.0")
|
||||
return _root_spec("py-black@:24.1.0")
|
||||
|
||||
|
||||
def flake8_root_spec() -> str:
|
||||
|
@@ -199,6 +199,8 @@ def initconfig_mpi_entries(self):
|
||||
mpiexec = "/usr/bin/srun"
|
||||
else:
|
||||
mpiexec = os.path.join(spec["slurm"].prefix.bin, "srun")
|
||||
elif hasattr(spec["mpi"].package, "mpiexec"):
|
||||
mpiexec = spec["mpi"].package.mpiexec
|
||||
else:
|
||||
mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpirun")
|
||||
if not os.path.exists(mpiexec):
|
||||
|
@@ -58,6 +58,62 @@ def _maybe_set_python_hints(pkg: spack.package_base.PackageBase, args: List[str]
|
||||
)
|
||||
|
||||
|
||||
def _supports_compilation_databases(pkg: spack.package_base.PackageBase) -> bool:
|
||||
"""Check if this package (and CMake) can support compilation databases."""
|
||||
|
||||
# CMAKE_EXPORT_COMPILE_COMMANDS only exists for CMake >= 3.5
|
||||
if not pkg.spec.satisfies("^cmake@3.5:"):
|
||||
return False
|
||||
|
||||
# CMAKE_EXPORT_COMPILE_COMMANDS is only implemented for Makefile and Ninja generators
|
||||
if not (pkg.spec.satisfies("generator=make") or pkg.spec.satisfies("generator=ninja")):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _conditional_cmake_defaults(pkg: spack.package_base.PackageBase, args: List[str]) -> None:
|
||||
"""Set a few default defines for CMake, depending on its version."""
|
||||
cmakes = pkg.spec.dependencies("cmake", dt.BUILD)
|
||||
|
||||
if len(cmakes) != 1:
|
||||
return
|
||||
|
||||
cmake = cmakes[0]
|
||||
|
||||
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
||||
try:
|
||||
ipo = pkg.spec.variants["ipo"].value
|
||||
except KeyError:
|
||||
ipo = False
|
||||
|
||||
if cmake.satisfies("@3.9:"):
|
||||
args.append(CMakeBuilder.define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", ipo))
|
||||
|
||||
# Disable Package Registry: export(PACKAGE) may put files in the user's home directory, and
|
||||
# find_package may search there. This is not what we want.
|
||||
|
||||
# Do not populate CMake User Package Registry
|
||||
if cmake.satisfies("@3.15:"):
|
||||
# see https://cmake.org/cmake/help/latest/policy/CMP0090.html
|
||||
args.append(CMakeBuilder.define("CMAKE_POLICY_DEFAULT_CMP0090", "NEW"))
|
||||
elif cmake.satisfies("@3.1:"):
|
||||
# see https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_NO_PACKAGE_REGISTRY.html
|
||||
args.append(CMakeBuilder.define("CMAKE_EXPORT_NO_PACKAGE_REGISTRY", True))
|
||||
|
||||
# Do not use CMake User/System Package Registry
|
||||
# https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#disabling-the-package-registry
|
||||
if cmake.satisfies("@3.16:"):
|
||||
args.append(CMakeBuilder.define("CMAKE_FIND_USE_PACKAGE_REGISTRY", False))
|
||||
elif cmake.satisfies("@3.1:3.15"):
|
||||
args.append(CMakeBuilder.define("CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY", False))
|
||||
args.append(CMakeBuilder.define("CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY", False))
|
||||
|
||||
# Export a compilation database if supported.
|
||||
if _supports_compilation_databases(pkg):
|
||||
args.append(CMakeBuilder.define("CMAKE_EXPORT_COMPILE_COMMANDS", True))
|
||||
|
||||
|
||||
def generator(*names: str, default: Optional[str] = None):
|
||||
"""The build system generator to use.
|
||||
|
||||
@@ -246,7 +302,10 @@ class CMakeBuilder(BaseBuilder):
|
||||
@property
|
||||
def archive_files(self):
|
||||
"""Files to archive for packages based on CMake"""
|
||||
return [os.path.join(self.build_directory, "CMakeCache.txt")]
|
||||
files = [os.path.join(self.build_directory, "CMakeCache.txt")]
|
||||
if _supports_compilation_databases(self):
|
||||
files.append(os.path.join(self.build_directory, "compile_commands.json"))
|
||||
return files
|
||||
|
||||
@property
|
||||
def root_cmakelists_dir(self):
|
||||
@@ -293,11 +352,6 @@ def std_args(pkg, generator=None):
|
||||
except KeyError:
|
||||
build_type = "RelWithDebInfo"
|
||||
|
||||
try:
|
||||
ipo = pkg.spec.variants["ipo"].value
|
||||
except KeyError:
|
||||
ipo = False
|
||||
|
||||
define = CMakeBuilder.define
|
||||
args = [
|
||||
"-G",
|
||||
@@ -306,10 +360,6 @@ def std_args(pkg, generator=None):
|
||||
define("CMAKE_BUILD_TYPE", build_type),
|
||||
]
|
||||
|
||||
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
||||
if pkg.spec.satisfies("^cmake@3.9:"):
|
||||
args.append(define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", ipo))
|
||||
|
||||
if primary_generator == "Unix Makefiles":
|
||||
args.append(define("CMAKE_VERBOSE_MAKEFILE", True))
|
||||
|
||||
@@ -318,6 +368,7 @@ def std_args(pkg, generator=None):
|
||||
[define("CMAKE_FIND_FRAMEWORK", "LAST"), define("CMAKE_FIND_APPBUNDLE", "LAST")]
|
||||
)
|
||||
|
||||
_conditional_cmake_defaults(pkg, args)
|
||||
_maybe_set_python_hints(pkg, args)
|
||||
|
||||
# Set up CMake rpath
|
||||
|
@@ -218,7 +218,7 @@ def pset_components(self):
|
||||
"+inspector": " intel-inspector",
|
||||
"+itac": " intel-itac intel-ta intel-tc" " intel-trace-analyzer intel-trace-collector",
|
||||
# Trace Analyzer and Collector
|
||||
"+vtune": " intel-vtune"
|
||||
"+vtune": " intel-vtune",
|
||||
# VTune, ..-profiler since 2020, ..-amplifier before
|
||||
}.items():
|
||||
if variant in self.spec:
|
||||
|
@@ -29,15 +29,12 @@ class LuaPackage(spack.package_base.PackageBase):
|
||||
|
||||
with when("build_system=lua"):
|
||||
depends_on("lua-lang")
|
||||
extends("lua", when="^lua")
|
||||
with when("^lua-luajit"):
|
||||
extends("lua-luajit")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit+lualinks")
|
||||
with when("^lua-luajit-openresty"):
|
||||
extends("lua-luajit-openresty")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit-openresty+lualinks")
|
||||
with when("^[virtuals=lua-lang] lua"):
|
||||
extends("lua")
|
||||
with when("^[virtuals=lua-lang] lua-luajit"):
|
||||
extends("lua-luajit+lualinks")
|
||||
with when("^[virtuals=lua-lang] lua-luajit-openresty"):
|
||||
extends("lua-luajit-openresty+lualinks")
|
||||
|
||||
@property
|
||||
def lua(self):
|
||||
|
@@ -69,7 +69,7 @@ class MSBuildBuilder(BaseBuilder):
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Return the directory containing the MSBuild solution or vcxproj."""
|
||||
return self.pkg.stage.source_path
|
||||
return fs.windows_sfn(self.pkg.stage.source_path)
|
||||
|
||||
@property
|
||||
def toolchain_version(self):
|
||||
|
@@ -77,7 +77,11 @@ def ignore_quotes(self):
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Return the directory containing the makefile."""
|
||||
return self.pkg.stage.source_path if not self.makefile_root else self.makefile_root
|
||||
return (
|
||||
fs.windows_sfn(self.pkg.stage.source_path)
|
||||
if not self.makefile_root
|
||||
else fs.windows_sfn(self.makefile_root)
|
||||
)
|
||||
|
||||
@property
|
||||
def std_nmake_args(self):
|
||||
|
@@ -9,10 +9,13 @@
|
||||
import shutil
|
||||
from os.path import basename, isdir
|
||||
|
||||
from llnl.util.filesystem import HeaderList, find_libraries, join_path, mkdirp
|
||||
from llnl.util import tty
|
||||
from llnl.util.filesystem import HeaderList, LibraryList, find_libraries, join_path, mkdirp
|
||||
from llnl.util.link_tree import LinkTree
|
||||
|
||||
from spack.build_environment import dso_suffix
|
||||
from spack.directives import conflicts, variant
|
||||
from spack.package_base import InstallError
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.util.executable import Executable
|
||||
|
||||
@@ -179,16 +182,72 @@ class IntelOneApiLibraryPackage(IntelOneApiPackage):
|
||||
|
||||
"""
|
||||
|
||||
def openmp_libs(self):
|
||||
"""Supply LibraryList for linking OpenMP"""
|
||||
|
||||
# NB: Hunting down explicit library files may be the Spack way of
|
||||
# doing things, but it is better to add the compiler defined option
|
||||
# e.g. -fopenmp
|
||||
|
||||
# If other packages use openmp, then all the packages need to
|
||||
# support the same ABI. Spack usually uses the same compiler
|
||||
# for all the packages, but you can force it if necessary:
|
||||
#
|
||||
# e.g. spack install blaspp%oneapi@2024 ^intel-oneapi-mkl%oneapi@2024
|
||||
#
|
||||
if self.spec.satisfies("%intel") or self.spec.satisfies("%oneapi"):
|
||||
libname = "libiomp5"
|
||||
elif self.spec.satisfies("%gcc"):
|
||||
libname = "libgomp"
|
||||
elif self.spec.satisfies("%clang"):
|
||||
libname = "libomp"
|
||||
else:
|
||||
raise InstallError(
|
||||
"OneAPI package with OpenMP threading requires one of %clang, %gcc, %oneapi, "
|
||||
"or %intel"
|
||||
)
|
||||
|
||||
# query the compiler for the library path
|
||||
with self.compiler.compiler_environment():
|
||||
omp_lib_path = Executable(self.compiler.cc)(
|
||||
"--print-file-name", f"{libname}.{dso_suffix}", output=str
|
||||
).strip()
|
||||
|
||||
# Newer versions of clang do not give the full path to libomp. If that's
|
||||
# the case, look in a path relative to the compiler where libomp is
|
||||
# typically found. If it's not found there, error out.
|
||||
if not os.path.exists(omp_lib_path) and self.spec.satisfies("%clang"):
|
||||
compiler_root = os.path.dirname(os.path.dirname(os.path.realpath(self.compiler.cc)))
|
||||
omp_lib_path_compiler = os.path.join(compiler_root, "lib", f"{libname}.{dso_suffix}")
|
||||
if os.path.exists(omp_lib_path_compiler):
|
||||
omp_lib_path = omp_lib_path_compiler
|
||||
|
||||
# if the compiler cannot find the file, it returns the input path
|
||||
if not os.path.exists(omp_lib_path):
|
||||
raise InstallError(f"OneAPI package cannot locate OpenMP library: {omp_lib_path}")
|
||||
|
||||
omp_libs = LibraryList(omp_lib_path)
|
||||
tty.info(f"OneAPI package requires OpenMP library: {omp_libs}")
|
||||
return omp_libs
|
||||
|
||||
# find_headers uses heuristics to determine the include directory
|
||||
# that does not work for oneapi packages. Use explicit directories
|
||||
# instead.
|
||||
def header_directories(self, dirs):
|
||||
h = HeaderList([])
|
||||
h.directories = dirs
|
||||
# trilinos passes the directories to cmake, and cmake requires
|
||||
# that the directory exists
|
||||
for dir in dirs:
|
||||
if not isdir(dir):
|
||||
raise RuntimeError(f"{dir} does not exist")
|
||||
return h
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return self.header_directories(
|
||||
[self.component_prefix.include, self.component_prefix.include.join(self.component_dir)]
|
||||
)
|
||||
# This should match the directories added to CPATH by
|
||||
# env/vars.sh for the component
|
||||
return self.header_directories([self.component_prefix.include])
|
||||
|
||||
@property
|
||||
def libs(self):
|
||||
|
@@ -2,11 +2,15 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import operator
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from typing import Iterable, List, Mapping, Optional
|
||||
import stat
|
||||
from typing import Dict, Iterable, List, Mapping, Optional, Tuple
|
||||
|
||||
import archspec
|
||||
|
||||
@@ -136,31 +140,52 @@ def view_file_conflicts(self, view, merge_map):
|
||||
return conflicts
|
||||
|
||||
def add_files_to_view(self, view, merge_map, skip_if_exists=True):
|
||||
if not self.extendee_spec:
|
||||
# Patch up shebangs to the python linked in the view only if python is built by Spack.
|
||||
if not self.extendee_spec or self.extendee_spec.external:
|
||||
return super().add_files_to_view(view, merge_map, skip_if_exists)
|
||||
|
||||
# We only patch shebangs in the bin directory.
|
||||
copied_files: Dict[Tuple[int, int], str] = {} # File identifier -> source
|
||||
delayed_links: List[Tuple[str, str]] = [] # List of symlinks from merge map
|
||||
|
||||
bin_dir = self.spec.prefix.bin
|
||||
python_prefix = self.extendee_spec.prefix
|
||||
python_is_external = self.extendee_spec.external
|
||||
global_view = fs.same_path(python_prefix, view.get_projection_for_spec(self.spec))
|
||||
for src, dst in merge_map.items():
|
||||
if os.path.exists(dst):
|
||||
if skip_if_exists and os.path.lexists(dst):
|
||||
continue
|
||||
elif global_view or not fs.path_contains_subdirectory(src, bin_dir):
|
||||
|
||||
if not fs.path_contains_subdirectory(src, bin_dir):
|
||||
view.link(src, dst)
|
||||
elif not os.path.islink(src):
|
||||
continue
|
||||
|
||||
s = os.lstat(src)
|
||||
|
||||
# Symlink is delayed because we may need to re-target if its target is copied in view
|
||||
if stat.S_ISLNK(s.st_mode):
|
||||
delayed_links.append((src, dst))
|
||||
continue
|
||||
|
||||
# If it's executable and has a shebang, copy and patch it.
|
||||
if (s.st_mode & 0b111) and fs.has_shebang(src):
|
||||
copied_files[(s.st_dev, s.st_ino)] = dst
|
||||
shutil.copy2(src, dst)
|
||||
is_script = fs.is_nonsymlink_exe_with_shebang(src)
|
||||
if is_script and not python_is_external:
|
||||
fs.filter_file(
|
||||
python_prefix,
|
||||
os.path.abspath(view.get_projection_for_spec(self.spec)),
|
||||
dst,
|
||||
)
|
||||
fs.filter_file(
|
||||
python_prefix, os.path.abspath(view.get_projection_for_spec(self.spec)), dst
|
||||
)
|
||||
else:
|
||||
orig_link_target = os.path.realpath(src)
|
||||
new_link_target = os.path.abspath(merge_map[orig_link_target])
|
||||
view.link(new_link_target, dst)
|
||||
view.link(src, dst)
|
||||
|
||||
# Finally re-target the symlinks that point to copied files.
|
||||
for src, dst in delayed_links:
|
||||
try:
|
||||
s = os.stat(src)
|
||||
target = copied_files[(s.st_dev, s.st_ino)]
|
||||
except (OSError, KeyError):
|
||||
target = None
|
||||
if target:
|
||||
os.symlink(os.path.relpath(target, os.path.dirname(dst)), dst)
|
||||
else:
|
||||
view.link(src, dst, spec=self.spec)
|
||||
|
||||
def remove_files_from_view(self, view, merge_map):
|
||||
ignore_namespace = False
|
||||
@@ -346,16 +371,19 @@ def headers(self) -> HeaderList:
|
||||
# Remove py- prefix in package name
|
||||
name = self.spec.name[3:]
|
||||
|
||||
# Headers may be in either location
|
||||
# Headers should only be in include or platlib, but no harm in checking purelib too
|
||||
include = self.prefix.join(self.spec["python"].package.include).join(name)
|
||||
platlib = self.prefix.join(self.spec["python"].package.platlib).join(name)
|
||||
headers = fs.find_all_headers(include) + fs.find_all_headers(platlib)
|
||||
purelib = self.prefix.join(self.spec["python"].package.purelib).join(name)
|
||||
|
||||
headers_list = map(fs.find_all_headers, [include, platlib, purelib])
|
||||
headers = functools.reduce(operator.add, headers_list)
|
||||
|
||||
if headers:
|
||||
return headers
|
||||
|
||||
msg = "Unable to locate {} headers in {} or {}"
|
||||
raise NoHeadersError(msg.format(self.spec.name, include, platlib))
|
||||
msg = "Unable to locate {} headers in {}, {}, or {}"
|
||||
raise NoHeadersError(msg.format(self.spec.name, include, platlib, purelib))
|
||||
|
||||
@property
|
||||
def libs(self) -> LibraryList:
|
||||
@@ -364,15 +392,19 @@ def libs(self) -> LibraryList:
|
||||
# Remove py- prefix in package name
|
||||
name = self.spec.name[3:]
|
||||
|
||||
root = self.prefix.join(self.spec["python"].package.platlib).join(name)
|
||||
# Libraries should only be in platlib, but no harm in checking purelib too
|
||||
platlib = self.prefix.join(self.spec["python"].package.platlib).join(name)
|
||||
purelib = self.prefix.join(self.spec["python"].package.purelib).join(name)
|
||||
|
||||
libs = fs.find_all_libraries(root, recursive=True)
|
||||
find_all_libraries = functools.partial(fs.find_all_libraries, recursive=True)
|
||||
libs_list = map(find_all_libraries, [platlib, purelib])
|
||||
libs = functools.reduce(operator.add, libs_list)
|
||||
|
||||
if libs:
|
||||
return libs
|
||||
|
||||
msg = "Unable to recursively locate {} libraries in {}"
|
||||
raise NoLibrariesError(msg.format(self.spec.name, root))
|
||||
msg = "Unable to recursively locate {} libraries in {} or {}"
|
||||
raise NoLibrariesError(msg.format(self.spec.name, platlib, purelib))
|
||||
|
||||
|
||||
@spack.builder.builder("python_pip")
|
||||
|
@@ -162,23 +162,9 @@ def hip_flags(amdgpu_target):
|
||||
|
||||
# Add compiler minimum versions based on the first release where the
|
||||
# processor is included in llvm/lib/Support/TargetParser.cpp
|
||||
depends_on("llvm-amdgpu@4.1.0:", when="amdgpu_target=gfx900:xnack-")
|
||||
depends_on("llvm-amdgpu@4.1.0:", when="amdgpu_target=gfx906:xnack-")
|
||||
depends_on("llvm-amdgpu@4.1.0:", when="amdgpu_target=gfx908:xnack-")
|
||||
depends_on("llvm-amdgpu@4.1.0:", when="amdgpu_target=gfx90c")
|
||||
depends_on("llvm-amdgpu@4.3.0:", when="amdgpu_target=gfx90a")
|
||||
depends_on("llvm-amdgpu@4.3.0:", when="amdgpu_target=gfx90a:xnack-")
|
||||
depends_on("llvm-amdgpu@4.3.0:", when="amdgpu_target=gfx90a:xnack+")
|
||||
depends_on("llvm-amdgpu@5.2.0:", when="amdgpu_target=gfx940")
|
||||
depends_on("llvm-amdgpu@5.7.0:", when="amdgpu_target=gfx941")
|
||||
depends_on("llvm-amdgpu@5.7.0:", when="amdgpu_target=gfx942")
|
||||
depends_on("llvm-amdgpu@4.5.0:", when="amdgpu_target=gfx1013")
|
||||
depends_on("llvm-amdgpu@3.8.0:", when="amdgpu_target=gfx1030")
|
||||
depends_on("llvm-amdgpu@3.9.0:", when="amdgpu_target=gfx1031")
|
||||
depends_on("llvm-amdgpu@4.1.0:", when="amdgpu_target=gfx1032")
|
||||
depends_on("llvm-amdgpu@4.1.0:", when="amdgpu_target=gfx1033")
|
||||
depends_on("llvm-amdgpu@4.3.0:", when="amdgpu_target=gfx1034")
|
||||
depends_on("llvm-amdgpu@4.5.0:", when="amdgpu_target=gfx1035")
|
||||
depends_on("llvm-amdgpu@5.2.0:", when="amdgpu_target=gfx1036")
|
||||
depends_on("llvm-amdgpu@5.3.0:", when="amdgpu_target=gfx1100")
|
||||
depends_on("llvm-amdgpu@5.3.0:", when="amdgpu_target=gfx1101")
|
||||
|
@@ -35,9 +35,9 @@ def _misc_cache():
|
||||
|
||||
|
||||
#: Spack's cache for small data
|
||||
MISC_CACHE: Union[
|
||||
spack.util.file_cache.FileCache, llnl.util.lang.Singleton
|
||||
] = llnl.util.lang.Singleton(_misc_cache)
|
||||
MISC_CACHE: Union[spack.util.file_cache.FileCache, llnl.util.lang.Singleton] = (
|
||||
llnl.util.lang.Singleton(_misc_cache)
|
||||
)
|
||||
|
||||
|
||||
def fetch_cache_location():
|
||||
@@ -91,6 +91,6 @@ def symlink(self, mirror_ref):
|
||||
|
||||
|
||||
#: Spack's local cache for downloaded source archives
|
||||
FETCH_CACHE: Union[
|
||||
spack.fetch_strategy.FsCache, llnl.util.lang.Singleton
|
||||
] = llnl.util.lang.Singleton(_fetch_cache)
|
||||
FETCH_CACHE: Union[spack.fetch_strategy.FsCache, llnl.util.lang.Singleton] = (
|
||||
llnl.util.lang.Singleton(_fetch_cache)
|
||||
)
|
||||
|
@@ -7,9 +7,7 @@
|
||||
get_job_name = lambda needs_entry: (
|
||||
needs_entry.get("job")
|
||||
if (isinstance(needs_entry, collections.abc.Mapping) and needs_entry.get("artifacts", True))
|
||||
else needs_entry
|
||||
if isinstance(needs_entry, str)
|
||||
else None
|
||||
else needs_entry if isinstance(needs_entry, str) else None
|
||||
)
|
||||
|
||||
|
||||
|
@@ -594,6 +594,15 @@ def _put_manifest(
|
||||
base_manifest, base_config = base_images[architecture]
|
||||
env = _retrieve_env_dict_from_config(base_config)
|
||||
|
||||
# If the base image uses `vnd.docker.distribution.manifest.v2+json`, then we use that too.
|
||||
# This is because Singularity / Apptainer is very strict about not mixing them.
|
||||
base_manifest_mediaType = base_manifest.get(
|
||||
"mediaType", "application/vnd.oci.image.manifest.v1+json"
|
||||
)
|
||||
use_docker_format = (
|
||||
base_manifest_mediaType == "application/vnd.docker.distribution.manifest.v2+json"
|
||||
)
|
||||
|
||||
spack.user_environment.environment_modifications_for_specs(*specs).apply_modifications(env)
|
||||
|
||||
# Create an oci.image.config file
|
||||
@@ -625,8 +634,8 @@ def _put_manifest(
|
||||
# Upload the config file
|
||||
upload_blob_with_retry(image_ref, file=config_file, digest=config_file_checksum)
|
||||
|
||||
oci_manifest = {
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
manifest = {
|
||||
"mediaType": base_manifest_mediaType,
|
||||
"schemaVersion": 2,
|
||||
"config": {
|
||||
"mediaType": base_manifest["config"]["mediaType"],
|
||||
@@ -637,7 +646,11 @@ def _put_manifest(
|
||||
*(layer for layer in base_manifest["layers"]),
|
||||
*(
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
"mediaType": (
|
||||
"application/vnd.docker.image.rootfs.diff.tar.gzip"
|
||||
if use_docker_format
|
||||
else "application/vnd.oci.image.layer.v1.tar+gzip"
|
||||
),
|
||||
"digest": str(checksums[s.dag_hash()].compressed_digest),
|
||||
"size": checksums[s.dag_hash()].size,
|
||||
}
|
||||
@@ -646,11 +659,11 @@ def _put_manifest(
|
||||
],
|
||||
}
|
||||
|
||||
if annotations:
|
||||
oci_manifest["annotations"] = annotations
|
||||
if not use_docker_format and annotations:
|
||||
manifest["annotations"] = annotations
|
||||
|
||||
# Finally upload the manifest
|
||||
upload_manifest_with_retry(image_ref, oci_manifest=oci_manifest)
|
||||
upload_manifest_with_retry(image_ref, manifest=manifest)
|
||||
|
||||
# delete the config file
|
||||
os.unlink(config_file)
|
||||
|
@@ -6,6 +6,7 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
@@ -157,7 +158,9 @@ def setup_parser(subparser):
|
||||
description=deindent(ci_reproduce.__doc__),
|
||||
help=spack.cmd.first_line(ci_reproduce.__doc__),
|
||||
)
|
||||
reproduce.add_argument("job_url", help="URL of job artifacts bundle")
|
||||
reproduce.add_argument(
|
||||
"job_url", help="URL of GitLab job web page or artifact", type=_gitlab_artifacts_url
|
||||
)
|
||||
reproduce.add_argument(
|
||||
"--runtime",
|
||||
help="Container runtime to use.",
|
||||
@@ -792,11 +795,6 @@ def ci_reproduce(args):
|
||||
artifacts of the provided gitlab pipeline rebuild job's URL will be used to derive
|
||||
instructions for reproducing the build locally
|
||||
"""
|
||||
job_url = args.job_url
|
||||
work_dir = args.working_dir
|
||||
autostart = args.autostart
|
||||
runtime = args.runtime
|
||||
|
||||
# Allow passing GPG key for reprocuding protected CI jobs
|
||||
if args.gpg_file:
|
||||
gpg_key_url = url_util.path_to_file_url(args.gpg_file)
|
||||
@@ -805,7 +803,47 @@ def ci_reproduce(args):
|
||||
else:
|
||||
gpg_key_url = None
|
||||
|
||||
return spack_ci.reproduce_ci_job(job_url, work_dir, autostart, gpg_key_url, runtime)
|
||||
return spack_ci.reproduce_ci_job(
|
||||
args.job_url, args.working_dir, args.autostart, gpg_key_url, args.runtime
|
||||
)
|
||||
|
||||
|
||||
def _gitlab_artifacts_url(url: str) -> str:
|
||||
"""Take a URL either to the URL of the job in the GitLab UI, or to the artifacts zip file,
|
||||
and output the URL to the artifacts zip file."""
|
||||
parsed = urlparse(url)
|
||||
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
raise ValueError(url)
|
||||
|
||||
parts = parsed.path.split("/")
|
||||
|
||||
if len(parts) < 2:
|
||||
raise ValueError(url)
|
||||
|
||||
# Just use API endpoints verbatim, they're probably generated by Spack.
|
||||
if parts[1] == "api":
|
||||
return url
|
||||
|
||||
# If it's a URL to the job in the Gitlab UI, we may need to append the artifacts path.
|
||||
minus_idx = parts.index("-")
|
||||
|
||||
# Remove repeated slashes in the remainder
|
||||
rest = [p for p in parts[minus_idx + 1 :] if p]
|
||||
|
||||
# Now the format is jobs/X or jobs/X/artifacts/download
|
||||
if len(rest) < 2 or rest[0] != "jobs":
|
||||
raise ValueError(url)
|
||||
|
||||
if len(rest) == 2:
|
||||
# replace jobs/X with jobs/X/artifacts/download
|
||||
rest.extend(("artifacts", "download"))
|
||||
|
||||
# Replace the parts and unparse.
|
||||
parts[minus_idx + 1 :] = rest
|
||||
|
||||
# Don't allow fragments / queries
|
||||
return urlunparse(parsed._replace(path="/".join(parts), fragment="", query=""))
|
||||
|
||||
|
||||
def ci(parser, args):
|
||||
|
@@ -8,6 +8,7 @@
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.cmd
|
||||
import spack.config
|
||||
import spack.spec
|
||||
import spack.util.path
|
||||
import spack.version
|
||||
@@ -21,6 +22,7 @@
|
||||
|
||||
def setup_parser(subparser):
|
||||
subparser.add_argument("-p", "--path", help="source location of package")
|
||||
subparser.add_argument("-b", "--build-directory", help="build directory for the package")
|
||||
|
||||
clone_group = subparser.add_mutually_exclusive_group()
|
||||
clone_group.add_argument(
|
||||
@@ -151,4 +153,11 @@ def develop(parser, args):
|
||||
env = spack.cmd.require_active_env(cmd_name="develop")
|
||||
tty.debug("Updating develop config for {0} transactionally".format(env.name))
|
||||
with env.write_transaction():
|
||||
if args.build_directory is not None:
|
||||
spack.config.add(
|
||||
"packages:{}:package_attributes:build_directory:{}".format(
|
||||
spec.name, args.build_directory
|
||||
),
|
||||
env.scope_name,
|
||||
)
|
||||
_update_config(spec, path)
|
||||
|
@@ -270,7 +270,8 @@ def create_temp_env_directory():
|
||||
def _tty_info(msg):
|
||||
"""tty.info like function that prints the equivalent printf statement for eval."""
|
||||
decorated = f'{colorize("@*b{==>}")} {msg}\n'
|
||||
print(f"printf {shlex.quote(decorated)};")
|
||||
executor = "echo" if sys.platform == "win32" else "printf"
|
||||
print(f"{executor} {shlex.quote(decorated)};")
|
||||
|
||||
|
||||
def env_activate(args):
|
||||
|
@@ -30,6 +30,7 @@
|
||||
@c{@min:max} version range (inclusive)
|
||||
@c{@min:} version <min> or higher
|
||||
@c{@:max} up to version <max> (inclusive)
|
||||
@c{@=version} exact version
|
||||
|
||||
compilers:
|
||||
@g{%compiler} build with <compiler>
|
||||
|
@@ -292,9 +292,11 @@ def head(n, span_id, title, anchor=None):
|
||||
out.write("<dd>\n")
|
||||
out.write(
|
||||
", ".join(
|
||||
d
|
||||
if d not in pkg_names
|
||||
else '<a class="reference internal" href="#%s">%s</a>' % (d, d)
|
||||
(
|
||||
d
|
||||
if d not in pkg_names
|
||||
else '<a class="reference internal" href="#%s">%s</a>' % (d, d)
|
||||
)
|
||||
for d in deps
|
||||
)
|
||||
)
|
||||
|
71
lib/spack/spack/cmd/logs.py
Normal file
71
lib/spack/spack/cmd/logs.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import errno
|
||||
import gzip
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import spack.cmd
|
||||
import spack.spec
|
||||
import spack.util.compression as compression
|
||||
from spack.cmd.common import arguments
|
||||
from spack.main import SpackCommandError
|
||||
|
||||
description = "print out logs for packages"
|
||||
section = "basic"
|
||||
level = "long"
|
||||
|
||||
|
||||
def setup_parser(subparser):
|
||||
arguments.add_common_arguments(subparser, ["spec"])
|
||||
|
||||
|
||||
def _dump_byte_stream_to_stdout(instream: io.BufferedIOBase) -> None:
|
||||
# Reopen stdout in binary mode so we don't have to worry about encoding
|
||||
outstream = os.fdopen(sys.stdout.fileno(), "wb", closefd=False)
|
||||
shutil.copyfileobj(instream, outstream)
|
||||
|
||||
|
||||
def _logs(cmdline_spec: spack.spec.Spec, concrete_spec: spack.spec.Spec):
|
||||
if concrete_spec.installed:
|
||||
log_path = concrete_spec.package.install_log_path
|
||||
elif os.path.exists(concrete_spec.package.stage.path):
|
||||
# TODO: `spack logs` can currently not show the logs while a package is being built, as the
|
||||
# combined log file is only written after the build is finished.
|
||||
log_path = concrete_spec.package.log_path
|
||||
else:
|
||||
raise SpackCommandError(f"{cmdline_spec} is not installed or staged")
|
||||
|
||||
try:
|
||||
stream = open(log_path, "rb")
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise SpackCommandError(f"No logs are available for {cmdline_spec}") from e
|
||||
raise SpackCommandError(f"Error reading logs for {cmdline_spec}: {e}") from e
|
||||
|
||||
with stream as f:
|
||||
ext = compression.extension_from_magic_numbers_by_stream(f, decompress=False)
|
||||
if ext and ext != "gz":
|
||||
raise SpackCommandError(f"Unsupported storage format for {log_path}: {ext}")
|
||||
|
||||
# If the log file is gzip compressed, wrap it with a decompressor
|
||||
_dump_byte_stream_to_stdout(gzip.GzipFile(fileobj=f) if ext == "gz" else f)
|
||||
|
||||
|
||||
def logs(parser, args):
|
||||
specs = spack.cmd.parse_specs(args.spec)
|
||||
|
||||
if not specs:
|
||||
raise SpackCommandError("You must supply a spec.")
|
||||
|
||||
if len(specs) != 1:
|
||||
raise SpackCommandError("Too many specs. Supply only one.")
|
||||
|
||||
concrete_spec = spack.cmd.matching_spec_from_env(specs[0])
|
||||
|
||||
_logs(specs[0], concrete_spec)
|
@@ -127,10 +127,7 @@ def _process_result(result, show, required_format, kwargs):
|
||||
print()
|
||||
|
||||
if result.unsolved_specs and "solutions" in show:
|
||||
tty.msg("Unsolved specs")
|
||||
for spec in result.unsolved_specs:
|
||||
print(spec)
|
||||
print()
|
||||
tty.msg(asp.Result.format_unsolved(result.unsolved_specs))
|
||||
|
||||
|
||||
def solve(parser, args):
|
||||
|
@@ -514,9 +514,10 @@ def get_compilers(config, cspec=None, arch_spec=None):
|
||||
for items in config:
|
||||
items = items["compiler"]
|
||||
|
||||
# NOTE: in principle this should be equality not satisfies, but config can still
|
||||
# be written in old format gcc@10.1.0 instead of gcc@=10.1.0.
|
||||
if cspec and not cspec.satisfies(items["spec"]):
|
||||
# We might use equality here.
|
||||
if cspec and not spack.spec.parse_with_version_concrete(
|
||||
items["spec"], compiler=True
|
||||
).satisfies(cspec):
|
||||
continue
|
||||
|
||||
# If an arch spec is given, confirm that this compiler
|
||||
|
@@ -7,6 +7,7 @@
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Dict, List, Set
|
||||
|
||||
import spack.compiler
|
||||
@@ -15,7 +16,7 @@
|
||||
import spack.util.executable
|
||||
from spack.compiler import Compiler
|
||||
from spack.error import SpackError
|
||||
from spack.version import Version
|
||||
from spack.version import Version, VersionRange
|
||||
|
||||
avail_fc_version: Set[str] = set()
|
||||
fc_path: Dict[str, str] = dict()
|
||||
@@ -292,6 +293,15 @@ def setup_custom_environment(self, pkg, env):
|
||||
else:
|
||||
env.set_path(env_var, int_env[env_var].split(os.pathsep))
|
||||
|
||||
# certain versions of ifx (2021.3.0:2023.1.0) do not play well with env:TMP
|
||||
# that has a "." character in the path
|
||||
# Work around by pointing tmp to the stage for the duration of the build
|
||||
if self.fc and Version(self.fc_version(self.fc)).satisfies(
|
||||
VersionRange("2021.3.0", "2023.1.0")
|
||||
):
|
||||
new_tmp = tempfile.mkdtemp(dir=pkg.stage.path)
|
||||
env.set("TMP", new_tmp)
|
||||
|
||||
env.set("CC", self.cc)
|
||||
env.set("CXX", self.cxx)
|
||||
env.set("FC", self.fc)
|
||||
|
@@ -826,7 +826,6 @@ def __init__(self, spec):
|
||||
|
||||
|
||||
class InsufficientArchitectureInfoError(spack.error.SpackError):
|
||||
|
||||
"""Raised when details on architecture cannot be collected from the
|
||||
system"""
|
||||
|
||||
|
@@ -63,10 +63,11 @@
|
||||
from spack.util.cpus import cpus_available
|
||||
|
||||
#: Dict from section names -> schema for that section
|
||||
SECTION_SCHEMAS = {
|
||||
SECTION_SCHEMAS: Dict[str, Any] = {
|
||||
"compilers": spack.schema.compilers.schema,
|
||||
"concretizer": spack.schema.concretizer.schema,
|
||||
"definitions": spack.schema.definitions.schema,
|
||||
"view": spack.schema.view.schema,
|
||||
"develop": spack.schema.develop.schema,
|
||||
"mirrors": spack.schema.mirrors.schema,
|
||||
"repos": spack.schema.repos.schema,
|
||||
@@ -81,7 +82,7 @@
|
||||
|
||||
# Same as above, but including keys for environments
|
||||
# this allows us to unify config reading between configs and environments
|
||||
_ALL_SCHEMAS = copy.deepcopy(SECTION_SCHEMAS)
|
||||
_ALL_SCHEMAS: Dict[str, Any] = copy.deepcopy(SECTION_SCHEMAS)
|
||||
_ALL_SCHEMAS.update({spack.schema.env.TOP_LEVEL_KEY: spack.schema.env.schema})
|
||||
|
||||
#: Path to the default configuration
|
||||
@@ -1096,7 +1097,7 @@ def read_config_file(
|
||||
data = syaml.load_config(f)
|
||||
|
||||
if data:
|
||||
if not schema:
|
||||
if schema is None:
|
||||
key = next(iter(data))
|
||||
schema = _ALL_SCHEMAS[key]
|
||||
validate(data, schema)
|
||||
|
@@ -71,7 +71,7 @@
|
||||
"almalinux:9": {
|
||||
"bootstrap": {
|
||||
"template": "container/almalinux_9.dockerfile",
|
||||
"image": "quay.io/almalinux/almalinux:9"
|
||||
"image": "quay.io/almalinuxorg/almalinux:9"
|
||||
},
|
||||
"os_package_manager": "dnf_epel",
|
||||
"build": "spack/almalinux9",
|
||||
@@ -79,13 +79,13 @@
|
||||
"develop": "latest"
|
||||
},
|
||||
"final": {
|
||||
"image": "quay.io/almalinux/almalinux:9"
|
||||
"image": "quay.io/almalinuxorg/almalinux:9"
|
||||
}
|
||||
},
|
||||
"almalinux:8": {
|
||||
"bootstrap": {
|
||||
"template": "container/almalinux_8.dockerfile",
|
||||
"image": "quay.io/almalinux/almalinux:8"
|
||||
"image": "quay.io/almalinuxorg/almalinux:8"
|
||||
},
|
||||
"os_package_manager": "dnf_epel",
|
||||
"build": "spack/almalinux8",
|
||||
@@ -93,7 +93,7 @@
|
||||
"develop": "latest"
|
||||
},
|
||||
"final": {
|
||||
"image": "quay.io/almalinux/almalinux:8"
|
||||
"image": "quay.io/almalinuxorg/almalinux:8"
|
||||
}
|
||||
},
|
||||
"centos:stream": {
|
||||
|
@@ -1687,7 +1687,11 @@ def root(key, record):
|
||||
with self.read_transaction():
|
||||
roots = [rec.spec for key, rec in self._data.items() if root(key, rec)]
|
||||
needed = set(id(spec) for spec in tr.traverse_nodes(roots, deptype=deptype))
|
||||
return [rec.spec for rec in self._data.values() if id(rec.spec) not in needed]
|
||||
return [
|
||||
rec.spec
|
||||
for rec in self._data.values()
|
||||
if id(rec.spec) not in needed and rec.installed
|
||||
]
|
||||
|
||||
def update_explicit(self, spec, explicit):
|
||||
"""
|
||||
|
@@ -36,6 +36,9 @@
|
||||
#: Default dependency type if none is specified
|
||||
DEFAULT: DepFlag = BUILD | LINK
|
||||
|
||||
#: A flag with no dependency types set
|
||||
NONE: DepFlag = 0
|
||||
|
||||
#: An iterator of all flag components
|
||||
ALL_FLAGS: Tuple[DepFlag, DepFlag, DepFlag, DepFlag] = (BUILD, LINK, RUN, TEST)
|
||||
|
||||
|
@@ -34,7 +34,7 @@ class OpenMpi(Package):
|
||||
import functools
|
||||
import os.path
|
||||
import re
|
||||
from typing import Any, Callable, List, Optional, Set, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Set, Tuple, Union
|
||||
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty.color
|
||||
@@ -57,6 +57,9 @@ class OpenMpi(Package):
|
||||
VersionLookupError,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import spack.package_base
|
||||
|
||||
__all__ = [
|
||||
"DirectiveError",
|
||||
"DirectiveMeta",
|
||||
@@ -349,6 +352,7 @@ def remove_directives(arg):
|
||||
return _decorator
|
||||
|
||||
|
||||
SubmoduleCallback = Callable[["spack.package_base.PackageBase"], Union[str, List[str], bool]]
|
||||
directive = DirectiveMeta.directive
|
||||
|
||||
|
||||
@@ -380,7 +384,7 @@ def version(
|
||||
tag: Optional[str] = None,
|
||||
branch: Optional[str] = None,
|
||||
get_full_repo: Optional[bool] = None,
|
||||
submodules: Optional[bool] = None,
|
||||
submodules: Union[SubmoduleCallback, Optional[bool]] = None,
|
||||
submodules_delete: Optional[bool] = None,
|
||||
# other version control
|
||||
svn: Optional[str] = None,
|
||||
|
@@ -21,7 +21,6 @@
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
import llnl.util.tty.color as clr
|
||||
from llnl.util.lang import dedupe
|
||||
from llnl.util.link_tree import ConflictingSpecsError
|
||||
from llnl.util.symlink import symlink
|
||||
|
||||
@@ -379,8 +378,8 @@ def _rewrite_relative_dev_paths_on_relocation(env, init_file_dir):
|
||||
if not dev_specs:
|
||||
return
|
||||
for name, entry in dev_specs.items():
|
||||
dev_path = entry["path"]
|
||||
expanded_path = os.path.normpath(os.path.join(init_file_dir, entry["path"]))
|
||||
dev_path = substitute_path_variables(entry["path"])
|
||||
expanded_path = spack.util.path.canonicalize_path(dev_path, default_wd=init_file_dir)
|
||||
|
||||
# Skip if the expanded path is the same (e.g. when absolute)
|
||||
if dev_path == expanded_path:
|
||||
@@ -663,30 +662,26 @@ def __contains__(self, spec):
|
||||
|
||||
return True
|
||||
|
||||
def specs_for_view(self, concretized_root_specs):
|
||||
"""
|
||||
From the list of concretized user specs in the environment, flatten
|
||||
the dags, and filter selected, installed specs, remove duplicates on dag hash.
|
||||
"""
|
||||
# With deps, requires traversal
|
||||
if self.link == "all" or self.link == "run":
|
||||
deptype = ("run") if self.link == "run" else ("link", "run")
|
||||
specs = list(
|
||||
traverse.traverse_nodes(
|
||||
concretized_root_specs, deptype=deptype, key=traverse.by_dag_hash
|
||||
)
|
||||
)
|
||||
def specs_for_view(self, concrete_roots: List[Spec]) -> List[Spec]:
|
||||
"""Flatten the DAGs of the concrete roots, keep only unique, selected, and installed specs
|
||||
in topological order from root to leaf."""
|
||||
if self.link == "all":
|
||||
deptype = dt.LINK | dt.RUN
|
||||
elif self.link == "run":
|
||||
deptype = dt.RUN
|
||||
else:
|
||||
specs = list(dedupe(concretized_root_specs, key=traverse.by_dag_hash))
|
||||
deptype = dt.NONE
|
||||
|
||||
specs = traverse.traverse_nodes(
|
||||
concrete_roots, order="topo", deptype=deptype, key=traverse.by_dag_hash
|
||||
)
|
||||
|
||||
# Filter selected, installed specs
|
||||
with spack.store.STORE.db.read_transaction():
|
||||
specs = [s for s in specs if s in self and s.installed]
|
||||
return [s for s in specs if s in self and s.installed]
|
||||
|
||||
return specs
|
||||
|
||||
def regenerate(self, concretized_root_specs):
|
||||
specs = self.specs_for_view(concretized_root_specs)
|
||||
def regenerate(self, concrete_roots: List[Spec]) -> None:
|
||||
specs = self.specs_for_view(concrete_roots)
|
||||
|
||||
# To ensure there are no conflicts with packages being installed
|
||||
# that cannot be resolved or have repos that have been removed
|
||||
@@ -703,14 +698,14 @@ def regenerate(self, concretized_root_specs):
|
||||
old_root = self._current_root
|
||||
|
||||
if new_root == old_root:
|
||||
tty.debug("View at %s does not need regeneration." % self.root)
|
||||
tty.debug(f"View at {self.root} does not need regeneration.")
|
||||
return
|
||||
|
||||
_error_on_nonempty_view_dir(new_root)
|
||||
|
||||
# construct view at new_root
|
||||
if specs:
|
||||
tty.msg("Updating view at {0}".format(self.root))
|
||||
tty.msg(f"Updating view at {self.root}")
|
||||
|
||||
view = self.view(new=new_root)
|
||||
|
||||
@@ -720,7 +715,7 @@ def regenerate(self, concretized_root_specs):
|
||||
# Create a new view
|
||||
try:
|
||||
fs.mkdirp(new_root)
|
||||
view.add_specs(*specs, with_dependencies=False)
|
||||
view.add_specs(*specs)
|
||||
|
||||
# create symlink from tmp_symlink_name to new_root
|
||||
if os.path.exists(tmp_symlink_name):
|
||||
@@ -734,7 +729,7 @@ def regenerate(self, concretized_root_specs):
|
||||
try:
|
||||
shutil.rmtree(new_root, ignore_errors=True)
|
||||
os.unlink(tmp_symlink_name)
|
||||
except (IOError, OSError):
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Give an informative error message for the typical error case: two specs, same package
|
||||
@@ -876,9 +871,55 @@ def _process_definition(self, item):
|
||||
else:
|
||||
self.spec_lists[name] = user_specs
|
||||
|
||||
def _process_view(self, env_view: Optional[Union[bool, str, Dict]]):
|
||||
"""Process view option(s), which can be boolean, string, or None.
|
||||
|
||||
A boolean environment view option takes precedence over any that may
|
||||
be included. So ``view: True`` results in the default view only. And
|
||||
``view: False`` means the environment will have no view.
|
||||
|
||||
Args:
|
||||
env_view: view option provided in the manifest or configuration
|
||||
"""
|
||||
|
||||
def add_view(name, values):
|
||||
"""Add the view with the name and the string or dict values."""
|
||||
if isinstance(values, str):
|
||||
self.views[name] = ViewDescriptor(self.path, values)
|
||||
elif isinstance(values, dict):
|
||||
self.views[name] = ViewDescriptor.from_dict(self.path, values)
|
||||
else:
|
||||
tty.error(f"Cannot add view named {name} for {type(values)} values {values}")
|
||||
|
||||
# If the configuration specifies 'view: False' then we are done
|
||||
# processing views. If this is called with the environment's view
|
||||
# view (versus an included view), then there are to be NO views.
|
||||
if env_view is False:
|
||||
return
|
||||
|
||||
# If the configuration specifies 'view: True' then only the default
|
||||
# view will be created for the environment and we are done processing
|
||||
# views.
|
||||
if env_view is True:
|
||||
add_view(default_view_name, self.view_path_default)
|
||||
return
|
||||
|
||||
# Otherwise, the configuration has a subdirectory or dictionary.
|
||||
if isinstance(env_view, str):
|
||||
add_view(default_view_name, env_view)
|
||||
elif env_view:
|
||||
for name, values in env_view.items():
|
||||
add_view(name, values)
|
||||
|
||||
# If we reach this point without an explicit view option then we
|
||||
# provide the default view.
|
||||
if self.views == dict():
|
||||
self.views[default_view_name] = ViewDescriptor(self.path, self.view_path_default)
|
||||
|
||||
def _construct_state_from_manifest(self):
|
||||
"""Set up user specs and views from the manifest file."""
|
||||
self.spec_lists = collections.OrderedDict()
|
||||
self.views = {}
|
||||
|
||||
for item in spack.config.get("definitions", []):
|
||||
self._process_definition(item)
|
||||
@@ -890,20 +931,7 @@ def _construct_state_from_manifest(self):
|
||||
)
|
||||
self.spec_lists[user_speclist_name] = user_specs
|
||||
|
||||
enable_view = env_configuration.get("view")
|
||||
# enable_view can be boolean, string, or None
|
||||
if enable_view is True or enable_view is None:
|
||||
self.views = {default_view_name: ViewDescriptor(self.path, self.view_path_default)}
|
||||
elif isinstance(enable_view, str):
|
||||
self.views = {default_view_name: ViewDescriptor(self.path, enable_view)}
|
||||
elif enable_view:
|
||||
path = self.path
|
||||
self.views = dict(
|
||||
(name, ViewDescriptor.from_dict(path, values))
|
||||
for name, values in enable_view.items()
|
||||
)
|
||||
else:
|
||||
self.views = {}
|
||||
self._process_view(spack.config.get("view", True))
|
||||
|
||||
@property
|
||||
def user_specs(self):
|
||||
@@ -1457,44 +1485,6 @@ def _concretize_separately(self, tests=False):
|
||||
]
|
||||
return results
|
||||
|
||||
def concretize_and_add(self, user_spec, concrete_spec=None, tests=False):
|
||||
"""Concretize and add a single spec to the environment.
|
||||
|
||||
Concretize the provided ``user_spec`` and add it along with the
|
||||
concretized result to the environment. If the given ``user_spec`` was
|
||||
already present in the environment, this does not add a duplicate.
|
||||
The concretized spec will be added unless the ``user_spec`` was
|
||||
already present and an associated concrete spec was already present.
|
||||
|
||||
Args:
|
||||
concrete_spec: if provided, then it is assumed that it is the
|
||||
result of concretizing the provided ``user_spec``
|
||||
"""
|
||||
if self.unify is True:
|
||||
msg = (
|
||||
"cannot install a single spec in an environment that is "
|
||||
"configured to be concretized together. Run instead:\n\n"
|
||||
" $ spack add <spec>\n"
|
||||
" $ spack install\n"
|
||||
)
|
||||
raise SpackEnvironmentError(msg)
|
||||
|
||||
spec = Spec(user_spec)
|
||||
|
||||
if self.add(spec):
|
||||
concrete = concrete_spec or spec.concretized(tests=tests)
|
||||
self._add_concrete_spec(spec, concrete)
|
||||
else:
|
||||
# spec might be in the user_specs, but not installed.
|
||||
# TODO: Redo name-based comparison for old style envs
|
||||
spec = next(s for s in self.user_specs if s.satisfies(user_spec))
|
||||
concrete = self.specs_by_hash.get(spec.dag_hash())
|
||||
if not concrete:
|
||||
concrete = spec.concretized(tests=tests)
|
||||
self._add_concrete_spec(spec, concrete)
|
||||
|
||||
return concrete
|
||||
|
||||
@property
|
||||
def default_view(self):
|
||||
if not self.has_view(default_view_name):
|
||||
@@ -2062,7 +2052,6 @@ def write(self, regenerate: bool = True) -> None:
|
||||
|
||||
if regenerate:
|
||||
self.regenerate_views()
|
||||
spack.hooks.post_env_write(self)
|
||||
|
||||
self.new_specs.clear()
|
||||
|
||||
|
@@ -697,7 +697,6 @@ def __str__(self):
|
||||
|
||||
@fetcher
|
||||
class GitFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
"""
|
||||
Fetch strategy that gets source code from a git repository.
|
||||
Use like this in a package:
|
||||
@@ -930,9 +929,12 @@ def clone(self, dest=None, commit=None, branch=None, tag=None, bare=False):
|
||||
git_commands = []
|
||||
submodules = self.submodules
|
||||
if callable(submodules):
|
||||
submodules = list(submodules(self.package))
|
||||
git_commands.append(["submodule", "init", "--"] + submodules)
|
||||
git_commands.append(["submodule", "update", "--recursive"])
|
||||
submodules = submodules(self.package)
|
||||
if submodules:
|
||||
if isinstance(submodules, str):
|
||||
submodules = [submodules]
|
||||
git_commands.append(["submodule", "init", "--"] + submodules)
|
||||
git_commands.append(["submodule", "update", "--recursive"])
|
||||
elif submodules:
|
||||
git_commands.append(["submodule", "update", "--init", "--recursive"])
|
||||
|
||||
@@ -1089,7 +1091,6 @@ def __str__(self):
|
||||
|
||||
@fetcher
|
||||
class SvnFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
"""Fetch strategy that gets source code from a subversion repository.
|
||||
Use like this in a package:
|
||||
|
||||
@@ -1184,7 +1185,6 @@ def __str__(self):
|
||||
|
||||
@fetcher
|
||||
class HgFetchStrategy(VCSFetchStrategy):
|
||||
|
||||
"""
|
||||
Fetch strategy that gets source code from a Mercurial repository.
|
||||
Use like this in a package:
|
||||
|
@@ -32,6 +32,7 @@
|
||||
from llnl.util.tty.color import colorize
|
||||
|
||||
import spack.config
|
||||
import spack.paths
|
||||
import spack.projections
|
||||
import spack.relocate
|
||||
import spack.schema.projections
|
||||
@@ -91,16 +92,16 @@ def view_copy(src: str, dst: str, view, spec: Optional[spack.spec.Spec] = None):
|
||||
prefix_to_projection[spack.store.STORE.layout.root] = view._root
|
||||
|
||||
# This is vestigial code for the *old* location of sbang.
|
||||
prefix_to_projection[
|
||||
"#!/bin/bash {0}/bin/sbang".format(spack.paths.spack_root)
|
||||
] = sbang.sbang_shebang_line()
|
||||
prefix_to_projection[f"#!/bin/bash {spack.paths.spack_root}/bin/sbang"] = (
|
||||
sbang.sbang_shebang_line()
|
||||
)
|
||||
|
||||
spack.relocate.relocate_text(files=[dst], prefixes=prefix_to_projection)
|
||||
|
||||
try:
|
||||
os.chown(dst, src_stat.st_uid, src_stat.st_gid)
|
||||
except OSError:
|
||||
tty.debug("Can't change the permissions for %s" % dst)
|
||||
tty.debug(f"Can't change the permissions for {dst}")
|
||||
|
||||
|
||||
def view_func_parser(parsed_name):
|
||||
@@ -112,7 +113,7 @@ def view_func_parser(parsed_name):
|
||||
elif parsed_name in ("add", "symlink", "soft"):
|
||||
return view_symlink
|
||||
else:
|
||||
raise ValueError("invalid link type for view: '%s'" % parsed_name)
|
||||
raise ValueError(f"invalid link type for view: '{parsed_name}'")
|
||||
|
||||
|
||||
def inverse_view_func_parser(view_type):
|
||||
@@ -270,9 +271,10 @@ def __init__(self, root, layout, **kwargs):
|
||||
# Ensure projections are the same from each source
|
||||
# Read projections file from view
|
||||
if self.projections != self.read_projections():
|
||||
msg = "View at %s has projections file" % self._root
|
||||
msg += " which does not match projections passed manually."
|
||||
raise ConflictingProjectionsError(msg)
|
||||
raise ConflictingProjectionsError(
|
||||
f"View at {self._root} has projections file"
|
||||
" which does not match projections passed manually."
|
||||
)
|
||||
|
||||
self._croot = colorize_root(self._root) + " "
|
||||
|
||||
@@ -313,11 +315,11 @@ def add_specs(self, *specs, **kwargs):
|
||||
|
||||
def add_standalone(self, spec):
|
||||
if spec.external:
|
||||
tty.warn(self._croot + "Skipping external package: %s" % colorize_spec(spec))
|
||||
tty.warn(f"{self._croot}Skipping external package: {colorize_spec(spec)}")
|
||||
return True
|
||||
|
||||
if self.check_added(spec):
|
||||
tty.warn(self._croot + "Skipping already linked package: %s" % colorize_spec(spec))
|
||||
tty.warn(f"{self._croot}Skipping already linked package: {colorize_spec(spec)}")
|
||||
return True
|
||||
|
||||
self.merge(spec)
|
||||
@@ -325,7 +327,7 @@ def add_standalone(self, spec):
|
||||
self.link_meta_folder(spec)
|
||||
|
||||
if self.verbose:
|
||||
tty.info(self._croot + "Linked package: %s" % colorize_spec(spec))
|
||||
tty.info(f"{self._croot}Linked package: {colorize_spec(spec)}")
|
||||
return True
|
||||
|
||||
def merge(self, spec, ignore=None):
|
||||
@@ -393,7 +395,7 @@ def needs_file(spec, file):
|
||||
|
||||
for file in files:
|
||||
if not os.path.lexists(file):
|
||||
tty.warn("Tried to remove %s which does not exist" % file)
|
||||
tty.warn(f"Tried to remove {file} which does not exist")
|
||||
continue
|
||||
|
||||
# remove if file is not owned by any other package in the view
|
||||
@@ -404,7 +406,7 @@ def needs_file(spec, file):
|
||||
# we are currently removing, as we remove files before unlinking the
|
||||
# metadata directory.
|
||||
if len([s for s in specs if needs_file(s, file)]) <= 1:
|
||||
tty.debug("Removing file " + file)
|
||||
tty.debug(f"Removing file {file}")
|
||||
os.remove(file)
|
||||
|
||||
def check_added(self, spec):
|
||||
@@ -477,14 +479,14 @@ def remove_standalone(self, spec):
|
||||
Remove (unlink) a standalone package from this view.
|
||||
"""
|
||||
if not self.check_added(spec):
|
||||
tty.warn(self._croot + "Skipping package not linked in view: %s" % spec.name)
|
||||
tty.warn(f"{self._croot}Skipping package not linked in view: {spec.name}")
|
||||
return
|
||||
|
||||
self.unmerge(spec)
|
||||
self.unlink_meta_folder(spec)
|
||||
|
||||
if self.verbose:
|
||||
tty.info(self._croot + "Removed package: %s" % colorize_spec(spec))
|
||||
tty.info(f"{self._croot}Removed package: {colorize_spec(spec)}")
|
||||
|
||||
def get_projection_for_spec(self, spec):
|
||||
"""
|
||||
@@ -558,9 +560,9 @@ def print_conflict(self, spec_active, spec_specified, level="error"):
|
||||
linked = tty.color.colorize(" (@gLinked@.)", color=color)
|
||||
specified = tty.color.colorize("(@rSpecified@.)", color=color)
|
||||
cprint(
|
||||
self._croot + "Package conflict detected:\n"
|
||||
"%s %s\n" % (linked, colorize_spec(spec_active))
|
||||
+ "%s %s" % (specified, colorize_spec(spec_specified))
|
||||
f"{self._croot}Package conflict detected:\n"
|
||||
f"{linked} {colorize_spec(spec_active)}\n"
|
||||
f"{specified} {colorize_spec(spec_specified)}"
|
||||
)
|
||||
|
||||
def print_status(self, *specs, **kwargs):
|
||||
@@ -572,14 +574,14 @@ def print_status(self, *specs, **kwargs):
|
||||
|
||||
for s, v in zip(specs, in_view):
|
||||
if not v:
|
||||
tty.error(self._croot + "Package not linked: %s" % s.name)
|
||||
tty.error(f"{self._croot}Package not linked: {s.name}")
|
||||
elif s != v:
|
||||
self.print_conflict(v, s, level="warn")
|
||||
|
||||
in_view = list(filter(None, in_view))
|
||||
|
||||
if len(specs) > 0:
|
||||
tty.msg("Packages linked in %s:" % self._croot[:-1])
|
||||
tty.msg(f"Packages linked in {self._croot[:-1]}:")
|
||||
|
||||
# Make a dict with specs keyed by architecture and compiler.
|
||||
index = index_by(specs, ("architecture", "compiler"))
|
||||
@@ -589,20 +591,19 @@ def print_status(self, *specs, **kwargs):
|
||||
if i > 0:
|
||||
print()
|
||||
|
||||
header = "%s{%s} / %s{%s}" % (
|
||||
spack.spec.ARCHITECTURE_COLOR,
|
||||
architecture,
|
||||
spack.spec.COMPILER_COLOR,
|
||||
compiler,
|
||||
header = (
|
||||
f"{spack.spec.ARCHITECTURE_COLOR}{{{architecture}}} "
|
||||
f"/ {spack.spec.COMPILER_COLOR}{{{compiler}}}"
|
||||
)
|
||||
tty.hline(colorize(header), char="-")
|
||||
|
||||
specs = index[(architecture, compiler)]
|
||||
specs.sort()
|
||||
|
||||
format_string = "{name}{@version}"
|
||||
format_string += "{%compiler}{compiler_flags}{variants}"
|
||||
abbreviated = [s.cformat(format_string) for s in specs]
|
||||
abbreviated = [
|
||||
s.cformat("{name}{@version}{%compiler}{compiler_flags}{variants}")
|
||||
for s in specs
|
||||
]
|
||||
|
||||
# Print one spec per line along with prefix path
|
||||
width = max(len(s) for s in abbreviated)
|
||||
@@ -634,22 +635,19 @@ def unlink_meta_folder(self, spec):
|
||||
|
||||
|
||||
class SimpleFilesystemView(FilesystemView):
|
||||
"""A simple and partial implementation of FilesystemView focused on
|
||||
performance and immutable views, where specs cannot be removed after they
|
||||
were added."""
|
||||
"""A simple and partial implementation of FilesystemView focused on performance and immutable
|
||||
views, where specs cannot be removed after they were added."""
|
||||
|
||||
def __init__(self, root, layout, **kwargs):
|
||||
super().__init__(root, layout, **kwargs)
|
||||
|
||||
def _sanity_check_view_projection(self, specs):
|
||||
"""A very common issue is that we end up with two specs of the same
|
||||
package, that project to the same prefix. We want to catch that as
|
||||
early as possible and give a sensible error to the user. Here we use
|
||||
the metadata dir (.spack) projection as a quick test to see whether
|
||||
two specs in the view are going to clash. The metadata dir is used
|
||||
because it's always added by Spack with identical files, so a
|
||||
guaranteed clash that's easily verified."""
|
||||
seen = dict()
|
||||
"""A very common issue is that we end up with two specs of the same package, that project
|
||||
to the same prefix. We want to catch that as early as possible and give a sensible error to
|
||||
the user. Here we use the metadata dir (.spack) projection as a quick test to see whether
|
||||
two specs in the view are going to clash. The metadata dir is used because it's always
|
||||
added by Spack with identical files, so a guaranteed clash that's easily verified."""
|
||||
seen = {}
|
||||
for current_spec in specs:
|
||||
metadata_dir = self.relative_metadata_dir_for_spec(current_spec)
|
||||
conflicting_spec = seen.get(metadata_dir)
|
||||
@@ -657,7 +655,8 @@ def _sanity_check_view_projection(self, specs):
|
||||
raise ConflictingSpecsError(current_spec, conflicting_spec)
|
||||
seen[metadata_dir] = current_spec
|
||||
|
||||
def add_specs(self, *specs, **kwargs):
|
||||
def add_specs(self, *specs: spack.spec.Spec) -> None:
|
||||
"""Link a root-to-leaf topologically ordered list of specs into the view."""
|
||||
assert all((s.concrete for s in specs))
|
||||
if len(specs) == 0:
|
||||
return
|
||||
@@ -668,9 +667,6 @@ def add_specs(self, *specs, **kwargs):
|
||||
tty.warn("Skipping external package: " + s.short_spec)
|
||||
specs = [s for s in specs if not s.external]
|
||||
|
||||
if kwargs.get("exclude", None):
|
||||
specs = set(filter_exclude(specs, kwargs["exclude"]))
|
||||
|
||||
self._sanity_check_view_projection(specs)
|
||||
|
||||
# Ignore spack meta data folder.
|
||||
@@ -695,13 +691,11 @@ def skip_list(file):
|
||||
# Inform about file-file conflicts.
|
||||
if visitor.file_conflicts:
|
||||
if self.ignore_conflicts:
|
||||
tty.debug("{0} file conflicts".format(len(visitor.file_conflicts)))
|
||||
tty.debug(f"{len(visitor.file_conflicts)} file conflicts")
|
||||
else:
|
||||
raise MergeConflictSummary(visitor.file_conflicts)
|
||||
|
||||
tty.debug(
|
||||
"Creating {0} dirs and {1} links".format(len(visitor.directories), len(visitor.files))
|
||||
)
|
||||
tty.debug(f"Creating {len(visitor.directories)} dirs and {len(visitor.files)} links")
|
||||
|
||||
# Make the directory structure
|
||||
for dst in visitor.directories:
|
||||
|
@@ -15,13 +15,6 @@
|
||||
* post_install(spec, explicit)
|
||||
* pre_uninstall(spec)
|
||||
* post_uninstall(spec)
|
||||
* on_install_start(spec)
|
||||
* on_install_success(spec)
|
||||
* on_install_failure(spec)
|
||||
* on_phase_success(pkg, phase_name, log_file)
|
||||
* on_phase_error(pkg, phase_name, log_file)
|
||||
* on_phase_error(pkg, phase_name, log_file)
|
||||
* post_env_write(env)
|
||||
|
||||
This can be used to implement support for things like module
|
||||
systems (e.g. modules, lmod, etc.) or to add other custom
|
||||
@@ -78,17 +71,5 @@ def __call__(self, *args, **kwargs):
|
||||
pre_install = _HookRunner("pre_install")
|
||||
post_install = _HookRunner("post_install")
|
||||
|
||||
# These hooks are run within an install subprocess
|
||||
pre_uninstall = _HookRunner("pre_uninstall")
|
||||
post_uninstall = _HookRunner("post_uninstall")
|
||||
on_phase_success = _HookRunner("on_phase_success")
|
||||
on_phase_error = _HookRunner("on_phase_error")
|
||||
|
||||
# These are hooks in installer.py, before starting install subprocess
|
||||
on_install_start = _HookRunner("on_install_start")
|
||||
on_install_success = _HookRunner("on_install_success")
|
||||
on_install_failure = _HookRunner("on_install_failure")
|
||||
on_install_cancel = _HookRunner("on_install_cancel")
|
||||
|
||||
# Environment hooks
|
||||
post_env_write = _HookRunner("post_env_write")
|
||||
|
@@ -4,7 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import os
|
||||
from typing import IO, Optional, Tuple
|
||||
from typing import BinaryIO, Optional, Tuple
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import BaseDirectoryVisitor, visit_directory_tree
|
||||
@@ -18,7 +18,7 @@ def should_keep(path: bytes) -> bool:
|
||||
return path.startswith(b"$") or (os.path.isabs(path) and os.path.lexists(path))
|
||||
|
||||
|
||||
def _drop_redundant_rpaths(f: IO) -> Optional[Tuple[bytes, bytes]]:
|
||||
def _drop_redundant_rpaths(f: BinaryIO) -> Optional[Tuple[bytes, bytes]]:
|
||||
"""Drop redundant entries from rpath.
|
||||
|
||||
Args:
|
||||
|
@@ -1705,7 +1705,6 @@ def _install_task(self, task: BuildTask, install_status: InstallStatus) -> None:
|
||||
except spack.build_environment.StopPhase as e:
|
||||
# A StopPhase exception means that do_install was asked to
|
||||
# stop early from clients, and is not an error at this point
|
||||
spack.hooks.on_install_failure(task.request.pkg.spec)
|
||||
pid = f"{self.pid}: " if tty.show_pid() else ""
|
||||
tty.debug(f"{pid}{str(e)}")
|
||||
tty.debug(f"Package stage directory: {pkg.stage.source_path}")
|
||||
@@ -2011,7 +2010,6 @@ def install(self) -> None:
|
||||
if task is None:
|
||||
continue
|
||||
|
||||
spack.hooks.on_install_start(task.request.pkg.spec)
|
||||
install_args = task.request.install_args
|
||||
keep_prefix = install_args.get("keep_prefix")
|
||||
|
||||
@@ -2037,9 +2035,6 @@ def install(self) -> None:
|
||||
tty.warn(f"{pkg_id} does NOT actually have any uninstalled deps left")
|
||||
dep_str = "dependencies" if task.priority > 1 else "dependency"
|
||||
|
||||
# Hook to indicate task failure, but without an exception
|
||||
spack.hooks.on_install_failure(task.request.pkg.spec)
|
||||
|
||||
raise InstallError(
|
||||
f"Cannot proceed with {pkg_id}: {task.priority} uninstalled "
|
||||
f"{dep_str}: {','.join(task.uninstalled_deps)}",
|
||||
@@ -2062,11 +2057,6 @@ def install(self) -> None:
|
||||
tty.warn(f"{pkg_id} failed to install")
|
||||
self._update_failed(task)
|
||||
|
||||
# Mark that the package failed
|
||||
# TODO: this should also be for the task.pkg, but we don't
|
||||
# model transitive yet.
|
||||
spack.hooks.on_install_failure(task.request.pkg.spec)
|
||||
|
||||
if self.fail_fast:
|
||||
raise InstallError(fail_fast_err, pkg=pkg)
|
||||
|
||||
@@ -2169,7 +2159,6 @@ def install(self) -> None:
|
||||
tty.error(
|
||||
f"Failed to install {pkg.name} due to " f"{exc.__class__.__name__}: {str(exc)}"
|
||||
)
|
||||
spack.hooks.on_install_cancel(task.request.pkg.spec)
|
||||
raise
|
||||
|
||||
except binary_distribution.NoChecksumException as exc:
|
||||
@@ -2188,7 +2177,6 @@ def install(self) -> None:
|
||||
|
||||
except (Exception, SystemExit) as exc:
|
||||
self._update_failed(task, True, exc)
|
||||
spack.hooks.on_install_failure(task.request.pkg.spec)
|
||||
|
||||
# Best effort installs suppress the exception and mark the
|
||||
# package as a failure.
|
||||
@@ -2372,9 +2360,6 @@ def run(self) -> bool:
|
||||
_print_timer(pre=self.pre, pkg_id=self.pkg_id, timer=self.timer)
|
||||
_print_installed_pkg(self.pkg.prefix)
|
||||
|
||||
# Send final status that install is successful
|
||||
spack.hooks.on_install_success(self.pkg.spec)
|
||||
|
||||
# preserve verbosity across runs
|
||||
return self.echo
|
||||
|
||||
@@ -2453,15 +2438,10 @@ def _real_install(self) -> None:
|
||||
# Catch any errors to report to logging
|
||||
self.timer.start(phase_fn.name)
|
||||
phase_fn.execute()
|
||||
spack.hooks.on_phase_success(pkg, phase_fn.name, log_file)
|
||||
self.timer.stop(phase_fn.name)
|
||||
|
||||
except BaseException:
|
||||
combine_phase_logs(pkg.phase_log_files, pkg.log_path)
|
||||
spack.hooks.on_phase_error(pkg, phase_fn.name, log_file)
|
||||
|
||||
# phase error indicates install error
|
||||
spack.hooks.on_install_failure(pkg.spec)
|
||||
raise
|
||||
|
||||
# We assume loggers share echo True/False
|
||||
|
@@ -950,14 +950,10 @@ def _main(argv=None):
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
# -h, -H, and -V are special as they do not require a command, but
|
||||
# all the other options do nothing without a command.
|
||||
# version is special as it does not require a command or loading and additional infrastructure
|
||||
if args.version:
|
||||
print(get_version())
|
||||
return 0
|
||||
elif args.help:
|
||||
sys.stdout.write(parser.format_help(level=args.help))
|
||||
return 0
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# This part of the `main()` sets up Spack's configuration.
|
||||
@@ -996,6 +992,12 @@ def _main(argv=None):
|
||||
print_setup_info(*args.print_shell_vars.split(","))
|
||||
return 0
|
||||
|
||||
# -h and -H are special as they do not require a command, but
|
||||
# all the other options do nothing without a command.
|
||||
if args.help:
|
||||
sys.stdout.write(parser.format_help(level=args.help))
|
||||
return 0
|
||||
|
||||
# At this point we've considered all the options to spack itself, so we
|
||||
# need a command or we're done.
|
||||
if not args.command:
|
||||
@@ -1038,9 +1040,9 @@ def finish_parse_and_run(parser, cmd_name, main_args, env_format_error):
|
||||
set_working_dir()
|
||||
|
||||
# now we can actually execute the command.
|
||||
if args.spack_profile or args.sorted_profile:
|
||||
if main_args.spack_profile or main_args.sorted_profile:
|
||||
_profile_wrapper(command, parser, args, unknown)
|
||||
elif args.pdb:
|
||||
elif main_args.pdb:
|
||||
import pdb
|
||||
|
||||
pdb.runctx("_invoke_command(command, parser, args, unknown)", globals(), locals())
|
||||
|
@@ -161,7 +161,7 @@ def upload_blob(
|
||||
|
||||
def upload_manifest(
|
||||
ref: ImageReference,
|
||||
oci_manifest: dict,
|
||||
manifest: dict,
|
||||
tag: bool = True,
|
||||
_urlopen: spack.oci.opener.MaybeOpen = None,
|
||||
):
|
||||
@@ -169,7 +169,7 @@ def upload_manifest(
|
||||
|
||||
Args:
|
||||
ref: The image reference.
|
||||
oci_manifest: The OCI manifest or index.
|
||||
manifest: The manifest or index.
|
||||
tag: When true, use the tag, otherwise use the digest,
|
||||
this is relevant for multi-arch images, where the
|
||||
tag is an index, referencing the manifests by digest.
|
||||
@@ -179,7 +179,7 @@ def upload_manifest(
|
||||
"""
|
||||
_urlopen = _urlopen or spack.oci.opener.urlopen
|
||||
|
||||
data = json.dumps(oci_manifest, separators=(",", ":")).encode()
|
||||
data = json.dumps(manifest, separators=(",", ":")).encode()
|
||||
digest = Digest.from_sha256(hashlib.sha256(data).hexdigest())
|
||||
size = len(data)
|
||||
|
||||
@@ -190,7 +190,7 @@ def upload_manifest(
|
||||
url=ref.manifest_url(),
|
||||
method="PUT",
|
||||
data=data,
|
||||
headers={"Content-Type": oci_manifest["mediaType"]},
|
||||
headers={"Content-Type": manifest["mediaType"]},
|
||||
)
|
||||
|
||||
response = _urlopen(request)
|
||||
|
@@ -9,6 +9,8 @@
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
from llnl.util import tty
|
||||
|
||||
from spack.error import SpackError
|
||||
from spack.util import windows_registry as winreg
|
||||
from spack.version import Version
|
||||
@@ -83,11 +85,50 @@ def compiler_search_paths(self):
|
||||
os.path.join(str(os.getenv("ONEAPI_ROOT")), "compiler", "*", "windows", "bin")
|
||||
)
|
||||
)
|
||||
|
||||
# Second strategy: Find MSVC via the registry
|
||||
msft = winreg.WindowsRegistryView(
|
||||
"SOFTWARE\\WOW6432Node\\Microsoft", winreg.HKEY.HKEY_LOCAL_MACHINE
|
||||
)
|
||||
vs_entries = msft.find_subkeys(r"VisualStudio_.*")
|
||||
def try_query_registry(retry=False):
|
||||
winreg_report_error = lambda e: tty.debug(
|
||||
'Windows registry query on "SOFTWARE\\WOW6432Node\\Microsoft"'
|
||||
f"under HKEY_LOCAL_MACHINE: {str(e)}"
|
||||
)
|
||||
try:
|
||||
# Registry interactions are subject to race conditions, etc and can generally
|
||||
# be flakey, do this in a catch block to prevent reg issues from interfering
|
||||
# with compiler detection
|
||||
msft = winreg.WindowsRegistryView(
|
||||
"SOFTWARE\\WOW6432Node\\Microsoft", winreg.HKEY.HKEY_LOCAL_MACHINE
|
||||
)
|
||||
return msft.find_subkeys(r"VisualStudio_.*", recursive=False)
|
||||
except OSError as e:
|
||||
# OSErrors propagated into caller by Spack's registry module are expected
|
||||
# and indicate a known issue with the registry query
|
||||
# i.e. user does not have permissions or the key/value
|
||||
# doesn't exist
|
||||
winreg_report_error(e)
|
||||
return []
|
||||
except winreg.InvalidRegistryOperation as e:
|
||||
# Other errors raised by the Spack's reg module indicate
|
||||
# an unexpected error type, and are handled specifically
|
||||
# as the underlying cause is difficult/impossible to determine
|
||||
# without manually exploring the registry
|
||||
# These errors can also be spurious (race conditions)
|
||||
# and may resolve on re-execution of the query
|
||||
# or are permanent (specific types of permission issues)
|
||||
# but the registry raises the same exception for all types of
|
||||
# atypical errors
|
||||
if retry:
|
||||
winreg_report_error(e)
|
||||
return []
|
||||
|
||||
vs_entries = try_query_registry()
|
||||
if not vs_entries:
|
||||
# Occasional spurious race conditions can arise when reading the MS reg
|
||||
# typically these race conditions resolve immediately and we can safely
|
||||
# retry the reg query without waiting
|
||||
# Note: Winreg does not support locking
|
||||
vs_entries = try_query_registry(retry=True)
|
||||
|
||||
vs_paths = []
|
||||
|
||||
def clean_vs_path(path):
|
||||
@@ -99,11 +140,8 @@ def clean_vs_path(path):
|
||||
val = entry.get_subkey("Capabilities").get_value("ApplicationDescription").value
|
||||
vs_paths.append(clean_vs_path(val))
|
||||
except FileNotFoundError as e:
|
||||
if hasattr(e, "winerror"):
|
||||
if e.winerror == 2:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
if hasattr(e, "winerror") and e.winerror == 2:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
@@ -566,6 +566,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta):
|
||||
provided: Dict["spack.spec.Spec", Set["spack.spec.Spec"]]
|
||||
provided_together: Dict["spack.spec.Spec", List[Set[str]]]
|
||||
patches: Dict["spack.spec.Spec", List["spack.patch.Patch"]]
|
||||
variants: Dict[str, Tuple["spack.variant.Variant", "spack.spec.Spec"]]
|
||||
|
||||
#: By default, packages are not virtual
|
||||
#: Virtual packages override this attribute
|
||||
|
@@ -3,6 +3,8 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import spack.util.path
|
||||
|
||||
|
||||
def get_projection(projections, spec):
|
||||
"""
|
||||
@@ -11,7 +13,7 @@ def get_projection(projections, spec):
|
||||
all_projection = None
|
||||
for spec_like, projection in projections.items():
|
||||
if spec.satisfies(spec_like):
|
||||
return projection
|
||||
return spack.util.path.substitute_path_variables(projection)
|
||||
elif spec_like == "all":
|
||||
all_projection = projection
|
||||
all_projection = spack.util.path.substitute_path_variables(projection)
|
||||
return all_projection
|
||||
|
@@ -7,6 +7,7 @@
|
||||
import os
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from typing import List, Optional
|
||||
|
||||
import macholib.mach_o
|
||||
import macholib.MachO
|
||||
@@ -47,7 +48,7 @@ def __init__(self, file_path, root_path):
|
||||
|
||||
|
||||
@memoized
|
||||
def _patchelf():
|
||||
def _patchelf() -> Optional[executable.Executable]:
|
||||
"""Return the full path to the patchelf binary, if available, else None."""
|
||||
import spack.bootstrap
|
||||
|
||||
@@ -55,9 +56,7 @@ def _patchelf():
|
||||
return None
|
||||
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
patchelf = spack.bootstrap.ensure_patchelf_in_path_or_raise()
|
||||
|
||||
return patchelf.path
|
||||
return spack.bootstrap.ensure_patchelf_in_path_or_raise()
|
||||
|
||||
|
||||
def _elf_rpaths_for(path):
|
||||
@@ -340,31 +339,34 @@ def macholib_get_paths(cur_path):
|
||||
return (rpaths, deps, ident)
|
||||
|
||||
|
||||
def _set_elf_rpaths(target, rpaths):
|
||||
"""Replace the original RPATH of the target with the paths passed
|
||||
as arguments.
|
||||
def _set_elf_rpaths_and_interpreter(
|
||||
target: str, rpaths: List[str], interpreter: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
"""Replace the original RPATH of the target with the paths passed as arguments.
|
||||
|
||||
Args:
|
||||
target: target executable. Must be an ELF object.
|
||||
rpaths: paths to be set in the RPATH
|
||||
interpreter: optionally set the interpreter
|
||||
|
||||
Returns:
|
||||
A string concatenating the stdout and stderr of the call
|
||||
to ``patchelf`` if it was invoked
|
||||
A string concatenating the stdout and stderr of the call to ``patchelf`` if it was invoked
|
||||
"""
|
||||
# Join the paths using ':' as a separator
|
||||
rpaths_str = ":".join(rpaths)
|
||||
|
||||
patchelf, output = executable.Executable(_patchelf()), None
|
||||
try:
|
||||
# TODO: error handling is not great here?
|
||||
# TODO: revisit the use of --force-rpath as it might be conditional
|
||||
# TODO: if we want to support setting RUNPATH from binary packages
|
||||
patchelf_args = ["--force-rpath", "--set-rpath", rpaths_str, target]
|
||||
output = patchelf(*patchelf_args, output=str, error=str)
|
||||
args = ["--force-rpath", "--set-rpath", rpaths_str]
|
||||
if interpreter:
|
||||
args.extend(["--set-interpreter", interpreter])
|
||||
args.append(target)
|
||||
return _patchelf()(*args, output=str, error=str)
|
||||
except executable.ProcessError as e:
|
||||
msg = "patchelf --force-rpath --set-rpath {0} failed with error {1}"
|
||||
tty.warn(msg.format(target, e))
|
||||
return output
|
||||
tty.warn(str(e))
|
||||
return None
|
||||
|
||||
|
||||
def needs_binary_relocation(m_type, m_subtype):
|
||||
@@ -501,10 +503,12 @@ def new_relocate_elf_binaries(binaries, prefix_to_prefix):
|
||||
|
||||
for path in binaries:
|
||||
try:
|
||||
elf.replace_rpath_in_place_or_raise(path, prefix_to_prefix)
|
||||
except elf.ElfDynamicSectionUpdateFailed as e:
|
||||
# Fall back to the old `patchelf --set-rpath` method.
|
||||
_set_elf_rpaths(path, e.new.decode("utf-8").split(":"))
|
||||
elf.substitute_rpath_and_pt_interp_in_place_or_raise(path, prefix_to_prefix)
|
||||
except elf.ElfCStringUpdatesFailed as e:
|
||||
# Fall back to `patchelf --set-rpath ... --set-interpreter ...`
|
||||
rpaths = e.rpath.new_value.decode("utf-8").split(":") if e.rpath else []
|
||||
interpreter = e.pt_interp.new_value.decode("utf-8") if e.pt_interp else None
|
||||
_set_elf_rpaths_and_interpreter(path, rpaths=rpaths, interpreter=interpreter)
|
||||
|
||||
|
||||
def relocate_elf_binaries(
|
||||
@@ -546,10 +550,10 @@ def relocate_elf_binaries(
|
||||
new_rpaths = _make_relative(new_binary, new_root, new_norm_rpaths)
|
||||
# check to see if relative rpaths are changed before rewriting
|
||||
if sorted(new_rpaths) != sorted(orig_rpaths):
|
||||
_set_elf_rpaths(new_binary, new_rpaths)
|
||||
_set_elf_rpaths_and_interpreter(new_binary, new_rpaths)
|
||||
else:
|
||||
new_rpaths = _transform_rpaths(orig_rpaths, orig_root, new_prefixes)
|
||||
_set_elf_rpaths(new_binary, new_rpaths)
|
||||
_set_elf_rpaths_and_interpreter(new_binary, new_rpaths)
|
||||
|
||||
|
||||
def make_link_relative(new_links, orig_links):
|
||||
@@ -596,7 +600,7 @@ def make_elf_binaries_relative(new_binaries, orig_binaries, orig_layout_root):
|
||||
orig_rpaths = _elf_rpaths_for(new_binary)
|
||||
if orig_rpaths:
|
||||
new_rpaths = _make_relative(orig_binary, orig_layout_root, orig_rpaths)
|
||||
_set_elf_rpaths(new_binary, new_rpaths)
|
||||
_set_elf_rpaths_and_interpreter(new_binary, new_rpaths)
|
||||
|
||||
|
||||
def warn_if_link_cant_be_relocated(link, target):
|
||||
|
@@ -6,7 +6,6 @@
|
||||
import warnings
|
||||
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty
|
||||
|
||||
|
||||
# jsonschema is imported lazily as it is heavy to import
|
||||
@@ -62,25 +61,3 @@ def _deprecated_properties(validator, deprecated, instance, schema):
|
||||
|
||||
|
||||
Validator = llnl.util.lang.Singleton(_make_validator)
|
||||
|
||||
spec_list_schema = {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"matrix": {
|
||||
"type": "array",
|
||||
"items": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"exclude": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
{"type": "string"},
|
||||
{"type": "null"},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
@@ -3,16 +3,17 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
"""Schema for bootstrap.yaml configuration file."""
|
||||
from typing import Any, Dict
|
||||
|
||||
#: Schema of a single source
|
||||
_source_schema = {
|
||||
_source_schema: Dict[str, Any] = {
|
||||
"type": "object",
|
||||
"properties": {"name": {"type": "string"}, "metadata": {"type": "string"}},
|
||||
"additionalProperties": False,
|
||||
"required": ["name", "metadata"],
|
||||
}
|
||||
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"bootstrap": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@@ -6,27 +6,31 @@
|
||||
"""Schema for a buildcache spec.yaml file
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/buildcache_spec.py
|
||||
:lines: 13-
|
||||
:lines: 15-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
import spack.schema.spec
|
||||
|
||||
properties: Dict[str, Any] = {
|
||||
# `buildinfo` is no longer needed as of Spack 0.21
|
||||
"buildinfo": {"type": "object"},
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"additionalProperties": True,
|
||||
"items": spack.schema.spec.properties,
|
||||
},
|
||||
"binary_cache_checksum": {
|
||||
"type": "object",
|
||||
"properties": {"hash_algorithm": {"type": "string"}, "hash": {"type": "string"}},
|
||||
},
|
||||
"buildcache_layout_version": {"type": "number"},
|
||||
}
|
||||
|
||||
schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Spack buildcache specfile schema",
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
# `buildinfo` is no longer needed as of Spack 0.21
|
||||
"buildinfo": {"type": "object"},
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"additionalProperties": True,
|
||||
"items": spack.schema.spec.properties,
|
||||
},
|
||||
"binary_cache_checksum": {
|
||||
"type": "object",
|
||||
"properties": {"hash_algorithm": {"type": "string"}, "hash": {"type": "string"}},
|
||||
},
|
||||
"buildcache_layout_version": {"type": "number"},
|
||||
},
|
||||
"properties": properties,
|
||||
}
|
||||
|
@@ -2,16 +2,15 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Schema for cdash.yaml configuration file.
|
||||
|
||||
.. literalinclude:: ../spack/schema/cdash.py
|
||||
:lines: 13-
|
||||
"""
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"cdash": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
|
@@ -2,12 +2,12 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Schema for gitlab-ci.yaml configuration file.
|
||||
|
||||
.. literalinclude:: ../spack/schema/ci.py
|
||||
:lines: 13-
|
||||
:lines: 16-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
from llnl.util.lang import union_dicts
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
}
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"ci": {
|
||||
"oneOf": [
|
||||
# TODO: Replace with core-shared-properties in Spack 0.23
|
||||
|
@@ -2,16 +2,17 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Schema for compilers.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/compilers.py
|
||||
:lines: 13-
|
||||
:lines: 15-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
import spack.schema.environment
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"compilers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@@ -2,14 +2,14 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Schema for concretizer.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/concretizer.py
|
||||
:lines: 13-
|
||||
:lines: 12-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"concretizer": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
|
@@ -5,15 +5,16 @@
|
||||
"""Schema for config.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/config.py
|
||||
:lines: 13-
|
||||
:lines: 17-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
from llnl.util.lang import union_dicts
|
||||
|
||||
import spack.schema.projections
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"config": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
"""Schema for the 'container' subsection of Spack environments."""
|
||||
from typing import Any, Dict
|
||||
|
||||
_stages_from_dockerhub = {
|
||||
"type": "object",
|
||||
@@ -85,4 +86,4 @@
|
||||
},
|
||||
}
|
||||
|
||||
properties = {"container": container_schema}
|
||||
properties: Dict[str, Any] = {"container": container_schema}
|
||||
|
@@ -11,112 +11,115 @@
|
||||
This does not specify a configuration - it is an input format
|
||||
that is consumed and transformed into Spack DB records.
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
properties: Dict[str, Any] = {
|
||||
"_meta": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"file-type": {"type": "string", "minLength": 1},
|
||||
"cpe-version": {"type": "string", "minLength": 1},
|
||||
"system-type": {"type": "string", "minLength": 1},
|
||||
"schema-version": {"type": "string", "minLength": 1},
|
||||
# Older schemas use did not have "cpe-version", just the
|
||||
# schema version; in that case it was just called "version"
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
"compilers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1},
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
"prefix": {"type": "string", "minLength": 1},
|
||||
"executables": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"cc": {"type": "string", "minLength": 1},
|
||||
"cxx": {"type": "string", "minLength": 1},
|
||||
"fc": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
"arch": {
|
||||
"type": "object",
|
||||
"required": ["os", "target"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"os": {"type": "string", "minLength": 1},
|
||||
"target": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"specs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "version", "arch", "compiler", "prefix", "hash"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1},
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
"arch": {
|
||||
"type": "object",
|
||||
"required": ["platform", "platform_os", "target"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"platform": {"type": "string", "minLength": 1},
|
||||
"platform_os": {"type": "string", "minLength": 1},
|
||||
"target": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["name"],
|
||||
"properties": {"name": {"type": "string", "minLength": 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"compiler": {
|
||||
"type": "object",
|
||||
"required": ["name", "version"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1},
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"\\w[\\w-]*": {
|
||||
"type": "object",
|
||||
"required": ["hash"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"hash": {"type": "string", "minLength": 1},
|
||||
"type": {
|
||||
"type": "array",
|
||||
"items": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"prefix": {"type": "string", "minLength": 1},
|
||||
"rpm": {"type": "string", "minLength": 1},
|
||||
"hash": {"type": "string", "minLength": 1},
|
||||
"parameters": {"type": "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
schema = {
|
||||
"$schema": "http://json-schema.org/schema#",
|
||||
"title": "CPE manifest schema",
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"file-type": {"type": "string", "minLength": 1},
|
||||
"cpe-version": {"type": "string", "minLength": 1},
|
||||
"system-type": {"type": "string", "minLength": 1},
|
||||
"schema-version": {"type": "string", "minLength": 1},
|
||||
# Older schemas use did not have "cpe-version", just the
|
||||
# schema version; in that case it was just called "version"
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
"compilers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1},
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
"prefix": {"type": "string", "minLength": 1},
|
||||
"executables": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"cc": {"type": "string", "minLength": 1},
|
||||
"cxx": {"type": "string", "minLength": 1},
|
||||
"fc": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
"arch": {
|
||||
"type": "object",
|
||||
"required": ["os", "target"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"os": {"type": "string", "minLength": 1},
|
||||
"target": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"specs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "version", "arch", "compiler", "prefix", "hash"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1},
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
"arch": {
|
||||
"type": "object",
|
||||
"required": ["platform", "platform_os", "target"],
|
||||
"additioanlProperties": False,
|
||||
"properties": {
|
||||
"platform": {"type": "string", "minLength": 1},
|
||||
"platform_os": {"type": "string", "minLength": 1},
|
||||
"target": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["name"],
|
||||
"properties": {"name": {"type": "string", "minLength": 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"compiler": {
|
||||
"type": "object",
|
||||
"required": ["name", "version"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1},
|
||||
"version": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"\\w[\\w-]*": {
|
||||
"type": "object",
|
||||
"required": ["hash"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"hash": {"type": "string", "minLength": 1},
|
||||
"type": {
|
||||
"type": "array",
|
||||
"items": {"type": "string", "minLength": 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"prefix": {"type": "string", "minLength": 1},
|
||||
"rpm": {"type": "string", "minLength": 1},
|
||||
"hash": {"type": "string", "minLength": 1},
|
||||
"parameters": {"type": "object"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"properties": properties,
|
||||
}
|
||||
|
@@ -6,12 +6,41 @@
|
||||
"""Schema for database index.json file
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/database_index.py
|
||||
:lines: 36-
|
||||
:lines: 17-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
import spack.schema.spec
|
||||
|
||||
# spack.schema.spec.properties
|
||||
|
||||
properties: Dict[str, Any] = {
|
||||
"database": {
|
||||
"type": "object",
|
||||
"required": ["installs", "version"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"installs": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
r"^[\w\d]{32}$": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"spec": spack.schema.spec.properties,
|
||||
"path": {"oneOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"installed": {"type": "boolean"},
|
||||
"ref_count": {"type": "integer", "minimum": 0},
|
||||
"explicit": {"type": "boolean"},
|
||||
"installation_time": {"type": "number"},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"version": {"type": "string"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#: Full schema with metadata
|
||||
schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
@@ -19,30 +48,5 @@
|
||||
"type": "object",
|
||||
"required": ["database"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"database": {
|
||||
"type": "object",
|
||||
"required": ["installs", "version"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"installs": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
r"^[\w\d]{32}$": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"spec": spack.schema.spec.properties,
|
||||
"path": {"oneOf": [{"type": "string"}, {"type": "null"}]},
|
||||
"installed": {"type": "boolean"},
|
||||
"ref_count": {"type": "integer", "minimum": 0},
|
||||
"explicit": {"type": "boolean"},
|
||||
"installation_time": {"type": "number"},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"version": {"type": "string"},
|
||||
},
|
||||
}
|
||||
},
|
||||
"properties": properties,
|
||||
}
|
||||
|
@@ -6,20 +6,21 @@
|
||||
"""Schema for definitions
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/definitions.py
|
||||
:lines: 13-
|
||||
:lines: 16-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
import spack.schema
|
||||
from .spec_list import spec_list_schema
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"definitions": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {"when": {"type": "string"}},
|
||||
"patternProperties": {r"^(?!when$)\w*": spack.schema.spec_list_schema},
|
||||
"patternProperties": {r"^(?!when$)\w*": spec_list_schema},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -2,9 +2,9 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"develop": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
|
@@ -6,74 +6,46 @@
|
||||
"""Schema for env.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/env.py
|
||||
:lines: 36-
|
||||
:lines: 19-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
from llnl.util.lang import union_dicts
|
||||
|
||||
import spack.schema.gitlab_ci # DEPRECATED
|
||||
import spack.schema.merged
|
||||
import spack.schema.projections
|
||||
|
||||
from .spec_list import spec_list_schema
|
||||
|
||||
#: Top level key in a manifest file
|
||||
TOP_LEVEL_KEY = "spack"
|
||||
|
||||
projections_scheme = spack.schema.projections.properties["projections"]
|
||||
properties: Dict[str, Any] = {
|
||||
"spack": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"additionalProperties": False,
|
||||
"properties": union_dicts(
|
||||
# Include deprecated "gitlab-ci" section
|
||||
spack.schema.gitlab_ci.properties,
|
||||
# merged configuration scope schemas
|
||||
spack.schema.merged.properties,
|
||||
# extra environment schema properties
|
||||
{
|
||||
"include": {"type": "array", "default": [], "items": {"type": "string"}},
|
||||
"specs": spec_list_schema,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Spack environment file schema",
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"spack": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"additionalProperties": False,
|
||||
"properties": union_dicts(
|
||||
# Include deprecated "gitlab-ci" section
|
||||
spack.schema.gitlab_ci.properties,
|
||||
# merged configuration scope schemas
|
||||
spack.schema.merged.properties,
|
||||
# extra environment schema properties
|
||||
{
|
||||
"include": {"type": "array", "default": [], "items": {"type": "string"}},
|
||||
"specs": spack.schema.spec_list_schema,
|
||||
"view": {
|
||||
"anyOf": [
|
||||
{"type": "boolean"},
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
r"\w+": {
|
||||
"required": ["root"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"root": {"type": "string"},
|
||||
"link": {
|
||||
"type": "string",
|
||||
"pattern": "(roots|all|run)",
|
||||
},
|
||||
"link_type": {"type": "string"},
|
||||
"select": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"exclude": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"projections": projections_scheme,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
},
|
||||
"properties": properties,
|
||||
}
|
||||
|
||||
|
||||
|
@@ -6,6 +6,7 @@
|
||||
schemas.
|
||||
"""
|
||||
import collections.abc
|
||||
from typing import Any, Dict
|
||||
|
||||
array_of_strings_or_num = {
|
||||
"type": "array",
|
||||
@@ -18,7 +19,7 @@
|
||||
"patternProperties": {r"\w[\w-]*": {"anyOf": [{"type": "string"}, {"type": "number"}]}},
|
||||
}
|
||||
|
||||
definition = {
|
||||
definition: Dict[str, Any] = {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"additionalProperties": False,
|
||||
|
@@ -6,8 +6,9 @@
|
||||
"""Schema for gitlab-ci.yaml configuration file.
|
||||
|
||||
.. literalinclude:: ../spack/schema/gitlab_ci.py
|
||||
:lines: 13-
|
||||
:lines: 15-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
from llnl.util.lang import union_dicts
|
||||
|
||||
@@ -35,7 +36,7 @@
|
||||
|
||||
runner_selector_schema = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"additionalProperties": True,
|
||||
"required": ["tags"],
|
||||
"properties": runner_attributes_schema_items,
|
||||
}
|
||||
@@ -112,7 +113,7 @@
|
||||
}
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {"gitlab-ci": gitlab_ci_properties}
|
||||
properties: Dict[str, Any] = {"gitlab-ci": gitlab_ci_properties}
|
||||
|
||||
#: Full schema with metadata
|
||||
schema = {
|
||||
|
@@ -6,8 +6,10 @@
|
||||
"""Schema for configuration merged into one file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/merged.py
|
||||
:lines: 39-
|
||||
:lines: 32-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
from llnl.util.lang import union_dicts
|
||||
|
||||
import spack.schema.bootstrap
|
||||
@@ -24,9 +26,10 @@
|
||||
import spack.schema.packages
|
||||
import spack.schema.repos
|
||||
import spack.schema.upstreams
|
||||
import spack.schema.view
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = union_dicts(
|
||||
properties: Dict[str, Any] = union_dicts(
|
||||
spack.schema.bootstrap.properties,
|
||||
spack.schema.cdash.properties,
|
||||
spack.schema.compilers.properties,
|
||||
@@ -41,6 +44,7 @@
|
||||
spack.schema.packages.properties,
|
||||
spack.schema.repos.properties,
|
||||
spack.schema.upstreams.properties,
|
||||
spack.schema.view.properties,
|
||||
)
|
||||
|
||||
|
||||
|
@@ -6,8 +6,9 @@
|
||||
"""Schema for mirrors.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/mirrors.py
|
||||
:lines: 12-69
|
||||
:lines: 13-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
#: Common properties for connection specification
|
||||
connection = {
|
||||
@@ -50,7 +51,7 @@
|
||||
}
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"mirrors": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
|
@@ -6,8 +6,10 @@
|
||||
"""Schema for modules.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/modules.py
|
||||
:lines: 13-
|
||||
:lines: 16-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
import spack.schema.environment
|
||||
import spack.schema.projections
|
||||
|
||||
@@ -141,7 +143,7 @@
|
||||
|
||||
|
||||
# Properties for inclusion into other schemas (requires definitions)
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"modules": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
|
@@ -5,8 +5,10 @@
|
||||
"""Schema for packages.yaml configuration files.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/packages.py
|
||||
:lines: 13-
|
||||
:lines: 14-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
import spack.schema.environment
|
||||
|
||||
permissions = {
|
||||
@@ -91,7 +93,7 @@
|
||||
REQUIREMENT_URL = "https://spack.readthedocs.io/en/latest/packages_yaml.html#package-requirements"
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"packages": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
|
@@ -6,12 +6,12 @@
|
||||
"""Schema for projections.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/projections.py
|
||||
:lines: 13-
|
||||
:lines: 14-
|
||||
"""
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"projections": {"type": "object", "patternProperties": {r"all|\w[\w-]*": {"type": "string"}}}
|
||||
}
|
||||
|
||||
|
@@ -6,12 +6,14 @@
|
||||
"""Schema for repos.yaml configuration file.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/repos.py
|
||||
:lines: 13-
|
||||
:lines: 14-
|
||||
"""
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {"repos": {"type": "array", "default": [], "items": {"type": "string"}}}
|
||||
properties: Dict[str, Any] = {
|
||||
"repos": {"type": "array", "default": [], "items": {"type": "string"}}
|
||||
}
|
||||
|
||||
|
||||
#: Full schema with metadata
|
||||
|
@@ -8,9 +8,9 @@
|
||||
TODO: This needs to be updated? Especially the hashes under properties.
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/spec.py
|
||||
:lines: 13-
|
||||
:lines: 15-
|
||||
"""
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
target = {
|
||||
"oneOf": [
|
||||
@@ -57,7 +57,7 @@
|
||||
}
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
|
24
lib/spack/spack/schema/spec_list.py
Normal file
24
lib/spack/spack/schema/spec_list.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
matrix_schema = {"type": "array", "items": {"type": "array", "items": {"type": "string"}}}
|
||||
|
||||
spec_list_schema = {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"matrix": matrix_schema,
|
||||
"exclude": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
{"type": "string"},
|
||||
{"type": "null"},
|
||||
]
|
||||
},
|
||||
}
|
@@ -2,10 +2,10 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties = {
|
||||
properties: Dict[str, Any] = {
|
||||
"upstreams": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
|
49
lib/spack/spack/schema/view.py
Normal file
49
lib/spack/spack/schema/view.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Schema for view
|
||||
|
||||
.. literalinclude:: _spack_root/lib/spack/spack/schema/view.py
|
||||
:lines: 15-
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
import spack.schema
|
||||
|
||||
projections_scheme = spack.schema.projections.properties["projections"]
|
||||
|
||||
#: Properties for inclusion in other schemas
|
||||
properties: Dict[str, Any] = {
|
||||
"view": {
|
||||
"anyOf": [
|
||||
{"type": "boolean"},
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
r"\w+": {
|
||||
"required": ["root"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"root": {"type": "string"},
|
||||
"link": {"type": "string", "pattern": "(roots|all|run)"},
|
||||
"link_type": {"type": "string"},
|
||||
"select": {"type": "array", "items": {"type": "string"}},
|
||||
"exclude": {"type": "array", "items": {"type": "string"}},
|
||||
"projections": projections_scheme,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#: Full schema with metadata
|
||||
schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Spack view configuration file schema",
|
||||
"properties": properties,
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -698,6 +698,14 @@ requirement_group_satisfied(node(ID, Package), X) :-
|
||||
activate_requirement(node(ID, Package), X),
|
||||
requirement_group(Package, X).
|
||||
|
||||
% Do not impose requirements, if the conditional requirement is not active
|
||||
do_not_impose(EffectID, node(ID, Package)) :-
|
||||
trigger_condition_holds(TriggerID, node(ID, Package)),
|
||||
pkg_fact(Package, condition_trigger(ConditionID, TriggerID)),
|
||||
pkg_fact(Package, condition_effect(ConditionID, EffectID)),
|
||||
requirement_group_member(ConditionID , Package, RequirementID),
|
||||
not activate_requirement(node(ID, Package), RequirementID).
|
||||
|
||||
% When we have a required provider, we need to ensure that the provider/2 facts respect
|
||||
% the requirement. This is particularly important for packages that could provide multiple
|
||||
% virtuals independently
|
||||
|
272
lib/spack/spack/solver/core.py
Normal file
272
lib/spack/spack/solver/core.py
Normal file
@@ -0,0 +1,272 @@
|
||||
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
"""Low-level wrappers around clingo API."""
|
||||
import importlib
|
||||
import pathlib
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, NamedTuple, Optional, Tuple, Union
|
||||
|
||||
from llnl.util import lang
|
||||
|
||||
|
||||
def _ast_getter(*names: str) -> Callable[[Any], Any]:
|
||||
"""Helper to retrieve AST attributes from different versions of the clingo API"""
|
||||
|
||||
def getter(node):
|
||||
for name in names:
|
||||
result = getattr(node, name, None)
|
||||
if result:
|
||||
return result
|
||||
raise KeyError(f"node has no such keys: {names}")
|
||||
|
||||
return getter
|
||||
|
||||
|
||||
ast_type = _ast_getter("ast_type", "type")
|
||||
ast_sym = _ast_getter("symbol", "term")
|
||||
|
||||
|
||||
class AspObject:
|
||||
"""Object representing a piece of ASP code."""
|
||||
|
||||
|
||||
def _id(thing: Any) -> Union[str, AspObject]:
|
||||
"""Quote string if needed for it to be a valid identifier."""
|
||||
if isinstance(thing, AspObject):
|
||||
return thing
|
||||
elif isinstance(thing, bool):
|
||||
return f'"{str(thing)}"'
|
||||
elif isinstance(thing, int):
|
||||
return str(thing)
|
||||
else:
|
||||
return f'"{str(thing)}"'
|
||||
|
||||
|
||||
@lang.key_ordering
|
||||
class AspFunction(AspObject):
|
||||
"""A term in the ASP logic program"""
|
||||
|
||||
__slots__ = ["name", "args"]
|
||||
|
||||
def __init__(self, name: str, args: Optional[Tuple[Any, ...]] = None) -> None:
|
||||
self.name = name
|
||||
self.args = () if args is None else tuple(args)
|
||||
|
||||
def _cmp_key(self) -> Tuple[str, Optional[Tuple[Any, ...]]]:
|
||||
return self.name, self.args
|
||||
|
||||
def __call__(self, *args: Any) -> "AspFunction":
|
||||
"""Return a new instance of this function with added arguments.
|
||||
|
||||
Note that calls are additive, so you can do things like::
|
||||
|
||||
>>> attr = AspFunction("attr")
|
||||
attr()
|
||||
|
||||
>>> attr("version")
|
||||
attr("version")
|
||||
|
||||
>>> attr("version")("foo")
|
||||
attr("version", "foo")
|
||||
|
||||
>>> v = AspFunction("attr", "version")
|
||||
attr("version")
|
||||
|
||||
>>> v("foo", "bar")
|
||||
attr("version", "foo", "bar")
|
||||
|
||||
"""
|
||||
return AspFunction(self.name, self.args + args)
|
||||
|
||||
def _argify(self, arg: Any) -> Any:
|
||||
"""Turn the argument into an appropriate clingo symbol"""
|
||||
if isinstance(arg, bool):
|
||||
return clingo().String(str(arg))
|
||||
elif isinstance(arg, int):
|
||||
return clingo().Number(arg)
|
||||
elif isinstance(arg, AspFunction):
|
||||
return clingo().Function(arg.name, [self._argify(x) for x in arg.args], positive=True)
|
||||
return clingo().String(str(arg))
|
||||
|
||||
def symbol(self):
|
||||
"""Return a clingo symbol for this function"""
|
||||
return clingo().Function(
|
||||
self.name, [self._argify(arg) for arg in self.args], positive=True
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name}({', '.join(str(_id(arg)) for arg in self.args)})"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
class _AspFunctionBuilder:
|
||||
def __getattr__(self, name):
|
||||
return AspFunction(name)
|
||||
|
||||
|
||||
#: Global AspFunction builder
|
||||
fn = _AspFunctionBuilder()
|
||||
|
||||
_CLINGO_MODULE: Optional[ModuleType] = None
|
||||
|
||||
|
||||
def clingo() -> ModuleType:
|
||||
"""Lazy imports the Python module for clingo, and returns it."""
|
||||
if _CLINGO_MODULE is not None:
|
||||
return _CLINGO_MODULE
|
||||
|
||||
try:
|
||||
clingo_mod = importlib.import_module("clingo")
|
||||
# Make sure we didn't import an empty module
|
||||
_ensure_clingo_or_raise(clingo_mod)
|
||||
except ImportError:
|
||||
clingo_mod = None
|
||||
|
||||
if clingo_mod is not None:
|
||||
return _set_clingo_module_cache(clingo_mod)
|
||||
|
||||
clingo_mod = _bootstrap_clingo()
|
||||
return _set_clingo_module_cache(clingo_mod)
|
||||
|
||||
|
||||
def _set_clingo_module_cache(clingo_mod: ModuleType) -> ModuleType:
|
||||
"""Sets the global cache to the lazy imported clingo module"""
|
||||
global _CLINGO_MODULE
|
||||
importlib.import_module("clingo.ast")
|
||||
_CLINGO_MODULE = clingo_mod
|
||||
return clingo_mod
|
||||
|
||||
|
||||
def _ensure_clingo_or_raise(clingo_mod: ModuleType) -> None:
|
||||
"""Ensures the clingo module can access expected attributes, otherwise raises an error."""
|
||||
# These are imports that may be problematic at top level (circular imports). They are used
|
||||
# only to provide exhaustive details when erroring due to a broken clingo module.
|
||||
import spack.config
|
||||
import spack.paths as sp
|
||||
import spack.util.path as sup
|
||||
|
||||
try:
|
||||
clingo_mod.Symbol
|
||||
except AttributeError:
|
||||
assert clingo_mod.__file__ is not None, "clingo installation is incomplete or invalid"
|
||||
# Reaching this point indicates a broken clingo installation
|
||||
# If Spack derived clingo, suggest user re-run bootstrap
|
||||
# if non-spack, suggest user investigate installation
|
||||
# assume Spack is not responsible for broken clingo
|
||||
msg = (
|
||||
f"Clingo installation at {clingo_mod.__file__} is incomplete or invalid."
|
||||
"Please repair installation or re-install. "
|
||||
"Alternatively, consider installing clingo via Spack."
|
||||
)
|
||||
# check whether Spack is responsible
|
||||
if (
|
||||
pathlib.Path(
|
||||
sup.canonicalize_path(
|
||||
spack.config.CONFIG.get("bootstrap:root", sp.default_user_bootstrap_path)
|
||||
)
|
||||
)
|
||||
in pathlib.Path(clingo_mod.__file__).parents
|
||||
):
|
||||
# Spack is responsible for the broken clingo
|
||||
msg = (
|
||||
"Spack bootstrapped copy of Clingo is broken, "
|
||||
"please re-run the bootstrapping process via command `spack bootstrap now`."
|
||||
" If this issue persists, please file a bug at: github.com/spack/spack"
|
||||
)
|
||||
raise RuntimeError(
|
||||
"Clingo installation may be broken or incomplete, "
|
||||
"please verify clingo has been installed correctly"
|
||||
"\n\nClingo does not provide symbol clingo.Symbol"
|
||||
f"{msg}"
|
||||
)
|
||||
|
||||
|
||||
def clingo_cffi() -> bool:
|
||||
"""Returns True if clingo uses the CFFI interface"""
|
||||
return hasattr(clingo().Symbol, "_rep")
|
||||
|
||||
|
||||
def _bootstrap_clingo() -> ModuleType:
|
||||
"""Bootstraps the clingo module and returns it"""
|
||||
import spack.bootstrap
|
||||
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
spack.bootstrap.ensure_core_dependencies()
|
||||
clingo_mod = importlib.import_module("clingo")
|
||||
|
||||
return clingo_mod
|
||||
|
||||
|
||||
def parse_files(*args, **kwargs):
|
||||
"""Wrapper around clingo parse_files, that dispatches the function according
|
||||
to clingo API version.
|
||||
"""
|
||||
clingo()
|
||||
try:
|
||||
return importlib.import_module("clingo.ast").parse_files(*args, **kwargs)
|
||||
except (ImportError, AttributeError):
|
||||
return clingo().parse_files(*args, **kwargs)
|
||||
|
||||
|
||||
def parse_term(*args, **kwargs):
|
||||
"""Wrapper around clingo parse_term, that dispatches the function according
|
||||
to clingo API version.
|
||||
"""
|
||||
clingo()
|
||||
try:
|
||||
return importlib.import_module("clingo.symbol").parse_term(*args, **kwargs)
|
||||
except (ImportError, AttributeError):
|
||||
return clingo().parse_term(*args, **kwargs)
|
||||
|
||||
|
||||
class NodeArgument(NamedTuple):
|
||||
"""Represents a node in the DAG"""
|
||||
|
||||
id: str
|
||||
pkg: str
|
||||
|
||||
|
||||
def intermediate_repr(sym):
|
||||
"""Returns an intermediate representation of clingo models for Spack's spec builder.
|
||||
|
||||
Currently, transforms symbols from clingo models either to strings or to NodeArgument objects.
|
||||
|
||||
Returns:
|
||||
This will turn a ``clingo.Symbol`` into a string or NodeArgument, or a sequence of
|
||||
``clingo.Symbol`` objects into a tuple of those objects.
|
||||
"""
|
||||
# TODO: simplify this when we no longer have to support older clingo versions.
|
||||
if isinstance(sym, (list, tuple)):
|
||||
return tuple(intermediate_repr(a) for a in sym)
|
||||
|
||||
try:
|
||||
if sym.name == "node":
|
||||
return NodeArgument(
|
||||
id=intermediate_repr(sym.arguments[0]), pkg=intermediate_repr(sym.arguments[1])
|
||||
)
|
||||
except RuntimeError:
|
||||
# This happens when using clingo w/ CFFI and trying to access ".name" for symbols
|
||||
# that are not functions
|
||||
pass
|
||||
|
||||
if clingo_cffi():
|
||||
# Clingo w/ CFFI will throw an exception on failure
|
||||
try:
|
||||
return sym.string
|
||||
except RuntimeError:
|
||||
return str(sym)
|
||||
else:
|
||||
return sym.string or str(sym)
|
||||
|
||||
|
||||
def extract_args(model, predicate_name):
|
||||
"""Extract the arguments to predicates with the provided name from a model.
|
||||
|
||||
Pull out all the predicates with name ``predicate_name`` from the model, and
|
||||
return their intermediate representation.
|
||||
"""
|
||||
return [intermediate_repr(sym.arguments) for sym in model if sym.name == predicate_name]
|
@@ -1501,7 +1501,9 @@ def edge_attributes(self) -> str:
|
||||
result = f"{deptypes_str} {virtuals_str}".strip()
|
||||
return f"[{result}]"
|
||||
|
||||
def dependencies(self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL):
|
||||
def dependencies(
|
||||
self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL
|
||||
) -> List["Spec"]:
|
||||
"""Return a list of direct dependencies (nodes in the DAG).
|
||||
|
||||
Args:
|
||||
@@ -1512,7 +1514,9 @@ def dependencies(self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.A
|
||||
deptype = dt.canonicalize(deptype)
|
||||
return [d.spec for d in self.edges_to_dependencies(name, depflag=deptype)]
|
||||
|
||||
def dependents(self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL):
|
||||
def dependents(
|
||||
self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL
|
||||
) -> List["Spec"]:
|
||||
"""Return a list of direct dependents (nodes in the DAG).
|
||||
|
||||
Args:
|
||||
@@ -1636,23 +1640,23 @@ def _add_dependency(self, spec: "Spec", *, depflag: dt.DepFlag, virtuals: Tuple[
|
||||
self.add_dependency_edge(spec, depflag=depflag, virtuals=virtuals)
|
||||
return
|
||||
|
||||
# Keep the intersection of constraints when a dependency is added
|
||||
# multiple times. Currently, we only allow identical edge types.
|
||||
# Keep the intersection of constraints when a dependency is added multiple times.
|
||||
# The only restriction, currently, is keeping the same dependency type
|
||||
orig = self._dependencies[spec.name]
|
||||
try:
|
||||
dspec = next(dspec for dspec in orig if depflag == dspec.depflag)
|
||||
except StopIteration:
|
||||
current_deps = ", ".join(
|
||||
dt.flag_to_chars(x.depflag) + " " + x.spec.short_spec for x in orig
|
||||
)
|
||||
edge_attrs = f"deptypes={dt.flag_to_chars(depflag).strip()}"
|
||||
required_dep_str = f"^[{edge_attrs}] {str(spec)}"
|
||||
|
||||
raise DuplicateDependencyError(
|
||||
f"{self.short_spec} cannot depend on '{spec.short_spec}' multiple times.\n"
|
||||
f"\tRequired: {dt.flag_to_chars(depflag)}\n"
|
||||
f"\tDependency: {current_deps}"
|
||||
f"{spec.name} is a duplicate dependency, with conflicting dependency types\n"
|
||||
f"\t'{str(self)}' cannot depend on '{required_dep_str}'"
|
||||
)
|
||||
|
||||
try:
|
||||
dspec.spec.constrain(spec)
|
||||
dspec.update_virtuals(virtuals=virtuals)
|
||||
except spack.error.UnsatisfiableSpecError:
|
||||
raise DuplicateDependencyError(
|
||||
f"Cannot depend on incompatible specs '{dspec.spec}' and '{spec}'"
|
||||
@@ -2087,7 +2091,12 @@ def to_node_dict(self, hash=ht.dag_hash):
|
||||
if hasattr(variant, "_patches_in_order_of_appearance"):
|
||||
d["patches"] = variant._patches_in_order_of_appearance
|
||||
|
||||
if self._concrete and hash.package_hash and self._package_hash:
|
||||
if (
|
||||
self._concrete
|
||||
and hash.package_hash
|
||||
and hasattr(self, "_package_hash")
|
||||
and self._package_hash
|
||||
):
|
||||
# We use the attribute here instead of `self.package_hash()` because this
|
||||
# should *always* be assignhed at concretization time. We don't want to try
|
||||
# to compute a package hash for concrete spec where a) the package might not
|
||||
|
@@ -199,9 +199,11 @@ def get_stage_root():
|
||||
def _mirror_roots():
|
||||
mirrors = spack.config.get("mirrors")
|
||||
return [
|
||||
sup.substitute_path_variables(root)
|
||||
if root.endswith(os.sep)
|
||||
else sup.substitute_path_variables(root) + os.sep
|
||||
(
|
||||
sup.substitute_path_variables(root)
|
||||
if root.endswith(os.sep)
|
||||
else sup.substitute_path_variables(root) + os.sep
|
||||
)
|
||||
for root in mirrors.values()
|
||||
]
|
||||
|
||||
|
@@ -16,6 +16,7 @@
|
||||
import spack
|
||||
import spack.binary_distribution
|
||||
import spack.ci as ci
|
||||
import spack.cmd.ci
|
||||
import spack.config
|
||||
import spack.environment as ev
|
||||
import spack.hash_types as ht
|
||||
@@ -2028,6 +2029,43 @@ def fake_download_and_extract_artifacts(url, work_dir):
|
||||
assert expect_out in rep_out
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url_in,url_out",
|
||||
[
|
||||
(
|
||||
"https://example.com/api/v4/projects/1/jobs/2/artifacts",
|
||||
"https://example.com/api/v4/projects/1/jobs/2/artifacts",
|
||||
),
|
||||
(
|
||||
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
|
||||
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
|
||||
),
|
||||
(
|
||||
"https://example.com/spack/spack/-/jobs/123456",
|
||||
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
|
||||
),
|
||||
(
|
||||
"https://example.com/spack/spack/-/jobs/////123456////?x=y#z",
|
||||
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_reproduce_build_url_validation(url_in, url_out):
|
||||
assert spack.cmd.ci._gitlab_artifacts_url(url_in) == url_out
|
||||
|
||||
|
||||
def test_reproduce_build_url_validation_fails():
|
||||
"""Wrong URLs should cause an exception"""
|
||||
with pytest.raises(SystemExit):
|
||||
ci_cmd("reproduce-build", "example.com/spack/spack/-/jobs/123456/artifacts/download")
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
ci_cmd("reproduce-build", "https://example.com/spack/spack/-/issues")
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
ci_cmd("reproduce-build", "https://example.com/spack/spack/-")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"subcmd", [(""), ("generate"), ("rebuild-index"), ("rebuild"), ("reproduce-build")]
|
||||
)
|
||||
|
@@ -215,6 +215,44 @@ def test_dev_build_env(tmpdir, install_mockery, mutable_mock_env_path):
|
||||
assert f.read() == spec.package.replacement_string
|
||||
|
||||
|
||||
def test_dev_build_env_with_vars(tmpdir, install_mockery, mutable_mock_env_path, monkeypatch):
|
||||
"""Test Spack does dev builds for packages in develop section of env (path with variables)."""
|
||||
# setup dev-build-test-install package for dev build
|
||||
build_dir = tmpdir.mkdir("build")
|
||||
spec = spack.spec.Spec(f"dev-build-test-install@0.0.0 dev_path={build_dir}")
|
||||
spec.concretize()
|
||||
|
||||
# store the build path in an environment variable that will be used in the environment
|
||||
monkeypatch.setenv("CUSTOM_BUILD_PATH", build_dir)
|
||||
|
||||
with build_dir.as_cwd(), open(spec.package.filename, "w") as f:
|
||||
f.write(spec.package.original_string)
|
||||
|
||||
# setup environment
|
||||
envdir = tmpdir.mkdir("env")
|
||||
with envdir.as_cwd():
|
||||
with open("spack.yaml", "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- dev-build-test-install@0.0.0
|
||||
|
||||
develop:
|
||||
dev-build-test-install:
|
||||
spec: dev-build-test-install@0.0.0
|
||||
path: $CUSTOM_BUILD_PATH
|
||||
"""
|
||||
)
|
||||
env("create", "test", "./spack.yaml")
|
||||
with ev.read("test"):
|
||||
install()
|
||||
|
||||
assert spec.package.filename in os.listdir(spec.prefix)
|
||||
with open(os.path.join(spec.prefix, spec.package.filename), "r") as f:
|
||||
assert f.read() == spec.package.replacement_string
|
||||
|
||||
|
||||
def test_dev_build_env_version_mismatch(tmpdir, install_mockery, mutable_mock_env_path):
|
||||
"""Test Spack constraints concretization by develop specs."""
|
||||
# setup dev-build-test-install package for dev build
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
import spack.config
|
||||
import spack.environment as ev
|
||||
import spack.spec
|
||||
from spack.main import SpackCommand
|
||||
@@ -21,7 +22,7 @@
|
||||
|
||||
@pytest.mark.usefixtures("mutable_mock_env_path", "mock_packages", "mock_fetch", "mutable_config")
|
||||
class TestDevelop:
|
||||
def check_develop(self, env, spec, path=None):
|
||||
def check_develop(self, env, spec, path=None, build_dir=None):
|
||||
path = path or spec.name
|
||||
|
||||
# check in memory representation
|
||||
@@ -41,6 +42,12 @@ def check_develop(self, env, spec, path=None):
|
||||
else:
|
||||
assert yaml_entry["path"] == path
|
||||
|
||||
if build_dir is not None:
|
||||
scope = env.scope_name
|
||||
assert build_dir == spack.config.get(
|
||||
"packages:{}:package_attributes:build_directory".format(spec.name), scope
|
||||
)
|
||||
|
||||
def test_develop_no_path_no_clone(self):
|
||||
env("create", "test")
|
||||
with ev.read("test") as e:
|
||||
@@ -72,6 +79,12 @@ def test_develop_no_args(self):
|
||||
develop()
|
||||
self.check_develop(e, spack.spec.Spec("mpich@=1.0"))
|
||||
|
||||
def test_develop_build_directory(self):
|
||||
env("create", "test")
|
||||
with ev.read("test") as e:
|
||||
develop("-b", "test_build_dir", "mpich@1.0")
|
||||
self.check_develop(e, spack.spec.Spec("mpich@=1.0"), None, "test_build_dir")
|
||||
|
||||
def test_develop_twice(self):
|
||||
env("create", "test")
|
||||
with ev.read("test") as e:
|
||||
|
@@ -1471,8 +1471,8 @@ def test_env_view_fails_dir_file(tmpdir, mock_packages, mock_stage, mock_fetch,
|
||||
view_dir = tmpdir.join("view")
|
||||
env("create", "--with-view=%s" % view_dir, "test")
|
||||
with ev.read("test"):
|
||||
add("view-dir-file")
|
||||
add("view-dir-dir")
|
||||
add("view-file")
|
||||
add("view-dir")
|
||||
with pytest.raises(
|
||||
llnl.util.link_tree.MergeConflictSummary, match=os.path.join("bin", "x")
|
||||
):
|
||||
@@ -1486,8 +1486,8 @@ def test_env_view_succeeds_symlinked_dir_file(
|
||||
view_dir = tmpdir.join("view")
|
||||
env("create", "--with-view=%s" % view_dir, "test")
|
||||
with ev.read("test"):
|
||||
add("view-dir-symlinked-dir")
|
||||
add("view-dir-dir")
|
||||
add("view-symlinked-dir")
|
||||
add("view-dir")
|
||||
install()
|
||||
x_dir = os.path.join(str(view_dir), "bin", "x")
|
||||
assert os.path.exists(os.path.join(x_dir, "file_in_dir"))
|
||||
@@ -2537,58 +2537,88 @@ def test_stack_view_no_activate_without_default(
|
||||
assert viewdir not in shell
|
||||
|
||||
|
||||
@pytest.mark.parametrize("include_views", [True, False, "split"])
|
||||
def test_stack_view_multiple_views(
|
||||
tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery
|
||||
tmp_path,
|
||||
mock_fetch,
|
||||
mock_packages,
|
||||
mock_archive,
|
||||
install_mockery,
|
||||
mutable_config,
|
||||
include_views,
|
||||
):
|
||||
filename = str(tmpdir.join("spack.yaml"))
|
||||
default_viewdir = str(tmpdir.join("default-view"))
|
||||
combin_viewdir = str(tmpdir.join("combinatorial-view"))
|
||||
with open(filename, "w") as f:
|
||||
f.write(
|
||||
"""\
|
||||
spack:
|
||||
"""Test multiple views as both included views (True), as both environment
|
||||
views (False), or as one included and the other in the environment."""
|
||||
# Write the view configuration and or manifest file
|
||||
view_filename = tmp_path / "view.yaml"
|
||||
base_content = """\
|
||||
definitions:
|
||||
- packages: [mpileaks, cmake]
|
||||
- compilers: ['%%gcc', '%%clang']
|
||||
- compilers: ['%gcc', '%clang']
|
||||
specs:
|
||||
- matrix:
|
||||
- [$packages]
|
||||
- [$compilers]
|
||||
"""
|
||||
|
||||
view:
|
||||
default:
|
||||
root: %s
|
||||
select: ['%%gcc']
|
||||
combinatorial:
|
||||
root: %s
|
||||
exclude: [callpath %%gcc]
|
||||
projections:
|
||||
'all': '{name}/{version}-{compiler.name}'"""
|
||||
% (default_viewdir, combin_viewdir)
|
||||
)
|
||||
with tmpdir.as_cwd():
|
||||
env("create", "test", "./spack.yaml")
|
||||
with ev.read("test"):
|
||||
install()
|
||||
include_content = f" include:\n - {view_filename}\n"
|
||||
view_line = " view:\n"
|
||||
|
||||
shell = env("activate", "--sh", "test")
|
||||
assert "PATH" in shell
|
||||
assert os.path.join(default_viewdir, "bin") in shell
|
||||
comb_dir = tmp_path / "combinatorial-view"
|
||||
comb_view = """\
|
||||
{0}combinatorial:
|
||||
{0} root: {1}
|
||||
{0} exclude: [callpath%gcc]
|
||||
{0} projections:
|
||||
"""
|
||||
|
||||
test = ev.read("test")
|
||||
for spec in test._get_environment_specs():
|
||||
projection = " 'all': '{name}/{version}-{compiler.name}'"
|
||||
|
||||
default_dir = tmp_path / "default-view"
|
||||
default_view = """\
|
||||
{0}default:
|
||||
{0} root: {1}
|
||||
{0} select: ['%gcc']
|
||||
"""
|
||||
|
||||
content = "spack:\n"
|
||||
indent = " "
|
||||
if include_views is True:
|
||||
# Include both the gcc and combinatorial views
|
||||
view = "view:\n" + default_view.format(indent, str(default_dir))
|
||||
view += comb_view.format(indent, str(comb_dir)) + indent + projection
|
||||
view_filename.write_text(view)
|
||||
content += include_content + base_content
|
||||
elif include_views == "split":
|
||||
# Include the gcc view and inline the combinatorial view
|
||||
view = "view:\n" + default_view.format(indent, str(default_dir))
|
||||
view_filename.write_text(view)
|
||||
content += include_content + base_content + view_line
|
||||
indent += " "
|
||||
content += comb_view.format(indent, str(comb_dir)) + indent + projection
|
||||
else:
|
||||
# Inline both the gcc and combinatorial views in the environment.
|
||||
indent += " "
|
||||
content += base_content + view_line
|
||||
content += default_view.format(indent, str(default_dir))
|
||||
content += comb_view.format(indent, str(comb_dir)) + indent + projection
|
||||
|
||||
filename = tmp_path / ev.manifest_name
|
||||
filename.write_text(content)
|
||||
|
||||
env("create", "test", str(filename))
|
||||
with ev.read("test"):
|
||||
install()
|
||||
|
||||
with ev.read("test") as e:
|
||||
assert os.path.exists(str(default_dir / "bin"))
|
||||
for spec in e._get_environment_specs():
|
||||
spec_subdir = f"{spec.version}-{spec.compiler.name}"
|
||||
comb_spec_dir = str(comb_dir / spec.name / spec_subdir)
|
||||
if not spec.satisfies("callpath%gcc"):
|
||||
assert os.path.exists(
|
||||
os.path.join(
|
||||
combin_viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name)
|
||||
)
|
||||
)
|
||||
assert os.path.exists(comb_spec_dir)
|
||||
else:
|
||||
assert not os.path.exists(
|
||||
os.path.join(
|
||||
combin_viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name)
|
||||
)
|
||||
)
|
||||
assert not os.path.exists(comb_spec_dir)
|
||||
|
||||
|
||||
def test_env_activate_sh_prints_shell_output(tmpdir, mock_stage, mock_fetch, install_mockery):
|
||||
@@ -2701,15 +2731,6 @@ def test_concretize_user_specs_together():
|
||||
assert all("mpich" not in spec for _, spec in e.concretized_specs())
|
||||
|
||||
|
||||
def test_cant_install_single_spec_when_concretizing_together():
|
||||
e = ev.create("coconcretization")
|
||||
e.unify = True
|
||||
|
||||
with pytest.raises(ev.SpackEnvironmentError, match=r"cannot install"):
|
||||
e.concretize_and_add("zlib")
|
||||
e.install_all()
|
||||
|
||||
|
||||
def test_duplicate_packages_raise_when_concretizing_together():
|
||||
e = ev.create("coconcretization")
|
||||
e.unify = True
|
||||
@@ -3714,3 +3735,191 @@ def test_environment_created_from_lockfile_has_view(mock_packages, temporary_sto
|
||||
# Make sure the view was created
|
||||
with ev.Environment(env_b) as e:
|
||||
assert os.path.isdir(e.view_path_default)
|
||||
|
||||
|
||||
def test_env_view_disabled(tmp_path, mutable_mock_env_path):
|
||||
"""Ensure an inlined view being disabled means not even the default view
|
||||
is created (since the case doesn't appear to be covered in this module)."""
|
||||
spack_yaml = tmp_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
"""\
|
||||
spack:
|
||||
specs:
|
||||
- mpileaks
|
||||
view: false
|
||||
"""
|
||||
)
|
||||
env("create", "disabled", str(spack_yaml))
|
||||
with ev.read("disabled") as e:
|
||||
e.concretize()
|
||||
|
||||
assert len(e.views) == 0
|
||||
assert not os.path.exists(e.view_path_default)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("first", ["false", "true", "custom"])
|
||||
def test_env_include_mixed_views(tmp_path, mutable_mock_env_path, mutable_config, first):
|
||||
"""Ensure including path and boolean views in different combinations result
|
||||
in the creation of only the first view if it is not disabled."""
|
||||
false_yaml = tmp_path / "false-view.yaml"
|
||||
false_yaml.write_text("view: false\n")
|
||||
|
||||
true_yaml = tmp_path / "true-view.yaml"
|
||||
true_yaml.write_text("view: true\n")
|
||||
|
||||
custom_name = "my-test-view"
|
||||
custom_view = tmp_path / custom_name
|
||||
custom_yaml = tmp_path / "custom-view.yaml"
|
||||
custom_yaml.write_text(
|
||||
f"""
|
||||
view:
|
||||
{custom_name}:
|
||||
root: {custom_view}
|
||||
"""
|
||||
)
|
||||
|
||||
if first == "false":
|
||||
order = [false_yaml, true_yaml, custom_yaml]
|
||||
elif first == "true":
|
||||
order = [true_yaml, custom_yaml, false_yaml]
|
||||
else:
|
||||
order = [custom_yaml, false_yaml, true_yaml]
|
||||
includes = [f" - {yaml}\n" for yaml in order]
|
||||
|
||||
spack_yaml = tmp_path / ev.manifest_name
|
||||
spack_yaml.write_text(
|
||||
f"""\
|
||||
spack:
|
||||
include:
|
||||
{''.join(includes)}
|
||||
specs:
|
||||
- mpileaks
|
||||
packages:
|
||||
mpileaks:
|
||||
compiler: [gcc]
|
||||
"""
|
||||
)
|
||||
|
||||
env("create", "test", str(spack_yaml))
|
||||
with ev.read("test") as e:
|
||||
concretize()
|
||||
|
||||
# Only the first included view should be created if view not disabled by it
|
||||
assert len(e.views) == 0 if first == "false" else 1
|
||||
if first == "true":
|
||||
assert os.path.exists(e.view_path_default)
|
||||
else:
|
||||
assert not os.path.exists(e.view_path_default)
|
||||
|
||||
if first == "custom":
|
||||
assert os.path.exists(custom_view)
|
||||
else:
|
||||
assert not os.path.exists(custom_view)
|
||||
|
||||
|
||||
def test_stack_view_multiple_views_same_name(
|
||||
tmp_path, mock_fetch, mock_packages, mock_archive, install_mockery, mutable_config
|
||||
):
|
||||
"""Test multiple views with the same name combine settings with precedence
|
||||
given to the options in spack.yaml."""
|
||||
# Write the view configuration and or manifest file
|
||||
|
||||
view_filename = tmp_path / "view.yaml"
|
||||
default_dir = tmp_path / "default-view"
|
||||
default_view = f"""\
|
||||
view:
|
||||
default:
|
||||
root: {default_dir}
|
||||
select: ['%gcc']
|
||||
projections:
|
||||
all: '{{name}}/{{version}}-{{compiler.name}}'
|
||||
"""
|
||||
view_filename.write_text(default_view)
|
||||
|
||||
view_dir = tmp_path / "view"
|
||||
content = f"""\
|
||||
spack:
|
||||
include:
|
||||
- {view_filename}
|
||||
definitions:
|
||||
- packages: [mpileaks, cmake]
|
||||
- compilers: ['%gcc', '%clang']
|
||||
specs:
|
||||
- matrix:
|
||||
- [$packages]
|
||||
- [$compilers]
|
||||
|
||||
view:
|
||||
default:
|
||||
root: {view_dir}
|
||||
exclude: ['cmake']
|
||||
projections:
|
||||
all: '{{name}}/{{compiler.name}}-{{version}}'
|
||||
"""
|
||||
|
||||
filename = tmp_path / ev.manifest_name
|
||||
filename.write_text(content)
|
||||
|
||||
env("create", "test", str(filename))
|
||||
with ev.read("test"):
|
||||
install()
|
||||
|
||||
with ev.read("test") as e:
|
||||
# the view root in the included view should NOT exist
|
||||
assert not os.path.exists(str(default_dir))
|
||||
|
||||
for spec in e._get_environment_specs():
|
||||
# no specs will exist in the included view projection
|
||||
included_spec_subdir = f"{spec.version}-{spec.compiler.name}"
|
||||
included_spec_dir = str(view_dir / spec.name / included_spec_subdir)
|
||||
assert not os.path.exists(included_spec_dir)
|
||||
|
||||
# only specs compiled with %gcc (selected in the included view) that
|
||||
# are also not cmake (excluded in the environment view) should exist
|
||||
env_spec_subdir = f"{spec.compiler.name}-{spec.version}"
|
||||
env_spec_dir = str(view_dir / spec.name / env_spec_subdir)
|
||||
if spec.satisfies("cmake") or spec.satisfies("%clang"):
|
||||
assert not os.path.exists(env_spec_dir)
|
||||
else:
|
||||
assert os.path.exists(env_spec_dir)
|
||||
|
||||
|
||||
def test_env_view_resolves_identical_file_conflicts(tmp_path, install_mockery, mock_fetch):
|
||||
"""When files clash in a view, but refer to the same file on disk (for example, the dependent
|
||||
symlinks to a file in the dependency at the same relative path), Spack links the first regular
|
||||
file instead of symlinks. This is important for copy type views where we need the underlying
|
||||
file to be copied instead of the symlink (when a symlink would be copied, it would become a
|
||||
self-referencing symlink after relocation). The test uses a symlink type view though, since
|
||||
that keeps track of the original file path."""
|
||||
with ev.create("env", with_view=tmp_path / "view") as e:
|
||||
add("view-resolve-conflict-top")
|
||||
install()
|
||||
top = e.matching_spec("view-resolve-conflict-top").prefix
|
||||
bottom = e.matching_spec("view-file").prefix
|
||||
|
||||
# In this example we have `./bin/x` in 3 prefixes, two links, one regular file. We expect the
|
||||
# regular file to be linked into the view. There are also 2 links at `./bin/y`, but no regular
|
||||
# file, so we expect standard behavior: first entry is linked into the view.
|
||||
|
||||
# view-resolve-conflict-top/bin/
|
||||
# x -> view-file/bin/x
|
||||
# y -> view-resolve-conflict-middle/bin/y # expect this y to be linked
|
||||
# view-resolve-conflict-middle/bin/
|
||||
# x -> view-file/bin/x
|
||||
# y -> view-file/bin/x
|
||||
# view-file/bin/
|
||||
# x # expect this x to be linked
|
||||
|
||||
assert os.readlink(tmp_path / "view" / "bin" / "x") == bottom.bin.x
|
||||
assert os.readlink(tmp_path / "view" / "bin" / "y") == top.bin.y
|
||||
|
||||
|
||||
def test_env_view_ignores_different_file_conflicts(tmp_path, install_mockery, mock_fetch):
|
||||
"""Test that file-file conflicts for two unique files in environment views are ignored, and
|
||||
that the dependent's file is linked into the view, not the dependency's file."""
|
||||
with ev.create("env", with_view=tmp_path / "view") as e:
|
||||
add("view-ignore-conflict")
|
||||
install()
|
||||
prefix_dependent = e.matching_spec("view-ignore-conflict").prefix
|
||||
# The dependent's file is linked into the view
|
||||
assert os.readlink(tmp_path / "view" / "bin" / "x") == prefix_dependent.bin.x
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user