blob: 4edb5da9ef0ed999643173640c8b4160a7325b72 [file] [log] [blame]
Patrick Williams169d7bc2024-01-05 11:33:25 -06001From 739f9da6bf0d2d9f0de624aee2ec71c65f62c275 Mon Sep 17 00:00:00 2001
2From: Hugo van Kemenade <hugovk@users.noreply.github.com>
3Date: Tue, 10 May 2022 18:17:50 +0300
4Subject: [PATCH] Update Versioneer to 0.22
5
6Upstream-Status: Backport [https://github.com/click-contrib/click-spinner/commit/5622ab0a0b4296dc8f10863f268ed98dccf4b642]
7
8Signed-off-by: Ny Antra Ranaivoarison <nyantra.ranaivoarison@smile.fr>
9---
10 click_spinner/__init__.py | 5 +-
11 click_spinner/_version.py | 665 +++++++++++++++++++++-
12 versioneer.py | 1128 ++++++++++++++++++++++++-------------
13 3 files changed, 1400 insertions(+), 398 deletions(-)
14
15diff --git a/click_spinner/__init__.py b/click_spinner/__init__.py
16index aeec089..8e9f4f9 100644
17--- a/click_spinner/__init__.py
18+++ b/click_spinner/__init__.py
19@@ -77,6 +77,5 @@ def spinner(beep=False, disable=False, force=False, stream=sys.stdout):
20 return Spinner(beep, disable, force, stream)
21
22
23-from ._version import get_versions
24-__version__ = get_versions()['version']
25-del get_versions
26+from . import _version
27+__version__ = _version.get_versions()['version']
28diff --git a/click_spinner/_version.py b/click_spinner/_version.py
29index 5ae340e..d44565d 100644
30--- a/click_spinner/_version.py
31+++ b/click_spinner/_version.py
32@@ -1,21 +1,658 @@
33
34-# This file was generated by 'versioneer.py' (0.16) from
35-# revision-control system data, or from the parent directory name of an
36-# unpacked source archive. Distribution tarballs contain a pre-generated copy
37-# of this file.
38+# This file helps to compute a version number in source trees obtained from
39+# git-archive tarball (such as those provided by githubs download-from-tag
40+# feature). Distribution tarballs (built by setup.py sdist) and build
41+# directories (produced by setup.py build) will contain a much shorter file
42+# that just contains the computed version number.
43
44-import json
45+# This file is released into the public domain. Generated by
46+# versioneer-0.22 (https://github.com/python-versioneer/python-versioneer)
47+
48+"""Git implementation of _version.py."""
49+
50+import errno
51+import os
52+import re
53+import subprocess
54 import sys
55+from typing import Callable, Dict
56+import functools
57+
58+
59+def get_keywords():
60+ """Get the keywords needed to look up the version information."""
61+ # these strings will be replaced by git during git-archive.
62+ # setup.py/versioneer.py will grep for the variable names, so they must
63+ # each be defined on a line of their own. _version.py will just call
64+ # get_keywords().
65+ git_refnames = "$Format:%d$"
66+ git_full = "$Format:%H$"
67+ git_date = "$Format:%ci$"
68+ keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
69+ return keywords
70+
71+
72+class VersioneerConfig:
73+ """Container for Versioneer configuration parameters."""
74+
75+
76+def get_config():
77+ """Create, populate and return the VersioneerConfig() object."""
78+ # these strings are filled in when 'setup.py versioneer' creates
79+ # _version.py
80+ cfg = VersioneerConfig()
81+ cfg.VCS = "git"
82+ cfg.style = "pep440"
83+ cfg.tag_prefix = "v"
84+ cfg.parentdir_prefix = "click-spinner-"
85+ cfg.versionfile_source = "click_spinner/_version.py"
86+ cfg.verbose = False
87+ return cfg
88+
89+
90+class NotThisMethod(Exception):
91+ """Exception raised if a method is not valid for the current scenario."""
92+
93+
94+LONG_VERSION_PY: Dict[str, str] = {}
95+HANDLERS: Dict[str, Dict[str, Callable]] = {}
96+
97+
98+def register_vcs_handler(vcs, method): # decorator
99+ """Create decorator to mark a method as the handler of a VCS."""
100+ def decorate(f):
101+ """Store f in HANDLERS[vcs][method]."""
102+ if vcs not in HANDLERS:
103+ HANDLERS[vcs] = {}
104+ HANDLERS[vcs][method] = f
105+ return f
106+ return decorate
107+
108+
109+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
110+ env=None):
111+ """Call the given command(s)."""
112+ assert isinstance(commands, list)
113+ process = None
114+
115+ popen_kwargs = {}
116+ if sys.platform == "win32":
117+ # This hides the console window if pythonw.exe is used
118+ startupinfo = subprocess.STARTUPINFO()
119+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
120+ popen_kwargs["startupinfo"] = startupinfo
121+
122+ for command in commands:
123+ try:
124+ dispcmd = str([command] + args)
125+ # remember shell=False, so use git.cmd on windows, not just git
126+ process = subprocess.Popen([command] + args, cwd=cwd, env=env,
127+ stdout=subprocess.PIPE,
128+ stderr=(subprocess.PIPE if hide_stderr
129+ else None), **popen_kwargs)
130+ break
131+ except OSError:
132+ e = sys.exc_info()[1]
133+ if e.errno == errno.ENOENT:
134+ continue
135+ if verbose:
136+ print("unable to run %s" % dispcmd)
137+ print(e)
138+ return None, None
139+ else:
140+ if verbose:
141+ print("unable to find command, tried %s" % (commands,))
142+ return None, None
143+ stdout = process.communicate()[0].strip().decode()
144+ if process.returncode != 0:
145+ if verbose:
146+ print("unable to run %s (error)" % dispcmd)
147+ print("stdout was %s" % stdout)
148+ return None, process.returncode
149+ return stdout, process.returncode
150+
151+
152+def versions_from_parentdir(parentdir_prefix, root, verbose):
153+ """Try to determine the version from the parent directory name.
154+
155+ Source tarballs conventionally unpack into a directory that includes both
156+ the project name and a version string. We will also support searching up
157+ two directory levels for an appropriately named parent directory
158+ """
159+ rootdirs = []
160+
161+ for _ in range(3):
162+ dirname = os.path.basename(root)
163+ if dirname.startswith(parentdir_prefix):
164+ return {"version": dirname[len(parentdir_prefix):],
165+ "full-revisionid": None,
166+ "dirty": False, "error": None, "date": None}
167+ rootdirs.append(root)
168+ root = os.path.dirname(root) # up a level
169+
170+ if verbose:
171+ print("Tried directories %s but none started with prefix %s" %
172+ (str(rootdirs), parentdir_prefix))
173+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
174+
175+
176+@register_vcs_handler("git", "get_keywords")
177+def git_get_keywords(versionfile_abs):
178+ """Extract version information from the given file."""
179+ # the code embedded in _version.py can just fetch the value of these
180+ # keywords. When used from setup.py, we don't want to import _version.py,
181+ # so we do it with a regexp instead. This function is not used from
182+ # _version.py.
183+ keywords = {}
184+ try:
185+ with open(versionfile_abs, "r") as fobj:
186+ for line in fobj:
187+ if line.strip().startswith("git_refnames ="):
188+ mo = re.search(r'=\s*"(.*)"', line)
189+ if mo:
190+ keywords["refnames"] = mo.group(1)
191+ if line.strip().startswith("git_full ="):
192+ mo = re.search(r'=\s*"(.*)"', line)
193+ if mo:
194+ keywords["full"] = mo.group(1)
195+ if line.strip().startswith("git_date ="):
196+ mo = re.search(r'=\s*"(.*)"', line)
197+ if mo:
198+ keywords["date"] = mo.group(1)
199+ except OSError:
200+ pass
201+ return keywords
202+
203+
204+@register_vcs_handler("git", "keywords")
205+def git_versions_from_keywords(keywords, tag_prefix, verbose):
206+ """Get version information from git keywords."""
207+ if "refnames" not in keywords:
208+ raise NotThisMethod("Short version file found")
209+ date = keywords.get("date")
210+ if date is not None:
211+ # Use only the last line. Previous lines may contain GPG signature
212+ # information.
213+ date = date.splitlines()[-1]
214+
215+ # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
216+ # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
217+ # -like" string, which we must then edit to make compliant), because
218+ # it's been around since git-1.5.3, and it's too difficult to
219+ # discover which version we're using, or to work around using an
220+ # older one.
221+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
222+ refnames = keywords["refnames"].strip()
223+ if refnames.startswith("$Format"):
224+ if verbose:
225+ print("keywords are unexpanded, not using")
226+ raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
227+ refs = {r.strip() for r in refnames.strip("()").split(",")}
228+ # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
229+ # just "foo-1.0". If we see a "tag: " prefix, prefer those.
230+ TAG = "tag: "
231+ tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
232+ if not tags:
233+ # Either we're using git < 1.8.3, or there really are no tags. We use
234+ # a heuristic: assume all version tags have a digit. The old git %d
235+ # expansion behaves like git log --decorate=short and strips out the
236+ # refs/heads/ and refs/tags/ prefixes that would let us distinguish
237+ # between branches and tags. By ignoring refnames without digits, we
238+ # filter out many common branch names like "release" and
239+ # "stabilization", as well as "HEAD" and "master".
240+ tags = {r for r in refs if re.search(r'\d', r)}
241+ if verbose:
242+ print("discarding '%s', no digits" % ",".join(refs - tags))
243+ if verbose:
244+ print("likely tags: %s" % ",".join(sorted(tags)))
245+ for ref in sorted(tags):
246+ # sorting will prefer e.g. "2.0" over "2.0rc1"
247+ if ref.startswith(tag_prefix):
248+ r = ref[len(tag_prefix):]
249+ # Filter out refs that exactly match prefix or that don't start
250+ # with a number once the prefix is stripped (mostly a concern
251+ # when prefix is '')
252+ if not re.match(r'\d', r):
253+ continue
254+ if verbose:
255+ print("picking %s" % r)
256+ return {"version": r,
257+ "full-revisionid": keywords["full"].strip(),
258+ "dirty": False, "error": None,
259+ "date": date}
260+ # no suitable tags, so version is "0+unknown", but full hex is still there
261+ if verbose:
262+ print("no suitable tags, using unknown + full revision id")
263+ return {"version": "0+unknown",
264+ "full-revisionid": keywords["full"].strip(),
265+ "dirty": False, "error": "no suitable tags", "date": None}
266+
267+
268+@register_vcs_handler("git", "pieces_from_vcs")
269+def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
270+ """Get version from 'git describe' in the root of the source tree.
271+
272+ This only gets called if the git-archive 'subst' keywords were *not*
273+ expanded, and _version.py hasn't already been rewritten with a short
274+ version string, meaning we're inside a checked out source tree.
275+ """
276+ GITS = ["git"]
277+ if sys.platform == "win32":
278+ GITS = ["git.cmd", "git.exe"]
279+
280+ # GIT_DIR can interfere with correct operation of Versioneer.
281+ # It may be intended to be passed to the Versioneer-versioned project,
282+ # but that should not change where we get our version from.
283+ env = os.environ.copy()
284+ env.pop("GIT_DIR", None)
285+ runner = functools.partial(runner, env=env)
286+
287+ _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
288+ hide_stderr=True)
289+ if rc != 0:
290+ if verbose:
291+ print("Directory %s not under git control" % root)
292+ raise NotThisMethod("'git rev-parse --git-dir' returned error")
293+
294+ MATCH_ARGS = ["--match", "%s*" % tag_prefix] if tag_prefix else []
295+
296+ # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
297+ # if there isn't one, this yields HEX[-dirty] (no NUM)
298+ describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty",
299+ "--always", "--long", *MATCH_ARGS],
300+ cwd=root)
301+ # --long was added in git-1.5.5
302+ if describe_out is None:
303+ raise NotThisMethod("'git describe' failed")
304+ describe_out = describe_out.strip()
305+ full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
306+ if full_out is None:
307+ raise NotThisMethod("'git rev-parse' failed")
308+ full_out = full_out.strip()
309+
310+ pieces = {}
311+ pieces["long"] = full_out
312+ pieces["short"] = full_out[:7] # maybe improved later
313+ pieces["error"] = None
314+
315+ branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
316+ cwd=root)
317+ # --abbrev-ref was added in git-1.6.3
318+ if rc != 0 or branch_name is None:
319+ raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
320+ branch_name = branch_name.strip()
321+
322+ if branch_name == "HEAD":
323+ # If we aren't exactly on a branch, pick a branch which represents
324+ # the current commit. If all else fails, we are on a branchless
325+ # commit.
326+ branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
327+ # --contains was added in git-1.5.4
328+ if rc != 0 or branches is None:
329+ raise NotThisMethod("'git branch --contains' returned error")
330+ branches = branches.split("\n")
331+
332+ # Remove the first line if we're running detached
333+ if "(" in branches[0]:
334+ branches.pop(0)
335+
336+ # Strip off the leading "* " from the list of branches.
337+ branches = [branch[2:] for branch in branches]
338+ if "master" in branches:
339+ branch_name = "master"
340+ elif not branches:
341+ branch_name = None
342+ else:
343+ # Pick the first branch that is returned. Good or bad.
344+ branch_name = branches[0]
345+
346+ pieces["branch"] = branch_name
347+
348+ # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
349+ # TAG might have hyphens.
350+ git_describe = describe_out
351+
352+ # look for -dirty suffix
353+ dirty = git_describe.endswith("-dirty")
354+ pieces["dirty"] = dirty
355+ if dirty:
356+ git_describe = git_describe[:git_describe.rindex("-dirty")]
357+
358+ # now we have TAG-NUM-gHEX or HEX
359+
360+ if "-" in git_describe:
361+ # TAG-NUM-gHEX
362+ mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
363+ if not mo:
364+ # unparsable. Maybe git-describe is misbehaving?
365+ pieces["error"] = ("unable to parse git-describe output: '%s'"
366+ % describe_out)
367+ return pieces
368+
369+ # tag
370+ full_tag = mo.group(1)
371+ if not full_tag.startswith(tag_prefix):
372+ if verbose:
373+ fmt = "tag '%s' doesn't start with prefix '%s'"
374+ print(fmt % (full_tag, tag_prefix))
375+ pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
376+ % (full_tag, tag_prefix))
377+ return pieces
378+ pieces["closest-tag"] = full_tag[len(tag_prefix):]
379+
380+ # distance: number of commits since tag
381+ pieces["distance"] = int(mo.group(2))
382+
383+ # commit: short hex revision ID
384+ pieces["short"] = mo.group(3)
385+
386+ else:
387+ # HEX: no tags
388+ pieces["closest-tag"] = None
389+ count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
390+ pieces["distance"] = int(count_out) # total number of commits
391
392-version_json = '''
393-{
394- "dirty": false,
395- "error": null,
396- "full-revisionid": "7cadb31e3e257c64a47a67255547f0a746e1a465",
397- "version": "0.1.10"
398-}
399-''' # END VERSION_JSON
400+ # commit date: see ISO-8601 comment in git_versions_from_keywords()
401+ date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
402+ # Use only the last line. Previous lines may contain GPG signature
403+ # information.
404+ date = date.splitlines()[-1]
405+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
406+
407+ return pieces
408+
409+
410+def plus_or_dot(pieces):
411+ """Return a + if we don't already have one, else return a ."""
412+ if "+" in pieces.get("closest-tag", ""):
413+ return "."
414+ return "+"
415+
416+
417+def render_pep440(pieces):
418+ """Build up version string, with post-release "local version identifier".
419+
420+ Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
421+ get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
422+
423+ Exceptions:
424+ 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
425+ """
426+ if pieces["closest-tag"]:
427+ rendered = pieces["closest-tag"]
428+ if pieces["distance"] or pieces["dirty"]:
429+ rendered += plus_or_dot(pieces)
430+ rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
431+ if pieces["dirty"]:
432+ rendered += ".dirty"
433+ else:
434+ # exception #1
435+ rendered = "0+untagged.%d.g%s" % (pieces["distance"],
436+ pieces["short"])
437+ if pieces["dirty"]:
438+ rendered += ".dirty"
439+ return rendered
440+
441+
442+def render_pep440_branch(pieces):
443+ """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
444+
445+ The ".dev0" means not master branch. Note that .dev0 sorts backwards
446+ (a feature branch will appear "older" than the master branch).
447+
448+ Exceptions:
449+ 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
450+ """
451+ if pieces["closest-tag"]:
452+ rendered = pieces["closest-tag"]
453+ if pieces["distance"] or pieces["dirty"]:
454+ if pieces["branch"] != "master":
455+ rendered += ".dev0"
456+ rendered += plus_or_dot(pieces)
457+ rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
458+ if pieces["dirty"]:
459+ rendered += ".dirty"
460+ else:
461+ # exception #1
462+ rendered = "0"
463+ if pieces["branch"] != "master":
464+ rendered += ".dev0"
465+ rendered += "+untagged.%d.g%s" % (pieces["distance"],
466+ pieces["short"])
467+ if pieces["dirty"]:
468+ rendered += ".dirty"
469+ return rendered
470+
471+
472+def pep440_split_post(ver):
473+ """Split pep440 version string at the post-release segment.
474+
475+ Returns the release segments before the post-release and the
476+ post-release version number (or -1 if no post-release segment is present).
477+ """
478+ vc = str.split(ver, ".post")
479+ return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
480+
481+
482+def render_pep440_pre(pieces):
483+ """TAG[.postN.devDISTANCE] -- No -dirty.
484+
485+ Exceptions:
486+ 1: no tags. 0.post0.devDISTANCE
487+ """
488+ if pieces["closest-tag"]:
489+ if pieces["distance"]:
490+ # update the post release segment
491+ tag_version, post_version = pep440_split_post(pieces["closest-tag"])
492+ rendered = tag_version
493+ if post_version is not None:
494+ rendered += ".post%d.dev%d" % (post_version+1, pieces["distance"])
495+ else:
496+ rendered += ".post0.dev%d" % (pieces["distance"])
497+ else:
498+ # no commits, use the tag as the version
499+ rendered = pieces["closest-tag"]
500+ else:
501+ # exception #1
502+ rendered = "0.post0.dev%d" % pieces["distance"]
503+ return rendered
504+
505+
506+def render_pep440_post(pieces):
507+ """TAG[.postDISTANCE[.dev0]+gHEX] .
508+
509+ The ".dev0" means dirty. Note that .dev0 sorts backwards
510+ (a dirty tree will appear "older" than the corresponding clean one),
511+ but you shouldn't be releasing software with -dirty anyways.
512+
513+ Exceptions:
514+ 1: no tags. 0.postDISTANCE[.dev0]
515+ """
516+ if pieces["closest-tag"]:
517+ rendered = pieces["closest-tag"]
518+ if pieces["distance"] or pieces["dirty"]:
519+ rendered += ".post%d" % pieces["distance"]
520+ if pieces["dirty"]:
521+ rendered += ".dev0"
522+ rendered += plus_or_dot(pieces)
523+ rendered += "g%s" % pieces["short"]
524+ else:
525+ # exception #1
526+ rendered = "0.post%d" % pieces["distance"]
527+ if pieces["dirty"]:
528+ rendered += ".dev0"
529+ rendered += "+g%s" % pieces["short"]
530+ return rendered
531+
532+
533+def render_pep440_post_branch(pieces):
534+ """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
535+
536+ The ".dev0" means not master branch.
537+
538+ Exceptions:
539+ 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
540+ """
541+ if pieces["closest-tag"]:
542+ rendered = pieces["closest-tag"]
543+ if pieces["distance"] or pieces["dirty"]:
544+ rendered += ".post%d" % pieces["distance"]
545+ if pieces["branch"] != "master":
546+ rendered += ".dev0"
547+ rendered += plus_or_dot(pieces)
548+ rendered += "g%s" % pieces["short"]
549+ if pieces["dirty"]:
550+ rendered += ".dirty"
551+ else:
552+ # exception #1
553+ rendered = "0.post%d" % pieces["distance"]
554+ if pieces["branch"] != "master":
555+ rendered += ".dev0"
556+ rendered += "+g%s" % pieces["short"]
557+ if pieces["dirty"]:
558+ rendered += ".dirty"
559+ return rendered
560+
561+
562+def render_pep440_old(pieces):
563+ """TAG[.postDISTANCE[.dev0]] .
564+
565+ The ".dev0" means dirty.
566+
567+ Exceptions:
568+ 1: no tags. 0.postDISTANCE[.dev0]
569+ """
570+ if pieces["closest-tag"]:
571+ rendered = pieces["closest-tag"]
572+ if pieces["distance"] or pieces["dirty"]:
573+ rendered += ".post%d" % pieces["distance"]
574+ if pieces["dirty"]:
575+ rendered += ".dev0"
576+ else:
577+ # exception #1
578+ rendered = "0.post%d" % pieces["distance"]
579+ if pieces["dirty"]:
580+ rendered += ".dev0"
581+ return rendered
582+
583+
584+def render_git_describe(pieces):
585+ """TAG[-DISTANCE-gHEX][-dirty].
586+
587+ Like 'git describe --tags --dirty --always'.
588+
589+ Exceptions:
590+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
591+ """
592+ if pieces["closest-tag"]:
593+ rendered = pieces["closest-tag"]
594+ if pieces["distance"]:
595+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
596+ else:
597+ # exception #1
598+ rendered = pieces["short"]
599+ if pieces["dirty"]:
600+ rendered += "-dirty"
601+ return rendered
602+
603+
604+def render_git_describe_long(pieces):
605+ """TAG-DISTANCE-gHEX[-dirty].
606+
607+ Like 'git describe --tags --dirty --always -long'.
608+ The distance/hash is unconditional.
609+
610+ Exceptions:
611+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
612+ """
613+ if pieces["closest-tag"]:
614+ rendered = pieces["closest-tag"]
615+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
616+ else:
617+ # exception #1
618+ rendered = pieces["short"]
619+ if pieces["dirty"]:
620+ rendered += "-dirty"
621+ return rendered
622+
623+
624+def render(pieces, style):
625+ """Render the given version pieces into the requested style."""
626+ if pieces["error"]:
627+ return {"version": "unknown",
628+ "full-revisionid": pieces.get("long"),
629+ "dirty": None,
630+ "error": pieces["error"],
631+ "date": None}
632+
633+ if not style or style == "default":
634+ style = "pep440" # the default
635+
636+ if style == "pep440":
637+ rendered = render_pep440(pieces)
638+ elif style == "pep440-branch":
639+ rendered = render_pep440_branch(pieces)
640+ elif style == "pep440-pre":
641+ rendered = render_pep440_pre(pieces)
642+ elif style == "pep440-post":
643+ rendered = render_pep440_post(pieces)
644+ elif style == "pep440-post-branch":
645+ rendered = render_pep440_post_branch(pieces)
646+ elif style == "pep440-old":
647+ rendered = render_pep440_old(pieces)
648+ elif style == "git-describe":
649+ rendered = render_git_describe(pieces)
650+ elif style == "git-describe-long":
651+ rendered = render_git_describe_long(pieces)
652+ else:
653+ raise ValueError("unknown style '%s'" % style)
654+
655+ return {"version": rendered, "full-revisionid": pieces["long"],
656+ "dirty": pieces["dirty"], "error": None,
657+ "date": pieces.get("date")}
658
659
660 def get_versions():
661- return json.loads(version_json)
662+ """Get version information or return default if unable to do so."""
663+ # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
664+ # __file__, we can work backwards from there to the root. Some
665+ # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
666+ # case we can only use expanded keywords.
667+
668+ cfg = get_config()
669+ verbose = cfg.verbose
670+
671+ try:
672+ return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
673+ verbose)
674+ except NotThisMethod:
675+ pass
676+
677+ try:
678+ root = os.path.realpath(__file__)
679+ # versionfile_source is the relative path from the top of the source
680+ # tree (where the .git directory might live) to this file. Invert
681+ # this to find the root from __file__.
682+ for _ in cfg.versionfile_source.split('/'):
683+ root = os.path.dirname(root)
684+ except NameError:
685+ return {"version": "0+unknown", "full-revisionid": None,
686+ "dirty": None,
687+ "error": "unable to find root of source tree",
688+ "date": None}
689+
690+ try:
691+ pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
692+ return render(pieces, cfg.style)
693+ except NotThisMethod:
694+ pass
695+
696+ try:
697+ if cfg.parentdir_prefix:
698+ return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
699+ except NotThisMethod:
700+ pass
701+
702+ return {"version": "0+unknown", "full-revisionid": None,
703+ "dirty": None,
704+ "error": "unable to compute version", "date": None}
705diff --git a/versioneer.py b/versioneer.py
706index 7ed2a21..a142bf5 100644
707--- a/versioneer.py
708+++ b/versioneer.py
709@@ -1,5 +1,5 @@
710
711-# Version: 0.16
712+# Version: 0.22
713
714 """The Versioneer - like a rocketeer, but for versions.
715
716@@ -7,18 +7,14 @@ The Versioneer
717 ==============
718
719 * like a rocketeer, but for versions!
720-* https://github.com/warner/python-versioneer
721+* https://github.com/python-versioneer/python-versioneer
722 * Brian Warner
723 * License: Public Domain
724-* Compatible With: python2.6, 2.7, 3.3, 3.4, 3.5, and pypy
725-* [![Latest Version]
726-(https://pypip.in/version/versioneer/badge.svg?style=flat)
727-](https://pypi.python.org/pypi/versioneer/)
728-* [![Build Status]
729-(https://travis-ci.org/warner/python-versioneer.png?branch=master)
730-](https://travis-ci.org/warner/python-versioneer)
731-
732-This is a tool for managing a recorded version number in distutils-based
733+* Compatible with: Python 3.6, 3.7, 3.8, 3.9, 3.10 and pypy3
734+* [![Latest Version][pypi-image]][pypi-url]
735+* [![Build Status][travis-image]][travis-url]
736+
737+This is a tool for managing a recorded version number in distutils/setuptools-based
738 python projects. The goal is to remove the tedious and error-prone "update
739 the embedded version string" step from your release process. Making a new
740 release should be as easy as recording a new tag in your version-control
741@@ -27,9 +23,10 @@ system, and maybe making new tarballs.
742
743 ## Quick Install
744
745-* `pip install versioneer` to somewhere to your $PATH
746-* add a `[versioneer]` section to your setup.cfg (see below)
747+* `pip install versioneer` to somewhere in your $PATH
748+* add a `[versioneer]` section to your setup.cfg (see [Install](INSTALL.md))
749 * run `versioneer install` in your source tree, commit the results
750+* Verify version information with `python setup.py version`
751
752 ## Version Identifiers
753
754@@ -61,7 +58,7 @@ version 1.3). Many VCS systems can report a description that captures this,
755 for example `git describe --tags --dirty --always` reports things like
756 "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
757 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
758-uncommitted changes.
759+uncommitted changes).
760
761 The version identifier is used for multiple purposes:
762
763@@ -88,127 +85,7 @@ the generated version data.
764
765 ## Installation
766
767-First, decide on values for the following configuration variables:
768-
769-* `VCS`: the version control system you use. Currently accepts "git".
770-
771-* `style`: the style of version string to be produced. See "Styles" below for
772- details. Defaults to "pep440", which looks like
773- `TAG[+DISTANCE.gSHORTHASH[.dirty]]`.
774-
775-* `versionfile_source`:
776-
777- A project-relative pathname into which the generated version strings should
778- be written. This is usually a `_version.py` next to your project's main
779- `__init__.py` file, so it can be imported at runtime. If your project uses
780- `src/myproject/__init__.py`, this should be `src/myproject/_version.py`.
781- This file should be checked in to your VCS as usual: the copy created below
782- by `setup.py setup_versioneer` will include code that parses expanded VCS
783- keywords in generated tarballs. The 'build' and 'sdist' commands will
784- replace it with a copy that has just the calculated version string.
785-
786- This must be set even if your project does not have any modules (and will
787- therefore never import `_version.py`), since "setup.py sdist" -based trees
788- still need somewhere to record the pre-calculated version strings. Anywhere
789- in the source tree should do. If there is a `__init__.py` next to your
790- `_version.py`, the `setup.py setup_versioneer` command (described below)
791- will append some `__version__`-setting assignments, if they aren't already
792- present.
793-
794-* `versionfile_build`:
795-
796- Like `versionfile_source`, but relative to the build directory instead of
797- the source directory. These will differ when your setup.py uses
798- 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`,
799- then you will probably have `versionfile_build='myproject/_version.py'` and
800- `versionfile_source='src/myproject/_version.py'`.
801-
802- If this is set to None, then `setup.py build` will not attempt to rewrite
803- any `_version.py` in the built tree. If your project does not have any
804- libraries (e.g. if it only builds a script), then you should use
805- `versionfile_build = None`. To actually use the computed version string,
806- your `setup.py` will need to override `distutils.command.build_scripts`
807- with a subclass that explicitly inserts a copy of
808- `versioneer.get_version()` into your script file. See
809- `test/demoapp-script-only/setup.py` for an example.
810-
811-* `tag_prefix`:
812-
813- a string, like 'PROJECTNAME-', which appears at the start of all VCS tags.
814- If your tags look like 'myproject-1.2.0', then you should use
815- tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this
816- should be an empty string, using either `tag_prefix=` or `tag_prefix=''`.
817-
818-* `parentdir_prefix`:
819-
820- a optional string, frequently the same as tag_prefix, which appears at the
821- start of all unpacked tarball filenames. If your tarball unpacks into
822- 'myproject-1.2.0', this should be 'myproject-'. To disable this feature,
823- just omit the field from your `setup.cfg`.
824-
825-This tool provides one script, named `versioneer`. That script has one mode,
826-"install", which writes a copy of `versioneer.py` into the current directory
827-and runs `versioneer.py setup` to finish the installation.
828-
829-To versioneer-enable your project:
830-
831-* 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and
832- populating it with the configuration values you decided earlier (note that
833- the option names are not case-sensitive):
834-
835- ````
836- [versioneer]
837- VCS = git
838- style = pep440
839- versionfile_source = src/myproject/_version.py
840- versionfile_build = myproject/_version.py
841- tag_prefix =
842- parentdir_prefix = myproject-
843- ````
844-
845-* 2: Run `versioneer install`. This will do the following:
846-
847- * copy `versioneer.py` into the top of your source tree
848- * create `_version.py` in the right place (`versionfile_source`)
849- * modify your `__init__.py` (if one exists next to `_version.py`) to define
850- `__version__` (by calling a function from `_version.py`)
851- * modify your `MANIFEST.in` to include both `versioneer.py` and the
852- generated `_version.py` in sdist tarballs
853-
854- `versioneer install` will complain about any problems it finds with your
855- `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all
856- the problems.
857-
858-* 3: add a `import versioneer` to your setup.py, and add the following
859- arguments to the setup() call:
860-
861- version=versioneer.get_version(),
862- cmdclass=versioneer.get_cmdclass(),
863-
864-* 4: commit these changes to your VCS. To make sure you won't forget,
865- `versioneer install` will mark everything it touched for addition using
866- `git add`. Don't forget to add `setup.py` and `setup.cfg` too.
867-
868-## Post-Installation Usage
869-
870-Once established, all uses of your tree from a VCS checkout should get the
871-current version string. All generated tarballs should include an embedded
872-version string (so users who unpack them will not need a VCS tool installed).
873-
874-If you distribute your project through PyPI, then the release process should
875-boil down to two steps:
876-
877-* 1: git tag 1.0
878-* 2: python setup.py register sdist upload
879-
880-If you distribute it through github (i.e. users use github to generate
881-tarballs with `git archive`), the process is:
882-
883-* 1: git tag 1.0
884-* 2: git push; git push --tags
885-
886-Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at
887-least one tag in its history.
888+See [INSTALL.md](./INSTALL.md) for detailed installation instructions.
889
890 ## Version-String Flavors
891
892@@ -229,6 +106,10 @@ information:
893 * `['full-revisionid']`: detailed revision identifier. For Git, this is the
894 full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".
895
896+* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
897+ commit date in ISO 8601 format. This will be None if the date is not
898+ available.
899+
900 * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
901 this is only accurate if run in a VCS checkout, otherwise it is likely to
902 be False or None
903@@ -267,8 +148,8 @@ that this commit is two revisions ("+2") beyond the "0.11" tag. For released
904 software (exactly equal to a known tag), the identifier will only contain the
905 stripped tag, e.g. "0.11".
906
907-Other styles are available. See details.md in the Versioneer source tree for
908-descriptions.
909+Other styles are available. See [details.md](details.md) in the Versioneer
910+source tree for descriptions.
911
912 ## Debugging
913
914@@ -278,51 +159,83 @@ version`, which will run the version-lookup code in a verbose mode, and will
915 display the full contents of `get_versions()` (including the `error` string,
916 which may help identify what went wrong).
917
918-## Updating Versioneer
919+## Known Limitations
920
921-To upgrade your project to a new release of Versioneer, do the following:
922+Some situations are known to cause problems for Versioneer. This details the
923+most significant ones. More can be found on Github
924+[issues page](https://github.com/python-versioneer/python-versioneer/issues).
925
926-* install the new Versioneer (`pip install -U versioneer` or equivalent)
927-* edit `setup.cfg`, if necessary, to include any new configuration settings
928- indicated by the release notes
929-* re-run `versioneer install` in your source tree, to replace
930- `SRC/_version.py`
931-* commit any changed files
932+### Subprojects
933+
934+Versioneer has limited support for source trees in which `setup.py` is not in
935+the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
936+two common reasons why `setup.py` might not be in the root:
937+
938+* Source trees which contain multiple subprojects, such as
939+ [Buildbot](https://github.com/buildbot/buildbot), which contains both
940+ "master" and "slave" subprojects, each with their own `setup.py`,
941+ `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
942+ distributions (and upload multiple independently-installable tarballs).
943+* Source trees whose main purpose is to contain a C library, but which also
944+ provide bindings to Python (and perhaps other languages) in subdirectories.
945+
946+Versioneer will look for `.git` in parent directories, and most operations
947+should get the right version string. However `pip` and `setuptools` have bugs
948+and implementation details which frequently cause `pip install .` from a
949+subproject directory to fail to find a correct version string (so it usually
950+defaults to `0+unknown`).
951
952-### Upgrading to 0.16
953+`pip install --editable .` should work correctly. `setup.py install` might
954+work too.
955
956-Nothing special.
957+Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
958+some later version.
959
960-### Upgrading to 0.15
961+[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
962+this issue. The discussion in
963+[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
964+issue from the Versioneer side in more detail.
965+[pip PR#3176](https://github.com/pypa/pip/pull/3176) and
966+[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
967+pip to let Versioneer work correctly.
968
969-Starting with this version, Versioneer is configured with a `[versioneer]`
970-section in your `setup.cfg` file. Earlier versions required the `setup.py` to
971-set attributes on the `versioneer` module immediately after import. The new
972-version will refuse to run (raising an exception during import) until you
973-have provided the necessary `setup.cfg` section.
974+Versioneer-0.16 and earlier only looked for a `.git` directory next to the
975+`setup.cfg`, so subprojects were completely unsupported with those releases.
976
977-In addition, the Versioneer package provides an executable named
978-`versioneer`, and the installation process is driven by running `versioneer
979-install`. In 0.14 and earlier, the executable was named
980-`versioneer-installer` and was run without an argument.
981+### Editable installs with setuptools <= 18.5
982
983-### Upgrading to 0.14
984+`setup.py develop` and `pip install --editable .` allow you to install a
985+project into a virtualenv once, then continue editing the source code (and
986+test) without re-installing after every change.
987
988-0.14 changes the format of the version string. 0.13 and earlier used
989-hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a
990-plus-separated "local version" section strings, with dot-separated
991-components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old
992-format, but should be ok with the new one.
993+"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
994+convenient way to specify executable scripts that should be installed along
995+with the python package.
996
997-### Upgrading from 0.11 to 0.12
998+These both work as expected when using modern setuptools. When using
999+setuptools-18.5 or earlier, however, certain operations will cause
1000+`pkg_resources.DistributionNotFound` errors when running the entrypoint
1001+script, which must be resolved by re-installing the package. This happens
1002+when the install happens with one version, then the egg_info data is
1003+regenerated while a different version is checked out. Many setup.py commands
1004+cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
1005+a different virtualenv), so this can be surprising.
1006
1007-Nothing special.
1008+[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
1009+this one, but upgrading to a newer version of setuptools should probably
1010+resolve it.
1011
1012-### Upgrading from 0.10 to 0.11
1013
1014-You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running
1015-`setup.py setup_versioneer`. This will enable the use of additional
1016-version-control systems (SVN, etc) in the future.
1017+## Updating Versioneer
1018+
1019+To upgrade your project to a new release of Versioneer, do the following:
1020+
1021+* install the new Versioneer (`pip install -U versioneer` or equivalent)
1022+* edit `setup.cfg`, if necessary, to include any new configuration settings
1023+ indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
1024+* re-run `versioneer install` in your source tree, to replace
1025+ `SRC/_version.py`
1026+* commit any changed files
1027
1028 ## Future Directions
1029
1030@@ -337,6 +250,14 @@ installation by editing setup.py . Alternatively, it might go the other
1031 direction and include code from all supported VCS systems, reducing the
1032 number of intermediate scripts.
1033
1034+## Similar projects
1035+
1036+* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
1037+ dependency
1038+* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
1039+ versioneer
1040+* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
1041+ plugin
1042
1043 ## License
1044
1045@@ -346,19 +267,28 @@ Specifically, both are released under the Creative Commons "Public Domain
1046 Dedication" license (CC0-1.0), as described in
1047 https://creativecommons.org/publicdomain/zero/1.0/ .
1048
1049+[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
1050+[pypi-url]: https://pypi.python.org/pypi/versioneer/
1051+[travis-image]:
1052+https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
1053+[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
1054+
1055 """
1056+# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
1057+# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
1058+# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
1059+# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
1060+# pylint:disable=attribute-defined-outside-init,too-many-arguments
1061
1062-from __future__ import print_function
1063-try:
1064- import configparser
1065-except ImportError:
1066- import ConfigParser as configparser
1067+import configparser
1068 import errno
1069 import json
1070 import os
1071 import re
1072 import subprocess
1073 import sys
1074+from typing import Callable, Dict
1075+import functools
1076
1077
1078 class VersioneerConfig:
1079@@ -393,10 +323,12 @@ def get_root():
1080 # module-import table will cache the first one. So we can't use
1081 # os.path.dirname(__file__), as that will find whichever
1082 # versioneer.py was first imported, even in later projects.
1083- me = os.path.realpath(os.path.abspath(__file__))
1084- if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]:
1085+ my_path = os.path.realpath(os.path.abspath(__file__))
1086+ me_dir = os.path.normcase(os.path.splitext(my_path)[0])
1087+ vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
1088+ if me_dir != vsr_dir:
1089 print("Warning: build in %s is using versioneer.py from %s"
1090- % (os.path.dirname(me), versioneer_py))
1091+ % (os.path.dirname(my_path), versioneer_py))
1092 except NameError:
1093 pass
1094 return root
1095@@ -404,85 +336,94 @@ def get_root():
1096
1097 def get_config_from_root(root):
1098 """Read the project setup.cfg file to determine Versioneer config."""
1099- # This might raise EnvironmentError (if setup.cfg is missing), or
1100+ # This might raise OSError (if setup.cfg is missing), or
1101 # configparser.NoSectionError (if it lacks a [versioneer] section), or
1102 # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
1103 # the top of versioneer.py for instructions on writing your setup.cfg .
1104 setup_cfg = os.path.join(root, "setup.cfg")
1105- parser = configparser.SafeConfigParser()
1106- with open(setup_cfg, "r") as f:
1107- parser.readfp(f)
1108+ parser = configparser.ConfigParser()
1109+ with open(setup_cfg, "r") as cfg_file:
1110+ parser.read_file(cfg_file)
1111 VCS = parser.get("versioneer", "VCS") # mandatory
1112
1113- def get(parser, name):
1114- if parser.has_option("versioneer", name):
1115- return parser.get("versioneer", name)
1116- return None
1117+ # Dict-like interface for non-mandatory entries
1118+ section = parser["versioneer"]
1119+
1120 cfg = VersioneerConfig()
1121 cfg.VCS = VCS
1122- cfg.style = get(parser, "style") or ""
1123- cfg.versionfile_source = get(parser, "versionfile_source")
1124- cfg.versionfile_build = get(parser, "versionfile_build")
1125- cfg.tag_prefix = get(parser, "tag_prefix")
1126+ cfg.style = section.get("style", "")
1127+ cfg.versionfile_source = section.get("versionfile_source")
1128+ cfg.versionfile_build = section.get("versionfile_build")
1129+ cfg.tag_prefix = section.get("tag_prefix")
1130 if cfg.tag_prefix in ("''", '""'):
1131 cfg.tag_prefix = ""
1132- cfg.parentdir_prefix = get(parser, "parentdir_prefix")
1133- cfg.verbose = get(parser, "verbose")
1134+ cfg.parentdir_prefix = section.get("parentdir_prefix")
1135+ cfg.verbose = section.get("verbose")
1136 return cfg
1137
1138
1139 class NotThisMethod(Exception):
1140 """Exception raised if a method is not valid for the current scenario."""
1141
1142+
1143 # these dictionaries contain VCS-specific tools
1144-LONG_VERSION_PY = {}
1145-HANDLERS = {}
1146+LONG_VERSION_PY: Dict[str, str] = {}
1147+HANDLERS: Dict[str, Dict[str, Callable]] = {}
1148
1149
1150 def register_vcs_handler(vcs, method): # decorator
1151- """Decorator to mark a method as the handler for a particular VCS."""
1152+ """Create decorator to mark a method as the handler of a VCS."""
1153 def decorate(f):
1154 """Store f in HANDLERS[vcs][method]."""
1155- if vcs not in HANDLERS:
1156- HANDLERS[vcs] = {}
1157- HANDLERS[vcs][method] = f
1158+ HANDLERS.setdefault(vcs, {})[method] = f
1159 return f
1160 return decorate
1161
1162
1163-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
1164+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
1165+ env=None):
1166 """Call the given command(s)."""
1167 assert isinstance(commands, list)
1168- p = None
1169- for c in commands:
1170+ process = None
1171+
1172+ popen_kwargs = {}
1173+ if sys.platform == "win32":
1174+ # This hides the console window if pythonw.exe is used
1175+ startupinfo = subprocess.STARTUPINFO()
1176+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
1177+ popen_kwargs["startupinfo"] = startupinfo
1178+
1179+ for command in commands:
1180 try:
1181- dispcmd = str([c] + args)
1182+ dispcmd = str([command] + args)
1183 # remember shell=False, so use git.cmd on windows, not just git
1184- p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
1185- stderr=(subprocess.PIPE if hide_stderr
1186- else None))
1187+ process = subprocess.Popen([command] + args, cwd=cwd, env=env,
1188+ stdout=subprocess.PIPE,
1189+ stderr=(subprocess.PIPE if hide_stderr
1190+ else None), **popen_kwargs)
1191 break
1192- except EnvironmentError:
1193+ except OSError:
1194 e = sys.exc_info()[1]
1195 if e.errno == errno.ENOENT:
1196 continue
1197 if verbose:
1198 print("unable to run %s" % dispcmd)
1199 print(e)
1200- return None
1201+ return None, None
1202 else:
1203 if verbose:
1204 print("unable to find command, tried %s" % (commands,))
1205- return None
1206- stdout = p.communicate()[0].strip()
1207- if sys.version_info[0] >= 3:
1208- stdout = stdout.decode()
1209- if p.returncode != 0:
1210+ return None, None
1211+ stdout = process.communicate()[0].strip().decode()
1212+ if process.returncode != 0:
1213 if verbose:
1214 print("unable to run %s (error)" % dispcmd)
1215- return None
1216- return stdout
1217-LONG_VERSION_PY['git'] = '''
1218+ print("stdout was %s" % stdout)
1219+ return None, process.returncode
1220+ return stdout, process.returncode
1221+
1222+
1223+LONG_VERSION_PY['git'] = r'''
1224 # This file helps to compute a version number in source trees obtained from
1225 # git-archive tarball (such as those provided by githubs download-from-tag
1226 # feature). Distribution tarballs (built by setup.py sdist) and build
1227@@ -490,7 +431,7 @@ LONG_VERSION_PY['git'] = '''
1228 # that just contains the computed version number.
1229
1230 # This file is released into the public domain. Generated by
1231-# versioneer-0.16 (https://github.com/warner/python-versioneer)
1232+# versioneer-0.22 (https://github.com/python-versioneer/python-versioneer)
1233
1234 """Git implementation of _version.py."""
1235
1236@@ -499,6 +440,8 @@ import os
1237 import re
1238 import subprocess
1239 import sys
1240+from typing import Callable, Dict
1241+import functools
1242
1243
1244 def get_keywords():
1245@@ -509,7 +452,8 @@ def get_keywords():
1246 # get_keywords().
1247 git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
1248 git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
1249- keywords = {"refnames": git_refnames, "full": git_full}
1250+ git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
1251+ keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
1252 return keywords
1253
1254
1255@@ -535,12 +479,12 @@ class NotThisMethod(Exception):
1256 """Exception raised if a method is not valid for the current scenario."""
1257
1258
1259-LONG_VERSION_PY = {}
1260-HANDLERS = {}
1261+LONG_VERSION_PY: Dict[str, str] = {}
1262+HANDLERS: Dict[str, Dict[str, Callable]] = {}
1263
1264
1265 def register_vcs_handler(vcs, method): # decorator
1266- """Decorator to mark a method as the handler for a particular VCS."""
1267+ """Create decorator to mark a method as the handler of a VCS."""
1268 def decorate(f):
1269 """Store f in HANDLERS[vcs][method]."""
1270 if vcs not in HANDLERS:
1271@@ -550,55 +494,71 @@ def register_vcs_handler(vcs, method): # decorator
1272 return decorate
1273
1274
1275-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
1276+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
1277+ env=None):
1278 """Call the given command(s)."""
1279 assert isinstance(commands, list)
1280- p = None
1281- for c in commands:
1282+ process = None
1283+
1284+ popen_kwargs = {}
1285+ if sys.platform == "win32":
1286+ # This hides the console window if pythonw.exe is used
1287+ startupinfo = subprocess.STARTUPINFO()
1288+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
1289+ popen_kwargs["startupinfo"] = startupinfo
1290+
1291+ for command in commands:
1292 try:
1293- dispcmd = str([c] + args)
1294+ dispcmd = str([command] + args)
1295 # remember shell=False, so use git.cmd on windows, not just git
1296- p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
1297- stderr=(subprocess.PIPE if hide_stderr
1298- else None))
1299+ process = subprocess.Popen([command] + args, cwd=cwd, env=env,
1300+ stdout=subprocess.PIPE,
1301+ stderr=(subprocess.PIPE if hide_stderr
1302+ else None), **popen_kwargs)
1303 break
1304- except EnvironmentError:
1305+ except OSError:
1306 e = sys.exc_info()[1]
1307 if e.errno == errno.ENOENT:
1308 continue
1309 if verbose:
1310 print("unable to run %%s" %% dispcmd)
1311 print(e)
1312- return None
1313+ return None, None
1314 else:
1315 if verbose:
1316 print("unable to find command, tried %%s" %% (commands,))
1317- return None
1318- stdout = p.communicate()[0].strip()
1319- if sys.version_info[0] >= 3:
1320- stdout = stdout.decode()
1321- if p.returncode != 0:
1322+ return None, None
1323+ stdout = process.communicate()[0].strip().decode()
1324+ if process.returncode != 0:
1325 if verbose:
1326 print("unable to run %%s (error)" %% dispcmd)
1327- return None
1328- return stdout
1329+ print("stdout was %%s" %% stdout)
1330+ return None, process.returncode
1331+ return stdout, process.returncode
1332
1333
1334 def versions_from_parentdir(parentdir_prefix, root, verbose):
1335 """Try to determine the version from the parent directory name.
1336
1337- Source tarballs conventionally unpack into a directory that includes
1338- both the project name and a version string.
1339+ Source tarballs conventionally unpack into a directory that includes both
1340+ the project name and a version string. We will also support searching up
1341+ two directory levels for an appropriately named parent directory
1342 """
1343- dirname = os.path.basename(root)
1344- if not dirname.startswith(parentdir_prefix):
1345- if verbose:
1346- print("guessing rootdir is '%%s', but '%%s' doesn't start with "
1347- "prefix '%%s'" %% (root, dirname, parentdir_prefix))
1348- raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
1349- return {"version": dirname[len(parentdir_prefix):],
1350- "full-revisionid": None,
1351- "dirty": False, "error": None}
1352+ rootdirs = []
1353+
1354+ for _ in range(3):
1355+ dirname = os.path.basename(root)
1356+ if dirname.startswith(parentdir_prefix):
1357+ return {"version": dirname[len(parentdir_prefix):],
1358+ "full-revisionid": None,
1359+ "dirty": False, "error": None, "date": None}
1360+ rootdirs.append(root)
1361+ root = os.path.dirname(root) # up a level
1362+
1363+ if verbose:
1364+ print("Tried directories %%s but none started with prefix %%s" %%
1365+ (str(rootdirs), parentdir_prefix))
1366+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
1367
1368
1369 @register_vcs_handler("git", "get_keywords")
1370@@ -610,18 +570,21 @@ def git_get_keywords(versionfile_abs):
1371 # _version.py.
1372 keywords = {}
1373 try:
1374- f = open(versionfile_abs, "r")
1375- for line in f.readlines():
1376- if line.strip().startswith("git_refnames ="):
1377- mo = re.search(r'=\s*"(.*)"', line)
1378- if mo:
1379- keywords["refnames"] = mo.group(1)
1380- if line.strip().startswith("git_full ="):
1381- mo = re.search(r'=\s*"(.*)"', line)
1382- if mo:
1383- keywords["full"] = mo.group(1)
1384- f.close()
1385- except EnvironmentError:
1386+ with open(versionfile_abs, "r") as fobj:
1387+ for line in fobj:
1388+ if line.strip().startswith("git_refnames ="):
1389+ mo = re.search(r'=\s*"(.*)"', line)
1390+ if mo:
1391+ keywords["refnames"] = mo.group(1)
1392+ if line.strip().startswith("git_full ="):
1393+ mo = re.search(r'=\s*"(.*)"', line)
1394+ if mo:
1395+ keywords["full"] = mo.group(1)
1396+ if line.strip().startswith("git_date ="):
1397+ mo = re.search(r'=\s*"(.*)"', line)
1398+ if mo:
1399+ keywords["date"] = mo.group(1)
1400+ except OSError:
1401 pass
1402 return keywords
1403
1404@@ -629,18 +592,31 @@ def git_get_keywords(versionfile_abs):
1405 @register_vcs_handler("git", "keywords")
1406 def git_versions_from_keywords(keywords, tag_prefix, verbose):
1407 """Get version information from git keywords."""
1408- if not keywords:
1409- raise NotThisMethod("no keywords at all, weird")
1410+ if "refnames" not in keywords:
1411+ raise NotThisMethod("Short version file found")
1412+ date = keywords.get("date")
1413+ if date is not None:
1414+ # Use only the last line. Previous lines may contain GPG signature
1415+ # information.
1416+ date = date.splitlines()[-1]
1417+
1418+ # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
1419+ # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
1420+ # -like" string, which we must then edit to make compliant), because
1421+ # it's been around since git-1.5.3, and it's too difficult to
1422+ # discover which version we're using, or to work around using an
1423+ # older one.
1424+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1425 refnames = keywords["refnames"].strip()
1426 if refnames.startswith("$Format"):
1427 if verbose:
1428 print("keywords are unexpanded, not using")
1429 raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
1430- refs = set([r.strip() for r in refnames.strip("()").split(",")])
1431+ refs = {r.strip() for r in refnames.strip("()").split(",")}
1432 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
1433 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
1434 TAG = "tag: "
1435- tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
1436+ tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
1437 if not tags:
1438 # Either we're using git < 1.8.3, or there really are no tags. We use
1439 # a heuristic: assume all version tags have a digit. The old git %%d
1440@@ -649,56 +625,72 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
1441 # between branches and tags. By ignoring refnames without digits, we
1442 # filter out many common branch names like "release" and
1443 # "stabilization", as well as "HEAD" and "master".
1444- tags = set([r for r in refs if re.search(r'\d', r)])
1445+ tags = {r for r in refs if re.search(r'\d', r)}
1446 if verbose:
1447- print("discarding '%%s', no digits" %% ",".join(refs-tags))
1448+ print("discarding '%%s', no digits" %% ",".join(refs - tags))
1449 if verbose:
1450 print("likely tags: %%s" %% ",".join(sorted(tags)))
1451 for ref in sorted(tags):
1452 # sorting will prefer e.g. "2.0" over "2.0rc1"
1453 if ref.startswith(tag_prefix):
1454 r = ref[len(tag_prefix):]
1455+ # Filter out refs that exactly match prefix or that don't start
1456+ # with a number once the prefix is stripped (mostly a concern
1457+ # when prefix is '')
1458+ if not re.match(r'\d', r):
1459+ continue
1460 if verbose:
1461 print("picking %%s" %% r)
1462 return {"version": r,
1463 "full-revisionid": keywords["full"].strip(),
1464- "dirty": False, "error": None
1465- }
1466+ "dirty": False, "error": None,
1467+ "date": date}
1468 # no suitable tags, so version is "0+unknown", but full hex is still there
1469 if verbose:
1470 print("no suitable tags, using unknown + full revision id")
1471 return {"version": "0+unknown",
1472 "full-revisionid": keywords["full"].strip(),
1473- "dirty": False, "error": "no suitable tags"}
1474+ "dirty": False, "error": "no suitable tags", "date": None}
1475
1476
1477 @register_vcs_handler("git", "pieces_from_vcs")
1478-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1479+def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
1480 """Get version from 'git describe' in the root of the source tree.
1481
1482 This only gets called if the git-archive 'subst' keywords were *not*
1483 expanded, and _version.py hasn't already been rewritten with a short
1484 version string, meaning we're inside a checked out source tree.
1485 """
1486- if not os.path.exists(os.path.join(root, ".git")):
1487- if verbose:
1488- print("no .git in %%s" %% root)
1489- raise NotThisMethod("no .git directory")
1490-
1491 GITS = ["git"]
1492 if sys.platform == "win32":
1493 GITS = ["git.cmd", "git.exe"]
1494+
1495+ # GIT_DIR can interfere with correct operation of Versioneer.
1496+ # It may be intended to be passed to the Versioneer-versioned project,
1497+ # but that should not change where we get our version from.
1498+ env = os.environ.copy()
1499+ env.pop("GIT_DIR", None)
1500+ runner = functools.partial(runner, env=env)
1501+
1502+ _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
1503+ hide_stderr=True)
1504+ if rc != 0:
1505+ if verbose:
1506+ print("Directory %%s not under git control" %% root)
1507+ raise NotThisMethod("'git rev-parse --git-dir' returned error")
1508+
1509+ MATCH_ARGS = ["--match", "%%s*" %% tag_prefix] if tag_prefix else []
1510+
1511 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
1512 # if there isn't one, this yields HEX[-dirty] (no NUM)
1513- describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
1514- "--always", "--long",
1515- "--match", "%%s*" %% tag_prefix],
1516- cwd=root)
1517+ describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty",
1518+ "--always", "--long", *MATCH_ARGS],
1519+ cwd=root)
1520 # --long was added in git-1.5.5
1521 if describe_out is None:
1522 raise NotThisMethod("'git describe' failed")
1523 describe_out = describe_out.strip()
1524- full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
1525+ full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
1526 if full_out is None:
1527 raise NotThisMethod("'git rev-parse' failed")
1528 full_out = full_out.strip()
1529@@ -708,6 +700,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1530 pieces["short"] = full_out[:7] # maybe improved later
1531 pieces["error"] = None
1532
1533+ branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
1534+ cwd=root)
1535+ # --abbrev-ref was added in git-1.6.3
1536+ if rc != 0 or branch_name is None:
1537+ raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
1538+ branch_name = branch_name.strip()
1539+
1540+ if branch_name == "HEAD":
1541+ # If we aren't exactly on a branch, pick a branch which represents
1542+ # the current commit. If all else fails, we are on a branchless
1543+ # commit.
1544+ branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
1545+ # --contains was added in git-1.5.4
1546+ if rc != 0 or branches is None:
1547+ raise NotThisMethod("'git branch --contains' returned error")
1548+ branches = branches.split("\n")
1549+
1550+ # Remove the first line if we're running detached
1551+ if "(" in branches[0]:
1552+ branches.pop(0)
1553+
1554+ # Strip off the leading "* " from the list of branches.
1555+ branches = [branch[2:] for branch in branches]
1556+ if "master" in branches:
1557+ branch_name = "master"
1558+ elif not branches:
1559+ branch_name = None
1560+ else:
1561+ # Pick the first branch that is returned. Good or bad.
1562+ branch_name = branches[0]
1563+
1564+ pieces["branch"] = branch_name
1565+
1566 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
1567 # TAG might have hyphens.
1568 git_describe = describe_out
1569@@ -724,7 +749,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1570 # TAG-NUM-gHEX
1571 mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
1572 if not mo:
1573- # unparseable. Maybe git-describe is misbehaving?
1574+ # unparsable. Maybe git-describe is misbehaving?
1575 pieces["error"] = ("unable to parse git-describe output: '%%s'"
1576 %% describe_out)
1577 return pieces
1578@@ -749,10 +774,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1579 else:
1580 # HEX: no tags
1581 pieces["closest-tag"] = None
1582- count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
1583- cwd=root)
1584+ count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
1585 pieces["distance"] = int(count_out) # total number of commits
1586
1587+ # commit date: see ISO-8601 comment in git_versions_from_keywords()
1588+ date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
1589+ # Use only the last line. Previous lines may contain GPG signature
1590+ # information.
1591+ date = date.splitlines()[-1]
1592+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1593+
1594 return pieces
1595
1596
1597@@ -788,19 +819,67 @@ def render_pep440(pieces):
1598 return rendered
1599
1600
1601-def render_pep440_pre(pieces):
1602- """TAG[.post.devDISTANCE] -- No -dirty.
1603+def render_pep440_branch(pieces):
1604+ """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
1605+
1606+ The ".dev0" means not master branch. Note that .dev0 sorts backwards
1607+ (a feature branch will appear "older" than the master branch).
1608
1609 Exceptions:
1610- 1: no tags. 0.post.devDISTANCE
1611+ 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
1612 """
1613 if pieces["closest-tag"]:
1614 rendered = pieces["closest-tag"]
1615+ if pieces["distance"] or pieces["dirty"]:
1616+ if pieces["branch"] != "master":
1617+ rendered += ".dev0"
1618+ rendered += plus_or_dot(pieces)
1619+ rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
1620+ if pieces["dirty"]:
1621+ rendered += ".dirty"
1622+ else:
1623+ # exception #1
1624+ rendered = "0"
1625+ if pieces["branch"] != "master":
1626+ rendered += ".dev0"
1627+ rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
1628+ pieces["short"])
1629+ if pieces["dirty"]:
1630+ rendered += ".dirty"
1631+ return rendered
1632+
1633+
1634+def pep440_split_post(ver):
1635+ """Split pep440 version string at the post-release segment.
1636+
1637+ Returns the release segments before the post-release and the
1638+ post-release version number (or -1 if no post-release segment is present).
1639+ """
1640+ vc = str.split(ver, ".post")
1641+ return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
1642+
1643+
1644+def render_pep440_pre(pieces):
1645+ """TAG[.postN.devDISTANCE] -- No -dirty.
1646+
1647+ Exceptions:
1648+ 1: no tags. 0.post0.devDISTANCE
1649+ """
1650+ if pieces["closest-tag"]:
1651 if pieces["distance"]:
1652- rendered += ".post.dev%%d" %% pieces["distance"]
1653+ # update the post release segment
1654+ tag_version, post_version = pep440_split_post(pieces["closest-tag"])
1655+ rendered = tag_version
1656+ if post_version is not None:
1657+ rendered += ".post%%d.dev%%d" %% (post_version+1, pieces["distance"])
1658+ else:
1659+ rendered += ".post0.dev%%d" %% (pieces["distance"])
1660+ else:
1661+ # no commits, use the tag as the version
1662+ rendered = pieces["closest-tag"]
1663 else:
1664 # exception #1
1665- rendered = "0.post.dev%%d" %% pieces["distance"]
1666+ rendered = "0.post0.dev%%d" %% pieces["distance"]
1667 return rendered
1668
1669
1670@@ -831,12 +910,41 @@ def render_pep440_post(pieces):
1671 return rendered
1672
1673
1674+def render_pep440_post_branch(pieces):
1675+ """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
1676+
1677+ The ".dev0" means not master branch.
1678+
1679+ Exceptions:
1680+ 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
1681+ """
1682+ if pieces["closest-tag"]:
1683+ rendered = pieces["closest-tag"]
1684+ if pieces["distance"] or pieces["dirty"]:
1685+ rendered += ".post%%d" %% pieces["distance"]
1686+ if pieces["branch"] != "master":
1687+ rendered += ".dev0"
1688+ rendered += plus_or_dot(pieces)
1689+ rendered += "g%%s" %% pieces["short"]
1690+ if pieces["dirty"]:
1691+ rendered += ".dirty"
1692+ else:
1693+ # exception #1
1694+ rendered = "0.post%%d" %% pieces["distance"]
1695+ if pieces["branch"] != "master":
1696+ rendered += ".dev0"
1697+ rendered += "+g%%s" %% pieces["short"]
1698+ if pieces["dirty"]:
1699+ rendered += ".dirty"
1700+ return rendered
1701+
1702+
1703 def render_pep440_old(pieces):
1704 """TAG[.postDISTANCE[.dev0]] .
1705
1706 The ".dev0" means dirty.
1707
1708- Eexceptions:
1709+ Exceptions:
1710 1: no tags. 0.postDISTANCE[.dev0]
1711 """
1712 if pieces["closest-tag"]:
1713@@ -899,17 +1007,22 @@ def render(pieces, style):
1714 return {"version": "unknown",
1715 "full-revisionid": pieces.get("long"),
1716 "dirty": None,
1717- "error": pieces["error"]}
1718+ "error": pieces["error"],
1719+ "date": None}
1720
1721 if not style or style == "default":
1722 style = "pep440" # the default
1723
1724 if style == "pep440":
1725 rendered = render_pep440(pieces)
1726+ elif style == "pep440-branch":
1727+ rendered = render_pep440_branch(pieces)
1728 elif style == "pep440-pre":
1729 rendered = render_pep440_pre(pieces)
1730 elif style == "pep440-post":
1731 rendered = render_pep440_post(pieces)
1732+ elif style == "pep440-post-branch":
1733+ rendered = render_pep440_post_branch(pieces)
1734 elif style == "pep440-old":
1735 rendered = render_pep440_old(pieces)
1736 elif style == "git-describe":
1737@@ -920,7 +1033,8 @@ def render(pieces, style):
1738 raise ValueError("unknown style '%%s'" %% style)
1739
1740 return {"version": rendered, "full-revisionid": pieces["long"],
1741- "dirty": pieces["dirty"], "error": None}
1742+ "dirty": pieces["dirty"], "error": None,
1743+ "date": pieces.get("date")}
1744
1745
1746 def get_versions():
1747@@ -944,12 +1058,13 @@ def get_versions():
1748 # versionfile_source is the relative path from the top of the source
1749 # tree (where the .git directory might live) to this file. Invert
1750 # this to find the root from __file__.
1751- for i in cfg.versionfile_source.split('/'):
1752+ for _ in cfg.versionfile_source.split('/'):
1753 root = os.path.dirname(root)
1754 except NameError:
1755 return {"version": "0+unknown", "full-revisionid": None,
1756 "dirty": None,
1757- "error": "unable to find root of source tree"}
1758+ "error": "unable to find root of source tree",
1759+ "date": None}
1760
1761 try:
1762 pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
1763@@ -965,7 +1080,7 @@ def get_versions():
1764
1765 return {"version": "0+unknown", "full-revisionid": None,
1766 "dirty": None,
1767- "error": "unable to compute version"}
1768+ "error": "unable to compute version", "date": None}
1769 '''
1770
1771
1772@@ -978,18 +1093,21 @@ def git_get_keywords(versionfile_abs):
1773 # _version.py.
1774 keywords = {}
1775 try:
1776- f = open(versionfile_abs, "r")
1777- for line in f.readlines():
1778- if line.strip().startswith("git_refnames ="):
1779- mo = re.search(r'=\s*"(.*)"', line)
1780- if mo:
1781- keywords["refnames"] = mo.group(1)
1782- if line.strip().startswith("git_full ="):
1783- mo = re.search(r'=\s*"(.*)"', line)
1784- if mo:
1785- keywords["full"] = mo.group(1)
1786- f.close()
1787- except EnvironmentError:
1788+ with open(versionfile_abs, "r") as fobj:
1789+ for line in fobj:
1790+ if line.strip().startswith("git_refnames ="):
1791+ mo = re.search(r'=\s*"(.*)"', line)
1792+ if mo:
1793+ keywords["refnames"] = mo.group(1)
1794+ if line.strip().startswith("git_full ="):
1795+ mo = re.search(r'=\s*"(.*)"', line)
1796+ if mo:
1797+ keywords["full"] = mo.group(1)
1798+ if line.strip().startswith("git_date ="):
1799+ mo = re.search(r'=\s*"(.*)"', line)
1800+ if mo:
1801+ keywords["date"] = mo.group(1)
1802+ except OSError:
1803 pass
1804 return keywords
1805
1806@@ -997,18 +1115,31 @@ def git_get_keywords(versionfile_abs):
1807 @register_vcs_handler("git", "keywords")
1808 def git_versions_from_keywords(keywords, tag_prefix, verbose):
1809 """Get version information from git keywords."""
1810- if not keywords:
1811- raise NotThisMethod("no keywords at all, weird")
1812+ if "refnames" not in keywords:
1813+ raise NotThisMethod("Short version file found")
1814+ date = keywords.get("date")
1815+ if date is not None:
1816+ # Use only the last line. Previous lines may contain GPG signature
1817+ # information.
1818+ date = date.splitlines()[-1]
1819+
1820+ # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
1821+ # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
1822+ # -like" string, which we must then edit to make compliant), because
1823+ # it's been around since git-1.5.3, and it's too difficult to
1824+ # discover which version we're using, or to work around using an
1825+ # older one.
1826+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1827 refnames = keywords["refnames"].strip()
1828 if refnames.startswith("$Format"):
1829 if verbose:
1830 print("keywords are unexpanded, not using")
1831 raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
1832- refs = set([r.strip() for r in refnames.strip("()").split(",")])
1833+ refs = {r.strip() for r in refnames.strip("()").split(",")}
1834 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
1835 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
1836 TAG = "tag: "
1837- tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
1838+ tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
1839 if not tags:
1840 # Either we're using git < 1.8.3, or there really are no tags. We use
1841 # a heuristic: assume all version tags have a digit. The old git %d
1842@@ -1017,56 +1148,72 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose):
1843 # between branches and tags. By ignoring refnames without digits, we
1844 # filter out many common branch names like "release" and
1845 # "stabilization", as well as "HEAD" and "master".
1846- tags = set([r for r in refs if re.search(r'\d', r)])
1847+ tags = {r for r in refs if re.search(r'\d', r)}
1848 if verbose:
1849- print("discarding '%s', no digits" % ",".join(refs-tags))
1850+ print("discarding '%s', no digits" % ",".join(refs - tags))
1851 if verbose:
1852 print("likely tags: %s" % ",".join(sorted(tags)))
1853 for ref in sorted(tags):
1854 # sorting will prefer e.g. "2.0" over "2.0rc1"
1855 if ref.startswith(tag_prefix):
1856 r = ref[len(tag_prefix):]
1857+ # Filter out refs that exactly match prefix or that don't start
1858+ # with a number once the prefix is stripped (mostly a concern
1859+ # when prefix is '')
1860+ if not re.match(r'\d', r):
1861+ continue
1862 if verbose:
1863 print("picking %s" % r)
1864 return {"version": r,
1865 "full-revisionid": keywords["full"].strip(),
1866- "dirty": False, "error": None
1867- }
1868+ "dirty": False, "error": None,
1869+ "date": date}
1870 # no suitable tags, so version is "0+unknown", but full hex is still there
1871 if verbose:
1872 print("no suitable tags, using unknown + full revision id")
1873 return {"version": "0+unknown",
1874 "full-revisionid": keywords["full"].strip(),
1875- "dirty": False, "error": "no suitable tags"}
1876+ "dirty": False, "error": "no suitable tags", "date": None}
1877
1878
1879 @register_vcs_handler("git", "pieces_from_vcs")
1880-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1881+def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
1882 """Get version from 'git describe' in the root of the source tree.
1883
1884 This only gets called if the git-archive 'subst' keywords were *not*
1885 expanded, and _version.py hasn't already been rewritten with a short
1886 version string, meaning we're inside a checked out source tree.
1887 """
1888- if not os.path.exists(os.path.join(root, ".git")):
1889- if verbose:
1890- print("no .git in %s" % root)
1891- raise NotThisMethod("no .git directory")
1892-
1893 GITS = ["git"]
1894 if sys.platform == "win32":
1895 GITS = ["git.cmd", "git.exe"]
1896+
1897+ # GIT_DIR can interfere with correct operation of Versioneer.
1898+ # It may be intended to be passed to the Versioneer-versioned project,
1899+ # but that should not change where we get our version from.
1900+ env = os.environ.copy()
1901+ env.pop("GIT_DIR", None)
1902+ runner = functools.partial(runner, env=env)
1903+
1904+ _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
1905+ hide_stderr=True)
1906+ if rc != 0:
1907+ if verbose:
1908+ print("Directory %s not under git control" % root)
1909+ raise NotThisMethod("'git rev-parse --git-dir' returned error")
1910+
1911+ MATCH_ARGS = ["--match", "%s*" % tag_prefix] if tag_prefix else []
1912+
1913 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
1914 # if there isn't one, this yields HEX[-dirty] (no NUM)
1915- describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
1916- "--always", "--long",
1917- "--match", "%s*" % tag_prefix],
1918- cwd=root)
1919+ describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty",
1920+ "--always", "--long", *MATCH_ARGS],
1921+ cwd=root)
1922 # --long was added in git-1.5.5
1923 if describe_out is None:
1924 raise NotThisMethod("'git describe' failed")
1925 describe_out = describe_out.strip()
1926- full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
1927+ full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
1928 if full_out is None:
1929 raise NotThisMethod("'git rev-parse' failed")
1930 full_out = full_out.strip()
1931@@ -1076,6 +1223,39 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1932 pieces["short"] = full_out[:7] # maybe improved later
1933 pieces["error"] = None
1934
1935+ branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
1936+ cwd=root)
1937+ # --abbrev-ref was added in git-1.6.3
1938+ if rc != 0 or branch_name is None:
1939+ raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
1940+ branch_name = branch_name.strip()
1941+
1942+ if branch_name == "HEAD":
1943+ # If we aren't exactly on a branch, pick a branch which represents
1944+ # the current commit. If all else fails, we are on a branchless
1945+ # commit.
1946+ branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
1947+ # --contains was added in git-1.5.4
1948+ if rc != 0 or branches is None:
1949+ raise NotThisMethod("'git branch --contains' returned error")
1950+ branches = branches.split("\n")
1951+
1952+ # Remove the first line if we're running detached
1953+ if "(" in branches[0]:
1954+ branches.pop(0)
1955+
1956+ # Strip off the leading "* " from the list of branches.
1957+ branches = [branch[2:] for branch in branches]
1958+ if "master" in branches:
1959+ branch_name = "master"
1960+ elif not branches:
1961+ branch_name = None
1962+ else:
1963+ # Pick the first branch that is returned. Good or bad.
1964+ branch_name = branches[0]
1965+
1966+ pieces["branch"] = branch_name
1967+
1968 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
1969 # TAG might have hyphens.
1970 git_describe = describe_out
1971@@ -1092,7 +1272,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1972 # TAG-NUM-gHEX
1973 mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
1974 if not mo:
1975- # unparseable. Maybe git-describe is misbehaving?
1976+ # unparsable. Maybe git-describe is misbehaving?
1977 pieces["error"] = ("unable to parse git-describe output: '%s'"
1978 % describe_out)
1979 return pieces
1980@@ -1117,10 +1297,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1981 else:
1982 # HEX: no tags
1983 pieces["closest-tag"] = None
1984- count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
1985- cwd=root)
1986+ count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
1987 pieces["distance"] = int(count_out) # total number of commits
1988
1989+ # commit date: see ISO-8601 comment in git_versions_from_keywords()
1990+ date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
1991+ # Use only the last line. Previous lines may contain GPG signature
1992+ # information.
1993+ date = date.splitlines()[-1]
1994+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1995+
1996 return pieces
1997
1998
1999@@ -1128,7 +1314,7 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
2000 """Git-specific installation logic for Versioneer.
2001
2002 For Git, this means creating/changing .gitattributes to mark _version.py
2003- for export-time keyword substitution.
2004+ for export-subst keyword substitution.
2005 """
2006 GITS = ["git"]
2007 if sys.platform == "win32":
2008@@ -1137,27 +1323,26 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
2009 if ipy:
2010 files.append(ipy)
2011 try:
2012- me = __file__
2013- if me.endswith(".pyc") or me.endswith(".pyo"):
2014- me = os.path.splitext(me)[0] + ".py"
2015- versioneer_file = os.path.relpath(me)
2016+ my_path = __file__
2017+ if my_path.endswith(".pyc") or my_path.endswith(".pyo"):
2018+ my_path = os.path.splitext(my_path)[0] + ".py"
2019+ versioneer_file = os.path.relpath(my_path)
2020 except NameError:
2021 versioneer_file = "versioneer.py"
2022 files.append(versioneer_file)
2023 present = False
2024 try:
2025- f = open(".gitattributes", "r")
2026- for line in f.readlines():
2027- if line.strip().startswith(versionfile_source):
2028- if "export-subst" in line.strip().split()[1:]:
2029- present = True
2030- f.close()
2031- except EnvironmentError:
2032+ with open(".gitattributes", "r") as fobj:
2033+ for line in fobj:
2034+ if line.strip().startswith(versionfile_source):
2035+ if "export-subst" in line.strip().split()[1:]:
2036+ present = True
2037+ break
2038+ except OSError:
2039 pass
2040 if not present:
2041- f = open(".gitattributes", "a+")
2042- f.write("%s export-subst\n" % versionfile_source)
2043- f.close()
2044+ with open(".gitattributes", "a+") as fobj:
2045+ fobj.write(f"{versionfile_source} export-subst\n")
2046 files.append(".gitattributes")
2047 run_command(GITS, ["add", "--"] + files)
2048
2049@@ -1165,27 +1350,34 @@ def do_vcs_install(manifest_in, versionfile_source, ipy):
2050 def versions_from_parentdir(parentdir_prefix, root, verbose):
2051 """Try to determine the version from the parent directory name.
2052
2053- Source tarballs conventionally unpack into a directory that includes
2054- both the project name and a version string.
2055+ Source tarballs conventionally unpack into a directory that includes both
2056+ the project name and a version string. We will also support searching up
2057+ two directory levels for an appropriately named parent directory
2058 """
2059- dirname = os.path.basename(root)
2060- if not dirname.startswith(parentdir_prefix):
2061- if verbose:
2062- print("guessing rootdir is '%s', but '%s' doesn't start with "
2063- "prefix '%s'" % (root, dirname, parentdir_prefix))
2064- raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
2065- return {"version": dirname[len(parentdir_prefix):],
2066- "full-revisionid": None,
2067- "dirty": False, "error": None}
2068+ rootdirs = []
2069+
2070+ for _ in range(3):
2071+ dirname = os.path.basename(root)
2072+ if dirname.startswith(parentdir_prefix):
2073+ return {"version": dirname[len(parentdir_prefix):],
2074+ "full-revisionid": None,
2075+ "dirty": False, "error": None, "date": None}
2076+ rootdirs.append(root)
2077+ root = os.path.dirname(root) # up a level
2078+
2079+ if verbose:
2080+ print("Tried directories %s but none started with prefix %s" %
2081+ (str(rootdirs), parentdir_prefix))
2082+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
2083+
2084
2085 SHORT_VERSION_PY = """
2086-# This file was generated by 'versioneer.py' (0.16) from
2087+# This file was generated by 'versioneer.py' (0.22) from
2088 # revision-control system data, or from the parent directory name of an
2089 # unpacked source archive. Distribution tarballs contain a pre-generated copy
2090 # of this file.
2091
2092 import json
2093-import sys
2094
2095 version_json = '''
2096 %s
2097@@ -1202,10 +1394,13 @@ def versions_from_file(filename):
2098 try:
2099 with open(filename) as f:
2100 contents = f.read()
2101- except EnvironmentError:
2102+ except OSError:
2103 raise NotThisMethod("unable to read _version.py")
2104 mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON",
2105 contents, re.M | re.S)
2106+ if not mo:
2107+ mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON",
2108+ contents, re.M | re.S)
2109 if not mo:
2110 raise NotThisMethod("no version_json in _version.py")
2111 return json.loads(mo.group(1))
2112@@ -1254,19 +1449,67 @@ def render_pep440(pieces):
2113 return rendered
2114
2115
2116-def render_pep440_pre(pieces):
2117- """TAG[.post.devDISTANCE] -- No -dirty.
2118+def render_pep440_branch(pieces):
2119+ """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
2120+
2121+ The ".dev0" means not master branch. Note that .dev0 sorts backwards
2122+ (a feature branch will appear "older" than the master branch).
2123
2124 Exceptions:
2125- 1: no tags. 0.post.devDISTANCE
2126+ 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
2127 """
2128 if pieces["closest-tag"]:
2129 rendered = pieces["closest-tag"]
2130+ if pieces["distance"] or pieces["dirty"]:
2131+ if pieces["branch"] != "master":
2132+ rendered += ".dev0"
2133+ rendered += plus_or_dot(pieces)
2134+ rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
2135+ if pieces["dirty"]:
2136+ rendered += ".dirty"
2137+ else:
2138+ # exception #1
2139+ rendered = "0"
2140+ if pieces["branch"] != "master":
2141+ rendered += ".dev0"
2142+ rendered += "+untagged.%d.g%s" % (pieces["distance"],
2143+ pieces["short"])
2144+ if pieces["dirty"]:
2145+ rendered += ".dirty"
2146+ return rendered
2147+
2148+
2149+def pep440_split_post(ver):
2150+ """Split pep440 version string at the post-release segment.
2151+
2152+ Returns the release segments before the post-release and the
2153+ post-release version number (or -1 if no post-release segment is present).
2154+ """
2155+ vc = str.split(ver, ".post")
2156+ return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
2157+
2158+
2159+def render_pep440_pre(pieces):
2160+ """TAG[.postN.devDISTANCE] -- No -dirty.
2161+
2162+ Exceptions:
2163+ 1: no tags. 0.post0.devDISTANCE
2164+ """
2165+ if pieces["closest-tag"]:
2166 if pieces["distance"]:
2167- rendered += ".post.dev%d" % pieces["distance"]
2168+ # update the post release segment
2169+ tag_version, post_version = pep440_split_post(pieces["closest-tag"])
2170+ rendered = tag_version
2171+ if post_version is not None:
2172+ rendered += ".post%d.dev%d" % (post_version+1, pieces["distance"])
2173+ else:
2174+ rendered += ".post0.dev%d" % (pieces["distance"])
2175+ else:
2176+ # no commits, use the tag as the version
2177+ rendered = pieces["closest-tag"]
2178 else:
2179 # exception #1
2180- rendered = "0.post.dev%d" % pieces["distance"]
2181+ rendered = "0.post0.dev%d" % pieces["distance"]
2182 return rendered
2183
2184
2185@@ -1297,12 +1540,41 @@ def render_pep440_post(pieces):
2186 return rendered
2187
2188
2189+def render_pep440_post_branch(pieces):
2190+ """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
2191+
2192+ The ".dev0" means not master branch.
2193+
2194+ Exceptions:
2195+ 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
2196+ """
2197+ if pieces["closest-tag"]:
2198+ rendered = pieces["closest-tag"]
2199+ if pieces["distance"] or pieces["dirty"]:
2200+ rendered += ".post%d" % pieces["distance"]
2201+ if pieces["branch"] != "master":
2202+ rendered += ".dev0"
2203+ rendered += plus_or_dot(pieces)
2204+ rendered += "g%s" % pieces["short"]
2205+ if pieces["dirty"]:
2206+ rendered += ".dirty"
2207+ else:
2208+ # exception #1
2209+ rendered = "0.post%d" % pieces["distance"]
2210+ if pieces["branch"] != "master":
2211+ rendered += ".dev0"
2212+ rendered += "+g%s" % pieces["short"]
2213+ if pieces["dirty"]:
2214+ rendered += ".dirty"
2215+ return rendered
2216+
2217+
2218 def render_pep440_old(pieces):
2219 """TAG[.postDISTANCE[.dev0]] .
2220
2221 The ".dev0" means dirty.
2222
2223- Eexceptions:
2224+ Exceptions:
2225 1: no tags. 0.postDISTANCE[.dev0]
2226 """
2227 if pieces["closest-tag"]:
2228@@ -1365,17 +1637,22 @@ def render(pieces, style):
2229 return {"version": "unknown",
2230 "full-revisionid": pieces.get("long"),
2231 "dirty": None,
2232- "error": pieces["error"]}
2233+ "error": pieces["error"],
2234+ "date": None}
2235
2236 if not style or style == "default":
2237 style = "pep440" # the default
2238
2239 if style == "pep440":
2240 rendered = render_pep440(pieces)
2241+ elif style == "pep440-branch":
2242+ rendered = render_pep440_branch(pieces)
2243 elif style == "pep440-pre":
2244 rendered = render_pep440_pre(pieces)
2245 elif style == "pep440-post":
2246 rendered = render_pep440_post(pieces)
2247+ elif style == "pep440-post-branch":
2248+ rendered = render_pep440_post_branch(pieces)
2249 elif style == "pep440-old":
2250 rendered = render_pep440_old(pieces)
2251 elif style == "git-describe":
2252@@ -1386,7 +1663,8 @@ def render(pieces, style):
2253 raise ValueError("unknown style '%s'" % style)
2254
2255 return {"version": rendered, "full-revisionid": pieces["long"],
2256- "dirty": pieces["dirty"], "error": None}
2257+ "dirty": pieces["dirty"], "error": None,
2258+ "date": pieces.get("date")}
2259
2260
2261 class VersioneerBadRootError(Exception):
2262@@ -1465,7 +1743,8 @@ def get_versions(verbose=False):
2263 print("unable to compute version")
2264
2265 return {"version": "0+unknown", "full-revisionid": None,
2266- "dirty": None, "error": "unable to compute version"}
2267+ "dirty": None, "error": "unable to compute version",
2268+ "date": None}
2269
2270
2271 def get_version():
2272@@ -1473,8 +1752,12 @@ def get_version():
2273 return get_versions()["version"]
2274
2275
2276-def get_cmdclass():
2277- """Get the custom setuptools/distutils subclasses used by Versioneer."""
2278+def get_cmdclass(cmdclass=None):
2279+ """Get the custom setuptools/distutils subclasses used by Versioneer.
2280+
2281+ If the package uses a different cmdclass (e.g. one from numpy), it
2282+ should be provide as an argument.
2283+ """
2284 if "versioneer" in sys.modules:
2285 del sys.modules["versioneer"]
2286 # this fixes the "python setup.py develop" case (also 'install' and
2287@@ -1488,12 +1771,15 @@ def get_cmdclass():
2288 # parent is protected against the child's "import versioneer". By
2289 # removing ourselves from sys.modules here, before the child build
2290 # happens, we protect the child from the parent's versioneer too.
2291- # Also see https://github.com/warner/python-versioneer/issues/52
2292+ # Also see https://github.com/python-versioneer/python-versioneer/issues/52
2293
2294- cmds = {}
2295+ cmds = {} if cmdclass is None else cmdclass.copy()
2296
2297 # we add "version" to both distutils and setuptools
2298- from distutils.core import Command
2299+ try:
2300+ from setuptools import Command
2301+ except ImportError:
2302+ from distutils.core import Command
2303
2304 class cmd_version(Command):
2305 description = "report generated version string"
2306@@ -1511,6 +1797,7 @@ def get_cmdclass():
2307 print("Version: %s" % vers["version"])
2308 print(" full-revisionid: %s" % vers.get("full-revisionid"))
2309 print(" dirty: %s" % vers.get("dirty"))
2310+ print(" date: %s" % vers.get("date"))
2311 if vers["error"]:
2312 print(" error: %s" % vers["error"])
2313 cmds["version"] = cmd_version
2314@@ -1524,9 +1811,16 @@ def get_cmdclass():
2315 # setuptools/bdist_egg -> distutils/install_lib -> build_py
2316 # setuptools/install -> bdist_egg ->..
2317 # setuptools/develop -> ?
2318+ # pip install:
2319+ # copies source tree to a tempdir before running egg_info/etc
2320+ # if .git isn't copied too, 'git describe' will fail
2321+ # then does setup.py bdist_wheel, or sometimes setup.py install
2322+ # setup.py egg_info -> ?
2323
2324 # we override different "build_py" commands for both environments
2325- if "setuptools" in sys.modules:
2326+ if 'build_py' in cmds:
2327+ _build_py = cmds['build_py']
2328+ elif "setuptools" in sys.modules:
2329 from setuptools.command.build_py import build_py as _build_py
2330 else:
2331 from distutils.command.build_py import build_py as _build_py
2332@@ -1546,8 +1840,41 @@ def get_cmdclass():
2333 write_to_version_file(target_versionfile, versions)
2334 cmds["build_py"] = cmd_build_py
2335
2336+ if 'build_ext' in cmds:
2337+ _build_ext = cmds['build_ext']
2338+ elif "setuptools" in sys.modules:
2339+ from setuptools.command.build_ext import build_ext as _build_ext
2340+ else:
2341+ from distutils.command.build_ext import build_ext as _build_ext
2342+
2343+ class cmd_build_ext(_build_ext):
2344+ def run(self):
2345+ root = get_root()
2346+ cfg = get_config_from_root(root)
2347+ versions = get_versions()
2348+ _build_ext.run(self)
2349+ if self.inplace:
2350+ # build_ext --inplace will only build extensions in
2351+ # build/lib<..> dir with no _version.py to write to.
2352+ # As in place builds will already have a _version.py
2353+ # in the module dir, we do not need to write one.
2354+ return
2355+ # now locate _version.py in the new build/ directory and replace
2356+ # it with an updated value
2357+ target_versionfile = os.path.join(self.build_lib,
2358+ cfg.versionfile_build)
2359+ print("UPDATING %s" % target_versionfile)
2360+ write_to_version_file(target_versionfile, versions)
2361+ cmds["build_ext"] = cmd_build_ext
2362+
2363 if "cx_Freeze" in sys.modules: # cx_freeze enabled?
2364 from cx_Freeze.dist import build_exe as _build_exe
2365+ # nczeczulin reports that py2exe won't like the pep440-style string
2366+ # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
2367+ # setup(console=[{
2368+ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION
2369+ # "product_version": versioneer.get_version(),
2370+ # ...
2371
2372 class cmd_build_exe(_build_exe):
2373 def run(self):
2374@@ -1572,8 +1899,35 @@ def get_cmdclass():
2375 cmds["build_exe"] = cmd_build_exe
2376 del cmds["build_py"]
2377
2378+ if 'py2exe' in sys.modules: # py2exe enabled?
2379+ from py2exe.distutils_buildexe import py2exe as _py2exe
2380+
2381+ class cmd_py2exe(_py2exe):
2382+ def run(self):
2383+ root = get_root()
2384+ cfg = get_config_from_root(root)
2385+ versions = get_versions()
2386+ target_versionfile = cfg.versionfile_source
2387+ print("UPDATING %s" % target_versionfile)
2388+ write_to_version_file(target_versionfile, versions)
2389+
2390+ _py2exe.run(self)
2391+ os.unlink(target_versionfile)
2392+ with open(cfg.versionfile_source, "w") as f:
2393+ LONG = LONG_VERSION_PY[cfg.VCS]
2394+ f.write(LONG %
2395+ {"DOLLAR": "$",
2396+ "STYLE": cfg.style,
2397+ "TAG_PREFIX": cfg.tag_prefix,
2398+ "PARENTDIR_PREFIX": cfg.parentdir_prefix,
2399+ "VERSIONFILE_SOURCE": cfg.versionfile_source,
2400+ })
2401+ cmds["py2exe"] = cmd_py2exe
2402+
2403 # we override different "sdist" commands for both environments
2404- if "setuptools" in sys.modules:
2405+ if 'sdist' in cmds:
2406+ _sdist = cmds['sdist']
2407+ elif "setuptools" in sys.modules:
2408 from setuptools.command.sdist import sdist as _sdist
2409 else:
2410 from distutils.command.sdist import sdist as _sdist
2411@@ -1640,21 +1994,26 @@ SAMPLE_CONFIG = """
2412
2413 """
2414
2415-INIT_PY_SNIPPET = """
2416+OLD_SNIPPET = """
2417 from ._version import get_versions
2418 __version__ = get_versions()['version']
2419 del get_versions
2420 """
2421
2422+INIT_PY_SNIPPET = """
2423+from . import {0}
2424+__version__ = {0}.get_versions()['version']
2425+"""
2426+
2427
2428 def do_setup():
2429- """Main VCS-independent setup function for installing Versioneer."""
2430+ """Do main VCS-independent setup function for installing Versioneer."""
2431 root = get_root()
2432 try:
2433 cfg = get_config_from_root(root)
2434- except (EnvironmentError, configparser.NoSectionError,
2435+ except (OSError, configparser.NoSectionError,
2436 configparser.NoOptionError) as e:
2437- if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
2438+ if isinstance(e, (OSError, configparser.NoSectionError)):
2439 print("Adding sample versioneer config to setup.cfg",
2440 file=sys.stderr)
2441 with open(os.path.join(root, "setup.cfg"), "a") as f:
2442@@ -1678,12 +2037,18 @@ def do_setup():
2443 try:
2444 with open(ipy, "r") as f:
2445 old = f.read()
2446- except EnvironmentError:
2447+ except OSError:
2448 old = ""
2449- if INIT_PY_SNIPPET not in old:
2450+ module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
2451+ snippet = INIT_PY_SNIPPET.format(module)
2452+ if OLD_SNIPPET in old:
2453+ print(" replacing boilerplate in %s" % ipy)
2454+ with open(ipy, "w") as f:
2455+ f.write(old.replace(OLD_SNIPPET, snippet))
2456+ elif snippet not in old:
2457 print(" appending to %s" % ipy)
2458 with open(ipy, "a") as f:
2459- f.write(INIT_PY_SNIPPET)
2460+ f.write(snippet)
2461 else:
2462 print(" %s unmodified" % ipy)
2463 else:
2464@@ -1702,7 +2067,7 @@ def do_setup():
2465 if line.startswith("include "):
2466 for include in line.split()[1:]:
2467 simple_includes.add(include)
2468- except EnvironmentError:
2469+ except OSError:
2470 pass
2471 # That doesn't cover everything MANIFEST.in can do
2472 # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
2473@@ -1723,7 +2088,7 @@ def do_setup():
2474 print(" versionfile_source already in MANIFEST.in")
2475
2476 # Make VCS-specific changes. For git, this means creating/changing
2477- # .gitattributes to mark _version.py for export-time keyword
2478+ # .gitattributes to mark _version.py for export-subst keyword
2479 # substitution.
2480 do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
2481 return 0
2482@@ -1765,6 +2130,7 @@ def scan_setup_py():
2483 errors += 1
2484 return errors
2485
2486+
2487 if __name__ == "__main__":
2488 cmd = sys.argv[1]
2489 if cmd == "setup":