summaryrefslogtreecommitdiff
path: root/lib/spack/spack/database.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/spack/database.py')
-rw-r--r--lib/spack/spack/database.py189
1 files changed, 140 insertions, 49 deletions
diff --git a/lib/spack/spack/database.py b/lib/spack/spack/database.py
index fd4aae00cd..c857802ab9 100644
--- a/lib/spack/spack/database.py
+++ b/lib/spack/spack/database.py
@@ -39,6 +39,8 @@ provides a cache and a sanity checking mechanism for what is in the
filesystem.
"""
+import datetime
+import time
import os
import sys
import socket
@@ -76,6 +78,11 @@ _db_lock_timeout = 60
_tracked_deps = ('link', 'run')
+def _now():
+ """Returns the time since the epoch"""
+ return time.time()
+
+
def _autospec(function):
"""Decorator that automatically converts the argument of a single-arg
function to a Spec."""
@@ -103,14 +110,31 @@ class InstallRecord(object):
actually remove from the database until a spec has no installed
dependents left.
+ Args:
+ spec (Spec): spec tracked by the install record
+ path (str): path where the spec has been installed
+ installed (bool): whether or not the spec is currently installed
+ ref_count (int): number of specs that depend on this one
+ explicit (bool, optional): whether or not this spec was explicitly
+ installed, or pulled-in as a dependency of something else
+ installation_time (time, optional): time of the installation
"""
- def __init__(self, spec, path, installed, ref_count=0, explicit=False):
+ def __init__(
+ self,
+ spec,
+ path,
+ installed,
+ ref_count=0,
+ explicit=False,
+ installation_time=None
+ ):
self.spec = spec
self.path = str(path)
self.installed = bool(installed)
self.ref_count = ref_count
self.explicit = explicit
+ self.installation_time = installation_time or _now()
def to_dict(self):
return {
@@ -118,14 +142,15 @@ class InstallRecord(object):
'path': self.path,
'installed': self.installed,
'ref_count': self.ref_count,
- 'explicit': self.explicit
+ 'explicit': self.explicit,
+ 'installation_time': self.installation_time
}
@classmethod
def from_dict(cls, spec, dictionary):
- d = dictionary
- return InstallRecord(spec, d['path'], d['installed'], d['ref_count'],
- d.get('explicit', False))
+ d = dict(dictionary.items())
+ d.pop('spec', None)
+ return InstallRecord(spec, **d)
class Database(object):
@@ -347,7 +372,7 @@ class Database(object):
def invalid_record(hash_key, error):
msg = ("Invalid record in Spack database: "
"hash: %s, cause: %s: %s")
- msg %= (hash_key, type(e).__name__, str(e))
+ msg %= (hash_key, type(error).__name__, str(error))
raise CorruptDatabaseError(msg, self._index_path)
# Build up the database in three passes:
@@ -442,12 +467,18 @@ class Database(object):
tty.debug(
'RECONSTRUCTING FROM SPEC.YAML: {0}'.format(spec))
explicit = True
+ inst_time = os.stat(spec.prefix).st_ctime
if old_data is not None:
old_info = old_data.get(spec.dag_hash())
if old_info is not None:
explicit = old_info.explicit
+ inst_time = old_info.installation_time
- self._add(spec, directory_layout, explicit=explicit)
+ extra_args = {
+ 'explicit': explicit,
+ 'installation_time': inst_time
+ }
+ self._add(spec, directory_layout, **extra_args)
processed_specs.add(spec)
@@ -479,7 +510,8 @@ class Database(object):
kwargs = {
'spec': entry.spec,
'directory_layout': layout,
- 'explicit': entry.explicit
+ 'explicit': entry.explicit,
+ 'installation_time': entry.installation_time # noqa: E501
}
self._add(**kwargs)
processed_specs.add(entry.spec)
@@ -579,23 +611,52 @@ class Database(object):
self._write(None, None, None)
self.reindex(spack.store.layout)
- def _add(self, spec, directory_layout=None, explicit=False):
+ def _add(
+ self,
+ spec,
+ directory_layout=None,
+ explicit=False,
+ installation_time=None
+ ):
"""Add an install record for this spec to the database.
Assumes spec is installed in ``layout.path_for_spec(spec)``.
Also ensures dependencies are present and updated in the DB as
- either intsalled or missing.
+ either installed or missing.
+
+ Args:
+ spec: spec to be added
+ directory_layout: layout of the spec installation
+ **kwargs:
+
+ explicit
+ Possible values: True, False, any
+
+ A spec that was installed following a specific user
+ request is marked as explicit. If instead it was
+ pulled-in as a dependency of a user requested spec
+ it's considered implicit.
+
+ installation_time
+ Date and time of installation
"""
if not spec.concrete:
raise NonConcreteSpecAddError(
"Specs added to DB must be concrete.")
+ # Retrieve optional arguments
+ installation_time = installation_time or _now()
+
for dep in spec.dependencies(_tracked_deps):
dkey = dep.dag_hash()
if dkey not in self._data:
- self._add(dep, directory_layout, explicit=False)
+ extra_args = {
+ 'explicit': False,
+ 'installation_time': installation_time
+ }
+ self._add(dep, directory_layout, **extra_args)
key = spec.dag_hash()
if key not in self._data:
@@ -613,8 +674,13 @@ class Database(object):
# Create a new install record with no deps initially.
new_spec = spec.copy(deps=False)
+ extra_args = {
+ 'explicit': explicit,
+ 'installation_time': installation_time
+ }
self._data[key] = InstallRecord(
- new_spec, path, installed, ref_count=0, explicit=explicit)
+ new_spec, path, installed, ref_count=0, **extra_args
+ )
# Connect dependencies from the DB to the new copy.
for name, dep in iteritems(spec.dependencies_dict(_tracked_deps)):
@@ -766,45 +832,57 @@ class Database(object):
continue
# TODO: conditional way to do this instead of catching exceptions
- def query(self, query_spec=any, known=any, installed=True, explicit=any):
- """Run a query on the database.
-
- ``query_spec``
- Queries iterate through specs in the database and return
- those that satisfy the supplied ``query_spec``. If
- query_spec is `any`, This will match all specs in the
- database. If it is a spec, we'll evaluate
- ``spec.satisfies(query_spec)``.
-
- The query can be constrained by two additional attributes:
-
- ``known``
- Possible values: True, False, any
-
- Specs that are "known" are those for which Spack can
- locate a ``package.py`` file -- i.e., Spack "knows" how to
- install them. Specs that are unknown may represent
- packages that existed in a previous version of Spack, but
- have since either changed their name or been removed.
-
- ``installed``
- Possible values: True, False, any
-
- Specs for which a prefix exists are "installed". A spec
- that is NOT installed will be in the database if some
- other spec depends on it but its installation has gone
- away since Spack installed it.
-
- TODO: Specs are a lot like queries. Should there be a
- wildcard spec object, and should specs have attributes
- like installed and known that can be queried? Or are
- these really special cases that only belong here?
-
+ def query(
+ self,
+ query_spec=any,
+ known=any,
+ installed=True,
+ explicit=any,
+ start_date=None,
+ end_date=None
+ ):
+ """Run a query on the database
+
+ Args:
+ query_spec: queries iterate through specs in the database and
+ return those that satisfy the supplied ``query_spec``. If
+ query_spec is `any`, This will match all specs in the
+ database. If it is a spec, we'll evaluate
+ ``spec.satisfies(query_spec)``
+
+ known (bool or any, optional): Specs that are "known" are those
+ for which Spack can locate a ``package.py`` file -- i.e.,
+ Spack "knows" how to install them. Specs that are unknown may
+ represent packages that existed in a previous version of
+ Spack, but have since either changed their name or
+ been removed
+
+ installed (bool or any, optional): Specs for which a prefix exists
+ are "installed". A spec that is NOT installed will be in the
+ database if some other spec depends on it but its installation
+ has gone away since Spack installed it.
+
+ explicit (bool or any, optional): A spec that was installed
+ following a specific user request is marked as explicit. If
+ instead it was pulled-in as a dependency of a user requested
+ spec it's considered implicit.
+
+ start_date (datetime, optional): filters the query discarding
+ specs that have been installed before ``start_date``.
+
+ end_date (datetime, optional): filters the query discarding
+ specs that have been installed after ``end_date``.
+
+ Returns:
+ list of specs that match the query
"""
+ # TODO: Specs are a lot like queries. Should there be a
+ # TODO: wildcard spec object, and should specs have attributes
+ # TODO: like installed and known that can be queried? Or are
+ # TODO: these really special cases that only belong here?
with self.read_transaction():
# Just look up concrete specs with hashes; no fancy search.
- if (isinstance(query_spec, spack.spec.Spec) and
- query_spec._concrete):
+ if isinstance(query_spec, spack.spec.Spec) and query_spec.concrete:
hash_key = query_spec.dag_hash()
if hash_key in self._data:
@@ -815,14 +893,26 @@ class Database(object):
# Abstract specs require more work -- currently we test
# against everything.
results = []
+ start_date = start_date or datetime.datetime.min
+ end_date = end_date or datetime.datetime.max
+
for key, rec in self._data.items():
if installed is not any and rec.installed != installed:
continue
+
if explicit is not any and rec.explicit != explicit:
continue
+
if known is not any and spack.repo.exists(
rec.spec.name) != known:
continue
+
+ inst_date = datetime.datetime.fromtimestamp(
+ rec.installation_time
+ )
+ if not (start_date < inst_date < end_date):
+ continue
+
if query_spec is any or rec.spec.satisfies(query_spec):
results.append(rec.spec)
@@ -835,7 +925,8 @@ class Database(object):
query. Returns None if no installed package matches.
"""
- concrete_specs = self.query(query_spec, known, installed)
+ concrete_specs = self.query(
+ query_spec, known=known, installed=installed)
assert len(concrete_specs) <= 1
return concrete_specs[0] if concrete_specs else None