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