1#! /usr/bin/python 2 3"""versioneer.py 4 5(like a rocketeer, but for versions) 6 7* https://github.com/warner/python-versioneer 8* Brian Warner 9* License: Public Domain 10* Version: 0.7+ 11 12This file helps distutils-based projects manage their version number by just 13creating version-control tags. 14 15For developers who work from a VCS-generated tree (e.g. 'git clone' etc), 16each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a 17version number by asking your version-control tool about the current 18checkout. The version number will be written into a generated _version.py 19file of your choosing, where it can be included by your __init__.py 20 21For users who work from a VCS-generated tarball (e.g. 'git archive'), it will 22compute a version number by looking at the name of the directory created when 23te tarball is unpacked. This conventionally includes both the name of the 24project and a version number. 25 26For users who work from a tarball built by 'setup.py sdist', it will get a 27version number from a previously-generated _version.py file. 28 29As a result, loading code directly from the source tree will not result in a 30real version. If you want real versions from VCS trees (where you frequently 31update from the upstream repository, or do new development), you will need to 32do a 'setup.py version' after each update, and load code from the build/ 33directory. 34 35You need to provide this code with a few configuration values: 36 37 versionfile_source: 38 A project-relative pathname into which the generated version strings 39 should be written. This is usually a _version.py next to your project's 40 main __init__.py file. If your project uses src/myproject/__init__.py, 41 this should be 'src/myproject/_version.py'. This file should be checked 42 in to your VCS as usual: the copy created below by 'setup.py 43 update_files' will include code that parses expanded VCS keywords in 44 generated tarballs. The 'build' and 'sdist' commands will replace it with 45 a copy that has just the calculated version string. 46 47 versionfile_build: 48 Like versionfile_source, but relative to the build directory instead of 49 the source directory. These will differ when your setup.py uses 50 'package_dir='. If you have package_dir={'myproject': 'src/myproject'}, 51 then you will probably have versionfile_build='myproject/_version.py' and 52 versionfile_source='src/myproject/_version.py'. 53 54 tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all 55 VCS tags. If your tags look like 'myproject-1.2.0', then you 56 should use tag_prefix='myproject-'. If you use unprefixed tags 57 like '1.2.0', this should be an empty string. 58 59 parentdir_prefix: a string, frequently the same as tag_prefix, which 60 appears at the start of all unpacked tarball filenames. If 61 your tarball unpacks into 'myproject-1.2.0', this should 62 be 'myproject-'. 63 64To use it: 65 66 1: include this file in the top level of your project 67 2: make the following changes to the top of your setup.py: 68 import versioneer 69 versioneer.versionfile_source = 'src/myproject/_version.py' 70 versioneer.versionfile_build = 'myproject/_version.py' 71 versioneer.tag_prefix = '' # tags are like 1.2.0 72 versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0' 73 3: add the following arguments to the setup() call in your setup.py: 74 version=versioneer.get_version(), 75 cmdclass=versioneer.get_cmdclass(), 76 4: run 'setup.py update_files', which will create _version.py, and will 77 append the following to your __init__.py: 78 from _version import __version__ 79 5: modify your MANIFEST.in to include versioneer.py 80 6: add both versioneer.py and the generated _version.py to your VCS 81""" 82 83import os, sys, re 84from distutils.core import Command 85from distutils.command.sdist import sdist as _sdist 86from distutils.command.build import build as _build 87 88versionfile_source = None 89versionfile_build = None 90tag_prefix = None 91parentdir_prefix = None 92 93VCS = "git" 94IN_LONG_VERSION_PY = False 95 96 97LONG_VERSION_PY = ''' 98IN_LONG_VERSION_PY = True 99# This file helps to compute a version number in source trees obtained from 100# git-archive tarball (such as those provided by githubs download-from-tag 101# feature). Distribution tarballs (build by setup.py sdist) and build 102# directories (produced by setup.py build) will contain a much shorter file 103# that just contains the computed version number. 104 105# This file is released into the public domain. Generated by 106# versioneer-0.7+ (https://github.com/warner/python-versioneer) 107 108# these strings will be replaced by git during git-archive 109git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" 110git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" 111 112 113import subprocess 114import sys 115 116def run_command(args, cwd=None, verbose=False): 117 try: 118 # remember shell=False, so use git.cmd on windows, not just git 119 p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) 120 except EnvironmentError: 121 e = sys.exc_info()[1] 122 if verbose: 123 print("unable to run %%s" %% args[0]) 124 print(e) 125 return None 126 stdout = p.communicate()[0].strip() 127 if sys.version >= '3': 128 stdout = stdout.decode() 129 if p.returncode != 0: 130 if verbose: 131 print("unable to run %%s (error)" %% args[0]) 132 return None 133 return stdout 134 135 136import sys 137import re 138import os.path 139 140def get_expanded_variables(versionfile_source): 141 # the code embedded in _version.py can just fetch the value of these 142 # variables. When used from setup.py, we don't want to import 143 # _version.py, so we do it with a regexp instead. This function is not 144 # used from _version.py. 145 variables = {} 146 try: 147 for line in open(versionfile_source,"r").readlines(): 148 if line.strip().startswith("git_refnames ="): 149 mo = re.search(r'=\s*"(.*)"', line) 150 if mo: 151 variables["refnames"] = mo.group(1) 152 if line.strip().startswith("git_full ="): 153 mo = re.search(r'=\s*"(.*)"', line) 154 if mo: 155 variables["full"] = mo.group(1) 156 except EnvironmentError: 157 pass 158 return variables 159 160def versions_from_expanded_variables(variables, tag_prefix, verbose=False): 161 refnames = variables["refnames"].strip() 162 if refnames.startswith("$Format"): 163 if verbose: 164 print("variables are unexpanded, not using") 165 return {} # unexpanded, so not in an unpacked git-archive tarball 166 refs = set([r.strip() for r in refnames.strip("()").split(",")]) 167 for ref in list(refs): 168 if not re.search(r'\d', ref): 169 if verbose: 170 print("discarding '%%s', no digits" %% ref) 171 refs.discard(ref) 172 # Assume all version tags have a digit. git's %%d expansion 173 # behaves like git log --decorate=short and strips out the 174 # refs/heads/ and refs/tags/ prefixes that would let us 175 # distinguish between branches and tags. By ignoring refnames 176 # without digits, we filter out many common branch names like 177 # "release" and "stabilization", as well as "HEAD" and "master". 178 if verbose: 179 print("remaining refs: %%s" %% ",".join(sorted(refs))) 180 for ref in sorted(refs): 181 # sorting will prefer e.g. "2.0" over "2.0rc1" 182 if ref.startswith(tag_prefix): 183 r = ref[len(tag_prefix):] 184 if verbose: 185 print("picking %%s" %% r) 186 return { "version": r, 187 "full": variables["full"].strip() } 188 # no suitable tags, so we use the full revision id 189 if verbose: 190 print("no suitable tags, using full revision id") 191 return { "version": variables["full"].strip(), 192 "full": variables["full"].strip() } 193 194def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): 195 # this runs 'git' from the root of the source tree. That either means 196 # someone ran a setup.py command (and this code is in versioneer.py, so 197 # IN_LONG_VERSION_PY=False, thus the containing directory is the root of 198 # the source tree), or someone ran a project-specific entry point (and 199 # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the 200 # containing directory is somewhere deeper in the source tree). This only 201 # gets called if the git-archive 'subst' variables were *not* expanded, 202 # and _version.py hasn't already been rewritten with a short version 203 # string, meaning we're inside a checked out source tree. 204 205 try: 206 here = os.path.abspath(__file__) 207 except NameError: 208 # some py2exe/bbfreeze/non-CPython implementations don't do __file__ 209 return {} # not always correct 210 211 # versionfile_source is the relative path from the top of the source tree 212 # (where the .git directory might live) to this file. Invert this to find 213 # the root from __file__. 214 root = here 215 if IN_LONG_VERSION_PY: 216 for i in range(len(versionfile_source.split("/"))): 217 root = os.path.dirname(root) 218 else: 219 root = os.path.dirname(here) 220 if not os.path.exists(os.path.join(root, ".git")): 221 if verbose: 222 print("no .git in %%s" %% root) 223 return {} 224 225 GIT = "git" 226 if sys.platform == "win32": 227 GIT = "git.cmd" 228 stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], 229 cwd=root) 230 if stdout is None: 231 return {} 232 if not stdout.startswith(tag_prefix): 233 if verbose: 234 print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix)) 235 return {} 236 tag = stdout[len(tag_prefix):] 237 stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) 238 if stdout is None: 239 return {} 240 full = stdout.strip() 241 if tag.endswith("-dirty"): 242 full += "-dirty" 243 return {"version": tag, "full": full} 244 245 246def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): 247 if IN_LONG_VERSION_PY: 248 # We're running from _version.py. If it's from a source tree 249 # (execute-in-place), we can work upwards to find the root of the 250 # tree, and then check the parent directory for a version string. If 251 # it's in an installed application, there's no hope. 252 try: 253 here = os.path.abspath(__file__) 254 except NameError: 255 # py2exe/bbfreeze/non-CPython don't have __file__ 256 return {} # without __file__, we have no hope 257 # versionfile_source is the relative path from the top of the source 258 # tree to _version.py. Invert this to find the root from __file__. 259 root = here 260 for i in range(len(versionfile_source.split("/"))): 261 root = os.path.dirname(root) 262 else: 263 # we're running from versioneer.py, which means we're running from 264 # the setup.py in a source tree. sys.argv[0] is setup.py in the root. 265 here = os.path.abspath(sys.argv[0]) 266 root = os.path.dirname(here) 267 268 # Source tarballs conventionally unpack into a directory that includes 269 # both the project name and a version string. 270 dirname = os.path.basename(root) 271 if not dirname.startswith(parentdir_prefix): 272 if verbose: 273 print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %% 274 (root, dirname, parentdir_prefix)) 275 return None 276 return {"version": dirname[len(parentdir_prefix):], "full": ""} 277 278tag_prefix = "%(TAG_PREFIX)s" 279parentdir_prefix = "%(PARENTDIR_PREFIX)s" 280versionfile_source = "%(VERSIONFILE_SOURCE)s" 281 282def get_versions(default={"version": "unknown", "full": ""}, verbose=False): 283 variables = { "refnames": git_refnames, "full": git_full } 284 ver = versions_from_expanded_variables(variables, tag_prefix, verbose) 285 if not ver: 286 ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) 287 if not ver: 288 ver = versions_from_parentdir(parentdir_prefix, versionfile_source, 289 verbose) 290 if not ver: 291 ver = default 292 return ver 293 294''' 295 296 297import subprocess 298import sys 299 300def run_command(args, cwd=None, verbose=False): 301 try: 302 # remember shell=False, so use git.cmd on windows, not just git 303 p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) 304 except EnvironmentError: 305 e = sys.exc_info()[1] 306 if verbose: 307 print("unable to run %s" % args[0]) 308 print(e) 309 return None 310 stdout = p.communicate()[0].strip() 311 if sys.version >= '3': 312 stdout = stdout.decode() 313 if p.returncode != 0: 314 if verbose: 315 print("unable to run %s (error)" % args[0]) 316 return None 317 return stdout 318 319 320import sys 321import re 322import os.path 323 324def get_expanded_variables(versionfile_source): 325 # the code embedded in _version.py can just fetch the value of these 326 # variables. When used from setup.py, we don't want to import 327 # _version.py, so we do it with a regexp instead. This function is not 328 # used from _version.py. 329 variables = {} 330 try: 331 for line in open(versionfile_source,"r").readlines(): 332 if line.strip().startswith("git_refnames ="): 333 mo = re.search(r'=\s*"(.*)"', line) 334 if mo: 335 variables["refnames"] = mo.group(1) 336 if line.strip().startswith("git_full ="): 337 mo = re.search(r'=\s*"(.*)"', line) 338 if mo: 339 variables["full"] = mo.group(1) 340 except EnvironmentError: 341 pass 342 return variables 343 344def versions_from_expanded_variables(variables, tag_prefix, verbose=False): 345 refnames = variables["refnames"].strip() 346 if refnames.startswith("$Format"): 347 if verbose: 348 print("variables are unexpanded, not using") 349 return {} # unexpanded, so not in an unpacked git-archive tarball 350 refs = set([r.strip() for r in refnames.strip("()").split(",")]) 351 for ref in list(refs): 352 if not re.search(r'\d', ref): 353 if verbose: 354 print("discarding '%s', no digits" % ref) 355 refs.discard(ref) 356 # Assume all version tags have a digit. git's %d expansion 357 # behaves like git log --decorate=short and strips out the 358 # refs/heads/ and refs/tags/ prefixes that would let us 359 # distinguish between branches and tags. By ignoring refnames 360 # without digits, we filter out many common branch names like 361 # "release" and "stabilization", as well as "HEAD" and "master". 362 if verbose: 363 print("remaining refs: %s" % ",".join(sorted(refs))) 364 for ref in sorted(refs): 365 # sorting will prefer e.g. "2.0" over "2.0rc1" 366 if ref.startswith(tag_prefix): 367 r = ref[len(tag_prefix):] 368 if verbose: 369 print("picking %s" % r) 370 return { "version": r, 371 "full": variables["full"].strip() } 372 # no suitable tags, so we use the full revision id 373 if verbose: 374 print("no suitable tags, using full revision id") 375 return { "version": variables["full"].strip(), 376 "full": variables["full"].strip() } 377 378def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): 379 # this runs 'git' from the root of the source tree. That either means 380 # someone ran a setup.py command (and this code is in versioneer.py, so 381 # IN_LONG_VERSION_PY=False, thus the containing directory is the root of 382 # the source tree), or someone ran a project-specific entry point (and 383 # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the 384 # containing directory is somewhere deeper in the source tree). This only 385 # gets called if the git-archive 'subst' variables were *not* expanded, 386 # and _version.py hasn't already been rewritten with a short version 387 # string, meaning we're inside a checked out source tree. 388 389 try: 390 here = os.path.abspath(__file__) 391 except NameError: 392 # some py2exe/bbfreeze/non-CPython implementations don't do __file__ 393 return {} # not always correct 394 395 # versionfile_source is the relative path from the top of the source tree 396 # (where the .git directory might live) to this file. Invert this to find 397 # the root from __file__. 398 root = here 399 if IN_LONG_VERSION_PY: 400 for i in range(len(versionfile_source.split("/"))): 401 root = os.path.dirname(root) 402 else: 403 root = os.path.dirname(here) 404 if not os.path.exists(os.path.join(root, ".git")): 405 if verbose: 406 print("no .git in %s" % root) 407 return {} 408 409 GIT = "git" 410 if sys.platform == "win32": 411 GIT = "git.cmd" 412 stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], 413 cwd=root) 414 if stdout is None: 415 return {} 416 if not stdout.startswith(tag_prefix): 417 if verbose: 418 print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix)) 419 return {} 420 tag = stdout[len(tag_prefix):] 421 stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) 422 if stdout is None: 423 return {} 424 full = stdout.strip() 425 if tag.endswith("-dirty"): 426 full += "-dirty" 427 return {"version": tag, "full": full} 428 429 430def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): 431 if IN_LONG_VERSION_PY: 432 # We're running from _version.py. If it's from a source tree 433 # (execute-in-place), we can work upwards to find the root of the 434 # tree, and then check the parent directory for a version string. If 435 # it's in an installed application, there's no hope. 436 try: 437 here = os.path.abspath(__file__) 438 except NameError: 439 # py2exe/bbfreeze/non-CPython don't have __file__ 440 return {} # without __file__, we have no hope 441 # versionfile_source is the relative path from the top of the source 442 # tree to _version.py. Invert this to find the root from __file__. 443 root = here 444 for i in range(len(versionfile_source.split("/"))): 445 root = os.path.dirname(root) 446 else: 447 # we're running from versioneer.py, which means we're running from 448 # the setup.py in a source tree. sys.argv[0] is setup.py in the root. 449 here = os.path.abspath(sys.argv[0]) 450 root = os.path.dirname(here) 451 452 # Source tarballs conventionally unpack into a directory that includes 453 # both the project name and a version string. 454 dirname = os.path.basename(root) 455 if not dirname.startswith(parentdir_prefix): 456 if verbose: 457 print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" % 458 (root, dirname, parentdir_prefix)) 459 return None 460 return {"version": dirname[len(parentdir_prefix):], "full": ""} 461 462import sys 463 464def do_vcs_install(versionfile_source, ipy): 465 GIT = "git" 466 if sys.platform == "win32": 467 GIT = "git.cmd" 468 run_command([GIT, "add", "versioneer.py"]) 469 run_command([GIT, "add", versionfile_source]) 470 run_command([GIT, "add", ipy]) 471 present = False 472 try: 473 f = open(".gitattributes", "r") 474 for line in f.readlines(): 475 if line.strip().startswith(versionfile_source): 476 if "export-subst" in line.strip().split()[1:]: 477 present = True 478 f.close() 479 except EnvironmentError: 480 pass 481 if not present: 482 f = open(".gitattributes", "a+") 483 f.write("%s export-subst\n" % versionfile_source) 484 f.close() 485 run_command([GIT, "add", ".gitattributes"]) 486 487 488SHORT_VERSION_PY = """ 489# This file was generated by 'versioneer.py' (0.7+) from 490# revision-control system data, or from the parent directory name of an 491# unpacked source archive. Distribution tarballs contain a pre-generated copy 492# of this file. 493 494version_version = '%(version)s' 495version_full = '%(full)s' 496def get_versions(default={}, verbose=False): 497 return {'version': version_version, 'full': version_full} 498 499""" 500 501DEFAULT = {"version": "unknown", "full": "unknown"} 502 503def versions_from_file(filename): 504 versions = {} 505 try: 506 f = open(filename) 507 except EnvironmentError: 508 return versions 509 for line in f.readlines(): 510 mo = re.match("version_version = '([^']+)'", line) 511 if mo: 512 versions["version"] = mo.group(1) 513 mo = re.match("version_full = '([^']+)'", line) 514 if mo: 515 versions["full"] = mo.group(1) 516 return versions 517 518def write_to_version_file(filename, versions): 519 f = open(filename, "w") 520 f.write(SHORT_VERSION_PY % versions) 521 f.close() 522 print("set %s to '%s'" % (filename, versions["version"])) 523 524 525def get_best_versions(versionfile, tag_prefix, parentdir_prefix, 526 default=DEFAULT, verbose=False): 527 # returns dict with two keys: 'version' and 'full' 528 # 529 # extract version from first of _version.py, 'git describe', parentdir. 530 # This is meant to work for developers using a source checkout, for users 531 # of a tarball created by 'setup.py sdist', and for users of a 532 # tarball/zipball created by 'git archive' or github's download-from-tag 533 # feature. 534 535 variables = get_expanded_variables(versionfile_source) 536 if variables: 537 ver = versions_from_expanded_variables(variables, tag_prefix) 538 if ver: 539 if verbose: print("got version from expanded variable %s" % ver) 540 return ver 541 542 ver = versions_from_file(versionfile) 543 if ver: 544 if verbose: print("got version from file %s %s" % (versionfile, ver)) 545 return ver 546 547 ver = versions_from_vcs(tag_prefix, versionfile_source, verbose) 548 if ver: 549 if verbose: print("got version from git %s" % ver) 550 return ver 551 552 ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose) 553 if ver: 554 if verbose: print("got version from parentdir %s" % ver) 555 return ver 556 557 if verbose: print("got version from default %s" % ver) 558 return default 559 560def get_versions(default=DEFAULT, verbose=False): 561 assert versionfile_source is not None, "please set versioneer.versionfile_source" 562 assert tag_prefix is not None, "please set versioneer.tag_prefix" 563 assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix" 564 return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix, 565 default=default, verbose=verbose) 566def get_version(verbose=False): 567 return get_versions(verbose=verbose)["version"] 568 569class cmd_version(Command): 570 description = "report generated version string" 571 user_options = [] 572 boolean_options = [] 573 def initialize_options(self): 574 pass 575 def finalize_options(self): 576 pass 577 def run(self): 578 ver = get_version(verbose=True) 579 print("Version is currently: %s" % ver) 580 581 582class cmd_build(_build): 583 def run(self): 584 versions = get_versions(verbose=True) 585 _build.run(self) 586 # now locate _version.py in the new build/ directory and replace it 587 # with an updated value 588 target_versionfile = os.path.join(self.build_lib, versionfile_build) 589 print("UPDATING %s" % target_versionfile) 590 os.unlink(target_versionfile) 591 f = open(target_versionfile, "w") 592 f.write(SHORT_VERSION_PY % versions) 593 f.close() 594 595class cmd_sdist(_sdist): 596 def run(self): 597 versions = get_versions(verbose=True) 598 self._versioneer_generated_versions = versions 599 # unless we update this, the command will keep using the old version 600 self.distribution.metadata.version = versions["version"] 601 return _sdist.run(self) 602 603 def make_release_tree(self, base_dir, files): 604 _sdist.make_release_tree(self, base_dir, files) 605 # now locate _version.py in the new base_dir directory (remembering 606 # that it may be a hardlink) and replace it with an updated value 607 target_versionfile = os.path.join(base_dir, versionfile_source) 608 print("UPDATING %s" % target_versionfile) 609 os.unlink(target_versionfile) 610 f = open(target_versionfile, "w") 611 f.write(SHORT_VERSION_PY % self._versioneer_generated_versions) 612 f.close() 613 614INIT_PY_SNIPPET = """ 615from ._version import get_versions 616__version__ = get_versions()['version'] 617del get_versions 618""" 619 620class cmd_update_files(Command): 621 description = "modify __init__.py and create _version.py" 622 user_options = [] 623 boolean_options = [] 624 def initialize_options(self): 625 pass 626 def finalize_options(self): 627 pass 628 def run(self): 629 ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py") 630 print(" creating %s" % versionfile_source) 631 f = open(versionfile_source, "w") 632 f.write(LONG_VERSION_PY % {"DOLLAR": "$", 633 "TAG_PREFIX": tag_prefix, 634 "PARENTDIR_PREFIX": parentdir_prefix, 635 "VERSIONFILE_SOURCE": versionfile_source, 636 }) 637 f.close() 638 try: 639 old = open(ipy, "r").read() 640 except EnvironmentError: 641 old = "" 642 if INIT_PY_SNIPPET not in old: 643 print(" appending to %s" % ipy) 644 f = open(ipy, "a") 645 f.write(INIT_PY_SNIPPET) 646 f.close() 647 else: 648 print(" %s unmodified" % ipy) 649 do_vcs_install(versionfile_source, ipy) 650 651def get_cmdclass(): 652 return {'version': cmd_version, 653 'update_files': cmd_update_files, 654 'build': cmd_build, 655 'sdist': cmd_sdist, 656 } 657