build.rna_tools._version.versions_from_parentdir()   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 23
Code Lines 14

Duplication

Lines 23
Ratio 100 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nop 3
dl 23
loc 23
rs 9.7
c 0
b 0
f 0
1
2
# This file helps to compute a version number in source trees obtained from
3
# git-archive tarball (such as those provided by githubs download-from-tag
4
# feature). Distribution tarballs (built by setup.py sdist) and build
5
# directories (produced by setup.py build) will contain a much shorter file
6
# that just contains the computed version number.
7
8
# This file is released into the public domain. Generated by
9
# versioneer-0.18 (https://github.com/warner/python-versioneer)
10
11
"""Git implementation of _version.py."""
12
13
import errno
14
import os
15
import re
16
import subprocess
17
import sys
18
19
20
def get_keywords():
21
    """Get the keywords needed to look up the version information."""
22
    # these strings will be replaced by git during git-archive.
23
    # setup.py/versioneer.py will grep for the variable names, so they must
24
    # each be defined on a line of their own. _version.py will just call
25
    # get_keywords().
26
    git_refnames = "$Format:%d$"
27
    git_full = "$Format:%H$"
28
    git_date = "$Format:%ci$"
29
    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
30
    return keywords
31
32
33
class VersioneerConfig:
34
    """Container for Versioneer configuration parameters."""
35
36
37
def get_config():
38
    """Create, populate and return the VersioneerConfig() object."""
39
    # these strings are filled in when 'setup.py versioneer' creates
40
    # _version.py
41
    cfg = VersioneerConfig()
42
    cfg.VCS = "git"
43
    cfg.style = "pep440"
44
    cfg.tag_prefix = ""
45
    cfg.parentdir_prefix = "rna_tools-"
46
    cfg.versionfile_source = "rna_tools/_version.py"
47
    cfg.verbose = False
48
    return cfg
49
50
51
class NotThisMethod(Exception):
52
    """Exception raised if a method is not valid for the current scenario."""
53
54
55
LONG_VERSION_PY = {}
56
HANDLERS = {}
57
58
59
def register_vcs_handler(vcs, method):  # decorator
60
    """Decorator to mark a method as the handler for a particular VCS."""
61
    def decorate(f):
62
        """Store f in HANDLERS[vcs][method]."""
63
        if vcs not in HANDLERS:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable vcs does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable HANDLERS does not seem to be defined.
Loading history...
64
            HANDLERS[vcs] = {}
65
        HANDLERS[vcs][method] = f
66
        return f
67
    return decorate
68
69
70 View Code Duplication
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
71
                env=None):
72
    """Call the given command(s)."""
73
    assert isinstance(commands, list)
74
    p = None
75
    for c in commands:
76
        try:
77
            dispcmd = str([c] + args)
78
            # remember shell=False, so use git.cmd on windows, not just git
79
            p = subprocess.Popen([c] + args, cwd=cwd, env=env,
80
                                 stdout=subprocess.PIPE,
81
                                 stderr=(subprocess.PIPE if hide_stderr
82
                                         else None))
83
            break
84
        except EnvironmentError:
85
            e = sys.exc_info()[1]
86
            if e.errno == errno.ENOENT:
87
                continue
88
            if verbose:
89
                print("unable to run %s" % dispcmd)
90
                print(e)
91
            return None, None
92
    else:
93
        if verbose:
94
            print("unable to find command, tried %s" % (commands,))
95
        return None, None
96
    stdout = p.communicate()[0].strip()
97
    if sys.version_info[0] >= 3:
98
        stdout = stdout.decode()
99
    if p.returncode != 0:
100
        if verbose:
101
            print("unable to run %s (error)" % dispcmd)
102
            print("stdout was %s" % stdout)
103
        return None, p.returncode
104
    return stdout, p.returncode
105
106
107 View Code Duplication
def versions_from_parentdir(parentdir_prefix, root, verbose):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
108
    """Try to determine the version from the parent directory name.
109
110
    Source tarballs conventionally unpack into a directory that includes both
111
    the project name and a version string. We will also support searching up
112
    two directory levels for an appropriately named parent directory
113
    """
114
    rootdirs = []
115
116
    for i in range(3):
117
        dirname = os.path.basename(root)
118
        if dirname.startswith(parentdir_prefix):
119
            return {"version": dirname[len(parentdir_prefix):],
120
                    "full-revisionid": None,
121
                    "dirty": False, "error": None, "date": None}
122
        else:
123
            rootdirs.append(root)
124
            root = os.path.dirname(root)  # up a level
