Browse code

remove versioneer and use miniver instead

Joseph Weston authored on 24/02/2018 15:47:38
Showing 1 changed files
... ...
@@ -1,520 +1,169 @@
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
1
+# -*- coding: utf-8 -*-
2
+# This file is part of 'miniver': https://github.com/jbweston/miniver
3
+#
4
+from collections import namedtuple
14 5
 import os
15
-import re
16 6
 import subprocess
17 7
 import sys
18 8
 
9
+from distutils.command.build import build as build_orig
10
+from setuptools.command.sdist import sdist as sdist_orig
19 11
 
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
12
+Version = namedtuple('Version', ('release', 'dev', 'labels'))
31 13
 
14
+# No public API
15
+__all__ = []
32 16
 
33
-class VersioneerConfig:
34
-    """Container for Versioneer configuration parameters."""
17
+package_root = os.path.dirname(os.path.realpath(__file__))
18
+package_name = os.path.basename(package_root)
19
+distr_root = os.path.dirname(package_root)
35 20
 
21
+STATIC_VERSION_FILE = '_static_version.py'
36 22
 
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 = "nord-"
46
-    cfg.versionfile_source = "nord/_version.py"
47
-    cfg.verbose = False
48
-    return cfg
49 23
 
24
+def get_version(version_file=STATIC_VERSION_FILE):
25
+    version_info = {}
26
+    with open(os.path.join(package_root, version_file), 'rb') as f:
27
+        exec(f.read(), {}, version_info)
28
+    version = version_info['version']
29
+    version_is_from_git = (version == "__use_git__")
30
+    if version_is_from_git:
31
+        version = get_version_from_git()
32
+        if not version:
33
+            version = get_version_from_git_archive(version_info)
34
+        if not version:
35
+            version = Version("unknown", None, None)
36
+        return semver_format(version)
37
+    else:
38
+        return version
50 39
 
51
-class NotThisMethod(Exception):
52
-    """Exception raised if a method is not valid for the current scenario."""
53 40
 
41
+def semver_format(version_info):
42
+    release, dev, labels = version_info
54 43
 
55
-LONG_VERSION_PY = {}
56
-HANDLERS = {}
44
+    version_parts = [release]
45
+    if dev:
46
+        if release.endswith('-dev'):
47
+            version_parts.append(dev)
48
+        elif release.contains('-'):
49
+            version_parts.append('.dev{}'.format(dev))
50
+        else:
51
+            version_parts.append('-dev{}'.format(dev))
57 52
 
53
+    if labels:
54
+        version_parts.append('+')
55
+        version_parts.append(".".join(labels))
58 56
 
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:
64
-            HANDLERS[vcs] = {}
65
-        HANDLERS[vcs][method] = f
66
-        return f
67
-    return decorate
57
+    return "".join(version_parts)
68 58
 
69 59
 
70
-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
71
-                env=None):
72
-    """Call the given command(s)."""
73
-    assert isinstance(commands, list)
74
-    p = None
75
-    for c in commands:
60
+def get_version_from_git():
61
+    try:
62
+        p = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'],
63
+                             cwd=distr_root,
64
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
65
+    except OSError:
66
+        return
67
+    if p.wait() != 0:
68
+        return
69
+    if not os.path.samefile(p.communicate()[0].decode().rstrip('\n'),
70
+                            distr_root):
71
+        # The top-level directory of the current Git repository is not the same
72
+        # as the root directory of the Kwant distribution: do not extract the
73
+        # version from Git.
74
+        return
75
+
76
+    # git describe --first-parent does not take into account tags from branches
77
+    # that were merged-in.
78
+    for opts in [['--first-parent'], []]:
76 79
         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))
80
+            p = subprocess.Popen(['git', 'describe', '--long'] + opts,
81
+                                 cwd=distr_root,
82
+                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
83
+        except OSError:
84
+            return
85
+        if p.wait() == 0:
83 86
             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 87
     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
-def versions_from_parentdir(parentdir_prefix, root, verbose):
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
-@register_vcs_handler("git", "get_keywords")
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
-@register_vcs_handler("git", "keywords")
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
-@register_vcs_handler("git", "pieces_from_vcs")
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)
88
+        return
89
+    description = p.communicate()[0].decode().strip('v').rstrip('\n')
292 90
 
91
+    release, dev, git = description.rsplit('-', 2)
92
+    labels = []
93
+    if dev == "0":
94
+        dev = None
293 95
     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
-def render_pep440(pieces):
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
-def render_pep440_pre(pieces):
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
-def render_pep440_post(pieces):
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
-def render_pep440_old(pieces):
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
-
96
+        labels.append(git)
404 97
 
