Environments: store spack version/commit in spack.lock (#32801)

Add a section to the lock file to track the Spack version/commit that produced
an environment. This should (eventually) enhance reproducibility, though we
do not currently do anything with the information. It just adds to provenance
at the moment.

Changes include:
- [x] adding the version/commit to `spack.lock`
- [x] refactor `spack.main.get_version()
- [x] fix a couple of environment lock file-related typos
This commit is contained in:
Tamara Dahlgren 2023-05-11 20:13:36 -07:00 committed by GitHub
parent b06d20be19
commit 8e18297cf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 25 deletions

View File

@ -16,18 +16,24 @@
The high-level format of a Spack lockfile hasn't changed much between versions, but the The high-level format of a Spack lockfile hasn't changed much between versions, but the
contents have. Lockfiles are JSON-formatted and their top-level sections are: contents have. Lockfiles are JSON-formatted and their top-level sections are:
1. ``_meta`` (object): this contains deatails about the file format, including: 1. ``_meta`` (object): this contains details about the file format, including:
* ``file-type``: always ``"spack-lockfile"`` * ``file-type``: always ``"spack-lockfile"``
* ``lockfile-version``: an integer representing the lockfile format version * ``lockfile-version``: an integer representing the lockfile format version
* ``specfile-version``: an integer representing the spec format version (since * ``specfile-version``: an integer representing the spec format version (since
``v0.17``) ``v0.17``)
2. ``roots`` (list): an ordered list of records representing the roots of the Spack 2. ``spack`` (object): optional, this identifies information about Spack
used to concretize the environment:
* ``type``: required, identifies form Spack version took (e.g., ``git``, ``release``)
* ``commit``: the commit if the version is from git
* ``version``: the Spack version
3. ``roots`` (list): an ordered list of records representing the roots of the Spack
environment. Each has two fields: environment. Each has two fields:
* ``hash``: a Spack spec hash uniquely identifying the concrete root spec * ``hash``: a Spack spec hash uniquely identifying the concrete root spec
* ``spec``: a string representation of the abstract spec that was concretized * ``spec``: a string representation of the abstract spec that was concretized
3. ``concrete_specs``: a dictionary containing the specs in the environment. 4. ``concrete_specs``: a dictionary containing the specs in the environment.
Compatibility Compatibility
------------- -------------
@ -271,6 +277,8 @@
Dependencies are keyed by ``hash`` (DAG hash) as well. There are no more ``build_hash`` Dependencies are keyed by ``hash`` (DAG hash) as well. There are no more ``build_hash``
fields in the specs, and there are no more issues with lockfiles being able to store fields in the specs, and there are no more issues with lockfiles being able to store
multiple specs with the same DAG hash (because the DAG hash is now finer-grained). multiple specs with the same DAG hash (because the DAG hash is now finer-grained).
An optional ``spack`` property may be included to track version information, such as
the commit or version.
.. code-block:: json .. code-block:: json
@ -278,8 +286,8 @@
{ {
"_meta": { "_meta": {
"file-type": "spack-lockfile", "file-type": "spack-lockfile",
"lockfile-version": 3, "lockfile-version": 4,
"specfile-version": 2 "specfile-version": 3
}, },
"roots": [ "roots": [
{ {
@ -326,7 +334,6 @@
} }
} }
} }
""" """
from .environment import ( from .environment import (

View File

@ -31,6 +31,7 @@
import spack.error import spack.error
import spack.hash_types as ht import spack.hash_types as ht
import spack.hooks import spack.hooks
import spack.main
import spack.paths import spack.paths
import spack.repo import spack.repo
import spack.schema.env import spack.schema.env
@ -2072,6 +2073,14 @@ def _to_lockfile_dict(self):
hash_spec_list = zip(self.concretized_order, self.concretized_user_specs) hash_spec_list = zip(self.concretized_order, self.concretized_user_specs)
spack_dict = {"version": spack.spack_version}
spack_commit = spack.main.get_spack_commit()
if spack_commit:
spack_dict["type"] = "git"
spack_dict["commit"] = spack_commit
else:
spack_dict["type"] = "release"
# this is the lockfile we'll write out # this is the lockfile we'll write out
data = { data = {
# metadata about the format # metadata about the format
@ -2080,6 +2089,8 @@ def _to_lockfile_dict(self):
"lockfile-version": lockfile_format_version, "lockfile-version": lockfile_format_version,
"specfile-version": spack.spec.SPECFILE_FORMAT_VERSION, "specfile-version": spack.spec.SPECFILE_FORMAT_VERSION,
}, },
# spack version information
"spack": spack_dict,
# users specs + hashes are the 'roots' of the environment # users specs + hashes are the 'roots' of the environment
"roots": [{"hash": h, "spec": str(s)} for h, s in hash_spec_list], "roots": [{"hash": h, "spec": str(s)} for h, s in hash_spec_list],
# Concrete specs by hash, including dependencies # Concrete specs by hash, including dependencies

View File

@ -126,19 +126,20 @@ def add_all_commands(parser):
parser.add_command(cmd) parser.add_command(cmd)
def get_version(): def get_spack_commit():
"""Get a descriptive version of this instance of Spack. """Get the Spack git commit sha.
Outputs '<PEP440 version> (<git commit sha>)'. Returns:
(str or None) the commit sha if available, otherwise None
The commit sha is only added when available.
""" """
version = spack.spack_version
git_path = os.path.join(spack.paths.prefix, ".git") git_path = os.path.join(spack.paths.prefix, ".git")
if os.path.exists(git_path): if not os.path.exists(git_path):
return None
git = spack.util.git.git() git = spack.util.git.git()
if not git: if not git:
return version return None
rev = git( rev = git(
"-C", "-C",
spack.paths.prefix, spack.paths.prefix,
@ -149,10 +150,23 @@ def get_version():
fail_on_error=False, fail_on_error=False,
) )
if git.returncode != 0: if git.returncode != 0:
return version return None
match = re.match(r"[a-f\d]{7,}$", rev) match = re.match(r"[a-f\d]{7,}$", rev)
if match: return match.group(0) if match else None
version += " ({0})".format(match.group(0))
def get_version():
"""Get a descriptive version of this instance of Spack.
Outputs '<PEP440 version> (<git commit sha>)'.
The commit sha is only added when available.
"""
version = spack.spack_version
commit = get_spack_commit()
if commit:
version += " ({0})".format(commit)
return version return version

View File

@ -7,6 +7,7 @@
import pytest import pytest
import spack.environment as ev import spack.environment as ev
from spack import spack_version
from spack.main import SpackCommand from spack.main import SpackCommand
pytestmark = pytest.mark.usefixtures("config", "mutable_mock_repo") pytestmark = pytest.mark.usefixtures("config", "mutable_mock_repo")
@ -54,3 +55,6 @@ def test_concretize_root_test_dependencies_are_concretized(unify, mutable_mock_e
add("b") add("b")
concretize("--test", "root") concretize("--test", "root")
assert e.matching_spec("test-dependency") assert e.matching_spec("test-dependency")
data = e._to_lockfile_dict()
assert data["spack"]["version"] == spack_version