diff options
| author | Scott Bahling <sbahling@suse.de> | 2015-04-19 19:37:23 +0200 |
|---|---|---|
| committer | Scott Bahling <sbahling@suse.de> | 2015-04-19 19:37:23 +0200 |
| commit | e3ffbe638eea380a6760586a2b44b9a3e3006e2b (patch) | |
| tree | b06a35b22ac981151822a82dce4562f7d4d0c1a0 | |
| parent | 480b40e658fb9c0edd1fdb0febf2629a06f13215 (diff) | |
| download | obsapi-0.0.3.tar.gz obsapi-0.0.3.tar.xz obsapi-0.0.3.zip | |
Update versioneer and add first unit testsobsapi-0.0.3
| -rw-r--r-- | MANIFEST.in | 1 | ||||
| -rwxr-xr-x | Makefile | 3 | ||||
| -rw-r--r-- | obsapi/__init__.py | 4 | ||||
| -rw-r--r-- | obsapi/_version.py | 243 | ||||
| -rw-r--r-- | setup.py | 31 | ||||
| -rw-r--r-- | test/test_api.py | 46 | ||||
| -rw-r--r-- | versioneer.py | 1070 |
7 files changed, 869 insertions, 529 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 02fba2b..b196286 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include README.rst COPYING AUTHORS ChangeLog include versioneer.py +include obsapi/_version.py @@ -45,3 +45,6 @@ install: uninstall: python setup.py uninstall --verbose + +test: + python setup.py test diff --git a/obsapi/__init__.py b/obsapi/__init__.py index 5af2406..52eabbf 100644 --- a/obsapi/__init__.py +++ b/obsapi/__init__.py @@ -1 +1,5 @@ from core import * + +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions diff --git a/obsapi/_version.py b/obsapi/_version.py index 173f2ab..7adc409 100644 --- a/obsapi/_version.py +++ b/obsapi/_version.py @@ -1,140 +1,162 @@ -IN_LONG_VERSION_PY = True # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build +# feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.7 (https://github.com/warner/python-versioneer) +# versioneer-0.13 (https://github.com/warner/python-versioneer) # these strings will be replaced by git during git-archive git_refnames = "$Format:%d$" git_full = "$Format:%H$" +# these strings are filled in when 'setup.py versioneer' creates _version.py +tag_prefix = "obsapi-" +parentdir_prefix = "obsapi-" +versionfile_source = "obsapi/_version.py" +import errno +import os +import re import subprocess +import sys -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError, e: + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % args[0]) + print(e) + return None + else: if verbose: - print "unable to run %s" % args[0] - print e + print("unable to find command, tried %s" % (commands,)) return None stdout = p.communicate()[0].strip() + if sys.version >= '3': + stdout = stdout.decode() if p.returncode != 0: if verbose: - print "unable to run %s (error)" % args[0] + print("unable to run %s (error)" % args[0]) return None return stdout -import sys -import re -import os.path +def versions_from_parentdir(parentdir_prefix, root, verbose=False): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} -def get_expanded_variables(versionfile_source): + +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - for line in open(versionfile_source,"r").readlines(): + f = open(versionfile_abs, "r") + for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) + f.close() except EnvironmentError: pass - return variables + return keywords + -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +def git_versions_from_keywords(keywords, tag_prefix, verbose=False): + if not keywords: + return {} # keyword-finding function failed to find keywords + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print "variables are unexpanded, not using" - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + return {} # unexpanded, so not in an unpacked git-archive tarball refs = set([r.strip() for r in refnames.strip("()").split(",")]) - for ref in list(refs): - if not re.search(r'\d', ref): - if verbose: - print "discarding '%s', no digits" % ref - refs.discard(ref) - # Assume all version tags have a digit. git's %d expansion - # behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us - # distinguish between branches and tags. By ignoring refnames - # without digits, we filter out many common branch names like - # "release" and "stabilization", as well as "HEAD" and "master". + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) if verbose: - print "remaining refs:", ",".join(sorted(refs)) - for ref in sorted(refs): + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: - print "picking %s" % r - return { "version": r, - "full": variables["full"].strip() } + print("picking %s" % r) + return {"version": r, + "full": keywords["full"].strip()} # no suitable tags, so we use the full revision id if verbose: - print "no suitable tags, using full revision id" - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using full revision id") + return {"version": keywords["full"].strip(), + "full": keywords["full"].strip()} + + +def git_versions_from_vcs(tag_prefix, root, verbose=False): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) if not os.path.exists(os.path.join(root, ".git")): if verbose: - print "no .git in", root + print("no .git in %s" % root) return {} - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], + GITS = ["git.cmd", "git.exe"] + stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], cwd=root) if stdout is None: return {} if not stdout.startswith(tag_prefix): if verbose: - print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix) + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (stdout, tag_prefix)) return {} tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) + stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if stdout is None: return {} full = stdout.strip() @@ -143,50 +165,27 @@ def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): return {"version": tag, "full": full} -def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope - # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) - - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print "guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % (root, dirname, parentdir_prefix) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} +def get_versions(default={"version": "unknown", "full": ""}, verbose=False): + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. -tag_prefix = "obsapi-" -parentdir_prefix = "obsapi-" -versionfile_source = "obsapi/_version.py" + keywords = {"refnames": git_refnames, "full": git_full} + ver = git_versions_from_keywords(keywords, tag_prefix, verbose) + if ver: + return ver -def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = { "refnames": git_refnames, "full": git_full } - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose) - if not ver: - ver = default - return ver + try: + root = os.path.realpath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in range(len(versionfile_source.split('/'))): + root = os.path.dirname(root) + except NameError: + return default + return (git_versions_from_vcs(tag_prefix, root, verbose) + or versions_from_parentdir(parentdir_prefix, root, verbose) + or default) @@ -1,10 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import os import sys - from setuptools import setup +from setuptools.command.test import test as TestCommand import versioneer @@ -12,19 +11,43 @@ required = [] PROJECT = 'obsapi' +versioneer.VCS = 'git' versioneer.versionfile_source = '%s/_version.py' % PROJECT versioneer.versionfile_build = '%s/_version.py' % PROJECT versioneer.tag_prefix = '%s-' % PROJECT # tags are like 1.2.0 versioneer.parentdir_prefix = '%s-' % PROJECT # dirname like 'myproject-1.2.0' + +class PyTest(TestCommand): + user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = [] + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(self.pytest_args) + sys.exit(errno) + +cmdclass = versioneer.get_cmdclass() +cmdclass['test'] = PyTest + setup( name=PROJECT, version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), + tests_require=['pytest'], + cmdclass=cmdclass, description='Python library for accessing the Open Build Service api', author='Scott Bahling', author_email='sbahling@suse.de', - packages= [ + packages=[ 'obsapi', ], install_requires=required, diff --git a/test/test_api.py b/test/test_api.py new file mode 100644 index 0000000..20787d4 --- /dev/null +++ b/test/test_api.py @@ -0,0 +1,46 @@ +from obsapi import ObsApi +from lxml import etree + +api = ObsApi(apiurl='https://api.suse.com') + +prj = 'Test:obsapi' +pkg = 'suse-hello-1.0' +repo = 'SLE_12' +arch = 'x86_64' + + +def test_get_package_meta(): + try: + response = etree.fromstring(api.get_package_meta(prj=prj, pkg=pkg)) + except: + response = {} + assert response.get('name', None) == 'suse-hello-1.0' + + +def test_ls_prj(): + assert api.ls(prj=prj) == ['suse-hello-1.0'] + + +def test_ls_pkg(): + files = ['COPYING', 'suse-hello-1.0.tar.bz2', + 'suse-hello.changes', 'suse-hello.spec', + ] + directory, listing = api.ls(prj=prj, pkg=pkg) + assert directory.name == 'suse-hello-1.0' + items = [i.name for i in listing] + for f in files: + assert f in items + + +def test_get_binaries(): + files = ['_statistics', 'rpmlint.log', 'suse-hello-1.0-2.1.src.rpm', + 'suse-hello-kmp-default-1.0_k3.12.28_4-2.1.x86_64.rpm', + ] + listing = api.get_binaries(prj=prj, pkg=pkg, repo=repo, arch=arch) + items = [i.filename for i in listing] + for f in files: + assert f in items + + +def test_get_vendor(): + assert api.get_vendor(prj=prj) == u'Test:obsapi' diff --git a/versioneer.py b/versioneer.py index ac77fb5..d3fe56c 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,236 +1,484 @@ -#! /usr/bin/python -"""versioneer.py +# Version: 0.13 -(like a rocketeer, but for versions) +""" +The Versioneer +============== +* like a rocketeer, but for versions! * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain -* Version: 0.7 - -This file helps distutils-based projects manage their version number by just -creating version-control tags. - -For developers who work from a VCS-generated tree (e.g. 'git clone' etc), -each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a -version number by asking your version-control tool about the current -checkout. The version number will be written into a generated _version.py -file of your choosing, where it can be included by your __init__.py - -For users who work from a VCS-generated tarball (e.g. 'git archive'), it will -compute a version number by looking at the name of the directory created when -te tarball is unpacked. This conventionally includes both the name of the -project and a version number. - -For users who work from a tarball built by 'setup.py sdist', it will get a -version number from a previously-generated _version.py file. - -As a result, loading code directly from the source tree will not result in a -real version. If you want real versions from VCS trees (where you frequently -update from the upstream repository, or do new development), you will need to -do a 'setup.py version' after each update, and load code from the build/ -directory. - -You need to provide this code with a few configuration values: - - versionfile_source: - A project-relative pathname into which the generated version strings - should be written. This is usually a _version.py next to your project's - main __init__.py file. If your project uses src/myproject/__init__.py, - this should be 'src/myproject/_version.py'. This file should be checked - in to your VCS as usual: the copy created below by 'setup.py - update_files' will include code that parses expanded VCS keywords in - generated tarballs. The 'build' and 'sdist' commands will replace it with - a copy that has just the calculated version string. - - versionfile_build: - Like versionfile_source, but relative to the build directory instead of - the source directory. These will differ when your setup.py uses - 'package_dir='. If you have package_dir={'myproject': 'src/myproject'}, - then you will probably have versionfile_build='myproject/_version.py' and - versionfile_source='src/myproject/_version.py'. - - tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all - VCS tags. If your tags look like 'myproject-1.2.0', then you - should use tag_prefix='myproject-'. If you use unprefixed tags - like '1.2.0', this should be an empty string. - - parentdir_prefix: a string, frequently the same as tag_prefix, which - appears at the start of all unpacked tarball filenames. If - your tarball unpacks into 'myproject-1.2.0', this should - be 'myproject-'. - -To use it: - - 1: include this file in the top level of your project - 2: make the following changes to the top of your setup.py: - import versioneer - versioneer.versionfile_source = 'src/myproject/_version.py' - versioneer.versionfile_build = 'myproject/_version.py' - versioneer.tag_prefix = '' # tags are like 1.2.0 - versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0' - 3: add the following arguments to the setup() call in your setup.py: - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), - 4: run 'setup.py update_files', which will create _version.py, and will - append the following to your __init__.py: - from _version import __version__ - 5: modify your MANIFEST.in to include versioneer.py - 6: add both versioneer.py and the generated _version.py to your VCS +* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy +* [](https://pypi.python.org/pypi/versioneer/) +* [](https://travis-ci.org/warner/python-versioneer) + +This is a tool for managing a recorded version number in distutils-based +python projects. The goal is to remove the tedious and error-prone "update +the embedded version string" step from your release process. Making a new +release should be as easy as recording a new tag in your version-control +system, and maybe making new tarballs. + + +## Quick Install + +* `pip install versioneer` to somewhere to your $PATH +* run `versioneer-installer` in your source tree: this installs `versioneer.py` +* follow the instructions below (also in the `versioneer.py` docstring) + +## Version Identifiers + +Source trees come from a variety of places: + +* a version-control system checkout (mostly used by developers) +* a nightly tarball, produced by build automation +* a snapshot tarball, produced by a web-based VCS browser, like github's + "tarball from tag" feature +* a release tarball, produced by "setup.py sdist", distributed through PyPI + +Within each source tree, the version identifier (either a string or a number, +this tool is format-agnostic) can come from a variety of places: + +* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows + about recent "tags" and an absolute revision-id +* the name of the directory into which the tarball was unpacked +* an expanded VCS keyword ($Id$, etc) +* a `_version.py` created by some earlier build step + +For released software, the version identifier is closely related to a VCS +tag. Some projects use tag names that include more than just the version +string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool +needs to strip the tag prefix to extract the version identifier. For +unreleased software (between tags), the version identifier should provide +enough information to help developers recreate the same tree, while also +giving them an idea of roughly how old the tree is (after version 1.2, before +version 1.3). Many VCS systems can report a description that captures this, +for example 'git describe --tags --dirty --always' reports things like +"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the +0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has +uncommitted changes. + +The version identifier is used for multiple purposes: + +* to allow the module to self-identify its version: `myproject.__version__` +* to choose a name and prefix for a 'setup.py sdist' tarball + +## Theory of Operation + +Versioneer works by adding a special `_version.py` file into your source +tree, where your `__init__.py` can import it. This `_version.py` knows how to +dynamically ask the VCS tool for version information at import time. However, +when you use "setup.py build" or "setup.py sdist", `_version.py` in the new +copy is replaced by a small static file that contains just the generated +version data. + +`_version.py` also contains `$Revision$` markers, and the installation +process marks `_version.py` to have this marker rewritten with a tag name +during the "git archive" command. As a result, generated tarballs will +contain enough information to get the proper version. + + +## Installation + +First, decide on values for the following configuration variables: + +* `VCS`: the version control system you use. Currently accepts "git". + +* `versionfile_source`: + + A project-relative pathname into which the generated version strings should + be written. This is usually a `_version.py` next to your project's main + `__init__.py` file, so it can be imported at runtime. If your project uses + `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. + This file should be checked in to your VCS as usual: the copy created below + by `setup.py versioneer` will include code that parses expanded VCS + keywords in generated tarballs. The 'build' and 'sdist' commands will + replace it with a copy that has just the calculated version string. + + This must be set even if your project does not have any modules (and will + therefore never import `_version.py`), since "setup.py sdist" -based trees + still need somewhere to record the pre-calculated version strings. Anywhere + in the source tree should do. If there is a `__init__.py` next to your + `_version.py`, the `setup.py versioneer` command (described below) will + append some `__version__`-setting assignments, if they aren't already + present. + +* `versionfile_build`: + + Like `versionfile_source`, but relative to the build directory instead of + the source directory. These will differ when your setup.py uses + 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, + then you will probably have `versionfile_build='myproject/_version.py'` and + `versionfile_source='src/myproject/_version.py'`. + + If this is set to None, then `setup.py build` will not attempt to rewrite + any `_version.py` in the built tree. If your project does not have any + libraries (e.g. if it only builds a script), then you should use + `versionfile_build = None` and override `distutils.command.build_scripts` + to explicitly insert a copy of `versioneer.get_version()` into your + generated script. + +* `tag_prefix`: + + a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. + If your tags look like 'myproject-1.2.0', then you should use + tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this + should be an empty string. + +* `parentdir_prefix`: + + a string, frequently the same as tag_prefix, which appears at the start of + all unpacked tarball filenames. If your tarball unpacks into + 'myproject-1.2.0', this should be 'myproject-'. + +This tool provides one script, named `versioneer-installer`. That script does +one thing: write a copy of `versioneer.py` into the current directory. + +To versioneer-enable your project: + +* 1: Run `versioneer-installer` to copy `versioneer.py` into the top of your + source tree. + +* 2: add the following lines to the top of your `setup.py`, with the + configuration values you decided earlier: + + import versioneer + versioneer.VCS = 'git' + versioneer.versionfile_source = 'src/myproject/_version.py' + versioneer.versionfile_build = 'myproject/_version.py' + versioneer.tag_prefix = '' # tags are like 1.2.0 + versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0' + +* 3: add the following arguments to the setup() call in your setup.py: + + version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass(), + +* 4: now run `setup.py versioneer`, which will create `_version.py`, and will + modify your `__init__.py` (if one exists next to `_version.py`) to define + `__version__` (by calling a function from `_version.py`). It will also + modify your `MANIFEST.in` to include both `versioneer.py` and the generated + `_version.py` in sdist tarballs. + +* 5: commit these changes to your VCS. To make sure you won't forget, + `setup.py versioneer` will mark everything it touched for addition. + +## Post-Installation Usage + +Once established, all uses of your tree from a VCS checkout should get the +current version string. All generated tarballs should include an embedded +version string (so users who unpack them will not need a VCS tool installed). + +If you distribute your project through PyPI, then the release process should +boil down to two steps: + +* 1: git tag 1.0 +* 2: python setup.py register sdist upload + +If you distribute it through github (i.e. users use github to generate +tarballs with `git archive`), the process is: + +* 1: git tag 1.0 +* 2: git push; git push --tags + +Currently, all version strings must be based upon a tag. Versioneer will +report "unknown" until your tree has at least one tag in its history. This +restriction will be fixed eventually (see issue #12). + +## Version-String Flavors + +Code which uses Versioneer can learn about its version string at runtime by +importing `_version` from your main `__init__.py` file and running the +`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can +import the top-level `versioneer.py` and run `get_versions()`. + +Both functions return a dictionary with different keys for different flavors +of the version string: + +* `['version']`: condensed tag+distance+shortid+dirty identifier. For git, + this uses the output of `git describe --tags --dirty --always` but strips + the tag_prefix. For example "0.11-2-g1076c97-dirty" indicates that the tree + is like the "1076c97" commit but has uncommitted changes ("-dirty"), and + that this commit is two revisions ("-2-") beyond the "0.11" tag. For + released software (exactly equal to a known tag), the identifier will only + contain the stripped tag, e.g. "0.11". + +* `['full']`: detailed revision identifier. For Git, this is the full SHA1 + commit id, followed by "-dirty" if the tree contains uncommitted changes, + e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac-dirty". + +Some variants are more useful than others. Including `full` in a bug report +should allow developers to reconstruct the exact code being tested (or +indicate the presence of local changes that should be shared with the +developers). `version` is suitable for display in an "about" box or a CLI +`--version` output: it can be easily compared against release notes and lists +of bugs fixed in various releases. + +In the future, this will also include a +[PEP-0440](http://legacy.python.org/dev/peps/pep-0440/) -compatible flavor +(e.g. `1.2.post0.dev123`). This loses a lot of information (and has no room +for a hash-based revision id), but is safe to use in a `setup.py` +"`version=`" argument. It also enables tools like *pip* to compare version +strings and evaluate compatibility constraint declarations. + +The `setup.py versioneer` command adds the following text to your +`__init__.py` to place a basic version in `YOURPROJECT.__version__`: + + from ._version import get_versions + __version__ = get_versions()['version'] + del get_versions + +## Updating Versioneer + +To upgrade your project to a new release of Versioneer, do the following: + +* install the new Versioneer (`pip install -U versioneer` or equivalent) +* re-run `versioneer-installer` in your source tree to replace your copy of + `versioneer.py` +* edit `setup.py`, if necessary, to include any new configuration settings + indicated by the release notes +* re-run `setup.py versioneer` to replace `SRC/_version.py` +* commit any changed files + +### Upgrading from 0.10 to 0.11 + +You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running +`setup.py versioneer`. This will enable the use of additional version-control +systems (SVN, etc) in the future. + +### Upgrading from 0.11 to 0.12 + +Nothing special. + +## Future Directions + +This tool is designed to make it easily extended to other version-control +systems: all VCS-specific components are in separate directories like +src/git/ . The top-level `versioneer.py` script is assembled from these +components by running make-versioneer.py . In the future, make-versioneer.py +will take a VCS name as an argument, and will construct a version of +`versioneer.py` that is specific to the given VCS. It might also take the +configuration arguments that are currently provided manually during +installation by editing setup.py . Alternatively, it might go the other +direction and include code from all supported VCS systems, reducing the +number of intermediate scripts. + + +## License + +To make Versioneer easier to embed, all its code is hereby released into the +public domain. The `_version.py` that it creates is also in the public +domain. + """ -import os, sys, re +import os, sys, re, subprocess, errno from distutils.core import Command from distutils.command.sdist import sdist as _sdist from distutils.command.build import build as _build +# these configuration settings will be overridden by setup.py after it +# imports us versionfile_source = None versionfile_build = None tag_prefix = None parentdir_prefix = None +VCS = None + +# these dictionaries contain VCS-specific tools +LONG_VERSION_PY = {} -VCS = "git" -IN_LONG_VERSION_PY = False +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % args[0]) + print(e) + return None + else: + if verbose: + print("unable to find command, tried %s" % (commands,)) + return None + stdout = p.communicate()[0].strip() + if sys.version >= '3': + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % args[0]) + return None + return stdout -LONG_VERSION_PY = ''' -IN_LONG_VERSION_PY = True +LONG_VERSION_PY['git'] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build +# feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.7 (https://github.com/warner/python-versioneer) +# versioneer-0.13 (https://github.com/warner/python-versioneer) # these strings will be replaced by git during git-archive git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" +# these strings are filled in when 'setup.py versioneer' creates _version.py +tag_prefix = "%(TAG_PREFIX)s" +parentdir_prefix = "%(PARENTDIR_PREFIX)s" +versionfile_source = "%(VERSIONFILE_SOURCE)s" +import errno +import os +import re import subprocess +import sys -def run_command(args, cwd=None, verbose=False): - try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError, e: + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): + assert isinstance(commands, list) + p = None + for c in commands: + try: + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %%s" %% args[0]) + print(e) + return None + else: if verbose: - print "unable to run %%s" %% args[0] - print e + print("unable to find command, tried %%s" %% (commands,)) return None stdout = p.communicate()[0].strip() + if sys.version >= '3': + stdout = stdout.decode() if p.returncode != 0: if verbose: - print "unable to run %%s (error)" %% args[0] + print("unable to run %%s (error)" %% args[0]) return None return stdout -import sys -import re -import os.path +def versions_from_parentdir(parentdir_prefix, root, verbose=False): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%%s', but '%%s' doesn't start with " + "prefix '%%s'" %% (root, dirname, parentdir_prefix)) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} + -def get_expanded_variables(versionfile_source): +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - for line in open(versionfile_source,"r").readlines(): + f = open(versionfile_abs, "r") + for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) + f.close() except EnvironmentError: pass - return variables + return keywords -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() + +def git_versions_from_keywords(keywords, tag_prefix, verbose=False): + if not keywords: + return {} # keyword-finding function failed to find keywords + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print "variables are unexpanded, not using" - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + return {} # unexpanded, so not in an unpacked git-archive tarball refs = set([r.strip() for r in refnames.strip("()").split(",")]) - for ref in list(refs): - if not re.search(r'\d', ref): - if verbose: - print "discarding '%%s', no digits" %% ref - refs.discard(ref) - # Assume all version tags have a digit. git's %%d expansion - # behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us - # distinguish between branches and tags. By ignoring refnames - # without digits, we filter out many common branch names like - # "release" and "stabilization", as well as "HEAD" and "master". + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %%d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%%s', no digits" %% ",".join(refs-tags)) if verbose: - print "remaining refs:", ",".join(sorted(refs)) - for ref in sorted(refs): + print("likely tags: %%s" %% ",".join(sorted(tags))) + for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: - print "picking %%s" %% r - return { "version": r, - "full": variables["full"].strip() } + print("picking %%s" %% r) + return {"version": r, + "full": keywords["full"].strip()} # no suitable tags, so we use the full revision id if verbose: - print "no suitable tags, using full revision id" - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using full revision id") + return {"version": keywords["full"].strip(), + "full": keywords["full"].strip()} + + +def git_versions_from_vcs(tag_prefix, root, verbose=False): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) if not os.path.exists(os.path.join(root, ".git")): if verbose: - print "no .git in", root + print("no .git in %%s" %% root) return {} - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], + GITS = ["git.cmd", "git.exe"] + stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], cwd=root) if stdout is None: return {} if not stdout.startswith(tag_prefix): if verbose: - print "tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix) + fmt = "tag '%%s' doesn't start with prefix '%%s'" + print(fmt %% (stdout, tag_prefix)) return {} tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) + stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if stdout is None: return {} full = stdout.strip() @@ -239,178 +487,122 @@ def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): return {"version": tag, "full": full} -def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope - # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) - - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print "guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %% \ - (root, dirname, parentdir_prefix) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "%(TAG_PREFIX)s" -parentdir_prefix = "%(PARENTDIR_PREFIX)s" -versionfile_source = "%(VERSIONFILE_SOURCE)s" - def get_versions(default={"version": "unknown", "full": ""}, verbose=False): - variables = { "refnames": git_refnames, "full": git_full } - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) - if not ver: - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if not ver: - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, - verbose) - if not ver: - ver = default - return ver - -''' - + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. -import subprocess + keywords = {"refnames": git_refnames, "full": git_full} + ver = git_versions_from_keywords(keywords, tag_prefix, verbose) + if ver: + return ver -def run_command(args, cwd=None, verbose=False): try: - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) - except EnvironmentError, e: - if verbose: - print "unable to run %s" % args[0] - print e - return None - stdout = p.communicate()[0].strip() - if p.returncode != 0: - if verbose: - print "unable to run %s (error)" % args[0] - return None - return stdout + root = os.path.realpath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in range(len(versionfile_source.split('/'))): + root = os.path.dirname(root) + except NameError: + return default + return (git_versions_from_vcs(tag_prefix, root, verbose) + or versions_from_parentdir(parentdir_prefix, root, verbose) + or default) +''' -import sys -import re -import os.path -def get_expanded_variables(versionfile_source): +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: - for line in open(versionfile_source,"r").readlines(): + f = open(versionfile_abs, "r") + for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) + f.close() except EnvironmentError: pass - return variables + return keywords + -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +def git_versions_from_keywords(keywords, tag_prefix, verbose=False): + if not keywords: + return {} # keyword-finding function failed to find keywords + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print "variables are unexpanded, not using" - return {} # unexpanded, so not in an unpacked git-archive tarball + print("keywords are unexpanded, not using") + return {} # unexpanded, so not in an unpacked git-archive tarball refs = set([r.strip() for r in refnames.strip("()").split(",")]) - for ref in list(refs): - if not re.search(r'\d', ref): - if verbose: - print "discarding '%s', no digits" % ref - refs.discard(ref) - # Assume all version tags have a digit. git's %d expansion - # behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us - # distinguish between branches and tags. By ignoring refnames - # without digits, we filter out many common branch names like - # "release" and "stabilization", as well as "HEAD" and "master". + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs-tags)) if verbose: - print "remaining refs:", ",".join(sorted(refs)) - for ref in sorted(refs): + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: - print "picking %s" % r - return { "version": r, - "full": variables["full"].strip() } + print("picking %s" % r) + return {"version": r, + "full": keywords["full"].strip()} # no suitable tags, so we use the full revision id if verbose: - print "no suitable tags, using full revision id" - return { "version": variables["full"].strip(), - "full": variables["full"].strip() } - -def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): - # this runs 'git' from the root of the source tree. That either means - # someone ran a setup.py command (and this code is in versioneer.py, so - # IN_LONG_VERSION_PY=False, thus the containing directory is the root of - # the source tree), or someone ran a project-specific entry point (and - # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the - # containing directory is somewhere deeper in the source tree). This only - # gets called if the git-archive 'subst' variables were *not* expanded, - # and _version.py hasn't already been rewritten with a short version - # string, meaning we're inside a checked out source tree. + print("no suitable tags, using full revision id") + return {"version": keywords["full"].strip(), + "full": keywords["full"].strip()} + + +def git_versions_from_vcs(tag_prefix, root, verbose=False): + # this runs 'git' from the root of the source tree. This only gets called + # if the git-archive 'subst' keywords were *not* expanded, and + # _version.py hasn't already been rewritten with a short version string, + # meaning we're inside a checked out source tree. - try: - here = os.path.abspath(__file__) - except NameError: - # some py2exe/bbfreeze/non-CPython implementations don't do __file__ - return {} # not always correct - - # versionfile_source is the relative path from the top of the source tree - # (where the .git directory might live) to this file. Invert this to find - # the root from __file__. - root = here - if IN_LONG_VERSION_PY: - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - root = os.path.dirname(here) if not os.path.exists(os.path.join(root, ".git")): if verbose: - print "no .git in", root + print("no .git in %s" % root) return {} - GIT = "git" + GITS = ["git"] if sys.platform == "win32": - GIT = "git.cmd" - stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], + GITS = ["git.cmd", "git.exe"] + stdout = run_command(GITS, ["describe", "--tags", "--dirty", "--always"], cwd=root) if stdout is None: return {} if not stdout.startswith(tag_prefix): if verbose: - print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix) + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (stdout, tag_prefix)) return {} tag = stdout[len(tag_prefix):] - stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) + stdout = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if stdout is None: return {} full = stdout.strip() @@ -419,47 +611,21 @@ def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): return {"version": tag, "full": full} -def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): - if IN_LONG_VERSION_PY: - # We're running from _version.py. If it's from a source tree - # (execute-in-place), we can work upwards to find the root of the - # tree, and then check the parent directory for a version string. If - # it's in an installed application, there's no hope. - try: - here = os.path.abspath(__file__) - except NameError: - # py2exe/bbfreeze/non-CPython don't have __file__ - return {} # without __file__, we have no hope - # versionfile_source is the relative path from the top of the source - # tree to _version.py. Invert this to find the root from __file__. - root = here - for i in range(len(versionfile_source.split("/"))): - root = os.path.dirname(root) - else: - # we're running from versioneer.py, which means we're running from - # the setup.py in a source tree. sys.argv[0] is setup.py in the root. - here = os.path.abspath(sys.argv[0]) - root = os.path.dirname(here) - - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print "guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % \ - (root, dirname, parentdir_prefix) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -import sys - -def do_vcs_install(versionfile_source, ipy): - GIT = "git" +def do_vcs_install(manifest_in, versionfile_source, ipy): + GITS = ["git"] if sys.platform == "win32": - GIT = "git.cmd" - run_command([GIT, "add", "versioneer.py"]) - run_command([GIT, "add", versionfile_source]) - run_command([GIT, "add", ipy]) + GITS = ["git.cmd", "git.exe"] + files = [manifest_in, versionfile_source] + if ipy: + files.append(ipy) + try: + me = __file__ + if me.endswith(".pyc") or me.endswith(".pyo"): + me = os.path.splitext(me)[0] + ".py" + versioneer_file = os.path.relpath(me) + except NameError: + versioneer_file = "versioneer.py" + files.append(versioneer_file) present = False try: f = open(".gitattributes", "r") @@ -469,16 +635,27 @@ def do_vcs_install(versionfile_source, ipy): present = True f.close() except EnvironmentError: - pass + pass if not present: f = open(".gitattributes", "a+") f.write("%s export-subst\n" % versionfile_source) f.close() - run_command([GIT, "add", ".gitattributes"]) - + files.append(".gitattributes") + run_command(GITS, ["add", "--"] + files) + +def versions_from_parentdir(parentdir_prefix, root, verbose=False): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print("guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.7) from +# This file was generated by 'versioneer.py' (0.13) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. @@ -495,66 +672,85 @@ DEFAULT = {"version": "unknown", "full": "unknown"} def versions_from_file(filename): versions = {} try: - f = open(filename) + with open(filename) as f: + for line in f.readlines(): + mo = re.match("version_version = '([^']+)'", line) + if mo: + versions["version"] = mo.group(1) + mo = re.match("version_full = '([^']+)'", line) + if mo: + versions["full"] = mo.group(1) except EnvironmentError: - return versions - for line in f.readlines(): - mo = re.match("version_version = '([^']+)'", line) - if mo: - versions["version"] = mo.group(1) - mo = re.match("version_full = '([^']+)'", line) - if mo: - versions["full"] = mo.group(1) + return {} + return versions def write_to_version_file(filename, versions): - f = open(filename, "w") - f.write(SHORT_VERSION_PY % versions) - f.close() - print "set %s to '%s'" % (filename, versions["version"]) + with open(filename, "w") as f: + f.write(SHORT_VERSION_PY % versions) + print("set %s to '%s'" % (filename, versions["version"])) -def get_best_versions(versionfile, tag_prefix, parentdir_prefix, - default=DEFAULT, verbose=False): + +def get_root(): + try: + return os.path.dirname(os.path.abspath(__file__)) + except NameError: + return os.path.dirname(os.path.abspath(sys.argv[0])) + +def vcs_function(vcs, suffix): + return getattr(sys.modules[__name__], '%s_%s' % (vcs, suffix), None) + +def get_versions(default=DEFAULT, verbose=False): # returns dict with two keys: 'version' and 'full' - # - # extract version from first of _version.py, 'git describe', parentdir. - # This is meant to work for developers using a source checkout, for users - # of a tarball created by 'setup.py sdist', and for users of a - # tarball/zipball created by 'git archive' or github's download-from-tag - # feature. - - variables = get_expanded_variables(versionfile_source) - if variables: - ver = versions_from_expanded_variables(variables, tag_prefix) + assert versionfile_source is not None, "please set versioneer.versionfile_source" + assert tag_prefix is not None, "please set versioneer.tag_prefix" + assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix" + assert VCS is not None, "please set versioneer.VCS" + + # I am in versioneer.py, which must live at the top of the source tree, + # which we use to compute the root directory. py2exe/bbfreeze/non-CPython + # don't have __file__, in which case we fall back to sys.argv[0] (which + # ought to be the setup.py script). We prefer __file__ since that's more + # robust in cases where setup.py was invoked in some weird way (e.g. pip) + root = get_root() + versionfile_abs = os.path.join(root, versionfile_source) + + # extract version from first of _version.py, VCS command (e.g. 'git + # describe'), parentdir. This is meant to work for developers using a + # source checkout, for users of a tarball created by 'setup.py sdist', + # and for users of a tarball/zipball created by 'git archive' or github's + # download-from-tag feature or the equivalent in other VCSes. + + get_keywords_f = vcs_function(VCS, "get_keywords") + versions_from_keywords_f = vcs_function(VCS, "versions_from_keywords") + if get_keywords_f and versions_from_keywords_f: + vcs_keywords = get_keywords_f(versionfile_abs) + ver = versions_from_keywords_f(vcs_keywords, tag_prefix) if ver: - if verbose: print "got version from expanded variable", ver + if verbose: print("got version from expanded keyword %s" % ver) return ver - ver = versions_from_file(versionfile) + ver = versions_from_file(versionfile_abs) if ver: - if verbose: print "got version from file %s" % versionfile, ver + if verbose: print("got version from file %s %s" % (versionfile_abs,ver)) return ver - ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) - if ver: - if verbose: print "got version from git", ver - return ver + versions_from_vcs_f = vcs_function(VCS, "versions_from_vcs") + if versions_from_vcs_f: + ver = versions_from_vcs_f(tag_prefix, root, verbose) + if ver: + if verbose: print("got version from VCS %s" % ver) + return ver - ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose) + ver = versions_from_parentdir(parentdir_prefix, root, verbose) if ver: - if verbose: print "got version from parentdir", ver + if verbose: print("got version from parentdir %s" % ver) return ver - if verbose: print "got version from default", ver + if verbose: print("got version from default %s" % default) return default -def get_versions(default=DEFAULT, verbose=False): - assert versionfile_source is not None, "please set versioneer.versionfile_source" - assert tag_prefix is not None, "please set versioneer.tag_prefix" - assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix" - return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix, - default=default, verbose=verbose) def get_version(verbose=False): return get_versions(verbose=verbose)["version"] @@ -568,7 +764,7 @@ class cmd_version(Command): pass def run(self): ver = get_version(verbose=True) - print "Version is currently:", ver + print("Version is currently: %s" % ver) class cmd_build(_build): @@ -577,12 +773,35 @@ class cmd_build(_build): _build.run(self) # now locate _version.py in the new build/ directory and replace it # with an updated value - target_versionfile = os.path.join(self.build_lib, versionfile_build) - print "UPDATING", target_versionfile - os.unlink(target_versionfile) - f = open(target_versionfile, "w") - f.write(SHORT_VERSION_PY % versions) - f.close() + if versionfile_build: + target_versionfile = os.path.join(self.build_lib, versionfile_build) + print("UPDATING %s" % target_versionfile) + os.unlink(target_versionfile) + with open(target_versionfile, "w") as f: + f.write(SHORT_VERSION_PY % versions) + +if 'cx_Freeze' in sys.modules: # cx_freeze enabled? + from cx_Freeze.dist import build_exe as _build_exe + + class cmd_build_exe(_build_exe): + def run(self): + versions = get_versions(verbose=True) + target_versionfile = versionfile_source + print("UPDATING %s" % target_versionfile) + os.unlink(target_versionfile) + with open(target_versionfile, "w") as f: + f.write(SHORT_VERSION_PY % versions) + + _build_exe.run(self) + os.unlink(target_versionfile) + with open(versionfile_source, "w") as f: + assert VCS is not None, "please set versioneer.VCS" + LONG = LONG_VERSION_PY[VCS] + f.write(LONG % {"DOLLAR": "$", + "TAG_PREFIX": tag_prefix, + "PARENTDIR_PREFIX": parentdir_prefix, + "VERSIONFILE_SOURCE": versionfile_source, + }) class cmd_sdist(_sdist): def run(self): @@ -597,11 +816,10 @@ class cmd_sdist(_sdist): # now locate _version.py in the new base_dir directory (remembering # that it may be a hardlink) and replace it with an updated value target_versionfile = os.path.join(base_dir, versionfile_source) - print "UPDATING", target_versionfile + print("UPDATING %s" % target_versionfile) os.unlink(target_versionfile) - f = open(target_versionfile, "w") - f.write(SHORT_VERSION_PY % self._versioneer_generated_versions) - f.close() + with open(target_versionfile, "w") as f: + f.write(SHORT_VERSION_PY % self._versioneer_generated_versions) INIT_PY_SNIPPET = """ from ._version import get_versions @@ -610,7 +828,7 @@ del get_versions """ class cmd_update_files(Command): - description = "modify __init__.py and create _version.py" + description = "install/upgrade Versioneer files: __init__.py SRC/_version.py" user_options = [] boolean_options = [] def initialize_options(self): @@ -618,32 +836,78 @@ class cmd_update_files(Command): def finalize_options(self): pass def run(self): + print(" creating %s" % versionfile_source) + with open(versionfile_source, "w") as f: + assert VCS is not None, "please set versioneer.VCS" + LONG = LONG_VERSION_PY[VCS] + f.write(LONG % {"DOLLAR": "$", + "TAG_PREFIX": tag_prefix, + "PARENTDIR_PREFIX": parentdir_prefix, + "VERSIONFILE_SOURCE": versionfile_source, + }) + ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py") - print " creating %s" % versionfile_source - f = open(versionfile_source, "w") - f.write(LONG_VERSION_PY % {"DOLLAR": "$", - "TAG_PREFIX": tag_prefix, - "PARENTDIR_PREFIX": parentdir_prefix, - "VERSIONFILE_SOURCE": versionfile_source, - }) - f.close() + if os.path.exists(ipy): + try: + with open(ipy, "r") as f: + old = f.read() + except EnvironmentError: + old = "" + if INIT_PY_SNIPPET not in old: + print(" appending to %s" % ipy) + with open(ipy, "a") as f: + f.write(INIT_PY_SNIPPET) + else: + print(" %s unmodified" % ipy) + else: + print(" %s doesn't exist, ok" % ipy) + ipy = None + + # Make sure both the top-level "versioneer.py" and versionfile_source + # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so + # they'll be copied into source distributions. Pip won't be able to + # install the package without this. + manifest_in = os.path.join(get_root(), "MANIFEST.in") + simple_includes = set() try: - old = open(ipy, "r").read() + with open(manifest_in, "r") as f: + for line in f: + if line.startswith("include "): + for include in line.split()[1:]: + simple_includes.add(include) except EnvironmentError: - old = "" - if INIT_PY_SNIPPET not in old: - print " appending to %s" % ipy - f = open(ipy, "a") - f.write(INIT_PY_SNIPPET) - f.close() + pass + # That doesn't cover everything MANIFEST.in can do + # (http://docs.python.org/2/distutils/sourcedist.html#commands), so + # it might give some false negatives. Appending redundant 'include' + # lines is safe, though. + if "versioneer.py" not in simple_includes: + print(" appending 'versioneer.py' to MANIFEST.in") + with open(manifest_in, "a") as f: + f.write("include versioneer.py\n") + else: + print(" 'versioneer.py' already in MANIFEST.in") + if versionfile_source not in simple_includes: + print(" appending versionfile_source ('%s') to MANIFEST.in" % + versionfile_source) + with open(manifest_in, "a") as f: + f.write("include %s\n" % versionfile_source) else: - print " %s unmodified" % ipy - do_vcs_install(versionfile_source, ipy) + print(" versionfile_source already in MANIFEST.in") + + # Make VCS-specific changes. For git, this means creating/changing + # .gitattributes to mark _version.py for export-time keyword + # substitution. + do_vcs_install(manifest_in, versionfile_source, ipy) def get_cmdclass(): - return {'version': cmd_version, - 'update_files': cmd_update_files, + cmds = {'version': cmd_version, + 'versioneer': cmd_update_files, 'build': cmd_build, 'sdist': cmd_sdist, } + if 'cx_Freeze' in sys.modules: # cx_freeze enabled? + cmds['build_exe'] = cmd_build_exe + del cmds['build'] + return cmds |
