Commit | Line | Data |
---|---|---|
56936af2 MJ |
1 | |
2 | # This file helps to compute a version number in source trees obtained from | |
3 | # git-archive tarball (such as those provided by githubs download-from-tag | |
4 | # feature). Distribution tarballs (built by setup.py sdist) and build | |
5 | # directories (produced by setup.py build) will contain a much shorter file | |
6 | # that just contains the computed version number. | |
7 | ||
8 | # This file is released into the public domain. Generated by | |
9 | # versioneer-0.15 (https://github.com/warner/python-versioneer) | |
10 | ||
11 | import errno | |
12 | import os | |
13 | import re | |
14 | import subprocess | |
15 | import sys | |
16 | ||
17 | ||
18 | def get_keywords(): | |
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 | |
22 | # get_keywords(). | |
23 | git_refnames = "$Format:%d$" | |
24 | git_full = "$Format:%H$" | |
25 | keywords = {"refnames": git_refnames, "full": git_full} | |
26 | return keywords | |
27 | ||
28 | ||
29 | class VersioneerConfig: | |
30 | pass | |
31 | ||
32 | ||
33 | def get_config(): | |
34 | # these strings are filled in when 'setup.py versioneer' creates | |
35 | # _version.py | |
36 | cfg = VersioneerConfig() | |
37 | cfg.VCS = "git" | |
38 | cfg.style = "pep440" | |
39 | cfg.tag_prefix = "v" | |
40 | cfg.parentdir_prefix = "lttnganalyses-" | |
41 | cfg.versionfile_source = "lttnganalyses/_version.py" | |
42 | cfg.verbose = False | |
43 | return cfg | |
44 | ||
45 | ||
46 | class NotThisMethod(Exception): | |
47 | pass | |
48 | ||
49 | ||
50 | LONG_VERSION_PY = {} | |
51 | HANDLERS = {} | |
52 | ||
53 | ||
54 | def register_vcs_handler(vcs, method): # decorator | |
55 | def decorate(f): | |
56 | if vcs not in HANDLERS: | |
57 | HANDLERS[vcs] = {} | |
58 | HANDLERS[vcs][method] = f | |
59 | return f | |
60 | return decorate | |
61 | ||
62 | ||
63 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): | |
64 | assert isinstance(commands, list) | |
65 | p = None | |
66 | for c in commands: | |
67 | try: | |
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 | |
72 | else None)) | |
73 | break | |
74 | except EnvironmentError: | |
75 | e = sys.exc_info()[1] | |
76 | if e.errno == errno.ENOENT: | |
77 | continue | |
78 | if verbose: | |
79 | print("unable to run %s" % dispcmd) | |
80 | print(e) | |
81 | return None | |
82 | else: | |
83 | if verbose: | |
84 | print("unable to find command, tried %s" % (commands,)) | |
85 | return None | |
86 | stdout = p.communicate()[0].strip() | |
87 | if sys.version_info[0] >= 3: | |
88 | stdout = stdout.decode() | |
89 | if p.returncode != 0: | |
90 | if verbose: | |
91 | print("unable to run %s (error)" % dispcmd) | |
92 | return None | |
93 | return stdout | |
94 | ||
95 | ||
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): | |
101 | if verbose: | |
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} | |
108 | ||
109 | ||
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 | |
115 | # _version.py. | |
116 | keywords = {} | |
117 | try: | |
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) | |
122 | if mo: | |
123 | keywords["refnames"] = mo.group(1) | |
124 | if line.strip().startswith("git_full ="): | |
125 | mo = re.search(r'=\s*"(.*)"', line) | |
126 | if mo: | |
127 | keywords["full"] = mo.group(1) | |
128 | f.close() | |
129 | except EnvironmentError: | |
130 | pass | |
131 | return keywords | |
132 | ||
133 | ||
134 | @register_vcs_handler("git", "keywords") | |
135 | def git_versions_from_keywords(keywords, tag_prefix, verbose): | |
136 | if not keywords: | |
137 | raise NotThisMethod("no keywords at all, weird") | |
138 | refnames = keywords["refnames"].strip() | |
139 | if refnames.startswith("$Format"): | |
140 | if verbose: | |
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. | |
146 | TAG = "tag: " | |
147 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) | |
148 | if not tags: | |
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)]) | |
157 | if verbose: | |
158 | print("discarding '%s', no digits" % ",".join(refs-tags)) | |
159 | if verbose: | |
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):] | |
165 | if verbose: | |
166 | print("picking %s" % r) | |
167 | return {"version": r, | |
168 | "full-revisionid": keywords["full"].strip(), | |
169 | "dirty": False, "error": None | |
170 | } | |
171 | # no suitable tags, so version is "0+unknown", but full hex is still there | |
172 | if verbose: | |
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"} | |
177 | ||
178 | ||
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. | |
185 | ||
186 | if not os.path.exists(os.path.join(root, ".git")): | |
187 | if verbose: | |
188 | print("no .git in %s" % root) | |
189 | raise NotThisMethod("no .git directory") | |
190 | ||
191 | GITS = ["git"] | |
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"], | |
198 | cwd=root) | |
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) | |
204 | if full_out is None: | |
205 | raise NotThisMethod("'git rev-parse' failed") | |
206 | full_out = full_out.strip() | |
207 | ||
208 | pieces = {} | |
209 | pieces["long"] = full_out | |
210 | pieces["short"] = full_out[:7] # maybe improved later | |
211 | pieces["error"] = None | |
212 | ||
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 | |
216 | ||
217 | # look for -dirty suffix | |
218 | dirty = git_describe.endswith("-dirty") | |
219 | pieces["dirty"] = dirty | |
220 | if dirty: | |
221 | git_describe = git_describe[:git_describe.rindex("-dirty")] | |
222 | ||
223 | # now we have TAG-NUM-gHEX or HEX | |
224 | ||
225 | if "-" in git_describe: | |
226 | # TAG-NUM-gHEX | |
227 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) | |
228 | if not mo: | |
229 | # unparseable. Maybe git-describe is misbehaving? | |
230 | pieces["error"] = ("unable to parse git-describe output: '%s'" | |
231 | % describe_out) | |
232 | return pieces | |
233 | ||
234 | # tag | |
235 | full_tag = mo.group(1) | |
236 | if not full_tag.startswith(tag_prefix): | |
237 | if verbose: | |
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)) | |
242 | return pieces | |
243 | pieces["closest-tag"] = full_tag[len(tag_prefix):] | |
244 | ||
245 | # distance: number of commits since tag | |
246 | pieces["distance"] = int(mo.group(2)) | |
247 | ||
248 | # commit: short hex revision ID | |
249 | pieces["short"] = mo.group(3) | |
250 | ||
251 | else: | |
252 | # HEX: no tags | |
253 | pieces["closest-tag"] = None | |
254 | count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], | |
255 | cwd=root) | |
256 | pieces["distance"] = int(count_out) # total number of commits | |
257 | ||
258 | return pieces | |
259 | ||
260 | ||
261 | def plus_or_dot(pieces): | |
262 | if "+" in pieces.get("closest-tag", ""): | |
263 | return "." | |
264 | return "+" | |
265 | ||
266 | ||
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 | |
271 | ||
272 | # exceptions: | |
273 | # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] | |
274 | ||
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"]) | |
280 | if pieces["dirty"]: | |
281 | rendered += ".dirty" | |
282 | else: | |
283 | # exception #1 | |
284 | rendered = "0+untagged.%d.g%s" % (pieces["distance"], | |
285 | pieces["short"]) | |
286 | if pieces["dirty"]: | |
287 | rendered += ".dirty" | |
288 | return rendered | |
289 | ||
290 | ||
291 | def render_pep440_pre(pieces): | |
292 | # TAG[.post.devDISTANCE] . No -dirty | |
293 | ||
294 | # exceptions: | |
295 | # 1: no tags. 0.post.devDISTANCE | |
296 | ||
297 | if pieces["closest-tag"]: | |
298 | rendered = pieces["closest-tag"] | |
299 | if pieces["distance"]: | |
300 | rendered += ".post.dev%d" % pieces["distance"] | |
301 | else: | |
302 | # exception #1 | |
303 | rendered = "0.post.dev%d" % pieces["distance"] | |
304 | return rendered | |
305 | ||
306 | ||
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 | |
311 | # -dirty anyways. | |
312 | ||
313 | # exceptions: | |
314 | # 1: no tags. 0.postDISTANCE[.dev0] | |
315 | ||
316 | if pieces["closest-tag"]: | |
317 | rendered = pieces["closest-tag"] | |
318 | if pieces["distance"] or pieces["dirty"]: | |
319 | rendered += ".post%d" % pieces["distance"] | |
320 | if pieces["dirty"]: | |
321 | rendered += ".dev0" | |
322 | rendered += plus_or_dot(pieces) | |
323 | rendered += "g%s" % pieces["short"] | |
324 | else: | |
325 | # exception #1 | |
326 | rendered = "0.post%d" % pieces["distance"] | |
327 | if pieces["dirty"]: | |
328 | rendered += ".dev0" | |
329 | rendered += "+g%s" % pieces["short"] | |
330 | return rendered | |
331 | ||
332 | ||
333 | def render_pep440_old(pieces): | |
334 | # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. | |
335 | ||
336 | # exceptions: | |
337 | # 1: no tags. 0.postDISTANCE[.dev0] | |
338 | ||
339 | if pieces["closest-tag"]: | |
340 | rendered = pieces["closest-tag"] | |
341 | if pieces["distance"] or pieces["dirty"]: | |
342 | rendered += ".post%d" % pieces["distance"] | |
343 | if pieces["dirty"]: | |
344 | rendered += ".dev0" | |
345 | else: | |
346 | # exception #1 | |
347 | rendered = "0.post%d" % pieces["distance"] | |
348 | if pieces["dirty"]: | |
349 | rendered += ".dev0" | |
350 | return rendered | |
351 | ||
352 | ||
353 | def render_git_describe(pieces): | |
354 | # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty | |
355 | # --always' | |
356 | ||
357 | # exceptions: | |
358 | # 1: no tags. HEX[-dirty] (note: no 'g' prefix) | |
359 | ||
360 | if pieces["closest-tag"]: | |
361 | rendered = pieces["closest-tag"] | |
362 | if pieces["distance"]: | |
363 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) | |
364 | else: | |
365 | # exception #1 | |
366 | rendered = pieces["short"] | |
367 | if pieces["dirty"]: | |
368 | rendered += "-dirty" | |
369 | return rendered | |
370 | ||
371 | ||
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. | |
375 | ||
376 | # exceptions: | |
377 | # 1: no tags. HEX[-dirty] (note: no 'g' prefix) | |
378 | ||
379 | if pieces["closest-tag"]: | |
380 | rendered = pieces["closest-tag"] | |
381 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) | |
382 | else: | |
383 | # exception #1 | |
384 | rendered = pieces["short"] | |
385 | if pieces["dirty"]: | |
386 | rendered += "-dirty" | |
387 | return rendered | |
388 | ||
389 | ||
390 | def render(pieces, style): | |
391 | if pieces["error"]: | |
392 | return {"version": "unknown", | |
393 | "full-revisionid": pieces.get("long"), | |
394 | "dirty": None, | |
395 | "error": pieces["error"]} | |
396 | ||
397 | if not style or style == "default": | |
398 | style = "pep440" # the default | |
399 | ||
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) | |
412 | else: | |
413 | raise ValueError("unknown style '%s'" % style) | |
414 | ||
415 | return {"version": rendered, "full-revisionid": pieces["long"], | |
416 | "dirty": pieces["dirty"], "error": None} | |
417 | ||
418 | ||
419 | def get_versions(): | |
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. | |
424 | ||
425 | cfg = get_config() | |
426 | verbose = cfg.verbose | |
427 | ||
428 | try: | |
429 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, | |
430 | verbose) | |
431 | except NotThisMethod: | |
432 | pass | |
433 | ||
434 | try: | |
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) | |
441 | except NameError: | |
442 | return {"version": "0+unknown", "full-revisionid": None, | |
443 | "dirty": None, | |
444 | "error": "unable to find root of source tree"} | |
445 | ||
446 | try: | |
447 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) | |
448 | return render(pieces, cfg.style) | |
449 | except NotThisMethod: | |
450 | pass | |
451 | ||
452 | try: | |
453 | if cfg.parentdir_prefix: | |
454 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) | |
455 | except NotThisMethod: | |
456 | pass | |
457 | ||
458 | return {"version": "0+unknown", "full-revisionid": None, | |
459 | "dirty": None, | |
460 | "error": "unable to compute version"} |