[tor-commits] [bridgedb/master] Start using automatic versioning system.

isis at torproject.org isis at torproject.org
Sun Jan 12 06:06:29 UTC 2014


commit 7227e6e47c7161d7639a27fd206be64aafae8549
Author: Isis Lovecruft <isis at torproject.org>
Date:   Mon Aug 19 22:14:38 2013 +0000

    Start using automatic versioning system.
    
     * FIXES part of #9425 (Create and document a better BridgeDB deployment
       strategy).
     * ADD versioneer.py to the top level of the project.
     * USE setuptools explicitly in setup.py, which will also make the project a
       bit more py3k compatible.
     * ADD maintainer and maintainer_email fields to setup.py setup() call (with
       my contact info).
     * ADD versioneer setup variables and calls to setup.py.
    
    Versioneer, as currently configured, will automatically create a package level
    attribute:
    
     >>> import bridgedb
     >>> print bridgedb.__version__
     0.0.1
    
    Bumping the version number at release time (which, for BridgeDB really means
    deploy time, as of right now) means doing the following:
    
     $ git checkout develop
     [merge some fix/bug/feature/etc branches]
     $ git checkout -b release-0.0.2 develop
     $ git tag -a -s bridgedb-0.0.2
     [pip maintainance commands *would* go here, if we ever have any]
     $ git checkout master
     $ git merge -S --no-ff release-0.0.2
     $ git checkout develop
     $ git merge -S --no-ff master
     $ git push <remote> master develop
    
    And be sure not to forget to do:
    
     $ git push --tags
    
    If the currently installed version is *not* from one of the signed tags, the
    version number attribute created by versioneer will be the short ID of the git
    commit from which the installation took place, prefixed with the most recent
    tagged release at that point, i.e.:
    
     >>> import bridgedb
     >>> bridgedb.__version__
     0.0.1-git528ff30c
---
 .gitattributes           |    1 +
 lib/bridgedb/__init__.py |    4 +
 lib/bridgedb/_version.py |  197 ++++++++++++++
 setup.py                 |   73 ++++--
 versioneer.py            |  656 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 906 insertions(+), 25 deletions(-)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..0e02de6
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+lib/bridgedb/_version.py export-subst
diff --git a/lib/bridgedb/__init__.py b/lib/bridgedb/__init__.py
index 1fad2ad..3979b43 100644
--- a/lib/bridgedb/__init__.py
+++ b/lib/bridgedb/__init__.py
@@ -1,2 +1,6 @@
 
 # This file tells Python that this is an honest to goodness package.
