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