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.16 (https://github.com/warner/python-versioneer)
11 """Git implementation of _version.py."""
21 """Get the keywords needed to look up the version information."""
22 # these strings will be replaced by git during git-archive.
23 # setup.py/versioneer.py will grep for the variable names, so they must
24 # each be defined on a line of their own. _version.py will just call
26 git_refnames = "$Format:%d$"
27 git_full = "$Format:%H$"
28 keywords = {"refnames": git_refnames, "full": git_full}
32 class VersioneerConfig:
33 """Container for Versioneer configuration parameters."""
37 """Create, populate and return the VersioneerConfig() object."""
38 # these strings are filled in when 'setup.py versioneer' creates
40 cfg = VersioneerConfig()
44 cfg.parentdir_prefix = "None"
45 cfg.versionfile_source = "src/leap/common/_version.py"
50 class NotThisMethod(Exception):
51 """Exception raised if a method is not valid for the current scenario."""
58 def register_vcs_handler(vcs, method): # decorator
59 """Decorator to mark a method as the handler for a particular VCS."""
61 """Store f in HANDLERS[vcs][method]."""
62 if vcs not in HANDLERS:
64 HANDLERS[vcs][method] = f
69 def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
70 """Call the given command(s)."""
71 assert isinstance(commands, list)
75 dispcmd = str([c] + args)
76 # remember shell=False, so use git.cmd on windows, not just git
77 p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
78 stderr=(subprocess.PIPE if hide_stderr
81 except EnvironmentError:
83 if e.errno == errno.ENOENT:
86 print("unable to run %s" % dispcmd)
91 print("unable to find command, tried %s" % (commands,))
93 stdout = p.communicate()[0].strip()
94 if sys.version_info[0] >= 3:
95 stdout = stdout.decode()
98 print("unable to run %s (error)" % dispcmd)
103 def versions_from_parentdir(parentdir_prefix, root, verbose):
104 """Try to determine the version from the parent directory name.
106 Source tarballs conventionally unpack into a directory that includes
107 both the project name and a version string.
109 dirname = os.path.basename(root)
110 if not dirname.startswith(parentdir_prefix):
112 print("guessing rootdir is '%s', but '%s' doesn't start with "
113 "prefix '%s'" % (root, dirname, parentdir_prefix))
114 raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
115 return {"version": dirname[len(parentdir_prefix):],
116 "full-revisionid": None,
117 "dirty": False, "error": None}
120 @register_vcs_handler("git", "get_keywords")
121 def git_get_keywords(versionfile_abs):
122 """Extract version information from the given file."""
123 # the code embedded in _version.py can just fetch the value of these
124 # keywords. When used from setup.py, we don't want to import _version.py,
125 # so we do it with a regexp instead. This function is not used from
129 f = open(versionfile_abs, "r")
130 for line in f.readlines():
131 if line.strip().startswith("git_refnames ="):
132 mo = re.search(r'=\s*"(.*)"', line)
134 keywords["refnames"] = mo.group(1)
135 if line.strip().startswith("git_full ="):
136 mo = re.search(r'=\s*"(.*)"', line)
138 keywords["full"] = mo.group(1)
140 except EnvironmentError:
145 @register_vcs_handler("git", "keywords")
146 def git_versions_from_keywords(keywords, tag_prefix, verbose):
147 """Get version information from git keywords."""
149 raise NotThisMethod("no keywords at all, weird")
150 refnames = keywords["refnames"].strip()
151 if refnames.startswith("$Format"):
153 print("keywords are unexpanded, not using")
154 raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
155 refs = set([r.strip() for r in refnames.strip("()").split(",")])
156 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
157 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
159 tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
161 # Either we're using git < 1.8.3, or there really are no tags. We use
162 # a heuristic: assume all version tags have a digit. The old git %d
163 # expansion behaves like git log --decorate=short and strips out the
164 # refs/heads/ and refs/tags/ prefixes that would let us distinguish
165 # between branches and tags. By ignoring refnames without digits, we
166 # filter out many common branch names like "release" and
167 # "stabilization", as well as "HEAD" and "master".
168 tags = set([r for r in refs if re.search(r'\d', r)])
170 print("discarding '%s', no digits" % ",".join(refs-tags))
172 print("likely tags: %s" % ",".join(sorted(tags)))
173 for ref in sorted(tags):
174 # sorting will prefer e.g. "2.0" over "2.0rc1"
175 if ref.startswith(tag_prefix):
176 r = ref[len(tag_prefix):]
178 print("picking %s" % r)
179 return {"version": r,
180 "full-revisionid": keywords["full"].strip(),
181 "dirty": False, "error": None
183 # no suitable tags, so version is "0+unknown", but full hex is still there
185 print("no suitable tags, using unknown + full revision id")
186 return {"version": "0+unknown",
187 "full-revisionid": keywords["full"].strip(),
188 "dirty": False, "error": "no suitable tags"}
191 @register_vcs_handler("git", "pieces_from_vcs")
192 def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
193 """Get version from 'git describe' in the root of the source tree.
195 This only gets called if the git-archive 'subst' keywords were *not*
196 expanded, and _version.py hasn't already been rewritten with a short
197 version string, meaning we're inside a checked out source tree.
199 if not os.path.exists(os.path.join(root, ".git")):
201 print("no .git in %s" % root)
202 raise NotThisMethod("no .git directory")
205 if sys.platform == "win32":
206 GITS = ["git.cmd", "git.exe"]
207 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
208 # if there isn't one, this yields HEX[-dirty] (no NUM)
209 describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
210 "--always", "--long",
211 "--match", "%s*" % tag_prefix],
213 # --long was added in git-1.5.5
214 if describe_out is None:
215 raise NotThisMethod("'git describe' failed")
216 describe_out = describe_out.strip()
217 full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
219 raise NotThisMethod("'git rev-parse' failed")
220 full_out = full_out.strip()
223 pieces["long"] = full_out
224 pieces["short"] = full_out[:7] # maybe improved later
225 pieces["error"] = None
227 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
228 # TAG might have hyphens.
229 git_describe = describe_out
231 # look for -dirty suffix
232 dirty = git_describe.endswith("-dirty")
233 pieces["dirty"] = dirty
235 git_describe = git_describe[:git_describe.rindex("-dirty")]
237 # now we have TAG-NUM-gHEX or HEX
239 if "-" in git_describe:
241 mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
243 # unparseable. Maybe git-describe is misbehaving?
244 pieces["error"] = ("unable to parse git-describe output: '%s'"
249 full_tag = mo.group(1)
250 if not full_tag.startswith(tag_prefix):
252 fmt = "tag '%s' doesn't start with prefix '%s'"
253 print(fmt % (full_tag, tag_prefix))
254 pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
255 % (full_tag, tag_prefix))
257 pieces["closest-tag"] = full_tag[len(tag_prefix):]
259 # distance: number of commits since tag
260 pieces["distance"] = int(mo.group(2))
262 # commit: short hex revision ID
263 pieces["short"] = mo.group(3)
267 pieces["closest-tag"] = None
268 count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
270 pieces["distance"] = int(count_out) # total number of commits
275 def plus_or_dot(pieces):
276 """Return a + if we don't already have one, else return a ."""
277 if "+" in pieces.get("closest-tag", ""):
282 def render_pep440(pieces):
283 """Build up version string, with post-release "local version identifier".
285 Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
286 get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
289 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
291 if pieces["closest-tag"]:
292 rendered = pieces["closest-tag"]
293 if pieces["distance"] or pieces["dirty"]:
294 rendered += plus_or_dot(pieces)
295 rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
300 rendered = "0+untagged.%d.g%s" % (pieces["distance"],
307 def render_pep440_pre(pieces):
308 """TAG[.post.devDISTANCE] -- No -dirty.
311 1: no tags. 0.post.devDISTANCE
313 if pieces["closest-tag"]:
314 rendered = pieces["closest-tag"]
315 if pieces["distance"]:
316 rendered += ".post.dev%d" % pieces["distance"]
319 rendered = "0.post.dev%d" % pieces["distance"]
323 def render_pep440_post(pieces):
324 """TAG[.postDISTANCE[.dev0]+gHEX] .
326 The ".dev0" means dirty. Note that .dev0 sorts backwards
327 (a dirty tree will appear "older" than the corresponding clean one),
328 but you shouldn't be releasing software with -dirty anyways.
331 1: no tags. 0.postDISTANCE[.dev0]
333 if pieces["closest-tag"]:
334 rendered = pieces["closest-tag"]
335 if pieces["distance"] or pieces["dirty"]:
336 rendered += ".post%d" % pieces["distance"]
339 rendered += plus_or_dot(pieces)
340 rendered += "g%s" % pieces["short"]
343 rendered = "0.post%d" % pieces["distance"]
346 rendered += "+g%s" % pieces["short"]
350 def render_pep440_old(pieces):
351 """TAG[.postDISTANCE[.dev0]] .
353 The ".dev0" means dirty.
356 1: no tags. 0.postDISTANCE[.dev0]
358 if pieces["closest-tag"]:
359 rendered = pieces["closest-tag"]
360 if pieces["distance"] or pieces["dirty"]:
361 rendered += ".post%d" % pieces["distance"]
366 rendered = "0.post%d" % pieces["distance"]
372 def render_git_describe(pieces):
373 """TAG[-DISTANCE-gHEX][-dirty].
375 Like 'git describe --tags --dirty --always'.
378 1: no tags. HEX[-dirty] (note: no 'g' prefix)
380 if pieces["closest-tag"]:
381 rendered = pieces["closest-tag"]
382 if pieces["distance"]:
383 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
386 rendered = pieces["short"]
392 def render_git_describe_long(pieces):
393 """TAG-DISTANCE-gHEX[-dirty].
395 Like 'git describe --tags --dirty --always -long'.
396 The distance/hash is unconditional.
399 1: no tags. HEX[-dirty] (note: no 'g' prefix)
401 if pieces["closest-tag"]:
402 rendered = pieces["closest-tag"]
403 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
406 rendered = pieces["short"]
412 def render(pieces, style):
413 """Render the given version pieces into the requested style."""
415 return {"version": "unknown",
416 "full-revisionid": pieces.get("long"),
418 "error": pieces["error"]}
420 if not style or style == "default":
421 style = "pep440" # the default
423 if style == "pep440":
424 rendered = render_pep440(pieces)
425 elif style == "pep440-pre":
426 rendered = render_pep440_pre(pieces)
427 elif style == "pep440-post":
428 rendered = render_pep440_post(pieces)
429 elif style == "pep440-old":
430 rendered = render_pep440_old(pieces)
431 elif style == "git-describe":
432 rendered = render_git_describe(pieces)
433 elif style == "git-describe-long":
434 rendered = render_git_describe_long(pieces)
436 raise ValueError("unknown style '%s'" % style)
438 return {"version": rendered, "full-revisionid": pieces["long"],
439 "dirty": pieces["dirty"], "error": None}
443 """Get version information or return default if unable to do so."""
444 # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
445 # __file__, we can work backwards from there to the root. Some
446 # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
447 # case we can only use expanded keywords.
450 verbose = cfg.verbose
453 return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
455 except NotThisMethod:
459 root = os.path.realpath(__file__)
460 # versionfile_source is the relative path from the top of the source
461 # tree (where the .git directory might live) to this file. Invert
462 # this to find the root from __file__.
463 for i in cfg.versionfile_source.split('/'):
464 root = os.path.dirname(root)
466 return {"version": "0+unknown", "full-revisionid": None,
468 "error": "unable to find root of source tree"}
471 pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
472 return render(pieces, cfg.style)
473 except NotThisMethod:
477 if cfg.parentdir_prefix:
478 return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
479 except NotThisMethod:
482 return {"version": "0+unknown", "full-revisionid": None,
484 "error": "unable to compute version"}