22 Determine which packages are being used in the system and their versions 29 import pickle
as pickle
30 from collections
import Mapping
32 from .versions
import getRuntimeVersions
34 __all__ = [
"getVersionFromPythonModule",
"getPythonPackages",
"getEnvironmentPackages",
"Packages"]
38 BUILDTIME = set([
"boost",
"eigen",
"tmv"])
42 PYTHON = set([
"galsim"])
46 ENVIRONMENT = set([
"astrometry_net",
"astrometry_net_data",
"minuit2",
"xpa"])
50 """Determine the version of a python module. 55 Module for which to get version. 64 Raised if __version__ attribute is not set. 68 We supplement the version with information from the 69 ``__dependency_versions__`` (a specific variable set by LSST's 70 `~lsst.sconsUtils` at build time) only for packages that are typically 71 used only at build-time. 73 version = module.__version__
74 if hasattr(module,
"__dependency_versions__"):
76 deps = module.__dependency_versions__
77 buildtime = BUILDTIME & set(deps.keys())
79 version +=
" with " +
" ".join(
"%s=%s" % (pkg, deps[pkg])
80 for pkg
in sorted(buildtime))
85 """Get imported python packages and their versions. 90 Keys (type `str`) are package names; values (type `str`) are their 95 We wade through `sys.modules` and attempt to determine the version for each 96 module. Note, therefore, that we can only report on modules that have 97 *already* been imported. 99 We don't include any module for which we cannot determine a version. 102 for module
in PYTHON:
104 importlib.import_module(module)
108 packages = {
"python": sys.version}
110 moduleNames = list(sys.modules.keys())
111 for name
in moduleNames:
112 module = sys.modules[name]
120 for ending
in (
".version",
"._version"):
121 if name.endswith(ending):
122 name = name[:-len(ending)]
124 assert ver == packages[name]
125 elif name
in packages:
126 assert ver == packages[name]
132 name = name.replace(
"lsst.",
"").replace(
".",
"_")
143 """Get products and their versions from the environment. 148 Keys (type `str`) are product names; values (type `str`) are their 153 We use EUPS to determine the version of certain products (those that don't 154 provide a means to determine the version any other way) and to check if 155 uninstalled packages are being used. We only report the product/version 159 from eups
import Eups
160 from eups.Product
import Product
162 from lsst.pex.logging
import getDefaultLog
163 getDefaultLog().warn(
"Unable to import eups, so cannot determine package versions from environment")
170 products = _eups.findProducts(tags=[
"setup"])
174 packages = {prod.name: prod.version
for prod
in products
if prod
in ENVIRONMENT}
180 for prod
in products:
181 if not prod.version.startswith(Product.LocalVersionPrefix):
185 gitDir = os.path.join(prod.dir,
".git")
186 if os.path.exists(gitDir):
188 revCmd = [
"git",
"--git-dir=" + gitDir,
"--work-tree=" + prod.dir,
"rev-parse",
"HEAD"]
189 diffCmd = [
"git",
"--no-pager",
"--git-dir=" + gitDir,
"--work-tree=" + prod.dir,
"diff",
192 rev = subprocess.check_output(revCmd).decode().strip()
193 diff = subprocess.check_output(diffCmd)
199 ver +=
"+" + hashlib.md5(diff).hexdigest()
203 packages[prod.name] = ver
208 """A table of packages and their versions. 210 There are a few different types of packages, and their versions are collected 213 1. Run-time libraries (e.g., cfitsio, fftw): we get their version from 214 interrogating the dynamic library 215 2. Python modules (e.g., afw, numpy; galsim is also in this group even though 216 we only use it through the library, because no version information is 217 currently provided through the library): we get their version from the 218 ``__version__`` module variable. Note that this means that we're only aware 219 of modules that have already been imported. 220 3. Other packages provide no run-time accessible version information (e.g., 221 astrometry_net): we get their version from interrogating the environment. 222 Currently, that means EUPS; if EUPS is replaced or dropped then we'll need 223 to consider an alternative means of getting this version information. 224 4. Local versions of packages (a non-installed EUPS package, selected with 225 ``setup -r /path/to/package``): we identify these through the environment 226 (EUPS again) and use as a version the path supplemented with the ``git`` 227 SHA and, if the git repo isn't clean, an MD5 of the diff. 229 These package versions are collected and stored in a Packages object, which 230 provides useful comparison and persistence features. 234 .. code-block:: python 236 from lsst.base import Packages 237 pkgs = Packages.fromSystem() 238 print("Current packages:", pkgs) 239 old = Packages.read("/path/to/packages.pickle") 240 print("Old packages:", old) 241 print("Missing packages compared to before:", pkgs.missing(old)) 242 print("Extra packages compared to before:", pkgs.extra(old)) 243 print("Different packages: ", pkgs.difference(old)) 244 old.update(pkgs) # Include any new packages in the old 245 old.write("/path/to/packages.pickle") 250 A mapping {package: version} where both keys and values are type `str`. 254 This is essentially a wrapper around a dict with some conveniences. 258 assert isinstance(packages, Mapping)
260 self.
_names = set(packages.keys())
264 """Construct a `Packages` by examining the system. 266 Determine packages by examining python's `sys.modules`, runtime 271 packages : `Packages` 281 """Read packages from filename. 286 Filename from which to read. 290 packages : `Packages` 292 with open(filename,
"rb")
as ff:
293 return pickle.load(ff)
301 Filename to which to write. 303 with open(filename,
"wb")
as ff:
304 pickle.dump(self, ff)
310 ss =
"%s({\n" % self.__class__.__name__
312 ss +=
",\n".join(
"%s: %s" % (repr(prod), repr(self.
_packages[prod]))
for 313 prod
in sorted(self.
_names))
318 return "%s(%s)" % (self.__class__.__name__, repr(self.
_packages))
327 """Update packages with contents of another set of packages. 332 Other packages to merge with self. 336 No check is made to see if we're clobbering anything. 342 """Get packages in self but not in another `Packages` object. 347 Other packages to compare against. 352 Extra packages. Keys (type `str`) are package names; values 353 (type `str`) are their versions. 355 return {pkg: self.
_packages[pkg]
for pkg
in self.
_names - other._names}
358 """Get packages in another `Packages` object but missing from self. 363 Other packages to compare against. 368 Missing packages. Keys (type `str`) are package names; values 369 (type `str`) are their versions. 371 return {pkg: other._packages[pkg]
for pkg
in other._names - self.
_names}
374 """Get packages in symmetric difference of self and another `Packages` 380 Other packages to compare against. 385 Packages in symmetric difference. Keys (type `str`) are package 386 names; values (type `str`) are their versions. 388 return {pkg: (self.
_packages[pkg], other._packages[pkg])
for 389 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)