125
126
    if verbose:
127
        print("Tried directories %s but none started with prefix %s" %
128
              (str(rootdirs), parentdir_prefix))
129
    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
130
131
132 View Code Duplication
@register_vcs_handler("git", "get_keywords")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
133
def git_get_keywords(versionfile_abs):
134
    """Extract version information from the given file."""
135
    # the code embedded in _version.py can just fetch the value of these
136
    # keywords. When used from setup.py, we don't want to import _version.py,
137
    # so we do it with a regexp instead. This function is not used from
138
    # _version.py.
139
    keywords = {}
140
    try:
141
        f = open(versionfile_abs, "r")
142
        for line in f.readlines():
143
            if line.strip().startswith("git_refnames ="):
144
                mo = re.search(r'=\s*"(.*)"', line)
145
                if mo:
146
                    keywords["refnames"] = mo.group(1)
147
            if line.strip().startswith("git_full ="):
148
                mo = re.search(r'=\s*"(.*)"', line)
149
                if mo:
150
                    keywords["full"] = mo.group(1)
151
            if line.strip().startswith("git_date ="):
152
                mo = re.search(r'=\s*"(.*)"', line)
153
                if mo:
154
                    keywords["date"] = mo.group(1)
155
        f.close()
156
    except EnvironmentError:
157
        pass
158
    return keywords
159
160
161 View Code Duplication
@register_vcs_handler("git", "keywords")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
162
def git_versions_from_keywords(keywords, tag_prefix, verbose):
163
    """Get version information from git keywords."""
164
    if not keywords:
165
        raise NotThisMethod("no keywords at all, weird")
166
    date = keywords.get("date")
167
    if date is not None:
168
        # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
169
        # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
170
        # -like" string, which we must then edit to make compliant), because
171
        # it's been around since git-1.5.3, and it's too difficult to
172
        # discover which version we're using, or to work around using an
173
        # older one.
174
        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
175
    refnames = keywords["refnames"].strip()
176
    if refnames.startswith("$Format"):
177
        if verbose:
178
            print("keywords are unexpanded, not using")
179
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
180
    refs = set([r.strip() for r in refnames.strip("()").split(",")])
181
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
182
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
183
    TAG = "tag: "
184
    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
185
    if not tags:
186
        # Either we're using git < 1.8.3, or there really are no tags. We use
187
        # a heuristic: assume all version tags have a digit. The old git %d
188
        # expansion behaves like git log --decorate=short and strips out the
189
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
190
        # between branches and tags. By ignoring refnames without digits, we
191
        # filter out many common branch names like "release" and
192
        # "stabilization", as well as "HEAD" and "master".
193
        tags = set([r for r in refs if re.search(r'\d', r)])
194
        if verbose:
195
            print("discarding '%s', no digits" % ",".join(refs - tags))
196
    if verbose:
197
        print("likely tags: %s" % ",".join(sorted(tags)))
198
    for ref in sorted(tags):
199
        # sorting will prefer e.g. "2.0" over "2.0rc1"
200
        if ref.startswith(tag_prefix):
201
            r = ref[len(tag_prefix):]
202
            if verbose:
203
                print("picking %s" % r)
204
            return {"version": r,
205
                    "full-revisionid": keywords["full"].strip(),
206
                    "dirty": False, "error": None,
207
                    "date": date}
208
    # no suitable tags, so version is "0+unknown", but full hex is still there
209
    if verbose:
210
        print("no suitable tags, using unknown + full revision id")
211
    return {"version": "0+unknown",
212
            "full-revisionid": keywords["full"].strip(),
213
            "dirty": False, "error": "no suitable tags", "date": None}
214
215
216 View Code Duplication
@register_vcs_handler("git", "pieces_from_vcs")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
217
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
218
    """Get version from 'git describe' in the root of the source tree.
219
220
    This only gets called if the git-archive 'subst' keywords were *not*
221
    expanded, and _version.py hasn't already been rewritten with a short
222
    version string, meaning we're inside a checked out source tree.
223
    """
224
    GITS = ["git"]
225
    if sys.platform == "win32":
226
        GITS = ["git.cmd", "git.exe"]
227
228
    out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
229
                          hide_stderr=True)
230
    if rc != 0:
231
        if verbose:
232
            print("Directory %s not under git control" % root)
233
        raise NotThisMethod("'git rev-parse --git-dir' returned error")
234
235
    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
236
    # if there isn't one, this yields HEX[-dirty] (no NUM)
237
    describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
238
                                          "--always", "--long",
