ed1e27faa1
when loading from cfg file, cfg.get takes just 2 parameters not three. Added some basic tests and test for the specific change as well Change-Id: I633d665f63271b6ada9196a0f08028d404b33110
265 lines
7.8 KiB
Python
265 lines
7.8 KiB
Python
|
|
# Copyright 2012 OpenStack Foundation
|
|
# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
__all__ = ['VersionInfo']
|
|
|
|
"""
|
|
Utilities for consuming the version from pkg_resources.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
import ConfigParser as configparser
|
|
except ImportError:
|
|
import configparser
|
|
|
|
import pkg_resources
|
|
|
|
|
|
def _expand_path(p):
|
|
"""Expand tildes and convert to an absolute path."""
|
|
return os.path.abspath(os.path.expanduser(p))
|
|
|
|
|
|
def _get_config_dirs(project=None):
|
|
"""Return a list of directories where config files may be located.
|
|
|
|
:param project: an optional project name
|
|
|
|
If a project is specified, the following directories are returned::
|
|
|
|
~/.${project}/
|
|
~/
|
|
/etc/${project}/
|
|
/etc/
|
|
|
|
Otherwise, these directories::
|
|
|
|
~/
|
|
/etc/
|
|
"""
|
|
if project:
|
|
cfg_dirs = [
|
|
_expand_path(os.path.join('~', '.' + project)),
|
|
_expand_path('~'),
|
|
os.path.join('/etc', project),
|
|
'/etc'
|
|
]
|
|
else:
|
|
cfg_dirs = [
|
|
_expand_path('~'),
|
|
'/etc'
|
|
]
|
|
|
|
return cfg_dirs
|
|
|
|
|
|
def _search_dirs(dirs, basename, extension=""):
|
|
"""Search a list of directories for a given filename.
|
|
|
|
Iterator over the supplied directories, returning the first file
|
|
found with the supplied name and extension.
|
|
|
|
:param dirs: a list of directories
|
|
:param basename: the filename, e.g. 'glance-api'
|
|
:param extension: the file extension, e.g. '.conf'
|
|
:returns: the path to a matching file, or None
|
|
"""
|
|
for d in dirs:
|
|
path = os.path.join(d, '%s%s' % (basename, extension))
|
|
if os.path.exists(path):
|
|
return path
|
|
|
|
|
|
def _find_config_files(project=None, prog=None, extension='.conf'):
|
|
"""Return a list of default configuration files.
|
|
|
|
:param project: an optional project name
|
|
:param prog: the program name, defaulting to the basename of sys.argv[0]
|
|
:param extension: the type of the config file
|
|
|
|
We default to two config files: [${project}.conf, ${prog}.conf]
|
|
|
|
And we look for those config files in the following directories::
|
|
|
|
~/.${project}/
|
|
~/
|
|
/etc/${project}/
|
|
/etc/
|
|
|
|
We return an absolute path for (at most) one of each of the default config
|
|
files, for the topmost directory it exists in.
|
|
|
|
For example, if project=foo, prog=bar and /etc/foo/foo.conf, /etc/bar.conf
|
|
and ~/.foo/bar.conf all exist, then we return ['/etc/foo/foo.conf',
|
|
'~/.foo/bar.conf']
|
|
|
|
If no project name is supplied, we only look for ${prog.conf}.
|
|
"""
|
|
if prog is None:
|
|
prog = os.path.basename(sys.argv[0])
|
|
|
|
cfg_dirs = _get_config_dirs(project)
|
|
|
|
config_files = []
|
|
if project:
|
|
config_files.append(_search_dirs(cfg_dirs, project, extension))
|
|
config_files.append(_search_dirs(cfg_dirs, prog, extension))
|
|
|
|
return [f for f in config_files if f]
|
|
|
|
|
|
class VersionInfo(object):
|
|
|
|
def __init__(self, package):
|
|
"""Object that understands versioning for a package
|
|
:param package: name of the python package, such as glance, or
|
|
python-glanceclient
|
|
"""
|
|
self.package = package
|
|
self._release = None
|
|
self._version = None
|
|
self._vendor = None
|
|
self._product = None
|
|
self._suffix = None
|
|
self._cached_version = None
|
|
self._provider = None
|
|
self._loaded = False
|
|
|
|
def __str__(self):
|
|
"""Make the VersionInfo object behave like a string."""
|
|
return self.version
|
|
|
|
def __repr__(self):
|
|
"""Include the name."""
|
|
return "VersionInfo(%s:%s)" % (self.package, self.version)
|
|
|
|
def _load_from_setup_cfg(self):
|
|
cfg = configparser.RawConfigParser()
|
|
cfg.read('setup.cfg')
|
|
|
|
self._vendor = cfg.get('metadata', 'author', None)
|
|
self._product = cfg.get('metadata', 'description', None)
|
|
|
|
def _load_from_pkg_info(self, provider):
|
|
import email
|
|
pkg_info = email.message_from_string(provider.get_metadata('PKG-INFO'))
|
|
self._vendor = pkg_info['Author']
|
|
self._product = pkg_info['Summary']
|
|
|
|
def _load_from_cfg_file(self, cfgfile):
|
|
cfg = configparser.RawConfigParser()
|
|
cfg.read(cfgfile)
|
|
|
|
project_name = self.package
|
|
if project_name.startswith('python-'):
|
|
project_name = project_name[7:]
|
|
|
|
self._vendor = cfg.get(project_name, "vendor")
|
|
self._product = cfg.get(project_name, "product")
|
|
self._suffix = cfg.get(project_name, "package")
|
|
|
|
def _load_vendor_strings(self):
|
|
"""Load default and override vendor strings.
|
|
|
|
Load default values from the project configuration. Then try loading
|
|
override values from release config files. At the end of this,
|
|
self.vendor, self.product and self.suffix should be directly
|
|
consumable.
|
|
"""
|
|
if self._loaded:
|
|
return
|
|
|
|
provider = self._get_provider()
|
|
if provider:
|
|
self._load_from_pkg_info(provider)
|
|
else:
|
|
self._load_from_setup_cfg()
|
|
|
|
cfgfile = _find_config_files("release")
|
|
if cfgfile:
|
|
self._load_from_cfg_file(cfgfile)
|
|
self._loaded = True
|
|
|
|
def _get_provider(self):
|
|
if self._provider is None:
|
|
try:
|
|
requirement = pkg_resources.Requirement.parse(self.package)
|
|
self._provider = pkg_resources.get_provider(requirement)
|
|
except pkg_resources.DistributionNotFound:
|
|
pass
|
|
return self._provider
|
|
|
|
def _get_version_from_pkg_resources(self):
|
|
"""Get the version of the package from the pkg_resources record
|
|
associated with the package.
|
|
"""
|
|
provider = self._get_provider()
|
|
if provider:
|
|
return provider.version
|
|
else:
|
|
# The most likely cause for this is running tests in a tree
|
|
# produced from a tarball where the package itself has not been
|
|
# installed into anything. Revert to setup-time logic.
|
|
try:
|
|
from pbr import packaging
|
|
return packaging.get_version(self.package)
|
|
except ImportError:
|
|
# You're killing me. We've got nothing here.
|
|
print("Unable to import pbr, or find pkg_resources")
|
|
print("information for %s" % self.package)
|
|
raise
|
|
|
|
@property
|
|
def release(self):
|
|
"""Return the full version of the package including suffixes indicating
|
|
VCS status.
|
|
"""
|
|
if self._release is None:
|
|
self._release = self._get_version_from_pkg_resources()
|
|
|
|
return self._release
|
|
|
|
@property
|
|
def version(self):
|
|
"""Return the short version minus any alpha/beta tags."""
|
|
if self._version is None:
|
|
parts = []
|
|
for part in self.release.split('.'):
|
|
if part[0].isdigit():
|
|
parts.append(part)
|
|
else:
|
|
break
|
|
self._version = ".".join(parts)
|
|
|
|
return self._version
|
|
|
|
@property
|
|
def vendor(self):
|
|
self._load_vendor_strings()
|
|
return self._vendor
|
|
|
|
@property
|
|
def product(self):
|
|
self._load_vendor_strings()
|
|
return self._product
|
|
|
|
@property
|
|
def suffix(self):
|
|
self._load_vendor_strings()
|
|
return self._suffix
|