+
+from ._version import get_versions
+__version__ = get_versions()['version']
+del get_versions
diff --git a/lib/bridgedb/_version.py b/lib/bridgedb/_version.py
new file mode 100644
index 0000000..8e9faf1
--- /dev/null
+++ b/lib/bridgedb/_version.py
@@ -0,0 +1,197 @@
+
+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
+# 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)
+
+# these strings will be replaced by git during git-archive
+git_refnames = "$Format:%d$"
+git_full = "$Format:%H$"
+
+
+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 = sys.exc_info()[1]
+        if verbose:
+            print("unable to run %s" % args[0])
+            print(e)
+        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
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+    # 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 = {}
+    try:
+        for line in open(versionfile_source,"r").readlines():
+            if line.strip().startswith("git_refnames ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["refnames"] = mo.group(1)
+            if line.strip().startswith("git_full ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["full"] = mo.group(1)
+    except EnvironmentError:
+        pass
+    return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+    refnames = variables["refnames"].strip()
+    if refnames.startswith("$Format"):
+        if verbose:
+            print("variables 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".
+    if verbose:
+        print("remaining refs: %s" % ",".join(sorted(refs)))
+    for ref in sorted(refs):
+        # 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() }
+    # 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.
+
+    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 %s" % root)
+        return {}
+
+    GIT = "git"
+    if sys.platform == "win32":
+        GIT = "git.cmd"
+    stdout = run_command([GIT, "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))
+        return {}
+    tag = stdout[len(tag_prefix):]
+    stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+    if stdout is None:
+        return {}
+    full = stdout.strip()
+    if tag.endswith("-dirty"):
+        full += "-dirty"
+    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 = "bridgedb-"
+parentdir_prefix = "bridgedb-"
+versionfile_source = "lib/bridgedb/_version.py"
+
+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
+
diff --git a/setup.py b/setup.py
index ae1e126..3a1f737 100644
--- a/setup.py
+++ b/setup.py
@@ -6,8 +6,8 @@
 import subprocess
 from distutils.command.install_data import install_data as _install_data
 import os
+import setuptools
 import sys
-from setuptools import setup, Command, find_packages
 
 # Fix circular dependency with setup.py install
 try:
@@ -16,6 +16,30 @@ try:
 except ImportError:
     compile_catalog = extract_messages = init_catalog = update_catalog = None
 
+# setup automatic versioning (see top-level versioneer.py file):
+import versioneer
+versioneer.versionfile_source = 'lib/bridgedb/_version.py'
+versioneer.versionfile_build = 'bridgedb/_version.py'
+versioneer.tag_prefix = 'bridgedb-' # tags should be like 'bridgedb-0.1.0'
+versioneer.parentdir_prefix = 'bridgedb-' # tarballs unpack to 'bridgedb-0.1.0'
+
+def get_cmdclass():
+    """Get our cmdclass dictionary for use in setuptool.setup().
+
+    This must be done outside the call to setuptools.setup() because we need
+    to add our own classes to the cmdclass dictionary, and then update that
+    dictionary with the one returned from versioneer.get_cmdclass().
+    """
+    cmdclass={'test' : runTests,
+              'compile_catalog': compile_catalog,
+              'extract_messages': extract_messages,
+              'init_catalog': init_catalog,
+              'update_catalog': update_catalog,
+              'install_data': installData}
+    cmdclass.update(versioneer.get_cmdclass())
+    return cmdclass
+
+
 class installData(_install_data):
     def run(self):
         self.data_files = []
@@ -28,7 +52,7 @@ class installData(_install_data):
             self.data_files.append( (lang_dir, [lang_file]) )
         _install_data.run(self)
 
-class runTests(Command):
+class runTests(setuptools.Command):
     # Based on setup.py from mixminion, which is based on setup.py
     # from Zooko's pyutil package, which is in turn based on
     # http://mail.python.org/pipermail/distutils-sig/2002-January/002714.html
@@ -53,27 +77,26 @@ class runTests(Command):
         finally:
             sys.path = old_path
 
-setup(name='BridgeDB',
-      version='0.1',
-      description='Bridge disbursal tool for use with Tor anonymity network',
-      author='Nick Mathewson',
-      author_email='nickm at torproject dot org',
-      url='https://www.torproject.org',
-      package_dir= {'' : 'lib'},
-      packages=find_packages('lib'),
-      py_modules=['TorBridgeDB'],
-      cmdclass={'test' : runTests,
-                'compile_catalog': compile_catalog,
-                'extract_messages': extract_messages,
-                'init_catalog': init_catalog,
-                'update_catalog': update_catalog,
-                'install_data': installData},
-      include_package_data=True,
-      package_data={'bridgedb': ['i18n/*/LC_MESSAGES/*.mo',
-                                 'templates/*.html',
-                                 'templates/assets/*']},
-      message_extractors = {'lib/bridgedb': [
-              ('**.py', 'python', None),
-              ('templates/**.html', 'mako', None),
-              ('public/**', 'ignore', None)]},  
+
+setuptools.setup(
+    name='bridgedb',
+    version=versioneer.get_version(),
+    description='Backend systems for distribution of Tor bridge relays',
+    author='Nick Mathewson',
+    author_email='nickm at torproject dot org',
+    maintainer='Isis Agora Lovecruft',
+    maintainer_email='isis at torproject.org 0xA3ADB67A2CDB8B35',
+    url='https://www.torproject.org',
+    package_dir= {'' : 'lib'},
+    packages=setuptools.find_packages('lib'),
+    py_modules=['TorBridgeDB'],
+    cmdclass=get_cmdclass(),
+    include_package_data=True,
+    package_data={'bridgedb': ['i18n/*/LC_MESSAGES/*.mo',
+                               'templates/*.html',
+                               'templates/assets/*']},
+    message_extractors = {'lib/bridgedb': [
+        ('**.py', 'python', None),
+        ('templates/**.html', 'mako', None),
+        ('public/**', 'ignore', None)]},
 )
diff --git a/versioneer.py b/versioneer.py
new file mode 100644
index 0000000..57d9941
--- /dev/null
+++ b/versioneer.py
@@ -0,0 +1,656 @@
+#! /usr/bin/python
+
+"""versioneer.py
+
+(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
+"""
+
+import os, sys, re
+from distutils.core import Command
+from distutils.command.sdist import sdist as _sdist
+from distutils.command.build import build as _build
+
+versionfile_source = None
+versionfile_build = None
+tag_prefix = None
+parentdir_prefix = None
+
+VCS = "git"
+IN_LONG_VERSION_PY = False
+
+
+LONG_VERSION_PY = '''
+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
+# 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)
+
+# these strings will be replaced by git during git-archive
+git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
+git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
+
+
+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 = sys.exc_info()[1]
+        if verbose:
+            print("unable to run %%s" %% args[0])
+            print(e)
+        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
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+    # 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 = {}
+    try:
+        for line in open(versionfile_source,"r").readlines():
+            if line.strip().startswith("git_refnames ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["refnames"] = mo.group(1)
+            if line.strip().startswith("git_full ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["full"] = mo.group(1)
+    except EnvironmentError:
+        pass
+    return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+    refnames = variables["refnames"].strip()
+    if refnames.startswith("$Format"):
+        if verbose:
+            print("variables 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".
+    if verbose:
+        print("remaining refs: %%s" %% ",".join(sorted(refs)))
+    for ref in sorted(refs):
+        # 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() }
+    # 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.
+
+    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 %%s" %% root)
+        return {}
+
+    GIT = "git"
+    if sys.platform == "win32":
+        GIT = "git.cmd"
+    stdout = run_command([GIT, "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))
+        return {}
+    tag = stdout[len(tag_prefix):]
+    stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+    if stdout is None:
+        return {}
+    full = stdout.strip()
+    if tag.endswith("-dirty"):
+        full += "-dirty"
+    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
+
+'''
+
+
+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 = sys.exc_info()[1]
+        if verbose:
+            print("unable to run %s" % args[0])
+            print(e)
+        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
+
+
+import sys
+import re
+import os.path
+
+def get_expanded_variables(versionfile_source):
+    # 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 = {}
+    try:
+        for line in open(versionfile_source,"r").readlines():
+            if line.strip().startswith("git_refnames ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["refnames"] = mo.group(1)
+            if line.strip().startswith("git_full ="):
+                mo = re.search(r'=\s*"(.*)"', line)
+                if mo:
+                    variables["full"] = mo.group(1)
+    except EnvironmentError:
+        pass
+    return variables
+
+def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
+    refnames = variables["refnames"].strip()
+    if refnames.startswith("$Format"):
+        if verbose:
+            print("variables 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".
+    if verbose:
+        print("remaining refs: %s" % ",".join(sorted(refs)))
+    for ref in sorted(refs):
+        # 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() }
+    # 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.
+
+    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 %s" % root)
+        return {}
+
+    GIT = "git"
+    if sys.platform == "win32":
+        GIT = "git.cmd"
+    stdout = run_command([GIT, "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))
+        return {}
+    tag = stdout[len(tag_prefix):]
+    stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
+    if stdout is None:
+        return {}
+    full = stdout.strip()
+    if tag.endswith("-dirty"):
+        full += "-dirty"
+    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"
+    if sys.platform == "win32":
+        GIT = "git.cmd"
+    run_command([GIT, "add", "versioneer.py"])
+    run_command([GIT, "add", versionfile_source])
+    run_command([GIT, "add", ipy])
+    present = False
+    try:
+        f = open(".gitattributes", "r")
+        for line in f.readlines():
+            if line.strip().startswith(versionfile_source):
+                if "export-subst" in line.strip().split()[1:]:
+                    present = True
+        f.close()
+    except EnvironmentError:
+        pass    
+    if not present:
+        f = open(".gitattributes", "a+")
+        f.write("%s export-subst\n" % versionfile_source)
+        f.close()
+        run_command([GIT, "add", ".gitattributes"])
+    
+
+SHORT_VERSION_PY = """
+# This file was generated by 'versioneer.py' (0.7+) 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.
+
+version_version = '%(version)s'
+version_full = '%(full)s'
+def get_versions(default={}, verbose=False):
+    return {'version': version_version, 'full': version_full}
+
+"""
+
+DEFAULT = {"version": "unknown", "full": "unknown"}
+
+def versions_from_file(filename):
+    versions = {}
+    try:
+        f = open(filename)
+    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 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"]))
+
+
+def get_best_versions(versionfile, tag_prefix, parentdir_prefix,
+                      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)
+        if ver:
+            if verbose: print("got version from expanded variable %s" % ver)
+            return ver
+
+    ver = versions_from_file(versionfile)
+    if ver:
+        if verbose: print("got version from file %s %s" % (versionfile, ver))
+        return ver
+
+    ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
+    if ver:
+        if verbose: print("got version from git %s" % ver)
+        return ver
+
+    ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose)
+    if ver:
+        if verbose: print("got version from parentdir %s" % ver)
+        return ver
+
+    if verbose: print("got version from default %s" % ver)
+    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"]
+
+class cmd_version(Command):
+    description = "report generated version string"
+    user_options = []
+    boolean_options = []
+    def initialize_options(self):
+        pass
+    def finalize_options(self):
+        pass
+    def run(self):
+        ver = get_version(verbose=True)
+        print("Version is currently: %s" % ver)
+
+
+class cmd_build(_build):
+    def run(self):
+        versions = get_versions(verbose=True)
+        _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 %s" % target_versionfile)
+        os.unlink(target_versionfile)
+        f = open(target_versionfile, "w")
+        f.write(SHORT_VERSION_PY % versions)
+        f.close()
+
+class cmd_sdist(_sdist):
+    def run(self):
+        versions = get_versions(verbose=True)
+        self._versioneer_generated_versions = versions
+        # unless we update this, the command will keep using the old version
+        self.distribution.metadata.version = versions["version"]
+        return _sdist.run(self)
+
+    def make_release_tree(self, base_dir, files):
+        _sdist.make_release_tree(self, base_dir, files)
+        # 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 %s" % target_versionfile)
+        os.unlink(target_versionfile)
+        f = open(target_versionfile, "w")
+        f.write(SHORT_VERSION_PY % self._versioneer_generated_versions)
+        f.close()
+
+INIT_PY_SNIPPET = """
+from ._version import get_versions
+__version__ = get_versions()['version']
+del get_versions
+"""
+
+class cmd_update_files(Command):
+    description = "modify __init__.py and create _version.py"
+    user_options = []
+    boolean_options = []
+    def initialize_options(self):
+        pass
+    def finalize_options(self):
+        pass
+    def run(self):
+        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()
+        try:
+            old = open(ipy, "r").read()
+        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()
+        else:
+            print(" %s unmodified" % ipy)
+        do_vcs_install(versionfile_source, ipy)
+
+def get_cmdclass():
+    return {'version': cmd_version,
+            'update_files': cmd_update_files,
+            'build': cmd_build,
+            'sdist': cmd_sdist,
+            }






More information about the tor-commits mailing list