Completed
Pull Request — develop (#7)
by Kale
57s
created

auxlib.get_version()   B

Complexity

Conditions 4

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 4
dl 0
loc 25
rs 8.5806
1
# -*- coding: utf-8 -*-
2
from __future__ import print_function, division, absolute_import
3
from logging import getLogger
4
from os import remove
5
6
from os.path import join
7
from re import match
8
from setuptools.command.build_py import build_py
9
from setuptools.command.sdist import sdist
10
from setuptools.command.test import test as TestCommand
11
from subprocess import CalledProcessError, check_call, check_output, call
12
import sys
13
14
from .path import absdirname, PackageFile
15
16
log = getLogger(__name__)
17
18
19
def _get_version_from_pkg_info(package_name):
20
    with PackageFile('.version', package_name) as fh:
21
        return fh.read()
22
23
24
def _is_git_dirty(path):
25
    try:
26
        check_call(('git', 'diff', '--quiet'), cwd=path)
27
        check_call(('git', 'diff', '--cached', '--quiet'), cwd=path)
28
        return False
29
    except CalledProcessError:
30
        return True
31
32
33
def _get_most_recent_git_tag(path):
34
    try:
35
        return check_output(("git", "describe", "--tags"), cwd=path).strip()
36
    except CalledProcessError as e:
37
        if e.returncode == 128:
38
            return "0.0.0.0"
39
        else:
40
            raise  # pragma: no cover
41
42
43
def _get_git_hash(path):
44
    try:
45
        return check_output(("git", "rev-parse", "HEAD"), cwd=path).strip()[:7]
46
    except CalledProcessError:
47
        return 0
48
49
50
def _get_version_from_git_tag(path):
51
    """Return a PEP440-compliant version derived from the git status.
52
    If that fails for any reason, return the first 7 chars of the changeset hash.
53
    """
54
    tag = _get_most_recent_git_tag(path)
55
    m = match(b"(?P<xyz>\d+\.\d+\.\d+)(?:-(?P<dev>\d+)-(?P<hash>.+))?", tag)
56
    version = m.group('xyz').decode('utf-8')
57
    if m.group('dev') or _is_git_dirty(path):
58
        dev = (m.group('dev') or b'0').decode('utf-8')
59
        hash_ = (m.group('hash') or _get_git_hash(path)).decode('utf-8')
60
        version += ".dev{dev}+{hash_}".format(dev=dev, hash_=hash_)
61
    return version
62
63
64
def is_git_repo(path):
65
    return call(('git', 'rev-parse'), cwd=path) == 0
66
67
68
def get_version(file, package):
69
    """Returns a version string for the current package, derived
70
    either from git or from a .version file.
71
72
    This function is expected to run in two contexts. In a development
73
    context, where .git/ exists, the version is pulled from git tags.
74
    Using the BuildPyCommand and SDistCommand classes for cmdclass in
75
    setup.py will write a .version file into any dist.
76
77
    In an installed context, the .version file written at dist build
78
    time is the source of version information.
79
80
    """
81
    # check for .version file
82
    try:
83
        version_from_pkg = _get_version_from_pkg_info(package)
84
        return version_from_pkg.decode('UTF-8') if hasattr(version_from_pkg, 'decode') else version_from_pkg  # NOQA
85
    except IOError:
86
        print('no .version file found; fall back to git repo')
87
    # no .version file found; fall back to git repo
88
    here = absdirname(file)
89
    if is_git_repo(here):
90
        return _get_version_from_git_tag(here)
91
92
    raise RuntimeError("Could not get package version (no .git or .version file)")
93
94
95
def write_version_into_init(target_init_file, version):
96
    with open(target_init_file, 'r') as f:
97
        init_lines = f.readlines()
98
    for q in range(len(init_lines)):
99
        if init_lines[q].startswith('__version__'):
100
            init_lines[q] = '__version__ = "{0}"\n'.format(version)
101
        elif init_lines[q].startswith(('from auxlib', 'import auxlib')):
102
            init_lines[q] = None
103
    print("UPDATING {0}".format(target_init_file))
104
    remove(target_init_file)
105
    with open(target_init_file, 'w') as f:
106
        f.write(''.join(l for l in init_lines if l is not None))
107
108
109
class BuildPyCommand(build_py):
110
    def run(self):
111
        build_py.run(self)
112
        target_init_file = join(self.build_lib, self.distribution.metadata.name, "__init__.py")
113
        write_version_into_init(target_init_file, self.distribution.metadata.version)
114
115
116
class SDistCommand(sdist):
117
    def make_release_tree(self, base_dir, files):
118
        sdist.make_release_tree(self, base_dir, files)
119
        target_init_file = join(base_dir, self.distribution.metadata.name, "__init__.py")
120
        write_version_into_init(target_init_file, self.distribution.metadata.version)
121
122
123
class Tox(TestCommand):
124
    user_options = [('tox-args=', 'a', "Arguments to pass to tox")]
125
126
    def initialize_options(self):
127
        TestCommand.initialize_options(self)
128
        self.tox_args = None
129
130
    def finalize_options(self):
131
        TestCommand.finalize_options(self)
132
        self.test_args = []
133
        self.test_suite = True
134
135
    def run_tests(self):
136
        # import here, cause outside the eggs aren't loaded
137
        import tox
138
        import shlex
139
        args = self.tox_args
140
        if args:
141
            args = shlex.split(self.tox_args)
142
        else:
143
            args = ''
144
        errno = tox.cmdline(args=args)
145
        sys.exit(errno)
146