239
                                          "--match", "%s*" % tag_prefix],
240
                                   cwd=root)
241
    # --long was added in git-1.5.5
242
    if describe_out is None:
243
        raise NotThisMethod("'git describe' failed")
244
    describe_out = describe_out.strip()
245
    full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
246
    if full_out is None:
247
        raise NotThisMethod("'git rev-parse' failed")
248
    full_out = full_out.strip()
249
250
    pieces = {}
251
    pieces["long"] = full_out
252
    pieces["short"] = full_out[:7]  # maybe improved later
253
    pieces["error"] = None
254
255
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
256
    # TAG might have hyphens.
257
    git_describe = describe_out
258
259
    # look for -dirty suffix
260
    dirty = git_describe.endswith("-dirty")
261
    pieces["dirty"] = dirty
262
    if dirty:
263
        git_describe = git_describe[:git_describe.rindex("-dirty")]
264
265
    # now we have TAG-NUM-gHEX or HEX
266
267
    if "-" in git_describe:
268
        # TAG-NUM-gHEX
269
        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
270
        if not mo:
271
            # unparseable. Maybe git-describe is misbehaving?
272
            pieces["error"] = ("unable to parse git-describe output: '%s'"
273
                               % describe_out)
274
            return pieces
275
276
        # tag
277
        full_tag = mo.group(1)
278
        if not full_tag.startswith(tag_prefix):
279
            if verbose:
280
                fmt = "tag '%s' doesn't start with prefix '%s'"
281
                print(fmt % (full_tag, tag_prefix))
282
            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
283
                               % (full_tag, tag_prefix))
284
            return pieces
285
        pieces["closest-tag"] = full_tag[len(tag_prefix):]
286
287
        # distance: number of commits since tag
288
        pieces["distance"] = int(mo.group(2))
289
290
        # commit: short hex revision ID
291
        pieces["short"] = mo.group(3)
292
293
    else:
294
        # HEX: no tags
295
        pieces["closest-tag"] = None
296
        count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
297
                                    cwd=root)
298
        pieces["distance"] = int(count_out)  # total number of commits
299
300
    # commit date: see ISO-8601 comment in git_versions_from_keywords()
301
    date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
302
                       cwd=root)[0].strip()
303
    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
304
305
    return pieces
306
307
308
def plus_or_dot(pieces):
309
    """Return a + if we don't already have one, else return a ."""
310
    if "+" in pieces.get("closest-tag", ""):
311
        return "."
312
    return "+"
313
314
315 View Code Duplication
def render_pep440(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
316
    """Build up version string, with post-release "local version identifier".
317
318
    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
319
    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
320
321
    Exceptions:
322
    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
323
    """
324
    if pieces["closest-tag"]:
325
        rendered = pieces["closest-tag"]
326
        if pieces["distance"] or pieces["dirty"]:
327
            rendered += plus_or_dot(pieces)
328
            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
329
            if pieces["dirty"]:
330
                rendered += ".dirty"
331
    else:
332
        # exception #1
333
        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
334
                                          pieces["short"])
335
        if pieces["dirty"]:
336
            rendered += ".dirty"
337
    return rendered
