Completed
Push — develop ( d3b6bc...d32356 )
by Kale
57s
created

auxlib.write_version_file()   A

Complexity

Conditions 3

Size

Total Lines 5

Duplication

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