summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/basic_usage.rst70
-rw-r--r--lib/spack/docs/conf.py2
-rw-r--r--lib/spack/docs/packaging_guide.rst56
-rw-r--r--lib/spack/spack/binary_distribution.py19
-rw-r--r--lib/spack/spack/build_systems/intel.py10
-rw-r--r--lib/spack/spack/build_systems/python.py2
-rw-r--r--lib/spack/spack/cmd/__init__.py2
-rw-r--r--lib/spack/spack/cmd/checksum.py13
-rw-r--r--lib/spack/spack/cmd/dev_build.py2
-rw-r--r--lib/spack/spack/cmd/develop.py11
-rw-r--r--lib/spack/spack/cmd/solve.py2
-rw-r--r--lib/spack/spack/cmd/spec.py2
-rw-r--r--lib/spack/spack/compilers/__init__.py12
-rw-r--r--lib/spack/spack/compilers/aocc.py2
-rw-r--r--lib/spack/spack/compilers/apple_clang.py26
-rw-r--r--lib/spack/spack/compilers/cce.py10
-rw-r--r--lib/spack/spack/compilers/clang.py20
-rw-r--r--lib/spack/spack/compilers/gcc.py24
-rw-r--r--lib/spack/spack/compilers/intel.py16
-rw-r--r--lib/spack/spack/compilers/pgi.py6
-rw-r--r--lib/spack/spack/compilers/xl.py14
-rw-r--r--lib/spack/spack/concretize.py13
-rw-r--r--lib/spack/spack/cray_manifest.py8
-rw-r--r--lib/spack/spack/database.py27
-rw-r--r--lib/spack/spack/directives.py132
-rw-r--r--lib/spack/spack/environment/environment.py25
-rw-r--r--lib/spack/spack/fetch_strategy.py28
-rw-r--r--lib/spack/spack/installer.py18
-rw-r--r--lib/spack/spack/package_base.py10
-rw-r--r--lib/spack/spack/package_prefs.py4
-rw-r--r--lib/spack/spack/parser.py19
-rw-r--r--lib/spack/spack/schema/packages.py16
-rw-r--r--lib/spack/spack/solver/asp.py142
-rw-r--r--lib/spack/spack/spec.py97
-rw-r--r--lib/spack/spack/spec_list.py6
-rw-r--r--lib/spack/spack/test/architecture.py8
-rw-r--r--lib/spack/spack/test/cmd/ci.py14
-rw-r--r--lib/spack/spack/test/cmd/config.py6
-rw-r--r--lib/spack/spack/test/cmd/dev_build.py4
-rw-r--r--lib/spack/spack/test/cmd/develop.py20
-rw-r--r--lib/spack/spack/test/cmd/env.py28
-rw-r--r--lib/spack/spack/test/cmd/fetch.py2
-rw-r--r--lib/spack/spack/test/cmd/install.py21
-rw-r--r--lib/spack/spack/test/cmd/spec.py24
-rw-r--r--lib/spack/spack/test/cmd/stage.py4
-rw-r--r--lib/spack/spack/test/compilers/basics.py217
-rw-r--r--lib/spack/spack/test/concretize.py124
-rw-r--r--lib/spack/spack/test/concretize_preferences.py9
-rw-r--r--lib/spack/spack/test/concretize_requirements.py159
-rw-r--r--lib/spack/spack/test/conftest.py8
-rw-r--r--lib/spack/spack/test/cray_manifest.py9
-rw-r--r--lib/spack/spack/test/cvs_fetch.py4
-rw-r--r--lib/spack/spack/test/directives.py20
-rw-r--r--lib/spack/spack/test/git_fetch.py24
-rw-r--r--lib/spack/spack/test/hg_fetch.py4
-rw-r--r--lib/spack/spack/test/installer.py9
-rw-r--r--lib/spack/spack/test/mirror.py6
-rw-r--r--lib/spack/spack/test/modules/lmod.py6
-rw-r--r--lib/spack/spack/test/multimethod.py10
-rw-r--r--lib/spack/spack/test/patch.py2
-rw-r--r--lib/spack/spack/test/spec_dag.py18
-rw-r--r--lib/spack/spack/test/spec_semantics.py6
-rw-r--r--lib/spack/spack/test/spec_syntax.py8
-rw-r--r--lib/spack/spack/test/svn_fetch.py4
-rw-r--r--lib/spack/spack/test/url_fetch.py22
-rw-r--r--lib/spack/spack/test/util/package_hash.py43
-rw-r--r--lib/spack/spack/test/versions.py458
-rw-r--r--lib/spack/spack/test/web.py34
-rw-r--r--lib/spack/spack/version.py1612
69 files changed, 2001 insertions, 1812 deletions
diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst
index cf1a9ec673..af6d2dab91 100644
--- a/lib/spack/docs/basic_usage.rst
+++ b/lib/spack/docs/basic_usage.rst
@@ -1103,16 +1103,31 @@ Below are more details about the specifiers that you can add to specs.
Version specifier
^^^^^^^^^^^^^^^^^
-A version specifier comes somewhere after a package name and starts
-with ``@``. It can be a single version, e.g. ``@1.0``, ``@3``, or
-``@1.2a7``. Or, it can be a range of versions, such as ``@1.0:1.5``
-(all versions between ``1.0`` and ``1.5``, inclusive). Version ranges
-can be open, e.g. ``:3`` means any version up to and including ``3``.
-This would include ``3.4`` and ``3.4.2``. ``4.2:`` means any version
-above and including ``4.2``. Finally, a version specifier can be a
-set of arbitrary versions, such as ``@1.0,1.5,1.7`` (``1.0``, ``1.5``,
-or ``1.7``). When you supply such a specifier to ``spack install``,
-it constrains the set of versions that Spack will install.
+A version specifier ``pkg@<specifier>`` comes after a package name
+and starts with ``@``. It can be something abstract that matches
+multiple known versions, or a specific version. During concretization,
+Spack will pick the optimal version within the spec's constraints
+according to policies set for the particular Spack installation.
+
+The version specifier can be *a specific version*, such as ``@=1.0.0`` or
+``@=1.2a7``. Or, it can be *a range of versions*, such as ``@1.0:1.5``.
+Version ranges are inclusive, so this example includes both ``1.0``
+and any ``1.5.x`` version. Version ranges can be unbounded, e.g. ``@:3``
+means any version up to and including ``3``. This would include ``3.4``
+and ``3.4.2``. Similarly, ``@4.2:`` means any version above and including
+``4.2``. As a short-hand, ``@3`` is equivalent to the range ``@3:3`` and
+includes any version with major version ``3``.
+
+Notice that you can distinguish between the specific version ``@=3.2`` and
+the range ``@3.2``. This is useful for packages that follow a versioning
+scheme that omits the zero patch version number: ``3.2``, ``3.2.1``,
+``3.2.2``, etc. In general it is preferable to use the range syntax
+``@3.2``, since ranges also match versions with one-off suffixes, such as
+``3.2-custom``.
+
+A version specifier can also be a list of ranges and specific versions,
+separated by commas. For example, ``@1.0:1.5,=1.7.1`` matches any version
+in the range ``1.0:1.5`` and the specific version ``1.7.1``.
For packages with a ``git`` attribute, ``git`` references
may be specified instead of a numerical version i.e. branches, tags
@@ -1121,36 +1136,35 @@ reference provided. Acceptable syntaxes for this are:
.. code-block:: sh
+ # commit hashes
+ foo@abcdef1234abcdef1234abcdef1234abcdef1234 # 40 character hashes are automatically treated as git commits
+ foo@git.abcdef1234abcdef1234abcdef1234abcdef1234
+
# branches and tags
foo@git.develop # use the develop branch
foo@git.0.19 # use the 0.19 tag
- # commit hashes
- foo@abcdef1234abcdef1234abcdef1234abcdef1234 # 40 character hashes are automatically treated as git commits
- foo@git.abcdef1234abcdef1234abcdef1234abcdef1234
+Spack always needs to associate a Spack version with the git reference,
+which is used for version comparison. This Spack version is heuristically
+taken from the closest valid git tag among ancestors of the git ref.
+
+Once a Spack version is associated with a git ref, it always printed with
+the git ref. For example, if the commit ``@git.abcdefg`` is tagged
+``0.19``, then the spec will be shown as ``@git.abcdefg=0.19``.
-Spack versions from git reference either have an associated version supplied by the user,
-or infer a relationship to known versions from the structure of the git repository. If an
-associated version is supplied by the user, Spack treats the git version as equivalent to that
-version for all version comparisons in the package logic (e.g. ``depends_on('foo', when='@1.5')``).
+If the git ref is not exactly a tag, then the distance to the nearest tag
+is also part of the resolved version. ``@git.abcdefg=0.19.git.8`` means
+that the commit is 8 commits away from the ``0.19`` tag.
-The associated version can be assigned with ``[git ref]=[version]`` syntax, with the caveat that the specified version is known to Spack from either the package definition, or in the configuration preferences (i.e. ``packages.yaml``).
+In cases where Spack cannot resolve a sensible version from a git ref,
+users can specify the Spack version to use for the git ref. This is done
+by appending ``=`` and the Spack version to the git ref. For example:
.. code-block:: sh
foo@git.my_ref=3.2 # use the my_ref tag or branch, but treat it as version 3.2 for version comparisons
foo@git.abcdef1234abcdef1234abcdef1234abcdef1234=develop # use the given commit, but treat it as develop for version comparisons
-If an associated version is not supplied then the tags in the git repo are used to determine
-the most recent previous version known to Spack. Details about how versions are compared
-and how Spack determines if one version is less than another are discussed in the developer guide.
-
-If the version spec is not provided, then Spack will choose one
-according to policies set for the particular spack installation. If
-the spec is ambiguous, i.e. it could match multiple versions, Spack
-will choose a version within the spec's constraints according to
-policies set for the particular Spack installation.
-
Details about how versions are compared and how Spack determines if
one version is less than another are discussed in the developer guide.
diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py
index 2b04fa642f..dafe318275 100644
--- a/lib/spack/docs/conf.py
+++ b/lib/spack/docs/conf.py
@@ -215,7 +215,7 @@ nitpick_ignore = [
("py:class", "spack.repo._PrependFileLoader"),
("py:class", "spack.build_systems._checks.BaseBuilder"),
# Spack classes that intersphinx is unable to resolve
- ("py:class", "spack.version.VersionBase"),
+ ("py:class", "spack.version.StandardVersion"),
("py:class", "spack.spec.DependencySpec"),
]
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index b6ab8f7230..1a90b8d417 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -851,16 +851,16 @@ Version comparison
^^^^^^^^^^^^^^^^^^
Most Spack versions are numeric, a tuple of integers; for example,
-``apex@0.1``, ``ferret@6.96`` or ``py-netcdf@1.2.3.1``. Spack knows
-how to compare and sort numeric versions.
+``0.1``, ``6.96`` or ``1.2.3.1``. Spack knows how to compare and sort
+numeric versions.
Some Spack versions involve slight extensions of numeric syntax; for
-example, ``py-sphinx-rtd-theme@0.1.10a0``. In this case, numbers are
+example, ``py-sphinx-rtd-theme@=0.1.10a0``. In this case, numbers are
always considered to be "newer" than letters. This is for consistency
with `RPM <https://bugzilla.redhat.com/show_bug.cgi?id=50977>`_.
Spack versions may also be arbitrary non-numeric strings, for example
-``@develop``, ``@master``, ``@local``.
+``develop``, ``master``, ``local``.
The order on versions is defined as follows. A version string is split
into a list of components based on delimiters such as ``.``, ``-`` etc.
@@ -918,6 +918,32 @@ use:
will be used.
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Ranges versus specific versions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When specifying versions in Spack using the ``pkg@<specifier>`` syntax,
+you can use either ranges or specific versions. It is generally
+recommended to use ranges instead of specific versions when packaging
+to avoid overly constraining dependencies, patches, and conflicts.
+
+For example, ``depends_on("python@3")`` denotes a range of versions,
+allowing Spack to pick any ``3.x.y`` version for Python, while
+``depends_on("python@=3.10.1")`` restricts it to a specific version.
+
+Specific ``@=`` versions should only be used in exceptional cases, such
+as when the package has a versioning scheme that omits the zero in the
+first patch release: ``3.1``, ``3.1.1``, ``3.1.2``. In this example,
+the specifier ``@=3.1`` is the correct way to select only the ``3.1``
+version, whereas ``@3.1`` would match all those versions.
+
+Ranges are preferred even if they would only match a single version
+defined in the package. This is because users can define custom versions
+in ``packages.yaml`` that typically include a custom suffix. For example,
+if the package defines the version ``1.2.3``, the specifier ``@1.2.3``
+will also match a user-defined version ``1.2.3-custom``.
+
+
.. _cmd-spack-checksum:
^^^^^^^^^^^^^^^^^^
@@ -2388,21 +2414,29 @@ requires Python 2, you can similarly leave out the lower bound:
Notice that we didn't use ``@:3``. Version ranges are *inclusive*, so
``@:3`` means "up to and including any 3.x version".
-What if a package can only be built with Python 2.7? You might be
-inclined to use:
+You can also simply write
.. code-block:: python
depends_on("python@2.7")
-However, this would be wrong. Spack assumes that all version constraints
-are exact, so it would try to install Python not at ``2.7.18``, but
-exactly at ``2.7``, which is a non-existent version. The correct way to
-specify this would be:
+to tell Spack that the package needs Python 2.7.x. This is equivalent to
+``@2.7:2.7``.
+
+In very rare cases, you may need to specify an exact version, for example
+if you need to distinguish between ``3.2`` and ``3.2.1``:
+
+.. code-block:: python
+
+ depends_on("pkg@=3.2")
+
+But in general, you should try to use version ranges as much as possible,
+so that custom suffixes are included too. The above example can be
+rewritten in terms of ranges as follows:
.. code-block:: python
- depends_on("python@2.7.0:2.7")
+ depends_on("pkg@3.2:3.2.0")
A spec can contain a version list of ranges and individual versions
separated by commas. For example, if you need Boost 1.59.0 or newer,
diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py
index e974eb2237..ca69695f11 100644
--- a/lib/spack/spack/binary_distribution.py
+++ b/lib/spack/spack/binary_distribution.py
@@ -776,11 +776,10 @@ def tarball_directory_name(spec):
Return name of the tarball directory according to the convention
<os>-<architecture>/<compiler>/<package>-<version>/
"""
- return "%s/%s/%s-%s" % (
- spec.architecture,
- str(spec.compiler).replace("@", "-"),
- spec.name,
- spec.version,
+ return os.path.join(
+ str(spec.architecture),
+ f"{spec.compiler.name}-{spec.compiler.version}",
+ f"{spec.name}-{spec.version}",
)
@@ -789,13 +788,9 @@ def tarball_name(spec, ext):
Return the name of the tarfile according to the convention
<os>-<architecture>-<package>-<dag_hash><ext>
"""
- return "%s-%s-%s-%s-%s%s" % (
- spec.architecture,
- str(spec.compiler).replace("@", "-"),
- spec.name,
- spec.version,
- spec.dag_hash(),
- ext,
+ return (
+ f"{spec.architecture}-{spec.compiler.name}-{spec.compiler.version}-"
+ f"{spec.name}-{spec.version}-{spec.dag_hash()}{ext}"
)
diff --git a/lib/spack/spack/build_systems/intel.py b/lib/spack/spack/build_systems/intel.py
index 771761119a..dbb8313acf 100644
--- a/lib/spack/spack/build_systems/intel.py
+++ b/lib/spack/spack/build_systems/intel.py
@@ -142,7 +142,7 @@ class IntelPackage(Package):
# The Intel libraries are provided without requiring a license as of
# version 2017.2. Trying to specify one anyway will fail. See:
# https://software.intel.com/en-us/articles/free-ipsxe-tools-and-libraries
- return self._has_compilers or self.version < ver("2017.2")
+ return self._has_compilers or self.version < Version("2017.2")
#: Comment symbol used in the license.lic file
license_comment = "#"
@@ -341,7 +341,7 @@ class IntelPackage(Package):
v_year = year
break
- return ver("%s.%s" % (v_year, v_tail))
+ return Version("%s.%s" % (v_year, v_tail))
# ---------------------------------------------------------------------
# Directory handling common to all Intel components
@@ -764,9 +764,9 @@ class IntelPackage(Package):
elif matches:
# TODO: Confirm that this covers clang (needed on Linux only)
gcc_version = Version(matches.groups()[1])
- if gcc_version >= ver("4.7"):
+ if gcc_version >= Version("4.7"):
abi = "gcc4.7"
- elif gcc_version >= ver("4.4"):
+ elif gcc_version >= Version("4.4"):
abi = "gcc4.4"
else:
abi = "gcc4.1" # unlikely, one hopes.
@@ -1019,7 +1019,7 @@ class IntelPackage(Package):
# Intel MPI since 2019 depends on libfabric which is not in the
# lib directory but in a directory of its own which should be
# included in the rpath
- if self.version_yearlike >= ver("2019"):
+ if self.version_yearlike >= Version("2019"):
d = ancestor(self.component_lib_dir("mpi"))
if "+external-libfabric" in self.spec:
result += self.spec["libfabric"].libs
diff --git a/lib/spack/spack/build_systems/python.py b/lib/spack/spack/build_systems/python.py
index 4641f85172..8df7144999 100644
--- a/lib/spack/spack/build_systems/python.py
+++ b/lib/spack/spack/build_systems/python.py
@@ -290,7 +290,7 @@ class PythonPackage(PythonExtension):
python_external_config = spack.config.get("packages:python:externals", [])
python_externals_configured = [
- spack.spec.Spec(item["spec"])
+ spack.spec.parse_with_version_concrete(item["spec"])
for item in python_external_config
if item["prefix"] == self.spec.external_path
]
diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py
index 2dae43d41b..9ca3ae4c6d 100644
--- a/lib/spack/spack/cmd/__init__.py
+++ b/lib/spack/spack/cmd/__init__.py
@@ -231,7 +231,7 @@ def parse_specs(args, **kwargs):
msg += "\n\n"
msg += unquoted_flags.report()
- raise spack.error.SpackError(msg)
+ raise spack.error.SpackError(msg) from e
def matching_spec_from_env(spec):
diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py
index a80aaa2eec..efb27a410c 100644
--- a/lib/spack/spack/cmd/checksum.py
+++ b/lib/spack/spack/cmd/checksum.py
@@ -19,7 +19,7 @@ import spack.util.crypto
from spack.package_base import deprecated_version, preferred_version
from spack.util.editor import editor
from spack.util.naming import valid_fully_qualified_module_name
-from spack.version import VersionBase, ver
+from spack.version import Version
description = "checksum available versions of a package"
section = "packaging"
@@ -83,9 +83,10 @@ def checksum(parser, args):
pkg = pkg_cls(spack.spec.Spec(args.package))
url_dict = {}
- versions = args.versions
- if (not versions) and args.preferred:
+ if not args.versions and args.preferred:
versions = [preferred_version(pkg)]
+ else:
+ versions = [Version(v) for v in args.versions]
if versions:
remote_versions = None
@@ -93,12 +94,6 @@ def checksum(parser, args):
if deprecated_version(pkg, version):
tty.warn("Version {0} is deprecated".format(version))
- version = ver(version)
- if not isinstance(version, VersionBase):
- tty.die(
- "Cannot generate checksums for version lists or "
- "version ranges. Use unambiguous versions."
- )
url = pkg.find_valid_url_for_version(version)
if url is not None:
url_dict[version] = url
diff --git a/lib/spack/spack/cmd/dev_build.py b/lib/spack/spack/cmd/dev_build.py
index 4ba0033873..c837b58c62 100644
--- a/lib/spack/spack/cmd/dev_build.py
+++ b/lib/spack/spack/cmd/dev_build.py
@@ -107,7 +107,7 @@ def dev_build(self, args):
" Use `spack create` to create a new package",
)
- if not spec.versions.concrete:
+ if not spec.versions.concrete_range_as_version:
tty.die(
"spack dev-build spec must have a single, concrete version. "
"Did you forget a package version number?"
diff --git a/lib/spack/spack/cmd/develop.py b/lib/spack/spack/cmd/develop.py
index 15354bdb0c..4746f9c96e 100644
--- a/lib/spack/spack/cmd/develop.py
+++ b/lib/spack/spack/cmd/develop.py
@@ -9,7 +9,9 @@ import llnl.util.tty as tty
import spack.cmd
import spack.cmd.common.arguments as arguments
+import spack.spec
import spack.util.path
+import spack.version
from spack.error import SpackError
description = "add a spec to an environment's dev-build information"
@@ -61,7 +63,9 @@ def develop(parser, args):
tty.msg(msg)
continue
- spec = spack.spec.Spec(entry["spec"])
+ # Both old syntax `spack develop pkg@x` and new syntax `spack develop pkg@=x`
+ # are currently supported.
+ spec = spack.spec.parse_with_version_concrete(entry["spec"])
pkg_cls = spack.repo.path.get_pkg_class(spec.name)
pkg_cls(spec).stage.steal_source(abspath)
@@ -75,9 +79,12 @@ def develop(parser, args):
raise SpackError("spack develop requires at most one named spec")
spec = specs[0]
- if not spec.versions.concrete:
+ version = spec.versions.concrete_range_as_version
+ if not version:
raise SpackError("Packages to develop must have a concrete version")
+ spec.versions = spack.version.VersionList([version])
+
# default path is relative path to spec.name
path = args.path or spec.name
abspath = spack.util.path.canonicalize_path(path, default_wd=env.path)
diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py
index 3752c16f41..fbb4d358ef 100644
--- a/lib/spack/spack/cmd/solve.py
+++ b/lib/spack/spack/cmd/solve.py
@@ -141,7 +141,7 @@ def _process_result(result, show, required_format, kwargs):
def solve(parser, args):
# these are the same options as `spack spec`
name_fmt = "{namespace}.{name}" if args.namespaces else "{name}"
- fmt = "{@version}{%compiler}{compiler_flags}{variants}{arch=architecture}"
+ fmt = "{@versions}{%compiler}{compiler_flags}{variants}{arch=architecture}"
install_status_fn = spack.spec.Spec.install_status
kwargs = {
"cover": args.cover,
diff --git a/lib/spack/spack/cmd/spec.py b/lib/spack/spack/cmd/spec.py
index b31c7a93b7..fc3ff05437 100644
--- a/lib/spack/spack/cmd/spec.py
+++ b/lib/spack/spack/cmd/spec.py
@@ -81,7 +81,7 @@ for further documentation regarding the spec syntax, see:
def spec(parser, args):
name_fmt = "{namespace}.{name}" if args.namespaces else "{name}"
- fmt = "{@version}{%compiler}{compiler_flags}{variants}{arch=architecture}"
+ fmt = "{@versions}{%compiler}{compiler_flags}{variants}{arch=architecture}"
install_status_fn = spack.spec.Spec.install_status
tree_kwargs = {
"cover": args.cover,
diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py
index b5dc6c4a8f..aedaaaa501 100644
--- a/lib/spack/spack/compilers/__init__.py
+++ b/lib/spack/spack/compilers/__init__.py
@@ -24,6 +24,7 @@ import spack.error
import spack.paths
import spack.platforms
import spack.spec
+import spack.version
from spack.util.environment import get_path
from spack.util.naming import mod_to_class
@@ -69,7 +70,7 @@ def pkg_spec_for_compiler(cspec):
break
else:
spec_str = str(cspec)
- return spack.spec.Spec(spec_str)
+ return spack.spec.parse_with_version_concrete(spec_str)
def _auto_compiler_spec(function):
@@ -213,7 +214,7 @@ def all_compilers_config(scope=None, init_config=True):
def all_compiler_specs(scope=None, init_config=True):
# Return compiler specs from the merged config.
return [
- spack.spec.CompilerSpec(s["compiler"]["spec"])
+ spack.spec.parse_with_version_concrete(s["compiler"]["spec"], compiler=True)
for s in all_compilers_config(scope, init_config)
]
@@ -384,7 +385,7 @@ class CacheReference(object):
def compiler_from_dict(items):
- cspec = spack.spec.CompilerSpec(items["spec"])
+ cspec = spack.spec.parse_with_version_concrete(items["spec"], compiler=True)
os = items.get("operating_system", None)
target = items.get("target", None)
@@ -453,7 +454,10 @@ def get_compilers(config, cspec=None, arch_spec=None):
for items in config:
items = items["compiler"]
- if cspec and items["spec"] != str(cspec):
+
+ # NOTE: in principle this should be equality not satisfies, but config can still
+ # be written in old format gcc@10.1.0 instead of gcc@=10.1.0.
+ if cspec and not cspec.satisfies(items["spec"]):
continue
# If an arch spec is given, confirm that this compiler
diff --git a/lib/spack/spack/compilers/aocc.py b/lib/spack/spack/compilers/aocc.py
index 4597d1a2f5..51f7b02e2b 100644
--- a/lib/spack/spack/compilers/aocc.py
+++ b/lib/spack/spack/compilers/aocc.py
@@ -143,5 +143,5 @@ class Aocc(Compiler):
def _handle_default_flag_addtions(self):
# This is a known issue for AOCC 3.0 see:
# https://developer.amd.com/wp-content/resources/AOCC-3.0-Install-Guide.pdf
- if self.real_version == ver("3.0.0"):
+ if self.real_version.satisfies(ver("3.0.0")):
return "-Wno-unused-command-line-argument " "-mllvm -eliminate-similar-expr=false"
diff --git a/lib/spack/spack/compilers/apple_clang.py b/lib/spack/spack/compilers/apple_clang.py
index 8a39b6427a..cb3c5d2646 100644
--- a/lib/spack/spack/compilers/apple_clang.py
+++ b/lib/spack/spack/compilers/apple_clang.py
@@ -13,7 +13,7 @@ from llnl.util.symlink import symlink
import spack.compiler
import spack.compilers.clang
import spack.util.executable
-import spack.version
+from spack.version import Version
class AppleClang(spack.compilers.clang.Clang):
@@ -41,7 +41,7 @@ class AppleClang(spack.compilers.clang.Clang):
@property
def cxx11_flag(self):
# Spack's AppleClang detection only valid from Xcode >= 4.6
- if self.real_version < spack.version.ver("4.0"):
+ if self.real_version < Version("4.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++11 standard", "cxx11_flag", "Xcode < 4.0"
)
@@ -49,38 +49,38 @@ class AppleClang(spack.compilers.clang.Clang):
@property
def cxx14_flag(self):
- if self.real_version < spack.version.ver("5.1"):
+ if self.real_version < Version("5.1"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++14 standard", "cxx14_flag", "Xcode < 5.1"
)
- elif self.real_version < spack.version.ver("6.1"):
+ elif self.real_version < Version("6.1"):
return "-std=c++1y"
return "-std=c++14"
@property
def cxx17_flag(self):
- if self.real_version < spack.version.ver("6.1"):
+ if self.real_version < Version("6.1"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++17 standard", "cxx17_flag", "Xcode < 6.1"
)
- elif self.real_version < spack.version.ver("10.0"):
+ elif self.real_version < Version("10.0"):
return "-std=c++1z"
return "-std=c++17"
@property
def cxx20_flag(self):
- if self.real_version < spack.version.ver("10.0"):
+ if self.real_version < Version("10.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++20 standard", "cxx20_flag", "Xcode < 10.0"
)
- elif self.real_version < spack.version.ver("13.0"):
+ elif self.real_version < Version("13.0"):
return "-std=c++2a"
return "-std=c++20"
@property
def cxx23_flag(self):
- if self.real_version < spack.version.ver("13.0"):
+ if self.real_version < Version("13.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++23 standard", "cxx23_flag", "Xcode < 13.0"
)
@@ -90,7 +90,7 @@ class AppleClang(spack.compilers.clang.Clang):
@property
def c99_flag(self):
- if self.real_version < spack.version.ver("4.0"):
+ if self.real_version < Version("4.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C99 standard", "c99_flag", "< 4.0"
)
@@ -98,7 +98,7 @@ class AppleClang(spack.compilers.clang.Clang):
@property
def c11_flag(self):
- if self.real_version < spack.version.ver("4.0"):
+ if self.real_version < Version("4.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C11 standard", "c11_flag", "< 4.0"
)
@@ -106,7 +106,7 @@ class AppleClang(spack.compilers.clang.Clang):
@property
def c17_flag(self):
- if self.real_version < spack.version.ver("11.0"):
+ if self.real_version < Version("11.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C17 standard", "c17_flag", "< 11.0"
)
@@ -114,7 +114,7 @@ class AppleClang(spack.compilers.clang.Clang):
@property
def c23_flag(self):
- if self.real_version < spack.version.ver("11.0.3"):
+ if self.real_version < Version("11.0.3"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C23 standard", "c23_flag", "< 11.0.3"
)
diff --git a/lib/spack/spack/compilers/cce.py b/lib/spack/spack/compilers/cce.py
index cd9d9f43f1..b2840a8229 100644
--- a/lib/spack/spack/compilers/cce.py
+++ b/lib/spack/spack/compilers/cce.py
@@ -5,7 +5,7 @@
import os
from spack.compiler import Compiler, UnsupportedCompilerFlag
-from spack.version import ver
+from spack.version import Version
class Cce(Compiler):
@@ -58,7 +58,7 @@ class Cce(Compiler):
@property
def is_clang_based(self):
version = self._real_version or self.version
- return version >= ver("9.0") and "classic" not in str(version)
+ return version >= Version("9.0") and "classic" not in str(version)
version_argument = "--version"
version_regex = r"[Cc]ray (?:clang|C :|C\+\+ :|Fortran :) [Vv]ersion.*?(\d+(\.\d+)+)"
@@ -98,9 +98,9 @@ class Cce(Compiler):
def c99_flag(self):
if self.is_clang_based:
return "-std=c99"
- elif self.real_version >= ver("8.4"):
+ elif self.real_version >= Version("8.4"):
return "-h std=c99,noconform,gnu"
- elif self.real_version >= ver("8.1"):
+ elif self.real_version >= Version("8.1"):
return "-h c99,noconform,gnu"
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 8.1")
@@ -108,7 +108,7 @@ class Cce(Compiler):
def c11_flag(self):
if self.is_clang_based:
return "-std=c11"
- elif self.real_version >= ver("8.5"):
+ elif self.real_version >= Version("8.5"):
return "-h std=c11,noconform,gnu"
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 8.5")
diff --git a/lib/spack/spack/compilers/clang.py b/lib/spack/spack/compilers/clang.py
index 53535256ae..a9356227de 100644
--- a/lib/spack/spack/compilers/clang.py
+++ b/lib/spack/spack/compilers/clang.py
@@ -10,7 +10,7 @@ import sys
import llnl.util.lang
from spack.compiler import Compiler, UnsupportedCompilerFlag
-from spack.version import ver
+from spack.version import Version
#: compiler symlink mappings for mixed f77 compilers
f77_mapping = [
@@ -100,24 +100,24 @@ class Clang(Compiler):
@property
def cxx11_flag(self):
- if self.real_version < ver("3.3"):
+ if self.real_version < Version("3.3"):
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 3.3")
return "-std=c++11"
@property
def cxx14_flag(self):
- if self.real_version < ver("3.4"):
+ if self.real_version < Version("3.4"):
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 3.5")
- elif self.real_version < ver("3.5"):
+ elif self.real_version < Version("3.5"):
return "-std=c++1y"
return "-std=c++14"
@property
def cxx17_flag(self):
- if self.real_version < ver("3.5"):
+ if self.real_version < Version("3.5"):
raise UnsupportedCompilerFlag(self, "the C++17 standard", "cxx17_flag", "< 3.5")
- elif self.real_version < ver("5.0"):
+ elif self.real_version < Version("5.0"):
return "-std=c++1z"
return "-std=c++17"
@@ -128,21 +128,21 @@ class Clang(Compiler):
@property
def c11_flag(self):
- if self.real_version < ver("3.0"):
+ if self.real_version < Version("3.0"):
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 3.0")
- if self.real_version < ver("3.1"):
+ if self.real_version < Version("3.1"):
return "-std=c1x"
return "-std=c11"
@property
def c17_flag(self):
- if self.real_version < ver("6.0"):
+ if self.real_version < Version("6.0"):
raise UnsupportedCompilerFlag(self, "the C17 standard", "c17_flag", "< 6.0")
return "-std=c17"
@property
def c23_flag(self):
- if self.real_version < ver("9.0"):
+ if self.real_version < Version("9.0"):
raise UnsupportedCompilerFlag(self, "the C23 standard", "c23_flag", "< 9.0")
return "-std=c2x"
diff --git a/lib/spack/spack/compilers/gcc.py b/lib/spack/spack/compilers/gcc.py
index 8a099417f4..ae8d5aa97e 100644
--- a/lib/spack/spack/compilers/gcc.py
+++ b/lib/spack/spack/compilers/gcc.py
@@ -11,7 +11,7 @@ from llnl.util.filesystem import ancestor
import spack.compiler
import spack.compilers.apple_clang as apple_clang
import spack.util.executable
-from spack.version import ver
+from spack.version import Version
class Gcc(spack.compiler.Compiler):
@@ -61,47 +61,47 @@ class Gcc(spack.compiler.Compiler):
@property
def cxx98_flag(self):
- if self.real_version < ver("6.0"):
+ if self.real_version < Version("6.0"):
return ""
else:
return "-std=c++98"
@property
def cxx11_flag(self):
- if self.real_version < ver("4.3"):
+ if self.real_version < Version("4.3"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++11 standard", "cxx11_flag", " < 4.3"
)
- elif self.real_version < ver("4.7"):
+ elif self.real_version < Version("4.7"):
return "-std=c++0x"
else:
return "-std=c++11"
@property
def cxx14_flag(self):
- if self.real_version < ver("4.8"):
+ if self.real_version < Version("4.8"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++14 standard", "cxx14_flag", "< 4.8"
)
- elif self.real_version < ver("4.9"):
+ elif self.real_version < Version("4.9"):
return "-std=c++1y"
else:
return "-std=c++14"
@property
def cxx17_flag(self):
- if self.real_version < ver("5.0"):
+ if self.real_version < Version("5.0"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C++17 standard", "cxx17_flag", "< 5.0"
)
- elif self.real_version < ver("6.0"):
+ elif self.real_version < Version("6.0"):
return "-std=c++1z"
else:
return "-std=c++17"
@property
def c99_flag(self):
- if self.real_version < ver("4.5"):
+ if self.real_version < Version("4.5"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C99 standard", "c99_flag", "< 4.5"
)
@@ -109,7 +109,7 @@ class Gcc(spack.compiler.Compiler):
@property
def c11_flag(self):
- if self.real_version < ver("4.7"):
+ if self.real_version < Version("4.7"):
raise spack.compiler.UnsupportedCompilerFlag(
self, "the C11 standard", "c11_flag", "< 4.7"
)
@@ -157,7 +157,7 @@ class Gcc(spack.compiler.Compiler):
return "unknown"
version = super(Gcc, cls).default_version(cc)
- if ver(version) >= ver("7"):
+ if Version(version) >= Version("7"):
output = spack.compiler.get_compiler_version_output(cc, "-dumpfullversion")
version = cls.extract_version_from_output(output)
return version
@@ -187,7 +187,7 @@ class Gcc(spack.compiler.Compiler):
output = spack.compiler.get_compiler_version_output(fc, "-dumpversion")
match = re.search(r"(?:GNU Fortran \(GCC\) )?([\d.]+)", output)
version = match.group(match.lastindex) if match else "unknown"
- if ver(version) >= ver("7"):
+ if Version(version) >= Version("7"):
output = spack.compiler.get_compiler_version_output(fc, "-dumpfullversion")
version = cls.extract_version_from_output(output)
return version
diff --git a/lib/spack/spack/compilers/intel.py b/lib/spack/spack/compilers/intel.py
index 4ecd3c9ef4..4ec2960525 100644
--- a/lib/spack/spack/compilers/intel.py
+++ b/lib/spack/spack/compilers/intel.py
@@ -7,7 +7,7 @@ import os
import sys
from spack.compiler import Compiler, UnsupportedCompilerFlag
-from spack.version import ver
+from spack.version import Version
class Intel(Compiler):
@@ -60,17 +60,17 @@ class Intel(Compiler):
@property
def openmp_flag(self):
- if self.real_version < ver("16.0"):
+ if self.real_version < Version("16.0"):
return "-openmp"
else:
return "-qopenmp"
@property
def cxx11_flag(self):
- if self.real_version < ver("11.1"):
+ if self.real_version < Version("11.1"):
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 11.1")
- elif self.real_version < ver("13"):
+ elif self.real_version < Version("13"):
return "-std=c++0x"
else:
return "-std=c++11"
@@ -78,23 +78,23 @@ class Intel(Compiler):
@property
def cxx14_flag(self):
# Adapted from CMake's Intel-CXX rules.
- if self.real_version < ver("15"):
+ if self.real_version < Version("15"):
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 15")
- elif self.real_version < ver("15.0.2"):
+ elif self.real_version < Version("15.0.2"):
return "-std=c++1y"
else:
return "-std=c++14"
@property
def c99_flag(self):
- if self.real_version < ver("12"):
+ if self.real_version < Version("12"):
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 12")
else:
return "-std=c99"
@property
def c11_flag(self):
- if self.real_version < ver("16"):
+ if self.real_version < Version("16"):
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 16")
else:
return "-std=c1x"
diff --git a/lib/spack/spack/compilers/pgi.py b/lib/spack/spack/compilers/pgi.py
index 85e450d4b1..77a0db7860 100644
--- a/lib/spack/spack/compilers/pgi.py
+++ b/lib/spack/spack/compilers/pgi.py
@@ -6,7 +6,7 @@
import os
from spack.compiler import Compiler, UnsupportedCompilerFlag
-from spack.version import ver
+from spack.version import Version
class Pgi(Compiler):
@@ -77,13 +77,13 @@ class Pgi(Compiler):
@property
def c99_flag(self):
- if self.real_version >= ver("12.10"):
+ if self.real_version >= Version("12.10"):
return "-c99"
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 12.10")
@property
def c11_flag(self):
- if self.real_version >= ver("15.3"):
+ if self.real_version >= Version("15.3"):
return "-c11"
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 15.3")
diff --git a/lib/spack/spack/compilers/xl.py b/lib/spack/spack/compilers/xl.py
index 8d70838904..db7241cb0f 100644
--- a/lib/spack/spack/compilers/xl.py
+++ b/lib/spack/spack/compilers/xl.py
@@ -6,7 +6,7 @@
import os
from spack.compiler import Compiler, UnsupportedCompilerFlag
-from spack.version import ver
+from spack.version import Version
class Xl(Compiler):
@@ -51,24 +51,24 @@ class Xl(Compiler):
@property
def cxx11_flag(self):
- if self.real_version < ver("13.1"):
+ if self.real_version < Version("13.1"):
raise UnsupportedCompilerFlag(self, "the C++11 standard", "cxx11_flag", "< 13.1")
else:
return "-qlanglvl=extended0x"
@property
def c99_flag(self):
- if self.real_version >= ver("13.1.1"):
+ if self.real_version >= Version("13.1.1"):
return "-std=gnu99"
- if self.real_version >= ver("10.1"):
+ if self.real_version >= Version("10.1"):
return "-qlanglvl=extc99"
raise UnsupportedCompilerFlag(self, "the C99 standard", "c99_flag", "< 10.1")
@property
def c11_flag(self):
- if self.real_version >= ver("13.1.2"):
+ if self.real_version >= Version("13.1.2"):
return "-std=gnu11"
- if self.real_version >= ver("12.1"):
+ if self.real_version >= Version("12.1"):
return "-qlanglvl=extc1x"
raise UnsupportedCompilerFlag(self, "the C11 standard", "c11_flag", "< 12.1")
@@ -76,7 +76,7 @@ class Xl(Compiler):
def cxx14_flag(self):
# .real_version does not have the "y.z" component of "w.x.y.z", which
# is required to distinguish whether support is available
- if self.version >= ver("16.1.1.8"):
+ if self.version >= Version("16.1.1.8"):
return "-std=c++14"
raise UnsupportedCompilerFlag(self, "the C++14 standard", "cxx14_flag", "< 16.1.1.8")
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
index 3452e42738..74eb6ea05d 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -41,7 +41,7 @@ import spack.util.path
import spack.variant as vt
from spack.config import config
from spack.package_prefs import PackagePrefs, is_spec_buildable, spec_externals
-from spack.version import Version, VersionList, VersionRange, ver
+from spack.version import ClosedOpenRange, VersionList, ver
#: impements rudimentary logic for ABI compatibility
_abi: Union[spack.abi.ABI, llnl.util.lang.Singleton] = llnl.util.lang.Singleton(
@@ -219,7 +219,7 @@ class Concretizer(object):
# Respect order listed in packages.yaml
-yaml_prefs(v),
# The preferred=True flag (packages or packages.yaml or both?)
- pkg_versions.get(Version(v)).get("preferred", False),
+ pkg_versions.get(v).get("preferred", False),
# ------- Regular case: use latest non-develop version by default.
# Avoid @develop version, which would otherwise be the "largest"
# in straight version comparisons
@@ -246,11 +246,12 @@ class Concretizer(object):
raise NoValidVersionError(spec)
else:
last = spec.versions[-1]
- if isinstance(last, VersionRange):
- if last.end:
- spec.versions = ver([last.end])
+ if isinstance(last, ClosedOpenRange):
+ range_as_version = VersionList([last]).concrete_range_as_version
+ if range_as_version:
+ spec.versions = ver([range_as_version])
else:
- spec.versions = ver([last.start])
+ raise NoValidVersionError(spec)
else:
spec.versions = ver([last])
diff --git a/lib/spack/spack/cray_manifest.py b/lib/spack/spack/cray_manifest.py
index 6f686aaf57..4fdbc095e5 100644
--- a/lib/spack/spack/cray_manifest.py
+++ b/lib/spack/spack/cray_manifest.py
@@ -11,7 +11,11 @@ import jsonschema.exceptions
import llnl.util.tty as tty
import spack.cmd
+import spack.error
import spack.hash_types as hash_types
+import spack.platforms
+import spack.repo
+import spack.spec
from spack.schema.cray_manifest import schema as manifest_schema
#: Cray systems can store a Spack-compatible description of system
@@ -74,13 +78,13 @@ def spec_from_entry(entry):
compiler_str = ""
if "compiler" in entry:
- compiler_format = "%{name}@{version}"
+ compiler_format = "%{name}@={version}"
compiler_str = compiler_format.format(
name=translated_compiler_name(entry["compiler"]["name"]),
version=entry["compiler"]["version"],
)
- spec_format = "{name}@{version} {compiler} {arch}"
+ spec_format = "{name}@={version} {compiler} {arch}"
spec_str = spec_format.format(
name=entry["name"], version=entry["version"], compiler=compiler_str, arch=arch_str
)
diff --git a/lib/spack/spack/database.py b/lib/spack/spack/database.py
index af321323c9..f75e540a70 100644
--- a/lib/spack/spack/database.py
+++ b/lib/spack/spack/database.py
@@ -46,10 +46,10 @@ import spack.spec
import spack.store
import spack.util.lock as lk
import spack.util.spack_json as sjson
+import spack.version as vn
from spack.directory_layout import DirectoryLayoutError, InconsistentInstallDirectoryError
from spack.error import SpackError
from spack.util.crypto import bit_length
-from spack.version import Version
# TODO: Provide an API automatically retyring a build after detecting and
# TODO: clearing a failure.
@@ -60,7 +60,7 @@ _db_dirname = ".spack-db"
# DB version. This is stuck in the DB file to track changes in format.
# Increment by one when the database format changes.
# Versions before 5 were not integers.
-_db_version = Version("6")
+_db_version = vn.Version("6")
# For any version combinations here, skip reindex when upgrading.
# Reindexing can take considerable time and is not always necessary.
@@ -70,8 +70,8 @@ _skip_reindex = [
# only difference is that v5 can contain "deprecated_for"
# fields. So, skip the reindex for this transition. The new
# version is saved to disk the first time the DB is written.
- (Version("0.9.3"), Version("5")),
- (Version("5"), Version("6")),
+ (vn.Version("0.9.3"), vn.Version("5")),
+ (vn.Version("5"), vn.Version("6")),
]
# Default timeout for spack database locks in seconds or None (no timeout).
@@ -105,7 +105,7 @@ default_install_record_fields = [
def reader(version):
- reader_cls = {Version("5"): spack.spec.SpecfileV1, Version("6"): spack.spec.SpecfileV3}
+ reader_cls = {vn.Version("5"): spack.spec.SpecfileV1, vn.Version("6"): spack.spec.SpecfileV3}
return reader_cls[version]
@@ -694,8 +694,7 @@ class Database(object):
spec_dict[hash.name] = hash_key
# Build spec from dict first.
- spec = spec_reader.from_node_dict(spec_dict)
- return spec
+ return spec_reader.from_node_dict(spec_dict)
def db_for_spec_hash(self, hash_key):
with self.read_transaction():
@@ -798,7 +797,7 @@ class Database(object):
installs = db["installs"]
# TODO: better version checking semantics.
- version = Version(db["version"])
+ version = vn.Version(db["version"])
spec_reader = reader(version)
if version > _db_version:
raise InvalidDatabaseVersionError(_db_version, version)
@@ -816,9 +815,11 @@ class Database(object):
)
def invalid_record(hash_key, error):
- msg = "Invalid record in Spack database: " "hash: %s, cause: %s: %s"
- msg %= (hash_key, type(error).__name__, str(error))
- raise CorruptDatabaseError(msg, self._index_path)
+ return CorruptDatabaseError(
+ f"Invalid record in Spack database: hash: {hash_key}, cause: "
+ f"{type(error).__name__}: {error}",
+ self._index_path,
+ )
# Build up the database in three passes:
#
@@ -846,7 +847,7 @@ class Database(object):
if not spec.external and "installed" in rec and rec["installed"]:
installed_prefixes.add(rec["path"])
except Exception as e:
- invalid_record(hash_key, e)
+ raise invalid_record(hash_key, e) from e
# Pass 2: Assign dependencies once all specs are created.
for hash_key in data:
@@ -855,7 +856,7 @@ class Database(object):
except MissingDependenciesError:
raise
except Exception as e:
- invalid_record(hash_key, e)
+ raise invalid_record(hash_key, e) from e
# Pass 3: Mark all specs concrete. Specs representing real
# installations must be explicitly marked.
diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
index 341b8b2c1f..27075d47b4 100644
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -32,7 +32,7 @@ import collections.abc
import functools
import os.path
import re
-from typing import List, Optional, Set
+from typing import List, Optional, Set, Union
import llnl.util.lang
import llnl.util.tty.color
@@ -45,7 +45,13 @@ import spack.variant
from spack.dependency import Dependency, canonical_deptype, default_deptype
from spack.fetch_strategy import from_kwargs
from spack.resource import Resource
-from spack.version import GitVersion, Version, VersionChecksumError, VersionLookupError
+from spack.version import (
+ GitVersion,
+ Version,
+ VersionChecksumError,
+ VersionError,
+ VersionLookupError,
+)
__all__ = [
"DirectiveError",
@@ -318,7 +324,7 @@ directive = DirectiveMeta.directive
@directive("versions")
def version(
- ver: str,
+ ver: Union[str, int],
# this positional argument is deprecated, use sha256=... instead
checksum: Optional[str] = None,
*,
@@ -362,64 +368,72 @@ def version(
The (keyword) arguments are turned into a valid fetch strategy for
code packages later. See ``spack.fetch_strategy.for_package_version()``.
"""
+ kwargs = {
+ key: value
+ for key, value in (
+ ("sha256", sha256),
+ ("sha384", sha384),
+ ("sha512", sha512),
+ ("preferred", preferred),
+ ("deprecated", deprecated),
+ ("expand", expand),
+ ("url", url),
+ ("extension", extension),
+ ("no_cache", no_cache),
+ ("fetch_options", fetch_options),
+ ("git", git),
+ ("svn", svn),
+ ("hg", hg),
+ ("cvs", cvs),
+ ("get_full_repo", get_full_repo),
+ ("branch", branch),
+ ("submodules", submodules),
+ ("submodules_delete", submodules_delete),
+ ("commit", commit),
+ ("tag", tag),
+ ("revision", revision),
+ ("date", date),
+ ("md5", md5),
+ ("sha1", sha1),
+ ("sha224", sha224),
+ ("checksum", checksum),
+ )
+ if value is not None
+ }
+ return lambda pkg: _execute_version(pkg, ver, **kwargs)
- def _execute_version(pkg):
- if (
- any((sha256, sha384, sha512, md5, sha1, sha224, checksum))
- and hasattr(pkg, "has_code")
- and not pkg.has_code
- ):
- raise VersionChecksumError(
- "{0}: Checksums not allowed in no-code packages "
- "(see '{1}' version).".format(pkg.name, ver)
- )
- kwargs = {
- key: value
- for key, value in (
- ("sha256", sha256),
- ("sha384", sha384),
- ("sha512", sha512),
- ("preferred", preferred),
- ("deprecated", deprecated),
- ("expand", expand),
- ("url", url),
- ("extension", extension),
- ("no_cache", no_cache),
- ("fetch_options", fetch_options),
- ("git", git),
- ("svn", svn),
- ("hg", hg),
- ("cvs", cvs),
- ("get_full_repo", get_full_repo),
- ("branch", branch),
- ("submodules", submodules),
- ("submodules_delete", submodules_delete),
- ("commit", commit),
- ("tag", tag),
- ("revision", revision),
- ("date", date),
- ("md5", md5),
- ("sha1", sha1),
- ("sha224", sha224),
- ("checksum", checksum),
- )
- if value is not None
- }
-
- # Store kwargs for the package to later with a fetch_strategy.
- version = Version(ver)
- if isinstance(version, GitVersion):
- if git is None and not hasattr(pkg, "git"):
- msg = "Spack version directives cannot include git hashes fetched from"
- msg += " URLs. Error in package '%s'\n" % pkg.name
- msg += " version('%s', " % version.string
- msg += ", ".join("%s='%s'" % (argname, value) for argname, value in kwargs.items())
- msg += ")"
- raise VersionLookupError(msg)
- pkg.versions[version] = kwargs
-
- return _execute_version
+def _execute_version(pkg, ver, **kwargs):
+ if (
+ any(
+ s in kwargs
+ for s in ("sha256", "sha384", "sha512", "md5", "sha1", "sha224", "checksum")
+ )
+ and hasattr(pkg, "has_code")
+ and not pkg.has_code
+ ):
+ raise VersionChecksumError(
+ "{0}: Checksums not allowed in no-code packages "
+ "(see '{1}' version).".format(pkg.name, ver)
+ )
+
+ if not isinstance(ver, (int, str)):
+ raise VersionError(
+ f"{pkg.name}: declared version '{ver!r}' in package should be a string or int."
+ )
+
+ # Declared versions are concrete
+ version = Version(ver)
+
+ if isinstance(version, GitVersion) and not hasattr(pkg, "git") and "git" not in kwargs:
+ args = ", ".join(f"{argname}='{value}'" for argname, value in kwargs.items())
+ raise VersionLookupError(
+ f"{pkg.name}: spack version directives cannot include git hashes fetched from URLs.\n"
+ f" version('{ver}', {args})"
+ )
+
+ # Store kwargs for the package to later with a fetch_strategy.
+ pkg.versions[version] = kwargs
def _depends_on(pkg, spec, when=None, type=default_deptype, patches=None):
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
index b3d4176c04..7774810102 100644
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -49,6 +49,7 @@ import spack.util.path
import spack.util.spack_json as sjson
import spack.util.spack_yaml as syaml
import spack.util.url
+import spack.version
from spack.filesystem_view import SimpleFilesystemView, inverse_view_func_parser, view_func_parser
from spack.installer import PackageInstaller
from spack.spec import Spec
@@ -774,7 +775,7 @@ class Environment:
self.views: Dict[str, ViewDescriptor] = {}
#: Specs from "spack.yaml"
- self.spec_lists = {user_speclist_name: SpecList()}
+ self.spec_lists: Dict[str, SpecList] = {user_speclist_name: SpecList()}
#: Dev-build specs from "spack.yaml"
self.dev_specs: Dict[str, Any] = {}
#: User specs from the last concretization
@@ -863,7 +864,7 @@ class Environment:
self.dev_specs = copy.deepcopy(configuration.get("develop", {}))
for name, entry in self.dev_specs.items():
# spec must include a concrete version
- assert Spec(entry["spec"]).version.concrete
+ assert Spec(entry["spec"]).versions.concrete_range_as_version
# default path is the spec name
if "path" not in entry:
self.dev_specs[name]["path"] = name
@@ -1139,21 +1140,21 @@ class Environment:
def change_existing_spec(
self,
- change_spec,
- list_name=user_speclist_name,
- match_spec=None,
+ change_spec: Spec,
+ list_name: str = user_speclist_name,
+ match_spec: Optional[Spec] = None,
allow_changing_multiple_specs=False,
):
"""
Find the spec identified by `match_spec` and change it to `change_spec`.
Arguments:
- change_spec (spack.spec.Spec): defines the spec properties that
+ change_spec: defines the spec properties that
need to be changed. This will not change attributes of the
matched spec unless they conflict with `change_spec`.
- list_name (str): identifies the spec list in the environment that
+ list_name: identifies the spec list in the environment that
should be modified
- match_spec (spack.spec.Spec): if set, this identifies the spec
+ match_spec: if set, this identifies the spec
that should be changed. If not set, it is assumed we are
looking for a spec with the same name as `change_spec`.
"""
@@ -1252,15 +1253,15 @@ class Environment:
del self.concretized_order[i]
del self.specs_by_hash[dag_hash]
- def develop(self, spec, path, clone=False):
+ def develop(self, spec: Spec, path: str, clone: bool = False) -> bool:
"""Add dev-build info for package
Args:
- spec (spack.spec.Spec): Set constraints on development specs. Must include a
+ spec: Set constraints on development specs. Must include a
concrete version.
- path (str): Path to find code for developer builds. Relative
+ path: Path to find code for developer builds. Relative
paths will be resolved relative to the environment.
- clone (bool): Clone the package code to the path.
+ clone: Clone the package code to the path.
If clone is False Spack will assume the code is already present
at ``path``.
diff --git a/lib/spack/spack/fetch_strategy.py b/lib/spack/spack/fetch_strategy.py
index 07d6a99966..d5530db524 100644
--- a/lib/spack/spack/fetch_strategy.py
+++ b/lib/spack/spack/fetch_strategy.py
@@ -874,12 +874,12 @@ class GitFetchStrategy(VCSFetchStrategy):
# If we want a particular branch ask for it.
if branch:
args.extend(["--branch", branch])
- elif tag and self.git_version >= spack.version.ver("1.8.5.2"):
+ elif tag and self.git_version >= spack.version.Version("1.8.5.2"):
args.extend(["--branch", tag])
# Try to be efficient if we're using a new enough git.
# This checks out only one branch's history
- if self.git_version >= spack.version.ver("1.7.10"):
+ if self.git_version >= spack.version.Version("1.7.10"):
if self.get_full_repo:
args.append("--no-single-branch")
else:
@@ -890,7 +890,7 @@ class GitFetchStrategy(VCSFetchStrategy):
# tree, if the in-use git and protocol permit it.
if (
(not self.get_full_repo)
- and self.git_version >= spack.version.ver("1.7.1")
+ and self.git_version >= spack.version.Version("1.7.1")
and self.protocol_supports_shallow_clone()
):
args.extend(["--depth", "1"])
@@ -907,7 +907,7 @@ class GitFetchStrategy(VCSFetchStrategy):
# For tags, be conservative and check them out AFTER
# cloning. Later git versions can do this with clone
# --branch, but older ones fail.
- if tag and self.git_version < spack.version.ver("1.8.5.2"):
+ if tag and self.git_version < spack.version.Version("1.8.5.2"):
# pull --tags returns a "special" error code of 1 in
# older versions that we have to ignore.
# see: https://github.com/git/git/commit/19d122b
@@ -1516,7 +1516,7 @@ def for_package_version(pkg, version=None):
assert not pkg.spec.concrete, "concrete specs should not pass the 'version=' argument"
# Specs are initialized with the universe range, if no version information is given,
# so here we make sure we always match the version passed as argument
- if not isinstance(version, spack.version.VersionBase):
+ if not isinstance(version, spack.version.StandardVersion):
version = spack.version.Version(version)
version_list = spack.version.VersionList()
@@ -1529,10 +1529,10 @@ def for_package_version(pkg, version=None):
if isinstance(version, spack.version.GitVersion):
if not hasattr(pkg, "git"):
raise web_util.FetchError(
- "Cannot fetch git version for %s. Package has no 'git' attribute" % pkg.name
+ f"Cannot fetch git version for {pkg.name}. Package has no 'git' attribute"
)
# Populate the version with comparisons to other commits
- version.generate_git_lookup(pkg.name)
+ version.attach_git_lookup_from_package(pkg.name)
# For GitVersion, we have no way to determine whether a ref is a branch or tag
# Fortunately, we handle branches and tags identically, except tags are
@@ -1545,15 +1545,11 @@ def for_package_version(pkg, version=None):
kwargs["submodules"] = getattr(pkg, "submodules", False)
- # if we have a ref_version already, and it is a version from the package
- # we can use that version's submodule specifications
- if pkg.version.ref_version:
- ref_version = spack.version.Version(pkg.version.ref_version[0])
- ref_version_attributes = pkg.versions.get(ref_version)
- if ref_version_attributes:
- kwargs["submodules"] = ref_version_attributes.get(
- "submodules", kwargs["submodules"]
- )
+ # if the ref_version is a known version from the package, use that version's
+ # submodule specifications
+ ref_version_attributes = pkg.versions.get(pkg.version.ref_version)
+ if ref_version_attributes:
+ kwargs["submodules"] = ref_version_attributes.get("submodules", kwargs["submodules"])
fetcher = GitFetchStrategy(**kwargs)
return fetcher
diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py
index 1c56a08980..40018ee609 100644
--- a/lib/spack/spack/installer.py
+++ b/lib/spack/spack/installer.py
@@ -52,6 +52,7 @@ import spack.mirror
import spack.package_base
import spack.package_prefs as prefs
import spack.repo
+import spack.spec
import spack.store
import spack.util.executable
import spack.util.path
@@ -628,9 +629,7 @@ def package_id(pkg):
derived
"""
if not pkg.spec.concrete:
- raise ValueError(
- "Cannot provide a unique, readable id when " "the spec is not concretized."
- )
+ raise ValueError("Cannot provide a unique, readable id when the spec is not concretized.")
return "{0}-{1}-{2}".format(pkg.name, pkg.version, pkg.spec.dag_hash())
@@ -908,7 +907,6 @@ class PackageInstaller(object):
"""
install_args = task.request.install_args
keep_prefix = install_args.get("keep_prefix")
- keep_stage = install_args.get("keep_stage")
restage = install_args.get("restage")
# Make sure the package is ready to be locally installed.
@@ -941,9 +939,9 @@ class PackageInstaller(object):
else:
tty.debug("{0} is partially installed".format(task.pkg_id))
- # Destroy the stage for a locally installed, non-DIYStage, package
- if restage and task.pkg.stage.managed_by_spack:
- task.pkg.stage.destroy()
+ # Destroy the stage for a locally installed, non-DIYStage, package
+ if restage and task.pkg.stage.managed_by_spack:
+ task.pkg.stage.destroy()
if installed_in_db and (
rec.spec.dag_hash() not in task.request.overwrite
@@ -955,12 +953,6 @@ class PackageInstaller(object):
if task.explicit:
spack.store.db.update_explicit(task.pkg.spec, True)
- # In case the stage directory has already been created, this
- # check ensures it is removed after we checked that the spec is
- # installed.
- if not keep_stage:
- task.pkg.stage.destroy()
-
def _cleanup_all_tasks(self):
"""Cleanup all build tasks to include releasing their locks."""
for pkg_id in self.locks:
diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py
index 98bf4d9c23..2da5380153 100644
--- a/lib/spack/spack/package_base.py
+++ b/lib/spack/spack/package_base.py
@@ -62,7 +62,7 @@ from spack.util.executable import ProcessError, which
from spack.util.package_hash import package_hash
from spack.util.prefix import Prefix
from spack.util.web import FetchError
-from spack.version import GitVersion, Version, VersionBase
+from spack.version import GitVersion, StandardVersion, Version
FLAG_HANDLER_RETURN_TYPE = Tuple[
Optional[Iterable[str]], Optional[Iterable[str]], Optional[Iterable[str]]
@@ -97,9 +97,9 @@ def deprecated_version(pkg, version):
Arguments:
pkg (PackageBase): The package whose version is to be checked.
- version (str or spack.version.VersionBase): The version being checked
+ version (str or spack.version.StandardVersion): The version being checked
"""
- if not isinstance(version, VersionBase):
+ if not isinstance(version, StandardVersion):
version = Version(version)
for k, v in pkg.versions.items():
@@ -120,7 +120,7 @@ def preferred_version(pkg):
# as preferred in the package, then on the fact that the
# version is not develop, then lexicographically
key_fn = lambda v: (pkg.versions[v].get("preferred", False), not v.isdevelop(), v)
- return sorted(pkg.versions, key=key_fn).pop()
+ return max(pkg.versions, key=key_fn)
class WindowsRPath(object):
@@ -928,7 +928,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta):
return self._implement_all_urls_for_version(version, uf)
def _implement_all_urls_for_version(self, version, custom_url_for_version=None):
- if not isinstance(version, VersionBase):
+ if not isinstance(version, StandardVersion):
version = Version(version)
urls = []
diff --git a/lib/spack/spack/package_prefs.py b/lib/spack/spack/package_prefs.py
index c0bc52b239..a30c9c7bfd 100644
--- a/lib/spack/spack/package_prefs.py
+++ b/lib/spack/spack/package_prefs.py
@@ -9,9 +9,9 @@ import spack.error
import spack.repo
from spack.config import ConfigError
from spack.util.path import canonicalize_path
-from spack.version import VersionList
+from spack.version import Version
-_lesser_spec_types = {"compiler": spack.spec.CompilerSpec, "version": VersionList}
+_lesser_spec_types = {"compiler": spack.spec.CompilerSpec, "version": Version}
def _spec_type(component):
diff --git a/lib/spack/spack/parser.py b/lib/spack/spack/parser.py
index c721cdde98..9ab3bc7c28 100644
--- a/lib/spack/spack/parser.py
+++ b/lib/spack/spack/parser.py
@@ -95,7 +95,7 @@ else:
VALUE = r"([a-zA-Z_0-9\-+\*.,:=\~\/\\]+)"
QUOTED_VALUE = r"[\"']+([a-zA-Z_0-9\-+\*.,:=\~\/\\\s]+)[\"']+"
-VERSION = r"([a-zA-Z0-9_][a-zA-Z_0-9\-\.]*\b)"
+VERSION = r"=?([a-zA-Z0-9_][a-zA-Z_0-9\-\.]*\b)"
VERSION_RANGE = rf"({VERSION}\s*:\s*{VERSION}(?!\s*=)|:\s*{VERSION}(?!\s*=)|{VERSION}\s*:|:)"
VERSION_LIST = rf"({VERSION_RANGE}|{VERSION})(\s*[,]\s*({VERSION_RANGE}|{VERSION}))*"
@@ -361,19 +361,10 @@ class SpecNodeParser:
raise spack.spec.MultipleVersionError(
f"{initial_spec} cannot have multiple versions"
)
-
- version_list = spack.version.VersionList()
- version_list.add(spack.version.from_string(self.ctx.current_token.value[1:]))
- initial_spec.versions = version_list
-
- # Add a git lookup method for GitVersions
- if (
- initial_spec.name
- and initial_spec.versions.concrete
- and isinstance(initial_spec.version, spack.version.GitVersion)
- ):
- initial_spec.version.generate_git_lookup(initial_spec.fullname)
-
+ initial_spec.versions = spack.version.VersionList(
+ [spack.version.from_string(self.ctx.current_token.value[1:])]
+ )
+ initial_spec.attach_git_version_lookup()
self.has_version = True
elif self.ctx.accept(TokenType.BOOL_VARIANT):
self.hash_not_parsed_or_raise(initial_spec, self.ctx.current_token.value)
diff --git a/lib/spack/spack/schema/packages.py b/lib/spack/spack/schema/packages.py
index cb05d57857..698787a914 100644
--- a/lib/spack/spack/schema/packages.py
+++ b/lib/spack/spack/schema/packages.py
@@ -53,7 +53,8 @@ properties = {
"version": {
"type": "array",
"default": [],
- # version strings
+ # version strings (type should be string, number is still possible
+ # but deprecated. this is to avoid issues with e.g. 3.10 -> 3.1)
"items": {"anyOf": [{"type": "string"}, {"type": "number"}]},
},
"target": {
@@ -131,3 +132,16 @@ schema = {
"additionalProperties": False,
"properties": properties,
}
+
+
+def update(data):
+ changed = False
+ for key in data:
+ version = data[key].get("version")
+ if not version or all(isinstance(v, str) for v in version):
+ continue
+
+ data[key]["version"] = [str(v) for v in version]
+ changed = True
+
+ return changed
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index 7c5d409ca0..ae3fc64e33 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -43,10 +43,11 @@ import spack.platforms
import spack.repo
import spack.spec
import spack.store
+import spack.traverse
import spack.util.path
import spack.util.timer
import spack.variant
-import spack.version
+import spack.version as vn
# these are from clingo.ast and bootstrapped later
ASTType = None
@@ -893,7 +894,7 @@ class SpackSolverSetup(object):
most_to_least_preferred = []
for _, group in itertools.groupby(partially_sorted_versions, key=key_fn):
most_to_least_preferred.extend(
- list(sorted(group, reverse=True, key=lambda x: spack.version.ver(x.version)))
+ list(sorted(group, reverse=True, key=lambda x: vn.ver(x.version)))
)
for weight, declared_version in enumerate(most_to_least_preferred):
@@ -920,7 +921,7 @@ class SpackSolverSetup(object):
if spec.concrete:
return [fn.attr("version", spec.name, spec.version)]
- if spec.versions == spack.version.ver(":"):
+ if spec.versions == vn.any_version:
return []
# record all version constraints for later
@@ -1286,6 +1287,7 @@ class SpackSolverSetup(object):
spec = spack.spec.Spec(spec_str)
if not spec.name:
spec.name = pkg_name
+ spec.attach_git_version_lookup()
when_spec = spec
if virtual:
when_spec = spack.spec.Spec(pkg_name)
@@ -1330,7 +1332,7 @@ class SpackSolverSetup(object):
# Read a list of all the specs for this package
externals = data.get("externals", [])
- external_specs = [spack.spec.Spec(x["spec"]) for x in externals]
+ external_specs = [spack.spec.parse_with_version_concrete(x["spec"]) for x in externals]
# Order the external versions to prefer more recent versions
# even if specs in packages.yaml are not ordered that way
@@ -1636,48 +1638,27 @@ class SpackSolverSetup(object):
version_preferences = packages_yaml.get(pkg_name, {}).get("version", [])
for idx, v in enumerate(version_preferences):
# v can be a string so force it into an actual version for comparisons
- ver = spack.version.Version(v)
+ ver = vn.Version(v)
self.declared_versions[pkg_name].append(
DeclaredVersion(version=ver, idx=idx, origin=version_provenance.packages_yaml)
)
+ self.possible_versions[pkg_name].add(ver)
def add_concrete_versions_from_specs(self, specs, origin):
"""Add concrete versions to possible versions from lists of CLI/dev specs."""
- for spec in specs:
- for dep in spec.traverse():
- if not dep.versions.concrete:
- continue
+ for s in spack.traverse.traverse_nodes(specs):
+ # If there is a concrete version on the CLI *that we know nothing
+ # about*, add it to the known versions. Use idx=0, which is the
+ # best possible, so they're guaranteed to be used preferentially.
+ version = s.versions.concrete
- known_versions = self.possible_versions[dep.name]
- if not isinstance(dep.version, spack.version.GitVersion) and any(
- v.satisfies(dep.version) for v in known_versions
- ):
- # some version we know about satisfies this constraint, so we
- # should use that one. e.g, if the user asks for qt@5 and we
- # know about qt@5.5. This ensures we don't add under-specified
- # versions to the solver
- #
- # For git versions, we know the version is already fully specified
- # so we don't have to worry about whether it's an under-specified
- # version
- continue
+ if version is None or any(v == version for v in self.possible_versions[s.name]):
+ continue
- # if there is a concrete version on the CLI *that we know nothing
- # about*, add it to the known versions. Use idx=0, which is the
- # best possible, so they're guaranteed to be used preferentially.
- self.declared_versions[dep.name].append(
- DeclaredVersion(version=dep.version, idx=0, origin=origin)
- )
- self.possible_versions[dep.name].add(dep.version)
- if (
- isinstance(dep.version, spack.version.GitVersion)
- and dep.version.user_supplied_reference
- ):
- defined_version = spack.version.Version(dep.version.ref_version_str)
- self.declared_versions[dep.name].append(
- DeclaredVersion(version=defined_version, idx=1, origin=origin)
- )
- self.possible_versions[dep.name].add(defined_version)
+ self.declared_versions[s.name].append(
+ DeclaredVersion(version=version, idx=0, origin=origin)
+ )
+ self.possible_versions[s.name].add(version)
def _supported_targets(self, compiler_name, compiler_version, targets):
"""Get a list of which targets are supported by the compiler.
@@ -1872,28 +1853,26 @@ class SpackSolverSetup(object):
# add compiler specs from the input line to possibilities if we
# don't require compilers to exist.
strict = spack.concretize.Concretizer().check_for_compiler_existence
- for spec in specs:
- for s in spec.traverse():
- # we don't need to validate compilers for already-built specs
- if s.concrete:
- continue
+ for s in spack.traverse.traverse_nodes(specs):
+ # we don't need to validate compilers for already-built specs
+ if s.concrete or not s.compiler:
+ continue
- if not s.compiler or not s.compiler.concrete:
- continue
+ version = s.compiler.versions.concrete
- if strict and s.compiler not in cspecs:
- if not s.concrete:
- raise spack.concretize.UnavailableCompilerVersionError(s.compiler)
- # Allow unknown compilers to exist if the associated spec
- # is already built
- else:
- compiler_cls = spack.compilers.class_for_compiler_name(s.compiler.name)
- compilers.append(
- compiler_cls(
- s.compiler, operating_system=None, target=None, paths=[None] * 4
- )
- )
- self.gen.fact(fn.allow_compiler(s.compiler.name, s.compiler.version))
+ if not version or any(c.satisfies(s.compiler) for c in cspecs):
+ continue
+
+ # Error when a compiler is not found and strict mode is enabled
+ if strict:
+ raise spack.concretize.UnavailableCompilerVersionError(s.compiler)
+
+ # Make up a compiler matching the input spec. This is for bootstrapping.
+ compiler_cls = spack.compilers.class_for_compiler_name(s.compiler.name)
+ compilers.append(
+ compiler_cls(s.compiler, operating_system=None, target=None, paths=[None] * 4)
+ )
+ self.gen.fact(fn.allow_compiler(s.compiler.name, version))
return list(
sorted(
@@ -1906,25 +1885,9 @@ class SpackSolverSetup(object):
def define_version_constraints(self):
"""Define what version_satisfies(...) means in ASP logic."""
for pkg_name, versions in sorted(self.version_constraints):
- # version must be *one* of the ones the spec allows.
- # Also, "possible versions" contain only concrete versions, so satisfies is appropriate
- allowed_versions = [
- v for v in sorted(self.possible_versions[pkg_name]) if v.satisfies(versions)
- ]
-
- # This is needed to account for a variable number of
- # numbers e.g. if both 1.0 and 1.0.2 are possible versions
- exact_match = [
- v
- for v in allowed_versions
- if v == versions and not isinstance(v, spack.version.GitVersion)
- ]
- if exact_match:
- allowed_versions = exact_match
-
# generate facts for each package constraint and the version
# that satisfies it
- for v in allowed_versions:
+ for v in sorted(v for v in self.possible_versions[pkg_name] if v.satisfies(versions)):
self.gen.fact(fn.version_satisfies(pkg_name, versions, v))
self.gen.newline()
@@ -1943,13 +1906,11 @@ class SpackSolverSetup(object):
# extract all the real versions mentioned in version ranges
def versions_for(v):
- if isinstance(v, spack.version.VersionBase):
+ if isinstance(v, vn.StandardVersion):
return [v]
- elif isinstance(v, spack.version.VersionRange):
- result = [v.start] if v.start else []
- result += [v.end] if v.end else []
- return result
- elif isinstance(v, spack.version.VersionList):
+ elif isinstance(v, vn.ClosedOpenRange):
+ return [v.lo, vn.prev_version(v.hi)]
+ elif isinstance(v, vn.VersionList):
return sum((versions_for(e) for e in v), [])
else:
raise TypeError("expected version type, found: %s" % type(v))
@@ -2237,14 +2198,9 @@ def _specs_from_requires(pkg_name, section):
spec.name = pkg_name
extracted_specs.append(spec)
- version_specs = []
- for spec in extracted_specs:
- try:
- spec.version
- version_specs.append(spec)
- except spack.error.SpecError:
- pass
-
+ version_specs = [x for x in extracted_specs if x.versions.concrete]
+ for spec in version_specs:
+ spec.attach_git_version_lookup()
return version_specs
@@ -2320,11 +2276,11 @@ class SpecBuilder(object):
self._specs[pkg].update_variant_validate(name, value)
def version(self, pkg, version):
- self._specs[pkg].versions = spack.version.ver([version])
+ self._specs[pkg].versions = vn.VersionList([vn.Version(version)])
def node_compiler_version(self, pkg, compiler, version):
self._specs[pkg].compiler = spack.spec.CompilerSpec(compiler)
- self._specs[pkg].compiler.versions = spack.version.VersionList([version])
+ self._specs[pkg].compiler.versions = vn.VersionList([vn.Version(version)])
def node_flag_compiler_default(self, pkg):
self._flag_compiler_defaults.add(pkg)
@@ -2525,8 +2481,8 @@ class SpecBuilder(object):
# concretization process)
for root in self._specs.values():
for spec in root.traverse():
- if isinstance(spec.version, spack.version.GitVersion):
- spec.version.generate_git_lookup(spec.fullname)
+ if isinstance(spec.version, vn.GitVersion):
+ spec.version.attach_git_lookup_from_package(spec.fullname)
return self._specs
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 87f07e88eb..43f73ab991 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -55,7 +55,7 @@ import itertools
import os
import re
import warnings
-from typing import Tuple
+from typing import Tuple, Union
import llnl.util.filesystem as fs
import llnl.util.lang as lang
@@ -145,12 +145,8 @@ color_formats = {
#: ``color_formats.keys()``.
_separators = "[\\%s]" % "\\".join(color_formats.keys())
-#: Versionlist constant so we don't have to build a list
-#: every time we call str()
-_any_version = vn.VersionList([":"])
-
-default_format = "{name}{@version}"
-default_format += "{%compiler.name}{@compiler.version}{compiler_flags}"
+default_format = "{name}{@versions}"
+default_format += "{%compiler.name}{@compiler.versions}{compiler_flags}"
default_format += "{variants}{arch=architecture}"
#: Regular expression to pull spec contents out of clearsigned signature
@@ -581,20 +577,18 @@ class CompilerSpec(object):
elif nargs == 2:
name, version = args
self.name = name
- self.versions = vn.VersionList()
- versions = vn.ver(version)
- self.versions.add(versions)
+ self.versions = vn.VersionList([vn.ver(version)])
else:
raise TypeError("__init__ takes 1 or 2 arguments. (%d given)" % nargs)
def _add_versions(self, version_list):
# If it already has a non-trivial version list, this is an error
- if self.versions and self.versions != vn.VersionList(":"):
+ if self.versions and self.versions != vn.any_version:
# Note: This may be impossible to reach by the current parser
# Keeping it in case the implementation changes.
raise MultipleVersionError(
- "A spec cannot contain multiple version signifiers." " Use a version list instead."
+ "A spec cannot contain multiple version signifiers. Use a version list instead."
)
self.versions = vn.VersionList()
for version in version_list:
@@ -677,9 +671,8 @@ class CompilerSpec(object):
def __str__(self):
out = self.name
- if self.versions and self.versions != _any_version:
- vlist = ",".join(str(v) for v in self.versions)
- out += "@%s" % vlist
+ if self.versions and self.versions != vn.any_version:
+ out += f"@{self.versions}"
return out
def __repr__(self):
@@ -1477,7 +1470,7 @@ class Spec(object):
def _add_versions(self, version_list):
"""Called by the parser to add an allowable version."""
# If it already has a non-trivial version list, this is an error
- if self.versions and self.versions != vn.VersionList(":"):
+ if self.versions and self.versions != vn.any_version:
raise MultipleVersionError(
"A spec cannot contain multiple version signifiers." " Use a version list instead."
)
@@ -2108,7 +2101,7 @@ class Spec(object):
# (and the user spec) have dependencies
new_spec = init_spec.copy()
package_cls = spack.repo.path.get_pkg_class(new_spec.name)
- if change_spec.versions and not change_spec.versions == spack.version.ver(":"):
+ if change_spec.versions and not change_spec.versions == vn.any_version:
new_spec.versions = change_spec.versions
for variant, value in change_spec.variants.items():
if variant in package_cls.variants:
@@ -2289,12 +2282,17 @@ class Spec(object):
"""
# Legacy specfile format
if isinstance(data["spec"], list):
- return SpecfileV1.load(data)
+ spec = SpecfileV1.load(data)
+ elif int(data["spec"]["_meta"]["version"]) == 2:
+ spec = SpecfileV2.load(data)
+ else:
+ spec = SpecfileV3.load(data)
+
+ # Any git version should
+ for s in spec.traverse():
+ s.attach_git_version_lookup()
- specfile_version = int(data["spec"]["_meta"]["version"])
- if specfile_version == 2:
- return SpecfileV2.load(data)
- return SpecfileV3.load(data)
+ return spec
@staticmethod
def from_yaml(stream):
@@ -2823,6 +2821,26 @@ class Spec(object):
return
self._normal = value
self._concrete = value
+ self._validate_version()
+
+ def _validate_version(self):
+ # Specs that were concretized with just a git sha as version, without associated
+ # Spack version, get their Spack version mapped to develop. This should only apply
+ # when reading specs concretized with Spack 0.19 or earlier. Currently Spack always
+ # ensures that GitVersion specs have an associated Spack version.
+ v = self.versions.concrete
+ if not isinstance(v, vn.GitVersion):
+ return
+
+ try:
+ v.ref_version
+ except vn.VersionLookupError:
+ before = self.cformat("{name}{@version}{/hash:7}")
+ v._ref_version = vn.StandardVersion.from_string("develop")
+ tty.debug(
+ f"the git sha of {before} could not be resolved to spack version; "
+ f"it has been replaced by {self.cformat('{name}{@version}{/hash:7}')}."
+ )
def _mark_concrete(self, value=True):
"""Mark this spec and its dependencies as concrete.
@@ -4166,9 +4184,13 @@ class Spec(object):
if part == "arch":
part = "architecture"
elif part == "version":
- # Version requires concrete spec, versions does not
- # when concrete, they print the same thing
- part = "versions"
+ # version (singular) requires a concrete versions list. Avoid
+ # pedantic errors by using versions (plural) when not concrete.
+ # These two are not entirely equivalent for pkg@=1.2.3:
+ # - version prints '1.2.3'
+ # - versions prints '=1.2.3'
+ if not current.versions.concrete:
+ part = "versions"
try:
current = getattr(current, part)
except AttributeError:
@@ -4177,7 +4199,7 @@ class Spec(object):
m += "Spec %s has no attribute %s" % (parent, part)
raise SpecFormatStringError(m)
if isinstance(current, vn.VersionList):
- if current == _any_version:
+ if current == vn.any_version:
# We don't print empty version lists
return
@@ -4195,7 +4217,7 @@ class Spec(object):
col = "="
elif "compiler" in parts or "compiler_flags" in parts:
col = "%"
- elif "version" in parts:
+ elif "version" in parts or "versions" in parts:
col = "@"
# Finally, write the output
@@ -4539,6 +4561,23 @@ class Spec(object):
def __reduce__(self):
return Spec.from_dict, (self.to_dict(hash=ht.process_hash),)
+ def attach_git_version_lookup(self):
+ # Add a git lookup method for GitVersions
+ if not self.name:
+ return
+ for v in self.versions:
+ if isinstance(v, vn.GitVersion) and v._ref_version is None:
+ v.attach_git_lookup_from_package(self.fullname)
+
+
+def parse_with_version_concrete(string: str, compiler: bool = False):
+ """Same as Spec(string), but interprets @x as @=x"""
+ s: Union[CompilerSpec, Spec] = CompilerSpec(string) if compiler else Spec(string)
+ interpreted_version = s.versions.concrete_range_as_version
+ if interpreted_version:
+ s.versions = vn.VersionList([interpreted_version])
+ return s
+
def merge_abstract_anonymous_specs(*abstract_specs: Spec):
"""Merge the abstracts specs passed as input and return the result.
@@ -4580,6 +4619,7 @@ class SpecfileReaderBase:
if "version" in node or "versions" in node:
spec.versions = vn.VersionList.from_dict(node)
+ spec.attach_git_version_lookup()
if "arch" in node:
spec.architecture = ArchSpec.from_dict(node)
@@ -4614,7 +4654,8 @@ class SpecfileReaderBase:
)
# specs read in are concrete unless marked abstract
- spec._concrete = node.get("concrete", True)
+ if node.get("concrete", True):
+ spec._mark_root_concrete()
if "patches" in node:
patches = node["patches"]
diff --git a/lib/spack/spack/spec_list.py b/lib/spack/spack/spec_list.py
index 9efa8cbb50..be70d6d522 100644
--- a/lib/spack/spack/spec_list.py
+++ b/lib/spack/spack/spec_list.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import itertools
+from typing import List
import spack.variant
from spack.error import SpackError
@@ -59,7 +60,7 @@ class SpecList(object):
return self._constraints
@property
- def specs(self):
+ def specs(self) -> List[Spec]:
if self._specs is None:
specs = []
# This could be slightly faster done directly from yaml_list,
@@ -167,6 +168,9 @@ class SpecList(object):
def __getitem__(self, key):
return self.specs[key]
+ def __iter__(self):
+ return iter(self.specs)
+
def _expand_matrix_constraints(matrix_config):
# recurse so we can handle nested matrices
diff --git a/lib/spack/spack/test/architecture.py b/lib/spack/spack/test/architecture.py
index 7aba886375..f00d66ad32 100644
--- a/lib/spack/spack/test/architecture.py
+++ b/lib/spack/spack/test/architecture.py
@@ -142,24 +142,24 @@ def test_optimization_flags(compiler_spec, target_name, expected_flags, config):
@pytest.mark.parametrize(
"compiler,real_version,target_str,expected_flags",
[
- (spack.spec.CompilerSpec("gcc@9.2.0"), None, "haswell", "-march=haswell -mtune=haswell"),
+ (spack.spec.CompilerSpec("gcc@=9.2.0"), None, "haswell", "-march=haswell -mtune=haswell"),
# Check that custom string versions are accepted
(
- spack.spec.CompilerSpec("gcc@10foo"),
+ spack.spec.CompilerSpec("gcc@=10foo"),
"9.2.0",
"icelake",
"-march=icelake-client -mtune=icelake-client",
),
# Check that we run version detection (4.4.0 doesn't support icelake)
(
- spack.spec.CompilerSpec("gcc@4.4.0-special"),
+ spack.spec.CompilerSpec("gcc@=4.4.0-special"),
"9.2.0",
"icelake",
"-march=icelake-client -mtune=icelake-client",
),
# Check that the special case for Apple's clang is treated correctly
# i.e. it won't try to detect the version again
- (spack.spec.CompilerSpec("apple-clang@9.1.0"), None, "x86_64", "-march=x86-64"),
+ (spack.spec.CompilerSpec("apple-clang@=9.1.0"), None, "x86_64", "-march=x86-64"),
],
)
def test_optimization_flags_with_custom_versions(
diff --git a/lib/spack/spack/test/cmd/ci.py b/lib/spack/spack/test/cmd/ci.py
index c32b627b2d..b0588bd1dd 100644
--- a/lib/spack/spack/test/cmd/ci.py
+++ b/lib/spack/spack/test/cmd/ci.py
@@ -281,7 +281,7 @@ spack:
- bootstrap:
- gcc@3.0
specs:
- - dyninst%gcc@3.0
+ - dyninst%gcc@=3.0
mirrors:
some-mirror: https://my.fake.mirror
ci:
@@ -341,7 +341,7 @@ spack:
- bootstrap:
- gcc@3.0
specs:
- - dyninst%gcc@3.0
+ - dyninst%gcc@=3.0
mirrors:
some-mirror: https://my.fake.mirror
ci:
@@ -1527,7 +1527,7 @@ def test_ci_generate_with_workarounds(
"""\
spack:
specs:
- - callpath%gcc@9.5
+ - callpath%gcc@=9.5
mirrors:
some-mirror: https://my.fake.mirror
ci:
@@ -1644,7 +1644,7 @@ def test_ci_generate_bootstrap_prune_dag(
mirror_url = "file://{0}".format(mirror_dir.strpath)
# Install a compiler, because we want to put it in a buildcache
- install_cmd("gcc@12.2.0%gcc@10.2.1")
+ install_cmd("gcc@=12.2.0%gcc@10.2.1")
# Put installed compiler in the buildcache
buildcache_cmd("push", "-u", "-a", "-f", "-d", mirror_dir.strpath, "gcc@12.2.0%gcc@10.2.1")
@@ -1654,12 +1654,12 @@ def test_ci_generate_bootstrap_prune_dag(
monkeypatch.setattr(spack.concretize.Concretizer, "check_for_compiler_existence", False)
spack.config.set("config:install_missing_compilers", True)
- assert CompilerSpec("gcc@12.2.0") not in compilers.all_compiler_specs()
+ assert CompilerSpec("gcc@=12.2.0") not in compilers.all_compiler_specs()
# Configure the mirror where we put that buildcache w/ the compiler
mirror_cmd("add", "test-mirror", mirror_url)
- install_cmd("--no-check-signature", "b%gcc@12.2.0")
+ install_cmd("--no-check-signature", "b%gcc@=12.2.0")
# Put spec built with installed compiler in the buildcache
buildcache_cmd("push", "-u", "-a", "-f", "-d", mirror_dir.strpath, "b%gcc@12.2.0")
@@ -1674,7 +1674,7 @@ def test_ci_generate_bootstrap_prune_dag(
spack:
definitions:
- bootstrap:
- - gcc@12.2.0%gcc@10.2.1
+ - gcc@=12.2.0%gcc@10.2.1
specs:
- b%gcc@12.2.0
mirrors:
diff --git a/lib/spack/spack/test/cmd/config.py b/lib/spack/spack/test/cmd/config.py
index 39cd9fcb7d..c410fcfc76 100644
--- a/lib/spack/spack/test/cmd/config.py
+++ b/lib/spack/spack/test/cmd/config.py
@@ -632,13 +632,13 @@ def test_config_prefer_upstream(
# Make sure only the non-default variants are set.
assert packages["boost"] == {
- "compiler": ["gcc@10.2.1"],
+ "compiler": ["gcc@=10.2.1"],
"variants": "+debug +graph",
"version": ["1.63.0"],
}
- assert packages["dependency-install"] == {"compiler": ["gcc@10.2.1"], "version": ["2.0"]}
+ assert packages["dependency-install"] == {"compiler": ["gcc@=10.2.1"], "version": ["2.0"]}
# Ensure that neither variant gets listed for hdf5, since they conflict
- assert packages["hdf5"] == {"compiler": ["gcc@10.2.1"], "version": ["2.3"]}
+ assert packages["hdf5"] == {"compiler": ["gcc@=10.2.1"], "version": ["2.3"]}
# Make sure a message about the conflicting hdf5's was given.
assert "- hdf5" in output
diff --git a/lib/spack/spack/test/cmd/dev_build.py b/lib/spack/spack/test/cmd/dev_build.py
index 010daa1176..c1aef58740 100644
--- a/lib/spack/spack/test/cmd/dev_build.py
+++ b/lib/spack/spack/test/cmd/dev_build.py
@@ -265,7 +265,7 @@ def test_dev_build_multiple(
# without the environment, the user would need to set dev_path for both the
# root and dependency if they wanted a dev build for both.
leaf_dir = tmpdir.mkdir("leaf")
- leaf_spec = spack.spec.Spec("dev-build-test-install@1.0.0")
+ leaf_spec = spack.spec.Spec("dev-build-test-install@=1.0.0") # non-existing version
leaf_pkg_cls = spack.repo.path.get_pkg_class(leaf_spec.name)
with leaf_dir.as_cwd():
with open(leaf_pkg_cls.filename, "w") as f:
@@ -293,7 +293,7 @@ spack:
develop:
dev-build-test-install:
path: %s
- spec: dev-build-test-install@1.0.0
+ spec: dev-build-test-install@=1.0.0
dev-build-test-dependent:
spec: dev-build-test-dependent@0.0.0
path: %s
diff --git a/lib/spack/spack/test/cmd/develop.py b/lib/spack/spack/test/cmd/develop.py
index bea3aa3b8a..1f77bbfc63 100644
--- a/lib/spack/spack/test/cmd/develop.py
+++ b/lib/spack/spack/test/cmd/develop.py
@@ -48,19 +48,19 @@ class TestDevelop(object):
# develop checks that the path exists
fs.mkdirp(os.path.join(e.path, "mpich"))
develop("--no-clone", "mpich@1.0")
- self.check_develop(e, spack.spec.Spec("mpich@1.0"))
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"))
def test_develop_no_clone(self, tmpdir):
env("create", "test")
with ev.read("test") as e:
develop("--no-clone", "-p", str(tmpdir), "mpich@1.0")
- self.check_develop(e, spack.spec.Spec("mpich@1.0"), str(tmpdir))
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"), str(tmpdir))
def test_develop(self):
env("create", "test")
with ev.read("test") as e:
develop("mpich@1.0")
- self.check_develop(e, spack.spec.Spec("mpich@1.0"))
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"))
def test_develop_no_args(self):
env("create", "test")
@@ -71,20 +71,20 @@ class TestDevelop(object):
# test develop with no args
develop()
- self.check_develop(e, spack.spec.Spec("mpich@1.0"))
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"))
def test_develop_twice(self):
env("create", "test")
with ev.read("test") as e:
develop("mpich@1.0")
- self.check_develop(e, spack.spec.Spec("mpich@1.0"))
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"))
develop("mpich@1.0")
# disk representation isn't updated unless we write
# second develop command doesn't change it, so we don't write
# but we check disk representation
e.write()
- self.check_develop(e, spack.spec.Spec("mpich@1.0"))
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"))
assert len(e.dev_specs) == 1
def test_develop_update_path(self, tmpdir):
@@ -92,7 +92,7 @@ class TestDevelop(object):
with ev.read("test") as e:
develop("mpich@1.0")
develop("-p", str(tmpdir), "mpich@1.0")
- self.check_develop(e, spack.spec.Spec("mpich@1.0"), str(tmpdir))
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"), str(tmpdir))
assert len(e.dev_specs) == 1
def test_develop_update_spec(self):
@@ -100,7 +100,7 @@ class TestDevelop(object):
with ev.read("test") as e:
develop("mpich@1.0")
develop("mpich@2.0")
- self.check_develop(e, spack.spec.Spec("mpich@2.0"))
+ self.check_develop(e, spack.spec.Spec("mpich@=2.0"))
assert len(e.dev_specs) == 1
def test_develop_canonicalize_path(self, monkeypatch, config):
@@ -115,7 +115,7 @@ class TestDevelop(object):
monkeypatch.setattr(spack.stage.Stage, "steal_source", check_path)
develop("-p", path, "mpich@1.0")
- self.check_develop(e, spack.spec.Spec("mpich@1.0"), path)
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"), path)
# Check modifications actually worked
assert spack.spec.Spec("mpich@1.0").concretized().satisfies("dev_path=%s" % abspath)
@@ -142,7 +142,7 @@ class TestDevelop(object):
os.rmdir(abspath)
develop()
- self.check_develop(e, spack.spec.Spec("mpich@1.0"), path)
+ self.check_develop(e, spack.spec.Spec("mpich@=1.0"), path)
# Check modifications actually worked
assert spack.spec.Spec("mpich@1.0").concretized().satisfies("dev_path=%s" % abspath)
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index 0fd5a34dd5..93ce112215 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -691,7 +691,7 @@ spack:
- mpileaks
packages:
mpileaks:
- version: [2.2]
+ version: ["2.2"]
"""
)
with e:
@@ -741,7 +741,7 @@ spack:
"""\
packages:
libelf:
- version: [0.8.10]
+ version: ["0.8.10"]
"""
)
@@ -751,7 +751,7 @@ spack:
"""\
packages:
mpileaks:
- version: [2.2]
+ version: ["2.2"]
"""
)
@@ -770,7 +770,7 @@ def packages_file(tmpdir):
raw_yaml = """
packages:
mpileaks:
- version: [2.2]
+ version: ["2.2"]
"""
filename = tmpdir.ensure("testconfig", "packages.yaml")
filename.write(raw_yaml)
@@ -829,7 +829,7 @@ def test_env_with_included_config_file_url(tmpdir, mutable_empty_config, package
assert len(scopes) == 1
cfg = spack.config.get("packages")
- assert cfg["mpileaks"]["version"] == [2.2]
+ assert cfg["mpileaks"]["version"] == ["2.2"]
def test_env_with_included_config_missing_file(tmpdir, mutable_empty_config):
@@ -895,7 +895,7 @@ def test_env_config_precedence(environment_from_manifest):
spack:
packages:
libelf:
- version: [0.8.12]
+ version: ["0.8.12"]
include:
- ./included-config.yaml
specs:
@@ -907,9 +907,9 @@ spack:
"""\
packages:
mpileaks:
- version: [2.2]
+ version: ["2.2"]
libelf:
- version: [0.8.11]
+ version: ["0.8.11"]
"""
)
@@ -940,7 +940,7 @@ spack:
"""\
packages:
libelf:
- version: [0.8.10] # this should override libelf version below
+ version: ["0.8.10"] # this should override libelf version below
"""
)
@@ -949,9 +949,9 @@ packages:
"""\
packages:
mpileaks:
- version: [2.2]
+ version: ["2.2"]
libelf:
- version: [0.8.12]
+ version: ["0.8.12"]
"""
)
@@ -2647,11 +2647,11 @@ def test_custom_version_concretize_together(tmpdir):
e.unify = True
# Concretize a first time using 'mpich' as the MPI provider
- e.add("hdf5@myversion")
+ e.add("hdf5@=myversion")
e.add("mpich")
e.concretize()
- assert any("hdf5@myversion" in spec for _, spec in e.concretized_specs())
+ assert any(spec.satisfies("hdf5@myversion") for _, spec in e.concretized_specs())
def test_modules_relative_to_views(environment_from_manifest, install_mockery, mock_fetch):
@@ -2751,7 +2751,7 @@ def test_query_develop_specs():
with ev.read("test") as e:
e.add("mpich")
e.add("mpileaks")
- e.develop(Spec("mpich@1"), "here", clone=False)
+ e.develop(Spec("mpich@=1"), "here", clone=False)
assert e.is_develop(Spec("mpich"))
assert not e.is_develop(Spec("mpileaks"))
diff --git a/lib/spack/spack/test/cmd/fetch.py b/lib/spack/spack/test/cmd/fetch.py
index cf4ec911b9..19c5d33585 100644
--- a/lib/spack/spack/test/cmd/fetch.py
+++ b/lib/spack/spack/test/cmd/fetch.py
@@ -30,7 +30,7 @@ def test_fetch_single_spec(tmpdir, mock_archive, mock_stage, mock_fetch, install
@pytest.mark.disable_clean_stage_check
def test_fetch_multiple_specs(tmpdir, mock_archive, mock_stage, mock_fetch, install_mockery):
- SpackCommand("fetch")("mpileaks", "gcc@10.2.0", "python")
+ SpackCommand("fetch")("mpileaks", "gcc@3.0", "python")
def test_fetch_no_argument():
diff --git a/lib/spack/spack/test/cmd/install.py b/lib/spack/spack/test/cmd/install.py
index ed1da4b740..70e73b6412 100644
--- a/lib/spack/spack/test/cmd/install.py
+++ b/lib/spack/spack/test/cmd/install.py
@@ -265,10 +265,7 @@ def test_install_commit(mock_git_version_info, install_mockery, mock_packages, m
)
# Use the earliest commit in the respository
- commit = commits[-1]
- spec = spack.spec.Spec("git-test-commit@%s" % commit)
- spec.concretize()
- print(spec)
+ spec = Spec(f"git-test-commit@{commits[-1]}").concretized()
spec.package.do_install()
# Ensure first commit file contents were written
@@ -942,10 +939,10 @@ def test_compiler_bootstrap(
):
monkeypatch.setattr(spack.concretize.Concretizer, "check_for_compiler_existence", False)
spack.config.set("config:install_missing_compilers", True)
- assert CompilerSpec("gcc@12.0") not in compilers.all_compiler_specs()
+ assert CompilerSpec("gcc@=12.0") not in compilers.all_compiler_specs()
# Test succeeds if it does not raise an error
- install("a%gcc@12.0")
+ install("a%gcc@=12.0")
def test_compiler_bootstrap_from_binary_mirror(
@@ -966,7 +963,7 @@ def test_compiler_bootstrap_from_binary_mirror(
mirror_url = "file://{0}".format(mirror_dir.strpath)
# Install a compiler, because we want to put it in a buildcache
- install("gcc@10.2.0")
+ install("gcc@=10.2.0")
# Put installed compiler in the buildcache
buildcache("push", "-u", "-a", "-f", "-d", mirror_dir.strpath, "gcc@10.2.0")
@@ -976,7 +973,7 @@ def test_compiler_bootstrap_from_binary_mirror(
monkeypatch.setattr(spack.concretize.Concretizer, "check_for_compiler_existence", False)
spack.config.set("config:install_missing_compilers", True)
- assert CompilerSpec("gcc@10.2.0") not in compilers.all_compiler_specs()
+ assert CompilerSpec("gcc@=10.2.0") not in compilers.all_compiler_specs()
# Configure the mirror where we put that buildcache w/ the compiler
mirror("add", "test-mirror", mirror_url)
@@ -984,7 +981,7 @@ def test_compiler_bootstrap_from_binary_mirror(
# Now make sure that when the compiler is installed from binary mirror,
# it also gets configured as a compiler. Test succeeds if it does not
# raise an error
- install("--no-check-signature", "--cache-only", "--only", "dependencies", "b%gcc@10.2.0")
+ install("--no-check-signature", "--cache-only", "--only", "dependencies", "b%gcc@=10.2.0")
install("--no-cache", "--only", "package", "b%gcc@10.2.0")
@@ -1000,11 +997,11 @@ def test_compiler_bootstrap_already_installed(
monkeypatch.setattr(spack.concretize.Concretizer, "check_for_compiler_existence", False)
spack.config.set("config:install_missing_compilers", True)
- assert CompilerSpec("gcc@12.0") not in compilers.all_compiler_specs()
+ assert CompilerSpec("gcc@=12.0") not in compilers.all_compiler_specs()
# Test succeeds if it does not raise an error
- install("gcc@12.0")
- install("a%gcc@12.0")
+ install("gcc@=12.0")
+ install("a%gcc@=12.0")
def test_install_fails_no_args(tmpdir):
diff --git a/lib/spack/spack/test/cmd/spec.py b/lib/spack/spack/test/cmd/spec.py
index 6ced00c0bc..0e454bf794 100644
--- a/lib/spack/spack/test/cmd/spec.py
+++ b/lib/spack/spack/test/cmd/spec.py
@@ -24,12 +24,12 @@ spec = SpackCommand("spec")
def test_spec():
output = spec("mpileaks")
- assert "mpileaks@2.3" in output
- assert "callpath@1.0" in output
- assert "dyninst@8.2" in output
- assert "libdwarf@20130729" in output
- assert "libelf@0.8.1" in output
- assert "mpich@3.0.4" in output
+ assert "mpileaks@=2.3" in output
+ assert "callpath@=1.0" in output
+ assert "dyninst@=8.2" in output
+ assert "libdwarf@=20130729" in output
+ assert "libelf@=0.8.1" in output
+ assert "mpich@=3.0.4" in output
def test_spec_concretizer_args(mutable_config, mutable_database):
@@ -196,12 +196,12 @@ def test_env_aware_spec(mutable_mock_env_path):
with env:
output = spec()
- assert "mpileaks@2.3" in output
- assert "callpath@1.0" in output
- assert "dyninst@8.2" in output
- assert "libdwarf@20130729" in output
- assert "libelf@0.8.1" in output
- assert "mpich@3.0.4" in output
+ assert "mpileaks@=2.3" in output
+ assert "callpath@=1.0" in output
+ assert "dyninst@=8.2" in output
+ assert "libdwarf@=20130729" in output
+ assert "libelf@=0.8.1" in output
+ assert "mpich@=3.0.4" in output
@pytest.mark.parametrize(
diff --git a/lib/spack/spack/test/cmd/stage.py b/lib/spack/spack/test/cmd/stage.py
index 27e6abc1c7..9fff89afb4 100644
--- a/lib/spack/spack/test/cmd/stage.py
+++ b/lib/spack/spack/test/cmd/stage.py
@@ -89,7 +89,7 @@ def test_stage_with_env_inside_env(mutable_mock_env_path, monkeypatch):
monkeypatch.setattr(spack.package_base.PackageBase, "do_stage", fake_stage)
e = ev.create("test")
- e.add("mpileaks@100.100")
+ e.add("mpileaks@=100.100")
e.concretize()
with e:
@@ -101,7 +101,7 @@ def test_stage_full_env(mutable_mock_env_path, monkeypatch):
"""Verify that stage filters specs in environment."""
e = ev.create("test")
- e.add("mpileaks@100.100")
+ e.add("mpileaks@=100.100")
e.concretize()
# list all the package names that should be staged
diff --git a/lib/spack/spack/test/compilers/basics.py b/lib/spack/spack/test/compilers/basics.py
index edd2f3acc9..66c5fe451f 100644
--- a/lib/spack/spack/test/compilers/basics.py
+++ b/lib/spack/spack/test/compilers/basics.py
@@ -58,8 +58,7 @@ def test_multiple_conflicting_compiler_definitions(mutable_config):
mutable_config.update_config("compilers", compiler_config)
arch_spec = spack.spec.ArchSpec(("test", "test", "test"))
- cspec = compiler_config[0]["compiler"]["spec"]
- cmp = compilers.compiler_for_spec(cspec, arch_spec)
+ cmp = compilers.compiler_for_spec("clang@=0.0.0", arch_spec)
assert cmp.f77 == "f77"
@@ -78,7 +77,7 @@ def test_get_compiler_duplicates(config):
def test_all_compilers(config):
all_compilers = compilers.all_compilers()
- filtered = [x for x in all_compilers if str(x.spec) == "clang@3.3"]
+ filtered = [x for x in all_compilers if str(x.spec) == "clang@=3.3"]
filtered = [x for x in filtered if x.operating_system == "SuSE11"]
assert len(filtered) == 1
@@ -525,135 +524,135 @@ def test_gcc_flags():
def test_intel_flags():
- supported_flag_test("openmp_flag", "-openmp", "intel@15.0")
- supported_flag_test("openmp_flag", "-qopenmp", "intel@16.0")
- unsupported_flag_test("cxx11_flag", "intel@11.0")
- supported_flag_test("cxx11_flag", "-std=c++0x", "intel@12.0")
- supported_flag_test("cxx11_flag", "-std=c++11", "intel@13")
- unsupported_flag_test("cxx14_flag", "intel@14.0")
- supported_flag_test("cxx14_flag", "-std=c++1y", "intel@15.0")
- supported_flag_test("cxx14_flag", "-std=c++14", "intel@15.0.2")
- unsupported_flag_test("c99_flag", "intel@11.0")
- supported_flag_test("c99_flag", "-std=c99", "intel@12.0")
- unsupported_flag_test("c11_flag", "intel@15.0")
- supported_flag_test("c11_flag", "-std=c1x", "intel@16.0")
- supported_flag_test("cc_pic_flag", "-fPIC", "intel@1.0")
- supported_flag_test("cxx_pic_flag", "-fPIC", "intel@1.0")
- supported_flag_test("f77_pic_flag", "-fPIC", "intel@1.0")
- supported_flag_test("fc_pic_flag", "-fPIC", "intel@1.0")
- supported_flag_test("stdcxx_libs", ("-cxxlib",), "intel@1.0")
- supported_flag_test("debug_flags", ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"], "intel@1.0")
+ supported_flag_test("openmp_flag", "-openmp", "intel@=15.0")
+ supported_flag_test("openmp_flag", "-qopenmp", "intel@=16.0")
+ unsupported_flag_test("cxx11_flag", "intel@=11.0")
+ supported_flag_test("cxx11_flag", "-std=c++0x", "intel@=12.0")
+ supported_flag_test("cxx11_flag", "-std=c++11", "intel@=13")
+ unsupported_flag_test("cxx14_flag", "intel@=14.0")
+ supported_flag_test("cxx14_flag", "-std=c++1y", "intel@=15.0")
+ supported_flag_test("cxx14_flag", "-std=c++14", "intel@=15.0.2")
+ unsupported_flag_test("c99_flag", "intel@=11.0")
+ supported_flag_test("c99_flag", "-std=c99", "intel@=12.0")
+ unsupported_flag_test("c11_flag", "intel@=15.0")
+ supported_flag_test("c11_flag", "-std=c1x", "intel@=16.0")
+ supported_flag_test("cc_pic_flag", "-fPIC", "intel@=1.0")
+ supported_flag_test("cxx_pic_flag", "-fPIC", "intel@=1.0")
+ supported_flag_test("f77_pic_flag", "-fPIC", "intel@=1.0")
+ supported_flag_test("fc_pic_flag", "-fPIC", "intel@=1.0")
+ supported_flag_test("stdcxx_libs", ("-cxxlib",), "intel@=1.0")
+ supported_flag_test("debug_flags", ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"], "intel@=1.0")
supported_flag_test(
- "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"], "intel@1.0"
+ "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"], "intel@=1.0"
)
def test_oneapi_flags():
- supported_flag_test("openmp_flag", "-fiopenmp", "oneapi@2020.8.0.0827")
- supported_flag_test("cxx11_flag", "-std=c++11", "oneapi@2020.8.0.0827")
- supported_flag_test("cxx14_flag", "-std=c++14", "oneapi@2020.8.0.0827")
- supported_flag_test("c99_flag", "-std=c99", "oneapi@2020.8.0.0827")
- supported_flag_test("c11_flag", "-std=c1x", "oneapi@2020.8.0.0827")
- supported_flag_test("cc_pic_flag", "-fPIC", "oneapi@2020.8.0.0827")
- supported_flag_test("cxx_pic_flag", "-fPIC", "oneapi@2020.8.0.0827")
- supported_flag_test("f77_pic_flag", "-fPIC", "oneapi@2020.8.0.0827")
- supported_flag_test("fc_pic_flag", "-fPIC", "oneapi@2020.8.0.0827")
- supported_flag_test("stdcxx_libs", ("-cxxlib",), "oneapi@2020.8.0.0827")
+ supported_flag_test("openmp_flag", "-fiopenmp", "oneapi@=2020.8.0.0827")
+ supported_flag_test("cxx11_flag", "-std=c++11", "oneapi@=2020.8.0.0827")
+ supported_flag_test("cxx14_flag", "-std=c++14", "oneapi@=2020.8.0.0827")
+ supported_flag_test("c99_flag", "-std=c99", "oneapi@=2020.8.0.0827")
+ supported_flag_test("c11_flag", "-std=c1x", "oneapi@=2020.8.0.0827")
+ supported_flag_test("cc_pic_flag", "-fPIC", "oneapi@=2020.8.0.0827")
+ supported_flag_test("cxx_pic_flag", "-fPIC", "oneapi@=2020.8.0.0827")
+ supported_flag_test("f77_pic_flag", "-fPIC", "oneapi@=2020.8.0.0827")
+ supported_flag_test("fc_pic_flag", "-fPIC", "oneapi@=2020.8.0.0827")
+ supported_flag_test("stdcxx_libs", ("-cxxlib",), "oneapi@=2020.8.0.0827")
supported_flag_test(
- "debug_flags", ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"], "oneapi@2020.8.0.0827"
+ "debug_flags", ["-debug", "-g", "-g0", "-g1", "-g2", "-g3"], "oneapi@=2020.8.0.0827"
)
supported_flag_test(
- "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"], "oneapi@2020.8.0.0827"
+ "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-Ofast", "-Os"], "oneapi@=2020.8.0.0827"
)
def test_nag_flags():
- supported_flag_test("openmp_flag", "-openmp", "nag@1.0")
- supported_flag_test("cxx11_flag", "-std=c++11", "nag@1.0")
- supported_flag_test("cc_pic_flag", "-fPIC", "nag@1.0")
- supported_flag_test("cxx_pic_flag", "-fPIC", "nag@1.0")
- supported_flag_test("f77_pic_flag", "-PIC", "nag@1.0")
- supported_flag_test("fc_pic_flag", "-PIC", "nag@1.0")
- supported_flag_test("cc_rpath_arg", "-Wl,-rpath,", "nag@1.0")
- supported_flag_test("cxx_rpath_arg", "-Wl,-rpath,", "nag@1.0")
- supported_flag_test("f77_rpath_arg", "-Wl,-Wl,,-rpath,,", "nag@1.0")
- supported_flag_test("fc_rpath_arg", "-Wl,-Wl,,-rpath,,", "nag@1.0")
- supported_flag_test("linker_arg", "-Wl,-Wl,,", "nag@1.0")
- supported_flag_test("debug_flags", ["-g", "-gline", "-g90"], "nag@1.0")
- supported_flag_test("opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"], "nag@1.0")
+ supported_flag_test("openmp_flag", "-openmp", "nag@=1.0")
+ supported_flag_test("cxx11_flag", "-std=c++11", "nag@=1.0")
+ supported_flag_test("cc_pic_flag", "-fPIC", "nag@=1.0")
+ supported_flag_test("cxx_pic_flag", "-fPIC", "nag@=1.0")
+ supported_flag_test("f77_pic_flag", "-PIC", "nag@=1.0")
+ supported_flag_test("fc_pic_flag", "-PIC", "nag@=1.0")
+ supported_flag_test("cc_rpath_arg", "-Wl,-rpath,", "nag@=1.0")
+ supported_flag_test("cxx_rpath_arg", "-Wl,-rpath,", "nag@=1.0")
+ supported_flag_test("f77_rpath_arg", "-Wl,-Wl,,-rpath,,", "nag@=1.0")
+ supported_flag_test("fc_rpath_arg", "-Wl,-Wl,,-rpath,,", "nag@=1.0")
+ supported_flag_test("linker_arg", "-Wl,-Wl,,", "nag@=1.0")
+ supported_flag_test("debug_flags", ["-g", "-gline", "-g90"], "nag@=1.0")
+ supported_flag_test("opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"], "nag@=1.0")
def test_nvhpc_flags():
- supported_flag_test("openmp_flag", "-mp", "nvhpc@20.9")
- supported_flag_test("cxx11_flag", "--c++11", "nvhpc@20.9")
- supported_flag_test("cxx14_flag", "--c++14", "nvhpc@20.9")
- supported_flag_test("cxx17_flag", "--c++17", "nvhpc@20.9")
- supported_flag_test("c99_flag", "-c99", "nvhpc@20.9")
- supported_flag_test("c11_flag", "-c11", "nvhpc@20.9")
- supported_flag_test("cc_pic_flag", "-fpic", "nvhpc@20.9")
- supported_flag_test("cxx_pic_flag", "-fpic", "nvhpc@20.9")
- supported_flag_test("f77_pic_flag", "-fpic", "nvhpc@20.9")
- supported_flag_test("fc_pic_flag", "-fpic", "nvhpc@20.9")
- supported_flag_test("debug_flags", ["-g", "-gopt"], "nvhpc@20.9")
- supported_flag_test("opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"], "nvhpc@20.9")
- supported_flag_test("stdcxx_libs", ("-c++libs",), "nvhpc@20.9")
+ supported_flag_test("openmp_flag", "-mp", "nvhpc@=20.9")
+ supported_flag_test("cxx11_flag", "--c++11", "nvhpc@=20.9")
+ supported_flag_test("cxx14_flag", "--c++14", "nvhpc@=20.9")
+ supported_flag_test("cxx17_flag", "--c++17", "nvhpc@=20.9")
+ supported_flag_test("c99_flag", "-c99", "nvhpc@=20.9")
+ supported_flag_test("c11_flag", "-c11", "nvhpc@=20.9")
+ supported_flag_test("cc_pic_flag", "-fpic", "nvhpc@=20.9")
+ supported_flag_test("cxx_pic_flag", "-fpic", "nvhpc@=20.9")
+ supported_flag_test("f77_pic_flag", "-fpic", "nvhpc@=20.9")
+ supported_flag_test("fc_pic_flag", "-fpic", "nvhpc@=20.9")
+ supported_flag_test("debug_flags", ["-g", "-gopt"], "nvhpc@=20.9")
+ supported_flag_test("opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"], "nvhpc@=20.9")
+ supported_flag_test("stdcxx_libs", ("-c++libs",), "nvhpc@=20.9")
def test_pgi_flags():
- supported_flag_test("openmp_flag", "-mp", "pgi@1.0")
- supported_flag_test("cxx11_flag", "-std=c++11", "pgi@1.0")
- unsupported_flag_test("c99_flag", "pgi@12.9")
- supported_flag_test("c99_flag", "-c99", "pgi@12.10")
- unsupported_flag_test("c11_flag", "pgi@15.2")
- supported_flag_test("c11_flag", "-c11", "pgi@15.3")
- supported_flag_test("cc_pic_flag", "-fpic", "pgi@1.0")
- supported_flag_test("cxx_pic_flag", "-fpic", "pgi@1.0")
- supported_flag_test("f77_pic_flag", "-fpic", "pgi@1.0")
- supported_flag_test("fc_pic_flag", "-fpic", "pgi@1.0")
- supported_flag_test("stdcxx_libs", ("-pgc++libs",), "pgi@1.0")
- supported_flag_test("debug_flags", ["-g", "-gopt"], "pgi@1.0")
- supported_flag_test("opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"], "pgi@1.0")
+ supported_flag_test("openmp_flag", "-mp", "pgi@=1.0")
+ supported_flag_test("cxx11_flag", "-std=c++11", "pgi@=1.0")
+ unsupported_flag_test("c99_flag", "pgi@=12.9")
+ supported_flag_test("c99_flag", "-c99", "pgi@=12.10")
+ unsupported_flag_test("c11_flag", "pgi@=15.2")
+ supported_flag_test("c11_flag", "-c11", "pgi@=15.3")
+ supported_flag_test("cc_pic_flag", "-fpic", "pgi@=1.0")
+ supported_flag_test("cxx_pic_flag", "-fpic", "pgi@=1.0")
+ supported_flag_test("f77_pic_flag", "-fpic", "pgi@=1.0")
+ supported_flag_test("fc_pic_flag", "-fpic", "pgi@=1.0")
+ supported_flag_test("stdcxx_libs", ("-pgc++libs",), "pgi@=1.0")
+ supported_flag_test("debug_flags", ["-g", "-gopt"], "pgi@=1.0")
+ supported_flag_test("opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4"], "pgi@=1.0")
def test_xl_flags():
- supported_flag_test("openmp_flag", "-qsmp=omp", "xl@1.0")
- unsupported_flag_test("cxx11_flag", "xl@13.0")
- supported_flag_test("cxx11_flag", "-qlanglvl=extended0x", "xl@13.1")
- unsupported_flag_test("c99_flag", "xl@10.0")
- supported_flag_test("c99_flag", "-qlanglvl=extc99", "xl@10.1")
- supported_flag_test("c99_flag", "-std=gnu99", "xl@13.1.1")
- unsupported_flag_test("c11_flag", "xl@12.0")
- supported_flag_test("c11_flag", "-qlanglvl=extc1x", "xl@12.1")
- supported_flag_test("c11_flag", "-std=gnu11", "xl@13.1.2")
- supported_flag_test("cc_pic_flag", "-qpic", "xl@1.0")
- supported_flag_test("cxx_pic_flag", "-qpic", "xl@1.0")
- supported_flag_test("f77_pic_flag", "-qpic", "xl@1.0")
- supported_flag_test("fc_pic_flag", "-qpic", "xl@1.0")
- supported_flag_test("fflags", "-qzerosize", "xl@1.0")
- supported_flag_test("debug_flags", ["-g", "-g0", "-g1", "-g2", "-g8", "-g9"], "xl@1.0")
+ supported_flag_test("openmp_flag", "-qsmp=omp", "xl@=1.0")
+ unsupported_flag_test("cxx11_flag", "xl@=13.0")
+ supported_flag_test("cxx11_flag", "-qlanglvl=extended0x", "xl@=13.1")
+ unsupported_flag_test("c99_flag", "xl@=10.0")
+ supported_flag_test("c99_flag", "-qlanglvl=extc99", "xl@=10.1")
+ supported_flag_test("c99_flag", "-std=gnu99", "xl@=13.1.1")
+ unsupported_flag_test("c11_flag", "xl@=12.0")
+ supported_flag_test("c11_flag", "-qlanglvl=extc1x", "xl@=12.1")
+ supported_flag_test("c11_flag", "-std=gnu11", "xl@=13.1.2")
+ supported_flag_test("cc_pic_flag", "-qpic", "xl@=1.0")
+ supported_flag_test("cxx_pic_flag", "-qpic", "xl@=1.0")
+ supported_flag_test("f77_pic_flag", "-qpic", "xl@=1.0")
+ supported_flag_test("fc_pic_flag", "-qpic", "xl@=1.0")
+ supported_flag_test("fflags", "-qzerosize", "xl@=1.0")
+ supported_flag_test("debug_flags", ["-g", "-g0", "-g1", "-g2", "-g8", "-g9"], "xl@=1.0")
supported_flag_test(
- "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4", "-O5", "-Ofast"], "xl@1.0"
+ "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4", "-O5", "-Ofast"], "xl@=1.0"
)
def test_xl_r_flags():
- supported_flag_test("openmp_flag", "-qsmp=omp", "xl_r@1.0")
- unsupported_flag_test("cxx11_flag", "xl_r@13.0")
- supported_flag_test("cxx11_flag", "-qlanglvl=extended0x", "xl_r@13.1")
- unsupported_flag_test("c99_flag", "xl_r@10.0")
- supported_flag_test("c99_flag", "-qlanglvl=extc99", "xl_r@10.1")
- supported_flag_test("c99_flag", "-std=gnu99", "xl_r@13.1.1")
- unsupported_flag_test("c11_flag", "xl_r@12.0")
- supported_flag_test("c11_flag", "-qlanglvl=extc1x", "xl_r@12.1")
- supported_flag_test("c11_flag", "-std=gnu11", "xl_r@13.1.2")
- supported_flag_test("cc_pic_flag", "-qpic", "xl_r@1.0")
- supported_flag_test("cxx_pic_flag", "-qpic", "xl_r@1.0")
- supported_flag_test("f77_pic_flag", "-qpic", "xl_r@1.0")
- supported_flag_test("fc_pic_flag", "-qpic", "xl_r@1.0")
- supported_flag_test("fflags", "-qzerosize", "xl_r@1.0")
- supported_flag_test("debug_flags", ["-g", "-g0", "-g1", "-g2", "-g8", "-g9"], "xl@1.0")
+ supported_flag_test("openmp_flag", "-qsmp=omp", "xl_r@=1.0")
+ unsupported_flag_test("cxx11_flag", "xl_r@=13.0")
+ supported_flag_test("cxx11_flag", "-qlanglvl=extended0x", "xl_r@=13.1")
+ unsupported_flag_test("c99_flag", "xl_r@=10.0")
+ supported_flag_test("c99_flag", "-qlanglvl=extc99", "xl_r@=10.1")
+ supported_flag_test("c99_flag", "-std=gnu99", "xl_r@=13.1.1")
+ unsupported_flag_test("c11_flag", "xl_r@=12.0")
+ supported_flag_test("c11_flag", "-qlanglvl=extc1x", "xl_r@=12.1")
+ supported_flag_test("c11_flag", "-std=gnu11", "xl_r@=13.1.2")
+ supported_flag_test("cc_pic_flag", "-qpic", "xl_r@=1.0")
+ supported_flag_test("cxx_pic_flag", "-qpic", "xl_r@=1.0")
+ supported_flag_test("f77_pic_flag", "-qpic", "xl_r@=1.0")
+ supported_flag_test("fc_pic_flag", "-qpic", "xl_r@=1.0")
+ supported_flag_test("fflags", "-qzerosize", "xl_r@=1.0")
+ supported_flag_test("debug_flags", ["-g", "-g0", "-g1", "-g2", "-g8", "-g9"], "xl@=1.0")
supported_flag_test(
- "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4", "-O5", "-Ofast"], "xl@1.0"
+ "opt_flags", ["-O", "-O0", "-O1", "-O2", "-O3", "-O4", "-O5", "-Ofast"], "xl@=1.0"
)
@@ -662,8 +661,8 @@ def test_xl_r_flags():
[("gcc@4.7.2", False), ("clang@3.3", False), ("clang@8.0.0", True)],
)
def test_detecting_mixed_toolchains(compiler_spec, expected_result, config):
- compiler = spack.compilers.compilers_for_spec(compiler_spec).pop()
- assert spack.compilers.is_mixed_toolchain(compiler) is expected_result
+ compiler = compilers.compilers_for_spec(compiler_spec).pop()
+ assert compilers.is_mixed_toolchain(compiler) is expected_result
@pytest.mark.regression("14798,13733")
@@ -692,7 +691,7 @@ def test_raising_if_compiler_target_is_over_specific(config):
with spack.config.override("compilers", compilers):
cfg = spack.compilers.get_compiler_config()
with pytest.raises(ValueError):
- spack.compilers.get_compilers(cfg, "gcc@9.0.1", arch_spec)
+ spack.compilers.get_compilers(cfg, spack.spec.CompilerSpec("gcc@9.0.1"), arch_spec)
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
@@ -844,7 +843,7 @@ def test_apple_clang_setup_environment(mock_executable, monkeypatch):
apple_clang_cls = spack.compilers.class_for_compiler_name("apple-clang")
compiler = apple_clang_cls(
- spack.spec.CompilerSpec("apple-clang@11.0.0"),
+ spack.spec.CompilerSpec("apple-clang@=11.0.0"),
"catalina",
"x86_64",
["/usr/bin/clang", "/usr/bin/clang++", None, None],
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 8671e5288f..8ef9b558b1 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -15,6 +15,7 @@ import llnl.util.lang
import spack.compilers
import spack.concretize
+import spack.config
import spack.detection
import spack.error
import spack.hash_types as ht
@@ -22,7 +23,7 @@ import spack.platforms
import spack.repo
import spack.variant as vt
from spack.concretize import find_spec
-from spack.spec import Spec
+from spack.spec import CompilerSpec, Spec
from spack.version import ver
@@ -148,10 +149,10 @@ class Root(Package):
homepage = "http://www.example.com"
url = "http://www.example.com/root-1.0.tar.gz"
- version(1.0, sha256='abcde')
- depends_on('changing')
+ version("1.0", sha256="abcde")
+ depends_on("changing")
- conflicts('changing~foo')
+ conflicts("changing~foo")
"""
packages_dir.join("root", "package.py").write(root_pkg_str, ensure=True)
@@ -162,17 +163,17 @@ class Changing(Package):
{% if not delete_version %}
- version(1.0, sha256='abcde')
+ version("1.0", sha256="abcde")
{% endif %}
- version(0.9, sha256='abcde')
+ version("0.9", sha256="abcde")
{% if not delete_variant %}
- variant('fee', default=True, description='nope')
+ variant("fee", default=True, description="nope")
{% endif %}
- variant('foo', default=True, description='nope')
+ variant("foo", default=True, description="nope")
{% if add_variant %}
- variant('fum', default=True, description='nope')
- variant('fum2', default=True, description='nope')
+ variant("fum", default=True, description="nope")
+ variant("fum2", default=True, description="nope")
{% endif %}
"""
@@ -228,7 +229,7 @@ class TestConcretize(object):
check_concretize(spec)
def test_concretize_mention_build_dep(self):
- spec = check_concretize("cmake-client ^cmake@3.21.3")
+ spec = check_concretize("cmake-client ^cmake@=3.21.3")
# Check parent's perspective of child
to_dependencies = spec.edges_to_dependencies(name="cmake")
@@ -243,9 +244,9 @@ class TestConcretize(object):
def test_concretize_preferred_version(self):
spec = check_concretize("python")
- assert spec.versions == ver("2.7.11")
+ assert spec.version == ver("=2.7.11")
spec = check_concretize("python@3.5.1")
- assert spec.versions == ver("3.5.1")
+ assert spec.version == ver("=3.5.1")
def test_concretize_with_restricted_virtual(self):
check_concretize("mpileaks ^mpich2")
@@ -280,10 +281,10 @@ class TestConcretize(object):
def test_concretize_enable_disable_compiler_existence_check(self):
with spack.concretize.enable_compiler_existence_check():
with pytest.raises(spack.concretize.UnavailableCompilerVersionError):
- check_concretize("dttop %gcc@100.100")
+ check_concretize("dttop %gcc@=100.100")
with spack.concretize.disable_compiler_existence_check():
- spec = check_concretize("dttop %gcc@100.100")
+ spec = check_concretize("dttop %gcc@=100.100")
assert spec.satisfies("%gcc@100.100")
assert spec["dtlink3"].satisfies("%gcc@100.100")
@@ -335,7 +336,7 @@ class TestConcretize(object):
spec = Spec("a %clang@12.2.0 platform=test os=fe target=fe")
# Get the compiler that matches the spec (
- compiler = spack.compilers.compiler_for_spec("clang@12.2.0", spec.architecture)
+ compiler = spack.compilers.compiler_for_spec("clang@=12.2.0", spec.architecture)
# Clear cache for compiler config since it has its own cache mechanism outside of config
spack.compilers._cache_config_file = []
@@ -479,7 +480,7 @@ class TestConcretize(object):
def test_no_matching_compiler_specs(self, mock_low_high_config):
# only relevant when not building compilers as needed
with spack.concretize.enable_compiler_existence_check():
- s = Spec("a %gcc@0.0.0")
+ s = Spec("a %gcc@=0.0.0")
with pytest.raises(spack.concretize.UnavailableCompilerVersionError):
s.concretize()
@@ -748,10 +749,10 @@ class TestConcretize(object):
@pytest.mark.parametrize(
"spec, best_achievable",
[
- ("mpileaks%gcc@4.4.7 ^dyninst@10.2.1 target=x86_64:", "core2"),
- ("mpileaks%gcc@4.8 target=x86_64:", "haswell"),
- ("mpileaks%gcc@5.3.0 target=x86_64:", "broadwell"),
- ("mpileaks%apple-clang@5.1.0 target=x86_64:", "x86_64"),
+ ("mpileaks%gcc@=4.4.7 ^dyninst@=10.2.1 target=x86_64:", "core2"),
+ ("mpileaks%gcc@=4.8 target=x86_64:", "haswell"),
+ ("mpileaks%gcc@=5.3.0 target=x86_64:", "broadwell"),
+ ("mpileaks%apple-clang@=5.1.0 target=x86_64:", "x86_64"),
],
)
@pytest.mark.regression("13361", "20537")
@@ -764,18 +765,15 @@ class TestConcretize(object):
s = Spec(spec).concretized()
assert str(s.architecture.target) == str(expected)
- @pytest.mark.regression("8735,14730")
def test_compiler_version_matches_any_entry_in_compilers_yaml(self):
- # Ensure that a concrete compiler with different compiler version
- # doesn't match (here it's 10.2 vs. 10.2.1)
- with pytest.raises(spack.concretize.UnavailableCompilerVersionError):
- s = Spec("mpileaks %gcc@10.2")
- s.concretize()
+ # The behavior here has changed since #8735 / #14730. Now %gcc@10.2 is an abstract
+ # compiler spec, and it should first find a matching compiler gcc@=10.2.1
+ assert Spec("mpileaks %gcc@10.2").concretized().compiler == CompilerSpec("gcc@=10.2.1")
+ assert Spec("mpileaks %gcc@10.2:").concretized().compiler == CompilerSpec("gcc@=10.2.1")
- # An abstract compiler with a version list could resolve to 4.5.0
- s = Spec("mpileaks %gcc@10.2:")
- s.concretize()
- assert str(s.compiler.version) == "10.2.1"
+ # This compiler does not exist
+ with pytest.raises(spack.concretize.UnavailableCompilerVersionError):
+ Spec("mpileaks %gcc@=10.2").concretized()
def test_concretize_anonymous(self):
with pytest.raises(spack.error.SpackError):
@@ -1158,7 +1156,7 @@ class TestConcretize(object):
else "d0df7988457ec999c148a4a2af25ce831bfaad13954ba18a4446374cb0aef55e"
)
localpatch = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
- spec = spack.spec.Spec("conditionally-patch-dependency+jasper")
+ spec = Spec("conditionally-patch-dependency+jasper")
spec.concretize()
assert (uuidpatch, localpatch) == spec["libelf"].variants["patches"].value
@@ -1411,14 +1409,14 @@ class TestConcretize(object):
# a transitive dependency with a multi-valued variant, that old
# version was preferred because of the order of our optimization
# criteria.
- s = spack.spec.Spec("root").concretized()
+ s = Spec("root").concretized()
assert s["gmt"].satisfies("@2.0")
@pytest.mark.regression("24205")
def test_provider_must_meet_requirements(self):
# A package can be a provider of a virtual only if the underlying
# requirements are met.
- s = spack.spec.Spec("unsat-virtual-dependency")
+ s = Spec("unsat-virtual-dependency")
with pytest.raises((RuntimeError, spack.error.UnsatisfiableSpecError)):
s.concretize()
@@ -1432,7 +1430,7 @@ class TestConcretize(object):
# root@1.0 <- middle@1.0 <- leaf@1.0
#
# and "blas" is pulled in only by newer versions of "leaf"
- s = spack.spec.Spec("root-adds-virtual").concretized()
+ s = Spec("root-adds-virtual").concretized()
assert s["leaf-adds-virtual"].satisfies("@2.0")
assert "blas" in s
@@ -1440,12 +1438,12 @@ class TestConcretize(object):
def test_versions_in_virtual_dependencies(self):
# Ensure that a package that needs a given version of a virtual
# package doesn't end up using a later implementation
- s = spack.spec.Spec("hpcviewer@2019.02").concretized()
+ s = Spec("hpcviewer@2019.02").concretized()
assert s["java"].satisfies("virtual-with-versions@1.8.0")
@pytest.mark.regression("26866")
def test_non_default_provider_of_multiple_virtuals(self):
- s = spack.spec.Spec("many-virtual-consumer ^low-priority-provider").concretized()
+ s = Spec("many-virtual-consumer ^low-priority-provider").concretized()
assert s["mpi"].name == "low-priority-provider"
assert s["lapack"].name == "low-priority-provider"
@@ -1471,7 +1469,7 @@ class TestConcretize(object):
# like additional constraints being added to concrete specs in
# the answer set produced by clingo.
with spack.config.override("concretizer:reuse", True):
- s = spack.spec.Spec(spec_str).concretized()
+ s = Spec(spec_str).concretized()
assert s.installed is expect_installed
assert s.satisfies(spec_str)
@@ -1485,12 +1483,12 @@ class TestConcretize(object):
# to have +allow-gcc set to be concretized with %gcc and clingo is not allowed
# to change the default ~allow-gcc
with pytest.raises(spack.error.SpackError):
- spack.spec.Spec("sticky-variant %gcc").concretized()
+ Spec("sticky-variant %gcc").concretized()
- s = spack.spec.Spec("sticky-variant+allow-gcc %gcc").concretized()
+ s = Spec("sticky-variant+allow-gcc %gcc").concretized()
assert s.satisfies("%gcc") and s.satisfies("+allow-gcc")
- s = spack.spec.Spec("sticky-variant %clang").concretized()
+ s = Spec("sticky-variant %clang").concretized()
assert s.satisfies("%clang") and s.satisfies("~allow-gcc")
def test_do_not_invent_new_concrete_versions_unless_necessary(self):
@@ -1499,10 +1497,10 @@ class TestConcretize(object):
# ensure we select a known satisfying version rather than creating
# a new '2.7' version.
- assert ver("2.7.11") == Spec("python@2.7").concretized().version
+ assert ver("=2.7.11") == Spec("python@2.7").concretized().version
# Here there is no known satisfying version - use the one on the spec.
- assert ver("2.7.21") == Spec("python@2.7.21").concretized().version
+ assert ver("=2.7.21") == Spec("python@=2.7.21").concretized().version
@pytest.mark.parametrize(
"spec_str,valid",
@@ -1663,7 +1661,7 @@ class TestConcretize(object):
if spack.config.get("config:concretizer") == "original":
pytest.skip("Original concretizer cannot concretize in rounds")
- specs = [spack.spec.Spec(s) for s in specs]
+ specs = [Spec(s) for s in specs]
solver = spack.solver.asp.Solver()
solver.reuse = False
concrete_specs = set()
@@ -1710,7 +1708,7 @@ class TestConcretize(object):
if spack.config.get("config:concretizer") == "original":
pytest.skip("Original concretizer cannot concretize in rounds")
- specs = [spack.spec.Spec(s) for s in specs]
+ specs = [Spec(s) for s in specs]
solver = spack.solver.asp.Solver()
solver.reuse = False
concrete_specs = {}
@@ -1731,9 +1729,9 @@ class TestConcretize(object):
reusable_specs = []
for s in ["mpileaks ^mpich", "zmpi"]:
- reusable_specs.extend(spack.spec.Spec(s).concretized().traverse(root=True))
+ reusable_specs.extend(Spec(s).concretized().traverse(root=True))
- root_specs = [spack.spec.Spec("mpileaks"), spack.spec.Spec("zmpi")]
+ root_specs = [Spec("mpileaks"), Spec("zmpi")]
import spack.solver.asp
@@ -1755,8 +1753,8 @@ class TestConcretize(object):
if spack.config.get("config:concretizer") == "original":
pytest.skip("Original concretizer cannot reuse")
- reusable_specs = [spack.spec.Spec("non-existing-conditional-dep@1.0").concretized()]
- root_spec = spack.spec.Spec("non-existing-conditional-dep@2.0")
+ reusable_specs = [Spec("non-existing-conditional-dep@1.0").concretized()]
+ root_spec = Spec("non-existing-conditional-dep@2.0")
with spack.config.override("concretizer:reuse", True):
solver = spack.solver.asp.Solver()
@@ -1774,10 +1772,8 @@ class TestConcretize(object):
if spack.config.get("config:concretizer") == "original":
pytest.skip("Original concretizer cannot reuse")
- reusable_specs = [
- spack.spec.Spec(spec_str).concretized() for spec_str in ("b@0.9", "b@1.0")
- ]
- root_spec = spack.spec.Spec("a foobar=bar")
+ reusable_specs = [Spec(spec_str).concretized() for spec_str in ("b@0.9", "b@1.0")]
+ root_spec = Spec("a foobar=bar")
with spack.config.override("concretizer:reuse", True):
solver = spack.solver.asp.Solver()
@@ -1811,7 +1807,7 @@ class TestConcretize(object):
if spack.config.get("config:concretizer") == "original":
pytest.skip("Original concretizer cannot reuse")
- root_spec = spack.spec.Spec("b")
+ root_spec = Spec("b")
s = root_spec.concretized()
wrong_compiler, wrong_os = s.copy(), s.copy()
wrong_compiler.compiler = spack.spec.CompilerSpec("gcc@12.1.0")
@@ -1926,7 +1922,7 @@ class TestConcretize(object):
# Add a conflict to "mpich" that match an already installed "mpich~debug"
pkg_cls = spack.repo.path.get_pkg_class("mpich")
- monkeypatch.setitem(pkg_cls.conflicts, "~debug", [(spack.spec.Spec(), None)])
+ monkeypatch.setitem(pkg_cls.conflicts, "~debug", [(Spec(), None)])
# If we concretize with --fresh the conflict is taken into account
with spack.config.override("concretizer:reuse", False):
@@ -1998,7 +1994,7 @@ class TestConcretize(object):
assert "python" in spec["py-extension1"]
assert spec["python"].prefix == fake_path
- # The spec is not equal to spack.spec.Spec("python@configured") because it gets a
+ # The spec is not equal to Spec("python@configured") because it gets a
# namespace and an external prefix before marking concrete
assert spec["python"].satisfies(python_spec)
@@ -2029,7 +2025,7 @@ class TestConcretize(object):
assert "python" in spec["py-extension1"]
assert spec["python"].prefix == fake_path
- # The spec is not equal to spack.spec.Spec("python@configured") because it gets a
+ # The spec is not equal to Spec("python@configured") because it gets a
# namespace and an external prefix before marking concrete
assert spec["python"].satisfies(python)
@@ -2037,7 +2033,7 @@ class TestConcretize(object):
"""Test that python extensions have access to a python dependency
when python isn't otherwise in the DAG"""
- python_spec = spack.spec.Spec("python@detected")
+ python_spec = Spec("python@=detected")
prefix = os.path.sep + "fake"
def find_fake_python(classes, path_hints):
@@ -2068,7 +2064,7 @@ class TestConcretize(object):
}
spack.config.set("packages", external_conf)
- abstract_specs = [spack.spec.Spec(s) for s in ["py-extension1", "python"]]
+ abstract_specs = [Spec(s) for s in ["py-extension1", "python"]]
specs = spack.concretize.concretize_specs_together(*abstract_specs)
assert specs[0]["python"] == specs[1]["python"]
@@ -2085,7 +2081,7 @@ class TestConcretize(object):
"""Check that the implementation of "result.specs" is correct in cases where we
know a concretization exists.
"""
- specs = [spack.spec.Spec(s) for s in specs]
+ specs = [Spec(s) for s in specs]
solver = spack.solver.asp.Solver()
setup = spack.solver.asp.SpackSolverSetup()
result, _, _ = solver.driver.solve(setup, specs, reuse=[])
@@ -2127,8 +2123,8 @@ class TestConcretize(object):
},
]
spack.config.set("compilers", compiler_configuration)
- s = spack.spec.Spec("a %gcc@:11").concretized()
- assert s.compiler.version == ver("11.1.0"), s
+ s = Spec("a %gcc@:11").concretized()
+ assert s.compiler.version == ver("=11.1.0"), s
@pytest.mark.regression("36339")
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows")
@@ -2148,8 +2144,8 @@ class TestConcretize(object):
}
]
spack.config.set("compilers", compiler_configuration)
- s = spack.spec.Spec("a %gcc@foo").concretized()
- assert s.compiler.version == ver("foo")
+ s = Spec("a %gcc@foo").concretized()
+ assert s.compiler.version == ver("=foo")
@pytest.mark.regression("36628")
def test_concretization_with_compilers_supporting_target_any(self):
diff --git a/lib/spack/spack/test/concretize_preferences.py b/lib/spack/spack/test/concretize_preferences.py
index 2a5ccd1f96..0a8d1c2ce6 100644
--- a/lib/spack/spack/test/concretize_preferences.py
+++ b/lib/spack/spack/test/concretize_preferences.py
@@ -13,7 +13,7 @@ import spack.package_prefs
import spack.repo
import spack.util.spack_yaml as syaml
from spack.config import ConfigError
-from spack.spec import Spec
+from spack.spec import CompilerSpec, Spec
from spack.version import Version
@@ -109,10 +109,13 @@ class TestConcretizePreferences(object):
)
def test_preferred_compilers(self, compiler_str, spec_str):
"""Test preferred compilers are applied correctly"""
- spec = spack.spec.Spec(spec_str)
+ spec = Spec(spec_str)
update_packages(spec.name, "compiler", [compiler_str])
spec.concretize()
- assert spec.compiler == spack.spec.CompilerSpec(compiler_str)
+ # note: lhs has concrete compiler version, rhs still abstract.
+ # Could be made more strict by checking for equality with `gcc@=4.5.0`
+ # etc.
+ assert spec.compiler.satisfies(CompilerSpec(compiler_str))
def test_preferred_target(self, mutable_mock_repo):
"""Test preferred targets are applied correctly"""
diff --git a/lib/spack/spack/test/concretize_requirements.py b/lib/spack/spack/test/concretize_requirements.py
index ecb33c6527..ee022b5f77 100644
--- a/lib/spack/spack/test/concretize_requirements.py
+++ b/lib/spack/spack/test/concretize_requirements.py
@@ -9,8 +9,10 @@ import pytest
import spack.build_systems.generic
import spack.config
+import spack.package_base
import spack.repo
import spack.util.spack_yaml as syaml
+import spack.version
from spack.solver.asp import UnsatisfiableSpecError
from spack.spec import Spec
from spack.util.url import path_to_file_url
@@ -27,14 +29,14 @@ _pkgx = (
"x",
"""\
class X(Package):
- version('1.1')
- version('1.0')
- version('0.9')
+ version("1.1")
+ version("1.0")
+ version("0.9")
- variant('shared', default=True,
- description='Build shared libraries')
+ variant("shared", default=True,
+ description="Build shared libraries")
- depends_on('y')
+ depends_on("y")
""",
)
@@ -43,12 +45,12 @@ _pkgy = (
"y",
"""\
class Y(Package):
- version('2.5')
- version('2.4')
- version('2.3', deprecated=True)
+ version("2.5")
+ version("2.4")
+ version("2.3", deprecated=True)
- variant('shared', default=True,
- description='Build shared libraries')
+ variant("shared", default=True,
+ description="Build shared libraries")
""",
)
@@ -57,8 +59,8 @@ _pkgv = (
"v",
"""\
class V(Package):
- version('2.1')
- version('2.0')
+ version("2.1")
+ version("2.0")
""",
)
@@ -150,27 +152,50 @@ def test_git_user_supplied_reference_satisfaction(
spack.package_base.PackageBase, "git", path_to_file_url(repo_path), raising=False
)
- specs = ["v@{commit0}=2.2", "v@{commit0}", "v@2.2", "v@{commit0}=2.3"]
+ hash_eq_ver = Spec(f"v@{commits[0]}=2.2")
+ hash_eq_ver_copy = Spec(f"v@{commits[0]}=2.2")
+ just_hash = Spec(f"v@{commits[0]}")
+ just_ver = Spec("v@=2.2")
+ hash_eq_other_ver = Spec(f"v@{commits[0]}=2.3")
- format_info = {"commit0": commits[0]}
+ assert not hash_eq_ver == just_hash
+ assert not hash_eq_ver.satisfies(just_hash)
+ assert not hash_eq_ver.intersects(just_hash)
- hash_eq_ver, just_hash, just_ver, hash_eq_other_ver = [
- Spec(x.format(**format_info)) for x in specs
- ]
-
- assert hash_eq_ver.satisfies(just_hash)
- assert not just_hash.satisfies(hash_eq_ver)
- assert hash_eq_ver.satisfies(just_ver)
+ # Git versions and literal versions are distinct versions, like
+ # pkg@10.1.0 and pkg@10.1.0-suffix are distinct versions.
+ assert not hash_eq_ver.satisfies(just_ver)
assert not just_ver.satisfies(hash_eq_ver)
+ assert not hash_eq_ver.intersects(just_ver)
+ assert hash_eq_ver != just_ver
+ assert just_ver != hash_eq_ver
+ assert not hash_eq_ver == just_ver
+ assert not just_ver == hash_eq_ver
+
+ # When a different version is associated, they're not equal
assert not hash_eq_ver.satisfies(hash_eq_other_ver)
assert not hash_eq_other_ver.satisfies(hash_eq_ver)
+ assert not hash_eq_ver.intersects(hash_eq_other_ver)
+ assert not hash_eq_other_ver.intersects(hash_eq_ver)
+ assert hash_eq_ver != hash_eq_other_ver
+ assert hash_eq_other_ver != hash_eq_ver
+ assert not hash_eq_ver == hash_eq_other_ver
+ assert not hash_eq_other_ver == hash_eq_ver
+
+ # These should be equal
+ assert hash_eq_ver == hash_eq_ver_copy
+ assert not hash_eq_ver != hash_eq_ver_copy
+ assert hash_eq_ver.satisfies(hash_eq_ver_copy)
+ assert hash_eq_ver_copy.satisfies(hash_eq_ver)
+ assert hash_eq_ver.intersects(hash_eq_ver_copy)
+ assert hash_eq_ver_copy.intersects(hash_eq_ver)
def test_requirement_adds_new_version(
concretize_scope, test_repo, mock_git_version_info, monkeypatch
):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
repo_path, filename, commits = mock_git_version_info
monkeypatch.setattr(
@@ -189,7 +214,6 @@ packages:
s1 = Spec("v").concretized()
assert s1.satisfies("@2.2")
- assert s1.satisfies("@{0}".format(a_commit_hash))
# Make sure the git commit info is retained
assert isinstance(s1.version, spack.version.GitVersion)
assert s1.version.ref == a_commit_hash
@@ -199,7 +223,7 @@ def test_requirement_adds_git_hash_version(
concretize_scope, test_repo, mock_git_version_info, monkeypatch
):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
repo_path, filename, commits = mock_git_version_info
monkeypatch.setattr(
@@ -207,46 +231,39 @@ def test_requirement_adds_git_hash_version(
)
a_commit_hash = commits[0]
- conf_str = """\
+ conf_str = f"""\
packages:
v:
- require: "@{0}"
-""".format(
- a_commit_hash
- )
+ require: "@{a_commit_hash}"
+"""
update_packages_config(conf_str)
s1 = Spec("v").concretized()
- assert s1.satisfies("@{0}".format(a_commit_hash))
+ assert isinstance(s1.version, spack.version.GitVersion)
+ assert s1.satisfies(f"v@{a_commit_hash}")
def test_requirement_adds_multiple_new_versions(
concretize_scope, test_repo, mock_git_version_info, monkeypatch
):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
repo_path, filename, commits = mock_git_version_info
monkeypatch.setattr(
spack.package_base.PackageBase, "git", path_to_file_url(repo_path), raising=False
)
- conf_str = """\
+ conf_str = f"""\
packages:
v:
require:
- - one_of: ["@{0}=2.2", "@{1}=2.3"]
-""".format(
- commits[0], commits[1]
- )
+ - one_of: ["@{commits[0]}=2.2", "@{commits[1]}=2.3"]
+"""
update_packages_config(conf_str)
- s1 = Spec("v").concretized()
- assert s1.satisfies("@2.2")
-
- s2 = Spec("v@{0}".format(commits[1])).concretized()
- assert s2.satisfies("@{0}".format(commits[1]))
- assert s2.satisfies("@2.3")
+ assert Spec("v").concretized().satisfies(f"@{commits[0]}=2.2")
+ assert Spec("v@2.3").concretized().satisfies(f"v@{commits[1]}=2.3")
# TODO: this belongs in the concretize_preferences test module but uses
@@ -255,35 +272,27 @@ def test_preference_adds_new_version(
concretize_scope, test_repo, mock_git_version_info, monkeypatch
):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
repo_path, filename, commits = mock_git_version_info
monkeypatch.setattr(
spack.package_base.PackageBase, "git", path_to_file_url(repo_path), raising=False
)
- conf_str = """\
+ conf_str = f"""\
packages:
v:
- version: ["{0}=2.2", "{1}=2.3"]
-""".format(
- commits[0], commits[1]
- )
+ version: ["{commits[0]}=2.2", "{commits[1]}=2.3"]
+"""
update_packages_config(conf_str)
- s1 = Spec("v").concretized()
- assert s1.satisfies("@2.2")
- assert s1.satisfies("@{0}".format(commits[0]))
-
- s2 = Spec("v@2.3").concretized()
- # Note: this check will fail: the command-line spec version is preferred
- # assert s2.satisfies("@{0}".format(commits[1]))
- assert s2.satisfies("@2.3")
+ assert Spec("v").concretized().satisfies(f"@{commits[0]}=2.2")
+ assert Spec("v@2.3").concretized().satisfies(f"@{commits[1]}=2.3")
- s3 = Spec("v@{0}".format(commits[1])).concretized()
- assert s3.satisfies("@{0}".format(commits[1]))
- # Note: this check will fail: the command-line spec version is preferred
- # assert s3.satisfies("@2.3")
+ # When installing by hash, a lookup is triggered, so it's not mapped to =2.3.
+ s3 = Spec(f"v@{commits[1]}").concretized()
+ assert s3.satisfies(f"v@{commits[1]}")
+ assert not s3.satisfies("@2.3")
def test_requirement_is_successfully_applied(concretize_scope, test_repo):
@@ -291,7 +300,7 @@ def test_requirement_is_successfully_applied(concretize_scope, test_repo):
concretization succeeds and the requirement spec is applied.
"""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
s1 = Spec("x").concretized()
# Without any requirements/preferences, the later version is preferred
@@ -313,7 +322,7 @@ def test_multiple_packages_requirements_are_respected(concretize_scope, test_rep
succeeds and both requirements are respected.
"""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
@@ -333,7 +342,7 @@ def test_oneof(concretize_scope, test_repo):
the specs in the group (but not all have to be satisfied).
"""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
@@ -353,7 +362,7 @@ def test_one_package_multiple_oneof_groups(concretize_scope, test_repo):
applied.
"""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
@@ -377,7 +386,7 @@ def test_requirements_for_package_that_is_not_needed(concretize_scope, test_repo
the requirements are used for the requested spec).
"""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
# Note that the exact contents aren't important since this isn't
# intended to be used, but the important thing is that a number of
@@ -403,7 +412,7 @@ def test_oneof_ordering(concretize_scope, test_repo):
later versions).
"""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
@@ -422,7 +431,7 @@ packages:
def test_reuse_oneof(concretize_scope, create_test_repo, mutable_database, fake_installs):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
@@ -445,7 +454,7 @@ packages:
def test_requirements_are_higher_priority_than_deprecation(concretize_scope, test_repo):
"""Test that users can override a deprecated version with a requirement."""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
# @2.3 is a deprecated versions. Ensure that any_of picks both constraints,
# since they are possible
@@ -466,7 +475,7 @@ packages:
def test_default_requirements_with_all(spec_str, requirement_str, concretize_scope, test_repo):
"""Test that default requirements are applied to all packages."""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
@@ -494,7 +503,7 @@ def test_default_and_package_specific_requirements(
):
"""Test that specific package requirements override default package requirements."""
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
generic_req, specific_req = requirements
generic_exp, specific_exp = expectations
conf_str = """\
@@ -517,7 +526,7 @@ packages:
@pytest.mark.parametrize("mpi_requirement", ["mpich", "mpich2", "zmpi"])
def test_requirements_on_virtual(mpi_requirement, concretize_scope, mock_packages):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
mpi:
@@ -540,7 +549,7 @@ def test_requirements_on_virtual_and_on_package(
mpi_requirement, specific_requirement, concretize_scope, mock_packages
):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
mpi:
@@ -560,7 +569,7 @@ packages:
def test_incompatible_virtual_requirements_raise(concretize_scope, mock_packages):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
mpi:
@@ -575,7 +584,7 @@ def test_incompatible_virtual_requirements_raise(concretize_scope, mock_packages
def test_non_existing_variants_under_all(concretize_scope, mock_packages):
if spack.config.get("config:concretizer") == "original":
- pytest.skip("Original concretizer does not support configuration" " requirements")
+ pytest.skip("Original concretizer does not support configuration requirements")
conf_str = """\
packages:
all:
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
index 1badc88019..cf67786136 100644
--- a/lib/spack/spack/test/conftest.py
+++ b/lib/spack/spack/test/conftest.py
@@ -123,7 +123,7 @@ def mock_git_version_info(git, tmpdir, override_git_repos_cache_path):
o second commit (v1.0)
o first commit
- The repo consists of a single file, in which the Version._cmp representation
+ The repo consists of a single file, in which the GitVersion._ref_version representation
of each commit is expressed as a string.
Important attributes of the repo for test coverage are: multiple branches,
@@ -175,7 +175,7 @@ def mock_git_version_info(git, tmpdir, override_git_repos_cache_path):
# Add two commits and a tag on 1.x branch
git("checkout", "-b", "1.x")
- write_file(filename, "[1, 0, '', 1]")
+ write_file(filename, "[1, 0, 'git', 1]")
commit("first 1.x commit")
commits.append(latest_commit())
@@ -186,7 +186,7 @@ def mock_git_version_info(git, tmpdir, override_git_repos_cache_path):
# Add two commits and a tag on main branch
git("checkout", main)
- write_file(filename, "[1, 0, '', 1]")
+ write_file(filename, "[1, 0, 'git', 1]")
commit("third main commit")
commits.append(latest_commit())
write_file(filename, "[2, 0]")
@@ -196,7 +196,7 @@ def mock_git_version_info(git, tmpdir, override_git_repos_cache_path):
# Add two more commits on 1.x branch to ensure we aren't cheating by using time
git("checkout", "1.x")
- write_file(filename, "[1, 1, '', 1]")
+ write_file(filename, "[1, 1, 'git', 1]")
commit("third 1.x commit")
commits.append(latest_commit())
write_file(filename, "[1, 2]")
diff --git a/lib/spack/spack/test/cray_manifest.py b/lib/spack/spack/test/cray_manifest.py
index 4ffff0b926..ba35f4083e 100644
--- a/lib/spack/spack/test/cray_manifest.py
+++ b/lib/spack/spack/test/cray_manifest.py
@@ -15,7 +15,12 @@ import os
import pytest
import spack
+import spack.cmd
+import spack.compilers
+import spack.config
import spack.cray_manifest as cray_manifest
+import spack.spec
+import spack.store
from spack.cray_manifest import compiler_from_entry, entries_to_specs
example_x_json_str = """\
@@ -348,7 +353,7 @@ def test_read_cray_manifest_twice_no_compiler_duplicates(
):
if spack.config.get("config:concretizer") == "clingo":
pytest.skip(
- "The ASP-based concretizer is currently picky about " " OS matching and will fail."
+ "The ASP-based concretizer is currently picky about OS matching and will fail."
)
with tmpdir.as_cwd():
@@ -362,7 +367,7 @@ def test_read_cray_manifest_twice_no_compiler_duplicates(
compilers = spack.compilers.all_compilers()
filtered = list(
- c for c in compilers if c.spec == spack.spec.CompilerSpec("gcc@10.2.0.cray")
+ c for c in compilers if c.spec == spack.spec.CompilerSpec("gcc@=10.2.0.cray")
)
assert len(filtered) == 1
diff --git a/lib/spack/spack/test/cvs_fetch.py b/lib/spack/spack/test/cvs_fetch.py
index bf96a8e40d..4cf3105799 100644
--- a/lib/spack/spack/test/cvs_fetch.py
+++ b/lib/spack/spack/test/cvs_fetch.py
@@ -13,7 +13,7 @@ from spack.fetch_strategy import CvsFetchStrategy
from spack.spec import Spec
from spack.stage import Stage
from spack.util.executable import which
-from spack.version import ver
+from spack.version import Version
pytestmark = pytest.mark.skipif(not which("cvs"), reason="requires CVS to be installed")
@@ -39,7 +39,7 @@ def test_fetch(type_of_test, mock_cvs_repository, config, mutable_mock_repo):
# Construct the package under test
spec = Spec("cvs-test").concretized()
- spec.package.versions[ver("cvs")] = test.args
+ spec.package.versions[Version("cvs")] = test.args
# Enter the stage directory and check some properties
with spec.package.stage:
diff --git a/lib/spack/spack/test/directives.py b/lib/spack/spack/test/directives.py
index 40bbd08d8b..bbb5789e21 100644
--- a/lib/spack/spack/test/directives.py
+++ b/lib/spack/spack/test/directives.py
@@ -2,11 +2,14 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+from collections import namedtuple
+
import pytest
import spack.directives
import spack.repo
import spack.spec
+import spack.version
def test_false_directives_do_not_exist(mock_packages):
@@ -84,3 +87,20 @@ def test_error_on_anonymous_dependency(config, mock_packages):
def test_maintainer_directive(config, mock_packages, package_name, expected_maintainers):
pkg_cls = spack.repo.path.get_pkg_class(package_name)
assert pkg_cls.maintainers == expected_maintainers
+
+
+def test_version_type_validation():
+ # A version should be a string or an int, not a float, because it leads to subtle issues
+ # such as 3.10 being interpreted as 3.1.
+
+ package = namedtuple("package", ["name"])
+
+ msg = r"python: declared version '.+' in package should be a string or int\."
+
+ # Pass a float
+ with pytest.raises(spack.version.VersionError, match=msg):
+ spack.directives._execute_version(package(name="python"), 3.10)
+
+ # Try passing a bogus type; it's just that we want a nice error message
+ with pytest.raises(spack.version.VersionError, match=msg):
+ spack.directives._execute_version(package(name="python"), {})
diff --git a/lib/spack/spack/test/git_fetch.py b/lib/spack/spack/test/git_fetch.py
index bf647da55e..a31648e224 100644
--- a/lib/spack/spack/test/git_fetch.py
+++ b/lib/spack/spack/test/git_fetch.py
@@ -16,7 +16,7 @@ import spack.repo
from spack.fetch_strategy import GitFetchStrategy
from spack.spec import Spec
from spack.stage import Stage
-from spack.version import ver
+from spack.version import Version
_mock_transport_error = "Mock HTTP transport error"
@@ -36,7 +36,7 @@ def git_version(git, request, monkeypatch):
# Don't patch; run with the real git_version method.
yield real_git_version
else:
- test_git_version = ver(request.param)
+ test_git_version = Version(request.param)
if test_git_version > real_git_version:
pytest.skip("Can't test clone logic for newer version of git.")
@@ -61,7 +61,7 @@ def mock_bad_git(monkeypatch):
# Patch the fetch strategy to think it's using a git version that
# will error out when git is called.
monkeypatch.setattr(GitFetchStrategy, "git", bad_git)
- monkeypatch.setattr(GitFetchStrategy, "git_version", ver("1.7.1"))
+ monkeypatch.setattr(GitFetchStrategy, "git_version", Version("1.7.1"))
yield
@@ -107,7 +107,7 @@ def test_fetch(
# Construct the package under test
s = default_mock_concretization("git-test")
- monkeypatch.setitem(s.package.versions, ver("git"), t.args)
+ monkeypatch.setitem(s.package.versions, Version("git"), t.args)
# Enter the stage directory and check some properties
with s.package.stage:
@@ -154,7 +154,7 @@ def test_fetch_pkg_attr_submodule_init(
# Construct the package under test
s = default_mock_concretization("git-test")
- monkeypatch.setitem(s.package.versions, ver("git"), t.args)
+ monkeypatch.setitem(s.package.versions, Version("git"), t.args)
s.package.do_stage()
collected_fnames = set()
@@ -180,7 +180,7 @@ def test_adhoc_version_submodules(
t = mock_git_repository.checks["tag"]
# Construct the package under test
pkg_class = spack.repo.path.get_pkg_class("git-test")
- monkeypatch.setitem(pkg_class.versions, ver("git"), t.args)
+ monkeypatch.setitem(pkg_class.versions, Version("git"), t.args)
monkeypatch.setattr(pkg_class, "git", "file://%s" % mock_git_repository.path, raising=False)
spec = Spec("git-test@{0}".format(mock_git_repository.unversioned_commit))
@@ -203,7 +203,7 @@ def test_debug_fetch(
# Construct the package under test
s = default_mock_concretization("git-test")
- monkeypatch.setitem(s.package.versions, ver("git"), t.args)
+ monkeypatch.setitem(s.package.versions, Version("git"), t.args)
# Fetch then ensure source path exists
with s.package.stage:
@@ -243,7 +243,7 @@ def test_get_full_repo(
):
"""Ensure that we can clone a full repository."""
- if git_version < ver("1.7.1"):
+ if git_version < Version("1.7.1"):
pytest.skip("Not testing get_full_repo for older git {0}".format(git_version))
secure = True
@@ -254,7 +254,7 @@ def test_get_full_repo(
s = default_mock_concretization("git-test")
args = copy.copy(t.args)
args["get_full_repo"] = get_full_repo
- monkeypatch.setitem(s.package.versions, ver("git"), args)
+ monkeypatch.setitem(s.package.versions, Version("git"), args)
with s.package.stage:
with spack.config.override("config:verify_ssl", secure):
@@ -299,7 +299,7 @@ def test_gitsubmodule(
s = default_mock_concretization("git-test")
args = copy.copy(t.args)
args["submodules"] = submodules
- monkeypatch.setitem(s.package.versions, ver("git"), args)
+ monkeypatch.setitem(s.package.versions, Version("git"), args)
s.package.do_stage()
with working_dir(s.package.stage.source_path):
for submodule_count in range(2):
@@ -332,7 +332,7 @@ def test_gitsubmodules_callable(
s = default_mock_concretization("git-test")
args = copy.copy(t.args)
args["submodules"] = submodules_callback
- monkeypatch.setitem(s.package.versions, ver("git"), args)
+ monkeypatch.setitem(s.package.versions, Version("git"), args)
s.package.do_stage()
with working_dir(s.package.stage.source_path):
file_path = os.path.join(s.package.stage.source_path, "third_party/submodule0/r0_file_0")
@@ -356,7 +356,7 @@ def test_gitsubmodules_delete(
args = copy.copy(t.args)
args["submodules"] = True
args["submodules_delete"] = ["third_party/submodule0", "third_party/submodule1"]
- monkeypatch.setitem(s.package.versions, ver("git"), args)
+ monkeypatch.setitem(s.package.versions, Version("git"), args)
s.package.do_stage()
with working_dir(s.package.stage.source_path):
file_path = os.path.join(s.package.stage.source_path, "third_party/submodule0")
diff --git a/lib/spack/spack/test/hg_fetch.py b/lib/spack/spack/test/hg_fetch.py
index e2f7603c09..3939d460e9 100644
--- a/lib/spack/spack/test/hg_fetch.py
+++ b/lib/spack/spack/test/hg_fetch.py
@@ -16,7 +16,7 @@ from spack.fetch_strategy import HgFetchStrategy
from spack.spec import Spec
from spack.stage import Stage
from spack.util.executable import which
-from spack.version import ver
+from spack.version import Version
# Test functionality covered is supported on Windows, but currently failing
# and expected to be fixed
@@ -44,7 +44,7 @@ def test_fetch(type_of_test, secure, mock_hg_repository, config, mutable_mock_re
# Construct the package under test
s = Spec("hg-test").concretized()
- monkeypatch.setitem(s.package.versions, ver("hg"), t.args)
+ monkeypatch.setitem(s.package.versions, Version("hg"), t.args)
# Enter the stage directory and check some properties
with s.package.stage:
diff --git a/lib/spack/spack/test/installer.py b/lib/spack/spack/test/installer.py
index 4e92802b72..91f02efbbc 100644
--- a/lib/spack/spack/test/installer.py
+++ b/lib/spack/spack/test/installer.py
@@ -17,12 +17,15 @@ import llnl.util.tty as tty
import spack.binary_distribution
import spack.compilers
+import spack.concretize
+import spack.config
import spack.installer as inst
import spack.package_prefs as prefs
import spack.repo
import spack.spec
import spack.store
import spack.util.lock as lk
+import spack.version
def _mock_repo(root, namespace):
@@ -528,10 +531,12 @@ def test_bootstrapping_compilers_with_different_names_from_spec(
):
with spack.config.override("config:install_missing_compilers", True):
with spack.concretize.disable_compiler_existence_check():
- spec = spack.spec.Spec("trivial-install-test-package%oneapi@22.2.0").concretized()
+ spec = spack.spec.Spec("trivial-install-test-package%oneapi@=22.2.0").concretized()
spec.package.do_install()
- assert spack.spec.CompilerSpec("oneapi@22.2.0") in spack.compilers.all_compiler_specs()
+ assert (
+ spack.spec.CompilerSpec("oneapi@=22.2.0") in spack.compilers.all_compiler_specs()
+ )
def test_dump_packages_deps_ok(install_mockery, tmpdir, mock_packages):
diff --git a/lib/spack/spack/test/mirror.py b/lib/spack/spack/test/mirror.py
index 80a6b8690c..2da1032d45 100644
--- a/lib/spack/spack/test/mirror.py
+++ b/lib/spack/spack/test/mirror.py
@@ -197,7 +197,7 @@ def test_invalid_json_mirror_collection(invalid_json, error_message):
def test_mirror_archive_paths_no_version(mock_packages, config, mock_archive):
- spec = Spec("trivial-install-test-package@nonexistingversion").concretized()
+ spec = Spec("trivial-install-test-package@=nonexistingversion").concretized()
fetcher = spack.fetch_strategy.URLFetchStrategy(mock_archive.url)
spack.mirror.mirror_archive_paths(fetcher, "per-package-ref", spec)
@@ -281,8 +281,8 @@ def test_mirror_cache_symlinks(tmpdir):
@pytest.mark.parametrize(
"specs,expected_specs",
[
- (["a"], ["a@1.0", "a@2.0"]),
- (["a", "brillig"], ["a@1.0", "a@2.0", "brillig@1.0.0", "brillig@2.0.0"]),
+ (["a"], ["a@=1.0", "a@=2.0"]),
+ (["a", "brillig"], ["a@=1.0", "a@=2.0", "brillig@=1.0.0", "brillig@=2.0.0"]),
],
)
def test_get_all_versions(specs, expected_specs):
diff --git a/lib/spack/spack/test/modules/lmod.py b/lib/spack/spack/test/modules/lmod.py
index 26370d1a6e..9dd37bb05f 100644
--- a/lib/spack/spack/test/modules/lmod.py
+++ b/lib/spack/spack/test/modules/lmod.py
@@ -24,7 +24,7 @@ writer_cls = spack.modules.lmod.LmodModulefileWriter
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
-@pytest.fixture(params=["clang@12.0.0", "gcc@10.2.1"])
+@pytest.fixture(params=["clang@=12.0.0", "gcc@=10.2.1"])
def compiler(request):
return request.param
@@ -61,10 +61,10 @@ class TestLmod(object):
# is transformed to r"Core" if the compiler is listed among core
# compilers
# Check that specs listed as core_specs are transformed to "Core"
- if compiler == "clang@3.3" or spec_string == "mpich@3.0.1":
+ if compiler == "clang@=3.3" or spec_string == "mpich@3.0.1":
assert "Core" in layout.available_path_parts
else:
- assert compiler.replace("@", "/") in layout.available_path_parts
+ assert compiler.replace("@=", "/") in layout.available_path_parts
# Check that the provider part instead has always an hash even if
# hash has been disallowed in the configuration file
diff --git a/lib/spack/spack/test/multimethod.py b/lib/spack/spack/test/multimethod.py
index 4df8eeafc2..8d4cec1253 100644
--- a/lib/spack/spack/test/multimethod.py
+++ b/lib/spack/spack/test/multimethod.py
@@ -54,8 +54,8 @@ def test_no_version_match(pkg_name):
("^mpich2@1.2", "mpi_version", 2),
("^mpich@1.0", "mpi_version", 1),
# Undefined mpi versions
- ("^mpich@0.4", "mpi_version", 1),
- ("^mpich@1.4", "mpi_version", 1),
+ ("^mpich@=0.4", "mpi_version", 1),
+ ("^mpich@=1.4", "mpi_version", 1),
# Constraints on compilers with a default
("%gcc", "has_a_default", "gcc"),
("%clang", "has_a_default", "clang"),
@@ -107,11 +107,11 @@ def test_target_match(pkg_name):
("multimethod@2.0", "inherited_and_overridden", "base@2.0"),
# Diamond-like inheritance (even though the MRO linearize everything)
("multimethod-diamond@1.0", "diamond_inheritance", "base_package"),
- ("multimethod-base@1.0", "diamond_inheritance", "base_package"),
+ ("multimethod-base@=1.0", "diamond_inheritance", "base_package"),
("multimethod-diamond@2.0", "diamond_inheritance", "first_parent"),
("multimethod-inheritor@2.0", "diamond_inheritance", "first_parent"),
- ("multimethod-diamond@3.0", "diamond_inheritance", "second_parent"),
- ("multimethod-diamond-parent@3.0", "diamond_inheritance", "second_parent"),
+ ("multimethod-diamond@=3.0", "diamond_inheritance", "second_parent"),
+ ("multimethod-diamond-parent@=3.0", "diamond_inheritance", "second_parent"),
("multimethod-diamond@4.0", "diamond_inheritance", "subclass"),
],
)
diff --git a/lib/spack/spack/test/patch.py b/lib/spack/spack/test/patch.py
index 396cff12df..1be3f44b18 100644
--- a/lib/spack/spack/test/patch.py
+++ b/lib/spack/spack/test/patch.py
@@ -148,7 +148,7 @@ def test_patch_mixed_versions_subset_constraint(mock_packages, config):
spec1.concretize()
assert biz_sha256 in spec1.variants["patches"].value
- spec2 = Spec("patch@1.0")
+ spec2 = Spec("patch@=1.0")
spec2.concretize()
assert biz_sha256 not in spec2.variants["patches"].value
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index 5c437da6ba..177deba6c2 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -986,8 +986,8 @@ def test_synthetic_construction_of_split_dependencies_from_same_package(mock_pac
# To demonstrate that a spec can now hold two direct
# dependencies from the same package
root = Spec("b").concretized()
- link_run_spec = Spec("c@1.0").concretized()
- build_spec = Spec("c@2.0").concretized()
+ link_run_spec = Spec("c@=1.0").concretized()
+ build_spec = Spec("c@=2.0").concretized()
root.add_dependency_edge(link_run_spec, deptypes="link")
root.add_dependency_edge(link_run_spec, deptypes="run")
@@ -1014,8 +1014,8 @@ def test_synthetic_construction_bootstrapping(mock_packages, config):
# | build
# b@1.0
#
- root = Spec("b@2.0").concretized()
- bootstrap = Spec("b@1.0").concretized()
+ root = Spec("b@=2.0").concretized()
+ bootstrap = Spec("b@=1.0").concretized()
root.add_dependency_edge(bootstrap, deptypes="build")
@@ -1032,8 +1032,8 @@ def test_addition_of_different_deptypes_in_multiple_calls(mock_packages, config)
# b@1.0
#
# with three calls and check we always have a single edge
- root = Spec("b@2.0").concretized()
- bootstrap = Spec("b@1.0").concretized()
+ root = Spec("b@=2.0").concretized()
+ bootstrap = Spec("b@=1.0").concretized()
for current_deptype in ("build", "link", "run"):
root.add_dependency_edge(bootstrap, deptypes=current_deptype)
@@ -1059,9 +1059,9 @@ def test_addition_of_different_deptypes_in_multiple_calls(mock_packages, config)
def test_adding_same_deptype_with_the_same_name_raises(
mock_packages, config, c1_deptypes, c2_deptypes
):
- p = Spec("b@2.0").concretized()
- c1 = Spec("b@1.0").concretized()
- c2 = Spec("b@2.0").concretized()
+ p = Spec("b@=2.0").concretized()
+ c1 = Spec("b@=1.0").concretized()
+ c2 = Spec("b@=2.0").concretized()
p.add_dependency_edge(c1, deptypes=c1_deptypes)
with pytest.raises(spack.error.SpackError):
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
index d1766eb8fc..bd755df84e 100644
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -628,16 +628,16 @@ class TestSpecSemantics(object):
# component: subcomponent of spec from which to get property
package_segments = [
("{NAME}", "", "name", lambda spec: spec),
- ("{VERSION}", "", "versions", lambda spec: spec),
+ ("{VERSION}", "", "version", lambda spec: spec),
("{compiler}", "", "compiler", lambda spec: spec),
("{compiler_flags}", "", "compiler_flags", lambda spec: spec),
("{variants}", "", "variants", lambda spec: spec),
("{architecture}", "", "architecture", lambda spec: spec),
- ("{@VERSIONS}", "@", "version", lambda spec: spec),
+ ("{@VERSIONS}", "@", "versions", lambda spec: spec),
("{%compiler}", "%", "compiler", lambda spec: spec),
("{arch=architecture}", "arch=", "architecture", lambda spec: spec),
("{compiler.name}", "", "name", lambda spec: spec.compiler),
- ("{compiler.version}", "", "versions", lambda spec: spec.compiler),
+ ("{compiler.version}", "", "version", lambda spec: spec.compiler),
("{%compiler.name}", "%", "name", lambda spec: spec.compiler),
("{@compiler.version}", "@", "version", lambda spec: spec.compiler),
("{architecture.platform}", "", "platform", lambda spec: spec.architecture),
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py
index 87d1c7b8e0..ffce60b104 100644
--- a/lib/spack/spack/test/spec_syntax.py
+++ b/lib/spack/spack/test/spec_syntax.py
@@ -662,7 +662,13 @@ def test_dep_spec_by_hash(database):
).next_spec()
assert "zmpi" in mpileaks_hash_zmpi
assert mpileaks_hash_zmpi["zmpi"] == zmpi
- assert mpileaks_hash_zmpi.compiler == mpileaks_zmpi.compiler
+
+ # notice: the round-trip str -> Spec loses specificity when
+ # since %gcc@=x gets printed as %gcc@x. So stick to satisfies
+ # here, unless/until we want to differentiate between ranges
+ # and specific versions in the future.
+ # assert mpileaks_hash_zmpi.compiler == mpileaks_zmpi.compiler
+ assert mpileaks_zmpi.compiler.satisfies(mpileaks_hash_zmpi.compiler)
mpileaks_hash_fake_and_zmpi = SpecParser(
f"mpileaks ^/{fake.dag_hash()[:4]} ^ /{zmpi.dag_hash()[:5]}"
diff --git a/lib/spack/spack/test/svn_fetch.py b/lib/spack/spack/test/svn_fetch.py
index 58b4bbbe35..523b4b012c 100644
--- a/lib/spack/spack/test/svn_fetch.py
+++ b/lib/spack/spack/test/svn_fetch.py
@@ -16,7 +16,7 @@ from spack.fetch_strategy import SvnFetchStrategy
from spack.spec import Spec
from spack.stage import Stage
from spack.util.executable import which
-from spack.version import ver
+from spack.version import Version
pytestmark = [
pytest.mark.skipif(
@@ -44,7 +44,7 @@ def test_fetch(type_of_test, secure, mock_svn_repository, config, mutable_mock_r
# Construct the package under test
s = Spec("svn-test").concretized()
- monkeypatch.setitem(s.package.versions, ver("svn"), t.args)
+ monkeypatch.setitem(s.package.versions, Version("svn"), t.args)
# Enter the stage directory and check some properties
with s.package.stage:
diff --git a/lib/spack/spack/test/url_fetch.py b/lib/spack/spack/test/url_fetch.py
index 5842c6855d..81ae9d7471 100644
--- a/lib/spack/spack/test/url_fetch.py
+++ b/lib/spack/spack/test/url_fetch.py
@@ -21,7 +21,6 @@ import spack.util.web as web_util
from spack.spec import Spec
from spack.stage import Stage
from spack.util.executable import which
-from spack.version import ver
@pytest.fixture(params=list(crypto.hashes.keys()))
@@ -153,7 +152,10 @@ def test_fetch(
# Get a spec and tweak the test package with new checksum params
s = default_mock_concretization("url-test")
s.package.url = mock_archive.url
- s.package.versions[ver("test")] = {checksum_type: checksum, "url": s.package.url}
+ s.package.versions[spack.version.Version("test")] = {
+ checksum_type: checksum,
+ "url": s.package.url,
+ }
# Enter the stage directory and check some properties
with s.package.stage:
@@ -175,13 +177,13 @@ def test_fetch(
@pytest.mark.parametrize(
"spec,url,digest",
[
- ("url-list-test @0.0.0", "foo-0.0.0.tar.gz", "00000000000000000000000000000000"),
- ("url-list-test @1.0.0", "foo-1.0.0.tar.gz", "00000000000000000000000000000100"),
- ("url-list-test @3.0", "foo-3.0.tar.gz", "00000000000000000000000000000030"),
- ("url-list-test @4.5", "foo-4.5.tar.gz", "00000000000000000000000000000450"),
- ("url-list-test @2.0.0b2", "foo-2.0.0b2.tar.gz", "000000000000000000000000000200b2"),
- ("url-list-test @3.0a1", "foo-3.0a1.tar.gz", "000000000000000000000000000030a1"),
- ("url-list-test @4.5-rc5", "foo-4.5-rc5.tar.gz", "000000000000000000000000000045c5"),
+ ("url-list-test @=0.0.0", "foo-0.0.0.tar.gz", "00000000000000000000000000000000"),
+ ("url-list-test @=1.0.0", "foo-1.0.0.tar.gz", "00000000000000000000000000000100"),
+ ("url-list-test @=3.0", "foo-3.0.tar.gz", "00000000000000000000000000000030"),
+ ("url-list-test @=4.5", "foo-4.5.tar.gz", "00000000000000000000000000000450"),
+ ("url-list-test @=2.0.0b2", "foo-2.0.0b2.tar.gz", "000000000000000000000000000200b2"),
+ ("url-list-test @=3.0a1", "foo-3.0a1.tar.gz", "000000000000000000000000000030a1"),
+ ("url-list-test @=4.5-rc5", "foo-4.5-rc5.tar.gz", "000000000000000000000000000045c5"),
],
)
@pytest.mark.parametrize("_fetch_method", ["curl", "urllib"])
@@ -209,7 +211,7 @@ def test_from_list_url(mock_packages, config, spec, url, digest, _fetch_method):
[
# This version is in the web data path (test/data/web/4.html), but not in the
# url-list-test package. We expect Spack to generate a URL with the new version.
- ("4.5.0", "foo-4.5.0.tar.gz", None),
+ ("=4.5.0", "foo-4.5.0.tar.gz", None),
# This version is in web data path and not in the package file, BUT the 2.0.0b2
# version in the package file satisfies 2.0.0, so Spack will use the known version.
# TODO: this is *probably* not what the user wants, but it's here as an example
diff --git a/lib/spack/spack/test/util/package_hash.py b/lib/spack/spack/test/util/package_hash.py
index 331e2c4989..9ab0ab0c47 100644
--- a/lib/spack/spack/test/util/package_hash.py
+++ b/lib/spack/spack/test/util/package_hash.py
@@ -40,29 +40,26 @@ def compare_hash_sans_name(eq, spec1, spec2):
content2 = content2.replace(pkg_cls2.__name__, "TestPackage")
hash2 = pkg_cls2(spec2).content_hash(content=content2)
- if eq:
- assert hash1 == hash2
- else:
- assert hash1 != hash2
+ assert (hash1 == hash2) == eq
def test_hash(mock_packages, config):
- ph.package_hash(Spec("hash-test1@1.2"))
+ ph.package_hash(Spec("hash-test1@=1.2"))
def test_different_variants(mock_packages, config):
- spec1 = Spec("hash-test1@1.2 +variantx")
- spec2 = Spec("hash-test1@1.2 +varianty")
+ spec1 = Spec("hash-test1@=1.2 +variantx")
+ spec2 = Spec("hash-test1@=1.2 +varianty")
assert ph.package_hash(spec1) == ph.package_hash(spec2)
def test_all_same_but_name(mock_packages, config):
- spec1 = Spec("hash-test1@1.2")
- spec2 = Spec("hash-test2@1.2")
+ spec1 = Spec("hash-test1@=1.2")
+ spec2 = Spec("hash-test2@=1.2")
compare_sans_name(True, spec1, spec2)
- spec1 = Spec("hash-test1@1.2 +varianty")
- spec2 = Spec("hash-test2@1.2 +varianty")
+ spec1 = Spec("hash-test1@=1.2 +varianty")
+ spec2 = Spec("hash-test2@=1.2 +varianty")
compare_sans_name(True, spec1, spec2)
@@ -70,26 +67,26 @@ def test_all_same_but_archive_hash(mock_packages, config):
"""
Archive hash is not intended to be reflected in Package hash.
"""
- spec1 = Spec("hash-test1@1.3")
- spec2 = Spec("hash-test2@1.3")
+ spec1 = Spec("hash-test1@=1.3")
+ spec2 = Spec("hash-test2@=1.3")
compare_sans_name(True, spec1, spec2)
def test_all_same_but_patch_contents(mock_packages, config):
- spec1 = Spec("hash-test1@1.1")
- spec2 = Spec("hash-test2@1.1")
+ spec1 = Spec("hash-test1@=1.1")
+ spec2 = Spec("hash-test2@=1.1")
compare_sans_name(True, spec1, spec2)
def test_all_same_but_patches_to_apply(mock_packages, config):
- spec1 = Spec("hash-test1@1.4")
- spec2 = Spec("hash-test2@1.4")
+ spec1 = Spec("hash-test1@=1.4")
+ spec2 = Spec("hash-test2@=1.4")
compare_sans_name(True, spec1, spec2)
def test_all_same_but_install(mock_packages, config):
- spec1 = Spec("hash-test1@1.5")
- spec2 = Spec("hash-test2@1.5")
+ spec1 = Spec("hash-test1@=1.5")
+ spec2 = Spec("hash-test2@=1.5")
compare_sans_name(False, spec1, spec2)
@@ -102,14 +99,14 @@ def test_content_hash_all_same_but_patch_contents(mock_packages, config):
def test_content_hash_not_concretized(mock_packages, config):
"""Check that Package.content_hash() works on abstract specs."""
# these are different due to the package hash
- spec1 = Spec("hash-test1@1.1")
- spec2 = Spec("hash-test2@1.3")
+ spec1 = Spec("hash-test1@=1.1")
+ spec2 = Spec("hash-test2@=1.3")
compare_hash_sans_name(False, spec1, spec2)
# at v1.1 these are actually the same package when @when's are removed
# and the name isn't considered
- spec1 = Spec("hash-test1@1.1")
- spec2 = Spec("hash-test2@1.1")
+ spec1 = Spec("hash-test1@=1.1")
+ spec2 = Spec("hash-test2@=1.1")
compare_hash_sans_name(True, spec1, spec2)
# these end up being different b/c we can't eliminate much of the package.py
diff --git a/lib/spack/spack/test/versions.py b/lib/spack/spack/test/versions.py
index d10ade63ca..8b58bf14ee 100644
--- a/lib/spack/spack/test/versions.py
+++ b/lib/spack/spack/test/versions.py
@@ -16,7 +16,16 @@ from llnl.util.filesystem import working_dir
import spack.package_base
import spack.spec
-from spack.version import GitVersion, Version, VersionBase, VersionList, VersionRange, ver
+from spack.version import (
+ GitVersion,
+ StandardVersion,
+ Version,
+ VersionList,
+ VersionLookupError,
+ VersionRange,
+ is_git_version,
+ ver,
+)
def assert_ver_lt(a, b):
@@ -98,160 +107,160 @@ def check_union(expected, a, b):
def test_string_prefix():
- assert_ver_eq("xsdk-0.2.0", "xsdk-0.2.0")
- assert_ver_lt("xsdk-0.2.0", "xsdk-0.3")
- assert_ver_gt("xsdk-0.3", "xsdk-0.2.0")
+ assert_ver_eq("=xsdk-0.2.0", "=xsdk-0.2.0")
+ assert_ver_lt("=xsdk-0.2.0", "=xsdk-0.3")
+ assert_ver_gt("=xsdk-0.3", "=xsdk-0.2.0")
def test_two_segments():
- assert_ver_eq("1.0", "1.0")
- assert_ver_lt("1.0", "2.0")
- assert_ver_gt("2.0", "1.0")
+ assert_ver_eq("=1.0", "=1.0")
+ assert_ver_lt("=1.0", "=2.0")
+ assert_ver_gt("=2.0", "=1.0")
def test_develop():
- assert_ver_eq("develop", "develop")
- assert_ver_eq("develop.local", "develop.local")
- assert_ver_lt("1.0", "develop")
- assert_ver_gt("develop", "1.0")
- assert_ver_eq("1.develop", "1.develop")
- assert_ver_lt("1.1", "1.develop")
- assert_ver_gt("1.develop", "1.0")
- assert_ver_gt("0.5.develop", "0.5")
- assert_ver_lt("0.5", "0.5.develop")
- assert_ver_lt("1.develop", "2.1")
- assert_ver_gt("2.1", "1.develop")
- assert_ver_lt("1.develop.1", "1.develop.2")
- assert_ver_gt("1.develop.2", "1.develop.1")
- assert_ver_lt("develop.1", "develop.2")
- assert_ver_gt("develop.2", "develop.1")
+ assert_ver_eq("=develop", "=develop")
+ assert_ver_eq("=develop.local", "=develop.local")
+ assert_ver_lt("=1.0", "=develop")
+ assert_ver_gt("=develop", "=1.0")
+ assert_ver_eq("=1.develop", "=1.develop")
+ assert_ver_lt("=1.1", "=1.develop")
+ assert_ver_gt("=1.develop", "=1.0")
+ assert_ver_gt("=0.5.develop", "=0.5")
+ assert_ver_lt("=0.5", "=0.5.develop")
+ assert_ver_lt("=1.develop", "=2.1")
+ assert_ver_gt("=2.1", "=1.develop")
+ assert_ver_lt("=1.develop.1", "=1.develop.2")
+ assert_ver_gt("=1.develop.2", "=1.develop.1")
+ assert_ver_lt("=develop.1", "=develop.2")
+ assert_ver_gt("=develop.2", "=develop.1")
# other +infinity versions
- assert_ver_gt("master", "9.0")
- assert_ver_gt("head", "9.0")
- assert_ver_gt("trunk", "9.0")
- assert_ver_gt("develop", "9.0")
+ assert_ver_gt("=master", "=9.0")
+ assert_ver_gt("=head", "=9.0")
+ assert_ver_gt("=trunk", "=9.0")
+ assert_ver_gt("=develop", "=9.0")
# hierarchical develop-like versions
- assert_ver_gt("develop", "master")
- assert_ver_gt("master", "head")
- assert_ver_gt("head", "trunk")
- assert_ver_gt("9.0", "system")
+ assert_ver_gt("=develop", "=master")
+ assert_ver_gt("=master", "=head")
+ assert_ver_gt("=head", "=trunk")
+ assert_ver_gt("=9.0", "=system")
# not develop
- assert_ver_lt("mydevelopmentnightmare", "1.1")
- assert_ver_lt("1.mydevelopmentnightmare", "1.1")
- assert_ver_gt("1.1", "1.mydevelopmentnightmare")
+ assert_ver_lt("=mydevelopmentnightmare", "=1.1")
+ assert_ver_lt("=1.mydevelopmentnightmare", "=1.1")
+ assert_ver_gt("=1.1", "=1.mydevelopmentnightmare")
def test_isdevelop():
- assert ver("develop").isdevelop()
- assert ver("develop.1").isdevelop()
- assert ver("develop.local").isdevelop()
- assert ver("master").isdevelop()
- assert ver("head").isdevelop()
- assert ver("trunk").isdevelop()
- assert ver("1.develop").isdevelop()
- assert ver("1.develop.2").isdevelop()
- assert not ver("1.1").isdevelop()
- assert not ver("1.mydevelopmentnightmare.3").isdevelop()
- assert not ver("mydevelopmentnightmare.3").isdevelop()
+ assert ver("=develop").isdevelop()
+ assert ver("=develop.1").isdevelop()
+ assert ver("=develop.local").isdevelop()
+ assert ver("=master").isdevelop()
+ assert ver("=head").isdevelop()
+ assert ver("=trunk").isdevelop()
+ assert ver("=1.develop").isdevelop()
+ assert ver("=1.develop.2").isdevelop()
+ assert not ver("=1.1").isdevelop()
+ assert not ver("=1.mydevelopmentnightmare.3").isdevelop()
+ assert not ver("=mydevelopmentnightmare.3").isdevelop()
def test_three_segments():
- assert_ver_eq("2.0.1", "2.0.1")
- assert_ver_lt("2.0", "2.0.1")
- assert_ver_gt("2.0.1", "2.0")
+ assert_ver_eq("=2.0.1", "=2.0.1")
+ assert_ver_lt("=2.0", "=2.0.1")
+ assert_ver_gt("=2.0.1", "=2.0")
def test_alpha():
# TODO: not sure whether I like this. 2.0.1a is *usually*
# TODO: less than 2.0.1, but special-casing it makes version
# TODO: comparison complicated. See version.py
- assert_ver_eq("2.0.1a", "2.0.1a")
- assert_ver_gt("2.0.1a", "2.0.1")
- assert_ver_lt("2.0.1", "2.0.1a")
+ assert_ver_eq("=2.0.1a", "=2.0.1a")
+ assert_ver_gt("=2.0.1a", "=2.0.1")
+ assert_ver_lt("=2.0.1", "=2.0.1a")
def test_patch():
- assert_ver_eq("5.5p1", "5.5p1")
- assert_ver_lt("5.5p1", "5.5p2")
- assert_ver_gt("5.5p2", "5.5p1")
- assert_ver_eq("5.5p10", "5.5p10")
- assert_ver_lt("5.5p1", "5.5p10")
- assert_ver_gt("5.5p10", "5.5p1")
+ assert_ver_eq("=5.5p1", "=5.5p1")
+ assert_ver_lt("=5.5p1", "=5.5p2")
+ assert_ver_gt("=5.5p2", "=5.5p1")
+ assert_ver_eq("=5.5p10", "=5.5p10")
+ assert_ver_lt("=5.5p1", "=5.5p10")
+ assert_ver_gt("=5.5p10", "=5.5p1")
def test_num_alpha_with_no_separator():
- assert_ver_lt("10xyz", "10.1xyz")
- assert_ver_gt("10.1xyz", "10xyz")
- assert_ver_eq("xyz10", "xyz10")
- assert_ver_lt("xyz10", "xyz10.1")
- assert_ver_gt("xyz10.1", "xyz10")
+ assert_ver_lt("=10xyz", "=10.1xyz")
+ assert_ver_gt("=10.1xyz", "=10xyz")
+ assert_ver_eq("=xyz10", "=xyz10")
+ assert_ver_lt("=xyz10", "=xyz10.1")
+ assert_ver_gt("=xyz10.1", "=xyz10")
def test_alpha_with_dots():
- assert_ver_eq("xyz.4", "xyz.4")
- assert_ver_lt("xyz.4", "8")
- assert_ver_gt("8", "xyz.4")
- assert_ver_lt("xyz.4", "2")
- assert_ver_gt("2", "xyz.4")
+ assert_ver_eq("=xyz.4", "=xyz.4")
+ assert_ver_lt("=xyz.4", "=8")
+ assert_ver_gt("=8", "=xyz.4")
+ assert_ver_lt("=xyz.4", "=2")
+ assert_ver_gt("=2", "=xyz.4")
def test_nums_and_patch():
- assert_ver_lt("5.5p2", "5.6p1")
- assert_ver_gt("5.6p1", "5.5p2")
- assert_ver_lt("5.6p1", "6.5p1")
- assert_ver_gt("6.5p1", "5.6p1")
+ assert_ver_lt("=5.5p2", "=5.6p1")
+ assert_ver_gt("=5.6p1", "=5.5p2")
+ assert_ver_lt("=5.6p1", "=6.5p1")
+ assert_ver_gt("=6.5p1", "=5.6p1")
def test_rc_versions():
- assert_ver_gt("6.0.rc1", "6.0")
- assert_ver_lt("6.0", "6.0.rc1")
+ assert_ver_gt("=6.0.rc1", "=6.0")
+ assert_ver_lt("=6.0", "=6.0.rc1")
def test_alpha_beta():
- assert_ver_gt("10b2", "10a1")
- assert_ver_lt("10a2", "10b2")
+ assert_ver_gt("=10b2", "=10a1")
+ assert_ver_lt("=10a2", "=10b2")
def test_double_alpha():
- assert_ver_eq("1.0aa", "1.0aa")
- assert_ver_lt("1.0a", "1.0aa")
- assert_ver_gt("1.0aa", "1.0a")
+ assert_ver_eq("=1.0aa", "=1.0aa")
+ assert_ver_lt("=1.0a", "=1.0aa")
+ assert_ver_gt("=1.0aa", "=1.0a")
def test_padded_numbers():
- assert_ver_eq("10.0001", "10.0001")
- assert_ver_eq("10.0001", "10.1")
- assert_ver_eq("10.1", "10.0001")
- assert_ver_lt("10.0001", "10.0039")
- assert_ver_gt("10.0039", "10.0001")
+ assert_ver_eq("=10.0001", "=10.0001")
+ assert_ver_eq("=10.0001", "=10.1")
+ assert_ver_eq("=10.1", "=10.0001")
+ assert_ver_lt("=10.0001", "=10.0039")
+ assert_ver_gt("=10.0039", "=10.0001")
def test_close_numbers():
- assert_ver_lt("4.999.9", "5.0")
- assert_ver_gt("5.0", "4.999.9")
+ assert_ver_lt("=4.999.9", "=5.0")
+ assert_ver_gt("=5.0", "=4.999.9")
def test_date_stamps():
- assert_ver_eq("20101121", "20101121")
- assert_ver_lt("20101121", "20101122")
- assert_ver_gt("20101122", "20101121")
+ assert_ver_eq("=20101121", "=20101121")
+ assert_ver_lt("=20101121", "=20101122")
+ assert_ver_gt("=20101122", "=20101121")
def test_underscores():
- assert_ver_eq("2_0", "2_0")
- assert_ver_eq("2.0", "2_0")
- assert_ver_eq("2_0", "2.0")
- assert_ver_eq("2-0", "2_0")
- assert_ver_eq("2_0", "2-0")
+ assert_ver_eq("=2_0", "=2_0")
+ assert_ver_eq("=2.0", "=2_0")
+ assert_ver_eq("=2_0", "=2.0")
+ assert_ver_eq("=2-0", "=2_0")
+ assert_ver_eq("=2_0", "=2-0")
def test_rpm_oddities():
- assert_ver_eq("1b.fc17", "1b.fc17")
- assert_ver_lt("1b.fc17", "1.fc17")
- assert_ver_gt("1.fc17", "1b.fc17")
- assert_ver_eq("1g.fc17", "1g.fc17")
- assert_ver_gt("1g.fc17", "1.fc17")
- assert_ver_lt("1.fc17", "1g.fc17")
+ assert_ver_eq("=1b.fc17", "=1b.fc17")
+ assert_ver_lt("=1b.fc17", "=1.fc17")
+ assert_ver_gt("=1.fc17", "=1b.fc17")
+ assert_ver_eq("=1g.fc17", "=1g.fc17")
+ assert_ver_gt("=1g.fc17", "=1.fc17")
+ assert_ver_lt("=1.fc17", "=1g.fc17")
# Stuff below here is not taken from RPM's tests and is
@@ -267,24 +276,24 @@ def test_version_ranges():
def test_contains():
- assert_in("1.3", "1.2:1.4")
- assert_in("1.2.5", "1.2:1.4")
- assert_in("1.3.5", "1.2:1.4")
- assert_in("1.3.5-7", "1.2:1.4")
- assert_not_in("1.1", "1.2:1.4")
- assert_not_in("1.5", "1.2:1.4")
- assert_not_in("1.5", "1.5.1:1.6")
- assert_not_in("1.5", "1.5.1:")
-
- assert_in("1.4.2", "1.2:1.4")
- assert_not_in("1.4.2", "1.2:1.4.0")
-
- assert_in("1.2.8", "1.2.7:1.4")
+ assert_in("=1.3", "1.2:1.4")
+ assert_in("=1.2.5", "1.2:1.4")
+ assert_in("=1.3.5", "1.2:1.4")
+ assert_in("=1.3.5-7", "1.2:1.4")
+ assert_not_in("=1.1", "1.2:1.4")
+ assert_not_in("=1.5", "1.2:1.4")
+ assert_not_in("=1.5", "1.5.1:1.6")
+ assert_not_in("=1.5", "1.5.1:")
+
+ assert_in("=1.4.2", "1.2:1.4")
+ assert_not_in("=1.4.2", "1.2:1.4.0")
+
+ assert_in("=1.2.8", "1.2.7:1.4")
assert_in("1.2.7:1.4", ":")
- assert_not_in("1.2.5", "1.2.7:1.4")
+ assert_not_in("=1.2.5", "1.2.7:1.4")
- assert_in("1.4.1", "1.2.7:1.4")
- assert_not_in("1.4.1", "1.2.7:1.4.0")
+ assert_in("=1.4.1", "1.2.7:1.4")
+ assert_not_in("=1.4.1", "1.2.7:1.4.0")
def test_in_list():
@@ -370,6 +379,8 @@ def test_intersection():
check_intersection(["2.5:2.7"], ["1.1:2.7"], ["2.5:3.0", "1.0"])
check_intersection(["0:1"], [":"], ["0:1"])
+ check_intersection(["=ref=1.0", "=1.1"], ["=ref=1.0", "1.1"], ["1:1.0", "=1.1"])
+
def test_intersect_with_containment():
check_intersection("1.6.5", "1.6.5", ":1.6")
@@ -397,6 +408,8 @@ def test_union_with_containment():
# Tests successor/predecessor case.
check_union("1:4", "1:2", "3:4")
+ check_union(["1:1.0", "1.1"], ["=ref=1.0", "1.1"], ["1:1.0", "=1.1"])
+
def test_basic_version_satisfaction():
assert_satisfies("4.7.3", "4.7.3")
@@ -522,7 +535,7 @@ def test_up_to():
def test_repr_and_str():
def check_repr_and_str(vrs):
a = Version(vrs)
- assert repr(a) == "VersionBase('" + vrs + "')"
+ assert repr(a) == f'Version("{vrs}")'
b = eval(repr(a))
assert a == b
assert str(a) == vrs
@@ -546,19 +559,19 @@ def test_get_item():
assert isinstance(a[1], int)
# Test slicing
b = a[0:2]
- assert isinstance(b, VersionBase)
+ assert isinstance(b, StandardVersion)
assert b == Version("0.1")
- assert repr(b) == "VersionBase('0.1')"
+ assert repr(b) == 'Version("0.1")'
assert str(b) == "0.1"
b = a[0:3]
- assert isinstance(b, VersionBase)
+ assert isinstance(b, StandardVersion)
assert b == Version("0.1_2")
- assert repr(b) == "VersionBase('0.1_2')"
+ assert repr(b) == 'Version("0.1_2")'
assert str(b) == "0.1_2"
b = a[1:]
- assert isinstance(b, VersionBase)
+ assert isinstance(b, StandardVersion)
assert b == Version("1_2-3")
- assert repr(b) == "VersionBase('1_2-3')"
+ assert repr(b) == 'Version("1_2-3")'
assert str(b) == "1_2-3"
# Raise TypeError on tuples
with pytest.raises(TypeError):
@@ -566,12 +579,12 @@ def test_get_item():
def test_list_highest():
- vl = VersionList(["master", "1.2.3", "develop", "3.4.5", "foobar"])
+ vl = VersionList(["=master", "=1.2.3", "=develop", "=3.4.5", "=foobar"])
assert vl.highest() == Version("develop")
assert vl.lowest() == Version("foobar")
assert vl.highest_numeric() == Version("3.4.5")
- vl2 = VersionList(["master", "develop"])
+ vl2 = VersionList(["=master", "=develop"])
assert vl2.highest_numeric() is None
assert vl2.preferred() == Version("develop")
assert vl2.lowest() == Version("master")
@@ -593,10 +606,8 @@ def test_versions_from_git(git, mock_git_version_info, monkeypatch, mock_package
for commit in commits:
spec = spack.spec.Spec("git-test-commit@%s" % commit)
- version = spec.version
- comparator = [
- str(v) if not isinstance(v, int) else v for v in version._cmp(version.ref_lookup)
- ]
+ version: GitVersion = spec.version
+ comparator = [str(v) if not isinstance(v, int) else v for v in version.ref_version]
with working_dir(repo_path):
git("checkout", commit)
@@ -655,14 +666,14 @@ def test_git_ref_comparisons(mock_git_version_info, install_mockery, mock_packag
spec_tag.concretize()
assert spec_tag.satisfies("@1.0")
assert not spec_tag.satisfies("@1.1:")
- assert str(spec_tag.version) == "git.v1.0"
+ assert str(spec_tag.version) == "git.v1.0=1.0"
# Spec based on branch 1.x
spec_branch = spack.spec.Spec("git-test-commit@git.1.x")
spec_branch.concretize()
assert spec_branch.satisfies("@1.2")
assert spec_branch.satisfies("@1.1:1.3")
- assert str(spec_branch.version) == "git.1.x"
+ assert str(spec_branch.version) == "git.1.x=1.2"
@pytest.mark.parametrize(
@@ -676,6 +687,7 @@ def test_git_ref_comparisons(mock_git_version_info, install_mockery, mock_packag
],
)
def test_version_git_vs_base(string, git):
+ assert is_git_version(string) == git
assert isinstance(Version(string), GitVersion) == git
@@ -713,21 +725,9 @@ def test_version_range_satisfies_means_nonempty_intersection():
assert not y.satisfies(x)
-@pytest.mark.regression("26482")
-def test_version_list_with_range_included_in_concrete_version_interpreted_as_range():
- # Note: this test only tests whether we can construct a version list of a range
- # and a version, where the range is contained in the version when it is interpreted
- # as a range. That is: Version('3.1') interpreted as VersionRange('3.1', '3.1').
- # Cleary it *shouldn't* be interpreted that way, but that is how Spack currently
- # behaves, and this test only ensures that creating a VersionList of this type
- # does not throw like reported in the linked Github issue.
- VersionList([Version("3.1"), VersionRange("3.1.1", "3.1.2")])
-
-
-@pytest.mark.xfail
def test_version_list_with_range_and_concrete_version_is_not_concrete():
- v = VersionList([Version("3.1"), VersionRange("3.1.1", "3.1.2")])
- assert v.concrete
+ v = VersionList([Version("3.1"), VersionRange(Version("3.1.1"), Version("3.1.2"))])
+ assert not v.concrete
@pytest.mark.parametrize(
@@ -744,15 +744,14 @@ def test_git_ref_can_be_assigned_a_version(vstring, eq_vstring, is_commit):
v = Version(vstring)
v_equivalent = Version(eq_vstring)
assert v.is_commit == is_commit
- assert v.is_ref
assert not v._ref_lookup
- assert v_equivalent.version == v.ref_version
+ assert v_equivalent == v.ref_version
@pytest.mark.parametrize(
"lhs_str,rhs_str,expected",
[
- # VersionBase
+ # StandardVersion
("4.7.3", "4.7.3", (True, True, True)),
("4.7.3", "4.7", (True, True, False)),
("4.7.3", "4", (True, True, False)),
@@ -808,3 +807,170 @@ def test_git_versions_without_explicit_reference(
for test_str, expected in tested_intersects:
assert spec.intersects(test_str) is expected, test_str
+
+
+def test_total_order_versions_and_ranges():
+ # The set of version ranges and individual versions are comparable, which is used in
+ # VersionList. The comparsion across types is based on default version comparsion
+ # of StandardVersion, GitVersion.ref_version, and ClosedOpenRange.lo.
+
+ # StandardVersion / GitVersion (at equal ref version)
+ assert_ver_lt("=1.2", "git.ref=1.2")
+ assert_ver_gt("git.ref=1.2", "=1.2")
+
+ # StandardVersion / GitVersion (at different ref versions)
+ assert_ver_lt("git.ref=1.2", "=1.3")
+ assert_ver_gt("=1.3", "git.ref=1.2")
+ assert_ver_lt("=1.2", "git.ref=1.3")
+ assert_ver_gt("git.ref=1.3", "=1.2")
+
+ # GitVersion / ClosedOpenRange (at equal ref/lo version)
+ assert_ver_lt("git.ref=1.2", "1.2")
+ assert_ver_gt("1.2", "git.ref=1.2")
+
+ # GitVersion / ClosedOpenRange (at different ref/lo version)
+ assert_ver_lt("git.ref=1.2", "1.3")
+ assert_ver_gt("1.3", "git.ref=1.2")
+ assert_ver_lt("1.2", "git.ref=1.3")
+ assert_ver_gt("git.ref=1.3", "1.2")
+
+ # StandardVersion / ClosedOpenRange (at equal lo version)
+ assert_ver_lt("=1.2", "1.2")
+ assert_ver_gt("1.2", "=1.2")
+
+ # StandardVersion / ClosedOpenRange (at different lo version)
+ assert_ver_lt("=1.2", "1.3")
+ assert_ver_gt("1.3", "=1.2")
+ assert_ver_lt("1.2", "=1.3")
+ assert_ver_gt("=1.3", "1.2")
+
+
+def test_git_version_accessors():
+ """Test whether iteration, indexing, slicing, dotted, dashed, and underscored works for
+ GitVersion."""
+ v = GitVersion("my_branch=1.2-3")
+ assert [x for x in v] == [1, 2, 3]
+ assert v[0] == 1
+ assert v[1] == 2
+ assert v[2] == 3
+ assert v[0:2] == Version("1.2")
+ assert v[0:10] == Version("1.2.3")
+ assert str(v.dotted) == "1.2.3"
+ assert str(v.dashed) == "1-2-3"
+ assert str(v.underscored) == "1_2_3"
+ assert v.up_to(1) == Version("1")
+ assert v.up_to(2) == Version("1.2")
+ assert len(v) == 3
+ assert not v.isdevelop()
+ assert GitVersion("my_branch=develop").isdevelop()
+
+
+def test_boolness_of_versions():
+ # We do implement __len__, but at the end of the day versions are used as elements in
+ # the first place, not as lists of version components. So VersionList(...).concrete
+ # should be truthy even when there are no version components.
+ assert bool(Version("1.2"))
+ assert bool(Version("1.2").up_to(0))
+
+ # bool(GitVersion) shouldn't trigger a ref lookup.
+ assert bool(GitVersion("a" * 40))
+
+
+def test_version_list_normalization():
+ # Git versions and ordinary versions can live together in a VersionList
+ assert len(VersionList(["=1.2", "ref=1.2"])) == 2
+
+ # But when a range is added, the only disjoint bit is the range.
+ assert VersionList(["=1.2", "ref=1.2", "ref=1.3", "1.2:1.3"]) == VersionList(["1.2:1.3"])
+
+ # Also test normalization when using ver.
+ assert ver("=1.0,ref=1.0,1.0:2.0") == ver(["1.0:2.0"])
+ assert ver("=1.0,1.0:2.0,ref=1.0") == ver(["1.0:2.0"])
+ assert ver("1.0:2.0,=1.0,ref=1.0") == ver(["1.0:2.0"])
+
+
+@pytest.mark.parametrize("version", ["=1.2", "git.ref=1.2", "1.2"])
+def test_version_comparison_with_list_fails(version):
+ vlist = VersionList(["=1.3"])
+
+ with pytest.raises(TypeError):
+ version < vlist
+
+ with pytest.raises(TypeError):
+ vlist < version
+
+ with pytest.raises(TypeError):
+ version <= vlist
+
+ with pytest.raises(TypeError):
+ vlist <= version
+
+ with pytest.raises(TypeError):
+ version >= vlist
+
+ with pytest.raises(TypeError):
+ vlist >= version
+
+ with pytest.raises(TypeError):
+ version > vlist
+
+ with pytest.raises(TypeError):
+ vlist > version
+
+
+def test_inclusion_upperbound():
+ is_specific = spack.spec.Spec("x@=1.2")
+ is_range = spack.spec.Spec("x@1.2")
+ upperbound = spack.spec.Spec("x@:1.2.0")
+
+ # The exact version is included in the range
+ assert is_specific.satisfies(upperbound)
+
+ # But the range 1.2:1.2 is not, since it includes for example 1.2.1
+ assert not is_range.satisfies(upperbound)
+
+ # They do intersect of course.
+ assert is_specific.intersects(upperbound) and is_range.intersects(upperbound)
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
+def test_git_version_repo_attached_after_serialization(
+ mock_git_version_info, mock_packages, monkeypatch
+):
+ """Test that a GitVersion instance can be serialized and deserialized
+ without losing its repository reference.
+ """
+ repo_path, _, commits = mock_git_version_info
+ monkeypatch.setattr(
+ spack.package_base.PackageBase, "git", "file://%s" % repo_path, raising=False
+ )
+ spec = spack.spec.Spec(f"git-test-commit@{commits[-2]}").concretized()
+
+ # Before serialization, the repo is attached
+ assert spec.satisfies("@1.0")
+
+ # After serialization, the repo is still attached
+ assert spack.spec.Spec.from_dict(spec.to_dict()).satisfies("@1.0")
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
+def test_resolved_git_version_is_shown_in_str(mock_git_version_info, mock_packages, monkeypatch):
+ """Test that a GitVersion from a commit without a user supplied version is printed
+ as <hash>=<version>, and not just <hash>."""
+ repo_path, _, commits = mock_git_version_info
+ monkeypatch.setattr(
+ spack.package_base.PackageBase, "git", "file://%s" % repo_path, raising=False
+ )
+ commit = commits[-3]
+ spec = spack.spec.Spec(f"git-test-commit@{commit}").concretized()
+
+ assert spec.version.satisfies(ver("1.0"))
+ assert str(spec.version) == f"{commit}=1.0-git.1"
+
+
+def test_unresolvable_git_versions_error(mock_packages):
+ """Test that VersionLookupError is raised when a git prop is not set on a package."""
+ with pytest.raises(VersionLookupError):
+ # The package exists, but does not have a git property set. When dereferencing
+ # the version, we should get VersionLookupError, not a generic AttributeError.
+ spack.spec.Spec(f"git-test-commit@{'a' * 40}").version.ref_version
diff --git a/lib/spack/spack/test/web.py b/lib/spack/spack/test/web.py
index 7f6c12a5e2..adebe23b97 100644
--- a/lib/spack/spack/test/web.py
+++ b/lib/spack/spack/test/web.py
@@ -16,7 +16,7 @@ import spack.paths
import spack.util.s3
import spack.util.url as url_util
import spack.util.web
-from spack.version import ver
+from spack.version import Version
def _create_url(relative_url):
@@ -102,47 +102,47 @@ def test_spider_no_response(monkeypatch):
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
def test_find_versions_of_archive_0():
versions = spack.util.web.find_versions_of_archive(root_tarball, root, list_depth=0)
- assert ver("0.0.0") in versions
+ assert Version("0.0.0") in versions
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
def test_find_versions_of_archive_1():
versions = spack.util.web.find_versions_of_archive(root_tarball, root, list_depth=1)
- assert ver("0.0.0") in versions
- assert ver("1.0.0") in versions
+ assert Version("0.0.0") in versions
+ assert Version("1.0.0") in versions
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
def test_find_versions_of_archive_2():
versions = spack.util.web.find_versions_of_archive(root_tarball, root, list_depth=2)
- assert ver("0.0.0") in versions
- assert ver("1.0.0") in versions
- assert ver("2.0.0") in versions
+ assert Version("0.0.0") in versions
+ assert Version("1.0.0") in versions
+ assert Version("2.0.0") in versions
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
def test_find_exotic_versions_of_archive_2():
versions = spack.util.web.find_versions_of_archive(root_tarball, root, list_depth=2)
# up for grabs to make this better.
- assert ver("2.0.0b2") in versions
+ assert Version("2.0.0b2") in versions
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
def test_find_versions_of_archive_3():
versions = spack.util.web.find_versions_of_archive(root_tarball, root, list_depth=3)
- assert ver("0.0.0") in versions
- assert ver("1.0.0") in versions
- assert ver("2.0.0") in versions
- assert ver("3.0") in versions
- assert ver("4.5") in versions
+ assert Version("0.0.0") in versions
+ assert Version("1.0.0") in versions
+ assert Version("2.0.0") in versions
+ assert Version("3.0") in versions
+ assert Version("4.5") in versions
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
def test_find_exotic_versions_of_archive_3():
versions = spack.util.web.find_versions_of_archive(root_tarball, root, list_depth=3)
- assert ver("2.0.0b2") in versions
- assert ver("3.0a1") in versions
- assert ver("4.5-rc5") in versions
+ assert Version("2.0.0b2") in versions
+ assert Version("3.0a1") in versions
+ assert Version("4.5-rc5") in versions
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
@@ -150,7 +150,7 @@ def test_find_versions_of_archive_with_fragment():
versions = spack.util.web.find_versions_of_archive(
root_tarball, root_with_fragment, list_depth=0
)
- assert ver("5.0.0") in versions
+ assert Version("5.0.0") in versions
def test_get_header():
diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py
index d744e223c2..b8452ab61d 100644
--- a/lib/spack/spack/version.py
+++ b/lib/spack/spack/version.py
@@ -6,31 +6,20 @@
"""
This module implements Version and version-ish objects. These are:
-Version
- A single version of a package.
-VersionRange
- A range of versions of a package.
-VersionList
- A list of Versions and VersionRanges.
-
-All of these types support the following operations, which can
-be called on any of the types::
-
- __eq__, __ne__, __lt__, __gt__, __ge__, __le__, __hash__
- __contains__
- satisfies
- overlaps
- union
- intersection
- concrete
+StandardVersion: A single version of a package.
+ClosedOpenRange: A range of versions of a package.
+VersionList: A ordered list of Version and VersionRange elements.
+
+The set of Version and ClosedOpenRange is totally ordered wiht <
+defined as Version(x) < VersionRange(Version(y), Version(x))
+if Version(x) <= Version(y).
"""
import numbers
import os
import re
from bisect import bisect_left
-from functools import wraps
+from typing import Dict, List, Optional, Tuple, Union
-import llnl.util.tty as tty
from llnl.util.filesystem import mkdirp, working_dir
import spack.caches
@@ -38,10 +27,9 @@ import spack.error
import spack.paths
import spack.util.executable
import spack.util.spack_json as sjson
+import spack.util.url
from spack.util.spack_yaml import syaml_dict
-__all__ = ["Version", "VersionRange", "VersionList", "ver"]
-
# Valid version characters
VALID_VERSION = re.compile(r"^[A-Za-z0-9_.-][=A-Za-z0-9_.-]*$")
@@ -59,199 +47,240 @@ SEMVER_REGEX = re.compile(
)
# Infinity-like versions. The order in the list implies the comparison rules
-infinity_versions = ["develop", "main", "master", "head", "trunk", "stable"]
+infinity_versions = ["stable", "trunk", "head", "master", "main", "develop"]
iv_min_len = min(len(s) for s in infinity_versions)
-def coerce_versions(a, b):
- """
- Convert both a and b to the 'greatest' type between them, in this order:
- VersionBase < GitVersion < VersionRange < VersionList
- This is used to simplify comparison operations below so that we're always
- comparing things that are of the same type.
- """
- order = (VersionBase, GitVersion, VersionRange, VersionList)
- ta, tb = type(a), type(b)
-
- def check_type(t):
- if t not in order:
- raise TypeError("coerce_versions cannot be called on %s" % t)
-
- check_type(ta)
- check_type(tb)
-
- if ta == tb:
- return (a, b)
- elif order.index(ta) > order.index(tb):
- if ta == GitVersion:
- return (a, GitVersion(b))
- elif ta == VersionRange:
- return (a, VersionRange(b, b))
- else:
- return (a, VersionList([b]))
- else:
- if tb == GitVersion:
- return (GitVersion(a), b)
- elif tb == VersionRange:
- return (VersionRange(a, a), b)
- else:
- return (VersionList([a]), b)
-
-
-def coerced(method):
- """Decorator that ensures that argument types of a method are coerced."""
-
- @wraps(method)
- def coercing_method(a, b, *args, **kwargs):
- if type(a) == type(b) or a is None or b is None:
- return method(a, b, *args, **kwargs)
- else:
- ca, cb = coerce_versions(a, b)
- return getattr(ca, method.__name__)(cb, *args, **kwargs)
-
- return coercing_method
-
-
class VersionStrComponent(object):
- # NOTE: this is intentionally not a UserString, the abc instanceof
- # check is slow enough to eliminate all gains
- __slots__ = ["inf_ver", "data"]
+ __slots__ = ["data"]
+
+ def __init__(self, data):
+ # int for infinity index, str for literal.
+ self.data: Union[int, str] = data
- def __init__(self, string):
- self.inf_ver = None
- self.data = string
+ @staticmethod
+ def from_string(string):
if len(string) >= iv_min_len:
try:
- self.inf_ver = infinity_versions.index(string)
+ string = infinity_versions.index(string)
except ValueError:
pass
+ return VersionStrComponent(string)
+
def __hash__(self):
return hash(self.data)
def __str__(self):
- return self.data
+ return (
+ ("infinity" if self.data >= len(infinity_versions) else infinity_versions[self.data])
+ if isinstance(self.data, int)
+ else self.data
+ )
- def __repr__(self):
- return f"VersionStrComponent('{self.data}')"
+ def __repr__(self) -> str:
+ return f'VersionStrComponent("{self}")'
def __eq__(self, other):
- if isinstance(other, VersionStrComponent):
- return self.data == other.data
- return self.data == other
+ return isinstance(other, VersionStrComponent) and self.data == other.data
def __lt__(self, other):
- if isinstance(other, VersionStrComponent):
- if self.inf_ver is not None:
- if other.inf_ver is not None:
- return self.inf_ver > other.inf_ver
- return False
- if other.inf_ver is not None:
- return True
-
- return self.data < other.data
+ lhs_inf = isinstance(self.data, int)
+ if isinstance(other, int):
+ return not lhs_inf
+ rhs_inf = isinstance(other.data, int)
+ return (not lhs_inf and rhs_inf) if lhs_inf ^ rhs_inf else self.data < other.data
- if self.inf_ver is not None:
- return False
+ def __le__(self, other):
+ return self < other or self == other
- # Numbers are always "newer" than letters.
- # This is for consistency with RPM. See patch
- # #60884 (and details) from bugzilla #50977 in
- # the RPM project at rpm.org. Or look at
- # rpmvercmp.c if you want to see how this is
- # implemented there.
+ def __gt__(self, other):
+ lhs_inf = isinstance(self.data, int)
if isinstance(other, int):
- return True
+ return lhs_inf
+ rhs_inf = isinstance(other.data, int)
+ return (lhs_inf and not rhs_inf) if lhs_inf ^ rhs_inf else self.data > other.data
+
+ def __ge__(self, other):
+ return self > other or self == other
- if isinstance(other, str):
- return self < VersionStrComponent(other)
- # If we get here, it's an unsupported comparison
- raise ValueError("VersionStrComponent can only be compared with itself, " "int and str")
+def parse_string_components(string: str) -> Tuple[tuple, tuple]:
+ string = string.strip()
- def __gt__(self, other):
- return not self.__lt__(other)
+ if string and not VALID_VERSION.match(string):
+ raise ValueError("Bad characters in version string: %s" % string)
+ segments = SEGMENT_REGEX.findall(string)
+ version = tuple(int(m[0]) if m[0] else VersionStrComponent.from_string(m[1]) for m in segments)
+ separators = tuple(m[2] for m in segments)
+ return version, separators
-def is_git_version(string):
- if string.startswith("git."):
- return True
- elif len(string) == 40 and COMMIT_VERSION.match(string):
+
+class ConcreteVersion:
+ pass
+
+
+class StandardVersion(ConcreteVersion):
+ """Class to represent versions"""
+
+ __slots__ = ["version", "string", "separators"]
+
+ def __init__(self, string: Optional[str], version: tuple, separators: tuple):
+ self.string = string
+ self.version = version
+ self.separators = separators
+
+ @staticmethod
+ def from_string(string: str):
+ return StandardVersion(string, *parse_string_components(string))
+
+ @staticmethod
+ def typemin():
+ return StandardVersion("", (), ())
+
+ @staticmethod
+ def typemax():
+ return StandardVersion("infinity", (VersionStrComponent(len(infinity_versions)),), ())
+
+ def __bool__(self):
return True
- elif "=" in string:
+
+ def __eq__(self, other):
+ if isinstance(other, StandardVersion):
+ return self.version == other.version
+ return False
+
+ def __ne__(self, other):
+ if isinstance(other, StandardVersion):
+ return self.version != other.version
return True
- return False
+ def __lt__(self, other):
+ if isinstance(other, StandardVersion):
+ return self.version < other.version
+ if isinstance(other, ClosedOpenRange):
+ # Use <= here so that Version(x) < ClosedOpenRange(Version(x), ...).
+ return self <= other.lo
+ return NotImplemented
-def Version(string): # capitalized for backwards compatibility
- if not isinstance(string, str):
- string = str(string) # to handle VersionBase and GitVersion types
+ def __le__(self, other):
+ if isinstance(other, StandardVersion):
+ return self.version <= other.version
+ if isinstance(other, ClosedOpenRange):
+ # Versions are never equal to ranges, so follow < logic.
+ return self <= other.lo
+ return NotImplemented
- if is_git_version(string):
- return GitVersion(string)
- return VersionBase(string)
+ def __ge__(self, other):
+ if isinstance(other, StandardVersion):
+ return self.version >= other.version
+ if isinstance(other, ClosedOpenRange):
+ # Versions are never equal to ranges, so follow > logic.
+ return self > other.lo
+ return NotImplemented
+ def __gt__(self, other):
+ if isinstance(other, StandardVersion):
+ return self.version > other.version
+ if isinstance(other, ClosedOpenRange):
+ return self > other.lo
+ return NotImplemented
-class VersionBase(object):
- """Class to represent versions
+ def __iter__(self):
+ return iter(self.version)
- Versions are compared by converting to a tuple and comparing
- lexicographically.
+ def __len__(self):
+ return len(self.version)
+
+ def __getitem__(self, idx):
+ cls = type(self)
- The most common Versions are alpha-numeric, and are parsed from strings
- such as ``2.3.0`` or ``1.2-5``. These Versions are represented by
- the tuples ``(2, 3, 0)`` and ``(1, 2, 5)`` respectively.
+ if isinstance(idx, numbers.Integral):
+ return self.version[idx]
- Versions are split on ``.``, ``-``, and
- ``_`` characters, as well as any point at which they switch from
- numeric to alphabetical or vice-versa. For example, the version
- ``0.1.3a`` is represented by the tuple ``(0, 1, 3, 'a') and the
- version ``a-5b`` is represented by the tuple ``('a', 5, 'b')``.
+ elif isinstance(idx, slice):
+ string_arg = []
+
+ pairs = zip(self.version[idx], self.separators[idx])
+ for token, sep in pairs:
+ string_arg.append(str(token))
+ string_arg.append(str(sep))
- Spack versions may also be arbitrary non-numeric strings or git
- commit SHAs. The following are the full rules for comparing
- versions.
+ if string_arg:
+ string_arg.pop() # We don't need the last separator
+ string_arg = "".join(string_arg)
+ return cls.from_string(string_arg)
+ else:
+ return StandardVersion.from_string("")
- 1. If the version represents a git reference (i.e. commit, tag, branch), see GitVersions.
+ message = "{cls.__name__} indices must be integers"
+ raise TypeError(message.format(cls=cls))
- 2. The version is split into fields based on the delimiters ``.``,
- ``-``, and ``_``, as well as alphabetic-numeric boundaries.
+ def __str__(self):
+ return (
+ self.string
+ if isinstance(self.string, str)
+ else ".".join((str(c) for c in self.version))
+ )
- 3. The following develop-like strings are greater (newer) than all
- numbers and are ordered as ``develop > main > master > head >
- trunk``.
+ def __repr__(self) -> str:
+ # Print indirect repr through Version(...)
+ return f'Version("{str(self)}")'
- 4. All other non-numeric versions are less than numeric versions,
- and are sorted alphabetically.
+ def __hash__(self):
+ return hash(self.version)
- These rules can be summarized as follows:
+ def __contains__(rhs, lhs):
+ # We should probably get rid of `x in y` for versions, since
+ # versions still have a dual interpretation as singleton sets
+ # or elements. x in y should be: is the lhs-element in the
+ # rhs-set. Instead this function also does subset checks.
+ if isinstance(lhs, (StandardVersion, ClosedOpenRange, VersionList)):
+ return lhs.satisfies(rhs)
+ raise ValueError(lhs)
+
+ def intersects(self, other: Union["StandardVersion", "GitVersion", "ClosedOpenRange"]) -> bool:
+ if isinstance(other, StandardVersion):
+ return self == other
+ return other.intersects(self)
+
+ def overlaps(self, other) -> bool:
+ return self.intersects(other)
- ``develop > main > master > head > trunk > 999 > 0 > 'zzz' > 'a' >
- ''``
+ def satisfies(
+ self, other: Union["ClosedOpenRange", "StandardVersion", "GitVersion", "VersionList"]
+ ) -> bool:
+ if isinstance(other, GitVersion):
+ return False
- """
+ if isinstance(other, StandardVersion):
+ return self == other
- __slots__ = ["version", "separators", "string"]
+ if isinstance(other, ClosedOpenRange):
+ return other.intersects(self)
- def __init__(self, string: str) -> None:
- if not isinstance(string, str):
- string = str(string)
+ if isinstance(other, VersionList):
+ return other.intersects(self)
- # preserve the original string, but trimmed.
- string = string.strip()
- self.string = string
+ return NotImplemented
- if string and not VALID_VERSION.match(string):
- raise ValueError("Bad characters in version string: %s" % string)
+ def union(self, other: Union["ClosedOpenRange", "StandardVersion"]):
+ if isinstance(other, StandardVersion):
+ return self if self == other else VersionList([self, other])
+ return other.union(self)
- self.separators, self.version = self._generate_separators_and_components(string)
+ def intersection(self, other: Union["ClosedOpenRange", "StandardVersion"]):
+ if isinstance(other, StandardVersion):
+ return self if self == other else VersionList()
+ return other.intersection(self)
- def _generate_separators_and_components(self, string):
- segments = SEGMENT_REGEX.findall(string)
- components = tuple(int(m[0]) if m[0] else VersionStrComponent(m[1]) for m in segments)
- separators = tuple(m[2] for m in segments)
- return separators, components
+ def isdevelop(self):
+ """Triggers on the special case of the `@develop-like` version."""
+ return any(
+ isinstance(p, VersionStrComponent) and isinstance(p.data, int) for p in self.version
+ )
@property
def dotted(self):
@@ -265,7 +294,7 @@ class VersionBase(object):
Returns:
Version: The version with separator characters replaced by dots
"""
- return type(self)(self.string.replace("-", ".").replace("_", "."))
+ return type(self).from_string(self.string.replace("-", ".").replace("_", "."))
@property
def underscored(self):
@@ -280,7 +309,7 @@ class VersionBase(object):
Version: The version with separator characters replaced by
underscores
"""
- return type(self)(self.string.replace(".", "_").replace("-", "_"))
+ return type(self).from_string(self.string.replace(".", "_").replace("-", "_"))
@property
def dashed(self):
@@ -294,7 +323,7 @@ class VersionBase(object):
Returns:
Version: The version with separator characters replaced by dashes
"""
- return type(self)(self.string.replace(".", "-").replace("_", "-"))
+ return type(self).from_string(self.string.replace(".", "-").replace("_", "-"))
@property
def joined(self):
@@ -308,7 +337,9 @@ class VersionBase(object):
Returns:
Version: The version with separator characters removed
"""
- return type(self)(self.string.replace(".", "").replace("-", "").replace("_", ""))
+ return type(self).from_string(
+ self.string.replace(".", "").replace("-", "").replace("_", "")
+ )
def up_to(self, index):
"""The version up to the specified component.
@@ -335,178 +366,8 @@ class VersionBase(object):
"""
return self[:index]
- def lowest(self):
- return self
- def highest(self):
- return self
-
- def isdevelop(self):
- """Triggers on the special case of the `@develop-like` version."""
- for inf in infinity_versions:
- for v in self.version:
- if v == inf:
- return True
-
- return False
-
- @coerced
- def intersects(self, other: "VersionBase") -> bool:
- """Return True if self intersects with other, False otherwise.
-
- Two versions intersect if one can be constrained by the other. For instance
- @4.7 and @4.7.3 intersect (the intersection being @4.7.3).
-
- Arg:
- other: version to be checked for intersection
- """
- n = min(len(self.version), len(other.version))
- return self.version[:n] == other.version[:n]
-
- @coerced
- def satisfies(self, other: "VersionBase") -> bool:
- """Return True if self is at least as specific and share a common prefix with other.
-
- For instance, @4.7.3 satisfies @4.7 but not vice-versa.
-
- Arg:
- other: version to be checked for intersection
- """
- nself = len(self.version)
- nother = len(other.version)
- return nother <= nself and self.version[:nother] == other.version
-
- def __iter__(self):
- return iter(self.version)
-
- def __len__(self):
- return len(self.version)
-
- def __getitem__(self, idx):
- cls = type(self)
-
- if isinstance(idx, numbers.Integral):
- return self.version[idx]
-
- elif isinstance(idx, slice):
- string_arg = []
-
- pairs = zip(self.version[idx], self.separators[idx])
- for token, sep in pairs:
- string_arg.append(str(token))
- string_arg.append(str(sep))
-
- if string_arg:
- string_arg.pop() # We don't need the last separator
- string_arg = "".join(string_arg)
- return cls(string_arg)
- else:
- return VersionBase("")
-
- message = "{cls.__name__} indices must be integers"
- raise TypeError(message.format(cls=cls))
-
- def __repr__(self):
- return "VersionBase(" + repr(self.string) + ")"
-
- def __str__(self):
- return self.string
-
- def __format__(self, format_spec):
- return str(self).format(format_spec)
-
- @property
- def concrete(self):
- return self
-
- @coerced
- def __lt__(self, other):
- """Version comparison is designed for consistency with the way RPM
- does things. If you need more complicated versions in installed
- packages, you should override your package's version string to
- express it more sensibly.
- """
- if other is None:
- return False
-
- # Use tuple comparison assisted by VersionStrComponent for performance
- return self.version < other.version
-
- @coerced
- def __eq__(self, other):
- # Cut out early if we don't have a version
- if other is None or type(other) != VersionBase:
- return False
-
- return self.version == other.version
-
- @coerced
- def __ne__(self, other):
- return not (self == other)
-
- @coerced
- def __le__(self, other):
- return self == other or self < other
-
- @coerced
- def __ge__(self, other):
- return not (self < other)
-
- @coerced
- def __gt__(self, other):
- return not (self == other) and not (self < other)
-
- def __hash__(self):
- return hash(self.version)
-
- @coerced
- def __contains__(self, other):
- if other is None:
- return False
-
- return other.version[: len(self.version)] == self.version
-
- @coerced
- def is_predecessor(self, other):
- """True if the other version is the immediate predecessor of this one.
- That is, NO non-git versions v exist such that:
- (self < v < other and v not in self).
- """
- if self.version[:-1] != other.version[:-1]:
- return False
-
- sl = self.version[-1]
- ol = other.version[-1]
- # TODO: extend this to consecutive letters, z/0, and infinity versions
- return type(sl) == int and type(ol) == int and (ol - sl == 1)
-
- @coerced
- def is_successor(self, other):
- return other.is_predecessor(self)
-
- def overlaps(self, other):
- return self.intersects(other)
-
- @coerced
- def union(self, other):
- if self == other or other in self:
- return self
- elif self in other:
- return other
- else:
- return VersionList([self, other])
-
- @coerced
- def intersection(self, other):
- if self in other: # also covers `self == other`
- return self
- elif other in self:
- return other
- else:
- return VersionList()
-
-
-class GitVersion(VersionBase):
+class GitVersion(ConcreteVersion):
"""Class to represent versions interpreted from git refs.
There are two distinct categories of git versions:
@@ -514,208 +375,196 @@ class GitVersion(VersionBase):
1) GitVersions instantiated with an associated reference version (e.g. 'git.foo=1.2')
2) GitVersions requiring commit lookups
- Git ref versions that are not paired with a known version
- are handled separately from all other version comparisons.
- When Spack identifies a git ref version, it associates a
- ``CommitLookup`` object with the version. This object
- handles caching of information from the git repo. When executing
- comparisons with a git ref version, Spack queries the
- ``CommitLookup`` for the most recent version previous to this
- git ref, as well as the distance between them expressed as a number
- of commits. If the previous version is ``X.Y.Z`` and the distance
- is ``D``, the git commit version is represented by the tuple ``(X,
- Y, Z, '', D)``. The component ``''`` cannot be parsed as part of
- any valid version, but is a valid component. This allows a git
- ref version to be less than (older than) every Version newer
- than its previous version, but still newer than its previous
+ Git ref versions that are not paired with a known version are handled separately from
+ all other version comparisons. When Spack identifies a git ref version, it associates a
+ ``CommitLookup`` object with the version. This object handles caching of information
+ from the git repo. When executing comparisons with a git ref version, Spack queries the
+ ``CommitLookup`` for the most recent version previous to this git ref, as well as the
+ distance between them expressed as a number of commits. If the previous version is
+ ``X.Y.Z`` and the distance is ``D``, the git commit version is represented by the
+ tuple ``(X, Y, Z, '', D)``. The component ``''`` cannot be parsed as part of any valid
+ version, but is a valid component. This allows a git ref version to be less than (older
+ than) every Version newer than its previous version, but still newer than its previous
version.
- To find the previous version from a git ref version, Spack
- queries the git repo for its tags. Any tag that matches a version
- known to Spack is associated with that version, as is any tag that
- is a known version prepended with the character ``v`` (i.e., a tag
- ``v1.0`` is associated with the known version
- ``1.0``). Additionally, any tag that represents a semver version
- (X.Y.Z with X, Y, Z all integers) is associated with the version
- it represents, even if that version is not known to Spack. Each
- tag is then queried in git to see whether it is an ancestor of the
- git ref in question, and if so the distance between the two. The
- previous version is the version that is an ancestor with the least
- distance from the git ref in question.
-
- This procedure can be circumvented if the user supplies a known version
- to associate with the GitVersion (e.g. ``[hash]=develop``). If the user
- prescribes the version then there is no need to do a lookup
- and the standard version comparison operations are sufficient.
-
- Non-git versions may be coerced to GitVersion for comparison, but no Spec will ever
- have a GitVersion that is not actually referencing a version from git."""
-
- def __init__(self, string):
- if not isinstance(string, str):
- string = str(string) # In case we got a VersionBase or GitVersion object
-
- # An object that can lookup git refs to compare them to versions
- self.user_supplied_reference = False
- self._ref_lookup = None
- self.ref_version = None
+ To find the previous version from a git ref version, Spack queries the git repo for its
+ tags. Any tag that matches a version known to Spack is associated with that version, as
+ is any tag that is a known version prepended with the character ``v`` (i.e., a tag
+ ``v1.0`` is associated with the known version ``1.0``). Additionally, any tag that
+ represents a semver version (X.Y.Z with X, Y, Z all integers) is associated with the
+ version it represents, even if that version is not known to Spack. Each tag is then
+ queried in git to see whether it is an ancestor of the git ref in question, and if so
+ the distance between the two. The previous version is the version that is an ancestor
+ with the least distance from the git ref in question.
+
+ This procedure can be circumvented if the user supplies a known version to associate
+ with the GitVersion (e.g. ``[hash]=develop``). If the user prescribes the version then
+ there is no need to do a lookup and the standard version comparison operations are
+ sufficient.
+ """
- git_prefix = string.startswith("git.")
- pruned_string = string[4:] if git_prefix else string
+ __slots__ = ["ref", "has_git_prefix", "is_commit", "_ref_lookup", "_ref_version"]
- if "=" in pruned_string:
- self.ref, self.ref_version_str = pruned_string.split("=")
- _, self.ref_version = self._generate_separators_and_components(self.ref_version_str)
- self.user_supplied_reference = True
- else:
- self.ref = pruned_string
-
- self.is_commit = bool(len(self.ref) == 40 and COMMIT_VERSION.match(self.ref))
- self.is_ref = git_prefix # is_ref False only for comparing to VersionBase
- self.is_ref |= bool(self.is_commit)
+ def __init__(self, string: str):
+ # An object that can lookup git refs to compare them to versions
+ self._ref_lookup: Optional[CommitLookup] = None
- # ensure git.<hash> and <hash> are treated the same by dropping 'git.'
- # unless we are assigning a version with =
- canonical_string = self.ref if (self.is_commit and not self.ref_version) else string
- super(GitVersion, self).__init__(canonical_string)
+ # This is the effective version.
+ self._ref_version: Optional[StandardVersion]
- def _cmp(self, other_lookups=None):
- # No need to rely on git comparisons for develop-like refs
- if len(self.version) == 2 and self.isdevelop():
- return self.version
+ self.has_git_prefix = string.startswith("git.")
- # If we've already looked this version up, return cached value
- if self.ref_version is not None:
- return self.ref_version
+ # Drop `git.` prefix
+ normalized_string = string[4:] if self.has_git_prefix else string
- ref_lookup = self.ref_lookup or other_lookups
+ if "=" in normalized_string:
+ # Store the git reference, and parse the user provided version.
+ self.ref, spack_version = normalized_string.split("=")
+ self._ref_version = StandardVersion(
+ spack_version, *parse_string_components(spack_version)
+ )
+ else:
+ # The ref_version is lazily attached after parsing, since we don't know what
+ # package it applies to here.
+ self._ref_version = None
+ self.ref = normalized_string
- if self.is_ref and ref_lookup:
- ref_info = ref_lookup.get(self.ref)
- if ref_info:
- prev_version, distance = ref_info
+ # Used by fetcher
+ self.is_commit: bool = len(self.ref) == 40 and bool(COMMIT_VERSION.match(self.ref))
- if prev_version is None:
- prev_version = "0"
+ @property
+ def ref_version(self) -> StandardVersion:
+ # Return cached version if we have it
+ if self._ref_version is not None:
+ return self._ref_version
+
+ if self.ref_lookup is None:
+ raise VersionLookupError(
+ f"git ref '{self.ref}' cannot be looked up: "
+ "call attach_git_lookup_from_package first"
+ )
- # Extend previous version by empty component and distance
- # If commit is exactly a known version, no distance suffix
- prev_tuple = VersionBase(prev_version).version if prev_version else ()
- dist_suffix = (VersionStrComponent(""), distance) if distance else ()
- self.ref_version = prev_tuple + dist_suffix
- return self.ref_version
+ version_string, distance = self.ref_lookup.get(self.ref)
+ version_string = version_string or "0"
- return self.version
+ # Add a -git.<distance> suffix when we're not exactly on a tag
+ if distance > 0:
+ version_string += f"-git.{distance}"
+ self._ref_version = StandardVersion(
+ version_string, *parse_string_components(version_string)
+ )
+ return self._ref_version
- @coerced
def intersects(self, other):
- # If they are both references, they must match exactly
- if self.is_ref and other.is_ref:
- return self.version == other.version
-
- # Otherwise the ref_version of the reference must intersect with the version of the other
- v1 = self.ref_version if self.is_ref else self.version
- v2 = other.ref_version if other.is_ref else other.version
- n = min(len(v1), len(v2))
- return v1[:n] == v2[:n]
-
- @coerced
- def satisfies(self, other):
- # In the case of two GitVersions we require the ref_versions
- # to satisfy one another and the versions to be an exact match.
-
- self_cmp = self._cmp(other.ref_lookup)
- other_cmp = other._cmp(self.ref_lookup)
-
- if self.is_ref and other.is_ref:
- if self.ref != other.ref:
- return False
- elif self.user_supplied_reference and other.user_supplied_reference:
- return self.ref_version == other.ref_version
- elif other.user_supplied_reference:
- return False
- else:
- # In this case, 'other' does not supply a version equivalence
- # with "=" and the commit strings are equal. 'self' may specify
- # a version equivalence, but that is extra info and will
- # satisfy no matter what it is.
- return True
- elif other.is_ref:
- # if other is a ref then satisfaction requires an exact version match
- # i.e. the GitRef must match this.version for satisfaction
- # this creates an asymmetric comparison:
- # - 'foo@main'.satisfies('foo@git.hash=main') == False
- # - 'foo@git.hash=main'.satisfies('foo@main') == True
- version_match = self.version == other.version
- elif self.is_ref:
- # other is not a ref then it is a version base and we need to compare
- # this.ref
- version_match = self.ref_version == other.version
- else:
- # neither is a git ref. We shouldn't ever be here, but if we are this variable
- # is not meaningful and defaults to true
- version_match = True
+ # For concrete things intersects = satisfies = equality
+ if isinstance(other, GitVersion):
+ return self == other
+ if isinstance(other, StandardVersion):
+ return False
+ if isinstance(other, ClosedOpenRange):
+ return self.ref_version.intersects(other)
+ if isinstance(other, VersionList):
+ return any(self.intersects(rhs) for rhs in other)
+ raise ValueError(f"Unexpected type {type(other)}")
- # Do the final comparison
- nself = len(self_cmp)
- nother = len(other_cmp)
- return nother <= nself and self_cmp[:nother] == other_cmp and version_match
+ def intersection(self, other):
+ if isinstance(other, ConcreteVersion):
+ return self if self == other else VersionList()
+ return other.intersection(self)
- def __repr__(self):
- return "GitVersion(" + repr(self.string) + ")"
+ def overlaps(self, other) -> bool:
+ return self.intersects(other)
- @coerced
- def __lt__(self, other):
- """Version comparison is designed for consistency with the way RPM
- does things. If you need more complicated versions in installed
- packages, you should override your package's version string to
- express it more sensibly.
- """
- if other is None:
+ def satisfies(
+ self, other: Union["GitVersion", StandardVersion, "ClosedOpenRange", "VersionList"]
+ ):
+ # Concrete versions mean we have to do an equality check
+ if isinstance(other, GitVersion):
+ return self == other
+ if isinstance(other, StandardVersion):
return False
+ if isinstance(other, ClosedOpenRange):
+ return self.ref_version.satisfies(other)
+ if isinstance(other, VersionList):
+ return any(self.satisfies(rhs) for rhs in other)
+ raise ValueError(f"Unexpected type {type(other)}")
- # If we haven't indexed yet, can't compare
- # If we called this, we know at least one is a git ref
- if not (self.ref_lookup or other.ref_lookup):
- return False
+ def __str__(self):
+ s = f"git.{self.ref}" if self.has_git_prefix else self.ref
+ # Note: the solver actually depends on str(...) to produce the effective version.
+ # So when a lookup is attached, we require the resolved version to be printed.
+ # But for standalone git versions that don't have a repo attached, it would still
+ # be nice if we could print @<hash>.
+ try:
+ s += f"={self.ref_version}"
+ except VersionLookupError:
+ pass
+ return s
- # Use tuple comparison assisted by VersionStrComponent for performance
- return self._cmp(other.ref_lookup) < other._cmp(self.ref_lookup)
+ def __repr__(self):
+ return f'GitVersion("{self}")'
+
+ def __bool__(self):
+ return True
- @coerced
def __eq__(self, other):
- # Cut out early if we don't have a git version
- if other is None or type(other) != GitVersion:
- return False
+ # GitVersion cannot be equal to StandardVersion, otherwise == is not transitive
+ return (
+ isinstance(other, GitVersion)
+ and self.ref == other.ref
+ and self.ref_version == other.ref_version
+ )
- return self._cmp(other.ref_lookup) == other._cmp(self.ref_lookup)
+ def __ne__(self, other):
+ return not self == other
- def __hash__(self):
- return hash(str(self))
+ def __lt__(self, other):
+ if isinstance(other, GitVersion):
+ return (self.ref_version, self.ref) < (other.ref_version, other.ref)
+ if isinstance(other, StandardVersion):
+ # GitVersion at equal ref version is larger than StandardVersion
+ return self.ref_version < other
+ if isinstance(other, ClosedOpenRange):
+ return self.ref_version < other
+ raise ValueError(f"Unexpected type {type(other)}")
- @coerced
- def __contains__(self, other):
- if other is None:
- return False
+ def __le__(self, other):
+ if isinstance(other, GitVersion):
+ return (self.ref_version, self.ref) <= (other.ref_version, other.ref)
+ if isinstance(other, StandardVersion):
+ # Note: GitVersion hash=1.2.3 > StandardVersion 1.2.3, so use < comparsion.
+ return self.ref_version < other
+ if isinstance(other, ClosedOpenRange):
+ # Equality is not a thing
+ return self.ref_version < other
+ raise ValueError(f"Unexpected type {type(other)}")
- self_cmp = self._cmp(other.ref_lookup)
- return other._cmp(self.ref_lookup)[: len(self_cmp)] == self_cmp
+ def __ge__(self, other):
+ if isinstance(other, GitVersion):
+ return (self.ref_version, self.ref) >= (other.ref_version, other.ref)
+ if isinstance(other, StandardVersion):
+ # Note: GitVersion hash=1.2.3 > StandardVersion 1.2.3, so use >= here.
+ return self.ref_version >= other
+ if isinstance(other, ClosedOpenRange):
+ return self.ref_version > other
+ raise ValueError(f"Unexpected type {type(other)}")
- @coerced
- def is_predecessor(self, other):
- """True if the other version is the immediate predecessor of this one.
- That is, NO non-commit versions v exist such that:
- (self < v < other and v not in self).
- """
- self_cmp = self._cmp(self.ref_lookup)
- other_cmp = other._cmp(other.ref_lookup)
+ def __gt__(self, other):
+ if isinstance(other, GitVersion):
+ return (self.ref_version, self.ref) > (other.ref_version, other.ref)
+ if isinstance(other, StandardVersion):
+ # Note: GitVersion hash=1.2.3 > StandardVersion 1.2.3, so use >= here.
+ return self.ref_version >= other
+ if isinstance(other, ClosedOpenRange):
+ return self.ref_version > other
+ raise ValueError(f"Unexpected type {type(other)}")
- if self_cmp[:-1] != other_cmp[:-1]:
- return False
+ def __hash__(self):
+ # hashing should not cause version lookup
+ return hash(self.ref)
- sl = self_cmp[-1]
- ol = other_cmp[-1]
- return type(sl) == int and type(ol) == int and (ol - sl == 1)
+ def __contains__(self, other):
+ raise Exception("Not implemented yet")
@property
def ref_lookup(self):
@@ -724,7 +573,7 @@ class GitVersion(VersionBase):
self._ref_lookup.get(self.ref)
return self._ref_lookup
- def generate_git_lookup(self, pkg_name):
+ def attach_git_lookup_from_package(self, pkg_name):
"""
Use the git fetcher to look up a version for a commit.
@@ -735,248 +584,177 @@ class GitVersion(VersionBase):
context of the version, we cannot continue. This implementation is
alongside the GitFetcher because eventually the git repos cache will
be one and the same with the source cache.
-
- Args:
- fetcher: the fetcher to use.
- versions: the known versions of the package
"""
+ self._ref_lookup = self._ref_lookup or CommitLookup(pkg_name)
- # Sanity check we have a commit
- if not self.is_ref:
- tty.die("%s is not a git version." % self)
+ def __iter__(self):
+ return self.ref_version.__iter__()
- # don't need a lookup if we already have a version assigned
- if self.ref_version:
- return
+ def __len__(self):
+ return self.ref_version.__len__()
- # Generate a commit looker-upper
- self._ref_lookup = CommitLookup(pkg_name)
+ def __getitem__(self, idx):
+ return self.ref_version.__getitem__(idx)
+ def isdevelop(self):
+ return self.ref_version.isdevelop()
-class VersionRange(object):
- def __init__(self, start, end):
- if isinstance(start, str):
- start = Version(start)
- if isinstance(end, str):
- end = Version(end)
+ @property
+ def dotted(self) -> StandardVersion:
+ return self.ref_version.dotted
- self.start = start
- self.end = end
+ @property
+ def underscored(self) -> StandardVersion:
+ return self.ref_version.underscored
- # Unbounded ranges are not empty
- if not start or not end:
- return
+ @property
+ def dashed(self) -> StandardVersion:
+ return self.ref_version.dashed
- # Do not allow empty ranges. We have to be careful about lexicographical
- # ordering of versions here: 1.2 < 1.2.3 lexicographically, but 1.2.3:1.2
- # means the range [1.2.3, 1.3), which is non-empty.
- min_len = min(len(start), len(end))
- if end.up_to(min_len) < start.up_to(min_len):
- raise ValueError(f"Invalid Version range: {self}")
+ @property
+ def joined(self) -> StandardVersion:
+ return self.ref_version.joined
- def lowest(self):
- return self.start
+ def up_to(self, index) -> StandardVersion:
+ return self.ref_version.up_to(index)
- def highest(self):
- return self.end
- @coerced
- def __lt__(self, other):
- """Sort VersionRanges lexicographically so that they are ordered first
- by start and then by end. None denotes an open range, so None in
- the start position is less than everything except None, and None in
- the end position is greater than everything but None.
- """
- if other is None:
- return False
+class ClosedOpenRange:
+ def __init__(self, lo: StandardVersion, hi: StandardVersion):
+ if hi < lo:
+ raise ValueError(f"{lo}:{hi} is an empty range")
+ self.lo: StandardVersion = lo
+ self.hi: StandardVersion = hi
+
+ @classmethod
+ def from_version_range(cls, lo: StandardVersion, hi: StandardVersion):
+ """Construct ClosedOpenRange from lo:hi range."""
+ return ClosedOpenRange(lo, next_version(hi))
+
+ def __str__(self):
+ # This simplifies 3.1:<3.2 to 3.1:3.1 to 3.1
+ # 3:3 -> 3
+ hi_prev = prev_version(self.hi)
+ if self.lo != StandardVersion.typemin() and self.lo == hi_prev:
+ return str(self.lo)
+ lhs = "" if self.lo == StandardVersion.typemin() else str(self.lo)
+ rhs = "" if hi_prev == StandardVersion.typemax() else str(hi_prev)
+ return f"{lhs}:{rhs}"
+
+ def __repr__(self):
+ return str(self)
- s, o = self, other
- if s.start != o.start:
- return s.start is None or (o.start is not None and s.start < o.start)
- return s.end != o.end and o.end is None or (s.end is not None and s.end < o.end)
+ def __hash__(self):
+ # prev_version for backward compat.
+ return hash((self.lo, prev_version(self.hi)))
- @coerced
def __eq__(self, other):
- return (
- other is not None
- and type(other) == VersionRange
- and self.start == other.start
- and self.end == other.end
- )
+ if isinstance(other, StandardVersion):
+ return False
+ if isinstance(other, ClosedOpenRange):
+ return (self.lo, self.hi) == (other.lo, other.hi)
+ return NotImplemented
- @coerced
def __ne__(self, other):
- return not (self == other)
+ if isinstance(other, StandardVersion):
+ return True
+ if isinstance(other, ClosedOpenRange):
+ return (self.lo, self.hi) != (other.lo, other.hi)
+ return NotImplemented
+
+ def __lt__(self, other):
+ if isinstance(other, StandardVersion):
+ return other > self
+ if isinstance(other, ClosedOpenRange):
+ return (self.lo, self.hi) < (other.lo, other.hi)
+ return NotImplemented
- @coerced
def __le__(self, other):
- return self == other or self < other
+ if isinstance(other, StandardVersion):
+ return other >= self
+ if isinstance(other, ClosedOpenRange):
+ return (self.lo, self.hi) <= (other.lo, other.hi)
+ return NotImplemented
- @coerced
def __ge__(self, other):
- return not (self < other)
+ if isinstance(other, StandardVersion):
+ return other <= self
+ if isinstance(other, ClosedOpenRange):
+ return (self.lo, self.hi) >= (other.lo, other.hi)
+ return NotImplemented
- @coerced
def __gt__(self, other):
- return not (self == other) and not (self < other)
-
- @property
- def concrete(self):
- return self.start if self.start == self.end else None
-
- @coerced
- def __contains__(self, other):
- if other is None:
+ if isinstance(other, StandardVersion):
+ return other < self
+ if isinstance(other, ClosedOpenRange):
+ return (self.lo, self.hi) > (other.lo, other.hi)
+ return NotImplemented
+
+ def __contains__(rhs, lhs):
+ if isinstance(lhs, (ConcreteVersion, ClosedOpenRange, VersionList)):
+ return lhs.satisfies(rhs)
+ raise ValueError(f"Unexpected type {type(lhs)}")
+
+ def intersects(self, other: Union[ConcreteVersion, "ClosedOpenRange", "VersionList"]):
+ if isinstance(other, StandardVersion):
+ return self.lo <= other < self.hi
+ if isinstance(other, GitVersion):
+ return self.lo <= other.ref_version < self.hi
+ if isinstance(other, ClosedOpenRange):
+ return (self.lo < other.hi) and (other.lo < self.hi)
+ if isinstance(other, VersionList):
+ return any(self.intersects(rhs) for rhs in other)
+ raise ValueError(f"Unexpected type {type(other)}")
+
+ def satisfies(self, other: Union["ClosedOpenRange", ConcreteVersion, "VersionList"]):
+ if isinstance(other, ConcreteVersion):
return False
+ if isinstance(other, ClosedOpenRange):
+ return not (self.lo < other.lo or other.hi < self.hi)
+ if isinstance(other, VersionList):
+ return any(self.satisfies(rhs) for rhs in other)
+ raise ValueError(other)
- in_lower = (
- self.start == other.start
- or self.start is None
- or (
- other.start is not None and (self.start < other.start or other.start in self.start)
- )
- )
- if not in_lower:
- return False
-
- in_upper = (
- self.end == other.end
- or self.end is None
- or (other.end is not None and (self.end > other.end or other.end in self.end))
- )
- return in_upper
-
- def intersects(self, other) -> bool:
- """Return two if two version ranges overlap with each other, False otherwise.
-
- This is a commutative operation.
-
- Examples:
- - 1:3 satisfies 2:4, as their intersection is 2:3.
- - 1:2 does not satisfy 3:4, as their intersection is empty.
- - 4.5:4.7 satisfies 4.7.2:4.8, as their intersection is 4.7.2:4.7
-
- Args:
- other: version range to be checked for intersection
- """
- return self.overlaps(other)
+ def overlaps(self, other: Union["ClosedOpenRange", ConcreteVersion, "VersionList"]) -> bool:
+ return self.intersects(other)
- @coerced
- def satisfies(self, other):
- """A version range satisfies another if it is a subset of the other.
+ def union(self, other: Union["ClosedOpenRange", ConcreteVersion, "VersionList"]):
+ if isinstance(other, StandardVersion):
+ return self if self.lo <= other < self.hi else VersionList([self, other])
- Examples:
- - 1:2 does not satisfy 3:4, as their intersection is empty.
- - 1:3 does not satisfy 2:4, as they overlap but neither is a subset of the other
- - 1:3 satisfies 1:4.
- """
- return self.intersection(other) == self
+ if isinstance(other, GitVersion):
+ return self if self.lo <= other.ref_version < self.hi else VersionList([self, other])
- @coerced
- def overlaps(self, other):
- return (
- self.start is None
- or other.end is None
- or self.start <= other.end
- or other.end in self.start
- or self.start in other.end
- ) and (
- other.start is None
- or self.end is None
- or other.start <= self.end
- or other.start in self.end
- or self.end in other.start
- )
-
- @coerced
- def union(self, other):
- if not self.overlaps(other):
- if (
- self.end is not None
- and other.start is not None
- and self.end.is_predecessor(other.start)
- ):
- return VersionRange(self.start, other.end)
-
- if (
- other.end is not None
- and self.start is not None
- and other.end.is_predecessor(self.start)
- ):
- return VersionRange(other.start, self.end)
+ if isinstance(other, ClosedOpenRange):
+ # Notice <= cause we want union(1:2, 3:4) = 1:4.
+ if self.lo <= other.hi and other.lo <= self.hi:
+ return ClosedOpenRange(min(self.lo, other.lo), max(self.hi, other.hi))
return VersionList([self, other])
- # if we're here, then we know the ranges overlap.
- if self.start is None or other.start is None:
- start = None
- else:
- start = self.start
- # TODO: See note in intersection() about < and in discrepancy.
- if self.start in other.start or other.start < self.start:
- start = other.start
+ if isinstance(other, VersionList):
+ v = other.copy()
+ v.add(self)
+ return v
- if self.end is None or other.end is None:
- end = None
- else:
- end = self.end
- # TODO: See note in intersection() about < and in discrepancy.
- if other.end not in self.end:
- if end in other.end or other.end > self.end:
- end = other.end
+ raise ValueError(f"Unexpected type {type(other)}")
- return VersionRange(start, end)
+ def intersection(self, other: Union["ClosedOpenRange", ConcreteVersion]):
+ # range - version -> singleton or nothing.
+ if isinstance(other, ConcreteVersion):
+ return other if self.intersects(other) else VersionList()
- @coerced
- def intersection(self, other):
- if not self.overlaps(other):
- return VersionList()
+ # range - range -> range or nothing.
+ max_lo = max(self.lo, other.lo)
+ min_hi = min(self.hi, other.hi)
+ return ClosedOpenRange(max_lo, min_hi) if max_lo < min_hi else VersionList()
- if self.start is None:
- start = other.start
- else:
- start = self.start
- if other.start is not None:
- if other.start > start or other.start in start:
- start = other.start
- if self.end is None:
- end = other.end
- else:
- end = self.end
- # TODO: does this make sense?
- # This is tricky:
- # 1.6.5 in 1.6 = True (1.6.5 is more specific)
- # 1.6 < 1.6.5 = True (lexicographic)
- # Should 1.6 NOT be less than 1.6.5? Hmm.
- # Here we test (not end in other.end) first to avoid paradox.
- if other.end is not None and end not in other.end:
- if other.end < end or other.end in end:
- end = other.end
-
- return VersionRange(start, end)
-
- def __hash__(self):
- return hash((self.start, self.end))
-
- def __repr__(self):
- return self.__str__()
-
- def __str__(self):
- out = ""
- if self.start:
- out += str(self.start)
- out += ":"
- if self.end:
- out += str(self.end)
- return out
-
-
-class VersionList(object):
- """Sorted, non-redundant list of Versions and VersionRanges."""
+class VersionList:
+ """Sorted, non-redundant list of Version and ClosedOpenRange elements."""
def __init__(self, vlist=None):
- self.versions = []
+ self.versions: List[StandardVersion, GitVersion, ClosedOpenRange] = []
if vlist is not None:
if isinstance(vlist, str):
vlist = from_string(vlist)
@@ -988,95 +766,112 @@ class VersionList(object):
for v in vlist:
self.add(ver(v))
- def add(self, version):
- if type(version) in (VersionBase, GitVersion, VersionRange):
- # This normalizes single-value version ranges.
- if version.concrete:
- version = version.concrete
+ def add(self, item):
+ if isinstance(item, ConcreteVersion):
+ i = bisect_left(self, item)
+ # Only insert when prev and next are not intersected.
+ if (i == 0 or not item.intersects(self[i - 1])) and (
+ i == len(self) or not item.intersects(self[i])
+ ):
+ self.versions.insert(i, item)
- i = bisect_left(self, version)
+ elif isinstance(item, ClosedOpenRange):
+ i = bisect_left(self, item)
- while i - 1 >= 0 and version.overlaps(self[i - 1]):
- version = version.union(self[i - 1])
+ # Note: can span multiple concrete versions to the left,
+ # For instance insert 1.2: into [1.2, hash=1.2, 1.3]
+ # would bisect to i = 1.
+ while i > 0 and item.intersects(self[i - 1]):
+ item = item.union(self[i - 1])
del self.versions[i - 1]
i -= 1
- while i < len(self) and version.overlaps(self[i]):
- version = version.union(self[i])
+ while i < len(self) and item.intersects(self[i]):
+ item = item.union(self[i])
del self.versions[i]
- self.versions.insert(i, version)
+ self.versions.insert(i, item)
- elif type(version) == VersionList:
- for v in version:
+ elif type(item) == VersionList:
+ for v in item:
self.add(v)
else:
- raise TypeError("Can't add %s to VersionList" % type(version))
+ raise TypeError("Can't add %s to VersionList" % type(item))
@property
- def concrete(self):
- if len(self) == 1:
- return self[0].concrete
- else:
+ def concrete(self) -> Optional[ConcreteVersion]:
+ return self[0] if len(self) == 1 and isinstance(self[0], ConcreteVersion) else None
+
+ @property
+ def concrete_range_as_version(self) -> Optional[ConcreteVersion]:
+ """Like concrete, but collapses VersionRange(x, x) to Version(x).
+ This is just for compatibility with old Spack."""
+ if len(self) != 1:
return None
+ v = self[0]
+ if isinstance(v, ConcreteVersion):
+ return v
+ if isinstance(v, ClosedOpenRange) and next_version(v.lo) == v.hi:
+ return v.lo
+ return None
def copy(self):
return VersionList(self)
- def lowest(self):
+ def lowest(self) -> Optional[StandardVersion]:
"""Get the lowest version in the list."""
- if not self:
- return None
- else:
- return self[0].lowest()
+ return None if not self else self[0]
- def highest(self):
+ def highest(self) -> Optional[StandardVersion]:
"""Get the highest version in the list."""
- if not self:
- return None
- else:
- return self[-1].highest()
+ return None if not self else self[-1]
- def highest_numeric(self):
+ def highest_numeric(self) -> Optional[StandardVersion]:
"""Get the highest numeric version in the list."""
numeric_versions = list(filter(lambda v: str(v) not in infinity_versions, self.versions))
- if not any(numeric_versions):
- return None
- else:
- return numeric_versions[-1].highest()
+ return None if not any(numeric_versions) else numeric_versions[-1]
- def preferred(self):
+ def preferred(self) -> Optional[StandardVersion]:
"""Get the preferred (latest) version in the list."""
- latest = self.highest_numeric()
- if latest is None:
- latest = self.highest()
- return latest
-
- @coerced
- def overlaps(self, other):
- if not other or not self:
- return False
+ return self.highest_numeric() or self.highest()
- s = o = 0
- while s < len(self) and o < len(other):
- if self[s].overlaps(other[o]):
- return True
- elif self[s] < other[o]:
- s += 1
- else:
- o += 1
- return False
+ def satisfies(self, other) -> bool:
+ # This exploits the fact that version lists are "reduced" and normalized, so we can
+ # never have a list like [1:3, 2:4] since that would be normalized to [1:4]
+ if isinstance(other, VersionList):
+ return all(any(lhs.satisfies(rhs) for rhs in other) for lhs in self)
+
+ if isinstance(other, (ConcreteVersion, ClosedOpenRange)):
+ return all(lhs.satisfies(other) for lhs in self)
+
+ raise ValueError(f"Unsupported type {type(other)}")
def intersects(self, other):
- return self.overlaps(other)
+ if isinstance(other, VersionList):
+ s = o = 0
+ while s < len(self) and o < len(other):
+ if self[s].intersects(other[o]):
+ return True
+ elif self[s] < other[o]:
+ s += 1
+ else:
+ o += 1
+ return False
+
+ if isinstance(other, (ClosedOpenRange, StandardVersion)):
+ return any(v.intersects(other) for v in self)
+
+ raise ValueError(f"Unsupported type {type(other)}")
+
+ def overlaps(self, other) -> bool:
+ return self.intersects(other)
def to_dict(self):
"""Generate human-readable dict for YAML."""
if self.concrete:
return syaml_dict([("version", str(self[0]))])
- else:
- return syaml_dict([("versions", [str(v) for v in self])])
+ return syaml_dict([("versions", [str(v) for v in self])])
@staticmethod
def from_dict(dictionary):
@@ -1084,38 +879,30 @@ class VersionList(object):
if "versions" in dictionary:
return VersionList(dictionary["versions"])
elif "version" in dictionary:
- return VersionList([dictionary["version"]])
- else:
- raise ValueError("Dict must have 'version' or 'versions' in it.")
+ return VersionList([Version(dictionary["version"])])
+ raise ValueError("Dict must have 'version' or 'versions' in it.")
- @coerced
- def satisfies(self, other) -> bool:
- # This exploits the fact that version lists are "reduced" and normalized, so we can
- # never have a list like [1:3, 2:4] since that would be normalized to [1:4]
- return all(any(lhs.satisfies(rhs) for rhs in other) for lhs in self)
-
- @coerced
- def update(self, other):
+ def update(self, other: "VersionList"):
for v in other.versions:
self.add(v)
- @coerced
- def union(self, other):
+ def union(self, other: "VersionList"):
result = self.copy()
result.update(other)
return result
- @coerced
- def intersection(self, other):
- # TODO: make this faster. This is O(n^2).
+ def intersection(self, other: "VersionList") -> "VersionList":
result = VersionList()
- for s in self:
- for o in other:
- result.add(s.intersection(o))
+ for lhs, rhs in ((self, other), (other, self)):
+ for x in lhs:
+ i = bisect_left(rhs.versions, x)
+ if i > 0:
+ result.add(rhs[i - 1].intersection(x))
+ if i < len(rhs):
+ result.add(rhs[i].intersection(x))
return result
- @coerced
- def intersect(self, other):
+ def intersect(self, other) -> bool:
"""Intersect this spec's list with other.
Return True if the spec changed as a result; False otherwise
@@ -1125,20 +912,15 @@ class VersionList(object):
self.versions = isection.versions
return changed
- @coerced
def __contains__(self, other):
- if len(self) == 0:
- return False
-
- for version in other:
+ if isinstance(other, (ClosedOpenRange, StandardVersion)):
i = bisect_left(self, other)
- if i == 0:
- if version not in self[0]:
- return False
- elif all(version not in v for v in self[i - 1 :]):
- return False
+ return (i > 0 and other in self[i - 1]) or (i < len(self) and other in self[i])
- return True
+ if isinstance(other, VersionList):
+ return all(item in self for item in other)
+
+ return False
def __getitem__(self, index):
return self.versions[index]
@@ -1155,60 +937,196 @@ class VersionList(object):
def __bool__(self):
return bool(self.versions)
- @coerced
def __eq__(self, other):
- return other is not None and self.versions == other.versions
+ if isinstance(other, VersionList):
+ return self.versions == other.versions
+ return False
- @coerced
def __ne__(self, other):
- return not (self == other)
+ if isinstance(other, VersionList):
+ return self.versions != other.versions
+ return False
- @coerced
def __lt__(self, other):
- return other is not None and self.versions < other.versions
+ if isinstance(other, VersionList):
+ return self.versions < other.versions
+ return NotImplemented
- @coerced
def __le__(self, other):
- return self == other or self < other
+ if isinstance(other, VersionList):
+ return self.versions <= other.versions
+ return NotImplemented
- @coerced
def __ge__(self, other):
- return not (self < other)
+ if isinstance(other, VersionList):
+ return self.versions >= other.versions
+ return NotImplemented
- @coerced
def __gt__(self, other):
- return not (self == other) and not (self < other)
+ if isinstance(other, VersionList):
+ return self.versions > other.versions
+ return NotImplemented
def __hash__(self):
return hash(tuple(self.versions))
def __str__(self):
- return ",".join(str(v) for v in self.versions)
+ return ",".join(
+ f"={v}" if isinstance(v, StandardVersion) else str(v) for v in self.versions
+ )
def __repr__(self):
return str(self.versions)
-def from_string(string):
- """Converts a string to a Version, VersionList, or VersionRange.
- This is private. Client code should use ver().
+def next_str(s: str) -> str:
+ """Produce the next string of A-Z and a-z characters"""
+ return (
+ (s + "A")
+ if (len(s) == 0 or s[-1] == "z")
+ else s[:-1] + ("a" if s[-1] == "Z" else chr(ord(s[-1]) + 1))
+ )
+
+
+def prev_str(s: str) -> str:
+ """Produce the previous string of A-Z and a-z characters"""
+ return (
+ s[:-1]
+ if (len(s) == 0 or s[-1] == "A")
+ else s[:-1] + ("Z" if s[-1] == "a" else chr(ord(s[-1]) - 1))
+ )
+
+
+def next_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
+ """
+ Produce the next VersionStrComponent, where
+ masteq -> mastes
+ master -> main
+ """
+ # First deal with the infinity case.
+ data = v.data
+ if isinstance(data, int):
+ return VersionStrComponent(data + 1)
+
+ # Find the next non-infinity string.
+ while True:
+ data = next_str(data)
+ if data not in infinity_versions:
+ break
+
+ return VersionStrComponent(data)
+
+
+def prev_version_str_component(v: VersionStrComponent) -> VersionStrComponent:
"""
+ Produce the previous VersionStrComponent, where
+ mastes -> masteq
+ master -> head
+ """
+ # First deal with the infinity case. Allow underflows
+ data = v.data
+ if isinstance(data, int):
+ return VersionStrComponent(data - 1)
+
+ # Find the next string.
+ while True:
+ data = prev_str(data)
+ if data not in infinity_versions:
+ break
+
+ return VersionStrComponent(data)
+
+
+def next_version(v: StandardVersion) -> StandardVersion:
+ if len(v.version) == 0:
+ nxt = VersionStrComponent("A")
+ elif isinstance(v.version[-1], VersionStrComponent):
+ nxt = next_version_str_component(v.version[-1])
+ else:
+ nxt = v.version[-1] + 1
+
+ # Construct a string-version for printing
+ string_components = []
+ for part, sep in zip(v.version[:-1], v.separators):
+ string_components.append(str(part))
+ string_components.append(str(sep))
+ string_components.append(str(nxt))
+
+ return StandardVersion("".join(string_components), v.version[:-1] + (nxt,), v.separators)
+
+
+def prev_version(v: StandardVersion) -> StandardVersion:
+ if len(v.version) == 0:
+ return v
+ elif isinstance(v.version[-1], VersionStrComponent):
+ prev = prev_version_str_component(v.version[-1])
+ else:
+ prev = v.version[-1] - 1
+
+ # Construct a string-version for printing
+ string_components = []
+ for part, sep in zip(v.version[:-1], v.separators):
+ string_components.append(str(part))
+ string_components.append(str(sep))
+ string_components.append(str(prev))
+
+ return StandardVersion("".join(string_components), v.version[:-1] + (prev,), v.separators)
+
+
+def is_git_version(string: str) -> bool:
+ return (
+ string.startswith("git.")
+ or len(string) == 40
+ and bool(COMMIT_VERSION.match(string))
+ or "=" in string[1:]
+ )
+
+
+def Version(string: Union[str, int]) -> Union[GitVersion, StandardVersion]:
+ if not isinstance(string, (str, int)):
+ raise ValueError(f"Cannot construct a version from {type(string)}")
+ string = str(string)
+ if is_git_version(string):
+ return GitVersion(string)
+ return StandardVersion.from_string(str(string))
+
+
+def VersionRange(lo: Union[str, StandardVersion], hi: Union[str, StandardVersion]):
+ lo = lo if isinstance(lo, StandardVersion) else StandardVersion.from_string(lo)
+ hi = hi if isinstance(hi, StandardVersion) else StandardVersion.from_string(hi)
+ return ClosedOpenRange.from_version_range(lo, hi)
+
+
+def from_string(string) -> Union[VersionList, ClosedOpenRange, StandardVersion, GitVersion]:
+ """Converts a string to a version object. This is private. Client code should use ver()."""
string = string.replace(" ", "")
+ # VersionList
if "," in string:
- return VersionList(string.split(","))
+ return VersionList(list(map(from_string, string.split(","))))
+ # ClosedOpenRange
elif ":" in string:
s, e = string.split(":")
- start = Version(s) if s else None
- end = Version(e) if e else None
- return VersionRange(start, end)
+ lo = StandardVersion.typemin() if s == "" else StandardVersion.from_string(s)
+ hi = StandardVersion.typemax() if e == "" else StandardVersion.from_string(e)
+ return VersionRange(lo, hi)
+
+ # StandardVersion
+ elif string.startswith("="):
+ # @=1.2.3 is an exact version
+ return Version(string[1:])
+
+ elif is_git_version(string):
+ return GitVersion(string)
else:
- return Version(string)
+ # @1.2.3 is short for 1.2.3:1.2.3
+ v = StandardVersion.from_string(string)
+ return VersionRange(v, v)
-def ver(obj):
+def ver(obj) -> Union[VersionList, ClosedOpenRange, StandardVersion, GitVersion]:
"""Parses a Version, VersionRange, or VersionList from a string
or list of strings.
"""
@@ -1218,12 +1136,16 @@ def ver(obj):
return from_string(obj)
elif isinstance(obj, (int, float)):
return from_string(str(obj))
- elif type(obj) in (VersionBase, GitVersion, VersionRange, VersionList):
+ elif isinstance(obj, (StandardVersion, GitVersion, ClosedOpenRange, VersionList)):
return obj
else:
raise TypeError("ver() can't convert %s to version!" % type(obj))
+#: This version contains all possible versions.
+any_version: VersionList = VersionList([":"])
+
+
class VersionError(spack.error.SpackError):
"""This is raised when something is wrong with a version."""
@@ -1239,16 +1161,15 @@ class VersionLookupError(VersionError):
class CommitLookup(object):
"""An object for cached lookups of git commits
- CommitLookup objects delegate to the misc_cache for locking.
- CommitLookup objects may be attached to a GitVersion object for which
- Version.is_ref returns True to allow for comparisons between git refs
- and versions as represented by tags in the git repository.
+ CommitLookup objects delegate to the misc_cache for locking. CommitLookup objects may
+ be attached to a GitVersion to allow for comparisons between git refs and versions as
+ represented by tags in the git repository.
"""
def __init__(self, pkg_name):
self.pkg_name = pkg_name
- self.data = {}
+ self.data: Dict[str, Tuple[Optional[str], int]] = {}
self._pkg = None
self._fetcher = None
@@ -1281,7 +1202,14 @@ class CommitLookup(object):
@property
def pkg(self):
if not self._pkg:
- self._pkg = spack.repo.path.get_pkg_class(self.pkg_name)
+ import spack.repo # break cycle
+
+ try:
+ pkg = spack.repo.path.get_pkg_class(self.pkg_name)
+ pkg.git
+ except (spack.repo.RepoError, AttributeError) as e:
+ raise VersionLookupError(f"Couldn't get the git repo for {self.pkg_name}") from e
+ self._pkg = pkg
return self._pkg
@property
@@ -1297,10 +1225,7 @@ class CommitLookup(object):
@property
def repository_uri(self):
- """
- Identifier for git repos used within the repo and metadata caches.
-
- """
+ """Identifier for git repos used within the repo and metadata caches."""
try:
components = [
str(c).lstrip("/") for c in spack.util.url.parse_git_url(self.pkg.git) if c
@@ -1311,21 +1236,17 @@ class CommitLookup(object):
return os.path.abspath(self.pkg.git)
def save(self):
- """
- Save the data to file
- """
+ """Save the data to file"""
with spack.caches.misc_cache.write_transaction(self.cache_key) as (old, new):
sjson.dump(self.data, new)
def load_data(self):
- """
- Load data if the path already exists.
- """
+ """Load data if the path already exists."""
if os.path.isfile(self.cache_path):
with spack.caches.misc_cache.read_transaction(self.cache_key) as cache_file:
self.data = sjson.load(cache_file)
- def get(self, ref):
+ def get(self, ref) -> Tuple[Optional[str], int]:
if not self.data:
self.load_data()
@@ -1335,7 +1256,7 @@ class CommitLookup(object):
return self.data[ref]
- def lookup_ref(self, ref):
+ def lookup_ref(self, ref) -> Tuple[Optional[str], int]:
"""Lookup the previous version and distance for a given commit.
We use git to compare the known versions from package to the git tags,
@@ -1413,10 +1334,9 @@ class CommitLookup(object):
).strip()
ancestor_commits.append((tag_commit, int(distance)))
- # Get nearest ancestor that is a known version
- ancestor_commits.sort(key=lambda x: x[1])
if ancestor_commits:
- prev_version_commit, distance = ancestor_commits[0]
+ # Get nearest ancestor that is a known version
+ prev_version_commit, distance = min(ancestor_commits, key=lambda x: x[1])
prev_version = commit_to_version[prev_version_commit]
else:
# Get list of all commits, this is in reverse order