summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/docs/basic_usage.rst24
-rw-r--r--lib/spack/docs/images/strumpack_virtuals.svg534
-rw-r--r--lib/spack/docs/packaging_guide.rst27
-rw-r--r--lib/spack/spack/directives.py37
-rw-r--r--lib/spack/spack/graph.py7
-rw-r--r--lib/spack/spack/parser.py74
-rw-r--r--lib/spack/spack/provider_index.py51
-rw-r--r--lib/spack/spack/solver/asp.py25
-rw-r--r--lib/spack/spack/solver/concretize.lp43
-rw-r--r--lib/spack/spack/spec.py108
-rw-r--r--lib/spack/spack/test/build_environment.py10
-rw-r--r--lib/spack/spack/test/cmd/dependencies.py9
-rw-r--r--lib/spack/spack/test/cmd/env.py15
-rw-r--r--lib/spack/spack/test/concretize.py46
-rw-r--r--lib/spack/spack/test/package_class.py1
-rw-r--r--lib/spack/spack/test/spec_dag.py1
-rw-r--r--lib/spack/spack/test/spec_semantics.py142
-rw-r--r--lib/spack/spack/test/spec_syntax.py23
-rw-r--r--var/spack/repos/builtin.mock/packages/intel-parallel-studio/package.py19
-rw-r--r--var/spack/repos/builtin.mock/packages/low-priority-provider/package.py4
-rw-r--r--var/spack/repos/builtin.mock/packages/many-virtual-consumer/package.py2
-rw-r--r--var/spack/repos/builtin.mock/packages/netlib-scalapack/package.py20
-rw-r--r--var/spack/repos/builtin.mock/packages/openblas-with-lapack/package.py3
-rw-r--r--var/spack/repos/builtin/packages/lua-luajit-openresty/package.py3
-rw-r--r--var/spack/repos/builtin/packages/lua-luajit/package.py3
-rw-r--r--var/spack/repos/builtin/packages/openblas/package.py3
-rw-r--r--var/spack/repos/edges.test/packages/conditional-edge/package.py24
-rw-r--r--var/spack/repos/edges.test/packages/zlib/package.py19
-rw-r--r--var/spack/repos/edges.test/repo.yaml2
29 files changed, 1126 insertions, 153 deletions
diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst
index d1f048ac05..52054a9405 100644
--- a/lib/spack/docs/basic_usage.rst
+++ b/lib/spack/docs/basic_usage.rst
@@ -1526,6 +1526,30 @@ any MPI implementation will do. If another package depends on
error. Likewise, if you try to plug in some package that doesn't
provide MPI, Spack will raise an error.
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Explicit binding of virtual dependencies
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+There are packages that provide more than just one virtual dependency. When interacting with them, users
+might want to utilize just a subset of what they could provide, and use other providers for virtuals they
+need.
+
+It is possible to be more explicit and tell Spack which dependency should provide which virtual, using a
+special syntax:
+
+.. code-block:: console
+
+ $ spack spec strumpack ^[virtuals=mpi] intel-parallel-studio+mkl ^[virtuals=lapack] openblas
+
+Concretizing the spec above produces the following DAG:
+
+.. figure:: images/strumpack_virtuals.svg
+ :scale: 60 %
+ :align: center
+
+where ``intel-parallel-studio`` *could* provide ``mpi``, ``lapack``, and ``blas`` but is used only for the former. The ``lapack``
+and ``blas`` dependencies are satisfied by ``openblas``.
+
^^^^^^^^^^^^^^^^^^^^^^^^
Specifying Specs by Hash
^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/lib/spack/docs/images/strumpack_virtuals.svg b/lib/spack/docs/images/strumpack_virtuals.svg
new file mode 100644
index 0000000000..eb580f0a58
--- /dev/null
+++ b/lib/spack/docs/images/strumpack_virtuals.svg
@@ -0,0 +1,534 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ --><!-- Title: G Pages: 1 --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3044pt" height="1683pt" viewBox="0.00 0.00 3043.65 1682.80">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1678.8)">
+<title>G</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1678.8 3039.6456,-1678.8 3039.6456,4 -4,4"/>
+<!-- hkcrbrtf2qex6rvzuok5tzdrbam55pdn -->
+<g id="node1" class="node">
+<title>hkcrbrtf2qex6rvzuok5tzdrbam55pdn</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M2407.965,-1198.3002C2407.965,-1198.3002 1948.1742,-1198.3002 1948.1742,-1198.3002 1942.1742,-1198.3002 1936.1742,-1192.3002 1936.1742,-1186.3002 1936.1742,-1186.3002 1936.1742,-1123.6998 1936.1742,-1123.6998 1936.1742,-1117.6998 1942.1742,-1111.6998 1948.1742,-1111.6998 1948.1742,-1111.6998 2407.965,-1111.6998 2407.965,-1111.6998 2413.965,-1111.6998 2419.965,-1117.6998 2419.965,-1123.6998 2419.965,-1123.6998 2419.965,-1186.3002 2419.965,-1186.3002 2419.965,-1192.3002 2413.965,-1198.3002 2407.965,-1198.3002"/>
+<text text-anchor="middle" x="2178.0696" y="-1147.8" font-family="Monaco" font-size="24.00" fill="#000000">netlib-scalapack@2.2.0%gcc@9.4.0/hkcrbrt</text>
+</g>
+<!-- o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="node8" class="node">
+<title>o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M901.2032,-1039.5002C901.2032,-1039.5002 486.936,-1039.5002 486.936,-1039.5002 480.936,-1039.5002 474.936,-1033.5002 474.936,-1027.5002 474.936,-1027.5002 474.936,-964.8998 474.936,-964.8998 474.936,-958.8998 480.936,-952.8998 486.936,-952.8998 486.936,-952.8998 901.2032,-952.8998 901.2032,-952.8998 907.2032,-952.8998 913.2032,-958.8998 913.2032,-964.8998 913.2032,-964.8998 913.2032,-1027.5002 913.2032,-1027.5002 913.2032,-1033.5002 907.2032,-1039.5002 901.2032,-1039.5002"/>
+<text text-anchor="middle" x="694.0696" y="-989" font-family="Monaco" font-size="24.00" fill="#000000">openblas@0.3.21%gcc@9.4.0/o524geb</text>
+</g>
+<!-- hkcrbrtf2qex6rvzuok5tzdrbam55pdn&#45;&gt;o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="edge10" class="edge">
+<title>hkcrbrtf2qex6rvzuok5tzdrbam55pdn-&gt;o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1936.1981,-1113.832C1933.0949,-1113.4088 1930.0059,-1112.9948 1926.9392,-1112.5915 1575.405,-1066.3348 1485.3504,-1074.0879 1131.9752,-1040.5955 1064.2267,-1034.1713 990.6114,-1026.9648 923.4066,-1020.2975"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1936.4684,-1111.8504C1933.3606,-1111.4265 1930.2716,-1111.0125 1927.2,-1110.6085 1575.2335,-1064.3422 1485.1789,-1072.0953 1132.164,-1038.6045 1064.4216,-1032.1808 990.8062,-1024.9744 923.604,-1018.3073"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="923.505,-1015.7853 913.2081,-1018.2801 922.8133,-1022.751 923.505,-1015.7853"/>
+<text text-anchor="middle" x="1368.79" y="-1067.6346" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=blas,lapack</text>
+</g>
+<!-- 2w3nq3n3hcj2tqlvcpewsryamltlu5tw -->
+<g id="node23" class="node">
+<title>2w3nq3n3hcj2tqlvcpewsryamltlu5tw</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M2767.3081,-1039.5002C2767.3081,-1039.5002 2166.8311,-1039.5002 2166.8311,-1039.5002 2160.8311,-1039.5002 2154.8311,-1033.5002 2154.8311,-1027.5002 2154.8311,-1027.5002 2154.8311,-964.8998 2154.8311,-964.8998 2154.8311,-958.8998 2160.8311,-952.8998 2166.8311,-952.8998 2166.8311,-952.8998 2767.3081,-952.8998 2767.3081,-952.8998 2773.3081,-952.8998 2779.3081,-958.8998 2779.3081,-964.8998 2779.3081,-964.8998 2779.3081,-1027.5002 2779.3081,-1027.5002 2779.3081,-1033.5002 2773.3081,-1039.5002 2767.3081,-1039.5002"/>
+<text text-anchor="middle" x="2467.0696" y="-989" font-family="Monaco" font-size="24.00" fill="#000000">intel-parallel-studio@cluster.2020.4%gcc@9.4.0/2w3nq3n</text>
+</g>
+<!-- hkcrbrtf2qex6rvzuok5tzdrbam55pdn&#45;&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw -->
+<g id="edge29" class="edge">
+<title>hkcrbrtf2qex6rvzuok5tzdrbam55pdn-&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2256.5586,-1110.7308C2294.3103,-1089.9869 2339.6329,-1065.083 2378.4976,-1043.7276"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2257.5217,-1112.4836C2295.2735,-1091.7397 2340.5961,-1066.8358 2379.4607,-1045.4804"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2381.116,-1047.4235 2388.1946,-1039.5403 2377.745,-1041.2886 2381.116,-1047.4235"/>
+<text text-anchor="middle" x="2286.6606" y="-1079.8414" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=mpi</text>
+</g>
+<!-- gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="node27" class="node">
+<title>gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M1539.1928,-1039.5002C1539.1928,-1039.5002 1152.9464,-1039.5002 1152.9464,-1039.5002 1146.9464,-1039.5002 1140.9464,-1033.5002 1140.9464,-1027.5002 1140.9464,-1027.5002 1140.9464,-964.8998 1140.9464,-964.8998 1140.9464,-958.8998 1146.9464,-952.8998 1152.9464,-952.8998 1152.9464,-952.8998 1539.1928,-952.8998 1539.1928,-952.8998 1545.1928,-952.8998 1551.1928,-958.8998 1551.1928,-964.8998 1551.1928,-964.8998 1551.1928,-1027.5002 1551.1928,-1027.5002 1551.1928,-1033.5002 1545.1928,-1039.5002 1539.1928,-1039.5002"/>
+<text text-anchor="middle" x="1346.0696" y="-989" font-family="Monaco" font-size="24.00" fill="#000000">cmake@3.25.1%gcc@9.4.0/gguve5i</text>
+</g>
+<!-- hkcrbrtf2qex6rvzuok5tzdrbam55pdn&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge17" class="edge">
+<title>hkcrbrtf2qex6rvzuok5tzdrbam55pdn-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1950.9968,-1111.6597C1829.5529,-1088.4802 1680.8338,-1060.0949 1561.2457,-1037.2697"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1561.7091,-1033.795 1551.2303,-1035.3581 1560.3967,-1040.6709 1561.7091,-1033.795"/>
+</g>
+<!-- i4avrindvhcamhurzbfdaggbj2zgsrrh -->
+<g id="node2" class="node">
+<title>i4avrindvhcamhurzbfdaggbj2zgsrrh</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M1536.3649,-86.7002C1536.3649,-86.7002 1155.7743,-86.7002 1155.7743,-86.7002 1149.7743,-86.7002 1143.7743,-80.7002 1143.7743,-74.7002 1143.7743,-74.7002 1143.7743,-12.0998 1143.7743,-12.0998 1143.7743,-6.0998 1149.7743,-.0998 1155.7743,-.0998 1155.7743,-.0998 1536.3649,-.0998 1536.3649,-.0998 1542.3649,-.0998 1548.3649,-6.0998 1548.3649,-12.0998 1548.3649,-12.0998 1548.3649,-74.7002 1548.3649,-74.7002 1548.3649,-80.7002 1542.3649,-86.7002 1536.3649,-86.7002"/>
+<text text-anchor="middle" x="1346.0696" y="-36.2" font-family="Monaco" font-size="24.00" fill="#000000">pkgconf@1.8.0%gcc@9.4.0/i4avrin</text>
+</g>
+<!-- ywrpvv2hgooeepdke33exkqrtdpd5gkl -->
+<g id="node3" class="node">
+<title>ywrpvv2hgooeepdke33exkqrtdpd5gkl</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M849.3673,-721.9002C849.3673,-721.9002 480.7719,-721.9002 480.7719,-721.9002 474.7719,-721.9002 468.7719,-715.9002 468.7719,-709.9002 468.7719,-709.9002 468.7719,-647.2998 468.7719,-647.2998 468.7719,-641.2998 474.7719,-635.2998 480.7719,-635.2998 480.7719,-635.2998 849.3673,-635.2998 849.3673,-635.2998 855.3673,-635.2998 861.3673,-641.2998 861.3673,-647.2998 861.3673,-647.2998 861.3673,-709.9002 861.3673,-709.9002 861.3673,-715.9002 855.3673,-721.9002 849.3673,-721.9002"/>
+<text text-anchor="middle" x="665.0696" y="-671.4" font-family="Monaco" font-size="24.00" fill="#000000">perl@5.36.0%gcc@9.4.0/ywrpvv2</text>
+</g>
+<!-- h3ujmb3ts4kxxxv77knh2knuystuerbx -->
+<g id="node7" class="node">
+<title>h3ujmb3ts4kxxxv77knh2knuystuerbx</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M392.4016,-563.1002C392.4016,-563.1002 19.7376,-563.1002 19.7376,-563.1002 13.7376,-563.1002 7.7376,-557.1002 7.7376,-551.1002 7.7376,-551.1002 7.7376,-488.4998 7.7376,-488.4998 7.7376,-482.4998 13.7376,-476.4998 19.7376,-476.4998 19.7376,-476.4998 392.4016,-476.4998 392.4016,-476.4998 398.4016,-476.4998 404.4016,-482.4998 404.4016,-488.4998 404.4016,-488.4998 404.4016,-551.1002 404.4016,-551.1002 404.4016,-557.1002 398.4016,-563.1002 392.4016,-563.1002"/>
+<text text-anchor="middle" x="206.0696" y="-512.6" font-family="Monaco" font-size="24.00" fill="#000000">bzip2@1.0.8%gcc@9.4.0/h3ujmb3</text>
+</g>
+<!-- ywrpvv2hgooeepdke33exkqrtdpd5gkl&#45;&gt;h3ujmb3ts4kxxxv77knh2knuystuerbx -->
+<g id="edge9" class="edge">
+<title>ywrpvv2hgooeepdke33exkqrtdpd5gkl-&gt;h3ujmb3ts4kxxxv77knh2knuystuerbx</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M539.3189,-636.1522C477.7157,-614.8394 403.4197,-589.1353 340.5959,-567.4002"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M539.9728,-634.2622C478.3696,-612.9494 404.0736,-587.2452 341.2498,-565.5101"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="341.9365,-563.1023 331.3417,-563.1403 339.6478,-569.7176 341.9365,-563.1023"/>
+</g>
+<!-- uabgssx6lsgrevwbttslldnr5nzguprj -->
+<g id="node19" class="node">
+<title>uabgssx6lsgrevwbttslldnr5nzguprj</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M1298.2296,-563.1002C1298.2296,-563.1002 937.9096,-563.1002 937.9096,-563.1002 931.9096,-563.1002 925.9096,-557.1002 925.9096,-551.1002 925.9096,-551.1002 925.9096,-488.4998 925.9096,-488.4998 925.9096,-482.4998 931.9096,-476.4998 937.9096,-476.4998 937.9096,-476.4998 1298.2296,-476.4998 1298.2296,-476.4998 1304.2296,-476.4998 1310.2296,-482.4998 1310.2296,-488.4998 1310.2296,-488.4998 1310.2296,-551.1002 1310.2296,-551.1002 1310.2296,-557.1002 1304.2296,-563.1002 1298.2296,-563.1002"/>
+<text text-anchor="middle" x="1118.0696" y="-512.6" font-family="Monaco" font-size="24.00" fill="#000000">gdbm@1.23%gcc@9.4.0/uabgssx</text>
+</g>
+<!-- ywrpvv2hgooeepdke33exkqrtdpd5gkl&#45;&gt;uabgssx6lsgrevwbttslldnr5nzguprj -->
+<g id="edge44" class="edge">
+<title>ywrpvv2hgooeepdke33exkqrtdpd5gkl-&gt;uabgssx6lsgrevwbttslldnr5nzguprj</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M788.523,-634.2635C849.3209,-612.9507 922.6457,-587.2465 984.6483,-565.5114"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M789.1847,-636.1509C849.9825,-614.8381 923.3073,-589.1339 985.3099,-567.3988"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="986.1559,-569.7515 994.435,-563.1403 983.8402,-563.1456 986.1559,-569.7515"/>
+</g>
+<!-- gkw4dg2p7rdnhru3m6lcnsjbzyr7g3hb -->
+<g id="node20" class="node">
+<title>gkw4dg2p7rdnhru3m6lcnsjbzyr7g3hb</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M896.1744,-563.1002C896.1744,-563.1002 433.9648,-563.1002 433.9648,-563.1002 427.9648,-563.1002 421.9648,-557.1002 421.9648,-551.1002 421.9648,-551.1002 421.9648,-488.4998 421.9648,-488.4998 421.9648,-482.4998 427.9648,-476.4998 433.9648,-476.4998 433.9648,-476.4998 896.1744,-476.4998 896.1744,-476.4998 902.1744,-476.4998 908.1744,-482.4998 908.1744,-488.4998 908.1744,-488.4998 908.1744,-551.1002 908.1744,-551.1002 908.1744,-557.1002 902.1744,-563.1002 896.1744,-563.1002"/>
+<text text-anchor="middle" x="665.0696" y="-512.6" font-family="Monaco" font-size="24.00" fill="#000000">berkeley-db@18.1.40%gcc@9.4.0/gkw4dg2</text>
+</g>
+<!-- ywrpvv2hgooeepdke33exkqrtdpd5gkl&#45;&gt;gkw4dg2p7rdnhru3m6lcnsjbzyr7g3hb -->
+<g id="edge23" class="edge">
+<title>ywrpvv2hgooeepdke33exkqrtdpd5gkl-&gt;gkw4dg2p7rdnhru3m6lcnsjbzyr7g3hb</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M664.0696,-635.2072C664.0696,-616.1263 664.0696,-593.5257 664.0696,-573.4046"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M666.0696,-635.2072C666.0696,-616.1263 666.0696,-593.5257 666.0696,-573.4046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="668.5697,-573.1403 665.0696,-563.1403 661.5697,-573.1404 668.5697,-573.1403"/>
+</g>
+<!-- nizxi5u5bbrzhzwfy2qb7hatlhuswlrz -->
+<g id="node24" class="node">
+<title>nizxi5u5bbrzhzwfy2qb7hatlhuswlrz</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M2195.2248,-563.1002C2195.2248,-563.1002 1840.9144,-563.1002 1840.9144,-563.1002 1834.9144,-563.1002 1828.9144,-557.1002 1828.9144,-551.1002 1828.9144,-551.1002 1828.9144,-488.4998 1828.9144,-488.4998 1828.9144,-482.4998 1834.9144,-476.4998 1840.9144,-476.4998 1840.9144,-476.4998 2195.2248,-476.4998 2195.2248,-476.4998 2201.2248,-476.4998 2207.2248,-482.4998 2207.2248,-488.4998 2207.2248,-488.4998 2207.2248,-551.1002 2207.2248,-551.1002 2207.2248,-557.1002 2201.2248,-563.1002 2195.2248,-563.1002"/>
+<text text-anchor="middle" x="2018.0696" y="-512.6" font-family="Monaco" font-size="24.00" fill="#000000">zlib@1.2.13%gcc@9.4.0/nizxi5u</text>
+</g>
+<!-- ywrpvv2hgooeepdke33exkqrtdpd5gkl&#45;&gt;nizxi5u5bbrzhzwfy2qb7hatlhuswlrz -->
+<g id="edge4" class="edge">
+<title>ywrpvv2hgooeepdke33exkqrtdpd5gkl-&gt;nizxi5u5bbrzhzwfy2qb7hatlhuswlrz</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M861.3292,-654.5584C1116.9929,-624.5514 1561.4447,-572.3867 1818.5758,-542.2075"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M861.5624,-656.5447C1117.2261,-626.5378 1561.6778,-574.373 1818.8089,-544.1939"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1819.373,-546.6449 1828.8968,-542.003 1818.5569,-539.6926 1819.373,-546.6449"/>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id -->
+<g id="node4" class="node">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M2383.212,-1674.7002C2383.212,-1674.7002 1972.9272,-1674.7002 1972.9272,-1674.7002 1966.9272,-1674.7002 1960.9272,-1668.7002 1960.9272,-1662.7002 1960.9272,-1662.7002 1960.9272,-1600.0998 1960.9272,-1600.0998 1960.9272,-1594.0998 1966.9272,-1588.0998 1972.9272,-1588.0998 1972.9272,-1588.0998 2383.212,-1588.0998 2383.212,-1588.0998 2389.212,-1588.0998 2395.212,-1594.0998 2395.212,-1600.0998 2395.212,-1600.0998 2395.212,-1662.7002 2395.212,-1662.7002 2395.212,-1668.7002 2389.212,-1674.7002 2383.212,-1674.7002"/>
+<text text-anchor="middle" x="2178.0696" y="-1624.2" font-family="Monaco" font-size="24.00" fill="#000000">strumpack@7.0.1%gcc@9.4.0/idvshq5</text>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;hkcrbrtf2qex6rvzuok5tzdrbam55pdn -->
+<g id="edge33" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;hkcrbrtf2qex6rvzuok5tzdrbam55pdn</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2177.0696,-1587.8598C2177.0696,-1500.5185 2177.0696,-1304.1624 2177.0696,-1208.8885"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2179.0696,-1587.8598C2179.0696,-1500.5185 2179.0696,-1304.1624 2179.0696,-1208.8885"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2181.5697,-1208.611 2178.0696,-1198.611 2174.5697,-1208.611 2181.5697,-1208.611"/>
+<text text-anchor="middle" x="2125.9224" y="-1397.5399" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=scalapack</text>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="edge8" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1960.6199,-1629.1097C1600.5855,-1621.4505 897.1143,-1596.5054 662.748,-1516.9469 459.8544,-1447.9506 281.1117,-1289.236 401.2427,-1111.0377 418.213,-1086.3492 472.759,-1062.01 530.3793,-1041.9698"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1960.6625,-1627.1101C1600.6564,-1619.4517 897.1852,-1594.5067 663.3912,-1515.0531 461.1823,-1446.4551 282.4397,-1287.7405 402.8965,-1112.1623 419.028,-1088.1757 473.574,-1063.8364 531.0362,-1043.8589"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="532.0142,-1046.1665 540.3395,-1039.6137 529.7449,-1039.5445 532.0142,-1046.1665"/>
+<text text-anchor="middle" x="1175.5163" y="-1600.8866" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=blas,lapack</text>
+</g>
+<!-- imopnxjmv7cwzyiecdw2saq42qvpnauh -->
+<g id="node12" class="node">
+<title>imopnxjmv7cwzyiecdw2saq42qvpnauh</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M3003.3872,-1357.1002C3003.3872,-1357.1002 2606.752,-1357.1002 2606.752,-1357.1002 2600.752,-1357.1002 2594.752,-1351.1002 2594.752,-1345.1002 2594.752,-1345.1002 2594.752,-1282.4998 2594.752,-1282.4998 2594.752,-1276.4998 2600.752,-1270.4998 2606.752,-1270.4998 2606.752,-1270.4998 3003.3872,-1270.4998 3003.3872,-1270.4998 3009.3872,-1270.4998 3015.3872,-1276.4998 3015.3872,-1282.4998 3015.3872,-1282.4998 3015.3872,-1345.1002 3015.3872,-1345.1002 3015.3872,-1351.1002 3009.3872,-1357.1002 3003.3872,-1357.1002"/>
+<text text-anchor="middle" x="2805.0696" y="-1306.6" font-family="Monaco" font-size="24.00" fill="#000000">parmetis@4.0.3%gcc@9.4.0/imopnxj</text>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;imopnxjmv7cwzyiecdw2saq42qvpnauh -->
+<g id="edge51" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;imopnxjmv7cwzyiecdw2saq42qvpnauh</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2393.6993,-1587.0809C2455.3565,-1569.7539 2521.1771,-1546.2699 2577.5864,-1515.1245 2649.1588,-1475.6656 2717.4141,-1409.6691 2759.9512,-1363.9364"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2394.2404,-1589.0062C2456.0286,-1571.6376 2521.8491,-1548.1536 2578.5528,-1516.8755 2650.5491,-1477.1034 2718.8043,-1411.107 2761.4156,-1365.2986"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2763.3454,-1366.8938 2767.5512,-1357.1695 2758.1992,-1362.1485 2763.3454,-1366.8938"/>
+</g>
+<!-- ern66gyp6qmhmpod4jaynxx4weoberfm -->
+<g id="node13" class="node">
+<title>ern66gyp6qmhmpod4jaynxx4weoberfm</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M2928.3784,-1198.3002C2928.3784,-1198.3002 2563.7608,-1198.3002 2563.7608,-1198.3002 2557.7608,-1198.3002 2551.7608,-1192.3002 2551.7608,-1186.3002 2551.7608,-1186.3002 2551.7608,-1123.6998 2551.7608,-1123.6998 2551.7608,-1117.6998 2557.7608,-1111.6998 2563.7608,-1111.6998 2563.7608,-1111.6998 2928.3784,-1111.6998 2928.3784,-1111.6998 2934.3784,-1111.6998 2940.3784,-1117.6998 2940.3784,-1123.6998 2940.3784,-1123.6998 2940.3784,-1186.3002 2940.3784,-1186.3002 2940.3784,-1192.3002 2934.3784,-1198.3002 2928.3784,-1198.3002"/>
+<text text-anchor="middle" x="2746.0696" y="-1147.8" font-family="Monaco" font-size="24.00" fill="#000000">metis@5.1.0%gcc@9.4.0/ern66gy</text>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;ern66gyp6qmhmpod4jaynxx4weoberfm -->
+<g id="edge25" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;ern66gyp6qmhmpod4jaynxx4weoberfm</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2371.6269,-1587.103C2443.5875,-1567.249 2513.691,-1542.0963 2537.3223,-1515.3355 2611.3482,-1433.6645 2525.4748,-1364.8484 2585.2274,-1269.8608 2602.2478,-1243.3473 2627.3929,-1221.1402 2652.8797,-1203.3777"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2372.1589,-1589.0309C2444.2629,-1569.1315 2514.3664,-1543.9788 2538.8169,-1516.6645 2612.5989,-1432.1038 2526.7255,-1363.2878 2586.9118,-1270.9392 2603.5717,-1244.8464 2628.7168,-1222.6393 2654.0229,-1205.0188"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2655.7411,-1206.8749 2662.0621,-1198.3722 2651.8184,-1201.0773 2655.7411,-1206.8749"/>
+</g>
+<!-- nqiyrxlid6tikfpvoqdpvsjt5drs2obf -->
+<g id="node14" class="node">
+<title>nqiyrxlid6tikfpvoqdpvsjt5drs2obf</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M1964.017,-1357.1002C1964.017,-1357.1002 1532.1222,-1357.1002 1532.1222,-1357.1002 1526.1222,-1357.1002 1520.1222,-1351.1002 1520.1222,-1345.1002 1520.1222,-1345.1002 1520.1222,-1282.4998 1520.1222,-1282.4998 1520.1222,-1276.4998 1526.1222,-1270.4998 1532.1222,-1270.4998 1532.1222,-1270.4998 1964.017,-1270.4998 1964.017,-1270.4998 1970.017,-1270.4998 1976.017,-1276.4998 1976.017,-1282.4998 1976.017,-1282.4998 1976.017,-1345.1002 1976.017,-1345.1002 1976.017,-1351.1002 1970.017,-1357.1002 1964.017,-1357.1002"/>
+<text text-anchor="middle" x="1748.0696" y="-1306.6" font-family="Monaco" font-size="24.00" fill="#000000">butterflypack@2.2.2%gcc@9.4.0/nqiyrxl</text>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;nqiyrxlid6tikfpvoqdpvsjt5drs2obf -->
+<g id="edge26" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;nqiyrxlid6tikfpvoqdpvsjt5drs2obf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2118.5874,-1588.7094C2039.1194,-1530.0139 1897.9154,-1425.72 1814.4793,-1364.0937"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2119.7757,-1587.1006C2040.3076,-1528.4052 1899.1036,-1424.1112 1815.6675,-1362.485"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1817.0581,-1360.404 1806.9348,-1357.2781 1812.8992,-1366.0347 1817.0581,-1360.404"/>
+</g>
+<!-- 4bu62kyfuh4ikdkuyxfxjxanf7e7qopu -->
+<g id="node16" class="node">
+<title>4bu62kyfuh4ikdkuyxfxjxanf7e7qopu</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M1106.2192,-1515.9002C1106.2192,-1515.9002 683.92,-1515.9002 683.92,-1515.9002 677.92,-1515.9002 671.92,-1509.9002 671.92,-1503.9002 671.92,-1503.9002 671.92,-1441.2998 671.92,-1441.2998 671.92,-1435.2998 677.92,-1429.2998 683.92,-1429.2998 683.92,-1429.2998 1106.2192,-1429.2998 1106.2192,-1429.2998 1112.2192,-1429.2998 1118.2192,-1435.2998 1118.2192,-1441.2998 1118.2192,-1441.2998 1118.2192,-1503.9002 1118.2192,-1503.9002 1118.2192,-1509.9002 1112.2192,-1515.9002 1106.2192,-1515.9002"/>
+<text text-anchor="middle" x="895.0696" y="-1465.4" font-family="Monaco" font-size="24.00" fill="#000000">slate@2022.07.00%gcc@9.4.0/4bu62ky</text>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;4bu62kyfuh4ikdkuyxfxjxanf7e7qopu -->
+<g id="edge5" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;4bu62kyfuh4ikdkuyxfxjxanf7e7qopu</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1960.6663,-1605.4991C1729.5518,-1576.8935 1365.2868,-1531.8075 1128.237,-1502.4673"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1960.912,-1603.5143C1729.7975,-1574.9086 1365.5325,-1529.8227 1128.4827,-1500.4825"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1128.5789,-1497.9754 1118.2247,-1500.2204 1127.719,-1504.9224 1128.5789,-1497.9754"/>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw -->
+<g id="edge20" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2395.1113,-1591.5061C2621.5772,-1545.7968 2953.3457,-1462.5053 3023.2362,-1356.6473 3049.986,-1316.785 3021.2047,-1131.5143 3003.3326,-1112.2759 2971.8969,-1077.7826 2884.3944,-1052.6467 2789.1441,-1034.9179"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2395.507,-1593.4665C2622.0642,-1547.7366 2953.8327,-1464.4452 3024.903,-1357.7527 3051.9623,-1316.478 3023.181,-1131.2073 3004.8066,-1110.9241 2972.4491,-1075.8603 2884.9466,-1050.7244 2789.5102,-1032.9517"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2789.9449,-1030.4898 2779.4781,-1032.132 2788.6845,-1037.3754 2789.9449,-1030.4898"/>
+<text text-anchor="middle" x="2611.7445" y="-1537.8321" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=mpi</text>
+</g>
+<!-- 7rzbmgoxhmm2jhellkgcjmn62uklf22x -->
+<g id="node25" class="node">
+<title>7rzbmgoxhmm2jhellkgcjmn62uklf22x</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M1749.1952,-1515.9002C1749.1952,-1515.9002 1398.944,-1515.9002 1398.944,-1515.9002 1392.944,-1515.9002 1386.944,-1509.9002 1386.944,-1503.9002 1386.944,-1503.9002 1386.944,-1441.2998 1386.944,-1441.2998 1386.944,-1435.2998 1392.944,-1429.2998 1398.944,-1429.2998 1398.944,-1429.2998 1749.1952,-1429.2998 1749.1952,-1429.2998 1755.1952,-1429.2998 1761.1952,-1435.2998 1761.1952,-1441.2998 1761.1952,-1441.2998 1761.1952,-1503.9002 1761.1952,-1503.9002 1761.1952,-1509.9002 1755.1952,-1515.9002 1749.1952,-1515.9002"/>
+<text text-anchor="middle" x="1574.0696" y="-1465.4" font-family="Monaco" font-size="24.00" fill="#000000">zfp@0.5.5%gcc@9.4.0/7rzbmgo</text>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;7rzbmgoxhmm2jhellkgcjmn62uklf22x -->
+<g id="edge36" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;7rzbmgoxhmm2jhellkgcjmn62uklf22x</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2012.7697,-1588.9743C1930.7903,-1567.4208 1831.729,-1541.3762 1748.4742,-1519.4874"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2013.2782,-1587.0401C1931.2989,-1565.4866 1832.2376,-1539.442 1748.9827,-1517.5531"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1749.477,-1515.0982 1738.9157,-1515.9403 1747.697,-1521.8681 1749.477,-1515.0982"/>
+</g>
+<!-- idvshq5nqmygzd4uo62mdispwgxsw7id&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge3" class="edge">
+<title>idvshq5nqmygzd4uo62mdispwgxsw7id-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2229.2864,-1587.9836C2336.2076,-1492.3172 2562.5717,-1260.0833 2429.0696,-1111.6 2372.2327,-1048.3851 1860.8259,-1017.0375 1561.5401,-1003.9799"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1561.5673,-1000.4779 1551.4253,-1003.5421 1561.2645,-1007.4714 1561.5673,-1000.4779"/>
+</g>
+<!-- mujlx42xgttdc6u6rmiftsktpsrcmpbs -->
+<g id="node5" class="node">
+<title>mujlx42xgttdc6u6rmiftsktpsrcmpbs</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M912.4048,-1198.3002C912.4048,-1198.3002 475.7344,-1198.3002 475.7344,-1198.3002 469.7344,-1198.3002 463.7344,-1192.3002 463.7344,-1186.3002 463.7344,-1186.3002 463.7344,-1123.6998 463.7344,-1123.6998 463.7344,-1117.6998 469.7344,-1111.6998 475.7344,-1111.6998 475.7344,-1111.6998 912.4048,-1111.6998 912.4048,-1111.6998 918.4048,-1111.6998 924.4048,-1117.6998 924.4048,-1123.6998 924.4048,-1123.6998 924.4048,-1186.3002 924.4048,-1186.3002 924.4048,-1192.3002 918.4048,-1198.3002 912.4048,-1198.3002"/>
+<text text-anchor="middle" x="694.0696" y="-1147.8" font-family="Monaco" font-size="24.00" fill="#000000">blaspp@2022.07.00%gcc@9.4.0/mujlx42</text>
+</g>
+<!-- mujlx42xgttdc6u6rmiftsktpsrcmpbs&#45;&gt;o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="edge16" class="edge">
+<title>mujlx42xgttdc6u6rmiftsktpsrcmpbs-&gt;o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M693.0696,-1111.6072C693.0696,-1092.5263 693.0696,-1069.9257 693.0696,-1049.8046"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M695.0696,-1111.6072C695.0696,-1092.5263 695.0696,-1069.9257 695.0696,-1049.8046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="697.5697,-1049.5403 694.0696,-1039.5403 690.5697,-1049.5404 697.5697,-1049.5403"/>
+<text text-anchor="middle" x="657.8516" y="-1079.8482" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=blas</text>
+</g>
+<!-- mujlx42xgttdc6u6rmiftsktpsrcmpbs&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge28" class="edge">
+<title>mujlx42xgttdc6u6rmiftsktpsrcmpbs-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M872.2315,-1111.6072C960.9952,-1089.988 1068.311,-1063.8504 1158.3512,-1041.9204"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1159.2354,-1045.3074 1168.1232,-1039.5403 1157.5789,-1038.5062 1159.2354,-1045.3074"/>
+</g>
+<!-- htzjns66gmq6pjofohp26djmjnpbegho -->
+<g id="node6" class="node">
+<title>htzjns66gmq6pjofohp26djmjnpbegho</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M2663.3553,-880.7002C2663.3553,-880.7002 2270.7839,-880.7002 2270.7839,-880.7002 2264.7839,-880.7002 2258.7839,-874.7002 2258.7839,-868.7002 2258.7839,-868.7002 2258.7839,-806.0998 2258.7839,-806.0998 2258.7839,-800.0998 2264.7839,-794.0998 2270.7839,-794.0998 2270.7839,-794.0998 2663.3553,-794.0998 2663.3553,-794.0998 2669.3553,-794.0998 2675.3553,-800.0998 2675.3553,-806.0998 2675.3553,-806.0998 2675.3553,-868.7002 2675.3553,-868.7002 2675.3553,-874.7002 2669.3553,-880.7002 2663.3553,-880.7002"/>
+<text text-anchor="middle" x="2467.0696" y="-830.2" font-family="Monaco" font-size="24.00" fill="#000000">patchelf@0.16.1%gcc@9.4.0/htzjns6</text>
+</g>
+<!-- xm3ldz3y3msfdc3hzshvxpbpg5hnt6o6 -->
+<g id="node15" class="node">
+<title>xm3ldz3y3msfdc3hzshvxpbpg5hnt6o6</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M394.2232,-404.3002C394.2232,-404.3002 17.916,-404.3002 17.916,-404.3002 11.916,-404.3002 5.916,-398.3002 5.916,-392.3002 5.916,-392.3002 5.916,-329.6998 5.916,-329.6998 5.916,-323.6998 11.916,-317.6998 17.916,-317.6998 17.916,-317.6998 394.2232,-317.6998 394.2232,-317.6998 400.2232,-317.6998 406.2232,-323.6998 406.2232,-329.6998 406.2232,-329.6998 406.2232,-392.3002 406.2232,-392.3002 406.2232,-398.3002 400.2232,-404.3002 394.2232,-404.3002"/>
+<text text-anchor="middle" x="206.0696" y="-353.8" font-family="Monaco" font-size="24.00" fill="#000000">diffutils@3.8%gcc@9.4.0/xm3ldz3</text>
+</g>
+<!-- h3ujmb3ts4kxxxv77knh2knuystuerbx&#45;&gt;xm3ldz3y3msfdc3hzshvxpbpg5hnt6o6 -->
+<g id="edge1" class="edge">
+<title>h3ujmb3ts4kxxxv77knh2knuystuerbx-&gt;xm3ldz3y3msfdc3hzshvxpbpg5hnt6o6</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M206.0696,-476.4072C206.0696,-457.3263 206.0696,-434.7257 206.0696,-414.6046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="209.5697,-414.3403 206.0696,-404.3403 202.5697,-414.3404 209.5697,-414.3403"/>
+</g>
+<!-- o524gebsxavobkte3k5fglgwnedfkadf&#45;&gt;ywrpvv2hgooeepdke33exkqrtdpd5gkl -->
+<g id="edge11" class="edge">
+<title>o524gebsxavobkte3k5fglgwnedfkadf-&gt;ywrpvv2hgooeepdke33exkqrtdpd5gkl</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M690.0981,-952.705C684.8522,-895.2533 675.6173,-794.1153 669.9514,-732.0637"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="673.4345,-731.7184 669.0396,-722.0781 666.4635,-732.355 673.4345,-731.7184"/>
+</g>
+<!-- 4vsmjofkhntilgzh4zebluqak5mdsu3x -->
+<g id="node9" class="node">
+<title>4vsmjofkhntilgzh4zebluqak5mdsu3x</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M1977.9121,-721.9002C1977.9121,-721.9002 1386.2271,-721.9002 1386.2271,-721.9002 1380.2271,-721.9002 1374.2271,-715.9002 1374.2271,-709.9002 1374.2271,-709.9002 1374.2271,-647.2998 1374.2271,-647.2998 1374.2271,-641.2998 1380.2271,-635.2998 1386.2271,-635.2998 1386.2271,-635.2998 1977.9121,-635.2998 1977.9121,-635.2998 1983.9121,-635.2998 1989.9121,-641.2998 1989.9121,-647.2998 1989.9121,-647.2998 1989.9121,-709.9002 1989.9121,-709.9002 1989.9121,-715.9002 1983.9121,-721.9002 1977.9121,-721.9002"/>
+<text text-anchor="middle" x="1682.0696" y="-671.4" font-family="Monaco" font-size="24.00" fill="#000000">ca-certificates-mozilla@2023-01-10%gcc@9.4.0/4vsmjof</text>
+</g>
+<!-- xiro2z6na56qdd4czjhj54eag3ekbiow -->
+<g id="node10" class="node">
+<title>xiro2z6na56qdd4czjhj54eag3ekbiow</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M988.1824,-1357.1002C988.1824,-1357.1002 533.9568,-1357.1002 533.9568,-1357.1002 527.9568,-1357.1002 521.9568,-1351.1002 521.9568,-1345.1002 521.9568,-1345.1002 521.9568,-1282.4998 521.9568,-1282.4998 521.9568,-1276.4998 527.9568,-1270.4998 533.9568,-1270.4998 533.9568,-1270.4998 988.1824,-1270.4998 988.1824,-1270.4998 994.1824,-1270.4998 1000.1824,-1276.4998 1000.1824,-1282.4998 1000.1824,-1282.4998 1000.1824,-1345.1002 1000.1824,-1345.1002 1000.1824,-1351.1002 994.1824,-1357.1002 988.1824,-1357.1002"/>
+<text text-anchor="middle" x="761.0696" y="-1306.6" font-family="Monaco" font-size="24.00" fill="#000000">lapackpp@2022.07.00%gcc@9.4.0/xiro2z6</text>
+</g>
+<!-- xiro2z6na56qdd4czjhj54eag3ekbiow&#45;&gt;mujlx42xgttdc6u6rmiftsktpsrcmpbs -->
+<g id="edge37" class="edge">
+<title>xiro2z6na56qdd4czjhj54eag3ekbiow-&gt;mujlx42xgttdc6u6rmiftsktpsrcmpbs</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M741.8402,-1270.7959C733.6789,-1251.4525 723.9915,-1228.4917 715.4149,-1208.1641"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M743.6829,-1270.0185C735.5216,-1250.675 725.8342,-1227.7143 717.2576,-1207.3866"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="719.4676,-1206.1933 712.3555,-1198.3403 713.0181,-1208.9144 719.4676,-1206.1933"/>
+</g>
+<!-- xiro2z6na56qdd4czjhj54eag3ekbiow&#45;&gt;o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="edge35" class="edge">
+<title>xiro2z6na56qdd4czjhj54eag3ekbiow-&gt;o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M597.2326,-1271.3826C534.1471,-1251.0571 472.8527,-1225.5904 454.2471,-1198.9688 432.1275,-1166.6075 433.5639,-1144.2113 454.2226,-1111.0684 472.6194,-1081.8657 500.3255,-1060.004 530.6572,-1043.4601"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M597.8458,-1269.4789C534.9144,-1249.2102 473.6201,-1223.7435 455.8921,-1197.8312 434.1234,-1166.7355 435.5598,-1144.3393 455.9166,-1112.1316 473.8583,-1083.4358 501.5644,-1061.5741 531.6142,-1045.2163"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="532.9062,-1047.362 540.1422,-1039.6231 529.6595,-1041.1605 532.9062,-1047.362"/>
+<text text-anchor="middle" x="474.3109" y="-1250.2598" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=blas,lapack</text>
+</g>
+<!-- xiro2z6na56qdd4czjhj54eag3ekbiow&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge45" class="edge">
+<title>xiro2z6na56qdd4czjhj54eag3ekbiow-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M833.5823,-1270.3956C865.3249,-1250.0918 902.2709,-1224.6296 933.0696,-1198.4 973.2414,-1164.1878 969.8532,-1140.395 1014.0696,-1111.6 1058.5051,-1082.6623 1111.0286,-1060.0733 1161.029,-1042.8573"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1162.313,-1046.1177 1170.6621,-1039.5953 1160.0678,-1039.4876 1162.313,-1046.1177"/>
+</g>
+<!-- j5rupoqliu7kasm6xndl7ui32wgawkru -->
+<g id="node11" class="node">
+<title>j5rupoqliu7kasm6xndl7ui32wgawkru</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M1527.3625,-245.5002C1527.3625,-245.5002 1164.7767,-245.5002 1164.7767,-245.5002 1158.7767,-245.5002 1152.7767,-239.5002 1152.7767,-233.5002 1152.7767,-233.5002 1152.7767,-170.8998 1152.7767,-170.8998 1152.7767,-164.8998 1158.7767,-158.8998 1164.7767,-158.8998 1164.7767,-158.8998 1527.3625,-158.8998 1527.3625,-158.8998 1533.3625,-158.8998 1539.3625,-164.8998 1539.3625,-170.8998 1539.3625,-170.8998 1539.3625,-233.5002 1539.3625,-233.5002 1539.3625,-239.5002 1533.3625,-245.5002 1527.3625,-245.5002"/>
+<text text-anchor="middle" x="1346.0696" y="-195" font-family="Monaco" font-size="24.00" fill="#000000">ncurses@6.4%gcc@9.4.0/j5rupoq</text>
+</g>
+<!-- j5rupoqliu7kasm6xndl7ui32wgawkru&#45;&gt;i4avrindvhcamhurzbfdaggbj2zgsrrh -->
+<g id="edge15" class="edge">
+<title>j5rupoqliu7kasm6xndl7ui32wgawkru-&gt;i4avrindvhcamhurzbfdaggbj2zgsrrh</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1346.0696,-158.8072C1346.0696,-139.7263 1346.0696,-117.1257 1346.0696,-97.0046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1349.5697,-96.7403 1346.0696,-86.7403 1342.5697,-96.7404 1349.5697,-96.7403"/>
+<text text-anchor="middle" x="1292.7436" y="-127.0482" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=pkgconfig</text>
+</g>
+<!-- imopnxjmv7cwzyiecdw2saq42qvpnauh&#45;&gt;ern66gyp6qmhmpod4jaynxx4weoberfm -->
+<g id="edge19" class="edge">
+<title>imopnxjmv7cwzyiecdw2saq42qvpnauh-&gt;ern66gyp6qmhmpod4jaynxx4weoberfm</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2788.0102,-1270.7555C2780.8234,-1251.412 2772.2926,-1228.4513 2764.7402,-1208.1236"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2789.885,-1270.0589C2782.6982,-1250.7155 2774.1674,-1227.7547 2766.615,-1207.4271"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2768.9358,-1206.4953 2762.1721,-1198.3403 2762.3741,-1208.9332 2768.9358,-1206.4953"/>
+</g>
+<!-- imopnxjmv7cwzyiecdw2saq42qvpnauh&#45;&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw -->
+<g id="edge12" class="edge">
+<title>imopnxjmv7cwzyiecdw2saq42qvpnauh-&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2907.2846,-1269.5018C2936.475,-1251.8137 2964.9158,-1228.1116 2981.1904,-1197.9236 2999.477,-1164.2363 3005.2125,-1141.4693 2981.289,-1112.225 2954.5472,-1078.5579 2876.5297,-1053.8974 2789.2983,-1036.3535"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M2908.3216,-1271.2119C2937.7554,-1253.3501 2966.1962,-1229.648 2982.9488,-1198.8764 3001.4164,-1164.7249 3007.1519,-1141.9579 2982.8502,-1110.975 2955.15,-1076.6509 2877.1325,-1051.9904 2789.6927,-1034.3928"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2790.125,-1031.93 2779.6364,-1033.4269 2788.7692,-1038.7974 2790.125,-1031.93"/>
+<text text-anchor="middle" x="2836.0561" y="-1059.5023" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=mpi</text>
+</g>
+<!-- imopnxjmv7cwzyiecdw2saq42qvpnauh&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge49" class="edge">
+<title>imopnxjmv7cwzyiecdw2saq42qvpnauh-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2883.731,-1270.4691C2909.4451,-1251.9243 2934.9956,-1227.7144 2949.0696,-1198.4 2965.7663,-1163.6227 2975.3506,-1139.841 2949.0696,-1111.6 2925.7161,-1086.5049 1993.0368,-1031.9055 1561.3071,-1007.9103"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1561.3813,-1004.4092 1551.2026,-1007.3492 1560.9931,-1011.3984 1561.3813,-1004.4092"/>
+</g>
+<!-- ern66gyp6qmhmpod4jaynxx4weoberfm&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge50" class="edge">
+<title>ern66gyp6qmhmpod4jaynxx4weoberfm-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2551.6031,-1113.7387C2547.0531,-1112.9948 2542.537,-1112.2802 2538.0696,-1111.6 2198.5338,-1059.8997 1800.8632,-1026.8711 1561.4583,-1009.9443"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1561.4619,-1006.436 1551.2407,-1009.2249 1560.9702,-1013.4187 1561.4619,-1006.436"/>
+</g>
+<!-- nqiyrxlid6tikfpvoqdpvsjt5drs2obf&#45;&gt;hkcrbrtf2qex6rvzuok5tzdrbam55pdn -->
+<g id="edge34" class="edge">
+<title>nqiyrxlid6tikfpvoqdpvsjt5drs2obf-&gt;hkcrbrtf2qex6rvzuok5tzdrbam55pdn</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1865.2226,-1269.4691C1922.6966,-1248.2438 1991.964,-1222.6632 2050.6644,-1200.985"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1865.9154,-1271.3453C1923.3894,-1250.12 1992.6569,-1224.5394 2051.3572,-1202.8612"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2052.5441,-1205.088 2060.7123,-1198.3403 2050.119,-1198.5215 2052.5441,-1205.088"/>
+<text text-anchor="middle" x="1910.9073" y="-1238.6056" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=scalapack</text>
+</g>
+<!-- nqiyrxlid6tikfpvoqdpvsjt5drs2obf&#45;&gt;o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="edge52" class="edge">
+<title>nqiyrxlid6tikfpvoqdpvsjt5drs2obf-&gt;o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1519.9696,-1290.6844C1394.6018,-1273.3057 1237.6631,-1244.7294 1102.7507,-1199.3478 1021.8138,-1171.8729 1008.1992,-1149.8608 932.6248,-1112.4956 887.1715,-1089.9216 836.578,-1065.4054 793.6914,-1044.8018"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1520.2442,-1288.7034C1394.9601,-1271.3381 1238.0214,-1242.7618 1103.3885,-1197.4522 1023.5148,-1170.8208 1009.9002,-1148.8087 933.5144,-1110.7044 888.0436,-1088.1218 837.4502,-1063.6056 794.5574,-1042.999"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="795.6235,-1040.7377 785.0938,-1039.565 792.5939,-1047.0482 795.6235,-1040.7377"/>
+<text text-anchor="middle" x="1046.8307" y="-1202.5988" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=blas,lapack</text>
+</g>
+<!-- lfh3aovn65e66cs24qiehq3nd2ddojef -->
+<g id="node21" class="node">
+<title>lfh3aovn65e66cs24qiehq3nd2ddojef</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M1547.9922,-1198.3002C1547.9922,-1198.3002 1144.147,-1198.3002 1144.147,-1198.3002 1138.147,-1198.3002 1132.147,-1192.3002 1132.147,-1186.3002 1132.147,-1186.3002 1132.147,-1123.6998 1132.147,-1123.6998 1132.147,-1117.6998 1138.147,-1111.6998 1144.147,-1111.6998 1144.147,-1111.6998 1547.9922,-1111.6998 1547.9922,-1111.6998 1553.9922,-1111.6998 1559.9922,-1117.6998 1559.9922,-1123.6998 1559.9922,-1123.6998 1559.9922,-1186.3002 1559.9922,-1186.3002 1559.9922,-1192.3002 1553.9922,-1198.3002 1547.9922,-1198.3002"/>
+<text text-anchor="middle" x="1346.0696" y="-1147.8" font-family="Monaco" font-size="24.00" fill="#000000">arpack-ng@3.8.0%gcc@9.4.0/lfh3aov</text>
+</g>
+<!-- nqiyrxlid6tikfpvoqdpvsjt5drs2obf&#45;&gt;lfh3aovn65e66cs24qiehq3nd2ddojef -->
+<g id="edge46" class="edge">
+<title>nqiyrxlid6tikfpvoqdpvsjt5drs2obf-&gt;lfh3aovn65e66cs24qiehq3nd2ddojef</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1637.8539,-1271.3373C1584.2332,-1250.1557 1519.6324,-1224.6368 1464.827,-1202.9873"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1638.5887,-1269.4771C1584.968,-1248.2956 1520.3672,-1222.7767 1465.5618,-1201.1272"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1466.3716,-1198.7592 1455.785,-1198.3403 1463.7998,-1205.2696 1466.3716,-1198.7592"/>
+</g>
+<!-- 57joith2sqq6sehge54vlloyolm36mdu -->
+<g id="node22" class="node">
+<title>57joith2sqq6sehge54vlloyolm36mdu</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M1906.2352,-1198.3002C1906.2352,-1198.3002 1589.904,-1198.3002 1589.904,-1198.3002 1583.904,-1198.3002 1577.904,-1192.3002 1577.904,-1186.3002 1577.904,-1186.3002 1577.904,-1123.6998 1577.904,-1123.6998 1577.904,-1117.6998 1583.904,-1111.6998 1589.904,-1111.6998 1589.904,-1111.6998 1906.2352,-1111.6998 1906.2352,-1111.6998 1912.2352,-1111.6998 1918.2352,-1117.6998 1918.2352,-1123.6998 1918.2352,-1123.6998 1918.2352,-1186.3002 1918.2352,-1186.3002 1918.2352,-1192.3002 1912.2352,-1198.3002 1906.2352,-1198.3002"/>
+<text text-anchor="middle" x="1748.0696" y="-1147.8" font-family="Monaco" font-size="24.00" fill="#000000">sed@4.8%gcc@9.4.0/57joith</text>
+</g>
+<!-- nqiyrxlid6tikfpvoqdpvsjt5drs2obf&#45;&gt;57joith2sqq6sehge54vlloyolm36mdu -->
+<g id="edge27" class="edge">
+<title>nqiyrxlid6tikfpvoqdpvsjt5drs2obf-&gt;57joith2sqq6sehge54vlloyolm36mdu</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1748.0696,-1270.4072C1748.0696,-1251.3263 1748.0696,-1228.7257 1748.0696,-1208.6046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1751.5697,-1208.3403 1748.0696,-1198.3403 1744.5697,-1208.3404 1751.5697,-1208.3403"/>
+</g>
+<!-- nqiyrxlid6tikfpvoqdpvsjt5drs2obf&#45;&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw -->
+<g id="edge24" class="edge">
+<title>nqiyrxlid6tikfpvoqdpvsjt5drs2obf-&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1975.9734,-1301.684C2148.2819,-1288.3961 2365.6859,-1259.5384 2428.3689,-1197.6866 2466.9261,-1160.1438 2472.9783,-1095.7153 2471.5152,-1049.9701"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1976.1272,-1303.678C2148.5451,-1290.3788 2365.949,-1261.521 2429.7703,-1199.1134 2468.9173,-1160.3309 2474.9695,-1095.9024 2473.5142,-1049.9065"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2476.0078,-1049.7027 2472.0657,-1039.8686 2469.0147,-1050.0146 2476.0078,-1049.7027"/>
+<text text-anchor="middle" x="2207.8884" y="-1273.0053" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=mpi</text>
+</g>
+<!-- nqiyrxlid6tikfpvoqdpvsjt5drs2obf&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge6" class="edge">
+<title>nqiyrxlid6tikfpvoqdpvsjt5drs2obf-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1520.1614,-1301.6771C1362.9712,-1287.992 1173.582,-1259.0928 1123.0696,-1198.4 1098.3914,-1168.7481 1103.0165,-1144.5563 1123.0696,-1111.6 1140.5998,-1082.79 1167.9002,-1060.8539 1197.4647,-1044.2681"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1199.1408,-1047.3408 1206.2789,-1039.5114 1195.8163,-1041.1806 1199.1408,-1047.3408"/>
+</g>
+<!-- ogcucq2eod3xusvvied5ol2iobui4nsb -->
+<g id="node18" class="node">
+<title>ogcucq2eod3xusvvied5ol2iobui4nsb</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M400.2088,-245.5002C400.2088,-245.5002 11.9304,-245.5002 11.9304,-245.5002 5.9304,-245.5002 -.0696,-239.5002 -.0696,-233.5002 -.0696,-233.5002 -.0696,-170.8998 -.0696,-170.8998 -.0696,-164.8998 5.9304,-158.8998 11.9304,-158.8998 11.9304,-158.8998 400.2088,-158.8998 400.2088,-158.8998 406.2088,-158.8998 412.2088,-164.8998 412.2088,-170.8998 412.2088,-170.8998 412.2088,-233.5002 412.2088,-233.5002 412.2088,-239.5002 406.2088,-245.5002 400.2088,-245.5002"/>
+<text text-anchor="middle" x="206.0696" y="-195" font-family="Monaco" font-size="24.00" fill="#000000">libiconv@1.17%gcc@9.4.0/ogcucq2</text>
+</g>
+<!-- xm3ldz3y3msfdc3hzshvxpbpg5hnt6o6&#45;&gt;ogcucq2eod3xusvvied5ol2iobui4nsb -->
+<g id="edge47" class="edge">
+<title>xm3ldz3y3msfdc3hzshvxpbpg5hnt6o6-&gt;ogcucq2eod3xusvvied5ol2iobui4nsb</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M205.0696,-317.6072C205.0696,-298.5263 205.0696,-275.9257 205.0696,-255.8046"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M207.0696,-317.6072C207.0696,-298.5263 207.0696,-275.9257 207.0696,-255.8046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="209.5697,-255.5403 206.0696,-245.5403 202.5697,-255.5404 209.5697,-255.5403"/>
+<text text-anchor="middle" x="165.5739" y="-285.8482" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=iconv</text>
+</g>
+<!-- 4bu62kyfuh4ikdkuyxfxjxanf7e7qopu&#45;&gt;mujlx42xgttdc6u6rmiftsktpsrcmpbs -->
+<g id="edge42" class="edge">
+<title>4bu62kyfuh4ikdkuyxfxjxanf7e7qopu-&gt;mujlx42xgttdc6u6rmiftsktpsrcmpbs</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M672.6614,-1430.2151C600.7916,-1411.3548 534.1254,-1386.9583 512.2667,-1357.7962 489.0909,-1326.029 493.54,-1304.0273 512.1928,-1269.9192 527.5256,-1242.0821 552.3382,-1220.1508 578.9347,-1203.0434"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M673.169,-1428.2806C601.4789,-1409.4766 534.8127,-1385.0802 513.8725,-1356.6038 491.0512,-1326.4254 495.5003,-1304.4237 513.9464,-1270.8808 528.8502,-1243.5806 553.6627,-1221.6493 580.016,-1204.7259"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="581.46,-1206.7724 588.1193,-1198.532 577.7747,-1200.8211 581.46,-1206.7724"/>
+</g>
+<!-- 4bu62kyfuh4ikdkuyxfxjxanf7e7qopu&#45;&gt;o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="edge43" class="edge">
+<title>4bu62kyfuh4ikdkuyxfxjxanf7e7qopu-&gt;o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M680.4783,-1430.2246C600.8632,-1410.3933 522.8724,-1385.2921 493.3877,-1357.9314 411.1392,-1281.1573 374.1678,-1206.1582 435.2305,-1111.0561 454.3431,-1081.6726 482.5021,-1059.8261 513.5088,-1043.3725"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M680.9617,-1428.2839C601.476,-1408.4895 523.4851,-1383.3883 494.7515,-1356.4686 412.9331,-1280.273 375.9616,-1205.2739 436.9087,-1112.1439 455.569,-1083.2528 483.728,-1061.4063 514.4455,-1045.1396"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="515.8631,-1047.2236 523.1893,-1039.5699 512.6893,-1040.9844 515.8631,-1047.2236"/>
+<text text-anchor="middle" x="453.0969" y="-1356.92" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=blas</text>
+</g>
+<!-- 4bu62kyfuh4ikdkuyxfxjxanf7e7qopu&#45;&gt;xiro2z6na56qdd4czjhj54eag3ekbiow -->
+<g id="edge38" class="edge">
+<title>4bu62kyfuh4ikdkuyxfxjxanf7e7qopu-&gt;xiro2z6na56qdd4czjhj54eag3ekbiow</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M857.6892,-1429.8521C840.9235,-1409.9835 820.9375,-1386.2985 803.4466,-1365.5705"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M859.2178,-1428.5623C842.4521,-1408.6937 822.466,-1385.0087 804.9751,-1364.2807"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="806.7654,-1362.5258 797.6414,-1357.1403 801.4156,-1367.0402 806.7654,-1362.5258"/>
+</g>
+<!-- 4bu62kyfuh4ikdkuyxfxjxanf7e7qopu&#45;&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw -->
+<g id="edge13" class="edge">
+<title>4bu62kyfuh4ikdkuyxfxjxanf7e7qopu-&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1118.1783,-1450.5735C1412.4221,-1422.447 1902.6188,-1374.0528 1984.8578,-1356.2227 2203.916,-1308.9943 2329.6342,-1377.1305 2461.2658,-1197.8052 2492.3675,-1156.1664 2488.743,-1094.1171 2480.3694,-1050.0521"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1118.3686,-1452.5644C1412.6186,-1424.4374 1902.8153,-1376.0432 1985.2814,-1358.1773 2202.963,-1310.7526 2328.6812,-1378.8889 2462.8734,-1198.9948 2494.3641,-1156.0498 2490.7395,-1094.0005 2482.3343,-1049.6791"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2484.7438,-1048.9818 2479.3189,-1039.8812 2477.8845,-1050.3784 2484.7438,-1048.9818"/>
+<text text-anchor="middle" x="1820.4407" y="-1379.7188" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=mpi</text>
+</g>
+<!-- 4bu62kyfuh4ikdkuyxfxjxanf7e7qopu&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge32" class="edge">
+<title>4bu62kyfuh4ikdkuyxfxjxanf7e7qopu-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M947.2173,-1428.5496C968.7089,-1408.5917 992.2747,-1383.3345 1008.2117,-1356.6861 1067.0588,-1259.8646 1008.3745,-1197.6371 1084.3226,-1110.9351 1110.3076,-1081.7965 1144.7149,-1059.7578 1180.1804,-1043.0531"/>
+<path fill="none" stroke="#daa520" stroke-width="2" d="M948.5783,-1430.0151C970.1712,-1409.9561 993.737,-1384.6989 1009.9275,-1357.7139 1068.5139,-1258.4924 1009.8295,-1196.2649 1085.8166,-1112.2649 1111.3864,-1083.4807 1145.7936,-1061.442 1181.0322,-1044.8626"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1182.4567,-1046.9607 1190.1008,-1039.6246 1179.5503,-1040.5926 1182.4567,-1046.9607"/>
+</g>
+<!-- 5xerf6imlgo4xlubacr4mljacc3edexo -->
+<g id="node17" class="node">
+<title>5xerf6imlgo4xlubacr4mljacc3edexo</title>
+<path fill="#add8e6" stroke="#000000" stroke-width="4" d="M1822.3657,-880.7002C1822.3657,-880.7002 1437.7735,-880.7002 1437.7735,-880.7002 1431.7735,-880.7002 1425.7735,-874.7002 1425.7735,-868.7002 1425.7735,-868.7002 1425.7735,-806.0998 1425.7735,-806.0998 1425.7735,-800.0998 1431.7735,-794.0998 1437.7735,-794.0998 1437.7735,-794.0998 1822.3657,-794.0998 1822.3657,-794.0998 1828.3657,-794.0998 1834.3657,-800.0998 1834.3657,-806.0998 1834.3657,-806.0998 1834.3657,-868.7002 1834.3657,-868.7002 1834.3657,-874.7002 1828.3657,-880.7002 1822.3657,-880.7002"/>
+<text text-anchor="middle" x="1630.0696" y="-830.2" font-family="Monaco" font-size="24.00" fill="#000000">openssl@1.1.1s%gcc@9.4.0/5xerf6i</text>
+</g>
+<!-- 5xerf6imlgo4xlubacr4mljacc3edexo&#45;&gt;ywrpvv2hgooeepdke33exkqrtdpd5gkl -->
+<g id="edge22" class="edge">
+<title>5xerf6imlgo4xlubacr4mljacc3edexo-&gt;ywrpvv2hgooeepdke33exkqrtdpd5gkl</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1425.7129,-803.7711C1262.7545,-776.9548 1035.5151,-739.5603 871.9084,-712.6373"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="872.1525,-709.1305 861.7169,-710.9602 871.0158,-716.0376 872.1525,-709.1305"/>
+</g>
+<!-- 5xerf6imlgo4xlubacr4mljacc3edexo&#45;&gt;4vsmjofkhntilgzh4zebluqak5mdsu3x -->
+<g id="edge48" class="edge">
+<title>5xerf6imlgo4xlubacr4mljacc3edexo-&gt;4vsmjofkhntilgzh4zebluqak5mdsu3x</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1644.2788,-794.0072C1650.5843,-774.7513 1658.0636,-751.9107 1664.6976,-731.6514"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1668.0917,-732.533 1667.8776,-721.9403 1661.4393,-730.3546 1668.0917,-732.533"/>
+</g>
+<!-- 5xerf6imlgo4xlubacr4mljacc3edexo&#45;&gt;nizxi5u5bbrzhzwfy2qb7hatlhuswlrz -->
+<g id="edge41" class="edge">
+<title>5xerf6imlgo4xlubacr4mljacc3edexo-&gt;nizxi5u5bbrzhzwfy2qb7hatlhuswlrz</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1834.3289,-793.5645C1906.6817,-774.1673 1975.9199,-749.2273 1998.2925,-721.3707 2031.5218,-680.681 2032.1636,-617.9031 2027.044,-573.3921"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1834.8468,-795.4962C1907.3595,-776.0489 1976.5977,-751.1089 1999.8467,-722.6293 2033.5217,-680.7015 2034.1635,-617.9235 2029.0309,-573.1639"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2031.4885,-572.6712 2026.7474,-563.1964 2024.5451,-573.5598 2031.4885,-572.6712"/>
+</g>
+<!-- v32wejd4d5lc6uka4qlrogwh5xae2h3r -->
+<g id="node26" class="node">
+<title>v32wejd4d5lc6uka4qlrogwh5xae2h3r</title>
+<path fill="#ff7f50" stroke="#000000" stroke-width="4" d="M1306.1776,-404.3002C1306.1776,-404.3002 929.9616,-404.3002 929.9616,-404.3002 923.9616,-404.3002 917.9616,-398.3002 917.9616,-392.3002 917.9616,-392.3002 917.9616,-329.6998 917.9616,-329.6998 917.9616,-323.6998 923.9616,-317.6998 929.9616,-317.6998 929.9616,-317.6998 1306.1776,-317.6998 1306.1776,-317.6998 1312.1776,-317.6998 1318.1776,-323.6998 1318.1776,-329.6998 1318.1776,-329.6998 1318.1776,-392.3002 1318.1776,-392.3002 1318.1776,-398.3002 1312.1776,-404.3002 1306.1776,-404.3002"/>
+<text text-anchor="middle" x="1118.0696" y="-353.8" font-family="Monaco" font-size="24.00" fill="#000000">readline@8.2%gcc@9.4.0/v32wejd</text>
+</g>
+<!-- uabgssx6lsgrevwbttslldnr5nzguprj&#45;&gt;v32wejd4d5lc6uka4qlrogwh5xae2h3r -->
+<g id="edge7" class="edge">
+<title>uabgssx6lsgrevwbttslldnr5nzguprj-&gt;v32wejd4d5lc6uka4qlrogwh5xae2h3r</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1117.0696,-476.4072C1117.0696,-457.3263 1117.0696,-434.7257 1117.0696,-414.6046"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1119.0696,-476.4072C1119.0696,-457.3263 1119.0696,-434.7257 1119.0696,-414.6046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1121.5697,-414.3403 1118.0696,-404.3403 1114.5697,-414.3404 1121.5697,-414.3403"/>
+</g>
+<!-- lfh3aovn65e66cs24qiehq3nd2ddojef&#45;&gt;o524gebsxavobkte3k5fglgwnedfkadf -->
+<g id="edge14" class="edge">
+<title>lfh3aovn65e66cs24qiehq3nd2ddojef-&gt;o524gebsxavobkte3k5fglgwnedfkadf</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1167.6711,-1112.5788C1078.9073,-1090.9596 971.5916,-1064.822 881.5513,-1042.892"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1168.1444,-1110.6356C1079.3806,-1089.0165 972.0649,-1062.8788 882.0246,-1040.9488"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="882.5603,-1038.5062 872.016,-1039.5403 880.9038,-1045.3074 882.5603,-1038.5062"/>
+<text text-anchor="middle" x="963.904" y="-1079.817" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=blas,lapack</text>
+</g>
+<!-- lfh3aovn65e66cs24qiehq3nd2ddojef&#45;&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw -->
+<g id="edge31" class="edge">
+<title>lfh3aovn65e66cs24qiehq3nd2ddojef-&gt;2w3nq3n3hcj2tqlvcpewsryamltlu5tw</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1559.7922,-1112.1043C1562.8511,-1111.5975 1565.8904,-1111.1002 1568.9103,-1110.6128 1759.2182,-1079.8992 1973.2397,-1052.1328 2144.6143,-1031.5343"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1560.1191,-1114.0774C1563.1741,-1113.5712 1566.2134,-1113.0739 1569.2289,-1112.5872 1759.4755,-1081.8826 1973.497,-1054.1161 2144.8529,-1033.52"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2145.1529,-1036.002 2154.6648,-1031.3357 2144.3191,-1029.0518 2145.1529,-1036.002"/>
+<text text-anchor="middle" x="1828.178" y="-1072.4692" font-family="Times,serif" font-size="14.00" fill="#000000">virtuals=mpi</text>
+</g>
+<!-- lfh3aovn65e66cs24qiehq3nd2ddojef&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge21" class="edge">
+<title>lfh3aovn65e66cs24qiehq3nd2ddojef-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1346.0696,-1111.6072C1346.0696,-1092.5263 1346.0696,-1069.9257 1346.0696,-1049.8046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1349.5697,-1049.5403 1346.0696,-1039.5403 1342.5697,-1049.5404 1349.5697,-1049.5403"/>
+</g>
+<!-- 2w3nq3n3hcj2tqlvcpewsryamltlu5tw&#45;&gt;htzjns66gmq6pjofohp26djmjnpbegho -->
+<g id="edge30" class="edge">
+<title>2w3nq3n3hcj2tqlvcpewsryamltlu5tw-&gt;htzjns66gmq6pjofohp26djmjnpbegho</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M2467.0696,-952.8072C2467.0696,-933.7263 2467.0696,-911.1257 2467.0696,-891.0046"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="2470.5697,-890.7403 2467.0696,-880.7403 2463.5697,-890.7404 2470.5697,-890.7403"/>
+</g>
+<!-- 7rzbmgoxhmm2jhellkgcjmn62uklf22x&#45;&gt;gguve5icmo5e4cw5o3hvvfsxremc46if -->
+<g id="edge2" class="edge">
+<title>7rzbmgoxhmm2jhellkgcjmn62uklf22x-&gt;gguve5icmo5e4cw5o3hvvfsxremc46if</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1422.351,-1429.2133C1312.2528,-1388.8872 1171.1589,-1316.8265 1103.0696,-1198.4 1083.8409,-1164.956 1082.4563,-1144.2088 1103.0696,-1111.6 1121.4102,-1082.5864 1149.2483,-1060.7204 1179.6189,-1044.2895"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1181.4205,-1047.2977 1188.6801,-1039.5809 1178.1927,-1041.0863 1181.4205,-1047.2977"/>
+</g>
+<!-- v32wejd4d5lc6uka4qlrogwh5xae2h3r&#45;&gt;j5rupoqliu7kasm6xndl7ui32wgawkru -->
+<g id="edge39" class="edge">
+<title>v32wejd4d5lc6uka4qlrogwh5xae2h3r-&gt;j5rupoqliu7kasm6xndl7ui32wgawkru</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1179.8001,-316.7866C1209.2065,-296.3053 1244.4355,-271.7686 1274.8343,-250.5961"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1180.9431,-318.4278C1210.3495,-297.9465 1245.5785,-273.4098 1275.9774,-252.2373"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1277.6375,-254.1277 1283.8429,-245.5403 1273.6367,-248.3836 1277.6375,-254.1277"/>
+</g>
+<!-- gguve5icmo5e4cw5o3hvvfsxremc46if&#45;&gt;j5rupoqliu7kasm6xndl7ui32wgawkru -->
+<g id="edge18" class="edge">
+<title>gguve5icmo5e4cw5o3hvvfsxremc46if-&gt;j5rupoqliu7kasm6xndl7ui32wgawkru</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1345.0696,-952.7909C1345.0696,-891.6316 1345.0696,-776.6094 1345.0696,-678.6 1345.0696,-678.6 1345.0696,-678.6 1345.0696,-519.8 1345.0696,-426.9591 1345.0696,-318.8523 1345.0696,-255.7237"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1347.0696,-952.7909C1347.0696,-891.6316 1347.0696,-776.6094 1347.0696,-678.6 1347.0696,-678.6 1347.0696,-678.6 1347.0696,-519.8 1347.0696,-426.9591 1347.0696,-318.8523 1347.0696,-255.7237"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1349.5697,-255.6091 1346.0696,-245.6091 1342.5697,-255.6092 1349.5697,-255.6091"/>
+</g>
+<!-- gguve5icmo5e4cw5o3hvvfsxremc46if&#45;&gt;5xerf6imlgo4xlubacr4mljacc3edexo -->
+<g id="edge40" class="edge">
+<title>gguve5icmo5e4cw5o3hvvfsxremc46if-&gt;5xerf6imlgo4xlubacr4mljacc3edexo</title>
+<path fill="none" stroke="#1e90ff" stroke-width="2" d="M1423.1858,-951.9344C1460.2844,-931.1905 1504.8229,-906.2866 1543.0151,-884.9312"/>
+<path fill="none" stroke="#dc143c" stroke-width="2" d="M1424.1619,-953.68C1461.2605,-932.9361 1505.799,-908.0322 1543.9912,-886.6769"/>
+<polygon fill="#1e90ff" stroke="#1e90ff" stroke-width="2" points="1545.5391,-888.6757 1552.5592,-880.7403 1542.1228,-882.5659 1545.5391,-888.6757"/>
+</g>
+</g>
+</svg> \ No newline at end of file
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index 89afac75fe..839f3b7c6f 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -2974,6 +2974,33 @@ The ``provides("mpi")`` call tells Spack that the ``mpich`` package
can be used to satisfy the dependency of any package that
``depends_on("mpi")``.
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Providing multiple virtuals simultaneously
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Packages can provide more than one virtual dependency. Sometimes, due to implementation details,
+there are subsets of those virtuals that need to be provided together by the same package.
+
+A well-known example is ``openblas``, which provides both the ``lapack`` and ``blas`` API in a single ``libopenblas``
+library. A package that needs ``lapack`` and ``blas`` must either use ``openblas`` to provide both, or not use
+``openblas`` at all. It cannot pick one or the other.
+
+To express this constraint in a package, the two virtual dependencies must be listed in the same ``provides`` directive:
+
+.. code-block:: python
+
+ provides('blas', 'lapack')
+
+This makes it impossible to select ``openblas`` as a provider for one of the two
+virtual dependencies and not for the other. If you try to, Spack will report an error:
+
+.. code-block:: console
+
+ $ spack spec netlib-scalapack ^[virtuals=lapack] openblas ^[virtuals=blas] atlas
+ ==> Error: concretization failed for the following reasons:
+
+ 1. Package 'openblas' needs to provide both 'lapack' and 'blas' together, but provides only 'lapack'
+
^^^^^^^^^^^^^^^^^^^^
Versioned Interfaces
^^^^^^^^^^^^^^^^^^^^
diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
index 7ebf68e548..bfd57fc6f9 100644
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -573,17 +573,21 @@ def extends(spec, type=("build", "run"), **kwargs):
return _execute_extends
-@directive("provided")
-def provides(*specs, **kwargs):
- """Allows packages to provide a virtual dependency. If a package provides
- 'mpi', other packages can declare that they depend on "mpi", and spack
- can use the providing package to satisfy the dependency.
+@directive(dicts=("provided", "provided_together"))
+def provides(*specs, when: Optional[str] = None):
+ """Allows packages to provide a virtual dependency.
+
+ If a package provides "mpi", other packages can declare that they depend on "mpi",
+ and spack can use the providing package to satisfy the dependency.
+
+ Args:
+ *specs: virtual specs provided by this package
+ when: condition when this provides clause needs to be considered
"""
def _execute_provides(pkg):
import spack.parser # Avoid circular dependency
- when = kwargs.get("when")
when_spec = make_when_spec(when)
if not when_spec:
return
@@ -591,15 +595,18 @@ def provides(*specs, **kwargs):
# ``when`` specs for ``provides()`` need a name, as they are used
# to build the ProviderIndex.
when_spec.name = pkg.name
-
- for string in specs:
- for provided_spec in spack.parser.parse(string):
- if pkg.name == provided_spec.name:
- raise CircularReferenceError("Package '%s' cannot provide itself." % pkg.name)
-
- if provided_spec not in pkg.provided:
- pkg.provided[provided_spec] = set()
- pkg.provided[provided_spec].add(when_spec)
+ spec_objs = [spack.spec.Spec(x) for x in specs]
+ spec_names = [x.name for x in spec_objs]
+ if len(spec_names) > 1:
+ pkg.provided_together.setdefault(when_spec, []).append(set(spec_names))
+
+ for provided_spec in spec_objs:
+ if pkg.name == provided_spec.name:
+ raise CircularReferenceError("Package '%s' cannot provide itself." % pkg.name)
+
+ if provided_spec not in pkg.provided:
+ pkg.provided[provided_spec] = set()
+ pkg.provided[provided_spec].add(when_spec)
return _execute_provides
diff --git a/lib/spack/spack/graph.py b/lib/spack/spack/graph.py
index 76ebbf636e..78bf38ec0e 100644
--- a/lib/spack/spack/graph.py
+++ b/lib/spack/spack/graph.py
@@ -528,10 +528,15 @@ class DAGWithDependencyTypes(DotGraphBuilder):
def edge_entry(self, edge):
colormap = {"build": "dodgerblue", "link": "crimson", "run": "goldenrod"}
+ label = ""
+ if edge.virtuals:
+ label = f" xlabel=\"virtuals={','.join(edge.virtuals)}\""
return (
edge.parent.dag_hash(),
edge.spec.dag_hash(),
- f"[color=\"{':'.join(colormap[x] for x in dt.flag_to_tuple(edge.depflag))}\"]",
+ f"[color=\"{':'.join(colormap[x] for x in dt.flag_to_tuple(edge.depflag))}\""
+ + label
+ + "]",
)
diff --git a/lib/spack/spack/parser.py b/lib/spack/spack/parser.py
index b73a189797..c69918b419 100644
--- a/lib/spack/spack/parser.py
+++ b/lib/spack/spack/parser.py
@@ -6,7 +6,7 @@
Here is the EBNF grammar for a spec::
- spec = [name] [node_options] { ^ node } |
+ spec = [name] [node_options] { ^[edge_properties] node } |
[name] [node_options] hash |
filename
@@ -14,7 +14,8 @@ Here is the EBNF grammar for a spec::
[name] [node_options] hash |
filename
- node_options = [@(version_list|version_pair)] [%compiler] { variant }
+ node_options = [@(version_list|version_pair)] [%compiler] { variant }
+ edge_properties = [ { bool_variant | key_value } ]
hash = / id
filename = (.|/|[a-zA-Z0-9-_]*/)([a-zA-Z0-9-_./]*)(.json|.yaml)
@@ -64,6 +65,7 @@ from typing import Iterator, List, Match, Optional
from llnl.util.tty import color
+import spack.deptypes
import spack.error
import spack.spec
import spack.version
@@ -126,6 +128,8 @@ class TokenType(TokenBase):
"""
# Dependency
+ START_EDGE_PROPERTIES = r"(?:\^\[)"
+ END_EDGE_PROPERTIES = r"(?:\])"
DEPENDENCY = r"(?:\^)"
# Version
VERSION_HASH_PAIR = rf"(?:@(?:{GIT_VERSION_PATTERN})=(?:{VERSION}))"
@@ -280,16 +284,15 @@ class SpecParser:
initial_spec = initial_spec or spack.spec.Spec()
root_spec = SpecNodeParser(self.ctx).parse(initial_spec)
while True:
- if self.ctx.accept(TokenType.DEPENDENCY):
- dependency = SpecNodeParser(self.ctx).parse()
-
- if dependency is None:
- msg = (
- "this dependency sigil needs to be followed by a package name "
- "or a node attribute (version, variant, etc.)"
- )
- raise SpecParsingError(msg, self.ctx.current_token, self.literal_str)
-
+ if self.ctx.accept(TokenType.START_EDGE_PROPERTIES):
+ edge_properties = EdgeAttributeParser(self.ctx, self.literal_str).parse()
+ edge_properties.setdefault("depflag", 0)
+ edge_properties.setdefault("virtuals", ())
+ dependency = self._parse_node(root_spec)
+ root_spec._add_dependency(dependency, **edge_properties)
+
+ elif self.ctx.accept(TokenType.DEPENDENCY):
+ dependency = self._parse_node(root_spec)
root_spec._add_dependency(dependency, depflag=0, virtuals=())
else:
@@ -297,6 +300,18 @@ class SpecParser:
return root_spec
+ def _parse_node(self, root_spec):
+ dependency = SpecNodeParser(self.ctx).parse()
+ if dependency is None:
+ msg = (
+ "the dependency sigil and any optional edge attributes must be followed by a "
+ "package name or a node attribute (version, variant, etc.)"
+ )
+ raise SpecParsingError(msg, self.ctx.current_token, self.literal_str)
+ if root_spec.concrete:
+ raise spack.spec.RedundantSpecError(root_spec, "^" + str(dependency))
+ return dependency
+
def all_specs(self) -> List["spack.spec.Spec"]:
"""Return all the specs that remain to be parsed"""
return list(iter(self.next_spec, None))
@@ -438,6 +453,41 @@ class FileParser:
return initial_spec
+class EdgeAttributeParser:
+ __slots__ = "ctx", "literal_str"
+
+ def __init__(self, ctx, literal_str):
+ self.ctx = ctx
+ self.literal_str = literal_str
+
+ def parse(self):
+ attributes = {}
+ while True:
+ if self.ctx.accept(TokenType.KEY_VALUE_PAIR):
+ name, value = self.ctx.current_token.value.split("=", maxsplit=1)
+ name = name.strip("'\" ")
+ value = value.strip("'\" ").split(",")
+ attributes[name] = value
+ if name not in ("deptypes", "virtuals"):
+ msg = (
+ "the only edge attributes that are currently accepted "
+ 'are "deptypes" and "virtuals"'
+ )
+ raise SpecParsingError(msg, self.ctx.current_token, self.literal_str)
+ # TODO: Add code to accept bool variants here as soon as use variants are implemented
+ elif self.ctx.accept(TokenType.END_EDGE_PROPERTIES):
+ break
+ else:
+ msg = "unexpected token in edge attributes"
+ raise SpecParsingError(msg, self.ctx.next_token, self.literal_str)
+
+ # Turn deptypes=... to depflag representation
+ if "deptypes" in attributes:
+ deptype_string = attributes.pop("deptypes")
+ attributes["depflag"] = spack.deptypes.canonicalize(deptype_string)
+ return attributes
+
+
def parse(text: str) -> List["spack.spec.Spec"]:
"""Parse text into a list of strings
diff --git a/lib/spack/spack/provider_index.py b/lib/spack/spack/provider_index.py
index 2624de56ac..32ace00a16 100644
--- a/lib/spack/spack/provider_index.py
+++ b/lib/spack/spack/provider_index.py
@@ -3,7 +3,6 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""Classes and functions to manage providers of virtual dependencies"""
-import itertools
from typing import Dict, List, Optional, Set
import spack.error
@@ -11,33 +10,6 @@ import spack.spec
import spack.util.spack_json as sjson
-def _cross_provider_maps(lmap, rmap):
- """Return a dictionary that combines constraint requests from both input.
-
- Args:
- lmap: main provider map
- rmap: provider map with additional constraints
- """
- # TODO: this is pretty darned nasty, and inefficient, but there
- # TODO: are not that many vdeps in most specs.
- result = {}
- for lspec, rspec in itertools.product(lmap, rmap):
- try:
- constrained = lspec.constrained(rspec)
- except spack.error.UnsatisfiableSpecError:
- continue
-
- # lp and rp are left and right provider specs.
- for lp_spec, rp_spec in itertools.product(lmap[lspec], rmap[rspec]):
- if lp_spec.name == rp_spec.name:
- try:
- const = lp_spec.constrained(rp_spec, deps=False)
- result.setdefault(constrained, set()).add(const)
- except spack.error.UnsatisfiableSpecError:
- continue
- return result
-
-
class _IndexBase:
#: This is a dict of dicts used for finding providers of particular
#: virtual dependencies. The dict of dicts looks like:
@@ -81,29 +53,6 @@ class _IndexBase:
def __contains__(self, name):
return name in self.providers
- def satisfies(self, other):
- """Determine if the providers of virtual specs are compatible.
-
- Args:
- other: another provider index
-
- Returns:
- True if the providers are compatible, False otherwise.
- """
- common = set(self.providers) & set(other.providers)
- if not common:
- return True
-
- # This ensures that some provider in other COULD satisfy the
- # vpkg constraints on self.
- result = {}
- for name in common:
- crossed = _cross_provider_maps(self.providers[name], other.providers[name])
- if crossed:
- result[name] = crossed
-
- return all(c in result for c in common)
-
def __eq__(self, other):
return self.providers == other.providers
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index 729a1febc4..63e32a7576 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -1501,6 +1501,17 @@ class SpackSolverSetup:
)
self.gen.newline()
+ for when, sets_of_virtuals in pkg.provided_together.items():
+ condition_id = self.condition(
+ when, name=pkg.name, msg="Virtuals are provided together"
+ )
+ for set_id, virtuals_together in enumerate(sets_of_virtuals):
+ for name in virtuals_together:
+ self.gen.fact(
+ fn.pkg_fact(pkg.name, fn.provided_together(condition_id, set_id, name))
+ )
+ self.gen.newline()
+
def package_dependencies_rules(self, pkg):
"""Translate 'depends_on' directives into ASP logic."""
for _, conditions in sorted(pkg.dependencies.items()):
@@ -1902,6 +1913,15 @@ class SpackSolverSetup:
clauses.append(fn.attr("package_hash", spec.name, spec._package_hash))
clauses.append(fn.attr("hash", spec.name, spec.dag_hash()))
+ edges = spec.edges_from_dependents()
+ virtuals = [x for x in itertools.chain.from_iterable([edge.virtuals for edge in edges])]
+ if not body:
+ for virtual in virtuals:
+ clauses.append(fn.attr("provider_set", spec.name, virtual))
+ else:
+ for virtual in virtuals:
+ clauses.append(fn.attr("virtual_on_incoming_edges", spec.name, virtual))
+
# add all clauses from dependencies
if transitive:
# TODO: Eventually distinguish 2 deps on the same pkg (build and link)
@@ -3124,10 +3144,11 @@ class InternalConcretizerError(spack.error.UnsatisfiableSpecError):
msg = (
"Spack concretizer internal error. Please submit a bug report and include the "
"command, environment if applicable and the following error message."
- f"\n {provided} is unsatisfiable, errors are:"
+ f"\n {provided} is unsatisfiable"
)
- msg += "".join([f"\n {conflict}" for conflict in conflicts])
+ if conflicts:
+ msg += ", errors are:" + "".join([f"\n {conflict}" for conflict in conflicts])
super(spack.error.UnsatisfiableSpecError, self).__init__(msg)
diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp
index 26c7907759..3b3a547eff 100644
--- a/lib/spack/spack/solver/concretize.lp
+++ b/lib/spack/spack/solver/concretize.lp
@@ -113,10 +113,11 @@ unification_set(SetID, VirtualNode)
multiple_nodes_attribute("node_flag_source").
multiple_nodes_attribute("depends_on").
multiple_nodes_attribute("virtual_on_edge").
+multiple_nodes_attribute("provider_set").
% Map constraint on the literal ID to facts on the node
attr(Name, node(min_dupe_id, A1)) :- literal(LiteralID, Name, A1), solve_literal(LiteralID).
-attr(Name, node(min_dupe_id, A1), A2) :- literal(LiteralID, Name, A1, A2), solve_literal(LiteralID).
+attr(Name, node(min_dupe_id, A1), A2) :- literal(LiteralID, Name, A1, A2), solve_literal(LiteralID), not multiple_nodes_attribute(Name).
attr(Name, node(min_dupe_id, A1), A2, A3) :- literal(LiteralID, Name, A1, A2, A3), solve_literal(LiteralID), not multiple_nodes_attribute(Name).
attr(Name, node(min_dupe_id, A1), A2, A3, A4) :- literal(LiteralID, Name, A1, A2, A3, A4), solve_literal(LiteralID).
@@ -124,6 +125,10 @@ attr(Name, node(min_dupe_id, A1), A2, A3, A4) :- literal(LiteralID, Name, A1, A2
attr("node_flag_source", node(min_dupe_id, A1), A2, node(min_dupe_id, A3)) :- literal(LiteralID, "node_flag_source", A1, A2, A3), solve_literal(LiteralID).
attr("depends_on", node(min_dupe_id, A1), node(min_dupe_id, A2), A3) :- literal(LiteralID, "depends_on", A1, A2, A3), solve_literal(LiteralID).
+attr("virtual_node", node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", _, Virtual), solve_literal(LiteralID).
+attr("provider_set", node(min_dupe_id, Provider), node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", Provider, Virtual), solve_literal(LiteralID).
+provider(node(min_dupe_id, Provider), node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", Provider, Virtual), solve_literal(LiteralID).
+
% Discriminate between "roots" that have been explicitly requested, and roots that are deduced from "virtual roots"
explicitly_requested_root(node(min_dupe_id, A1)) :- literal(LiteralID, "root", A1), solve_literal(LiteralID).
@@ -476,6 +481,19 @@ error(1, Msg)
% Virtual dependencies
%-----------------------------------------------------------------------------
+% If the provider is set from the command line, its weight is 0
+possible_provider_weight(ProviderNode, VirtualNode, 0, "Set on the command line")
+ :- attr("provider_set", ProviderNode, VirtualNode).
+
+% Enforces all virtuals to be provided, if multiple of them are provided together
+error(100, "Package '{0}' needs to provide both '{1}' and '{2}' together, but provides only '{1}'", Package, Virtual1, Virtual2)
+:- condition_holds(ID, node(X, Package)),
+ pkg_fact(Package, provided_together(ID, SetID, Virtual1)),
+ pkg_fact(Package, provided_together(ID, SetID, Virtual2)),
+ Virtual1 != Virtual2,
+ attr("virtual_on_incoming_edges", node(X, Package), Virtual1),
+ not attr("virtual_on_incoming_edges", node(X, Package), Virtual2).
+
% if a package depends on a virtual, it's not external and we have a
% provider for that virtual then it depends on the provider
node_depends_on_virtual(PackageNode, Virtual, Type)
@@ -494,6 +512,9 @@ attr("virtual_on_edge", PackageNode, ProviderNode, Virtual)
provider(ProviderNode, node(_, Virtual)),
not external(PackageNode).
+attr("virtual_on_incoming_edges", ProviderNode, Virtual)
+ :- attr("virtual_on_edge", _, ProviderNode, Virtual).
+
% dependencies on virtuals also imply that the virtual is a virtual node
1 { attr("virtual_node", node(0..X-1, Virtual)) : max_dupes(Virtual, X) }
:- node_depends_on_virtual(PackageNode, Virtual).
@@ -501,6 +522,10 @@ attr("virtual_on_edge", PackageNode, ProviderNode, Virtual)
% If there's a virtual node, we must select one and only one provider.
% The provider must be selected among the possible providers.
+error(100, "'{0}' cannot be a provider for the '{1}' virtual", Package, Virtual)
+ :- attr("provider_set", node(min_dupe_id, Package), node(min_dupe_id, Virtual)),
+ not virtual_condition_holds( node(min_dupe_id, Package), Virtual).
+
error(100, "Cannot find valid provider for virtual {0}", Virtual)
:- attr("virtual_node", node(X, Virtual)),
not provider(_, node(X, Virtual)).
@@ -521,20 +546,6 @@ attr("root", PackageNode) :- attr("virtual_root", VirtualNode), provider(Package
attr("node", PackageNode), virtual_condition_holds(PackageNode, Virtual) } 1
:- attr("virtual_node", node(X, Virtual)).
-% If a spec is selected as a provider, it is for all the virtual it could provide
-:- provider(PackageNode, node(X, Virtual1)),
- virtual_condition_holds(PackageNode, Virtual2),
- Virtual2 != Virtual1,
- unification_set(SetID, PackageNode),
- unification_set(SetID, node(X, Virtual2)),
- not provider(PackageNode, node(X, Virtual2)).
-
-% If a spec is a dependency, and could provide a needed virtual, it must be a provider
-:- node_depends_on_virtual(PackageNode, Virtual),
- depends_on(PackageNode, PossibleProviderNode),
- virtual_condition_holds(PossibleProviderNode, Virtual),
- not attr("virtual_on_edge", PackageNode, PossibleProviderNode, Virtual).
-
% The provider provides the virtual if some provider condition holds.
virtual_condition_holds(node(ProviderID, Provider), Virtual) :- virtual_condition_holds(ID, node(ProviderID, Provider), Virtual).
virtual_condition_holds(ID, node(ProviderID, Provider), Virtual) :-
@@ -561,6 +572,8 @@ do_not_impose(EffectID, node(X, Package))
not virtual_condition_holds(PackageNode, Virtual),
internal_error("Virtual when provides not respected").
+#defined provided_together/4.
+
%-----------------------------------------------------------------------------
% Virtual dependency weights
%-----------------------------------------------------------------------------
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 6030ff2681..20e5c3ffa3 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -59,7 +59,7 @@ import platform
import re
import socket
import warnings
-from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
import llnl.path
import llnl.string
@@ -1464,6 +1464,26 @@ class Spec:
"""
return [d for d in self._dependencies.select(child=name, depflag=depflag)]
+ @property
+ def edge_attributes(self) -> str:
+ """Helper method to print edge attributes in spec literals"""
+ edges = self.edges_from_dependents()
+ if not edges:
+ return ""
+
+ union = DependencySpec(parent=Spec(), spec=self, depflag=0, virtuals=())
+ for edge in edges:
+ union.update_deptypes(edge.depflag)
+ union.update_virtuals(edge.virtuals)
+ deptypes_str = (
+ f"deptypes={','.join(dt.flag_to_tuple(union.depflag))}" if union.depflag else ""
+ )
+ virtuals_str = f"virtuals={','.join(union.virtuals)}" if union.virtuals else ""
+ if not deptypes_str and not virtuals_str:
+ return ""
+ result = f"{deptypes_str} {virtuals_str}".strip()
+ return f"[{result}]"
+
def dependencies(self, name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL):
"""Return a list of direct dependencies (nodes in the DAG).
@@ -3688,8 +3708,15 @@ class Spec:
if other.concrete and self.concrete:
return self.dag_hash() == other.dag_hash()
- self_hash = self.dag_hash() if self.concrete else self.abstract_hash
- other_hash = other.dag_hash() if other.concrete else other.abstract_hash
+ elif self.concrete:
+ return self.satisfies(other)
+
+ elif other.concrete:
+ return other.satisfies(self)
+
+ # From here we know both self and other are not concrete
+ self_hash = self.abstract_hash
+ other_hash = other.abstract_hash
if (
self_hash
@@ -3778,10 +3805,6 @@ class Spec:
repository=spack.repo.PATH, specs=other.traverse(), restrict=True
)
- # This handles cases where there are already providers for both vpkgs
- if not self_index.satisfies(other_index):
- return False
-
# These two loops handle cases where there is an overly restrictive
# vpkg in one spec for a provider in the other (e.g., mpi@3: is not
# compatible with mpich2)
@@ -3879,7 +3902,46 @@ class Spec:
return False
# If we arrived here, then rhs is abstract. At the moment we don't care about the edge
- # structure of an abstract DAG - hence the deps=False parameter.
+ # structure of an abstract DAG, so we check if any edge could satisfy the properties
+ # we ask for.
+ lhs_edges: Dict[str, Set[DependencySpec]] = collections.defaultdict(set)
+ for rhs_edge in other.traverse_edges(root=False, cover="edges"):
+ # If we are checking for ^mpi we need to verify if there is any edge
+ if rhs_edge.spec.virtual:
+ rhs_edge.update_virtuals(virtuals=(rhs_edge.spec.name,))
+
+ if not rhs_edge.virtuals:
+ continue
+
+ if not lhs_edges:
+ # Construct a map of the link/run subDAG + direct "build" edges,
+ # keyed by dependency name
+ for lhs_edge in self.traverse_edges(
+ root=False, cover="edges", deptype=("link", "run")
+ ):
+ lhs_edges[lhs_edge.spec.name].add(lhs_edge)
+ for virtual_name in lhs_edge.virtuals:
+ lhs_edges[virtual_name].add(lhs_edge)
+
+ build_edges = self.edges_to_dependencies(depflag=dt.BUILD)
+ for lhs_edge in build_edges:
+ lhs_edges[lhs_edge.spec.name].add(lhs_edge)
+ for virtual_name in lhs_edge.virtuals:
+ lhs_edges[virtual_name].add(lhs_edge)
+
+ # We don't have edges to this dependency
+ current_dependency_name = rhs_edge.spec.name
+ if current_dependency_name not in lhs_edges:
+ return False
+
+ for virtual in rhs_edge.virtuals:
+ has_virtual = any(
+ virtual in edge.virtuals for edge in lhs_edges[current_dependency_name]
+ )
+ if not has_virtual:
+ return False
+
+ # Edges have been checked above already, hence deps=False
return all(
any(lhs.satisfies(rhs, deps=False) for lhs in self.traverse(root=False))
for rhs in other.traverse(root=False)
@@ -4081,9 +4143,7 @@ class Spec:
"""
query_parameters = name.split(":")
if len(query_parameters) > 2:
- msg = "key has more than one ':' symbol."
- msg += " At most one is admitted."
- raise KeyError(msg)
+ raise KeyError("key has more than one ':' symbol. At most one is admitted.")
name, query_parameters = query_parameters[0], query_parameters[1:]
if query_parameters:
@@ -4108,11 +4168,17 @@ class Spec:
itertools.chain(
# Regular specs
(x for x in order() if x.name == name),
+ (
+ x
+ for x in order()
+ if (not x.virtual)
+ and any(name in edge.virtuals for edge in x.edges_from_dependents())
+ ),
(x for x in order() if (not x.virtual) and x.package.provides(name)),
)
)
except StopIteration:
- raise KeyError("No spec with name %s in %s" % (name, self))
+ raise KeyError(f"No spec with name {name} in {self}")
if self._concrete:
return SpecBuildInterface(value, name, query_parameters)
@@ -4490,17 +4556,27 @@ class Spec:
return str(path_ctor(*output_path_components))
def __str__(self):
- sorted_nodes = [self] + sorted(
+ root_str = [self.format()]
+ sorted_dependencies = sorted(
self.traverse(root=False), key=lambda x: (x.name, x.abstract_hash)
)
- return " ^".join(d.format() for d in sorted_nodes).strip()
+ sorted_dependencies = [
+ d.format("{edge_attributes} " + DEFAULT_FORMAT) for d in sorted_dependencies
+ ]
+ spec_str = " ^".join(root_str + sorted_dependencies)
+ return spec_str.strip()
@property
def colored_str(self):
- sorted_nodes = [self] + sorted(
+ root_str = [self.cformat()]
+ sorted_dependencies = sorted(
self.traverse(root=False), key=lambda x: (x.name, x.abstract_hash)
)
- return " ^".join(d.cformat() for d in sorted_nodes).strip()
+ sorted_dependencies = [
+ d.cformat("{edge_attributes} " + DISPLAY_FORMAT) for d in sorted_dependencies
+ ]
+ spec_str = " ^".join(root_str + sorted_dependencies)
+ return spec_str.strip()
def install_status(self):
"""Helper for tree to print DB install status."""
diff --git a/lib/spack/spack/test/build_environment.py b/lib/spack/spack/test/build_environment.py
index 0893b76a98..f2bf740272 100644
--- a/lib/spack/spack/test/build_environment.py
+++ b/lib/spack/spack/test/build_environment.py
@@ -642,3 +642,13 @@ def test_effective_deptype_run_environment(default_mock_concretization):
for spec, effective_type in spack.build_environment.effective_deptypes(s, context=Context.RUN):
assert effective_type & expected_flags.pop(spec.name) == effective_type
assert not expected_flags, f"Missing {expected_flags.keys()} from effective_deptypes"
+
+
+def test_monkey_patching_works_across_virtual(default_mock_concretization):
+ """Assert that a monkeypatched attribute is found regardless we access through the
+ real name or the virtual name.
+ """
+ s = default_mock_concretization("mpileaks ^mpich")
+ s["mpich"].foo = "foo"
+ assert s["mpich"].foo == "foo"
+ assert s["mpi"].foo == "foo"
diff --git a/lib/spack/spack/test/cmd/dependencies.py b/lib/spack/spack/test/cmd/dependencies.py
index f61c19a7f1..bc615c7a3a 100644
--- a/lib/spack/spack/test/cmd/dependencies.py
+++ b/lib/spack/spack/test/cmd/dependencies.py
@@ -14,7 +14,14 @@ from spack.main import SpackCommand
dependencies = SpackCommand("dependencies")
-mpis = ["low-priority-provider", "mpich", "mpich2", "multi-provider-mpi", "zmpi"]
+mpis = [
+ "intel-parallel-studio",
+ "low-priority-provider",
+ "mpich",
+ "mpich2",
+ "multi-provider-mpi",
+ "zmpi",
+]
mpi_deps = ["fake"]
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index 24657c30f9..5d244f422c 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -2461,8 +2461,12 @@ def test_concretize_user_specs_together():
e.remove("mpich")
e.add("mpich2")
+ exc_cls = spack.error.SpackError
+ if spack.config.get("config:concretizer") == "clingo":
+ exc_cls = spack.error.UnsatisfiableSpecError
+
# Concretizing without invalidating the concrete spec for mpileaks fails
- with pytest.raises(spack.error.UnsatisfiableSpecError):
+ with pytest.raises(exc_cls):
e.concretize()
e.concretize(force=True)
@@ -2494,9 +2498,12 @@ def test_duplicate_packages_raise_when_concretizing_together():
e.add("mpileaks~opt")
e.add("mpich")
- with pytest.raises(
- spack.error.UnsatisfiableSpecError, match=r"You could consider setting `concretizer:unify`"
- ):
+ exc_cls, match = spack.error.SpackError, None
+ if spack.config.get("config:concretizer") == "clingo":
+ exc_cls = spack.error.UnsatisfiableSpecError
+ match = r"You could consider setting `concretizer:unify`"
+
+ with pytest.raises(exc_cls, match=match):
e.concretize()
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 04959a19b3..1dd530ac70 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -1838,7 +1838,8 @@ class TestConcretize:
# If we concretize with --reuse it is not, since "mpich~debug" was already installed
with spack.config.override("concretizer:reuse", True):
s = Spec("mpich").concretized()
- assert s.satisfies("~debug")
+ assert s.installed
+ assert s.satisfies("~debug"), s
@pytest.mark.regression("32471")
@pytest.mark.only_clingo("Use case not supported by the original concretizer")
@@ -2132,14 +2133,16 @@ class TestConcretize:
@pytest.fixture()
def duplicates_test_repository():
- builder_test_path = os.path.join(spack.paths.repos_path, "duplicates.test")
- with spack.repo.use_repositories(builder_test_path) as mock_repo:
+ repository_path = os.path.join(spack.paths.repos_path, "duplicates.test")
+ with spack.repo.use_repositories(repository_path) as mock_repo:
yield mock_repo
@pytest.mark.usefixtures("mutable_config", "duplicates_test_repository")
@pytest.mark.only_clingo("Not supported by the original concretizer")
class TestConcretizeSeparately:
+ """Collects test on separate concretization"""
+
@pytest.mark.parametrize("strategy", ["minimal", "full"])
def test_two_gmake(self, strategy):
"""Tests that we can concretize a spec with nodes using the same build
@@ -2320,3 +2323,40 @@ class TestConcreteSpecsByHash:
assert node == container[node.dag_hash()]
assert node.dag_hash() in container
assert node is not container[node.dag_hash()]
+
+
+@pytest.fixture()
+def edges_test_repository():
+ repository_path = os.path.join(spack.paths.repos_path, "edges.test")
+ with spack.repo.use_repositories(repository_path) as mock_repo:
+ yield mock_repo
+
+
+@pytest.mark.usefixtures("mutable_config", "edges_test_repository")
+@pytest.mark.only_clingo("Edge properties not supported by the original concretizer")
+class TestConcretizeEdges:
+ """Collects tests on edge properties"""
+
+ @pytest.mark.parametrize(
+ "spec_str,expected_satisfies,expected_not_satisfies",
+ [
+ ("conditional-edge", ["^zlib@2.0"], ["^zlib-api"]),
+ ("conditional-edge~foo", ["^zlib@2.0"], ["^zlib-api"]),
+ (
+ "conditional-edge+foo",
+ ["^zlib@1.0", "^zlib-api", "^[virtuals=zlib-api] zlib"],
+ ["^[virtuals=mpi] zlib"],
+ ),
+ ],
+ )
+ def test_condition_triggered_by_edge_property(
+ self, spec_str, expected_satisfies, expected_not_satisfies
+ ):
+ """Tests that we can enforce constraints based on edge attributes"""
+ s = Spec(spec_str).concretized()
+
+ for expected in expected_satisfies:
+ assert s.satisfies(expected), str(expected)
+
+ for not_expected in expected_not_satisfies:
+ assert not s.satisfies(not_expected), str(not_expected)
diff --git a/lib/spack/spack/test/package_class.py b/lib/spack/spack/test/package_class.py
index d0126af230..279693a529 100644
--- a/lib/spack/spack/test/package_class.py
+++ b/lib/spack/spack/test/package_class.py
@@ -37,6 +37,7 @@ def mpileaks_possible_deps(mock_packages, mpi_names):
"low-priority-provider": set(),
"dyninst": set(["libdwarf", "libelf"]),
"fake": set(),
+ "intel-parallel-studio": set(),
"libdwarf": set(["libelf"]),
"libelf": set(),
"mpich": set(),
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index be646b1e03..3a9c0350ae 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -532,6 +532,7 @@ class TestSpecDag:
assert not spec.eq_dag(expected_normalized, deptypes=True)
assert not spec.eq_dag(non_unique_nodes, deptypes=True)
+ @pytest.mark.xfail(reason="String representation changed")
def test_normalize_with_virtual_package(self):
spec = Spec("mpileaks ^mpi ^libelf@1.8.11 ^libdwarf")
spec.normalize()
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
index 579ba4486c..87ed1e4b3f 100644
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -294,13 +294,10 @@ class TestSpecSemantics:
("foo@4.0%pgi@4.5", "@1:3%pgi@4.4:4.6"),
("builtin.mock.mpich", "builtin.mpich"),
("mpileaks ^builtin.mock.mpich", "^builtin.mpich"),
- ("mpileaks^mpich", "^zmpi"),
- ("mpileaks^zmpi", "^mpich"),
("mpileaks^mpich@1.2", "^mpich@2.0"),
("mpileaks^mpich@4.0^callpath@1.5", "^mpich@1:3^callpath@1.4:1.6"),
("mpileaks^mpich@2.0^callpath@1.7", "^mpich@1:3^callpath@1.4:1.6"),
("mpileaks^mpich@4.0^callpath@1.7", "^mpich@1:3^callpath@1.4:1.6"),
- ("mpileaks^mpich", "^zmpi"),
("mpileaks^mpi@3", "^mpi@1.2:1.6"),
("mpileaks^mpi@3:", "^mpich2@1.4"),
("mpileaks^mpi@3:", "^mpich2"),
@@ -338,30 +335,30 @@ class TestSpecSemantics:
rhs.constrain(lhs)
@pytest.mark.parametrize(
- "lhs,rhs,intersection_expected",
+ "lhs,rhs",
[
- ("mpich", "mpich +foo", True),
- ("mpich", "mpich~foo", True),
- ("mpich", "mpich foo=1", True),
- ("mpich", "mpich++foo", True),
- ("mpich", "mpich~~foo", True),
- ("mpich", "mpich foo==1", True),
+ ("mpich", "mpich +foo"),
+ ("mpich", "mpich~foo"),
+ ("mpich", "mpich foo=1"),
+ ("mpich", "mpich++foo"),
+ ("mpich", "mpich~~foo"),
+ ("mpich", "mpich foo==1"),
# Flags semantics is currently different from other variant
- ("mpich", 'mpich cflags="-O3"', True),
- ("mpich cflags=-O3", 'mpich cflags="-O3 -Ofast"', False),
- ("mpich cflags=-O2", 'mpich cflags="-O3"', False),
- ("multivalue-variant foo=bar", "multivalue-variant +foo", False),
- ("multivalue-variant foo=bar", "multivalue-variant ~foo", False),
- ("multivalue-variant fee=bar", "multivalue-variant fee=baz", False),
+ ("mpich", 'mpich cflags="-O3"'),
+ ("mpich cflags=-O3", 'mpich cflags="-O3 -Ofast"'),
+ ("mpich cflags=-O2", 'mpich cflags="-O3"'),
+ ("multivalue-variant foo=bar", "multivalue-variant +foo"),
+ ("multivalue-variant foo=bar", "multivalue-variant ~foo"),
+ ("multivalue-variant fee=bar", "multivalue-variant fee=baz"),
],
)
def test_concrete_specs_which_do_not_satisfy_abstract(
- self, lhs, rhs, intersection_expected, default_mock_concretization
+ self, lhs, rhs, default_mock_concretization
):
lhs, rhs = default_mock_concretization(lhs), Spec(rhs)
- assert lhs.intersects(rhs) is intersection_expected
- assert rhs.intersects(lhs) is intersection_expected
+ assert lhs.intersects(rhs) is False
+ assert rhs.intersects(lhs) is False
assert not lhs.satisfies(rhs)
assert not rhs.satisfies(lhs)
@@ -483,10 +480,14 @@ class TestSpecSemantics:
assert Spec("mpich2").intersects(Spec("mpi"))
assert Spec("zmpi").intersects(Spec("mpi"))
- def test_intersects_virtual_dep_with_virtual_constraint(self):
+ def test_intersects_virtual_providers(self):
+ """Tests that we can always intersect virtual providers from abstract specs.
+ Concretization will give meaning to virtuals, and eventually forbid certain
+ configurations.
+ """
assert Spec("netlib-lapack ^openblas").intersects("netlib-lapack ^openblas")
- assert not Spec("netlib-lapack ^netlib-blas").intersects("netlib-lapack ^openblas")
- assert not Spec("netlib-lapack ^openblas").intersects("netlib-lapack ^netlib-blas")
+ assert Spec("netlib-lapack ^netlib-blas").intersects("netlib-lapack ^openblas")
+ assert Spec("netlib-lapack ^openblas").intersects("netlib-lapack ^netlib-blas")
assert Spec("netlib-lapack ^netlib-blas").intersects("netlib-lapack ^netlib-blas")
def test_intersectable_concrete_specs_must_have_the_same_hash(self):
@@ -1006,6 +1007,103 @@ class TestSpecSemantics:
assert new_spec.compiler_flags["cflags"] == ["-O2"]
assert new_spec.compiler_flags["cxxflags"] == ["-O1"]
+ @pytest.mark.parametrize(
+ "spec_str,specs_in_dag",
+ [
+ ("hdf5 ^[virtuals=mpi] mpich", [("mpich", "mpich"), ("mpi", "mpich")]),
+ # Try different combinations with packages that provides a
+ # disjoint set of virtual dependencies
+ (
+ "netlib-scalapack ^mpich ^openblas-with-lapack",
+ [
+ ("mpi", "mpich"),
+ ("lapack", "openblas-with-lapack"),
+ ("blas", "openblas-with-lapack"),
+ ],
+ ),
+ (
+ "netlib-scalapack ^[virtuals=mpi] mpich ^openblas-with-lapack",
+ [
+ ("mpi", "mpich"),
+ ("lapack", "openblas-with-lapack"),
+ ("blas", "openblas-with-lapack"),
+ ],
+ ),
+ (
+ "netlib-scalapack ^mpich ^[virtuals=lapack] openblas-with-lapack",
+ [
+ ("mpi", "mpich"),
+ ("lapack", "openblas-with-lapack"),
+ ("blas", "openblas-with-lapack"),
+ ],
+ ),
+ (
+ "netlib-scalapack ^[virtuals=mpi] mpich ^[virtuals=lapack] openblas-with-lapack",
+ [
+ ("mpi", "mpich"),
+ ("lapack", "openblas-with-lapack"),
+ ("blas", "openblas-with-lapack"),
+ ],
+ ),
+ # Test that we can mix dependencies that provide an overlapping
+ # sets of virtual dependencies
+ (
+ "netlib-scalapack ^[virtuals=mpi] intel-parallel-studio "
+ "^[virtuals=lapack] openblas-with-lapack",
+ [
+ ("mpi", "intel-parallel-studio"),
+ ("lapack", "openblas-with-lapack"),
+ ("blas", "openblas-with-lapack"),
+ ],
+ ),
+ (
+ "netlib-scalapack ^[virtuals=mpi] intel-parallel-studio ^openblas-with-lapack",
+ [
+ ("mpi", "intel-parallel-studio"),
+ ("lapack", "openblas-with-lapack"),
+ ("blas", "openblas-with-lapack"),
+ ],
+ ),
+ (
+ "netlib-scalapack ^intel-parallel-studio ^[virtuals=lapack] openblas-with-lapack",
+ [
+ ("mpi", "intel-parallel-studio"),
+ ("lapack", "openblas-with-lapack"),
+ ("blas", "openblas-with-lapack"),
+ ],
+ ),
+ # Test that we can bind more than one virtual to the same provider
+ (
+ "netlib-scalapack ^[virtuals=lapack,blas] openblas-with-lapack",
+ [("lapack", "openblas-with-lapack"), ("blas", "openblas-with-lapack")],
+ ),
+ ],
+ )
+ def test_virtual_deps_bindings(self, default_mock_concretization, spec_str, specs_in_dag):
+ if spack.config.get("config:concretizer") == "original":
+ pytest.skip("Use case not supported by the original concretizer")
+
+ s = default_mock_concretization(spec_str)
+ for label, expected in specs_in_dag:
+ assert label in s
+ assert s[label].satisfies(expected), label
+
+ @pytest.mark.parametrize(
+ "spec_str",
+ [
+ # openblas-with-lapack needs to provide blas and lapack together
+ "netlib-scalapack ^[virtuals=blas] intel-parallel-studio ^openblas-with-lapack",
+ # intel-* provides blas and lapack together, openblas can provide blas only
+ "netlib-scalapack ^[virtuals=lapack] intel-parallel-studio ^openblas",
+ ],
+ )
+ def test_unsatisfiable_virtual_deps_bindings(self, spec_str):
+ if spack.config.get("config:concretizer") == "original":
+ pytest.skip("Use case not supported by the original concretizer")
+
+ with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
+ Spec(spec_str).concretized()
+
@pytest.mark.parametrize(
"spec_str,format_str,expected",
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py
index 1d98731785..3cbb59e69f 100644
--- a/lib/spack/spack/test/spec_syntax.py
+++ b/lib/spack/spack/test/spec_syntax.py
@@ -531,6 +531,26 @@ def specfile_for(default_mock_concretization):
"@:0.4%nvhpc",
),
(
+ "^[virtuals=mpi] openmpi",
+ [
+ Token(TokenType.START_EDGE_PROPERTIES, value="^["),
+ Token(TokenType.KEY_VALUE_PAIR, value="virtuals=mpi"),
+ Token(TokenType.END_EDGE_PROPERTIES, value="]"),
+ Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
+ ],
+ "^[virtuals=mpi] openmpi",
+ ),
+ (
+ "^[deptypes=link,build] zlib",
+ [
+ Token(TokenType.START_EDGE_PROPERTIES, value="^["),
+ Token(TokenType.KEY_VALUE_PAIR, value="deptypes=link,build"),
+ Token(TokenType.END_EDGE_PROPERTIES, value="]"),
+ Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
+ ],
+ "^[deptypes=build,link] zlib",
+ ),
+ (
"zlib@git.foo/bar",
[
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, "zlib"),
@@ -923,6 +943,9 @@ def test_disambiguate_hash_by_spec(spec1, spec2, constraint, mock_packages, monk
("x platform=test platform=test", spack.spec.DuplicateArchitectureError),
("x os=fe platform=test target=fe os=fe", spack.spec.DuplicateArchitectureError),
("x target=be platform=test os=be os=fe", spack.spec.DuplicateArchitectureError),
+ ("^[@foo] zlib", spack.parser.SpecParsingError),
+ # TODO: Remove this as soon as use variants are added and we can parse custom attributes
+ ("^[foo=bar] zlib", spack.parser.SpecParsingError),
],
)
def test_error_conditions(text, exc_cls):
diff --git a/var/spack/repos/builtin.mock/packages/intel-parallel-studio/package.py b/var/spack/repos/builtin.mock/packages/intel-parallel-studio/package.py
new file mode 100644
index 0000000000..1ec5cf6932
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/intel-parallel-studio/package.py
@@ -0,0 +1,19 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+from spack.package import *
+
+
+class IntelParallelStudio(Package):
+ """Intel Parallel Studio."""
+
+ homepage = "https://software.intel.com/en-us/intel-parallel-studio-xe"
+ url = "http://tec/16225/parallel_studio_xe_2020_cluster_edition.tgz"
+
+ version("cluster.2020.0", sha256="b1d3e3e425b2e44a06760ff173104bdf")
+
+ provides("mpi@:3")
+ provides("scalapack")
+ provides("blas", "lapack")
diff --git a/var/spack/repos/builtin.mock/packages/low-priority-provider/package.py b/var/spack/repos/builtin.mock/packages/low-priority-provider/package.py
index 5b7bfc03c1..940dea3daf 100644
--- a/var/spack/repos/builtin.mock/packages/low-priority-provider/package.py
+++ b/var/spack/repos/builtin.mock/packages/low-priority-provider/package.py
@@ -14,5 +14,5 @@ class LowPriorityProvider(Package):
version("1.0", md5="0123456789abcdef0123456789abcdef")
- provides("lapack")
- provides("mpi")
+ # A low priority provider that provides both these specs together
+ provides("mpi", "lapack")
diff --git a/var/spack/repos/builtin.mock/packages/many-virtual-consumer/package.py b/var/spack/repos/builtin.mock/packages/many-virtual-consumer/package.py
index 070adf60bc..087cfb77cc 100644
--- a/var/spack/repos/builtin.mock/packages/many-virtual-consumer/package.py
+++ b/var/spack/repos/builtin.mock/packages/many-virtual-consumer/package.py
@@ -19,4 +19,4 @@ class ManyVirtualConsumer(Package):
# This directive is an example of imposing a constraint on a
# dependency is that dependency is in the DAG. This pattern
# is mainly used with virtual providers.
- depends_on("low-priority-provider@1.0", when="^low-priority-provider")
+ depends_on("low-priority-provider@1.0", when="^[virtuals=mpi,lapack] low-priority-provider")
diff --git a/var/spack/repos/builtin.mock/packages/netlib-scalapack/package.py b/var/spack/repos/builtin.mock/packages/netlib-scalapack/package.py
new file mode 100644
index 0000000000..fe5d7f90a1
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/netlib-scalapack/package.py
@@ -0,0 +1,20 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+
+from spack.package import *
+
+
+class NetlibScalapack(Package):
+ homepage = "http://www.netlib.org/scalapack/"
+ url = "http://www.netlib.org/scalapack/scalapack-2.1.0.tgz"
+
+ version("2.1.0", "b1d3e3e425b2e44a06760ff173104bdf")
+
+ provides("scalapack")
+
+ depends_on("mpi")
+ depends_on("lapack")
+ depends_on("blas")
diff --git a/var/spack/repos/builtin.mock/packages/openblas-with-lapack/package.py b/var/spack/repos/builtin.mock/packages/openblas-with-lapack/package.py
index 0156085877..1273b70def 100644
--- a/var/spack/repos/builtin.mock/packages/openblas-with-lapack/package.py
+++ b/var/spack/repos/builtin.mock/packages/openblas-with-lapack/package.py
@@ -14,5 +14,4 @@ class OpenblasWithLapack(Package):
version("0.2.15", md5="b1190f3d3471685f17cfd1ec1d252ac9")
- provides("lapack")
- provides("blas")
+ provides("lapack", "blas")
diff --git a/var/spack/repos/builtin/packages/lua-luajit-openresty/package.py b/var/spack/repos/builtin/packages/lua-luajit-openresty/package.py
index fbcc63cded..081e07fe6c 100644
--- a/var/spack/repos/builtin/packages/lua-luajit-openresty/package.py
+++ b/var/spack/repos/builtin/packages/lua-luajit-openresty/package.py
@@ -28,8 +28,7 @@ class LuaLuajitOpenresty(LuaImplPackage):
description="add symlinks to make lua-luajit a drop-in lua replacement",
)
- provides("lua-lang@5.1", when="+lualinks")
- provides("luajit")
+ provides("luajit", "lua-lang@5.1", when="+lualinks")
lua_version_override = "5.1"
@run_after("install")
diff --git a/var/spack/repos/builtin/packages/lua-luajit/package.py b/var/spack/repos/builtin/packages/lua-luajit/package.py
index e8a1c124e0..dfe9f51cd0 100644
--- a/var/spack/repos/builtin/packages/lua-luajit/package.py
+++ b/var/spack/repos/builtin/packages/lua-luajit/package.py
@@ -33,8 +33,7 @@ class LuaLuajit(LuaImplPackage):
description="add symlinks to make lua-luajit a drop-in lua replacement",
)
- provides("lua-lang@5.1", when="+lualinks")
- provides("luajit")
+ provides("luajit", "lua-lang@5.1", when="+lualinks")
lua_version_override = "5.1"
conflicts("platform=darwin", msg="luajit not supported on MacOS, see lua-luajit-openresty")
diff --git a/var/spack/repos/builtin/packages/openblas/package.py b/var/spack/repos/builtin/packages/openblas/package.py
index 43c3dafdad..409dfa004d 100644
--- a/var/spack/repos/builtin/packages/openblas/package.py
+++ b/var/spack/repos/builtin/packages/openblas/package.py
@@ -88,8 +88,7 @@ class Openblas(CMakePackage, MakefilePackage):
)
# virtual dependency
- provides("blas")
- provides("lapack")
+ provides("blas", "lapack")
provides("lapack@3.9.1:", when="@0.3.15:")
provides("lapack@3.7.0", when="@0.2.20")
diff --git a/var/spack/repos/edges.test/packages/conditional-edge/package.py b/var/spack/repos/edges.test/packages/conditional-edge/package.py
new file mode 100644
index 0000000000..964596fcc1
--- /dev/null
+++ b/var/spack/repos/edges.test/packages/conditional-edge/package.py
@@ -0,0 +1,24 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+from spack.package import *
+
+
+class ConditionalEdge(Package):
+ """This package has a variant that triggers a condition only if a required dependency is
+ providing a virtual.
+ """
+
+ homepage = "http://www.example.com"
+ url = "http://www.example.com/a-1.0.tar.gz"
+
+ version("2.0", md5="abcdef0123456789abcdef0123456789")
+ version("1.0", md5="0123456789abcdef0123456789abcdef")
+
+ variant("foo", default=False, description="Just a regular foo")
+
+ # zlib is a real package, providing zlib-api
+ depends_on("zlib")
+ depends_on("zlib-api", when="+foo")
+ depends_on("zlib@1.0", when="^[virtuals=zlib-api] zlib")
diff --git a/var/spack/repos/edges.test/packages/zlib/package.py b/var/spack/repos/edges.test/packages/zlib/package.py
new file mode 100644
index 0000000000..66dfc4f58b
--- /dev/null
+++ b/var/spack/repos/edges.test/packages/zlib/package.py
@@ -0,0 +1,19 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+from spack.package import *
+
+
+class Zlib(Package):
+ """This package has a variant that triggers a condition only if a required dependency is
+ providing a virtual.
+ """
+
+ homepage = "http://www.example.com"
+ url = "http://www.example.com/a-1.0.tar.gz"
+
+ version("2.0", md5="abcdef0123456789abcdef0123456789")
+ version("1.0", md5="0123456789abcdef0123456789abcdef")
+
+ provides("zlib-api")
diff --git a/var/spack/repos/edges.test/repo.yaml b/var/spack/repos/edges.test/repo.yaml
new file mode 100644
index 0000000000..86df79affe
--- /dev/null
+++ b/var/spack/repos/edges.test/repo.yaml
@@ -0,0 +1,2 @@
+repo:
+ namespace: edges.test