338
339
340 View Code Duplication
def render_pep440_pre(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
341
    """TAG[.post.devDISTANCE] -- No -dirty.
342
343
    Exceptions:
344
    1: no tags. 0.post.devDISTANCE
345
    """
346
    if pieces["closest-tag"]:
347
        rendered = pieces["closest-tag"]
348
        if pieces["distance"]:
349
            rendered += ".post.dev%d" % pieces["distance"]
350
    else:
351
        # exception #1
352
        rendered = "0.post.dev%d" % pieces["distance"]
353
    return rendered
354
355
356 View Code Duplication
def render_pep440_post(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
357
    """TAG[.postDISTANCE[.dev0]+gHEX] .
358
359
    The ".dev0" means dirty. Note that .dev0 sorts backwards
360
    (a dirty tree will appear "older" than the corresponding clean one),
361
    but you shouldn't be releasing software with -dirty anyways.
362
363
    Exceptions:
364
    1: no tags. 0.postDISTANCE[.dev0]
365
    """
366
    if pieces["closest-tag"]:
367
        rendered = pieces["closest-tag"]
368
        if pieces["distance"] or pieces["dirty"]:
369
            rendered += ".post%d" % pieces["distance"]
370
            if pieces["dirty"]:
371
                rendered += ".dev0"
372
            rendered += plus_or_dot(pieces)
373
            rendered += "g%s" % pieces["short"]
374
    else:
375
        # exception #1
376
        rendered = "0.post%d" % pieces["distance"]
377
        if pieces["dirty"]:
378
            rendered += ".dev0"
379
        rendered += "+g%s" % pieces["short"]
380
    return rendered
381
382
383 View Code Duplication
def render_pep440_old(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
384
    """TAG[.postDISTANCE[.dev0]] .
385
386
    The ".dev0" means dirty.
387
388
    Eexceptions:
389
    1: no tags. 0.postDISTANCE[.dev0]
390
    """
391
    if pieces["closest-tag"]:
392
        rendered = pieces["closest-tag"]
393
        if pieces["distance"] or pieces["dirty"]:
394
            rendered += ".post%d" % pieces["distance"]
395
            if pieces["dirty"]:
396
                rendered += ".dev0"
397
    else:
398
        # exception #1
399
        rendered = "0.post%d" % pieces["distance"]
400
        if pieces["dirty"]:
401
            rendered += ".dev0"
402
    return rendered
403
404
405 View Code Duplication
def render_git_describe(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
406
    """TAG[-DISTANCE-gHEX][-dirty].
407
408
    Like 'git describe --tags --dirty --always'.
409
410
    Exceptions:
411
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
412
    """
413
    if pieces["closest-tag"]:
414
        rendered = pieces["closest-tag"]
415
        if pieces["distance"]:
416
            rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
417
    else:
418
        # exception #1
419
        rendered = pieces["short"]
420
    if pieces["dirty"]:
421
        rendered += "-dirty"
422
    return rendered
423
424
425 View Code Duplication
def render_git_describe_long(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
426
    """TAG-DISTANCE-gHEX[-dirty].
427
428
    Like 'git describe --tags --dirty --always -long'.
429
    The distance/hash is unconditional.
430
431
    Exceptions:
432
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
433
    """
434
    if pieces["closest-tag"]:
435
        rendered = pieces["closest-tag"]
436
        rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
437
    else:
438
        # exception #1
439
        rendered = pieces["short"]
440
    if pieces["dirty"]:
441
        rendered += "-dirty"
442
    return rendered
443
444
445 View Code Duplication
def render(pieces, style):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
446
    """Render the given version pieces into the requested style."""
447
    if pieces["error"]:
448
        return {"version": "unknown",
449
                "full-revisionid": pieces.get("long"),
450
                "dirty": None,
451
                "error": pieces["error"],
452
                "date": None}
453
454
    if not style or style == "default":
455
        style = "pep440"  # the default
456
457
    if style == "pep440":
458
        rendered = render_pep440(pieces)
459
    elif style == "pep440-pre":
460
        rendered = render_pep440_pre(pieces)
461
    elif style == "pep440-post":
462
        rendered = render_pep440_post(pieces)
463
    elif style == "pep440-old":
464
        rendered = render_pep440_old(pieces)
465
    elif style == "git-describe":
466
        rendered = render_git_describe(pieces)
467
    elif style == "git-describe-long":
468
        rendered = render_git_describe_long(pieces)
469
    else:
470
        raise ValueError("unknown style '%s'" % style)
471
472
    return {"version": rendered, "full-revisionid": pieces["long"],
473
            "dirty": pieces["dirty"], "error": None,
474
            "date": pieces.get("date")}
475
476
477
def get_versions():
478
    """Get version information or return default if unable to do so."""
479
    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
480
    # __file__, we can work backwards from there to the root. Some
481
    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
482
    # case we can only use expanded keywords.
483
484
    cfg = get_config()
485
    verbose = cfg.verbose
486
487
    try:
488
        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
489
                                          verbose)
490
    except NotThisMethod:
491
        pass
492
493
    try:
494
        root = os.path.realpath(__file__)
495
        # versionfile_source is the relative path from the top of the source
496
        # tree (where the .git directory might live) to this file. Invert
497
        # this to find the root from __file__.
498
        for i in cfg.versionfile_source.split('/'):
499
            root = os.path.dirname(root)
500
    except NameError:
501
        return {"version": "0+unknown", "full-revisionid": None,
502
                "dirty": None,
503
                "error": "unable to find root of source tree",
504
                "date": None}
505
506
    try:
507
        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
508
        return render(pieces, cfg.style)
509
    except NotThisMethod:
510
        pass
511
512
    try:
513
        if cfg.parentdir_prefix:
514
            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
515
    except NotThisMethod:
516
        pass
517
518
    return {"version": "0+unknown", "full-revisionid": None,
519
            "dirty": None,
520
            "error": "unable to compute version", "date": None}
521