1# Version: 0.19 2 3"""The Versioneer - like a rocketeer, but for versions. 4 5The Versioneer 6============== 7 8* like a rocketeer, but for versions! 9* https://github.com/python-versioneer/python-versioneer 10* Brian Warner 11* License: Public Domain 12* Compatible with: Python 3.6, 3.7, 3.8, 3.9 and pypy3 13* [![Latest Version][pypi-image]][pypi-url] 14* [![Build Status][travis-image]][travis-url] 15 16This is a tool for managing a recorded version number in distutils-based 17python projects. The goal is to remove the tedious and error-prone "update 18the embedded version string" step from your release process. Making a new 19release should be as easy as recording a new tag in your version-control 20system, and maybe making new tarballs. 21 22 23## Quick Install 24 25* `pip install versioneer` to somewhere in your $PATH 26* add a `[versioneer]` section to your setup.cfg (see [Install](INSTALL.md)) 27* run `versioneer install` in your source tree, commit the results 28* Verify version information with `python setup.py version` 29 30## Version Identifiers 31 32Source trees come from a variety of places: 33 34* a version-control system checkout (mostly used by developers) 35* a nightly tarball, produced by build automation 36* a snapshot tarball, produced by a web-based VCS browser, like github's 37 "tarball from tag" feature 38* a release tarball, produced by "setup.py sdist", distributed through PyPI 39 40Within each source tree, the version identifier (either a string or a number, 41this tool is format-agnostic) can come from a variety of places: 42 43* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows 44 about recent "tags" and an absolute revision-id 45* the name of the directory into which the tarball was unpacked 46* an expanded VCS keyword ($Id$, etc) 47* a `_version.py` created by some earlier build step 48 49For released software, the version identifier is closely related to a VCS 50tag. Some projects use tag names that include more than just the version 51string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool 52needs to strip the tag prefix to extract the version identifier. For 53unreleased software (between tags), the version identifier should provide 54enough information to help developers recreate the same tree, while also 55giving them an idea of roughly how old the tree is (after version 1.2, before 56version 1.3). Many VCS systems can report a description that captures this, 57for example `git describe --tags --dirty --always` reports things like 58"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 590.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has 60uncommitted changes). 61 62The version identifier is used for multiple purposes: 63 64* to allow the module to self-identify its version: `myproject.__version__` 65* to choose a name and prefix for a 'setup.py sdist' tarball 66 67## Theory of Operation 68 69Versioneer works by adding a special `_version.py` file into your source 70tree, where your `__init__.py` can import it. This `_version.py` knows how to 71dynamically ask the VCS tool for version information at import time. 72 73`_version.py` also contains `$Revision$` markers, and the installation 74process marks `_version.py` to have this marker rewritten with a tag name 75during the `git archive` command. As a result, generated tarballs will 76contain enough information to get the proper version. 77 78To allow `setup.py` to compute a version too, a `versioneer.py` is added to 79the top level of your source tree, next to `setup.py` and the `setup.cfg` 80that configures it. This overrides several distutils/setuptools commands to 81compute the version when invoked, and changes `setup.py build` and `setup.py 82sdist` to replace `_version.py` with a small static file that contains just 83the generated version data. 84 85## Installation 86 87See [INSTALL.md](./INSTALL.md) for detailed installation instructions. 88 89## Version-String Flavors 90 91Code which uses Versioneer can learn about its version string at runtime by 92importing `_version` from your main `__init__.py` file and running the 93`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can 94import the top-level `versioneer.py` and run `get_versions()`. 95 96Both functions return a dictionary with different flavors of version 97information: 98 99* `['version']`: A condensed version string, rendered using the selected 100 style. This is the most commonly used value for the project's version 101 string. The default "pep440" style yields strings like `0.11`, 102 `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section 103 below for alternative styles. 104 105* `['full-revisionid']`: detailed revision identifier. For Git, this is the 106 full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". 107 108* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the 109 commit date in ISO 8601 format. This will be None if the date is not 110 available. 111 112* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that 113 this is only accurate if run in a VCS checkout, otherwise it is likely to 114 be False or None 115 116* `['error']`: if the version string could not be computed, this will be set 117 to a string describing the problem, otherwise it will be None. It may be 118 useful to throw an exception in setup.py if this is set, to avoid e.g. 119 creating tarballs with a version string of "unknown". 120 121Some variants are more useful than others. Including `full-revisionid` in a 122bug report should allow developers to reconstruct the exact code being tested 123(or indicate the presence of local changes that should be shared with the 124developers). `version` is suitable for display in an "about" box or a CLI 125`--version` output: it can be easily compared against release notes and lists 126of bugs fixed in various releases. 127 128The installer adds the following text to your `__init__.py` to place a basic 129version in `YOURPROJECT.__version__`: 130 131 from ._version import get_versions 132 __version__ = get_versions()['version'] 133 del get_versions 134 135## Styles 136 137The setup.cfg `style=` configuration controls how the VCS information is 138rendered into a version string. 139 140The default style, "pep440", produces a PEP440-compliant string, equal to the 141un-prefixed tag name for actual releases, and containing an additional "local 142version" section with more detail for in-between builds. For Git, this is 143TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags 144--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the 145tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and 146that this commit is two revisions ("+2") beyond the "0.11" tag. For released 147software (exactly equal to a known tag), the identifier will only contain the 148stripped tag, e.g. "0.11". 149 150Other styles are available. See [details.md](details.md) in the Versioneer 151source tree for descriptions. 152 153## Debugging 154 155Versioneer tries to avoid fatal errors: if something goes wrong, it will tend 156to return a version of "0+unknown". To investigate the problem, run `setup.py 157version`, which will run the version-lookup code in a verbose mode, and will 158display the full contents of `get_versions()` (including the `error` string, 159which may help identify what went wrong). 160 161## Known Limitations 162 163Some situations are known to cause problems for Versioneer. This details the 164most significant ones. More can be found on Github 165[issues page](https://github.com/python-versioneer/python-versioneer/issues). 166 167### Subprojects 168 169Versioneer has limited support for source trees in which `setup.py` is not in 170the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are 171two common reasons why `setup.py` might not be in the root: 172 173* Source trees which contain multiple subprojects, such as 174 [Buildbot](https://github.com/buildbot/buildbot), which contains both 175 "master" and "slave" subprojects, each with their own `setup.py`, 176 `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI 177 distributions (and upload multiple independently-installable tarballs). 178* Source trees whose main purpose is to contain a C library, but which also 179 provide bindings to Python (and perhaps other languages) in subdirectories. 180 181Versioneer will look for `.git` in parent directories, and most operations 182should get the right version string. However `pip` and `setuptools` have bugs 183and implementation details which frequently cause `pip install .` from a 184subproject directory to fail to find a correct version string (so it usually 185defaults to `0+unknown`). 186 187`pip install --editable .` should work correctly. `setup.py install` might 188work too. 189 190Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in 191some later version. 192 193[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking 194this issue. The discussion in 195[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the 196issue from the Versioneer side in more detail. 197[pip PR#3176](https://github.com/pypa/pip/pull/3176) and 198[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve 199pip to let Versioneer work correctly. 200 201Versioneer-0.16 and earlier only looked for a `.git` directory next to the 202`setup.cfg`, so subprojects were completely unsupported with those releases. 203 204### Editable installs with setuptools <= 18.5 205 206`setup.py develop` and `pip install --editable .` allow you to install a 207project into a virtualenv once, then continue editing the source code (and 208test) without re-installing after every change. 209 210"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a 211convenient way to specify executable scripts that should be installed along 212with the python package. 213 214These both work as expected when using modern setuptools. When using 215setuptools-18.5 or earlier, however, certain operations will cause 216`pkg_resources.DistributionNotFound` errors when running the entrypoint 217script, which must be resolved by re-installing the package. This happens 218when the install happens with one version, then the egg_info data is 219regenerated while a different version is checked out. Many setup.py commands 220cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into 221a different virtualenv), so this can be surprising. 222 223[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes 224this one, but upgrading to a newer version of setuptools should probably 225resolve it. 226 227 228## Updating Versioneer 229 230To upgrade your project to a new release of Versioneer, do the following: 231 232* install the new Versioneer (`pip install -U versioneer` or equivalent) 233* edit `setup.cfg`, if necessary, to include any new configuration settings 234 indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. 235* re-run `versioneer install` in your source tree, to replace 236 `SRC/_version.py` 237* commit any changed files 238 239## Future Directions 240 241This tool is designed to make it easily extended to other version-control 242systems: all VCS-specific components are in separate directories like 243src/git/ . The top-level `versioneer.py` script is assembled from these 244components by running make-versioneer.py . In the future, make-versioneer.py 245will take a VCS name as an argument, and will construct a version of 246`versioneer.py` that is specific to the given VCS. It might also take the 247configuration arguments that are currently provided manually during 248installation by editing setup.py . Alternatively, it might go the other 249direction and include code from all supported VCS systems, reducing the 250number of intermediate scripts. 251 252## Similar projects 253 254* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time 255 dependency 256* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of 257 versioneer 258 259## License 260 261To make Versioneer easier to embed, all its code is dedicated to the public 262domain. The `_version.py` that it creates is also in the public domain. 263Specifically, both are released under the Creative Commons "Public Domain 264Dedication" license (CC0-1.0), as described in 265https://creativecommons.org/publicdomain/zero/1.0/ . 266 267[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg 268[pypi-url]: https://pypi.python.org/pypi/versioneer/ 269[travis-image]: 270https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg 271[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer 272 273""" 274 275import configparser 276import errno 277import json 278import os 279import re 280import subprocess 281import sys 282 283 284class VersioneerConfig: 285 """Container for Versioneer configuration parameters.""" 286 287 288def get_root(): 289 """Get the project root directory. 290 291 We require that all commands are run from the project root, i.e. the 292 directory that contains setup.py, setup.cfg, and versioneer.py . 293 """ 294 root = os.path.realpath(os.path.abspath(os.getcwd())) 295 setup_py = os.path.join(root, "setup.py") 296 versioneer_py = os.path.join(root, "versioneer.py") 297 if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 298 # allow 'python path/to/setup.py COMMAND' 299 root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) 300 setup_py = os.path.join(root, "setup.py") 301 versioneer_py = os.path.join(root, "versioneer.py") 302 if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 303 err = ( 304 "Versioneer was unable to run the project root directory. " 305 "Versioneer requires setup.py to be executed from " 306 "its immediate directory (like 'python setup.py COMMAND'), " 307 "or in a way that lets it use sys.argv[0] to find the root " 308 "(like 'python path/to/setup.py COMMAND')." 309 ) 310 raise VersioneerBadRootError(err) 311 try: 312 # Certain runtime workflows (setup.py install/develop in a setuptools 313 # tree) execute all dependencies in a single python process, so 314 # "versioneer" may be imported multiple times, and python's shared 315 # module-import table will cache the first one. So we can't use 316 # os.path.dirname(__file__), as that will find whichever 317 # versioneer.py was first imported, even in later projects. 318 me = os.path.realpath(os.path.abspath(__file__)) 319 me_dir = os.path.normcase(os.path.splitext(me)[0]) 320 vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) 321 if me_dir != vsr_dir: 322 print( 323 "Warning: build in %s is using versioneer.py from %s" 324 % (os.path.dirname(me), versioneer_py) 325 ) 326 except NameError: 327 pass 328 return root 329 330 331def get_config_from_root(root): 332 """Read the project setup.cfg file to determine Versioneer config.""" 333 # This might raise EnvironmentError (if setup.cfg is missing), or 334 # configparser.NoSectionError (if it lacks a [versioneer] section), or 335 # configparser.NoOptionError (if it lacks "VCS="). See the docstring at 336 # the top of versioneer.py for instructions on writing your setup.cfg . 337 setup_cfg = os.path.join(root, "setup.cfg") 338 parser = configparser.ConfigParser() 339 with open(setup_cfg, "r") as f: 340 parser.read_file(f) 341 VCS = parser.get("versioneer", "VCS") # mandatory 342 343 def get(parser, name): 344 if parser.has_option("versioneer", name): 345 return parser.get("versioneer", name) 346 return None 347 348 cfg = VersioneerConfig() 349 cfg.VCS = VCS 350 cfg.style = get(parser, "style") or "" 351 cfg.versionfile_source = get(parser, "versionfile_source") 352 cfg.versionfile_build = get(parser, "versionfile_build") 353 cfg.tag_prefix = get(parser, "tag_prefix") 354 if cfg.tag_prefix in ("''", '""'): 355 cfg.tag_prefix = "" 356 cfg.parentdir_prefix = get(parser, "parentdir_prefix") 357 cfg.verbose = get(parser, "verbose") 358 return cfg 359 360 361class NotThisMethod(Exception): 362 """Exception raised if a method is not valid for the current scenario.""" 363 364 365# these dictionaries contain VCS-specific tools 366LONG_VERSION_PY = {} 367HANDLERS = {} 368 369 370def register_vcs_handler(vcs, method): # decorator 371 """Create decorator to mark a method as the handler of a VCS.""" 372 373 def decorate(f): 374 """Store f in HANDLERS[vcs][method].""" 375 if vcs not in HANDLERS: 376 HANDLERS[vcs] = {} 377 HANDLERS[vcs][method] = f 378 return f 379 380 return decorate 381 382 383def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): 384 """Call the given command(s).""" 385 assert isinstance(commands, list) 386 p = None 387 for c in commands: 388 try: 389 dispcmd = str([c] + args) 390 # remember shell=False, so use git.cmd on windows, not just git 391 p = subprocess.Popen( 392 [c] + args, 393 cwd=cwd, 394 env=env, 395 stdout=subprocess.PIPE, 396 stderr=(subprocess.PIPE if hide_stderr else None), 397 ) 398 break 399 except EnvironmentError: 400 e = sys.exc_info()[1] 401 if e.errno == errno.ENOENT: 402 continue 403 if verbose: 404 print("unable to run %s" % dispcmd) 405 print(e) 406 return None, None 407 else: 408 if verbose: 409 print("unable to find command, tried %s" % (commands,)) 410 return None, None 411 stdout = p.communicate()[0].strip().decode() 412 if p.returncode != 0: 413 if verbose: 414 print("unable to run %s (error)" % dispcmd) 415 print("stdout was %s" % stdout) 416 return None, p.returncode 417 return stdout, p.returncode 418 419 420LONG_VERSION_PY[ 421 "git" 422] = r''' 423# This file helps to compute a version number in source trees obtained from 424# git-archive tarball (such as those provided by githubs download-from-tag 425# feature). Distribution tarballs (built by setup.py sdist) and build 426# directories (produced by setup.py build) will contain a much shorter file 427# that just contains the computed version number. 428 429# This file is released into the public domain. Generated by 430# versioneer-0.19 (https://github.com/python-versioneer/python-versioneer) 431 432"""Git implementation of _version.py.""" 433 434import errno 435import os 436import re 437import subprocess 438import sys 439 440 441def get_keywords(): 442 """Get the keywords needed to look up the version information.""" 443 # these strings will be replaced by git during git-archive. 444 # setup.py/versioneer.py will grep for the variable names, so they must 445 # each be defined on a line of their own. _version.py will just call 446 # get_keywords(). 447 git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" 448 git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" 449 git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" 450 keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} 451 return keywords 452 453 454class VersioneerConfig: 455 """Container for Versioneer configuration parameters.""" 456 457 458def get_config(): 459 """Create, populate and return the VersioneerConfig() object.""" 460 # these strings are filled in when 'setup.py versioneer' creates 461 # _version.py 462 cfg = VersioneerConfig() 463 cfg.VCS = "git" 464 cfg.style = "%(STYLE)s" 465 cfg.tag_prefix = "%(TAG_PREFIX)s" 466 cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" 467 cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" 468 cfg.verbose = False 469 return cfg 470 471 472class NotThisMethod(Exception): 473 """Exception raised if a method is not valid for the current scenario.""" 474 475 476LONG_VERSION_PY = {} 477HANDLERS = {} 478 479 480def register_vcs_handler(vcs, method): # decorator 481 """Create decorator to mark a method as the handler of a VCS.""" 482 def decorate(f): 483 """Store f in HANDLERS[vcs][method].""" 484 if vcs not in HANDLERS: 485 HANDLERS[vcs] = {} 486 HANDLERS[vcs][method] = f 487 return f 488 return decorate 489 490 491def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, 492 env=None): 493 """Call the given command(s).""" 494 assert isinstance(commands, list) 495 p = None 496 for c in commands: 497 try: 498 dispcmd = str([c] + args) 499 # remember shell=False, so use git.cmd on windows, not just git 500 p = subprocess.Popen([c] + args, cwd=cwd, env=env, 501 stdout=subprocess.PIPE, 502 stderr=(subprocess.PIPE if hide_stderr 503 else None)) 504 break 505 except EnvironmentError: 506 e = sys.exc_info()[1] 507 if e.errno == errno.ENOENT: 508 continue 509 if verbose: 510 print("unable to run %%s" %% dispcmd) 511 print(e) 512 return None, None 513 else: 514 if verbose: 515 print("unable to find command, tried %%s" %% (commands,)) 516 return None, None 517 stdout = p.communicate()[0].strip().decode() 518 if p.returncode != 0: 519 if verbose: 520 print("unable to run %%s (error)" %% dispcmd) 521 print("stdout was %%s" %% stdout) 522 return None, p.returncode 523 return stdout, p.returncode 524 525 526def versions_from_parentdir(parentdir_prefix, root, verbose): 527 """Try to determine the version from the parent directory name. 528 529 Source tarballs conventionally unpack into a directory that includes both 530 the project name and a version string. We will also support searching up 531 two directory levels for an appropriately named parent directory 532 """ 533 rootdirs = [] 534 535 for i in range(3): 536 dirname = os.path.basename(root) 537 if dirname.startswith(parentdir_prefix): 538 return {"version": dirname[len(parentdir_prefix):], 539 "full-revisionid": None, 540 "dirty": False, "error": None, "date": None} 541 else: 542 rootdirs.append(root) 543 root = os.path.dirname(root) # up a level 544 545 if verbose: 546 print("Tried directories %%s but none started with prefix %%s" %% 547 (str(rootdirs), parentdir_prefix)) 548 raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 549 550 551@register_vcs_handler("git", "get_keywords") 552def git_get_keywords(versionfile_abs): 553 """Extract version information from the given file.""" 554 # the code embedded in _version.py can just fetch the value of these 555 # keywords. When used from setup.py, we don't want to import _version.py, 556 # so we do it with a regexp instead. This function is not used from 557 # _version.py. 558 keywords = {} 559 try: 560 f = open(versionfile_abs, "r") 561 for line in f.readlines(): 562 if line.strip().startswith("git_refnames ="): 563 mo = re.search(r'=\s*"(.*)"', line) 564 if mo: 565 keywords["refnames"] = mo.group(1) 566 if line.strip().startswith("git_full ="): 567 mo = re.search(r'=\s*"(.*)"', line) 568 if mo: 569 keywords["full"] = mo.group(1) 570 if line.strip().startswith("git_date ="): 571 mo = re.search(r'=\s*"(.*)"', line) 572 if mo: 573 keywords["date"] = mo.group(1) 574 f.close() 575 except EnvironmentError: 576 pass 577 return keywords 578 579 580@register_vcs_handler("git", "keywords") 581def git_versions_from_keywords(keywords, tag_prefix, verbose): 582 """Get version information from git keywords.""" 583 if not keywords: 584 raise NotThisMethod("no keywords at all, weird") 585 date = keywords.get("date") 586 if date is not None: 587 # Use only the last line. Previous lines may contain GPG signature 588 # information. 589 date = date.splitlines()[-1] 590 591 # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant 592 # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 593 # -like" string, which we must then edit to make compliant), because 594 # it's been around since git-1.5.3, and it's too difficult to 595 # discover which version we're using, or to work around using an 596 # older one. 597 date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 598 refnames = keywords["refnames"].strip() 599 if refnames.startswith("$Format"): 600 if verbose: 601 print("keywords are unexpanded, not using") 602 raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 603 refs = set([r.strip() for r in refnames.strip("()").split(",")]) 604 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 605 # just "foo-1.0". If we see a "tag: " prefix, prefer those. 606 TAG = "tag: " 607 tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 608 if not tags: 609 # Either we're using git < 1.8.3, or there really are no tags. We use 610 # a heuristic: assume all version tags have a digit. The old git %%d 611 # expansion behaves like git log --decorate=short and strips out the 612 # refs/heads/ and refs/tags/ prefixes that would let us distinguish 613 # between branches and tags. By ignoring refnames without digits, we 614 # filter out many common branch names like "release" and 615 # "stabilization", as well as "HEAD" and "master". 616 tags = set([r for r in refs if re.search(r'\d', r)]) 617 if verbose: 618 print("discarding '%%s', no digits" %% ",".join(refs - tags)) 619 if verbose: 620 print("likely tags: %%s" %% ",".join(sorted(tags))) 621 for ref in sorted(tags): 622 # sorting will prefer e.g. "2.0" over "2.0rc1" 623 if ref.startswith(tag_prefix): 624 r = ref[len(tag_prefix):] 625 if verbose: 626 print("picking %%s" %% r) 627 return {"version": r, 628 "full-revisionid": keywords["full"].strip(), 629 "dirty": False, "error": None, 630 "date": date} 631 # no suitable tags, so version is "0+unknown", but full hex is still there 632 if verbose: 633 print("no suitable tags, using unknown + full revision id") 634 return {"version": "0+unknown", 635 "full-revisionid": keywords["full"].strip(), 636 "dirty": False, "error": "no suitable tags", "date": None} 637 638 639@register_vcs_handler("git", "pieces_from_vcs") 640def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 641 """Get version from 'git describe' in the root of the source tree. 642 643 This only gets called if the git-archive 'subst' keywords were *not* 644 expanded, and _version.py hasn't already been rewritten with a short 645 version string, meaning we're inside a checked out source tree. 646 """ 647 GITS = ["git"] 648 if sys.platform == "win32": 649 GITS = ["git.cmd", "git.exe"] 650 651 out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, 652 hide_stderr=True) 653 if rc != 0: 654 if verbose: 655 print("Directory %%s not under git control" %% root) 656 raise NotThisMethod("'git rev-parse --git-dir' returned error") 657 658 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 659 # if there isn't one, this yields HEX[-dirty] (no NUM) 660 describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", 661 "--always", "--long", 662 "--match", "%%s*" %% tag_prefix], 663 cwd=root) 664 # --long was added in git-1.5.5 665 if describe_out is None: 666 raise NotThisMethod("'git describe' failed") 667 describe_out = describe_out.strip() 668 full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 669 if full_out is None: 670 raise NotThisMethod("'git rev-parse' failed") 671 full_out = full_out.strip() 672 673 pieces = {} 674 pieces["long"] = full_out 675 pieces["short"] = full_out[:7] # maybe improved later 676 pieces["error"] = None 677 678 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 679 # TAG might have hyphens. 680 git_describe = describe_out 681 682 # look for -dirty suffix 683 dirty = git_describe.endswith("-dirty") 684 pieces["dirty"] = dirty 685 if dirty: 686 git_describe = git_describe[:git_describe.rindex("-dirty")] 687 688 # now we have TAG-NUM-gHEX or HEX 689 690 if "-" in git_describe: 691 # TAG-NUM-gHEX 692 mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 693 if not mo: 694 # unparseable. Maybe git-describe is misbehaving? 695 pieces["error"] = ("unable to parse git-describe output: '%%s'" 696 %% describe_out) 697 return pieces 698 699 # tag 700 full_tag = mo.group(1) 701 if not full_tag.startswith(tag_prefix): 702 if verbose: 703 fmt = "tag '%%s' doesn't start with prefix '%%s'" 704 print(fmt %% (full_tag, tag_prefix)) 705 pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" 706 %% (full_tag, tag_prefix)) 707 return pieces 708 pieces["closest-tag"] = full_tag[len(tag_prefix):] 709 710 # distance: number of commits since tag 711 pieces["distance"] = int(mo.group(2)) 712 713 # commit: short hex revision ID 714 pieces["short"] = mo.group(3) 715 716 else: 717 # HEX: no tags 718 pieces["closest-tag"] = None 719 count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], 720 cwd=root) 721 pieces["distance"] = int(count_out) # total number of commits 722 723 # commit date: see ISO-8601 comment in git_versions_from_keywords() 724 date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], 725 cwd=root)[0].strip() 726 # Use only the last line. Previous lines may contain GPG signature 727 # information. 728 date = date.splitlines()[-1] 729 pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 730 731 return pieces 732 733 734def plus_or_dot(pieces): 735 """Return a + if we don't already have one, else return a .""" 736 if "+" in pieces.get("closest-tag", ""): 737 return "." 738 return "+" 739 740 741def render_pep440(pieces): 742 """Build up version string, with post-release "local version identifier". 743 744 Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 745 get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 746 747 Exceptions: 748 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 749 """ 750 if pieces["closest-tag"]: 751 rendered = pieces["closest-tag"] 752 if pieces["distance"] or pieces["dirty"]: 753 rendered += plus_or_dot(pieces) 754 rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) 755 if pieces["dirty"]: 756 rendered += ".dirty" 757 else: 758 # exception #1 759 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], 760 pieces["short"]) 761 if pieces["dirty"]: 762 rendered += ".dirty" 763 return rendered 764 765 766def render_pep440_pre(pieces): 767 """TAG[.post0.devDISTANCE] -- No -dirty. 768 769 Exceptions: 770 1: no tags. 0.post0.devDISTANCE 771 """ 772 if pieces["closest-tag"]: 773 rendered = pieces["closest-tag"] 774 if pieces["distance"]: 775 rendered += ".post0.dev%%d" %% pieces["distance"] 776 else: 777 # exception #1 778 rendered = "0.post0.dev%%d" %% pieces["distance"] 779 return rendered 780 781 782def render_pep440_post(pieces): 783 """TAG[.postDISTANCE[.dev0]+gHEX] . 784 785 The ".dev0" means dirty. Note that .dev0 sorts backwards 786 (a dirty tree will appear "older" than the corresponding clean one), 787 but you shouldn't be releasing software with -dirty anyways. 788 789 Exceptions: 790 1: no tags. 0.postDISTANCE[.dev0] 791 """ 792 if pieces["closest-tag"]: 793 rendered = pieces["closest-tag"] 794 if pieces["distance"] or pieces["dirty"]: 795 rendered += ".post%%d" %% pieces["distance"] 796 if pieces["dirty"]: 797 rendered += ".dev0" 798 rendered += plus_or_dot(pieces) 799 rendered += "g%%s" %% pieces["short"] 800 else: 801 # exception #1 802 rendered = "0.post%%d" %% pieces["distance"] 803 if pieces["dirty"]: 804 rendered += ".dev0" 805 rendered += "+g%%s" %% pieces["short"] 806 return rendered 807 808 809def render_pep440_old(pieces): 810 """TAG[.postDISTANCE[.dev0]] . 811 812 The ".dev0" means dirty. 813 814 Exceptions: 815 1: no tags. 0.postDISTANCE[.dev0] 816 """ 817 if pieces["closest-tag"]: 818 rendered = pieces["closest-tag"] 819 if pieces["distance"] or pieces["dirty"]: 820 rendered += ".post%%d" %% pieces["distance"] 821 if pieces["dirty"]: 822 rendered += ".dev0" 823 else: 824 # exception #1 825 rendered = "0.post%%d" %% pieces["distance"] 826 if pieces["dirty"]: 827 rendered += ".dev0" 828 return rendered 829 830 831def render_git_describe(pieces): 832 """TAG[-DISTANCE-gHEX][-dirty]. 833 834 Like 'git describe --tags --dirty --always'. 835 836 Exceptions: 837 1: no tags. HEX[-dirty] (note: no 'g' prefix) 838 """ 839 if pieces["closest-tag"]: 840 rendered = pieces["closest-tag"] 841 if pieces["distance"]: 842 rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 843 else: 844 # exception #1 845 rendered = pieces["short"] 846 if pieces["dirty"]: 847 rendered += "-dirty" 848 return rendered 849 850 851def render_git_describe_long(pieces): 852 """TAG-DISTANCE-gHEX[-dirty]. 853 854 Like 'git describe --tags --dirty --always -long'. 855 The distance/hash is unconditional. 856 857 Exceptions: 858 1: no tags. HEX[-dirty] (note: no 'g' prefix) 859 """ 860 if pieces["closest-tag"]: 861 rendered = pieces["closest-tag"] 862 rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 863 else: 864 # exception #1 865 rendered = pieces["short"] 866 if pieces["dirty"]: 867 rendered += "-dirty" 868 return rendered 869 870 871def render(pieces, style): 872 """Render the given version pieces into the requested style.""" 873 if pieces["error"]: 874 return {"version": "unknown", 875 "full-revisionid": pieces.get("long"), 876 "dirty": None, 877 "error": pieces["error"], 878 "date": None} 879 880 if not style or style == "default": 881 style = "pep440" # the default 882 883 if style == "pep440": 884 rendered = render_pep440(pieces) 885 elif style == "pep440-pre": 886 rendered = render_pep440_pre(pieces) 887 elif style == "pep440-post": 888 rendered = render_pep440_post(pieces) 889 elif style == "pep440-old": 890 rendered = render_pep440_old(pieces) 891 elif style == "git-describe": 892 rendered = render_git_describe(pieces) 893 elif style == "git-describe-long": 894 rendered = render_git_describe_long(pieces) 895 else: 896 raise ValueError("unknown style '%%s'" %% style) 897 898 return {"version": rendered, "full-revisionid": pieces["long"], 899 "dirty": pieces["dirty"], "error": None, 900 "date": pieces.get("date")} 901 902 903def get_versions(): 904 """Get version information or return default if unable to do so.""" 905 # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 906 # __file__, we can work backwards from there to the root. Some 907 # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 908 # case we can only use expanded keywords. 909 910 cfg = get_config() 911 verbose = cfg.verbose 912 913 try: 914 return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, 915 verbose) 916 except NotThisMethod: 917 pass 918 919 try: 920 root = os.path.realpath(__file__) 921 # versionfile_source is the relative path from the top of the source 922 # tree (where the .git directory might live) to this file. Invert 923 # this to find the root from __file__. 924 for i in cfg.versionfile_source.split('/'): 925 root = os.path.dirname(root) 926 except NameError: 927 return {"version": "0+unknown", "full-revisionid": None, 928 "dirty": None, 929 "error": "unable to find root of source tree", 930 "date": None} 931 932 try: 933 pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 934 return render(pieces, cfg.style) 935 except NotThisMethod: 936 pass 937 938 try: 939 if cfg.parentdir_prefix: 940 return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 941 except NotThisMethod: 942 pass 943 944 return {"version": "0+unknown", "full-revisionid": None, 945 "dirty": None, 946 "error": "unable to compute version", "date": None} 947''' 948 949 950@register_vcs_handler("git", "get_keywords") 951def git_get_keywords(versionfile_abs): 952 """Extract version information from the given file.""" 953 # the code embedded in _version.py can just fetch the value of these 954 # keywords. When used from setup.py, we don't want to import _version.py, 955 # so we do it with a regexp instead. This function is not used from 956 # _version.py. 957 keywords = {} 958 try: 959 f = open(versionfile_abs, "r") 960 for line in f.readlines(): 961 if line.strip().startswith("git_refnames ="): 962 mo = re.search(r'=\s*"(.*)"', line) 963 if mo: 964 keywords["refnames"] = mo.group(1) 965 if line.strip().startswith("git_full ="): 966 mo = re.search(r'=\s*"(.*)"', line) 967 if mo: 968 keywords["full"] = mo.group(1) 969 if line.strip().startswith("git_date ="): 970 mo = re.search(r'=\s*"(.*)"', line) 971 if mo: 972 keywords["date"] = mo.group(1) 973 f.close() 974 except EnvironmentError: 975 pass 976 return keywords 977 978 979@register_vcs_handler("git", "keywords") 980def git_versions_from_keywords(keywords, tag_prefix, verbose): 981 """Get version information from git keywords.""" 982 if not keywords: 983 raise NotThisMethod("no keywords at all, weird") 984 date = keywords.get("date") 985 if date is not None: 986 # Use only the last line. Previous lines may contain GPG signature 987 # information. 988 date = date.splitlines()[-1] 989 990 # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant 991 # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 992 # -like" string, which we must then edit to make compliant), because 993 # it's been around since git-1.5.3, and it's too difficult to 994 # discover which version we're using, or to work around using an 995 # older one. 996 date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 997 refnames = keywords["refnames"].strip() 998 if refnames.startswith("$Format"): 999 if verbose: 1000 print("keywords are unexpanded, not using") 1001 raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 1002 refs = set([r.strip() for r in refnames.strip("()").split(",")]) 1003 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 1004 # just "foo-1.0". If we see a "tag: " prefix, prefer those. 1005 TAG = "tag: " 1006 tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) 1007 if not tags: 1008 # Either we're using git < 1.8.3, or there really are no tags. We use 1009 # a heuristic: assume all version tags have a digit. The old git %d 1010 # expansion behaves like git log --decorate=short and strips out the 1011 # refs/heads/ and refs/tags/ prefixes that would let us distinguish 1012 # between branches and tags. By ignoring refnames without digits, we 1013 # filter out many common branch names like "release" and 1014 # "stabilization", as well as "HEAD" and "master". 1015 tags = set([r for r in refs if re.search(r"\d", r)]) 1016 if verbose: 1017 print("discarding '%s', no digits" % ",".join(refs - tags)) 1018 if verbose: 1019 print("likely tags: %s" % ",".join(sorted(tags))) 1020 for ref in sorted(tags): 1021 # sorting will prefer e.g. "2.0" over "2.0rc1" 1022 if ref.startswith(tag_prefix): 1023 r = ref[len(tag_prefix) :] 1024 if verbose: 1025 print("picking %s" % r) 1026 return { 1027 "version": r, 1028 "full-revisionid": keywords["full"].strip(), 1029 "dirty": False, 1030 "error": None, 1031 "date": date, 1032 } 1033 # no suitable tags, so version is "0+unknown", but full hex is still there 1034 if verbose: 1035 print("no suitable tags, using unknown + full revision id") 1036 return { 1037 "version": "0+unknown", 1038 "full-revisionid": keywords["full"].strip(), 1039 "dirty": False, 1040 "error": "no suitable tags", 1041 "date": None, 1042 } 1043 1044 1045@register_vcs_handler("git", "pieces_from_vcs") 1046def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 1047 """Get version from 'git describe' in the root of the source tree. 1048 1049 This only gets called if the git-archive 'subst' keywords were *not* 1050 expanded, and _version.py hasn't already been rewritten with a short 1051 version string, meaning we're inside a checked out source tree. 1052 """ 1053 GITS = ["git"] 1054 if sys.platform == "win32": 1055 GITS = ["git.cmd", "git.exe"] 1056 1057 out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) 1058 if rc != 0: 1059 if verbose: 1060 print("Directory %s not under git control" % root) 1061 raise NotThisMethod("'git rev-parse --git-dir' returned error") 1062 1063 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 1064 # if there isn't one, this yields HEX[-dirty] (no NUM) 1065 describe_out, rc = run_command( 1066 GITS, 1067 [ 1068 "describe", 1069 "--tags", 1070 "--dirty", 1071 "--always", 1072 "--long", 1073 "--match", 1074 "%s*" % tag_prefix, 1075 ], 1076 cwd=root, 1077 ) 1078 # --long was added in git-1.5.5 1079 if describe_out is None: 1080 raise NotThisMethod("'git describe' failed") 1081 describe_out = describe_out.strip() 1082 full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 1083 if full_out is None: 1084 raise NotThisMethod("'git rev-parse' failed") 1085 full_out = full_out.strip() 1086 1087 pieces = {} 1088 pieces["long"] = full_out 1089 pieces["short"] = full_out[:7] # maybe improved later 1090 pieces["error"] = None 1091 1092 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 1093 # TAG might have hyphens. 1094 git_describe = describe_out 1095 1096 # look for -dirty suffix 1097 dirty = git_describe.endswith("-dirty") 1098 pieces["dirty"] = dirty 1099 if dirty: 1100 git_describe = git_describe[: git_describe.rindex("-dirty")] 1101 1102 # now we have TAG-NUM-gHEX or HEX 1103 1104 if "-" in git_describe: 1105 # TAG-NUM-gHEX 1106 mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) 1107 if not mo: 1108 # unparseable. Maybe git-describe is misbehaving? 1109 pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out 1110 return pieces 1111 1112 # tag 1113 full_tag = mo.group(1) 1114 if not full_tag.startswith(tag_prefix): 1115 if verbose: 1116 fmt = "tag '%s' doesn't start with prefix '%s'" 1117 print(fmt % (full_tag, tag_prefix)) 1118 pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % ( 1119 full_tag, 1120 tag_prefix, 1121 ) 1122 return pieces 1123 pieces["closest-tag"] = full_tag[len(tag_prefix) :] 1124 1125 # distance: number of commits since tag 1126 pieces["distance"] = int(mo.group(2)) 1127 1128 # commit: short hex revision ID 1129 pieces["short"] = mo.group(3) 1130 1131 else: 1132 # HEX: no tags 1133 pieces["closest-tag"] = None 1134 count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) 1135 pieces["distance"] = int(count_out) # total number of commits 1136 1137 # commit date: see ISO-8601 comment in git_versions_from_keywords() 1138 date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[ 1139 0 1140 ].strip() 1141 # Use only the last line. Previous lines may contain GPG signature 1142 # information. 1143 date = date.splitlines()[-1] 1144 pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 1145 1146 return pieces 1147 1148 1149def do_vcs_install(manifest_in, versionfile_source, ipy): 1150 """Git-specific installation logic for Versioneer. 1151 1152 For Git, this means creating/changing .gitattributes to mark _version.py 1153 for export-subst keyword substitution. 1154 """ 1155 GITS = ["git"] 1156 if sys.platform == "win32": 1157 GITS = ["git.cmd", "git.exe"] 1158 files = [manifest_in, versionfile_source] 1159 if ipy: 1160 files.append(ipy) 1161 try: 1162 me = __file__ 1163 if me.endswith(".pyc") or me.endswith(".pyo"): 1164 me = os.path.splitext(me)[0] + ".py" 1165 versioneer_file = os.path.relpath(me) 1166 except NameError: 1167 versioneer_file = "versioneer.py" 1168 files.append(versioneer_file) 1169 present = False 1170 try: 1171 f = open(".gitattributes", "r") 1172 for line in f.readlines(): 1173 if line.strip().startswith(versionfile_source): 1174 if "export-subst" in line.strip().split()[1:]: 1175 present = True 1176 f.close() 1177 except EnvironmentError: 1178 pass 1179 if not present: 1180 f = open(".gitattributes", "a+") 1181 f.write("%s export-subst\n" % versionfile_source) 1182 f.close() 1183 files.append(".gitattributes") 1184 run_command(GITS, ["add", "--"] + files) 1185 1186 1187def versions_from_parentdir(parentdir_prefix, root, verbose): 1188 """Try to determine the version from the parent directory name. 1189 1190 Source tarballs conventionally unpack into a directory that includes both 1191 the project name and a version string. We will also support searching up 1192 two directory levels for an appropriately named parent directory 1193 """ 1194 rootdirs = [] 1195 1196 for i in range(3): 1197 dirname = os.path.basename(root) 1198 if dirname.startswith(parentdir_prefix): 1199 return { 1200 "version": dirname[len(parentdir_prefix) :], 1201 "full-revisionid": None, 1202 "dirty": False, 1203 "error": None, 1204 "date": None, 1205 } 1206 else: 1207 rootdirs.append(root) 1208 root = os.path.dirname(root) # up a level 1209 1210 if verbose: 1211 print( 1212 "Tried directories %s but none started with prefix %s" 1213 % (str(rootdirs), parentdir_prefix) 1214 ) 1215 raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 1216 1217 1218SHORT_VERSION_PY = """ 1219# This file was generated by 'versioneer.py' (0.19) from 1220# revision-control system data, or from the parent directory name of an 1221# unpacked source archive. Distribution tarballs contain a pre-generated copy 1222# of this file. 1223 1224import json 1225 1226version_json = ''' 1227%s 1228''' # END VERSION_JSON 1229 1230 1231def get_versions(): 1232 return json.loads(version_json) 1233""" 1234 1235 1236def versions_from_file(filename): 1237 """Try to determine the version from _version.py if present.""" 1238 try: 1239 with open(filename) as f: 1240 contents = f.read() 1241 except EnvironmentError: 1242 raise NotThisMethod("unable to read _version.py") 1243 mo = re.search( 1244 r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S 1245 ) 1246 if not mo: 1247 mo = re.search( 1248 r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S 1249 ) 1250 if not mo: 1251 raise NotThisMethod("no version_json in _version.py") 1252 return json.loads(mo.group(1)) 1253 1254 1255def write_to_version_file(filename, versions): 1256 """Write the given version number to the given _version.py file.""" 1257 os.unlink(filename) 1258 contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) 1259 with open(filename, "w") as f: 1260 f.write(SHORT_VERSION_PY % contents) 1261 1262 print("set %s to '%s'" % (filename, versions["version"])) 1263 1264 1265def plus_or_dot(pieces): 1266 """Return a + if we don't already have one, else return a .""" 1267 if "+" in pieces.get("closest-tag", ""): 1268 return "." 1269 return "+" 1270 1271 1272def render_pep440(pieces): 1273 """Build up version string, with post-release "local version identifier". 1274 1275 Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 1276 get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 1277 1278 Exceptions: 1279 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 1280 """ 1281 if pieces["closest-tag"]: 1282 rendered = pieces["closest-tag"] 1283 if pieces["distance"] or pieces["dirty"]: 1284 rendered += plus_or_dot(pieces) 1285 rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 1286 if pieces["dirty"]: 1287 rendered += ".dirty" 1288 else: 1289 # exception #1 1290 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) 1291 if pieces["dirty"]: 1292 rendered += ".dirty" 1293 return rendered 1294 1295 1296def render_pep440_pre(pieces): 1297 """TAG[.post0.devDISTANCE] -- No -dirty. 1298 1299 Exceptions: 1300 1: no tags. 0.post0.devDISTANCE 1301 """ 1302 if pieces["closest-tag"]: 1303 rendered = pieces["closest-tag"] 1304 if pieces["distance"]: 1305 rendered += ".post0.dev%d" % pieces["distance"] 1306 else: 1307 # exception #1 1308 rendered = "0.post0.dev%d" % pieces["distance"] 1309 return rendered 1310 1311 1312def render_pep440_post(pieces): 1313 """TAG[.postDISTANCE[.dev0]+gHEX] . 1314 1315 The ".dev0" means dirty. Note that .dev0 sorts backwards 1316 (a dirty tree will appear "older" than the corresponding clean one), 1317 but you shouldn't be releasing software with -dirty anyways. 1318 1319 Exceptions: 1320 1: no tags. 0.postDISTANCE[.dev0] 1321 """ 1322 if pieces["closest-tag"]: 1323 rendered = pieces["closest-tag"] 1324 if pieces["distance"] or pieces["dirty"]: 1325 rendered += ".post%d" % pieces["distance"] 1326 if pieces["dirty"]: 1327 rendered += ".dev0" 1328 rendered += plus_or_dot(pieces) 1329 rendered += "g%s" % pieces["short"] 1330 else: 1331 # exception #1 1332 rendered = "0.post%d" % pieces["distance"] 1333 if pieces["dirty"]: 1334 rendered += ".dev0" 1335 rendered += "+g%s" % pieces["short"] 1336 return rendered 1337 1338 1339def render_pep440_old(pieces): 1340 """TAG[.postDISTANCE[.dev0]] . 1341 1342 The ".dev0" means dirty. 1343 1344 Exceptions: 1345 1: no tags. 0.postDISTANCE[.dev0] 1346 """ 1347 if pieces["closest-tag"]: 1348 rendered = pieces["closest-tag"] 1349 if pieces["distance"] or pieces["dirty"]: 1350 rendered += ".post%d" % pieces["distance"] 1351 if pieces["dirty"]: 1352 rendered += ".dev0" 1353 else: 1354 # exception #1 1355 rendered = "0.post%d" % pieces["distance"] 1356 if pieces["dirty"]: 1357 rendered += ".dev0" 1358 return rendered 1359 1360 1361def render_git_describe(pieces): 1362 """TAG[-DISTANCE-gHEX][-dirty]. 1363 1364 Like 'git describe --tags --dirty --always'. 1365 1366 Exceptions: 1367 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1368 """ 1369 if pieces["closest-tag"]: 1370 rendered = pieces["closest-tag"] 1371 if pieces["distance"]: 1372 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1373 else: 1374 # exception #1 1375 rendered = pieces["short"] 1376 if pieces["dirty"]: 1377 rendered += "-dirty" 1378 return rendered 1379 1380 1381def render_git_describe_long(pieces): 1382 """TAG-DISTANCE-gHEX[-dirty]. 1383 1384 Like 'git describe --tags --dirty --always -long'. 1385 The distance/hash is unconditional. 1386 1387 Exceptions: 1388 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1389 """ 1390 if pieces["closest-tag"]: 1391 rendered = pieces["closest-tag"] 1392 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1393 else: 1394 # exception #1 1395 rendered = pieces["short"] 1396 if pieces["dirty"]: 1397 rendered += "-dirty" 1398 return rendered 1399 1400 1401def render(pieces, style): 1402 """Render the given version pieces into the requested style.""" 1403 if pieces["error"]: 1404 return { 1405 "version": "unknown", 1406 "full-revisionid": pieces.get("long"), 1407 "dirty": None, 1408 "error": pieces["error"], 1409 "date": None, 1410 } 1411 1412 if not style or style == "default": 1413 style = "pep440" # the default 1414 1415 if style == "pep440": 1416 rendered = render_pep440(pieces) 1417 elif style == "pep440-pre": 1418 rendered = render_pep440_pre(pieces) 1419 elif style == "pep440-post": 1420 rendered = render_pep440_post(pieces) 1421 elif style == "pep440-old": 1422 rendered = render_pep440_old(pieces) 1423 elif style == "git-describe": 1424 rendered = render_git_describe(pieces) 1425 elif style == "git-describe-long": 1426 rendered = render_git_describe_long(pieces) 1427 else: 1428 raise ValueError("unknown style '%s'" % style) 1429 1430 return { 1431 "version": rendered, 1432 "full-revisionid": pieces["long"], 1433 "dirty": pieces["dirty"], 1434 "error": None, 1435 "date": pieces.get("date"), 1436 } 1437 1438 1439class VersioneerBadRootError(Exception): 1440 """The project root directory is unknown or missing key files.""" 1441 1442 1443def get_versions(verbose=False): 1444 """Get the project version from whatever source is available. 1445 1446 Returns dict with two keys: 'version' and 'full'. 1447 """ 1448 if "versioneer" in sys.modules: 1449 # see the discussion in cmdclass.py:get_cmdclass() 1450 del sys.modules["versioneer"] 1451 1452 root = get_root() 1453 cfg = get_config_from_root(root) 1454 1455 assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" 1456 handlers = HANDLERS.get(cfg.VCS) 1457 assert handlers, "unrecognized VCS '%s'" % cfg.VCS 1458 verbose = verbose or cfg.verbose 1459 assert ( 1460 cfg.versionfile_source is not None 1461 ), "please set versioneer.versionfile_source" 1462 assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" 1463 1464 versionfile_abs = os.path.join(root, cfg.versionfile_source) 1465 1466 # extract version from first of: _version.py, VCS command (e.g. 'git 1467 # describe'), parentdir. This is meant to work for developers using a 1468 # source checkout, for users of a tarball created by 'setup.py sdist', 1469 # and for users of a tarball/zipball created by 'git archive' or github's 1470 # download-from-tag feature or the equivalent in other VCSes. 1471 1472 get_keywords_f = handlers.get("get_keywords") 1473 from_keywords_f = handlers.get("keywords") 1474 if get_keywords_f and from_keywords_f: 1475 try: 1476 keywords = get_keywords_f(versionfile_abs) 1477 ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) 1478 if verbose: 1479 print("got version from expanded keyword %s" % ver) 1480 return ver 1481 except NotThisMethod: 1482 pass 1483 1484 try: 1485 ver = versions_from_file(versionfile_abs) 1486 if verbose: 1487 print("got version from file %s %s" % (versionfile_abs, ver)) 1488 return ver 1489 except NotThisMethod: 1490 pass 1491 1492 from_vcs_f = handlers.get("pieces_from_vcs") 1493 if from_vcs_f: 1494 try: 1495 pieces = from_vcs_f(cfg.tag_prefix, root, verbose) 1496 ver = render(pieces, cfg.style) 1497 if verbose: 1498 print("got version from VCS %s" % ver) 1499 return ver 1500 except NotThisMethod: 1501 pass 1502 1503 try: 1504 if cfg.parentdir_prefix: 1505 ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 1506 if verbose: 1507 print("got version from parentdir %s" % ver) 1508 return ver 1509 except NotThisMethod: 1510 pass 1511 1512 if verbose: 1513 print("unable to compute version") 1514 1515 return { 1516 "version": "0+unknown", 1517 "full-revisionid": None, 1518 "dirty": None, 1519 "error": "unable to compute version", 1520 "date": None, 1521 } 1522 1523 1524def get_version(): 1525 """Get the short version string for this project.""" 1526 return get_versions()["version"] 1527 1528 1529def get_cmdclass(cmdclass=None): 1530 """Get the custom setuptools/distutils subclasses used by Versioneer. 1531 1532 If the package uses a different cmdclass (e.g. one from numpy), it 1533 should be provide as an argument. 1534 """ 1535 if "versioneer" in sys.modules: 1536 del sys.modules["versioneer"] 1537 # this fixes the "python setup.py develop" case (also 'install' and 1538 # 'easy_install .'), in which subdependencies of the main project are 1539 # built (using setup.py bdist_egg) in the same python process. Assume 1540 # a main project A and a dependency B, which use different versions 1541 # of Versioneer. A's setup.py imports A's Versioneer, leaving it in 1542 # sys.modules by the time B's setup.py is executed, causing B to run 1543 # with the wrong versioneer. Setuptools wraps the sub-dep builds in a 1544 # sandbox that restores sys.modules to it's pre-build state, so the 1545 # parent is protected against the child's "import versioneer". By 1546 # removing ourselves from sys.modules here, before the child build 1547 # happens, we protect the child from the parent's versioneer too. 1548 # Also see https://github.com/python-versioneer/python-versioneer/issues/52 1549 1550 cmds = {} if cmdclass is None else cmdclass.copy() 1551 1552 # we add "version" to both distutils and setuptools 1553 from distutils.core import Command 1554 1555 class cmd_version(Command): 1556 description = "report generated version string" 1557 user_options = [] 1558 boolean_options = [] 1559 1560 def initialize_options(self): 1561 pass 1562 1563 def finalize_options(self): 1564 pass 1565 1566 def run(self): 1567 vers = get_versions(verbose=True) 1568 print("Version: %s" % vers["version"]) 1569 print(" full-revisionid: %s" % vers.get("full-revisionid")) 1570 print(" dirty: %s" % vers.get("dirty")) 1571 print(" date: %s" % vers.get("date")) 1572 if vers["error"]: 1573 print(" error: %s" % vers["error"]) 1574 1575 cmds["version"] = cmd_version 1576 1577 # we override "build_py" in both distutils and setuptools 1578 # 1579 # most invocation pathways end up running build_py: 1580 # distutils/build -> build_py 1581 # distutils/install -> distutils/build ->.. 1582 # setuptools/bdist_wheel -> distutils/install ->.. 1583 # setuptools/bdist_egg -> distutils/install_lib -> build_py 1584 # setuptools/install -> bdist_egg ->.. 1585 # setuptools/develop -> ? 1586 # pip install: 1587 # copies source tree to a tempdir before running egg_info/etc 1588 # if .git isn't copied too, 'git describe' will fail 1589 # then does setup.py bdist_wheel, or sometimes setup.py install 1590 # setup.py egg_info -> ? 1591 1592 # we override different "build_py" commands for both environments 1593 if "build_py" in cmds: 1594 _build_py = cmds["build_py"] 1595 elif "setuptools" in sys.modules: 1596 from setuptools.command.build_py import build_py as _build_py 1597 else: 1598 from distutils.command.build_py import build_py as _build_py 1599 1600 class cmd_build_py(_build_py): 1601 def run(self): 1602 root = get_root() 1603 cfg = get_config_from_root(root) 1604 versions = get_versions() 1605 _build_py.run(self) 1606 # now locate _version.py in the new build/ directory and replace 1607 # it with an updated value 1608 if cfg.versionfile_build: 1609 target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) 1610 print("UPDATING %s" % target_versionfile) 1611 write_to_version_file(target_versionfile, versions) 1612 1613 cmds["build_py"] = cmd_build_py 1614 1615 if "setuptools" in sys.modules: 1616 from setuptools.command.build_ext import build_ext as _build_ext 1617 else: 1618 from distutils.command.build_ext import build_ext as _build_ext 1619 1620 class cmd_build_ext(_build_ext): 1621 def run(self): 1622 root = get_root() 1623 cfg = get_config_from_root(root) 1624 versions = get_versions() 1625 _build_ext.run(self) 1626 if self.inplace: 1627 # build_ext --inplace will only build extensions in 1628 # build/lib<..> dir with no _version.py to write to. 1629 # As in place builds will already have a _version.py 1630 # in the module dir, we do not need to write one. 1631 return 1632 # now locate _version.py in the new build/ directory and replace 1633 # it with an updated value 1634 target_versionfile = os.path.join(self.build_lib, cfg.versionfile_source) 1635 print("UPDATING %s" % target_versionfile) 1636 write_to_version_file(target_versionfile, versions) 1637 1638 cmds["build_ext"] = cmd_build_ext 1639 1640 if "cx_Freeze" in sys.modules: # cx_freeze enabled? 1641 from cx_Freeze.dist import build_exe as _build_exe 1642 1643 # nczeczulin reports that py2exe won't like the pep440-style string 1644 # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. 1645 # setup(console=[{ 1646 # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION 1647 # "product_version": versioneer.get_version(), 1648 # ... 1649 1650 class cmd_build_exe(_build_exe): 1651 def run(self): 1652 root = get_root() 1653 cfg = get_config_from_root(root) 1654 versions = get_versions() 1655 target_versionfile = cfg.versionfile_source 1656 print("UPDATING %s" % target_versionfile) 1657 write_to_version_file(target_versionfile, versions) 1658 1659 _build_exe.run(self) 1660 os.unlink(target_versionfile) 1661 with open(cfg.versionfile_source, "w") as f: 1662 LONG = LONG_VERSION_PY[cfg.VCS] 1663 f.write( 1664 LONG 1665 % { 1666 "DOLLAR": "$", 1667 "STYLE": cfg.style, 1668 "TAG_PREFIX": cfg.tag_prefix, 1669 "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1670 "VERSIONFILE_SOURCE": cfg.versionfile_source, 1671 } 1672 ) 1673 1674 cmds["build_exe"] = cmd_build_exe 1675 del cmds["build_py"] 1676 1677 if "py2exe" in sys.modules: # py2exe enabled? 1678 from py2exe.distutils_buildexe import py2exe as _py2exe 1679 1680 class cmd_py2exe(_py2exe): 1681 def run(self): 1682 root = get_root() 1683 cfg = get_config_from_root(root) 1684 versions = get_versions() 1685 target_versionfile = cfg.versionfile_source 1686 print("UPDATING %s" % target_versionfile) 1687 write_to_version_file(target_versionfile, versions) 1688 1689 _py2exe.run(self) 1690 os.unlink(target_versionfile) 1691 with open(cfg.versionfile_source, "w") as f: 1692 LONG = LONG_VERSION_PY[cfg.VCS] 1693 f.write( 1694 LONG 1695 % { 1696 "DOLLAR": "$", 1697 "STYLE": cfg.style, 1698 "TAG_PREFIX": cfg.tag_prefix, 1699 "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1700 "VERSIONFILE_SOURCE": cfg.versionfile_source, 1701 } 1702 ) 1703 1704 cmds["py2exe"] = cmd_py2exe 1705 1706 # we override different "sdist" commands for both environments 1707 if "sdist" in cmds: 1708 _sdist = cmds["sdist"] 1709 elif "setuptools" in sys.modules: 1710 from setuptools.command.sdist import sdist as _sdist 1711 else: 1712 from distutils.command.sdist import sdist as _sdist 1713 1714 class cmd_sdist(_sdist): 1715 def run(self): 1716 versions = get_versions() 1717 self._versioneer_generated_versions = versions 1718 # unless we update this, the command will keep using the old 1719 # version 1720 self.distribution.metadata.version = versions["version"] 1721 return _sdist.run(self) 1722 1723 def make_release_tree(self, base_dir, files): 1724 root = get_root() 1725 cfg = get_config_from_root(root) 1726 _sdist.make_release_tree(self, base_dir, files) 1727 # now locate _version.py in the new base_dir directory 1728 # (remembering that it may be a hardlink) and replace it with an 1729 # updated value 1730 target_versionfile = os.path.join(base_dir, cfg.versionfile_source) 1731 print("UPDATING %s" % target_versionfile) 1732 write_to_version_file( 1733 target_versionfile, self._versioneer_generated_versions 1734 ) 1735 1736 cmds["sdist"] = cmd_sdist 1737 1738 return cmds 1739 1740 1741CONFIG_ERROR = """ 1742setup.cfg is missing the necessary Versioneer configuration. You need 1743a section like: 1744 1745 [versioneer] 1746 VCS = git 1747 style = pep440 1748 versionfile_source = src/myproject/_version.py 1749 versionfile_build = myproject/_version.py 1750 tag_prefix = 1751 parentdir_prefix = myproject- 1752 1753You will also need to edit your setup.py to use the results: 1754 1755 import versioneer 1756 setup(version=versioneer.get_version(), 1757 cmdclass=versioneer.get_cmdclass(), ...) 1758 1759Please read the docstring in ./versioneer.py for configuration instructions, 1760edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. 1761""" 1762 1763SAMPLE_CONFIG = """ 1764# See the docstring in versioneer.py for instructions. Note that you must 1765# re-run 'versioneer.py setup' after changing this section, and commit the 1766# resulting files. 1767 1768[versioneer] 1769#VCS = git 1770#style = pep440 1771#versionfile_source = 1772#versionfile_build = 1773#tag_prefix = 1774#parentdir_prefix = 1775 1776""" 1777 1778INIT_PY_SNIPPET = """ 1779from ._version import get_versions 1780__version__ = get_versions()['version'] 1781del get_versions 1782""" 1783 1784 1785def do_setup(): 1786 """Do main VCS-independent setup function for installing Versioneer.""" 1787 root = get_root() 1788 try: 1789 cfg = get_config_from_root(root) 1790 except ( 1791 EnvironmentError, 1792 configparser.NoSectionError, 1793 configparser.NoOptionError, 1794 ) as e: 1795 if isinstance(e, (EnvironmentError, configparser.NoSectionError)): 1796 print("Adding sample versioneer config to setup.cfg", file=sys.stderr) 1797 with open(os.path.join(root, "setup.cfg"), "a") as f: 1798 f.write(SAMPLE_CONFIG) 1799 print(CONFIG_ERROR, file=sys.stderr) 1800 return 1 1801 1802 print(" creating %s" % cfg.versionfile_source) 1803 with open(cfg.versionfile_source, "w") as f: 1804 LONG = LONG_VERSION_PY[cfg.VCS] 1805 f.write( 1806 LONG 1807 % { 1808 "DOLLAR": "$", 1809 "STYLE": cfg.style, 1810 "TAG_PREFIX": cfg.tag_prefix, 1811 "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1812 "VERSIONFILE_SOURCE": cfg.versionfile_source, 1813 } 1814 ) 1815 1816 ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") 1817 if os.path.exists(ipy): 1818 try: 1819 with open(ipy, "r") as f: 1820 old = f.read() 1821 except EnvironmentError: 1822 old = "" 1823 if INIT_PY_SNIPPET not in old: 1824 print(" appending to %s" % ipy) 1825 with open(ipy, "a") as f: 1826 f.write(INIT_PY_SNIPPET) 1827 else: 1828 print(" %s unmodified" % ipy) 1829 else: 1830 print(" %s doesn't exist, ok" % ipy) 1831 ipy = None 1832 1833 # Make sure both the top-level "versioneer.py" and versionfile_source 1834 # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so 1835 # they'll be copied into source distributions. Pip won't be able to 1836 # install the package without this. 1837 manifest_in = os.path.join(root, "MANIFEST.in") 1838 simple_includes = set() 1839 try: 1840 with open(manifest_in, "r") as f: 1841 for line in f: 1842 if line.startswith("include "): 1843 for include in line.split()[1:]: 1844 simple_includes.add(include) 1845 except EnvironmentError: 1846 pass 1847 # That doesn't cover everything MANIFEST.in can do 1848 # (http://docs.python.org/2/distutils/sourcedist.html#commands), so 1849 # it might give some false negatives. Appending redundant 'include' 1850 # lines is safe, though. 1851 if "versioneer.py" not in simple_includes: 1852 print(" appending 'versioneer.py' to MANIFEST.in") 1853 with open(manifest_in, "a") as f: 1854 f.write("include versioneer.py\n") 1855 else: 1856 print(" 'versioneer.py' already in MANIFEST.in") 1857 if cfg.versionfile_source not in simple_includes: 1858 print( 1859 " appending versionfile_source ('%s') to MANIFEST.in" 1860 % cfg.versionfile_source 1861 ) 1862 with open(manifest_in, "a") as f: 1863 f.write("include %s\n" % cfg.versionfile_source) 1864 else: 1865 print(" versionfile_source already in MANIFEST.in") 1866 1867 # Make VCS-specific changes. For git, this means creating/changing 1868 # .gitattributes to mark _version.py for export-subst keyword 1869 # substitution. 1870 do_vcs_install(manifest_in, cfg.versionfile_source, ipy) 1871 return 0 1872 1873 1874def scan_setup_py(): 1875 """Validate the contents of setup.py against Versioneer's expectations.""" 1876 found = set() 1877 setters = False 1878 errors = 0 1879 with open("setup.py", "r") as f: 1880 for line in f.readlines(): 1881 if "import versioneer" in line: 1882 found.add("import") 1883 if "versioneer.get_cmdclass()" in line: 1884 found.add("cmdclass") 1885 if "versioneer.get_version()" in line: 1886 found.add("get_version") 1887 if "versioneer.VCS" in line: 1888 setters = True 1889 if "versioneer.versionfile_source" in line: 1890 setters = True 1891 if len(found) != 3: 1892 print("") 1893 print("Your setup.py appears to be missing some important items") 1894 print("(but I might be wrong). Please make sure it has something") 1895 print("roughly like the following:") 1896 print("") 1897 print(" import versioneer") 1898 print(" setup( version=versioneer.get_version(),") 1899 print(" cmdclass=versioneer.get_cmdclass(), ...)") 1900 print("") 1901 errors += 1 1902 if setters: 1903 print("You should remove lines like 'versioneer.VCS = ' and") 1904 print("'versioneer.versionfile_source = ' . This configuration") 1905 print("now lives in setup.cfg, and should be removed from setup.py") 1906 print("") 1907 errors += 1 1908 return errors 1909 1910 1911if __name__ == "__main__": 1912 cmd = sys.argv[1] 1913 if cmd == "setup": 1914 errors = do_setup() 1915 errors += scan_setup_py() 1916 if errors: 1917 sys.exit(1) 1918