2 # This file helps to compute a version number in source trees obtained from
3 # git-archive tarball (such as those provided by githubs download-from-tag
4 # feature). Distribution tarballs (built by setup.py sdist) and build
5 # directories (produced by setup.py build) will contain a much shorter file
6 # that just contains the computed version number.
8 # This file is released into the public domain. Generated by
9 # versioneer-0.15 (https://github.com/warner/python-versioneer)
19 # these strings will be replaced by git during git-archive.
20 # setup.py/versioneer.py will grep for the variable names, so they must
21 # each be defined on a line of their own. _version.py will just call
23 git_refnames = "$Format:%d$"
24 git_full = "$Format:%H$"
25 keywords = {"refnames": git_refnames, "full": git_full}
29 class VersioneerConfig:
34 # these strings are filled in when 'setup.py versioneer' creates
36 cfg = VersioneerConfig()
39 cfg.tag_prefix = "zfec-"
40 cfg.parentdir_prefix = "zfec-"
41 cfg.versionfile_source = "zfec/_version.py"
46 class NotThisMethod(Exception):
54 def register_vcs_handler(vcs, method): # decorator
56 if vcs not in HANDLERS:
58 HANDLERS[vcs][method] = f
63 def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
64 assert isinstance(commands, list)
68 dispcmd = str([c] + args)
69 # remember shell=False, so use git.cmd on windows, not just git
70 p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
71 stderr=(subprocess.PIPE if hide_stderr
74 except EnvironmentError:
76 if e.errno == errno.ENOENT:
79 print("unable to run %s" % dispcmd)
84 print("unable to find command, tried %s" % (commands,))
86 stdout = p.communicate()[0].strip()
87 if sys.version_info[0] >= 3:
88 stdout = stdout.decode()
91 print("unable to run %s (error)" % dispcmd)
96 def versions_from_parentdir(parentdir_prefix, root, verbose):
97 # Source tarballs conventionally unpack into a directory that includes
98 # both the project name and a version string.
99 dirname = os.path.basename(root)
100 if not dirname.startswith(parentdir_prefix):
102 print("guessing rootdir is '%s', but '%s' doesn't start with "
103 "prefix '%s'" % (root, dirname, parentdir_prefix))
104 raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
105 return {"version": dirname[len(parentdir_prefix):],
106 "full-revisionid": None,
107 "dirty": False, "error": None}
110 @register_vcs_handler("git", "get_keywords")
111 def git_get_keywords(versionfile_abs):
112 # the code embedded in _version.py can just fetch the value of these
113 # keywords. When used from setup.py, we don't want to import _version.py,
114 # so we do it with a regexp instead. This function is not used from
118 f = open(versionfile_abs, "r")
119 for line in f.readlines():
120 if line.strip().startswith("git_refnames ="):
121 mo = re.search(r'=\s*"(.*)"', line)
123 keywords["refnames"] = mo.group(1)
124 if line.strip().startswith("git_full ="):
125 mo = re.search(r'=\s*"(.*)"', line)
127 keywords["full"] = mo.group(1)
129 except EnvironmentError:
134 @register_vcs_handler("git", "keywords")
135 def git_versions_from_keywords(keywords, tag_prefix, verbose):
137 raise NotThisMethod("no keywords at all, weird")
138 refnames = keywords["refnames"].strip()
139 if refnames.startswith("$Format"):
141 print("keywords are unexpanded, not using")
142 raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
143 refs = set([r.strip() for r in refnames.strip("()").split(",")])
144 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
145 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
147 tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
149 # Either we're using git < 1.8.3, or there really are no tags. We use
150 # a heuristic: assume all version tags have a digit. The old git %d
151 # expansion behaves like git log --decorate=short and strips out the
152 # refs/heads/ and refs/tags/ prefixes that would let us distinguish
153 # between branches and tags. By ignoring refnames without digits, we
154 # filter out many common branch names like "release" and
155 # "stabilization", as well as "HEAD" and "master".
156 tags = set([r for r in refs if re.search(r'\d', r)])
158 print("discarding '%s', no digits" % ",".join(refs-tags))
160 print("likely tags: %s" % ",".join(sorted(tags)))
161 for ref in sorted(tags):
162 # sorting will prefer e.g. "2.0" over "2.0rc1"
163 if ref.startswith(tag_prefix):
164 r = ref[len(tag_prefix):]
166 print("picking %s" % r)
167 return {"version": r,
168 "full-revisionid": keywords["full"].strip(),
169 "dirty": False, "error": None
171 # no suitable tags, so version is "0+unknown", but full hex is still there
173 print("no suitable tags, using unknown + full revision id")
174 return {"version": "0+unknown",
175 "full-revisionid": keywords["full"].strip(),
176 "dirty": False, "error": "no suitable tags"}
179 @register_vcs_handler("git", "pieces_from_vcs")
180 def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
181 # this runs 'git' from the root of the source tree. This only gets called
182 # if the git-archive 'subst' keywords were *not* expanded, and
183 # _version.py hasn't already been rewritten with a short version string,
184 # meaning we're inside a checked out source tree.
186 if not os.path.exists(os.path.join(root, ".git")):
188 print("no .git in %s" % root)
189 raise NotThisMethod("no .git directory")
192 if sys.platform == "win32":
193 GITS = ["git.cmd", "git.exe"]
194 # if there is a tag, this yields TAG-NUM-gHEX[-dirty]
195 # if there are no tags, this yields HEX[-dirty] (no NUM)
196 describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
197 "--always", "--long"],
199 # --long was added in git-1.5.5
200 if describe_out is None:
201 raise NotThisMethod("'git describe' failed")
202 describe_out = describe_out.strip()
203 full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
205 raise NotThisMethod("'git rev-parse' failed")
206 full_out = full_out.strip()
209 pieces["long"] = full_out
210 pieces["short"] = full_out[:7] # maybe improved later
211 pieces["error"] = None
213 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
214 # TAG might have hyphens.
215 git_describe = describe_out
217 # look for -dirty suffix
218 dirty = git_describe.endswith("-dirty")
219 pieces["dirty"] = dirty
221 git_describe = git_describe[:git_describe.rindex("-dirty")]
223 # now we have TAG-NUM-gHEX or HEX
225 if "-" in git_describe:
227 mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
229 # unparseable. Maybe git-describe is misbehaving?
230 pieces["error"] = ("unable to parse git-describe output: '%s'"
235 full_tag = mo.group(1)
236 if not full_tag.startswith(tag_prefix):
238 fmt = "tag '%s' doesn't start with prefix '%s'"
239 print(fmt % (full_tag, tag_prefix))
240 pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
241 % (full_tag, tag_prefix))
243 pieces["closest-tag"] = full_tag[len(tag_prefix):]
245 # distance: number of commits since tag
246 pieces["distance"] = int(mo.group(2))
248 # commit: short hex revision ID
249 pieces["short"] = mo.group(3)
253 pieces["closest-tag"] = None
254 count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
256 pieces["distance"] = int(count_out) # total number of commits
261 def plus_or_dot(pieces):
262 if "+" in pieces.get("closest-tag", ""):
267 def render_pep440(pieces):
268 # now build up version string, with post-release "local version
269 # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
270 # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
273 # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
275 if pieces["closest-tag"]:
276 rendered = pieces["closest-tag"]
277 if pieces["distance"] or pieces["dirty"]:
278 rendered += plus_or_dot(pieces)
279 rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
284 rendered = "0+untagged.%d.g%s" % (pieces["distance"],
291 def render_pep440_pre(pieces):
292 # TAG[.post.devDISTANCE] . No -dirty
295 # 1: no tags. 0.post.devDISTANCE
297 if pieces["closest-tag"]:
298 rendered = pieces["closest-tag"]
299 if pieces["distance"]:
300 rendered += ".post.dev%d" % pieces["distance"]
303 rendered = "0.post.dev%d" % pieces["distance"]
307 def render_pep440_post(pieces):
308 # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that
309 # .dev0 sorts backwards (a dirty tree will appear "older" than the
310 # corresponding clean one), but you shouldn't be releasing software with
314 # 1: no tags. 0.postDISTANCE[.dev0]
316 if pieces["closest-tag"]:
317 rendered = pieces["closest-tag"]
318 if pieces["distance"] or pieces["dirty"]:
319 rendered += ".post%d" % pieces["distance"]
322 rendered += plus_or_dot(pieces)
323 rendered += "g%s" % pieces["short"]
326 rendered = "0.post%d" % pieces["distance"]
329 rendered += "+g%s" % pieces["short"]
333 def render_pep440_old(pieces):
334 # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty.
337 # 1: no tags. 0.postDISTANCE[.dev0]
339 if pieces["closest-tag"]:
340 rendered = pieces["closest-tag"]
341 if pieces["distance"] or pieces["dirty"]:
342 rendered += ".post%d" % pieces["distance"]
347 rendered = "0.post%d" % pieces["distance"]
353 def render_git_describe(pieces):
354 # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty
358 # 1: no tags. HEX[-dirty] (note: no 'g' prefix)
360 if pieces["closest-tag"]:
361 rendered = pieces["closest-tag"]
362 if pieces["distance"]:
363 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
366 rendered = pieces["short"]
372 def render_git_describe_long(pieces):
373 # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty
374 # --always -long'. The distance/hash is unconditional.
377 # 1: no tags. HEX[-dirty] (note: no 'g' prefix)
379 if pieces["closest-tag"]:
380 rendered = pieces["closest-tag"]
381 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
384 rendered = pieces["short"]
390 def render(pieces, style):
392 return {"version": "unknown",
393 "full-revisionid": pieces.get("long"),
395 "error": pieces["error"]}
397 if not style or style == "default":
398 style = "pep440" # the default
400 if style == "pep440":
401 rendered = render_pep440(pieces)
402 elif style == "pep440-pre":
403 rendered = render_pep440_pre(pieces)
404 elif style == "pep440-post":
405 rendered = render_pep440_post(pieces)
406 elif style == "pep440-old":
407 rendered = render_pep440_old(pieces)
408 elif style == "git-describe":
409 rendered = render_git_describe(pieces)
410 elif style == "git-describe-long":
411 rendered = render_git_describe_long(pieces)
413 raise ValueError("unknown style '%s'" % style)
415 return {"version": rendered, "full-revisionid": pieces["long"],
416 "dirty": pieces["dirty"], "error": None}
420 # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
421 # __file__, we can work backwards from there to the root. Some
422 # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
423 # case we can only use expanded keywords.
426 verbose = cfg.verbose
429 return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
431 except NotThisMethod:
435 root = os.path.realpath(__file__)
436 # versionfile_source is the relative path from the top of the source
437 # tree (where the .git directory might live) to this file. Invert
438 # this to find the root from __file__.
439 for i in cfg.versionfile_source.split('/'):
440 root = os.path.dirname(root)
442 return {"version": "0+unknown", "full-revisionid": None,
444 "error": "unable to find root of source tree"}
447 pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
448 return render(pieces, cfg.style)
449 except NotThisMethod:
453 if cfg.parentdir_prefix:
454 return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
455 except NotThisMethod:
458 return {"version": "0+unknown", "full-revisionid": None,
460 "error": "unable to compute version"}