diff options
Diffstat (limited to 'lib/spack/spack/database.py')
-rw-r--r-- | lib/spack/spack/database.py | 189 |
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 |