405
-def render_git_describe(pieces):
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"])
98
+    try:
99
+        p = subprocess.Popen(['git', 'diff', '--quiet'], cwd=distr_root)
100
+    except OSError:
101
+        labels.append('confused') # This should never happen.
417 102
     else:
418
-        # exception #1
419
-        rendered = pieces["short"]
420
-    if pieces["dirty"]:
421
-        rendered += "-dirty"
422
-    return rendered
423
-
103
+        if p.wait() == 1:
104
+            labels.append('dirty')
424 105
 
425
-def render_git_describe_long(pieces):
426
-    """TAG-DISTANCE-gHEX[-dirty].
106
+    return Version(release, dev, labels)
427 107
 
428
-    Like 'git describe --tags --dirty --always -long'.
429
-    The distance/hash is unconditional.
430 108
 
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
-def render(pieces, style):
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)
109
+# TODO: change this logic when there is a git pretty-format
110
+#       that gives the same output as 'git describe'.
111
+#       Currently we can only tell the tag the current commit is
112
+#       pointing to, or its hash (with no version info)
113
+#       if it is not tagged.
114
+def get_version_from_git_archive(version_info):
115
+    try:
116
+        refnames = version_info['refnames']
117
+        git_hash = version_info['git_hash']
118
+    except KeyError:
119
+        # These fields are not present if we are running from an sdist.
120
+        # Execution should never reach here, though
121
+        return None
122
+
123
+    if git_hash.startswith('$Format') or refnames.startswith('$Format'):
124
+        # variables not expanded during 'git archive'
125
+        return None
126
+
127
+    VTAG = 'tag: v'
128
+    refs = set(r.strip() for r in refnames.split(","))
129
+    version_tags = set(r[len(VTAG):] for r in refs if r.startswith(VTAG))
130
+    if version_tags:
131
+        release, *_ = sorted(version_tags)  # prefer e.g. "2.0" over "2.0rc1"
132
+        return Version(release, dev=None, labels=None)
469 133
     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")}
134
+        return Version('unknown', dev=None, labels=[f'g{git_hash}'])
475 135
 
476 136
 
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.
137
+__version__ = get_version()
483 138
 
484
-    cfg = get_config()
485
-    verbose = cfg.verbose
139
+# The following section defines a module global 'cmdclass',
140
+# which can be used from setup.py. The 'package_name' and
141
+# '__version__' module globals are used (but not modified).
486 142
 
143
+def _write_version(fname):
144
+    # This could be a hard link, so try to delete it first.  Is there any way
145
+    # to do this atomically together with opening?
487 146
     try:
488
-        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
489
-                                          verbose)
490
-    except NotThisMethod:
147
+        os.remove(fname)
148
+    except OSError:
491 149
         pass
150
+    with open(fname, 'w') as f:
151
+        f.write("# This file has been created by setup.py.\n"
152
+                "version = '{}'\n".format(__version__))
492 153
 
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 154
 
506
-    try:
507
-        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
508
-        return render(pieces, cfg.style)
509
-    except NotThisMethod:
510
-        pass
155
+class _build(build_orig):
156
+    def run(self):
157
+        super().run()
158
+        _write_version(os.path.join(self.build_lib, package_name,
159
+                                    STATIC_VERSION_FILE))
160
+
161
+
162
+class _sdist(sdist_orig):
163
+    def make_release_tree(self, base_dir, files):
164
+        super().make_release_tree(base_dir, files)
165
+        _write_version(os.path.join(base_dir, package_name,
166
+                                    STATIC_VERSION_FILE))
511 167
 
512
-    try:
513
-        if cfg.parentdir_prefix:
514
-            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
515
-    except NotThisMethod:
516
-        pass
517 168
 
518
-    return {"version": "0+unknown", "full-revisionid": None,
519
-            "dirty": None,
520
-            "error": "unable to compute version", "date": None}
169
+cmdclass = dict(sdist=_sdist, build=_build)
Browse code

add versioneer

Versioneer is quite big, but it is the simplest solution in terms
of installation.

Closes #10

Joseph Weston authored on 07/09/2017 13:37:54
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,520 @@
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 = "nord-"
46
+    cfg.versionfile_source = "nord/_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:
64
+            HANDLERS[vcs] = {}
65
+        HANDLERS[vcs][method] = f
66
+        return f
67
+    return decorate
68
+
69
+
70
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
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
+def versions_from_parentdir(parentdir_prefix, root, verbose):
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
+@register_vcs_handler("git", "get_keywords")
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
+@register_vcs_handler("git", "keywords")
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
+@register_vcs_handler("git", "pieces_from_vcs")
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
+def render_pep440(pieces):
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
+def render_pep440_pre(pieces):
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
+def render_pep440_post(pieces):
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
+def render_pep440_old(pieces):
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
+def render_git_describe(pieces):
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
+def render_git_describe_long(pieces):
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
+def render(pieces, style):
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}