... | ... |
@@ -1,520 +1,169 @@ |
1 |
- |
|
2 |
-# This file helps to compute a version number in source trees obtained from |
|
3 |
-# git-archive tarball (such as those provided by githubs download-from-tag |
|
4 |
-# feature). Distribution tarballs (built by setup.py sdist) and build |
|
5 |
-# directories (produced by setup.py build) will contain a much shorter file |
|
6 |
-# that just contains the computed version number. |
|
7 |
- |
|
8 |
-# This file is released into the public domain. Generated by |
|
9 |
-# versioneer-0.18 (https://github.com/warner/python-versioneer) |
|
10 |
- |
|
11 |
-"""Git implementation of _version.py.""" |
|
12 |
- |
|
13 |
-import errno |
|
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+# This file is part of 'miniver': https://github.com/jbweston/miniver |
|
3 |
+# |
|
4 |
+from collections import namedtuple |
|
14 | 5 |
import os |
15 |
-import re |
|
16 | 6 |
import subprocess |
17 | 7 |
import sys |
18 | 8 |
|
9 |
+from distutils.command.build import build as build_orig |
|
10 |
+from setuptools.command.sdist import sdist as sdist_orig |
|
19 | 11 |
|
20 |
-def get_keywords(): |
|
21 |
- """Get the keywords needed to look up the version information.""" |
|
22 |
- # these strings will be replaced by git during git-archive. |
|
23 |
- # setup.py/versioneer.py will grep for the variable names, so they must |
|
24 |
- # each be defined on a line of their own. _version.py will just call |
|
25 |
- # get_keywords(). |
|
26 |
- git_refnames = "$Format:%d$" |
|
27 |
- git_full = "$Format:%H$" |
|
28 |
- git_date = "$Format:%ci$" |
|
29 |
- keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} |
|
30 |
- return keywords |
|
12 |
+Version = namedtuple('Version', ('release', 'dev', 'labels')) |
|
31 | 13 |
|
14 |
+# No public API |
|
15 |
+__all__ = [] |
|
32 | 16 |
|
33 |
-class VersioneerConfig: |
|
34 |
- """Container for Versioneer configuration parameters.""" |
|
17 |
+package_root = os.path.dirname(os.path.realpath(__file__)) |
|
18 |
+package_name = os.path.basename(package_root) |
|
19 |
+distr_root = os.path.dirname(package_root) |
|
35 | 20 |
|
21 |
+STATIC_VERSION_FILE = '_static_version.py' |
|
36 | 22 |
|
37 |
-def get_config(): |
|
38 |
- """Create, populate and return the VersioneerConfig() object.""" |
|
39 |
- # these strings are filled in when 'setup.py versioneer' creates |
|
40 |
- # _version.py |
|
41 |
- cfg = VersioneerConfig() |
|
42 |
- cfg.VCS = "git" |
|
43 |
- cfg.style = "pep440" |
|
44 |
- cfg.tag_prefix = "" |
|
45 |
- cfg.parentdir_prefix = "nord-" |
|
46 |
- cfg.versionfile_source = "nord/_version.py" |
|
47 |
- cfg.verbose = False |
|
48 |
- return cfg |
|
49 | 23 |
|
24 |
+def get_version(version_file=STATIC_VERSION_FILE): |
|
25 |
+ version_info = {} |
|
26 |
+ with open(os.path.join(package_root, version_file), 'rb') as f: |
|
27 |
+ exec(f.read(), {}, version_info) |
|
28 |
+ version = version_info['version'] |
|
29 |
+ version_is_from_git = (version == "__use_git__") |
|
30 |
+ if version_is_from_git: |
|
31 |
+ version = get_version_from_git() |
|
32 |
+ if not version: |
|
33 |
+ version = get_version_from_git_archive(version_info) |
|
34 |
+ if not version: |
|
35 |
+ version = Version("unknown", None, None) |
|
36 |
+ return semver_format(version) |
|
37 |
+ else: |
|
38 |
+ return version |
|
50 | 39 |
|
51 |
-class NotThisMethod(Exception): |
|
52 |
- """Exception raised if a method is not valid for the current scenario.""" |
|
53 | 40 |
|
41 |
+def semver_format(version_info): |
|
42 |
+ release, dev, labels = version_info |
|
54 | 43 |
|
55 |
-LONG_VERSION_PY = {} |
|
56 |
-HANDLERS = {} |
|
44 |
+ version_parts = [release] |
|
45 |
+ if dev: |
|
46 |
+ if release.endswith('-dev'): |
|
47 |
+ version_parts.append(dev) |
|
48 |
+ elif release.contains('-'): |
|
49 |
+ version_parts.append('.dev{}'.format(dev)) |
|
50 |
+ else: |
|
51 |
+ version_parts.append('-dev{}'.format(dev)) |
|
57 | 52 |
|
53 |
+ if labels: |
|
54 |
+ version_parts.append('+') |
|
55 |
+ version_parts.append(".".join(labels)) |
|
58 | 56 |
|
59 |
-def register_vcs_handler(vcs, method): # decorator |
|
60 |
- """Decorator to mark a method as the handler for a particular VCS.""" |
|
61 |
- def decorate(f): |
|
62 |
- """Store f in HANDLERS[vcs][method].""" |
|
63 |
- if vcs not in HANDLERS: |
|
64 |
- HANDLERS[vcs] = {} |
|
65 |
- HANDLERS[vcs][method] = f |
|
66 |
- return f |
|
67 |
- return decorate |
|
57 |
+ return "".join(version_parts) |
|
68 | 58 |
|
69 | 59 |
|
70 |
-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, |
|
71 |
- env=None): |
|
72 |
- """Call the given command(s).""" |
|
73 |
- assert isinstance(commands, list) |
|
74 |
- p = None |
|
75 |
- for c in commands: |
|
60 |
+def get_version_from_git(): |
|
61 |
+ try: |
|
62 |
+ p = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'], |
|
63 |
+ cwd=distr_root, |
|
64 |
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|
65 |
+ except OSError: |
|
66 |
+ return |
|
67 |
+ if p.wait() != 0: |
|
68 |
+ return |
|
69 |
+ if not os.path.samefile(p.communicate()[0].decode().rstrip('\n'), |
|
70 |
+ distr_root): |
|
71 |
+ # The top-level directory of the current Git repository is not the same |
|
72 |
+ # as the root directory of the Kwant distribution: do not extract the |
|
73 |
+ # version from Git. |
|
74 |
+ return |
|
75 |
+ |
|
76 |
+ # git describe --first-parent does not take into account tags from branches |
|
77 |
+ # that were merged-in. |
|
78 |
+ for opts in [['--first-parent'], []]: |
|
76 | 79 |
try: |
77 |
- dispcmd = str([c] + args) |
|
78 |
- # remember shell=False, so use git.cmd on windows, not just git |
|
79 |
- p = subprocess.Popen([c] + args, cwd=cwd, env=env, |
|
80 |
- stdout=subprocess.PIPE, |
|
81 |
- stderr=(subprocess.PIPE if hide_stderr |
|
82 |
- else None)) |
|
80 |
+ p = subprocess.Popen(['git', 'describe', '--long'] + opts, |
|
81 |
+ cwd=distr_root, |
|
82 |
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|
83 |
+ except OSError: |
|
84 |
+ return |
|
85 |
+ if p.wait() == 0: |
|
83 | 86 |
break |
84 |
- except EnvironmentError: |
|
85 |
- e = sys.exc_info()[1] |
|
86 |
- if e.errno == errno.ENOENT: |
|
87 |
- continue |
|
88 |
- if verbose: |
|
89 |
- print("unable to run %s" % dispcmd) |
|
90 |
- print(e) |
|
91 |
- return None, None |
|
92 | 87 |
else: |
93 |
- if verbose: |
|
94 |
- print("unable to find command, tried %s" % (commands,)) |
|
95 |
- return None, None |
|
96 |
- stdout = p.communicate()[0].strip() |
|
97 |
- if sys.version_info[0] >= 3: |
|
98 |
- stdout = stdout.decode() |
|
99 |
- if p.returncode != 0: |
|
100 |
- if verbose: |
|
101 |
- print("unable to run %s (error)" % dispcmd) |
|
102 |
- print("stdout was %s" % stdout) |
|
103 |
- return None, p.returncode |
|
104 |
- return stdout, p.returncode |
|
105 |
- |
|
106 |
- |
|
107 |
-def versions_from_parentdir(parentdir_prefix, root, verbose): |
|
108 |
- """Try to determine the version from the parent directory name. |
|
109 |
- |
|
110 |
- Source tarballs conventionally unpack into a directory that includes both |
|
111 |
- the project name and a version string. We will also support searching up |
|
112 |
- two directory levels for an appropriately named parent directory |
|
113 |
- """ |
|
114 |
- rootdirs = [] |
|
115 |
- |
|
116 |
- for i in range(3): |
|
117 |
- dirname = os.path.basename(root) |
|
118 |
- if dirname.startswith(parentdir_prefix): |
|
119 |
- return {"version": dirname[len(parentdir_prefix):], |
|
120 |
- "full-revisionid": None, |
|
121 |
- "dirty": False, "error": None, "date": None} |
|
122 |
- else: |
|
123 |
- rootdirs.append(root) |
|
124 |
- root = os.path.dirname(root) # up a level |
|
125 |
- |
|
126 |
- if verbose: |
|
127 |
- print("Tried directories %s but none started with prefix %s" % |
|
128 |
- (str(rootdirs), parentdir_prefix)) |
|
129 |
- raise NotThisMethod("rootdir doesn't start with parentdir_prefix") |
|
130 |
- |
|
131 |
- |
|
132 |
-@register_vcs_handler("git", "get_keywords") |
|
133 |
-def git_get_keywords(versionfile_abs): |
|
134 |
- """Extract version information from the given file.""" |
|
135 |
- # the code embedded in _version.py can just fetch the value of these |
|
136 |
- # keywords. When used from setup.py, we don't want to import _version.py, |
|
137 |
- # so we do it with a regexp instead. This function is not used from |
|
138 |
- # _version.py. |
|
139 |
- keywords = {} |
|
140 |
- try: |
|
141 |
- f = open(versionfile_abs, "r") |
|
142 |
- for line in f.readlines(): |
|
143 |
- if line.strip().startswith("git_refnames ="): |
|
144 |
- mo = re.search(r'=\s*"(.*)"', line) |
|
145 |
- if mo: |
|
146 |
- keywords["refnames"] = mo.group(1) |
|
147 |
- if line.strip().startswith("git_full ="): |
|
148 |
- mo = re.search(r'=\s*"(.*)"', line) |
|
149 |
- if mo: |
|
150 |
- keywords["full"] = mo.group(1) |
|
151 |
- if line.strip().startswith("git_date ="): |
|
152 |
- mo = re.search(r'=\s*"(.*)"', line) |
|
153 |
- if mo: |
|
154 |
- keywords["date"] = mo.group(1) |
|
155 |
- f.close() |
|
156 |
- except EnvironmentError: |
|
157 |
- pass |
|
158 |
- return keywords |
|
159 |
- |
|
160 |
- |
|
161 |
-@register_vcs_handler("git", "keywords") |
|
162 |
-def git_versions_from_keywords(keywords, tag_prefix, verbose): |
|
163 |
- """Get version information from git keywords.""" |
|
164 |
- if not keywords: |
|
165 |
- raise NotThisMethod("no keywords at all, weird") |
|
166 |
- date = keywords.get("date") |
|
167 |
- if date is not None: |
|
168 |
- # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant |
|
169 |
- # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 |
|
170 |
- # -like" string, which we must then edit to make compliant), because |
|
171 |
- # it's been around since git-1.5.3, and it's too difficult to |
|
172 |
- # discover which version we're using, or to work around using an |
|
173 |
- # older one. |
|
174 |
- date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) |
|
175 |
- refnames = keywords["refnames"].strip() |
|
176 |
- if refnames.startswith("$Format"): |
|
177 |
- if verbose: |
|
178 |
- print("keywords are unexpanded, not using") |
|
179 |
- raise NotThisMethod("unexpanded keywords, not a git-archive tarball") |
|
180 |
- refs = set([r.strip() for r in refnames.strip("()").split(",")]) |
|
181 |
- # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of |
|
182 |
- # just "foo-1.0". If we see a "tag: " prefix, prefer those. |
|
183 |
- TAG = "tag: " |
|
184 |
- tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) |
|
185 |
- if not tags: |
|
186 |
- # Either we're using git < 1.8.3, or there really are no tags. We use |
|
187 |
- # a heuristic: assume all version tags have a digit. The old git %d |
|
188 |
- # expansion behaves like git log --decorate=short and strips out the |
|
189 |
- # refs/heads/ and refs/tags/ prefixes that would let us distinguish |
|
190 |
- # between branches and tags. By ignoring refnames without digits, we |
|
191 |
- # filter out many common branch names like "release" and |
|
192 |
- # "stabilization", as well as "HEAD" and "master". |
|
193 |
- tags = set([r for r in refs if re.search(r'\d', r)]) |
|
194 |
- if verbose: |
|
195 |
- print("discarding '%s', no digits" % ",".join(refs - tags)) |
|
196 |
- if verbose: |
|
197 |
- print("likely tags: %s" % ",".join(sorted(tags))) |
|
198 |
- for ref in sorted(tags): |
|
199 |
- # sorting will prefer e.g. "2.0" over "2.0rc1" |
|
200 |
- if ref.startswith(tag_prefix): |
|
201 |
- r = ref[len(tag_prefix):] |
|
202 |
- if verbose: |
|
203 |
- print("picking %s" % r) |
|
204 |
- return {"version": r, |
|
205 |
- "full-revisionid": keywords["full"].strip(), |
|
206 |
- "dirty": False, "error": None, |
|
207 |
- "date": date} |
|
208 |
- # no suitable tags, so version is "0+unknown", but full hex is still there |
|
209 |
- if verbose: |
|
210 |
- print("no suitable tags, using unknown + full revision id") |
|
211 |
- return {"version": "0+unknown", |
|
212 |
- "full-revisionid": keywords["full"].strip(), |
|
213 |
- "dirty": False, "error": "no suitable tags", "date": None} |
|
214 |
- |
|
215 |
- |
|
216 |
-@register_vcs_handler("git", "pieces_from_vcs") |
|
217 |
-def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): |
|
218 |
- """Get version from 'git describe' in the root of the source tree. |
|
219 |
- |
|
220 |
- This only gets called if the git-archive 'subst' keywords were *not* |
|
221 |
- expanded, and _version.py hasn't already been rewritten with a short |
|
222 |
- version string, meaning we're inside a checked out source tree. |
|
223 |
- """ |
|
224 |
- GITS = ["git"] |
|
225 |
- if sys.platform == "win32": |
|
226 |
- GITS = ["git.cmd", "git.exe"] |
|
227 |
- |
|
228 |
- out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, |
|
229 |
- hide_stderr=True) |
|
230 |
- if rc != 0: |
|
231 |
- if verbose: |
|
232 |
- print("Directory %s not under git control" % root) |
|
233 |
- raise NotThisMethod("'git rev-parse --git-dir' returned error") |
|
234 |
- |
|
235 |
- # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] |
|
236 |
- # if there isn't one, this yields HEX[-dirty] (no NUM) |
|
237 |
- describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", |
|
238 |
- "--always", "--long", |
|
239 |
- "--match", "%s*" % tag_prefix], |
|
240 |
- cwd=root) |
|
241 |
- # --long was added in git-1.5.5 |
|
242 |
- if describe_out is None: |
|
243 |
- raise NotThisMethod("'git describe' failed") |
|
244 |
- describe_out = describe_out.strip() |
|
245 |
- full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) |
|
246 |
- if full_out is None: |
|
247 |
- raise NotThisMethod("'git rev-parse' failed") |
|
248 |
- full_out = full_out.strip() |
|
249 |
- |
|
250 |
- pieces = {} |
|
251 |
- pieces["long"] = full_out |
|
252 |
- pieces["short"] = full_out[:7] # maybe improved later |
|
253 |
- pieces["error"] = None |
|
254 |
- |
|
255 |
- # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] |
|
256 |
- # TAG might have hyphens. |
|
257 |
- git_describe = describe_out |
|
258 |
- |
|
259 |
- # look for -dirty suffix |
|
260 |
- dirty = git_describe.endswith("-dirty") |
|
261 |
- pieces["dirty"] = dirty |
|
262 |
- if dirty: |
|
263 |
- git_describe = git_describe[:git_describe.rindex("-dirty")] |
|
264 |
- |
|
265 |
- # now we have TAG-NUM-gHEX or HEX |
|
266 |
- |
|
267 |
- if "-" in git_describe: |
|
268 |
- # TAG-NUM-gHEX |
|
269 |
- mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) |
|
270 |
- if not mo: |
|
271 |
- # unparseable. Maybe git-describe is misbehaving? |
|
272 |
- pieces["error"] = ("unable to parse git-describe output: '%s'" |
|
273 |
- % describe_out) |
|
274 |
- return pieces |
|
275 |
- |
|
276 |
- # tag |
|
277 |
- full_tag = mo.group(1) |
|
278 |
- if not full_tag.startswith(tag_prefix): |
|
279 |
- if verbose: |
|
280 |
- fmt = "tag '%s' doesn't start with prefix '%s'" |
|
281 |
- print(fmt % (full_tag, tag_prefix)) |
|
282 |
- pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" |
|
283 |
- % (full_tag, tag_prefix)) |
|
284 |
- return pieces |
|
285 |
- pieces["closest-tag"] = full_tag[len(tag_prefix):] |
|
286 |
- |
|
287 |
- # distance: number of commits since tag |
|
288 |
- pieces["distance"] = int(mo.group(2)) |
|
289 |
- |
|
290 |
- # commit: short hex revision ID |
|
291 |
- pieces["short"] = mo.group(3) |
|
88 |
+ return |
|
89 |
+ description = p.communicate()[0].decode().strip('v').rstrip('\n') |
|
292 | 90 |
|
91 |
+ release, dev, git = description.rsplit('-', 2) |
|
92 |
+ labels = [] |
|
93 |
+ if dev == "0": |
|
94 |
+ dev = None |
|
293 | 95 |
else: |
294 |
- # HEX: no tags |
|
295 |
- pieces["closest-tag"] = None |
|
296 |
- count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], |
|
297 |
- cwd=root) |
|
298 |
- pieces["distance"] = int(count_out) # total number of commits |
|
299 |
- |
|
300 |
- # commit date: see ISO-8601 comment in git_versions_from_keywords() |
|
301 |
- date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], |
|
302 |
- cwd=root)[0].strip() |
|
303 |
- pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) |
|
304 |
- |
|
305 |
- return pieces |
|
306 |
- |
|
307 |
- |
|
308 |
-def plus_or_dot(pieces): |
|
309 |
- """Return a + if we don't already have one, else return a .""" |
|
310 |
- if "+" in pieces.get("closest-tag", ""): |
|
311 |
- return "." |
|
312 |
- return "+" |
|
313 |
- |
|
314 |
- |
|
315 |
-def render_pep440(pieces): |
|
316 |
- """Build up version string, with post-release "local version identifier". |
|
317 |
- |
|
318 |
- Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you |
|
319 |
- get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty |
|
320 |
- |
|
321 |
- Exceptions: |
|
322 |
- 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] |
|
323 |
- """ |
|
324 |
- if pieces["closest-tag"]: |
|
325 |
- rendered = pieces["closest-tag"] |
|
326 |
- if pieces["distance"] or pieces["dirty"]: |
|
327 |
- rendered += plus_or_dot(pieces) |
|
328 |
- rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) |
|
329 |
- if pieces["dirty"]: |
|
330 |
- rendered += ".dirty" |
|
331 |
- else: |
|
332 |
- # exception #1 |
|
333 |
- rendered = "0+untagged.%d.g%s" % (pieces["distance"], |
|
334 |
- pieces["short"]) |
|
335 |
- if pieces["dirty"]: |
|
336 |
- rendered += ".dirty" |
|
337 |
- return rendered |
|
338 |
- |
|
339 |
- |
|
340 |
-def render_pep440_pre(pieces): |
|
341 |
- """TAG[.post.devDISTANCE] -- No -dirty. |
|
342 |
- |
|
343 |
- Exceptions: |
|
344 |
- 1: no tags. 0.post.devDISTANCE |
|
345 |
- """ |
|
346 |
- if pieces["closest-tag"]: |
|
347 |
- rendered = pieces["closest-tag"] |
|
348 |
- if pieces["distance"]: |
|
349 |
- rendered += ".post.dev%d" % pieces["distance"] |
|
350 |
- else: |
|
351 |
- # exception #1 |
|
352 |
- rendered = "0.post.dev%d" % pieces["distance"] |
|
353 |
- return rendered |
|
354 |
- |
|
355 |
- |
|
356 |
-def render_pep440_post(pieces): |
|
357 |
- """TAG[.postDISTANCE[.dev0]+gHEX] . |
|
358 |
- |
|
359 |
- The ".dev0" means dirty. Note that .dev0 sorts backwards |
|
360 |
- (a dirty tree will appear "older" than the corresponding clean one), |
|
361 |
- but you shouldn't be releasing software with -dirty anyways. |
|
362 |
- |
|
363 |
- Exceptions: |
|
364 |
- 1: no tags. 0.postDISTANCE[.dev0] |
|
365 |
- """ |
|
366 |
- if pieces["closest-tag"]: |
|
367 |
- rendered = pieces["closest-tag"] |
|
368 |
- if pieces["distance"] or pieces["dirty"]: |
|
369 |
- rendered += ".post%d" % pieces["distance"] |
|
370 |
- if pieces["dirty"]: |
|
371 |
- rendered += ".dev0" |
|
372 |
- rendered += plus_or_dot(pieces) |
|
373 |
- rendered += "g%s" % pieces["short"] |
|
374 |
- else: |
|
375 |
- # exception #1 |
|
376 |
- rendered = "0.post%d" % pieces["distance"] |
|
377 |
- if pieces["dirty"]: |
|
378 |
- rendered += ".dev0" |
|
379 |
- rendered += "+g%s" % pieces["short"] |
|
380 |
- return rendered |
|
381 |
- |
|
382 |
- |
|
383 |
-def render_pep440_old(pieces): |
|
384 |
- """TAG[.postDISTANCE[.dev0]] . |
|
385 |
- |
|
386 |
- The ".dev0" means dirty. |
|
387 |
- |
|
388 |
- Eexceptions: |
|
389 |
- 1: no tags. 0.postDISTANCE[.dev0] |
|
390 |
- """ |
|
391 |
- if pieces["closest-tag"]: |
|
392 |
- rendered = pieces["closest-tag"] |
|
393 |
- if pieces["distance"] or pieces["dirty"]: |
|
394 |
- rendered += ".post%d" % pieces["distance"] |
|
395 |
- if pieces["dirty"]: |
|
396 |
- rendered += ".dev0" |
|
397 |
- else: |
|
398 |
- # exception #1 |
|
399 |
- rendered = "0.post%d" % pieces["distance"] |
|
400 |
- if pieces["dirty"]: |
|
401 |
- rendered += ".dev0" |
|
402 |
- return rendered |
|
403 |
- |
|
96 |
+ labels.append(git) |
|
404 | 97 |
|
405 |
-def render_git_describe(pieces): |
|
406 |
- """TAG[-DISTANCE-gHEX][-dirty]. |
|
407 |
- |
|
408 |
- Like 'git describe --tags --dirty --always'. |
|
409 |
- |
|
410 |
- Exceptions: |
|
411 |
- 1: no tags. HEX[-dirty] (note: no 'g' prefix) |
|
412 |
- """ |
|
413 |
- if pieces["closest-tag"]: |
|
414 |
- rendered = pieces["closest-tag"] |
|
415 |
- if pieces["distance"]: |
|
416 |
- rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) |
|
98 |
+ try: |
|
99 |
+ p = subprocess.Popen(['git', 'diff', '--quiet'], cwd=distr_root) |
|
100 |
+ except OSError: |
|
101 |
+ labels.append('confused') # This should never happen. |
|
417 | 102 |
else: |
418 |
- # exception #1 |
|
419 |
- rendered = pieces["short"] |
|
420 |
- if pieces["dirty"]: |
|
421 |
- rendered += "-dirty" |
|
422 |
- return rendered |
|
423 |
- |
|
103 |
+ if p.wait() == 1: |
|
104 |
+ labels.append('dirty') |
|
424 | 105 |
|
425 |
-def render_git_describe_long(pieces): |
|
426 |
- """TAG-DISTANCE-gHEX[-dirty]. |
|
106 |
+ return Version(release, dev, labels) |
|
427 | 107 |
|
428 |
- Like 'git describe --tags --dirty --always -long'. |
|
429 |
- The distance/hash is unconditional. |
|
430 | 108 |
|
431 |
- Exceptions: |
|
432 |
- 1: no tags. HEX[-dirty] (note: no 'g' prefix) |
|
433 |
- """ |
|
434 |
- if pieces["closest-tag"]: |
|
435 |
- rendered = pieces["closest-tag"] |
|
436 |
- rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) |
|
437 |
- else: |
|
438 |
- # exception #1 |
|
439 |
- rendered = pieces["short"] |
|
440 |
- if pieces["dirty"]: |
|
441 |
- rendered += "-dirty" |
|
442 |
- return rendered |
|
443 |
- |
|
444 |
- |
|
445 |
-def render(pieces, style): |
|
446 |
- """Render the given version pieces into the requested style.""" |
|
447 |
- if pieces["error"]: |
|
448 |
- return {"version": "unknown", |
|
449 |
- "full-revisionid": pieces.get("long"), |
|
450 |
- "dirty": None, |
|
451 |
- "error": pieces["error"], |
|
452 |
- "date": None} |
|
453 |
- |
|
454 |
- if not style or style == "default": |
|
455 |
- style = "pep440" # the default |
|
456 |
- |
|
457 |
- if style == "pep440": |
|
458 |
- rendered = render_pep440(pieces) |
|
459 |
- elif style == "pep440-pre": |
|
460 |
- rendered = render_pep440_pre(pieces) |
|
461 |
- elif style == "pep440-post": |
|
462 |
- rendered = render_pep440_post(pieces) |
|
463 |
- elif style == "pep440-old": |
|
464 |
- rendered = render_pep440_old(pieces) |
|
465 |
- elif style == "git-describe": |
|
466 |
- rendered = render_git_describe(pieces) |
|
467 |
- elif style == "git-describe-long": |
|
468 |
- rendered = render_git_describe_long(pieces) |
|
109 |
+# TODO: change this logic when there is a git pretty-format |
|
110 |
+# that gives the same output as 'git describe'. |
|
111 |
+# Currently we can only tell the tag the current commit is |
|
112 |
+# pointing to, or its hash (with no version info) |
|
113 |
+# if it is not tagged. |
|
114 |
+def get_version_from_git_archive(version_info): |
|
115 |
+ try: |
|
116 |
+ refnames = version_info['refnames'] |
|
117 |
+ git_hash = version_info['git_hash'] |
|
118 |
+ except KeyError: |
|
119 |
+ # These fields are not present if we are running from an sdist. |
|
120 |
+ # Execution should never reach here, though |
|
121 |
+ return None |
|
122 |
+ |
|
123 |
+ if git_hash.startswith('$Format') or refnames.startswith('$Format'): |
|
124 |
+ # variables not expanded during 'git archive' |
|
125 |
+ return None |
|
126 |
+ |
|
127 |
+ VTAG = 'tag: v' |
|
128 |
+ refs = set(r.strip() for r in refnames.split(",")) |
|
129 |
+ version_tags = set(r[len(VTAG):] for r in refs if r.startswith(VTAG)) |
|
130 |
+ if version_tags: |
|
131 |
+ release, *_ = sorted(version_tags) # prefer e.g. "2.0" over "2.0rc1" |
|
132 |
+ return Version(release, dev=None, labels=None) |
|
469 | 133 |
else: |
470 |
- raise ValueError("unknown style '%s'" % style) |
|
471 |
- |
|
472 |
- return {"version": rendered, "full-revisionid": pieces["long"], |
|
473 |
- "dirty": pieces["dirty"], "error": None, |
|
474 |
- "date": pieces.get("date")} |
|
134 |
+ return Version('unknown', dev=None, labels=[f'g{git_hash}']) |
|
475 | 135 |
|
476 | 136 |
|
477 |
-def get_versions(): |
|
478 |
- """Get version information or return default if unable to do so.""" |
|
479 |
- # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have |
|
480 |
- # __file__, we can work backwards from there to the root. Some |
|
481 |
- # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which |
|
482 |
- # case we can only use expanded keywords. |
|
137 |
+__version__ = get_version() |
|
483 | 138 |
|
484 |
- cfg = get_config() |
|
485 |
- verbose = cfg.verbose |
|
139 |
+# The following section defines a module global 'cmdclass', |
|
140 |
+# which can be used from setup.py. The 'package_name' and |
|
141 |
+# '__version__' module globals are used (but not modified). |
|
486 | 142 |
|
143 |
+def _write_version(fname): |
|
144 |
+ # This could be a hard link, so try to delete it first. Is there any way |
|
145 |
+ # to do this atomically together with opening? |
|
487 | 146 |
try: |
488 |
- return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, |
|
489 |
- verbose) |
|
490 |
- except NotThisMethod: |
|
147 |
+ os.remove(fname) |
|
148 |
+ except OSError: |
|
491 | 149 |
pass |
150 |
+ with open(fname, 'w') as f: |
|
151 |
+ f.write("# This file has been created by setup.py.\n" |
|
152 |
+ "version = '{}'\n".format(__version__)) |
|
492 | 153 |
|
493 |
- try: |
|
494 |
- root = os.path.realpath(__file__) |
|
495 |
- # versionfile_source is the relative path from the top of the source |
|
496 |
- # tree (where the .git directory might live) to this file. Invert |
|
497 |
- # this to find the root from __file__. |
|
498 |
- for i in cfg.versionfile_source.split('/'): |
|
499 |
- root = os.path.dirname(root) |
|
500 |
- except NameError: |
|
501 |
- return {"version": "0+unknown", "full-revisionid": None, |
|
502 |
- "dirty": None, |
|
503 |
- "error": "unable to find root of source tree", |
|
504 |
- "date": None} |
|
505 | 154 |
|
506 |
- try: |
|
507 |
- pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) |
|
508 |
- return render(pieces, cfg.style) |
|
509 |
- except NotThisMethod: |
|
510 |
- pass |
|
155 |
+class _build(build_orig): |
|
156 |
+ def run(self): |
|
157 |
+ super().run() |
|
158 |
+ _write_version(os.path.join(self.build_lib, package_name, |
|
159 |
+ STATIC_VERSION_FILE)) |
|
160 |
+ |
|
161 |
+ |
|
162 |
+class _sdist(sdist_orig): |
|
163 |
+ def make_release_tree(self, base_dir, files): |
|
164 |
+ super().make_release_tree(base_dir, files) |
|
165 |
+ _write_version(os.path.join(base_dir, package_name, |
|
166 |
+ STATIC_VERSION_FILE)) |
|
511 | 167 |
|
512 |
- try: |
|
513 |
- if cfg.parentdir_prefix: |
|
514 |
- return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) |
|
515 |
- except NotThisMethod: |
|
516 |
- pass |
|
517 | 168 |
|
518 |
- return {"version": "0+unknown", "full-revisionid": None, |
|
519 |
- "dirty": None, |
|
520 |
- "error": "unable to compute version", "date": None} |
|
169 |
+cmdclass = dict(sdist=_sdist, build=_build) |
Versioneer is quite big, but it is the simplest solution in terms
of installation.
Closes #10
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,520 @@ |
1 |
+ |
|
2 |
+# This file helps to compute a version number in source trees obtained from |
|
3 |
+# git-archive tarball (such as those provided by githubs download-from-tag |
|
4 |
+# feature). Distribution tarballs (built by setup.py sdist) and build |
|
5 |
+# directories (produced by setup.py build) will contain a much shorter file |
|
6 |
+# that just contains the computed version number. |
|
7 |
+ |
|
8 |
+# This file is released into the public domain. Generated by |
|
9 |
+# versioneer-0.18 (https://github.com/warner/python-versioneer) |
|
10 |
+ |
|
11 |
+"""Git implementation of _version.py.""" |
|
12 |
+ |
|
13 |
+import errno |
|
14 |
+import os |
|
15 |
+import re |
|
16 |
+import subprocess |
|
17 |
+import sys |
|
18 |
+ |
|
19 |
+ |
|
20 |
+def get_keywords(): |
|
21 |
+ """Get the keywords needed to look up the version information.""" |
|
22 |
+ # these strings will be replaced by git during git-archive. |
|
23 |
+ # setup.py/versioneer.py will grep for the variable names, so they must |
|
24 |
+ # each be defined on a line of their own. _version.py will just call |
|
25 |
+ # get_keywords(). |
|
26 |
+ git_refnames = "$Format:%d$" |
|
27 |
+ git_full = "$Format:%H$" |
|
28 |
+ git_date = "$Format:%ci$" |
|
29 |
+ keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} |
|
30 |
+ return keywords |
|
31 |
+ |
|
32 |
+ |
|
33 |
+class VersioneerConfig: |
|
34 |
+ """Container for Versioneer configuration parameters.""" |
|
35 |
+ |
|
36 |
+ |
|
37 |
+def get_config(): |
|
38 |
+ """Create, populate and return the VersioneerConfig() object.""" |
|
39 |
+ # these strings are filled in when 'setup.py versioneer' creates |
|
40 |
+ # _version.py |
|
41 |
+ cfg = VersioneerConfig() |
|
42 |
+ cfg.VCS = "git" |
|
43 |
+ cfg.style = "pep440" |
|
44 |
+ cfg.tag_prefix = "" |
|
45 |
+ cfg.parentdir_prefix = "nord-" |
|
46 |
+ cfg.versionfile_source = "nord/_version.py" |
|
47 |
+ cfg.verbose = False |
|
48 |
+ return cfg |
|
49 |
+ |
|
50 |
+ |
|
51 |
+class NotThisMethod(Exception): |
|
52 |
+ """Exception raised if a method is not valid for the current scenario.""" |
|
53 |
+ |
|
54 |
+ |
|
55 |
+LONG_VERSION_PY = {} |
|
56 |
+HANDLERS = {} |
|
57 |
+ |
|
58 |
+ |
|
59 |
+def register_vcs_handler(vcs, method): # decorator |
|
60 |
+ """Decorator to mark a method as the handler for a particular VCS.""" |
|
61 |
+ def decorate(f): |
|
62 |
+ """Store f in HANDLERS[vcs][method].""" |
|
63 |
+ if vcs not in HANDLERS: |
|
64 |
+ HANDLERS[vcs] = {} |
|
65 |
+ HANDLERS[vcs][method] = f |
|
66 |
+ return f |
|
67 |
+ return decorate |
|
68 |
+ |
|
69 |
+ |
|
70 |
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, |
|
71 |
+ env=None): |
|
72 |
+ """Call the given command(s).""" |
|
73 |
+ assert isinstance(commands, list) |
|
74 |
+ p = None |
|
75 |
+ for c in commands: |
|
76 |
+ try: |
|
77 |
+ dispcmd = str([c] + args) |
|
78 |
+ # remember shell=False, so use git.cmd on windows, not just git |
|
79 |
+ p = subprocess.Popen([c] + args, cwd=cwd, env=env, |
|
80 |
+ stdout=subprocess.PIPE, |
|
81 |
+ stderr=(subprocess.PIPE if hide_stderr |
|
82 |
+ else None)) |
|
83 |
+ break |
|
84 |
+ except EnvironmentError: |
|
85 |
+ e = sys.exc_info()[1] |
|
86 |
+ if e.errno == errno.ENOENT: |
|
87 |
+ continue |
|
88 |
+ if verbose: |
|
89 |
+ print("unable to run %s" % dispcmd) |
|
90 |
+ print(e) |
|
91 |
+ return None, None |
|
92 |
+ else: |
|
93 |
+ if verbose: |
|
94 |
+ print("unable to find command, tried %s" % (commands,)) |
|
95 |
+ return None, None |
|
96 |
+ stdout = p.communicate()[0].strip() |
|
97 |
+ if sys.version_info[0] >= 3: |
|
98 |
+ stdout = stdout.decode() |
|
99 |
+ if p.returncode != 0: |
|
100 |
+ if verbose: |
|
101 |
+ print("unable to run %s (error)" % dispcmd) |
|
102 |
+ print("stdout was %s" % stdout) |
|
103 |
+ return None, p.returncode |
|
104 |
+ return stdout, p.returncode |
|
105 |
+ |
|
106 |
+ |
|
107 |
+def versions_from_parentdir(parentdir_prefix, root, verbose): |
|
108 |
+ """Try to determine the version from the parent directory name. |
|
109 |
+ |
|
110 |
+ Source tarballs conventionally unpack into a directory that includes both |
|
111 |
+ the project name and a version string. We will also support searching up |
|
112 |
+ two directory levels for an appropriately named parent directory |
|
113 |
+ """ |
|
114 |
+ rootdirs = [] |
|
115 |
+ |
|
116 |
+ for i in range(3): |
|
117 |
+ dirname = os.path.basename(root) |
|
118 |
+ if dirname.startswith(parentdir_prefix): |
|
119 |
+ return {"version": dirname[len(parentdir_prefix):], |
|
120 |
+ "full-revisionid": None, |
|
121 |
+ "dirty": False, "error": None, "date": None} |
|
122 |
+ else: |
|
123 |
+ rootdirs.append(root) |
|
124 |
+ root = os.path.dirname(root) # up a level |
|
125 |
+ |
|
126 |
+ if verbose: |
|
127 |
+ print("Tried directories %s but none started with prefix %s" % |
|
128 |
+ (str(rootdirs), parentdir_prefix)) |
|
129 |
+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix") |
|
130 |
+ |
|
131 |
+ |
|
132 |
+@register_vcs_handler("git", "get_keywords") |
|
133 |
+def git_get_keywords(versionfile_abs): |
|
134 |
+ """Extract version information from the given file.""" |
|
135 |
+ # the code embedded in _version.py can just fetch the value of these |
|
136 |
+ # keywords. When used from setup.py, we don't want to import _version.py, |
|
137 |
+ # so we do it with a regexp instead. This function is not used from |
|
138 |
+ # _version.py. |
|
139 |
+ keywords = {} |
|
140 |
+ try: |
|
141 |
+ f = open(versionfile_abs, "r") |
|
142 |
+ for line in f.readlines(): |
|
143 |
+ if line.strip().startswith("git_refnames ="): |
|
144 |
+ mo = re.search(r'=\s*"(.*)"', line) |
|
145 |
+ if mo: |
|
146 |
+ keywords["refnames"] = mo.group(1) |
|
147 |
+ if line.strip().startswith("git_full ="): |
|
148 |
+ mo = re.search(r'=\s*"(.*)"', line) |
|
149 |
+ if mo: |
|
150 |
+ keywords["full"] = mo.group(1) |
|
151 |
+ if line.strip().startswith("git_date ="): |
|
152 |
+ mo = re.search(r'=\s*"(.*)"', line) |
|
153 |
+ if mo: |
|
154 |
+ keywords["date"] = mo.group(1) |
|
155 |
+ f.close() |
|
156 |
+ except EnvironmentError: |
|
157 |
+ pass |
|
158 |
+ return keywords |
|
159 |
+ |
|
160 |
+ |
|
161 |
+@register_vcs_handler("git", "keywords") |
|
162 |
+def git_versions_from_keywords(keywords, tag_prefix, verbose): |
|
163 |
+ """Get version information from git keywords.""" |
|
164 |
+ if not keywords: |
|
165 |
+ raise NotThisMethod("no keywords at all, weird") |
|
166 |
+ date = keywords.get("date") |
|
167 |
+ if date is not None: |
|
168 |
+ # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant |
|
169 |
+ # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 |
|
170 |
+ # -like" string, which we must then edit to make compliant), because |
|
171 |
+ # it's been around since git-1.5.3, and it's too difficult to |
|
172 |
+ # discover which version we're using, or to work around using an |
|
173 |
+ # older one. |
|
174 |
+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) |
|
175 |
+ refnames = keywords["refnames"].strip() |
|
176 |
+ if refnames.startswith("$Format"): |
|
177 |
+ if verbose: |
|
178 |
+ print("keywords are unexpanded, not using") |
|
179 |
+ raise NotThisMethod("unexpanded keywords, not a git-archive tarball") |
|
180 |
+ refs = set([r.strip() for r in refnames.strip("()").split(",")]) |
|
181 |
+ # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of |
|
182 |
+ # just "foo-1.0". If we see a "tag: " prefix, prefer those. |
|
183 |
+ TAG = "tag: " |
|
184 |
+ tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) |
|
185 |
+ if not tags: |
|
186 |
+ # Either we're using git < 1.8.3, or there really are no tags. We use |
|
187 |
+ # a heuristic: assume all version tags have a digit. The old git %d |
|
188 |
+ # expansion behaves like git log --decorate=short and strips out the |
|
189 |
+ # refs/heads/ and refs/tags/ prefixes that would let us distinguish |
|
190 |
+ # between branches and tags. By ignoring refnames without digits, we |
|
191 |
+ # filter out many common branch names like "release" and |
|
192 |
+ # "stabilization", as well as "HEAD" and "master". |
|
193 |
+ tags = set([r for r in refs if re.search(r'\d', r)]) |
|
194 |
+ if verbose: |
|
195 |
+ print("discarding '%s', no digits" % ",".join(refs - tags)) |
|
196 |
+ if verbose: |
|
197 |
+ print("likely tags: %s" % ",".join(sorted(tags))) |
|
198 |
+ for ref in sorted(tags): |
|
199 |
+ # sorting will prefer e.g. "2.0" over "2.0rc1" |
|
200 |
+ if ref.startswith(tag_prefix): |
|
201 |
+ r = ref[len(tag_prefix):] |
|
202 |
+ if verbose: |
|
203 |
+ print("picking %s" % r) |
|
204 |
+ return {"version": r, |
|
205 |
+ "full-revisionid": keywords["full"].strip(), |
|
206 |
+ "dirty": False, "error": None, |
|
207 |
+ "date": date} |
|
208 |
+ # no suitable tags, so version is "0+unknown", but full hex is still there |
|
209 |
+ if verbose: |
|
210 |
+ print("no suitable tags, using unknown + full revision id") |
|
211 |
+ return {"version": "0+unknown", |
|
212 |
+ "full-revisionid": keywords["full"].strip(), |
|
213 |
+ "dirty": False, "error": "no suitable tags", "date": None} |
|
214 |
+ |
|
215 |
+ |
|
216 |
+@register_vcs_handler("git", "pieces_from_vcs") |
|
217 |
+def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): |
|
218 |
+ """Get version from 'git describe' in the root of the source tree. |
|
219 |
+ |
|
220 |
+ This only gets called if the git-archive 'subst' keywords were *not* |
|
221 |
+ expanded, and _version.py hasn't already been rewritten with a short |
|
222 |
+ version string, meaning we're inside a checked out source tree. |
|
223 |
+ """ |
|
224 |
+ GITS = ["git"] |
|
225 |
+ if sys.platform == "win32": |
|
226 |
+ GITS = ["git.cmd", "git.exe"] |
|
227 |
+ |
|
228 |
+ out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, |
|
229 |
+ hide_stderr=True) |
|
230 |
+ if rc != 0: |
|
231 |
+ if verbose: |
|
232 |
+ print("Directory %s not under git control" % root) |
|
233 |
+ raise NotThisMethod("'git rev-parse --git-dir' returned error") |
|
234 |
+ |
|
235 |
+ # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] |
|
236 |
+ # if there isn't one, this yields HEX[-dirty] (no NUM) |
|
237 |
+ describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", |
|
238 |
+ "--always", "--long", |
|
239 |
+ "--match", "%s*" % tag_prefix], |
|
240 |
+ cwd=root) |
|
241 |
+ # --long was added in git-1.5.5 |
|
242 |
+ if describe_out is None: |
|
243 |
+ raise NotThisMethod("'git describe' failed") |
|
244 |
+ describe_out = describe_out.strip() |
|
245 |
+ full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) |
|
246 |
+ if full_out is None: |
|
247 |
+ raise NotThisMethod("'git rev-parse' failed") |
|
248 |
+ full_out = full_out.strip() |
|
249 |
+ |
|
250 |
+ pieces = {} |
|
251 |
+ pieces["long"] = full_out |
|
252 |
+ pieces["short"] = full_out[:7] # maybe improved later |
|
253 |
+ pieces["error"] = None |
|
254 |
+ |
|
255 |
+ # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] |
|
256 |
+ # TAG might have hyphens. |
|
257 |
+ git_describe = describe_out |
|
258 |
+ |
|
259 |
+ # look for -dirty suffix |
|
260 |
+ dirty = git_describe.endswith("-dirty") |
|
261 |
+ pieces["dirty"] = dirty |
|
262 |
+ if dirty: |
|
263 |
+ git_describe = git_describe[:git_describe.rindex("-dirty")] |
|
264 |
+ |
|
265 |
+ # now we have TAG-NUM-gHEX or HEX |
|
266 |
+ |
|
267 |
+ if "-" in git_describe: |
|
268 |
+ # TAG-NUM-gHEX |
|
269 |
+ mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) |
|
270 |
+ if not mo: |
|
271 |
+ # unparseable. Maybe git-describe is misbehaving? |
|
272 |
+ pieces["error"] = ("unable to parse git-describe output: '%s'" |
|
273 |
+ % describe_out) |
|
274 |
+ return pieces |
|
275 |
+ |
|
276 |
+ # tag |
|
277 |
+ full_tag = mo.group(1) |
|
278 |
+ if not full_tag.startswith(tag_prefix): |
|
279 |
+ if verbose: |
|
280 |
+ fmt = "tag '%s' doesn't start with prefix '%s'" |
|
281 |
+ print(fmt % (full_tag, tag_prefix)) |
|
282 |
+ pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" |
|
283 |
+ % (full_tag, tag_prefix)) |
|
284 |
+ return pieces |
|
285 |
+ pieces["closest-tag"] = full_tag[len(tag_prefix):] |
|
286 |
+ |
|
287 |
+ # distance: number of commits since tag |
|
288 |
+ pieces["distance"] = int(mo.group(2)) |
|
289 |
+ |
|
290 |
+ # commit: short hex revision ID |
|
291 |
+ pieces["short"] = mo.group(3) |
|
292 |
+ |
|
293 |
+ else: |
|
294 |
+ # HEX: no tags |
|
295 |
+ pieces["closest-tag"] = None |
|
296 |
+ count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], |
|
297 |
+ cwd=root) |
|
298 |
+ pieces["distance"] = int(count_out) # total number of commits |
|
299 |
+ |
|
300 |
+ # commit date: see ISO-8601 comment in git_versions_from_keywords() |
|
301 |
+ date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], |
|
302 |
+ cwd=root)[0].strip() |
|
303 |
+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) |
|
304 |
+ |
|
305 |
+ return pieces |
|
306 |
+ |
|
307 |
+ |
|
308 |
+def plus_or_dot(pieces): |
|
309 |
+ """Return a + if we don't already have one, else return a .""" |
|
310 |
+ if "+" in pieces.get("closest-tag", ""): |
|
311 |
+ return "." |
|
312 |
+ return "+" |
|
313 |
+ |
|
314 |
+ |
|
315 |
+def render_pep440(pieces): |
|
316 |
+ """Build up version string, with post-release "local version identifier". |
|
317 |
+ |
|
318 |
+ Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you |
|
319 |
+ get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty |
|
320 |
+ |
|
321 |
+ Exceptions: |
|
322 |
+ 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] |
|
323 |
+ """ |
|
324 |
+ if pieces["closest-tag"]: |
|
325 |
+ rendered = pieces["closest-tag"] |
|
326 |
+ if pieces["distance"] or pieces["dirty"]: |
|
327 |
+ rendered += plus_or_dot(pieces) |
|
328 |
+ rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) |
|
329 |
+ if pieces["dirty"]: |
|
330 |
+ rendered += ".dirty" |
|
331 |
+ else: |
|
332 |
+ # exception #1 |
|
333 |
+ rendered = "0+untagged.%d.g%s" % (pieces["distance"], |
|
334 |
+ pieces["short"]) |
|
335 |
+ if pieces["dirty"]: |
|
336 |
+ rendered += ".dirty" |
|
337 |
+ return rendered |
|
338 |
+ |
|
339 |
+ |
|
340 |
+def render_pep440_pre(pieces): |
|
341 |
+ """TAG[.post.devDISTANCE] -- No -dirty. |
|
342 |
+ |
|
343 |
+ Exceptions: |
|
344 |
+ 1: no tags. 0.post.devDISTANCE |
|
345 |
+ """ |
|
346 |
+ if pieces["closest-tag"]: |
|
347 |
+ rendered = pieces["closest-tag"] |
|
348 |
+ if pieces["distance"]: |
|
349 |
+ rendered += ".post.dev%d" % pieces["distance"] |
|
350 |
+ else: |
|
351 |
+ # exception #1 |
|
352 |
+ rendered = "0.post.dev%d" % pieces["distance"] |
|
353 |
+ return rendered |
|
354 |
+ |
|
355 |
+ |
|
356 |
+def render_pep440_post(pieces): |
|
357 |
+ """TAG[.postDISTANCE[.dev0]+gHEX] . |
|
358 |
+ |
|
359 |
+ The ".dev0" means dirty. Note that .dev0 sorts backwards |
|
360 |
+ (a dirty tree will appear "older" than the corresponding clean one), |
|
361 |
+ but you shouldn't be releasing software with -dirty anyways. |
|
362 |
+ |
|
363 |
+ Exceptions: |
|
364 |
+ 1: no tags. 0.postDISTANCE[.dev0] |
|
365 |
+ """ |
|
366 |
+ if pieces["closest-tag"]: |
|
367 |
+ rendered = pieces["closest-tag"] |
|
368 |
+ if pieces["distance"] or pieces["dirty"]: |
|
369 |
+ rendered += ".post%d" % pieces["distance"] |
|
370 |
+ if pieces["dirty"]: |
|
371 |
+ rendered += ".dev0" |
|
372 |
+ rendered += plus_or_dot(pieces) |
|
373 |
+ rendered += "g%s" % pieces["short"] |
|
374 |
+ else: |
|
375 |
+ # exception #1 |
|
376 |
+ rendered = "0.post%d" % pieces["distance"] |
|
377 |
+ if pieces["dirty"]: |
|
378 |
+ rendered += ".dev0" |
|
379 |
+ rendered += "+g%s" % pieces["short"] |
|
380 |
+ return rendered |
|
381 |
+ |
|
382 |
+ |
|
383 |
+def render_pep440_old(pieces): |
|
384 |
+ """TAG[.postDISTANCE[.dev0]] . |
|
385 |
+ |
|
386 |
+ The ".dev0" means dirty. |
|
387 |
+ |
|
388 |
+ Eexceptions: |
|
389 |
+ 1: no tags. 0.postDISTANCE[.dev0] |
|
390 |
+ """ |
|
391 |
+ if pieces["closest-tag"]: |
|
392 |
+ rendered = pieces["closest-tag"] |
|
393 |
+ if pieces["distance"] or pieces["dirty"]: |
|
394 |
+ rendered += ".post%d" % pieces["distance"] |
|
395 |
+ if pieces["dirty"]: |
|
396 |
+ rendered += ".dev0" |
|
397 |
+ else: |
|
398 |
+ # exception #1 |
|
399 |
+ rendered = "0.post%d" % pieces["distance"] |
|
400 |
+ if pieces["dirty"]: |
|
401 |
+ rendered += ".dev0" |
|
402 |
+ return rendered |
|
403 |
+ |
|
404 |
+ |
|
405 |
+def render_git_describe(pieces): |
|
406 |
+ """TAG[-DISTANCE-gHEX][-dirty]. |
|
407 |
+ |
|
408 |
+ Like 'git describe --tags --dirty --always'. |
|
409 |
+ |
|
410 |
+ Exceptions: |
|
411 |
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix) |
|
412 |
+ """ |
|
413 |
+ if pieces["closest-tag"]: |
|
414 |
+ rendered = pieces["closest-tag"] |
|
415 |
+ if pieces["distance"]: |
|
416 |
+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) |
|
417 |
+ else: |
|
418 |
+ # exception #1 |
|
419 |
+ rendered = pieces["short"] |
|
420 |
+ if pieces["dirty"]: |
|
421 |
+ rendered += "-dirty" |
|
422 |
+ return rendered |
|
423 |
+ |
|
424 |
+ |
|
425 |
+def render_git_describe_long(pieces): |
|
426 |
+ """TAG-DISTANCE-gHEX[-dirty]. |
|
427 |
+ |
|
428 |
+ Like 'git describe --tags --dirty --always -long'. |
|
429 |
+ The distance/hash is unconditional. |
|
430 |
+ |
|
431 |
+ Exceptions: |
|
432 |
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix) |
|
433 |
+ """ |
|
434 |
+ if pieces["closest-tag"]: |
|
435 |
+ rendered = pieces["closest-tag"] |
|
436 |
+ rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) |
|
437 |
+ else: |
|
438 |
+ # exception #1 |
|
439 |
+ rendered = pieces["short"] |
|
440 |
+ if pieces["dirty"]: |
|
441 |
+ rendered += "-dirty" |
|
442 |
+ return rendered |
|
443 |
+ |
|
444 |
+ |
|
445 |
+def render(pieces, style): |
|
446 |
+ """Render the given version pieces into the requested style.""" |
|
447 |
+ if pieces["error"]: |
|
448 |
+ return {"version": "unknown", |
|
449 |
+ "full-revisionid": pieces.get("long"), |
|
450 |
+ "dirty": None, |
|
451 |
+ "error": pieces["error"], |
|
452 |
+ "date": None} |
|
453 |
+ |
|
454 |
+ if not style or style == "default": |
|
455 |
+ style = "pep440" # the default |
|
456 |
+ |
|
457 |
+ if style == "pep440": |
|
458 |
+ rendered = render_pep440(pieces) |
|
459 |
+ elif style == "pep440-pre": |
|
460 |
+ rendered = render_pep440_pre(pieces) |
|
461 |
+ elif style == "pep440-post": |
|
462 |
+ rendered = render_pep440_post(pieces) |
|
463 |
+ elif style == "pep440-old": |
|
464 |
+ rendered = render_pep440_old(pieces) |
|
465 |
+ elif style == "git-describe": |
|
466 |
+ rendered = render_git_describe(pieces) |
|
467 |
+ elif style == "git-describe-long": |
|
468 |
+ rendered = render_git_describe_long(pieces) |
|
469 |
+ else: |
|
470 |
+ raise ValueError("unknown style '%s'" % style) |
|
471 |
+ |
|
472 |
+ return {"version": rendered, "full-revisionid": pieces["long"], |
|
473 |
+ "dirty": pieces["dirty"], "error": None, |
|
474 |
+ "date": pieces.get("date")} |
|
475 |
+ |
|
476 |
+ |
|
477 |
+def get_versions(): |
|
478 |
+ """Get version information or return default if unable to do so.""" |
|
479 |
+ # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have |
|
480 |
+ # __file__, we can work backwards from there to the root. Some |
|
481 |
+ # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which |
|
482 |
+ # case we can only use expanded keywords. |
|
483 |
+ |
|
484 |
+ cfg = get_config() |
|
485 |
+ verbose = cfg.verbose |
|
486 |
+ |
|
487 |
+ try: |
|
488 |
+ return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, |
|
489 |
+ verbose) |
|
490 |
+ except NotThisMethod: |
|
491 |
+ pass |
|
492 |
+ |
|
493 |
+ try: |
|
494 |
+ root = os.path.realpath(__file__) |
|
495 |
+ # versionfile_source is the relative path from the top of the source |
|
496 |
+ # tree (where the .git directory might live) to this file. Invert |
|
497 |
+ # this to find the root from __file__. |
|
498 |
+ for i in cfg.versionfile_source.split('/'): |
|
499 |
+ root = os.path.dirname(root) |
|
500 |
+ except NameError: |
|
501 |
+ return {"version": "0+unknown", "full-revisionid": None, |
|
502 |
+ "dirty": None, |
|
503 |
+ "error": "unable to find root of source tree", |
|
504 |
+ "date": None} |
|
505 |
+ |
|
506 |
+ try: |
|
507 |
+ pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) |
|
508 |
+ return render(pieces, cfg.style) |
|
509 |
+ except NotThisMethod: |
|
510 |
+ pass |
|
511 |
+ |
|
512 |
+ try: |
|
513 |
+ if cfg.parentdir_prefix: |
|
514 |
+ return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) |
|
515 |
+ except NotThisMethod: |
|
516 |
+ pass |
|
517 |
+ |
|
518 |
+ return {"version": "0+unknown", "full-revisionid": None, |
|
519 |
+ "dirty": None, |
|
520 |
+ "error": "unable to compute version", "date": None} |