summaryrefslogtreecommitdiff
path: root/pbr/packaging.py
diff options
context:
space:
mode:
Diffstat (limited to 'pbr/packaging.py')
-rw-r--r--pbr/packaging.py238
1 files changed, 171 insertions, 67 deletions
diff --git a/pbr/packaging.py b/pbr/packaging.py
index 9a8f1c5..c42600e 100644
--- a/pbr/packaging.py
+++ b/pbr/packaging.py
@@ -1,5 +1,3 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2011 OpenStack LLC.
# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
@@ -22,13 +20,16 @@ Utilities with minimum-depends for use in setup.py
from __future__ import unicode_literals
+from distutils.command import install as du_install
+from distutils import log
import email
+import functools
+import itertools
import os
+import platform
import re
import sys
-from distutils.command import install as du_install
-from distutils import log
import pkg_resources
from setuptools.command import easy_install
from setuptools.command import egg_info
@@ -40,6 +41,7 @@ from pbr import extra_files
from pbr import git
from pbr import options
import pbr.pbr_json
+from pbr import version
REQUIREMENTS_FILES = ('requirements.txt', 'tools/pip-requires')
TEST_REQUIREMENTS_FILES = ('test-requirements.txt', 'tools/test-requires')
@@ -52,10 +54,26 @@ def get_requirements_files():
# Returns a list composed of:
# - REQUIREMENTS_FILES with -py2 or -py3 in the name
# (e.g. requirements-py3.txt)
+ # - REQUIREMENTS_FILES with -{platform.system} in the name
+ # (e.g. requirements-windows.txt)
+ # - REQUIREMENTS_FILES with both Python version and platform's
+ # system in the name
+ # (e.g. requirements-freebsd-py2.txt)
# - REQUIREMENTS_FILES
- return (list(map(('-py' + str(sys.version_info[0])).join,
- map(os.path.splitext, REQUIREMENTS_FILES)))
- + list(REQUIREMENTS_FILES))
+ pyversion = sys.version_info[0]
+ system = platform.system().lower()
+ parts = list(map(os.path.splitext, REQUIREMENTS_FILES))
+
+ version_cb = functools.partial("{1}-py{0}{2}".format, pyversion)
+ platform_cb = functools.partial("{1}-{0}{2}".format, system)
+ both_cb = functools.partial("{2}-{0}-py{1}{3}".format, system, pyversion)
+
+ return list(itertools.chain(
+ itertools.starmap(both_cb, parts),
+ itertools.starmap(platform_cb, parts),
+ itertools.starmap(version_cb, parts),
+ REQUIREMENTS_FILES,
+ ))
def append_text_list(config, key, text_list):
@@ -425,7 +443,10 @@ class LocalSDist(sdist.sdist):
def run(self):
option_dict = self.distribution.get_option_dict('pbr')
- git.write_git_changelog(option_dict=option_dict)
+ changelog = git._iter_log_oneline(option_dict=option_dict)
+ if changelog:
+ changelog = git._iter_changelog(changelog)
+ git.write_git_changelog(option_dict=option_dict, changelog=changelog)
git.generate_authors(option_dict=option_dict)
# sdist.sdist is an old style class, can't use super()
sdist.sdist.run(self)
@@ -447,56 +468,128 @@ def have_sphinx():
return _have_sphinx
-def _get_revno(git_dir):
- """Return the commit count since the most recent tag.
+def _get_increment_kwargs(git_dir, tag):
+ """Calculate the sort of semver increment needed from git history.
+
+ Every commit from HEAD to tag is consider for Sem-Ver metadata lines.
+ See the pbr docs for their syntax.
+
+ :return: a dict of kwargs for passing into SemanticVersion.increment.
+ """
+ result = {}
+ if tag:
+ version_spec = tag + "..HEAD"
+ else:
+ version_spec = "HEAD"
+ changelog = git._run_git_command(['log', version_spec], git_dir)
+ header_len = len(' sem-ver:')
+ commands = [line[header_len:].strip() for line in changelog.split('\n')
+ if line.lower().startswith(' sem-ver:')]
+ symbols = set()
+ for command in commands:
+ symbols.update([symbol.strip() for symbol in command.split(',')])
+
+ def _handle_symbol(symbol, symbols, impact):
+ if symbol in symbols:
+ result[impact] = True
+ symbols.discard(symbol)
+ _handle_symbol('bugfix', symbols, 'patch')
+ _handle_symbol('feature', symbols, 'minor')
+ _handle_symbol('deprecation', symbols, 'minor')
+ _handle_symbol('api-break', symbols, 'major')
+ for symbol in symbols:
+ log.info('[pbr] Unknown Sem-Ver symbol %r' % symbol)
+ # We don't want patch in the kwargs since it is not a keyword argument -
+ # its the default minimum increment.
+ result.pop('patch', None)
+ return result
+
+
+def _get_revno_and_last_tag(git_dir):
+ """Return the commit data about the most recent tag.
We use git-describe to find this out, but if there are no
tags then we fall back to counting commits since the beginning
of time.
"""
- raw_tag_info = git._get_raw_tag_info(git_dir)
- if raw_tag_info:
- return raw_tag_info
-
- # no tags found
- revlist = git._run_git_command(
- ['rev-list', '--abbrev-commit', 'HEAD'], git_dir)
- return len(revlist.splitlines())
-
-
-def _get_version_from_git(pre_version):
- """Return a version which is equal to the tag that's on the current
- revision if there is one, or tag plus number of additional revisions
- if the current revision has no tag.
+ changelog = git._iter_log_oneline(git_dir=git_dir)
+ row_count = 0
+ for row_count, (ignored, tag_set, ignored) in enumerate(changelog):
+ version_tags = set()
+ for tag in list(tag_set):
+ try:
+ version_tags.add(version.SemanticVersion.from_pip_string(tag))
+ except Exception:
+ pass
+ if version_tags:
+ return max(version_tags).release_string(), row_count
+ return "", row_count
+
+
+def _get_version_from_git_target(git_dir, target_version):
+ """Calculate a version from a target version in git_dir.
+
+ This is used for untagged versions only. A new version is calculated as
+ necessary based on git metadata - distance to tags, current hash, contents
+ of commit messages.
+
+ :param git_dir: The git directory we're working from.
+ :param target_version: If None, the last tagged version (or 0 if there are
+ no tags yet) is incremented as needed to produce an appropriate target
+ version following semver rules. Otherwise target_version is used as a
+ constraint - if semver rules would result in a newer version then an
+ exception is raised.
+ :return: A semver version object.
+ """
+ tag, distance = _get_revno_and_last_tag(git_dir)
+ last_semver = version.SemanticVersion.from_pip_string(tag or '0')
+ if distance == 0:
+ new_version = last_semver
+ else:
+ new_version = last_semver.increment(
+ **_get_increment_kwargs(git_dir, tag))
+ if target_version is not None and new_version > target_version:
+ raise ValueError(
+ "git history requires a target version of %(new)s, but target "
+ "version is %(target)s" %
+ dict(new=new_version, target=target_version))
+ if distance == 0:
+ return last_semver
+ if target_version is not None:
+ return target_version.to_dev(distance)
+ else:
+ return new_version.to_dev(distance)
+
+
+def _get_version_from_git(pre_version=None):
+ """Calculate a version string from git.
+
+ If the revision is tagged, return that. Otherwise calculate a semantic
+ version description of the tree.
+
+ The number of revisions since the last tag is included in the dev counter
+ in the version for untagged versions.
+
+ :param pre_version: If supplied use this as the target version rather than
+ inferring one from the last tag + commit messages.
"""
-
git_dir = git._run_git_functions()
if git_dir:
- if pre_version:
- try:
- return git._run_git_command(
- ['describe', '--exact-match'], git_dir,
- throw_on_error=True).replace('-', '.')
- except Exception:
- return "%s.dev%s" % (pre_version, _get_revno(git_dir))
- else:
- # git describe always is going to return one of three things
- # - a short-sha if there are no tags
- # - a tag, if there's one on the current revision
- # - a string of the form $last_tag-$revs_since_last_tag-g$short_sha
- raw_version = git._run_git_command(['describe', '--always'],
- git_dir)
- # First, if there are no -'s or .'s, then it's just a short sha.
- # Create a synthetic version for it.
- if '-' not in raw_version and '.' not in raw_version:
- return "0.0.0.post%s" % _get_revno(git_dir)
- # Now, we want to strip the short-sha prefix
- stripped_version = raw_version.split('-g')[0]
- # Finally, if we convert - to .post, which will turn the remaining
- # - which separates the version from the revcount into a PEP440
- # post string
- return stripped_version.replace('-', '.post')
-
+ try:
+ tagged = git._run_git_command(
+ ['describe', '--exact-match'], git_dir,
+ throw_on_error=True).replace('-', '.')
+ target_version = version.SemanticVersion.from_pip_string(tagged)
+ except Exception:
+ if pre_version:
+ # not released yet - use pre_version as the target
+ target_version = version.SemanticVersion.from_pip_string(
+ pre_version)
+ else:
+ # not released yet - just calculate from git history
+ target_version = None
+ result = _get_version_from_git_target(git_dir, target_version)
+ return result.release_string()
# If we don't know the version, return an empty string so at least
# the downstream users of the value always have the same type of
# object to work with.
@@ -506,40 +599,51 @@ def _get_version_from_git(pre_version):
return ''
-def _get_version_from_pkg_info(package_name):
- """Get the version from PKG-INFO file if we can."""
- try:
- pkg_info_file = open('PKG-INFO', 'r')
- except (IOError, OSError):
- return None
- try:
- pkg_info = email.message_from_file(pkg_info_file)
- except email.MessageError:
- return None
+def _get_version_from_pkg_metadata(package_name):
+ """Get the version from package metadata if present.
+
+ This looks for PKG-INFO if present (for sdists), and if not looks
+ for METADATA (for wheels) and failing that will return None.
+ """
+ pkg_metadata_filenames = ['PKG-INFO', 'METADATA']
+ pkg_metadata = {}
+ for filename in pkg_metadata_filenames:
+ try:
+ pkg_metadata_file = open(filename, 'r')
+ except (IOError, OSError):
+ continue
+ try:
+ pkg_metadata = email.message_from_file(pkg_metadata_file)
+ except email.MessageError:
+ continue
+
# Check to make sure we're in our own dir
- if pkg_info.get('Name', None) != package_name:
+ if pkg_metadata.get('Name', None) != package_name:
return None
- return pkg_info.get('Version', None)
+ return pkg_metadata.get('Version', None)
def get_version(package_name, pre_version=None):
- """Get the version of the project. First, try getting it from PKG-INFO, if
- it exists. If it does, that means we're in a distribution tarball or that
- install has happened. Otherwise, if there is no PKG-INFO file, pull the
- version from git.
+ """Get the version of the project. First, try getting it from PKG-INFO or
+ METADATA, if it exists. If it does, that means we're in a distribution
+ tarball or that install has happened. Otherwise, if there is no PKG-INFO
+ or METADATA file, pull the version from git.
We do not support setup.py version sanity in git archive tarballs, nor do
we support packagers directly sucking our git repo into theirs. We expect
that a source tarball be made from our git repo - or that if someone wants
to make a source tarball from a fork of our repo with additional tags in it
that they understand and desire the results of doing that.
+
+ :param pre_version: The version field from setup.cfg - if set then this
+ version will be the next release.
"""
version = os.environ.get(
"PBR_VERSION",
os.environ.get("OSLO_PACKAGE_VERSION", None))
if version:
return version
- version = _get_version_from_pkg_info(package_name)
+ version = _get_version_from_pkg_metadata(package_name)
if version:
return version
version = _get_version_from_git(pre_version)