2 Determine which packages are being used in the system and their versions 4 There are a few different types of packages, and their versions are collected in different ways: 5 1. Run-time libraries (e.g., cfitsio, fftw): we get their version from interrogating the dynamic library 6 2. Python modules (e.g., afw, numpy; galsim is also in this group even though we only use it through the 7 library, because no version information is currently provided through the library): we get their version 8 from the __version__ module variable. Note that this means that we're only aware of modules that have 10 3. Other packages provide no run-time accessible version information (e.g., astrometry_net): we get their 11 version from interrogating the environment. Currently, that means EUPS; if EUPS is replaced or dropped then 12 we'll need to consider an alternative means of getting this version information. 13 4. Local versions of packages (a non-installed EUPS package, selected with "setup -r /path/to/package"): we 14 identify these through the environment (EUPS again) and use as a version the path supplemented with the 15 git SHA and, if the git repo isn't clean, an MD5 of the diff. 17 These package versions are collected and stored in a Packages object, which provides useful comparison and 22 from lsst.base import Packages 23 pkgs = Packages.fromSystem() 24 print "Current packages:", pkgs 25 old = Packages.read("/path/to/packages.pickle") 26 print "Old packages:", old 27 print "Missing packages compared to before:", pkgs.missing(old) 28 print "Extra packages compared to before:", pkgs.extra(old) 29 print "Different packages: ", pkgs.difference(old) 30 old.update(pkgs) # Include any new packages in the old 31 old.write("/path/to/packages.pickle") 33 from builtins
import object
40 import pickle
as pickle
41 from collections
import Mapping
43 from .versions
import getRuntimeVersions
45 from future
import standard_library
46 standard_library.install_aliases()
48 __all__ = [
"getVersionFromPythonModule",
"getPythonPackages",
"getEnvironmentPackages",
"Packages"]
52 BUILDTIME = set([
"boost",
"eigen",
"tmv"])
56 PYTHON = set([
"galsim"])
60 ENVIRONMENT = set([
"astrometry_net",
"astrometry_net_data",
"minuit2",
"xpa"])
64 """Determine the version of a python module 66 Will raise AttributeError if the __version__ attribute is not set. 68 We supplement the version with information from the __dependency_versions__ 69 (a specific variable set by LSST's sconsUtils at build time) only for packages 70 that are typically used only at build-time. 72 version = module.__version__
73 if hasattr(module,
"__dependency_versions__"):
75 deps = module.__dependency_versions__
76 buildtime = BUILDTIME & set(deps.keys())
78 version +=
" with " +
" ".join(
"%s=%s" % (pkg, deps[pkg])
79 for pkg
in sorted(buildtime))
84 """Return a dict of imported python packages and their versions 86 We wade through sys.modules and attempt to determine the version for each 87 module. Note, therefore, that we can only report on modules that have 88 *already* been imported. 90 We don't include any module for which we cannot determine a version. 95 importlib.import_module(module)
99 packages = {
"python": sys.version}
101 moduleNames = list(sys.modules.keys())
102 for name
in moduleNames:
103 module = sys.modules[name]
111 for ending
in (
".version",
"._version"):
112 if name.endswith(ending):
113 name = name[:-len(ending)]
115 assert ver == packages[name]
116 elif name
in packages:
117 assert ver == packages[name]
123 name = name.replace(
"lsst.",
"").replace(
".",
"_")
134 """Provide a dict of products and their versions from the environment 136 We use EUPS to determine the version of certain products (those that don't provide 137 a means to determine the version any other way) and to check if uninstalled packages 138 are being used. We only report the product/version for these packages. 141 from eups
import Eups
142 from eups.Product
import Product
144 from lsst.pex.logging
import getDefaultLog
145 getDefaultLog().warn(
"Unable to import eups, so cannot determine package versions from environment")
152 products = _eups.findProducts(tags=[
"setup"])
156 packages = {prod.name: prod.version
for prod
in products
if prod
in ENVIRONMENT}
162 for prod
in products:
163 if not prod.version.startswith(Product.LocalVersionPrefix):
167 gitDir = os.path.join(prod.dir,
".git")
168 if os.path.exists(gitDir):
170 revCmd = [
"git",
"--git-dir=" + gitDir,
"--work-tree=" + prod.dir,
"rev-parse",
"HEAD"]
171 diffCmd = [
"git",
"--no-pager",
"--git-dir=" + gitDir,
"--work-tree=" + prod.dir,
"diff",
174 rev = subprocess.check_output(revCmd).decode().strip()
175 diff = subprocess.check_output(diffCmd)
181 ver +=
"+" + hashlib.md5(diff).hexdigest()
185 packages[prod.name] = ver
190 """A table of packages and their versions 192 Essentially a wrapper around a dict with some conveniences. 198 'packages' should be a mapping {package: version}, such as a dict. 200 assert isinstance(packages, Mapping)
202 self.
_names = set(packages.keys())
206 """Construct from the system 208 Attempts to determine packages by examining the system (python's sys.modules, 209 runtime libraries and EUPS). 219 """Read packages from filename""" 220 with open(filename,
"rb")
as ff:
221 return pickle.load(ff)
224 """Write packages to file""" 225 with open(filename,
"wb")
as ff:
226 pickle.dump(self, ff)
232 ss =
"%s({\n" % self.__class__.__name__
234 ss +=
",\n".join(
"%s: %s" % (repr(prod), repr(self.
_packages[prod]))
for 235 prod
in sorted(self.
_names))
240 return "%s(%s)" % (self.__class__.__name__, repr(self.
_packages))
249 """Update packages using another set of packages 251 No check is made to see if we're clobbering anything. 257 """Return packages in 'self' but not in 'other' 259 These are extra packages in 'self' compared to 'other'. 261 return {pkg: self.
_packages[pkg]
for pkg
in self.
_names - other._names}
264 """Return packages in 'other' but not in 'self' 266 These are missing packages in 'self' compared to 'other'. 268 return {pkg: other._packages[pkg]
for pkg
in other._names - self.
_names}
271 """Return packages different between 'self' and 'other'""" 272 return {pkg: (self.
_packages[pkg], other._packages[pkg])
for 273 pkg
in self.
_names & other._names
if self.
_packages[pkg] != other._packages[pkg]}
def __init__(self, packages)
def write(self, filename)
std::map< std::string, std::string > getRuntimeVersions()
Return version strings for dependencies.
def getEnvironmentPackages()
def getVersionFromPythonModule(module)
def __contains__(self, pkg)
def difference(self, other)