22 Determine which packages are being used in the system and their versions 30 import pickle
as pickle
31 from collections
import Mapping
33 from .versions
import getRuntimeVersions
35 log = logging.getLogger(__name__)
37 __all__ = [
"getVersionFromPythonModule",
"getPythonPackages",
"getEnvironmentPackages",
"Packages"]
41 BUILDTIME =
set([
"boost",
"eigen",
"tmv"])
45 PYTHON =
set([
"galsim"])
49 ENVIRONMENT =
set([
"astrometry_net",
"astrometry_net_data",
"minuit2",
"xpa"])
53 """Determine the version of a python module. 58 Module for which to get version. 67 Raised if __version__ attribute is not set. 71 We supplement the version with information from the 72 ``__dependency_versions__`` (a specific variable set by LSST's 73 `~lsst.sconsUtils` at build time) only for packages that are typically 74 used only at build-time. 76 version = module.__version__
77 if hasattr(module,
"__dependency_versions__"):
79 deps = module.__dependency_versions__
80 buildtime = BUILDTIME &
set(deps.keys())
82 version +=
" with " +
" ".join(
"%s=%s" % (pkg, deps[pkg])
83 for pkg
in sorted(buildtime))
88 """Get imported python packages and their versions. 93 Keys (type `str`) are package names; values (type `str`) are their 98 We wade through `sys.modules` and attempt to determine the version for each 99 module. Note, therefore, that we can only report on modules that have 100 *already* been imported. 102 We don't include any module for which we cannot determine a version. 105 for module
in PYTHON:
107 importlib.import_module(module)
111 packages = {
"python": sys.version}
113 moduleNames =
list(sys.modules.keys())
114 for name
in moduleNames:
115 module = sys.modules[name]
123 for ending
in (
".version",
"._version"):
124 if name.endswith(ending):
125 name = name[:-len(ending)]
127 assert ver == packages[name]
128 elif name
in packages:
129 assert ver == packages[name]
135 name = name.replace(
"lsst.",
"").replace(
".",
"_")
146 """Get products and their versions from the environment. 151 Keys (type `str`) are product names; values (type `str`) are their 156 We use EUPS to determine the version of certain products (those that don't 157 provide a means to determine the version any other way) and to check if 158 uninstalled packages are being used. We only report the product/version 162 from eups
import Eups
163 from eups.Product
import Product
165 log.warning(
"Unable to import eups, so cannot determine package versions from environment")
172 products = _eups.findProducts(tags=[
"setup"])
176 packages = {prod.name: prod.version
for prod
in products
if prod
in ENVIRONMENT}
182 for prod
in products:
183 if not prod.version.startswith(Product.LocalVersionPrefix):
187 gitDir = os.path.join(prod.dir,
".git")
188 if os.path.exists(gitDir):
190 revCmd = [
"git",
"--git-dir=" + gitDir,
"--work-tree=" + prod.dir,
"rev-parse",
"HEAD"]
191 diffCmd = [
"git",
"--no-pager",
"--git-dir=" + gitDir,
"--work-tree=" + prod.dir,
"diff",
194 rev = subprocess.check_output(revCmd).decode().
strip()
195 diff = subprocess.check_output(diffCmd)
201 ver +=
"+" + hashlib.md5(diff).hexdigest()
205 packages[prod.name] = ver
210 """A table of packages and their versions. 212 There are a few different types of packages, and their versions are collected 215 1. Run-time libraries (e.g., cfitsio, fftw): we get their version from 216 interrogating the dynamic library 217 2. Python modules (e.g., afw, numpy; galsim is also in this group even though 218 we only use it through the library, because no version information is 219 currently provided through the library): we get their version from the 220 ``__version__`` module variable. Note that this means that we're only aware 221 of modules that have already been imported. 222 3. Other packages provide no run-time accessible version information (e.g., 223 astrometry_net): we get their version from interrogating the environment. 224 Currently, that means EUPS; if EUPS is replaced or dropped then we'll need 225 to consider an alternative means of getting this version information. 226 4. Local versions of packages (a non-installed EUPS package, selected with 227 ``setup -r /path/to/package``): we identify these through the environment 228 (EUPS again) and use as a version the path supplemented with the ``git`` 229 SHA and, if the git repo isn't clean, an MD5 of the diff. 231 These package versions are collected and stored in a Packages object, which 232 provides useful comparison and persistence features. 236 .. code-block:: python 238 from lsst.base import Packages 239 pkgs = Packages.fromSystem() 240 print("Current packages:", pkgs) 241 old = Packages.read("/path/to/packages.pickle") 242 print("Old packages:", old) 243 print("Missing packages compared to before:", pkgs.missing(old)) 244 print("Extra packages compared to before:", pkgs.extra(old)) 245 print("Different packages: ", pkgs.difference(old)) 246 old.update(pkgs) # Include any new packages in the old 247 old.write("/path/to/packages.pickle") 252 A mapping {package: version} where both keys and values are type `str`. 256 This is essentially a wrapper around a dict with some conveniences. 260 assert isinstance(packages, Mapping)
266 """Construct a `Packages` by examining the system. 268 Determine packages by examining python's `sys.modules`, runtime 273 packages : `Packages` 283 """Read packages from filename. 288 Filename from which to read. 292 packages : `Packages` 294 with open(filename,
"rb")
as ff:
295 return pickle.load(ff)
303 Filename to which to write. 305 with open(filename,
"wb")
as ff:
306 pickle.dump(self, ff)
312 ss =
"%s({\n" % self.__class__.__name__
314 ss +=
",\n".join(
"%s: %s" % (repr(prod), repr(self.
_packages[prod]))
for 315 prod
in sorted(self.
_names))
320 return "%s(%s)" % (self.__class__.__name__, repr(self.
_packages))
329 """Update packages with contents of another set of packages. 334 Other packages to merge with self. 338 No check is made to see if we're clobbering anything. 344 """Get packages in self but not in another `Packages` object. 349 Other packages to compare against. 354 Extra packages. Keys (type `str`) are package names; values 355 (type `str`) are their versions. 357 return {pkg: self.
_packages[pkg]
for pkg
in self.
_names - other._names}
360 """Get packages in another `Packages` object but missing from self. 365 Other packages to compare against. 370 Missing packages. Keys (type `str`) are package names; values 371 (type `str`) are their versions. 373 return {pkg: other._packages[pkg]
for pkg
in other._names - self.
_names}
376 """Get packages in symmetric difference of self and another `Packages` 382 Other packages to compare against. 387 Packages in symmetric difference. Keys (type `str`) are package 388 names; values (type `str`) are their versions. 390 return {pkg: (self.
_packages[pkg], other._packages[pkg])
for 391 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()
daf::base::PropertySet * set
def getVersionFromPythonModule(module)
def __contains__(self, pkg)
def difference(self, other)
daf::base::PropertyList * list