1# Version: 0.20 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* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools 259 plugin 260 261## License 262 263To make Versioneer easier to embed, all its code is dedicated to the public 264domain. The `_version.py` that it creates is also in the public domain. 265Specifically, both are released under the Creative Commons "Public Domain 266Dedication" license (CC0-1.0), as described in 267https://creativecommons.org/publicdomain/zero/1.0/ . 268 269[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg 270[pypi-url]: https://pypi.python.org/pypi/versioneer/ 271[travis-image]: 272https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg 273[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer 274 275""" 276 277import configparser 278import errno 279import json 280import os 281import re 282import subprocess 283import sys 284 285 286class VersioneerConfig: # pylint: disable=too-few-public-methods # noqa 287 """Container for Versioneer configuration parameters.""" 288 289 290def get_root(): 291 """Get the project root directory. 292 293 We require that all commands are run from the project root, i.e. the 294 directory that contains setup.py, setup.cfg, and versioneer.py . 295 """ 296 root = os.path.realpath(os.path.abspath(os.getcwd())) 297 setup_py = os.path.join(root, "setup.py") 298 versioneer_py = os.path.join(root, "versioneer.py") 299 if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 300 # allow 'python path/to/setup.py COMMAND' 301 root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) 302 setup_py = os.path.join(root, "setup.py") 303 versioneer_py = os.path.join(root, "versioneer.py") 304 if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 305 err = ( 306 "Versioneer was unable to run the project root directory. " 307 "Versioneer requires setup.py to be executed from " 308 "its immediate directory (like 'python setup.py COMMAND'), " 309 "or in a way that lets it use sys.argv[0] to find the root " 310 "(like 'python path/to/setup.py COMMAND')." 311 ) 312 raise VersioneerBadRootError(err) 313 try: 314 # Certain runtime workflows (setup.py install/develop in a setuptools 315 # tree) execute all dependencies in a single python process, so 316 # "versioneer" may be imported multiple times, and python's shared 317 # module-import table will cache the first one. So we can't use 318 # os.path.dirname(__file__), as that will find whichever 319 # versioneer.py was first imported, even in later projects. 320 my_path = os.path.realpath(os.path.abspath(__file__)) 321 me_dir = os.path.normcase(os.path.splitext(my_path)[0]) 322 vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) 323 if me_dir != vsr_dir: 324 print( 325 "Warning: build in %s is using versioneer.py from %s" 326 % (os.path.dirname(my_path), versioneer_py) 327 ) 328 except NameError: 329 pass 330 return root 331 332 333def get_config_from_root(root): 334 """Read the project setup.cfg file to determine Versioneer config.""" 335 # This might raise EnvironmentError (if setup.cfg is missing), or 336 # configparser.NoSectionError (if it lacks a [versioneer] section), or 337 # configparser.NoOptionError (if it lacks "VCS="). See the docstring at 338 # the top of versioneer.py for instructions on writing your setup.cfg . 339 setup_cfg = os.path.join(root, "setup.cfg") 340 parser = configparser.ConfigParser() 341 with open(setup_cfg, "r") as cfg_file: 342 parser.read_file(cfg_file) 343 VCS = parser.get("versioneer", "VCS") # mandatory 344 345 # Dict-like interface for non-mandatory entries 346 section = parser["versioneer"] 347 348 # pylint:disable=attribute-defined-outside-init # noqa 349 cfg = VersioneerConfig() 350 cfg.VCS = VCS 351 cfg.style = section.get("style", "") 352 cfg.versionfile_source = section.get("versionfile_source") 353 cfg.versionfile_build = section.get("versionfile_build") 354 cfg.tag_prefix = section.get("tag_prefix") 355 if cfg.tag_prefix in ("''", '""'): 356 cfg.tag_prefix = "" 357 cfg.parentdir_prefix = section.get("parentdir_prefix") 358 cfg.verbose = section.get("verbose") 359 return cfg 360 361 362class NotThisMethod(Exception): 363 """Exception raised if a method is not valid for the current scenario.""" 364 365 366# these dictionaries contain VCS-specific tools 367LONG_VERSION_PY = {} 368HANDLERS = {} 369 370 371def register_vcs_handler(vcs, method): # decorator 372 """Create decorator to mark a method as the handler of a VCS.""" 373 374 def decorate(f): 375 """Store f in HANDLERS[vcs][method].""" 376 HANDLERS.setdefault(vcs, {})[method] = f 377 return f 378 379 return decorate 380 381 382# pylint:disable=too-many-arguments,consider-using-with # noqa 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 process = None 387 for command in commands: 388 try: 389 dispcmd = str([command] + args) 390 # remember shell=False, so use git.cmd on windows, not just git 391 process = subprocess.Popen( 392 [command] + 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 = process.communicate()[0].strip().decode() 412 if process.returncode != 0: 413 if verbose: 414 print("unable to run %s (error)" % dispcmd) 415 print("stdout was %s" % stdout) 416 return None, process.returncode 417 return stdout, process.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.20 (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: # pylint: disable=too-few-public-methods 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 491# pylint:disable=too-many-arguments,consider-using-with # noqa 492def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, 493 env=None): 494 """Call the given command(s).""" 495 assert isinstance(commands, list) 496 process = None 497 for command in commands: 498 try: 499 dispcmd = str([command] + args) 500 # remember shell=False, so use git.cmd on windows, not just git 501 process = subprocess.Popen([command] + args, cwd=cwd, env=env, 502 stdout=subprocess.PIPE, 503 stderr=(subprocess.PIPE if hide_stderr 504 else None)) 505 break 506 except EnvironmentError: 507 e = sys.exc_info()[1] 508 if e.errno == errno.ENOENT: 509 continue 510 if verbose: 511 print("unable to run %%s" %% dispcmd) 512 print(e) 513 return None, None 514 else: 515 if verbose: 516 print("unable to find command, tried %%s" %% (commands,)) 517 return None, None 518 stdout = process.communicate()[0].strip().decode() 519 if process.returncode != 0: 520 if verbose: 521 print("unable to run %%s (error)" %% dispcmd) 522 print("stdout was %%s" %% stdout) 523 return None, process.returncode 524 return stdout, process.returncode 525 526 527def versions_from_parentdir(parentdir_prefix, root, verbose): 528 """Try to determine the version from the parent directory name. 529 530 Source tarballs conventionally unpack into a directory that includes both 531 the project name and a version string. We will also support searching up 532 two directory levels for an appropriately named parent directory 533 """ 534 rootdirs = [] 535 536 for _ in range(3): 537 dirname = os.path.basename(root) 538 if dirname.startswith(parentdir_prefix): 539 return {"version": dirname[len(parentdir_prefix):], 540 "full-revisionid": None, 541 "dirty": False, "error": None, "date": None} 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 with open(versionfile_abs, "r") as fobj: 561 for line in fobj: 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 except EnvironmentError: 575 pass 576 return keywords 577 578 579@register_vcs_handler("git", "keywords") 580def git_versions_from_keywords(keywords, tag_prefix, verbose): 581 """Get version information from git keywords.""" 582 if "refnames" not in keywords: 583 raise NotThisMethod("Short version file found") 584 date = keywords.get("date") 585 if date is not None: 586 # Use only the last line. Previous lines may contain GPG signature 587 # information. 588 date = date.splitlines()[-1] 589 590 # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant 591 # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 592 # -like" string, which we must then edit to make compliant), because 593 # it's been around since git-1.5.3, and it's too difficult to 594 # discover which version we're using, or to work around using an 595 # older one. 596 date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 597 refnames = keywords["refnames"].strip() 598 if refnames.startswith("$Format"): 599 if verbose: 600 print("keywords are unexpanded, not using") 601 raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 602 refs = {r.strip() for r in refnames.strip("()").split(",")} 603 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 604 # just "foo-1.0". If we see a "tag: " prefix, prefer those. 605 TAG = "tag: " 606 tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} 607 if not tags: 608 # Either we're using git < 1.8.3, or there really are no tags. We use 609 # a heuristic: assume all version tags have a digit. The old git %%d 610 # expansion behaves like git log --decorate=short and strips out the 611 # refs/heads/ and refs/tags/ prefixes that would let us distinguish 612 # between branches and tags. By ignoring refnames without digits, we 613 # filter out many common branch names like "release" and 614 # "stabilization", as well as "HEAD" and "master". 615 tags = {r for r in refs if re.search(r'\d', r)} 616 if verbose: 617 print("discarding '%%s', no digits" %% ",".join(refs - tags)) 618 if verbose: 619 print("likely tags: %%s" %% ",".join(sorted(tags))) 620 for ref in sorted(tags): 621 # sorting will prefer e.g. "2.0" over "2.0rc1" 622 if ref.startswith(tag_prefix): 623 r = ref[len(tag_prefix):] 624 # Filter out refs that exactly match prefix or that don't start 625 # with a number once the prefix is stripped (mostly a concern 626 # when prefix is '') 627 if not re.match(r'\d', r): 628 continue 629 if verbose: 630 print("picking %%s" %% r) 631 return {"version": r, 632 "full-revisionid": keywords["full"].strip(), 633 "dirty": False, "error": None, 634 "date": date} 635 # no suitable tags, so version is "0+unknown", but full hex is still there 636 if verbose: 637 print("no suitable tags, using unknown + full revision id") 638 return {"version": "0+unknown", 639 "full-revisionid": keywords["full"].strip(), 640 "dirty": False, "error": "no suitable tags", "date": None} 641 642 643@register_vcs_handler("git", "pieces_from_vcs") 644def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): 645 """Get version from 'git describe' in the root of the source tree. 646 647 This only gets called if the git-archive 'subst' keywords were *not* 648 expanded, and _version.py hasn't already been rewritten with a short 649 version string, meaning we're inside a checked out source tree. 650 """ 651 GITS = ["git"] 652 if sys.platform == "win32": 653 GITS = ["git.cmd", "git.exe"] 654 655 _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, 656 hide_stderr=True) 657 if rc != 0: 658 if verbose: 659 print("Directory %%s not under git control" %% root) 660 raise NotThisMethod("'git rev-parse --git-dir' returned error") 661 662 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 663 # if there isn't one, this yields HEX[-dirty] (no NUM) 664 describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty", 665 "--always", "--long", 666 "--match", "%%s*" %% tag_prefix], 667 cwd=root) 668 # --long was added in git-1.5.5 669 if describe_out is None: 670 raise NotThisMethod("'git describe' failed") 671 describe_out = describe_out.strip() 672 full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) 673 if full_out is None: 674 raise NotThisMethod("'git rev-parse' failed") 675 full_out = full_out.strip() 676 677 pieces = {} 678 pieces["long"] = full_out 679 pieces["short"] = full_out[:7] # maybe improved later 680 pieces["error"] = None 681 682 branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], 683 cwd=root) 684 # --abbrev-ref was added in git-1.6.3 685 if rc != 0 or branch_name is None: 686 raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") 687 branch_name = branch_name.strip() 688 689 if branch_name == "HEAD": 690 # If we aren't exactly on a branch, pick a branch which represents 691 # the current commit. If all else fails, we are on a branchless 692 # commit. 693 branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) 694 # --contains was added in git-1.5.4 695 if rc != 0 or branches is None: 696 raise NotThisMethod("'git branch --contains' returned error") 697 branches = branches.split("\n") 698 699 # Remove the first line if we're running detached 700 if "(" in branches[0]: 701 branches.pop(0) 702 703 # Strip off the leading "* " from the list of branches. 704 branches = [branch[2:] for branch in branches] 705 if "master" in branches: 706 branch_name = "master" 707 elif not branches: 708 branch_name = None 709 else: 710 # Pick the first branch that is returned. Good or bad. 711 branch_name = branches[0] 712 713 pieces["branch"] = branch_name 714 715 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 716 # TAG might have hyphens. 717 git_describe = describe_out 718 719 # look for -dirty suffix 720 dirty = git_describe.endswith("-dirty") 721 pieces["dirty"] = dirty 722 if dirty: 723 git_describe = git_describe[:git_describe.rindex("-dirty")] 724 725 # now we have TAG-NUM-gHEX or HEX 726 727 if "-" in git_describe: 728 # TAG-NUM-gHEX 729 mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 730 if not mo: 731 # unparseable. Maybe git-describe is misbehaving? 732 pieces["error"] = ("unable to parse git-describe output: '%%s'" 733 %% describe_out) 734 return pieces 735 736 # tag 737 full_tag = mo.group(1) 738 if not full_tag.startswith(tag_prefix): 739 if verbose: 740 fmt = "tag '%%s' doesn't start with prefix '%%s'" 741 print(fmt %% (full_tag, tag_prefix)) 742 pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" 743 %% (full_tag, tag_prefix)) 744 return pieces 745 pieces["closest-tag"] = full_tag[len(tag_prefix):] 746 747 # distance: number of commits since tag 748 pieces["distance"] = int(mo.group(2)) 749 750 # commit: short hex revision ID 751 pieces["short"] = mo.group(3) 752 753 else: 754 # HEX: no tags 755 pieces["closest-tag"] = None 756 count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) 757 pieces["distance"] = int(count_out) # total number of commits 758 759 # commit date: see ISO-8601 comment in git_versions_from_keywords() 760 date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() 761 # Use only the last line. Previous lines may contain GPG signature 762 # information. 763 date = date.splitlines()[-1] 764 pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 765 766 return pieces 767 768 769def plus_or_dot(pieces): 770 """Return a + if we don't already have one, else return a .""" 771 if "+" in pieces.get("closest-tag", ""): 772 return "." 773 return "+" 774 775 776def render_pep440(pieces): 777 """Build up version string, with post-release "local version identifier". 778 779 Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 780 get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 781 782 Exceptions: 783 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 784 """ 785 if pieces["closest-tag"]: 786 rendered = pieces["closest-tag"] 787 if pieces["distance"] or pieces["dirty"]: 788 rendered += plus_or_dot(pieces) 789 rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) 790 if pieces["dirty"]: 791 rendered += ".dirty" 792 else: 793 # exception #1 794 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], 795 pieces["short"]) 796 if pieces["dirty"]: 797 rendered += ".dirty" 798 return rendered 799 800 801def render_pep440_branch(pieces): 802 """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . 803 804 The ".dev0" means not master branch. Note that .dev0 sorts backwards 805 (a feature branch will appear "older" than the master branch). 806 807 Exceptions: 808 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] 809 """ 810 if pieces["closest-tag"]: 811 rendered = pieces["closest-tag"] 812 if pieces["distance"] or pieces["dirty"]: 813 if pieces["branch"] != "master": 814 rendered += ".dev0" 815 rendered += plus_or_dot(pieces) 816 rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) 817 if pieces["dirty"]: 818 rendered += ".dirty" 819 else: 820 # exception #1 821 rendered = "0" 822 if pieces["branch"] != "master": 823 rendered += ".dev0" 824 rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], 825 pieces["short"]) 826 if pieces["dirty"]: 827 rendered += ".dirty" 828 return rendered 829 830 831def render_pep440_pre(pieces): 832 """TAG[.post0.devDISTANCE] -- No -dirty. 833 834 Exceptions: 835 1: no tags. 0.post0.devDISTANCE 836 """ 837 if pieces["closest-tag"]: 838 rendered = pieces["closest-tag"] 839 if pieces["distance"]: 840 rendered += ".post0.dev%%d" %% pieces["distance"] 841 else: 842 # exception #1 843 rendered = "0.post0.dev%%d" %% pieces["distance"] 844 return rendered 845 846 847def render_pep440_post(pieces): 848 """TAG[.postDISTANCE[.dev0]+gHEX] . 849 850 The ".dev0" means dirty. Note that .dev0 sorts backwards 851 (a dirty tree will appear "older" than the corresponding clean one), 852 but you shouldn't be releasing software with -dirty anyways. 853 854 Exceptions: 855 1: no tags. 0.postDISTANCE[.dev0] 856 """ 857 if pieces["closest-tag"]: 858 rendered = pieces["closest-tag"] 859 if pieces["distance"] or pieces["dirty"]: 860 rendered += ".post%%d" %% pieces["distance"] 861 if pieces["dirty"]: 862 rendered += ".dev0" 863 rendered += plus_or_dot(pieces) 864 rendered += "g%%s" %% pieces["short"] 865 else: 866 # exception #1 867 rendered = "0.post%%d" %% pieces["distance"] 868 if pieces["dirty"]: 869 rendered += ".dev0" 870 rendered += "+g%%s" %% pieces["short"] 871 return rendered 872 873 874def render_pep440_post_branch(pieces): 875 """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . 876 877 The ".dev0" means not master branch. 878 879 Exceptions: 880 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] 881 """ 882 if pieces["closest-tag"]: 883 rendered = pieces["closest-tag"] 884 if pieces["distance"] or pieces["dirty"]: 885 rendered += ".post%%d" %% pieces["distance"] 886 if pieces["branch"] != "master": 887 rendered += ".dev0" 888 rendered += plus_or_dot(pieces) 889 rendered += "g%%s" %% pieces["short"] 890 if pieces["dirty"]: 891 rendered += ".dirty" 892 else: 893 # exception #1 894 rendered = "0.post%%d" %% pieces["distance"] 895 if pieces["branch"] != "master": 896 rendered += ".dev0" 897 rendered += "+g%%s" %% pieces["short"] 898 if pieces["dirty"]: 899 rendered += ".dirty" 900 return rendered 901 902 903def render_pep440_old(pieces): 904 """TAG[.postDISTANCE[.dev0]] . 905 906 The ".dev0" means dirty. 907 908 Exceptions: 909 1: no tags. 0.postDISTANCE[.dev0] 910 """ 911 if pieces["closest-tag"]: 912 rendered = pieces["closest-tag"] 913 if pieces["distance"] or pieces["dirty"]: 914 rendered += ".post%%d" %% pieces["distance"] 915 if pieces["dirty"]: 916 rendered += ".dev0" 917 else: 918 # exception #1 919 rendered = "0.post%%d" %% pieces["distance"] 920 if pieces["dirty"]: 921 rendered += ".dev0" 922 return rendered 923 924 925def render_git_describe(pieces): 926 """TAG[-DISTANCE-gHEX][-dirty]. 927 928 Like 'git describe --tags --dirty --always'. 929 930 Exceptions: 931 1: no tags. HEX[-dirty] (note: no 'g' prefix) 932 """ 933 if pieces["closest-tag"]: 934 rendered = pieces["closest-tag"] 935 if pieces["distance"]: 936 rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 937 else: 938 # exception #1 939 rendered = pieces["short"] 940 if pieces["dirty"]: 941 rendered += "-dirty" 942 return rendered 943 944 945def render_git_describe_long(pieces): 946 """TAG-DISTANCE-gHEX[-dirty]. 947 948 Like 'git describe --tags --dirty --always -long'. 949 The distance/hash is unconditional. 950 951 Exceptions: 952 1: no tags. HEX[-dirty] (note: no 'g' prefix) 953 """ 954 if pieces["closest-tag"]: 955 rendered = pieces["closest-tag"] 956 rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 957 else: 958 # exception #1 959 rendered = pieces["short"] 960 if pieces["dirty"]: 961 rendered += "-dirty" 962 return rendered 963 964 965def render(pieces, style): 966 """Render the given version pieces into the requested style.""" 967 if pieces["error"]: 968 return {"version": "unknown", 969 "full-revisionid": pieces.get("long"), 970 "dirty": None, 971 "error": pieces["error"], 972 "date": None} 973 974 if not style or style == "default": 975 style = "pep440" # the default 976 977 if style == "pep440": 978 rendered = render_pep440(pieces) 979 elif style == "pep440-branch": 980 rendered = render_pep440_branch(pieces) 981 elif style == "pep440-pre": 982 rendered = render_pep440_pre(pieces) 983 elif style == "pep440-post": 984 rendered = render_pep440_post(pieces) 985 elif style == "pep440-post-branch": 986 rendered = render_pep440_post_branch(pieces) 987 elif style == "pep440-old": 988 rendered = render_pep440_old(pieces) 989 elif style == "git-describe": 990 rendered = render_git_describe(pieces) 991 elif style == "git-describe-long": 992 rendered = render_git_describe_long(pieces) 993 else: 994 raise ValueError("unknown style '%%s'" %% style) 995 996 return {"version": rendered, "full-revisionid": pieces["long"], 997 "dirty": pieces["dirty"], "error": None, 998 "date": pieces.get("date")} 999 1000 1001def get_versions(): 1002 """Get version information or return default if unable to do so.""" 1003 # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 1004 # __file__, we can work backwards from there to the root. Some 1005 # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 1006 # case we can only use expanded keywords. 1007 1008 cfg = get_config() 1009 verbose = cfg.verbose 1010 1011 try: 1012 return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, 1013 verbose) 1014 except NotThisMethod: 1015 pass 1016 1017 try: 1018 root = os.path.realpath(__file__) 1019 # versionfile_source is the relative path from the top of the source 1020 # tree (where the .git directory might live) to this file. Invert 1021 # this to find the root from __file__. 1022 for _ in cfg.versionfile_source.split('/'): 1023 root = os.path.dirname(root) 1024 except NameError: 1025 return {"version": "0+unknown", "full-revisionid": None, 1026 "dirty": None, 1027 "error": "unable to find root of source tree", 1028 "date": None} 1029 1030 try: 1031 pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 1032 return render(pieces, cfg.style) 1033 except NotThisMethod: 1034 pass 1035 1036 try: 1037 if cfg.parentdir_prefix: 1038 return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 1039 except NotThisMethod: 1040 pass 1041 1042 return {"version": "0+unknown", "full-revisionid": None, 1043 "dirty": None, 1044 "error": "unable to compute version", "date": None} 1045''' 1046 1047 1048@register_vcs_handler("git", "get_keywords") 1049def git_get_keywords(versionfile_abs): 1050 """Extract version information from the given file.""" 1051 # the code embedded in _version.py can just fetch the value of these 1052 # keywords. When used from setup.py, we don't want to import _version.py, 1053 # so we do it with a regexp instead. This function is not used from 1054 # _version.py. 1055 keywords = {} 1056 try: 1057 with open(versionfile_abs, "r") as fobj: 1058 for line in fobj: 1059 if line.strip().startswith("git_refnames ="): 1060 mo = re.search(r'=\s*"(.*)"', line) 1061 if mo: 1062 keywords["refnames"] = mo.group(1) 1063 if line.strip().startswith("git_full ="): 1064 mo = re.search(r'=\s*"(.*)"', line) 1065 if mo: 1066 keywords["full"] = mo.group(1) 1067 if line.strip().startswith("git_date ="): 1068 mo = re.search(r'=\s*"(.*)"', line) 1069 if mo: 1070 keywords["date"] = mo.group(1) 1071 except EnvironmentError: 1072 pass 1073 return keywords 1074 1075 1076@register_vcs_handler("git", "keywords") 1077def git_versions_from_keywords(keywords, tag_prefix, verbose): 1078 """Get version information from git keywords.""" 1079 if "refnames" not in keywords: 1080 raise NotThisMethod("Short version file found") 1081 date = keywords.get("date") 1082 if date is not None: 1083 # Use only the last line. Previous lines may contain GPG signature 1084 # information. 1085 date = date.splitlines()[-1] 1086 1087 # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant 1088 # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 1089 # -like" string, which we must then edit to make compliant), because 1090 # it's been around since git-1.5.3, and it's too difficult to 1091 # discover which version we're using, or to work around using an 1092 # older one. 1093 date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 1094 refnames = keywords["refnames"].strip() 1095 if refnames.startswith("$Format"): 1096 if verbose: 1097 print("keywords are unexpanded, not using") 1098 raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 1099 refs = {r.strip() for r in refnames.strip("()").split(",")} 1100 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 1101 # just "foo-1.0". If we see a "tag: " prefix, prefer those. 1102 TAG = "tag: " 1103 tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)} 1104 if not tags: 1105 # Either we're using git < 1.8.3, or there really are no tags. We use 1106 # a heuristic: assume all version tags have a digit. The old git %d 1107 # expansion behaves like git log --decorate=short and strips out the 1108 # refs/heads/ and refs/tags/ prefixes that would let us distinguish 1109 # between branches and tags. By ignoring refnames without digits, we 1110 # filter out many common branch names like "release" and 1111 # "stabilization", as well as "HEAD" and "master". 1112 tags = {r for r in refs if re.search(r"\d", r)} 1113 if verbose: 1114 print("discarding '%s', no digits" % ",".join(refs - tags)) 1115 if verbose: 1116 print("likely tags: %s" % ",".join(sorted(tags))) 1117 for ref in sorted(tags): 1118 # sorting will prefer e.g. "2.0" over "2.0rc1" 1119 if ref.startswith(tag_prefix): 1120 r = ref[len(tag_prefix) :] 1121 # Filter out refs that exactly match prefix or that don't start 1122 # with a number once the prefix is stripped (mostly a concern 1123 # when prefix is '') 1124 if not re.match(r"\d", r): 1125 continue 1126 if verbose: 1127 print("picking %s" % r) 1128 return { 1129 "version": r, 1130 "full-revisionid": keywords["full"].strip(), 1131 "dirty": False, 1132 "error": None, 1133 "date": date, 1134 } 1135 # no suitable tags, so version is "0+unknown", but full hex is still there 1136 if verbose: 1137 print("no suitable tags, using unknown + full revision id") 1138 return { 1139 "version": "0+unknown", 1140 "full-revisionid": keywords["full"].strip(), 1141 "dirty": False, 1142 "error": "no suitable tags", 1143 "date": None, 1144 } 1145 1146 1147@register_vcs_handler("git", "pieces_from_vcs") 1148def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): 1149 """Get version from 'git describe' in the root of the source tree. 1150 1151 This only gets called if the git-archive 'subst' keywords were *not* 1152 expanded, and _version.py hasn't already been rewritten with a short 1153 version string, meaning we're inside a checked out source tree. 1154 """ 1155 GITS = ["git"] 1156 if sys.platform == "win32": 1157 GITS = ["git.cmd", "git.exe"] 1158 1159 _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) 1160 if rc != 0: 1161 if verbose: 1162 print("Directory %s not under git control" % root) 1163 raise NotThisMethod("'git rev-parse --git-dir' returned error") 1164 1165 # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 1166 # if there isn't one, this yields HEX[-dirty] (no NUM) 1167 describe_out, rc = runner( 1168 GITS, 1169 [ 1170 "describe", 1171 "--tags", 1172 "--dirty", 1173 "--always", 1174 "--long", 1175 "--match", 1176 "%s*" % tag_prefix, 1177 ], 1178 cwd=root, 1179 ) 1180 # --long was added in git-1.5.5 1181 if describe_out is None: 1182 raise NotThisMethod("'git describe' failed") 1183 describe_out = describe_out.strip() 1184 full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) 1185 if full_out is None: 1186 raise NotThisMethod("'git rev-parse' failed") 1187 full_out = full_out.strip() 1188 1189 pieces = {} 1190 pieces["long"] = full_out 1191 pieces["short"] = full_out[:7] # maybe improved later 1192 pieces["error"] = None 1193 1194 branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) 1195 # --abbrev-ref was added in git-1.6.3 1196 if rc != 0 or branch_name is None: 1197 raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") 1198 branch_name = branch_name.strip() 1199 1200 if branch_name == "HEAD": 1201 # If we aren't exactly on a branch, pick a branch which represents 1202 # the current commit. If all else fails, we are on a branchless 1203 # commit. 1204 branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) 1205 # --contains was added in git-1.5.4 1206 if rc != 0 or branches is None: 1207 raise NotThisMethod("'git branch --contains' returned error") 1208 branches = branches.split("\n") 1209 1210 # Remove the first line if we're running detached 1211 if "(" in branches[0]: 1212 branches.pop(0) 1213 1214 # Strip off the leading "* " from the list of branches. 1215 branches = [branch[2:] for branch in branches] 1216 if "master" in branches: 1217 branch_name = "master" 1218 elif not branches: 1219 branch_name = None 1220 else: 1221 # Pick the first branch that is returned. Good or bad. 1222 branch_name = branches[0] 1223 1224 pieces["branch"] = branch_name 1225 1226 # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 1227 # TAG might have hyphens. 1228 git_describe = describe_out 1229 1230 # look for -dirty suffix 1231 dirty = git_describe.endswith("-dirty") 1232 pieces["dirty"] = dirty 1233 if dirty: 1234 git_describe = git_describe[: git_describe.rindex("-dirty")] 1235 1236 # now we have TAG-NUM-gHEX or HEX 1237 1238 if "-" in git_describe: 1239 # TAG-NUM-gHEX 1240 mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) 1241 if not mo: 1242 # unparseable. Maybe git-describe is misbehaving? 1243 pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out 1244 return pieces 1245 1246 # tag 1247 full_tag = mo.group(1) 1248 if not full_tag.startswith(tag_prefix): 1249 if verbose: 1250 fmt = "tag '%s' doesn't start with prefix '%s'" 1251 print(fmt % (full_tag, tag_prefix)) 1252 pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % ( 1253 full_tag, 1254 tag_prefix, 1255 ) 1256 return pieces 1257 pieces["closest-tag"] = full_tag[len(tag_prefix) :] 1258 1259 # distance: number of commits since tag 1260 pieces["distance"] = int(mo.group(2)) 1261 1262 # commit: short hex revision ID 1263 pieces["short"] = mo.group(3) 1264 1265 else: 1266 # HEX: no tags 1267 pieces["closest-tag"] = None 1268 count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root) 1269 pieces["distance"] = int(count_out) # total number of commits 1270 1271 # commit date: see ISO-8601 comment in git_versions_from_keywords() 1272 date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() 1273 # Use only the last line. Previous lines may contain GPG signature 1274 # information. 1275 date = date.splitlines()[-1] 1276 pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 1277 1278 return pieces 1279 1280 1281def do_vcs_install(manifest_in, versionfile_source, ipy): 1282 """Git-specific installation logic for Versioneer. 1283 1284 For Git, this means creating/changing .gitattributes to mark _version.py 1285 for export-subst keyword substitution. 1286 """ 1287 GITS = ["git"] 1288 if sys.platform == "win32": 1289 GITS = ["git.cmd", "git.exe"] 1290 files = [manifest_in, versionfile_source] 1291 if ipy: 1292 files.append(ipy) 1293 try: 1294 my_path = __file__ 1295 if my_path.endswith(".pyc") or my_path.endswith(".pyo"): 1296 my_path = os.path.splitext(my_path)[0] + ".py" 1297 versioneer_file = os.path.relpath(my_path) 1298 except NameError: 1299 versioneer_file = "versioneer.py" 1300 files.append(versioneer_file) 1301 present = False 1302 try: 1303 with open(".gitattributes", "r") as fobj: 1304 for line in fobj: 1305 if line.strip().startswith(versionfile_source): 1306 if "export-subst" in line.strip().split()[1:]: 1307 present = True 1308 break 1309 except EnvironmentError: 1310 pass 1311 if not present: 1312 with open(".gitattributes", "a+") as fobj: 1313 fobj.write(f"{versionfile_source} export-subst\n") 1314 files.append(".gitattributes") 1315 run_command(GITS, ["add", "--"] + files) 1316 1317 1318def versions_from_parentdir(parentdir_prefix, root, verbose): 1319 """Try to determine the version from the parent directory name. 1320 1321 Source tarballs conventionally unpack into a directory that includes both 1322 the project name and a version string. We will also support searching up 1323 two directory levels for an appropriately named parent directory 1324 """ 1325 rootdirs = [] 1326 1327 for _ in range(3): 1328 dirname = os.path.basename(root) 1329 if dirname.startswith(parentdir_prefix): 1330 return { 1331 "version": dirname[len(parentdir_prefix) :], 1332 "full-revisionid": None, 1333 "dirty": False, 1334 "error": None, 1335 "date": None, 1336 } 1337 rootdirs.append(root) 1338 root = os.path.dirname(root) # up a level 1339 1340 if verbose: 1341 print( 1342 "Tried directories %s but none started with prefix %s" 1343 % (str(rootdirs), parentdir_prefix) 1344 ) 1345 raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 1346 1347 1348SHORT_VERSION_PY = """ 1349# This file was generated by 'versioneer.py' (0.20) from 1350# revision-control system data, or from the parent directory name of an 1351# unpacked source archive. Distribution tarballs contain a pre-generated copy 1352# of this file. 1353 1354import json 1355 1356version_json = ''' 1357%s 1358''' # END VERSION_JSON 1359 1360 1361def get_versions(): 1362 return json.loads(version_json) 1363""" 1364 1365 1366def versions_from_file(filename): 1367 """Try to determine the version from _version.py if present.""" 1368 try: 1369 with open(filename) as f: 1370 contents = f.read() 1371 except EnvironmentError: 1372 raise NotThisMethod("unable to read _version.py") 1373 mo = re.search( 1374 r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S 1375 ) 1376 if not mo: 1377 mo = re.search( 1378 r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S 1379 ) 1380 if not mo: 1381 raise NotThisMethod("no version_json in _version.py") 1382 return json.loads(mo.group(1)) 1383 1384 1385def write_to_version_file(filename, versions): 1386 """Write the given version number to the given _version.py file.""" 1387 os.unlink(filename) 1388 contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) 1389 with open(filename, "w") as f: 1390 f.write(SHORT_VERSION_PY % contents) 1391 1392 print("set %s to '%s'" % (filename, versions["version"])) 1393 1394 1395def plus_or_dot(pieces): 1396 """Return a + if we don't already have one, else return a .""" 1397 if "+" in pieces.get("closest-tag", ""): 1398 return "." 1399 return "+" 1400 1401 1402def render_pep440(pieces): 1403 """Build up version string, with post-release "local version identifier". 1404 1405 Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 1406 get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 1407 1408 Exceptions: 1409 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 1410 """ 1411 if pieces["closest-tag"]: 1412 rendered = pieces["closest-tag"] 1413 if pieces["distance"] or pieces["dirty"]: 1414 rendered += plus_or_dot(pieces) 1415 rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 1416 if pieces["dirty"]: 1417 rendered += ".dirty" 1418 else: 1419 # exception #1 1420 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) 1421 if pieces["dirty"]: 1422 rendered += ".dirty" 1423 return rendered 1424 1425 1426def render_pep440_branch(pieces): 1427 """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . 1428 1429 The ".dev0" means not master branch. Note that .dev0 sorts backwards 1430 (a feature branch will appear "older" than the master branch). 1431 1432 Exceptions: 1433 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] 1434 """ 1435 if pieces["closest-tag"]: 1436 rendered = pieces["closest-tag"] 1437 if pieces["distance"] or pieces["dirty"]: 1438 if pieces["branch"] != "master": 1439 rendered += ".dev0" 1440 rendered += plus_or_dot(pieces) 1441 rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 1442 if pieces["dirty"]: 1443 rendered += ".dirty" 1444 else: 1445 # exception #1 1446 rendered = "0" 1447 if pieces["branch"] != "master": 1448 rendered += ".dev0" 1449 rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) 1450 if pieces["dirty"]: 1451 rendered += ".dirty" 1452 return rendered 1453 1454 1455def render_pep440_pre(pieces): 1456 """TAG[.post0.devDISTANCE] -- No -dirty. 1457 1458 Exceptions: 1459 1: no tags. 0.post0.devDISTANCE 1460 """ 1461 if pieces["closest-tag"]: 1462 rendered = pieces["closest-tag"] 1463 if pieces["distance"]: 1464 rendered += ".post0.dev%d" % pieces["distance"] 1465 else: 1466 # exception #1 1467 rendered = "0.post0.dev%d" % pieces["distance"] 1468 return rendered 1469 1470 1471def render_pep440_post(pieces): 1472 """TAG[.postDISTANCE[.dev0]+gHEX] . 1473 1474 The ".dev0" means dirty. Note that .dev0 sorts backwards 1475 (a dirty tree will appear "older" than the corresponding clean one), 1476 but you shouldn't be releasing software with -dirty anyways. 1477 1478 Exceptions: 1479 1: no tags. 0.postDISTANCE[.dev0] 1480 """ 1481 if pieces["closest-tag"]: 1482 rendered = pieces["closest-tag"] 1483 if pieces["distance"] or pieces["dirty"]: 1484 rendered += ".post%d" % pieces["distance"] 1485 if pieces["dirty"]: 1486 rendered += ".dev0" 1487 rendered += plus_or_dot(pieces) 1488 rendered += "g%s" % pieces["short"] 1489 else: 1490 # exception #1 1491 rendered = "0.post%d" % pieces["distance"] 1492 if pieces["dirty"]: 1493 rendered += ".dev0" 1494 rendered += "+g%s" % pieces["short"] 1495 return rendered 1496 1497 1498def render_pep440_post_branch(pieces): 1499 """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . 1500 1501 The ".dev0" means not master branch. 1502 1503 Exceptions: 1504 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] 1505 """ 1506 if pieces["closest-tag"]: 1507 rendered = pieces["closest-tag"] 1508 if pieces["distance"] or pieces["dirty"]: 1509 rendered += ".post%d" % pieces["distance"] 1510 if pieces["branch"] != "master": 1511 rendered += ".dev0" 1512 rendered += plus_or_dot(pieces) 1513 rendered += "g%s" % pieces["short"] 1514 if pieces["dirty"]: 1515 rendered += ".dirty" 1516 else: 1517 # exception #1 1518 rendered = "0.post%d" % pieces["distance"] 1519 if pieces["branch"] != "master": 1520 rendered += ".dev0" 1521 rendered += "+g%s" % pieces["short"] 1522 if pieces["dirty"]: 1523 rendered += ".dirty" 1524 return rendered 1525 1526 1527def render_pep440_old(pieces): 1528 """TAG[.postDISTANCE[.dev0]] . 1529 1530 The ".dev0" means dirty. 1531 1532 Exceptions: 1533 1: no tags. 0.postDISTANCE[.dev0] 1534 """ 1535 if pieces["closest-tag"]: 1536 rendered = pieces["closest-tag"] 1537 if pieces["distance"] or pieces["dirty"]: 1538 rendered += ".post%d" % pieces["distance"] 1539 if pieces["dirty"]: 1540 rendered += ".dev0" 1541 else: 1542 # exception #1 1543 rendered = "0.post%d" % pieces["distance"] 1544 if pieces["dirty"]: 1545 rendered += ".dev0" 1546 return rendered 1547 1548 1549def render_git_describe(pieces): 1550 """TAG[-DISTANCE-gHEX][-dirty]. 1551 1552 Like 'git describe --tags --dirty --always'. 1553 1554 Exceptions: 1555 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1556 """ 1557 if pieces["closest-tag"]: 1558 rendered = pieces["closest-tag"] 1559 if pieces["distance"]: 1560 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1561 else: 1562 # exception #1 1563 rendered = pieces["short"] 1564 if pieces["dirty"]: 1565 rendered += "-dirty" 1566 return rendered 1567 1568 1569def render_git_describe_long(pieces): 1570 """TAG-DISTANCE-gHEX[-dirty]. 1571 1572 Like 'git describe --tags --dirty --always -long'. 1573 The distance/hash is unconditional. 1574 1575 Exceptions: 1576 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1577 """ 1578 if pieces["closest-tag"]: 1579 rendered = pieces["closest-tag"] 1580 rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1581 else: 1582 # exception #1 1583 rendered = pieces["short"] 1584 if pieces["dirty"]: 1585 rendered += "-dirty" 1586 return rendered 1587 1588 1589def render(pieces, style): 1590 """Render the given version pieces into the requested style.""" 1591 if pieces["error"]: 1592 return { 1593 "version": "unknown", 1594 "full-revisionid": pieces.get("long"), 1595 "dirty": None, 1596 "error": pieces["error"], 1597 "date": None, 1598 } 1599 1600 if not style or style == "default": 1601 style = "pep440" # the default 1602 1603 if style == "pep440": 1604 rendered = render_pep440(pieces) 1605 elif style == "pep440-branch": 1606 rendered = render_pep440_branch(pieces) 1607 elif style == "pep440-pre": 1608 rendered = render_pep440_pre(pieces) 1609 elif style == "pep440-post": 1610 rendered = render_pep440_post(pieces) 1611 elif style == "pep440-post-branch": 1612 rendered = render_pep440_post_branch(pieces) 1613 elif style == "pep440-old": 1614 rendered = render_pep440_old(pieces) 1615 elif style == "git-describe": 1616 rendered = render_git_describe(pieces) 1617 elif style == "git-describe-long": 1618 rendered = render_git_describe_long(pieces) 1619 else: 1620 raise ValueError("unknown style '%s'" % style) 1621 1622 return { 1623 "version": rendered, 1624 "full-revisionid": pieces["long"], 1625 "dirty": pieces["dirty"], 1626 "error": None, 1627 "date": pieces.get("date"), 1628 } 1629 1630 1631class VersioneerBadRootError(Exception): 1632 """The project root directory is unknown or missing key files.""" 1633 1634 1635def get_versions(verbose=False): 1636 """Get the project version from whatever source is available. 1637 1638 Returns dict with two keys: 'version' and 'full'. 1639 """ 1640 if "versioneer" in sys.modules: 1641 # see the discussion in cmdclass.py:get_cmdclass() 1642 del sys.modules["versioneer"] 1643 1644 root = get_root() 1645 cfg = get_config_from_root(root) 1646 1647 assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" 1648 handlers = HANDLERS.get(cfg.VCS) 1649 assert handlers, "unrecognized VCS '%s'" % cfg.VCS 1650 verbose = verbose or cfg.verbose 1651 assert ( 1652 cfg.versionfile_source is not None 1653 ), "please set versioneer.versionfile_source" 1654 assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" 1655 1656 versionfile_abs = os.path.join(root, cfg.versionfile_source) 1657 1658 # extract version from first of: _version.py, VCS command (e.g. 'git 1659 # describe'), parentdir. This is meant to work for developers using a 1660 # source checkout, for users of a tarball created by 'setup.py sdist', 1661 # and for users of a tarball/zipball created by 'git archive' or github's 1662 # download-from-tag feature or the equivalent in other VCSes. 1663 1664 get_keywords_f = handlers.get("get_keywords") 1665 from_keywords_f = handlers.get("keywords") 1666 if get_keywords_f and from_keywords_f: 1667 try: 1668 keywords = get_keywords_f(versionfile_abs) 1669 ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) 1670 if verbose: 1671 print("got version from expanded keyword %s" % ver) 1672 return ver 1673 except NotThisMethod: 1674 pass 1675 1676 try: 1677 ver = versions_from_file(versionfile_abs) 1678 if verbose: 1679 print("got version from file %s %s" % (versionfile_abs, ver)) 1680 return ver 1681 except NotThisMethod: 1682 pass 1683 1684 from_vcs_f = handlers.get("pieces_from_vcs") 1685 if from_vcs_f: 1686 try: 1687 pieces = from_vcs_f(cfg.tag_prefix, root, verbose) 1688 ver = render(pieces, cfg.style) 1689 if verbose: 1690 print("got version from VCS %s" % ver) 1691 return ver 1692 except NotThisMethod: 1693 pass 1694 1695 try: 1696 if cfg.parentdir_prefix: 1697 ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 1698 if verbose: 1699 print("got version from parentdir %s" % ver) 1700 return ver 1701 except NotThisMethod: 1702 pass 1703 1704 if verbose: 1705 print("unable to compute version") 1706 1707 return { 1708 "version": "0+unknown", 1709 "full-revisionid": None, 1710 "dirty": None, 1711 "error": "unable to compute version", 1712 "date": None, 1713 } 1714 1715 1716def get_version(): 1717 """Get the short version string for this project.""" 1718 return get_versions()["version"] 1719 1720 1721def get_cmdclass(cmdclass=None): 1722 """Get the custom setuptools/distutils subclasses used by Versioneer. 1723 1724 If the package uses a different cmdclass (e.g. one from numpy), it 1725 should be provide as an argument. 1726 """ 1727 if "versioneer" in sys.modules: 1728 del sys.modules["versioneer"] 1729 # this fixes the "python setup.py develop" case (also 'install' and 1730 # 'easy_install .'), in which subdependencies of the main project are 1731 # built (using setup.py bdist_egg) in the same python process. Assume 1732 # a main project A and a dependency B, which use different versions 1733 # of Versioneer. A's setup.py imports A's Versioneer, leaving it in 1734 # sys.modules by the time B's setup.py is executed, causing B to run 1735 # with the wrong versioneer. Setuptools wraps the sub-dep builds in a 1736 # sandbox that restores sys.modules to it's pre-build state, so the 1737 # parent is protected against the child's "import versioneer". By 1738 # removing ourselves from sys.modules here, before the child build 1739 # happens, we protect the child from the parent's versioneer too. 1740 # Also see https://github.com/python-versioneer/python-versioneer/issues/52 1741 1742 cmds = {} if cmdclass is None else cmdclass.copy() 1743 1744 # we add "version" to both distutils and setuptools 1745 from distutils.core import Command 1746 1747 class cmd_version(Command): 1748 description = "report generated version string" 1749 user_options = [] 1750 boolean_options = [] 1751 1752 def initialize_options(self): 1753 pass 1754 1755 def finalize_options(self): 1756 pass 1757 1758 def run(self): 1759 vers = get_versions(verbose=True) 1760 print("Version: %s" % vers["version"]) 1761 print(" full-revisionid: %s" % vers.get("full-revisionid")) 1762 print(" dirty: %s" % vers.get("dirty")) 1763 print(" date: %s" % vers.get("date")) 1764 if vers["error"]: 1765 print(" error: %s" % vers["error"]) 1766 1767 cmds["version"] = cmd_version 1768 1769 # we override "build_py" in both distutils and setuptools 1770 # 1771 # most invocation pathways end up running build_py: 1772 # distutils/build -> build_py 1773 # distutils/install -> distutils/build ->.. 1774 # setuptools/bdist_wheel -> distutils/install ->.. 1775 # setuptools/bdist_egg -> distutils/install_lib -> build_py 1776 # setuptools/install -> bdist_egg ->.. 1777 # setuptools/develop -> ? 1778 # pip install: 1779 # copies source tree to a tempdir before running egg_info/etc 1780 # if .git isn't copied too, 'git describe' will fail 1781 # then does setup.py bdist_wheel, or sometimes setup.py install 1782 # setup.py egg_info -> ? 1783 1784 # we override different "build_py" commands for both environments 1785 if "build_py" in cmds: 1786 _build_py = cmds["build_py"] 1787 elif "setuptools" in sys.modules: 1788 from setuptools.command.build_py import build_py as _build_py 1789 else: 1790 from distutils.command.build_py import build_py as _build_py 1791 1792 class cmd_build_py(_build_py): 1793 def run(self): 1794 root = get_root() 1795 cfg = get_config_from_root(root) 1796 versions = get_versions() 1797 _build_py.run(self) 1798 # now locate _version.py in the new build/ directory and replace 1799 # it with an updated value 1800 if cfg.versionfile_build: 1801 target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) 1802 print("UPDATING %s" % target_versionfile) 1803 write_to_version_file(target_versionfile, versions) 1804 1805 cmds["build_py"] = cmd_build_py 1806 1807 if "build_ext" in cmds: 1808 _build_ext = cmds["build_ext"] 1809 elif "setuptools" in sys.modules: 1810 from setuptools.command.build_ext import build_ext as _build_ext 1811 else: 1812 from distutils.command.build_ext import build_ext as _build_ext 1813 1814 class cmd_build_ext(_build_ext): 1815 def run(self): 1816 root = get_root() 1817 cfg = get_config_from_root(root) 1818 versions = get_versions() 1819 _build_ext.run(self) 1820 if self.inplace: 1821 # build_ext --inplace will only build extensions in 1822 # build/lib<..> dir with no _version.py to write to. 1823 # As in place builds will already have a _version.py 1824 # in the module dir, we do not need to write one. 1825 return 1826 # now locate _version.py in the new build/ directory and replace 1827 # it with an updated value 1828 target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) 1829 print("UPDATING %s" % target_versionfile) 1830 write_to_version_file(target_versionfile, versions) 1831 1832 cmds["build_ext"] = cmd_build_ext 1833 1834 if "cx_Freeze" in sys.modules: # cx_freeze enabled? 1835 from cx_Freeze.dist import build_exe as _build_exe 1836 1837 # nczeczulin reports that py2exe won't like the pep440-style string 1838 # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. 1839 # setup(console=[{ 1840 # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION 1841 # "product_version": versioneer.get_version(), 1842 # ... 1843 1844 class cmd_build_exe(_build_exe): 1845 def run(self): 1846 root = get_root() 1847 cfg = get_config_from_root(root) 1848 versions = get_versions() 1849 target_versionfile = cfg.versionfile_source 1850 print("UPDATING %s" % target_versionfile) 1851 write_to_version_file(target_versionfile, versions) 1852 1853 _build_exe.run(self) 1854 os.unlink(target_versionfile) 1855 with open(cfg.versionfile_source, "w") as f: 1856 LONG = LONG_VERSION_PY[cfg.VCS] 1857 f.write( 1858 LONG 1859 % { 1860 "DOLLAR": "$", 1861 "STYLE": cfg.style, 1862 "TAG_PREFIX": cfg.tag_prefix, 1863 "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1864 "VERSIONFILE_SOURCE": cfg.versionfile_source, 1865 } 1866 ) 1867 1868 cmds["build_exe"] = cmd_build_exe 1869 del cmds["build_py"] 1870 1871 if "py2exe" in sys.modules: # py2exe enabled? 1872 from py2exe.distutils_buildexe import py2exe as _py2exe 1873 1874 class cmd_py2exe(_py2exe): 1875 def run(self): 1876 root = get_root() 1877 cfg = get_config_from_root(root) 1878 versions = get_versions() 1879 target_versionfile = cfg.versionfile_source 1880 print("UPDATING %s" % target_versionfile) 1881 write_to_version_file(target_versionfile, versions) 1882 1883 _py2exe.run(self) 1884 os.unlink(target_versionfile) 1885 with open(cfg.versionfile_source, "w") as f: 1886 LONG = LONG_VERSION_PY[cfg.VCS] 1887 f.write( 1888 LONG 1889 % { 1890 "DOLLAR": "$", 1891 "STYLE": cfg.style, 1892 "TAG_PREFIX": cfg.tag_prefix, 1893 "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1894 "VERSIONFILE_SOURCE": cfg.versionfile_source, 1895 } 1896 ) 1897 1898 cmds["py2exe"] = cmd_py2exe 1899 1900 # we override different "sdist" commands for both environments 1901 if "sdist" in cmds: 1902 _sdist = cmds["sdist"] 1903 elif "setuptools" in sys.modules: 1904 from setuptools.command.sdist import sdist as _sdist 1905 else: 1906 from distutils.command.sdist import sdist as _sdist 1907 1908 class cmd_sdist(_sdist): 1909 def run(self): 1910 versions = get_versions() 1911 # pylint:disable=attribute-defined-outside-init # noqa 1912 self._versioneer_generated_versions = versions 1913 # unless we update this, the command will keep using the old 1914 # version 1915 self.distribution.metadata.version = versions["version"] 1916 return _sdist.run(self) 1917 1918 def make_release_tree(self, base_dir, files): 1919 root = get_root() 1920 cfg = get_config_from_root(root) 1921 _sdist.make_release_tree(self, base_dir, files) 1922 # now locate _version.py in the new base_dir directory 1923 # (remembering that it may be a hardlink) and replace it with an 1924 # updated value 1925 target_versionfile = os.path.join(base_dir, cfg.versionfile_source) 1926 print("UPDATING %s" % target_versionfile) 1927 write_to_version_file( 1928 target_versionfile, self._versioneer_generated_versions 1929 ) 1930 1931 cmds["sdist"] = cmd_sdist 1932 1933 return cmds 1934 1935 1936CONFIG_ERROR = """ 1937setup.cfg is missing the necessary Versioneer configuration. You need 1938a section like: 1939 1940 [versioneer] 1941 VCS = git 1942 style = pep440 1943 versionfile_source = src/myproject/_version.py 1944 versionfile_build = myproject/_version.py 1945 tag_prefix = 1946 parentdir_prefix = myproject- 1947 1948You will also need to edit your setup.py to use the results: 1949 1950 import versioneer 1951 setup(version=versioneer.get_version(), 1952 cmdclass=versioneer.get_cmdclass(), ...) 1953 1954Please read the docstring in ./versioneer.py for configuration instructions, 1955edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. 1956""" 1957 1958SAMPLE_CONFIG = """ 1959# See the docstring in versioneer.py for instructions. Note that you must 1960# re-run 'versioneer.py setup' after changing this section, and commit the 1961# resulting files. 1962 1963[versioneer] 1964#VCS = git 1965#style = pep440 1966#versionfile_source = 1967#versionfile_build = 1968#tag_prefix = 1969#parentdir_prefix = 1970 1971""" 1972 1973OLD_SNIPPET = """ 1974from ._version import get_versions 1975__version__ = get_versions()['version'] 1976del get_versions 1977""" 1978 1979INIT_PY_SNIPPET = """ 1980from . import {0} 1981__version__ = {0}.get_versions()['version'] 1982""" 1983 1984 1985def do_setup(): 1986 """Do main VCS-independent setup function for installing Versioneer.""" 1987 root = get_root() 1988 try: 1989 cfg = get_config_from_root(root) 1990 except ( 1991 EnvironmentError, 1992 configparser.NoSectionError, 1993 configparser.NoOptionError, 1994 ) as e: 1995 if isinstance(e, (EnvironmentError, configparser.NoSectionError)): 1996 print("Adding sample versioneer config to setup.cfg", file=sys.stderr) 1997 with open(os.path.join(root, "setup.cfg"), "a") as f: 1998 f.write(SAMPLE_CONFIG) 1999 print(CONFIG_ERROR, file=sys.stderr) 2000 return 1 2001 2002 print(" creating %s" % cfg.versionfile_source) 2003 with open(cfg.versionfile_source, "w") as f: 2004 LONG = LONG_VERSION_PY[cfg.VCS] 2005 f.write( 2006 LONG 2007 % { 2008 "DOLLAR": "$", 2009 "STYLE": cfg.style, 2010 "TAG_PREFIX": cfg.tag_prefix, 2011 "PARENTDIR_PREFIX": cfg.parentdir_prefix, 2012 "VERSIONFILE_SOURCE": cfg.versionfile_source, 2013 } 2014 ) 2015 2016 ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") 2017 if os.path.exists(ipy): 2018 try: 2019 with open(ipy, "r") as f: 2020 old = f.read() 2021 except EnvironmentError: 2022 old = "" 2023 module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] 2024 snippet = INIT_PY_SNIPPET.format(module) 2025 if OLD_SNIPPET in old: 2026 print(" replacing boilerplate in %s" % ipy) 2027 with open(ipy, "w") as f: 2028 f.write(old.replace(OLD_SNIPPET, snippet)) 2029 elif snippet not in old: 2030 print(" appending to %s" % ipy) 2031 with open(ipy, "a") as f: 2032 f.write(snippet) 2033 else: 2034 print(" %s unmodified" % ipy) 2035 else: 2036 print(" %s doesn't exist, ok" % ipy) 2037 ipy = None 2038 2039 # Make sure both the top-level "versioneer.py" and versionfile_source 2040 # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so 2041 # they'll be copied into source distributions. Pip won't be able to 2042 # install the package without this. 2043 manifest_in = os.path.join(root, "MANIFEST.in") 2044 simple_includes = set() 2045 try: 2046 with open(manifest_in, "r") as f: 2047 for line in f: 2048 if line.startswith("include "): 2049 for include in line.split()[1:]: 2050 simple_includes.add(include) 2051 except EnvironmentError: 2052 pass 2053 # That doesn't cover everything MANIFEST.in can do 2054 # (http://docs.python.org/2/distutils/sourcedist.html#commands), so 2055 # it might give some false negatives. Appending redundant 'include' 2056 # lines is safe, though. 2057 if "versioneer.py" not in simple_includes: 2058 print(" appending 'versioneer.py' to MANIFEST.in") 2059 with open(manifest_in, "a") as f: 2060 f.write("include versioneer.py\n") 2061 else: 2062 print(" 'versioneer.py' already in MANIFEST.in") 2063 if cfg.versionfile_source not in simple_includes: 2064 print( 2065 " appending versionfile_source ('%s') to MANIFEST.in" 2066 % cfg.versionfile_source 2067 ) 2068 with open(manifest_in, "a") as f: 2069 f.write("include %s\n" % cfg.versionfile_source) 2070 else: 2071 print(" versionfile_source already in MANIFEST.in") 2072 2073 # Make VCS-specific changes. For git, this means creating/changing 2074 # .gitattributes to mark _version.py for export-subst keyword 2075 # substitution. 2076 do_vcs_install(manifest_in, cfg.versionfile_source, ipy) 2077 return 0 2078 2079 2080def scan_setup_py(): 2081 """Validate the contents of setup.py against Versioneer's expectations.""" 2082 found = set() 2083 setters = False 2084 errors = 0 2085 with open("setup.py", "r") as f: 2086 for line in f.readlines(): 2087 if "import versioneer" in line: 2088 found.add("import") 2089 if "versioneer.get_cmdclass()" in line: 2090 found.add("cmdclass") 2091 if "versioneer.get_version()" in line: 2092 found.add("get_version") 2093 if "versioneer.VCS" in line: 2094 setters = True 2095 if "versioneer.versionfile_source" in line: 2096 setters = True 2097 if len(found) != 3: 2098 print("") 2099 print("Your setup.py appears to be missing some important items") 2100 print("(but I might be wrong). Please make sure it has something") 2101 print("roughly like the following:") 2102 print("") 2103 print(" import versioneer") 2104 print(" setup( version=versioneer.get_version(),") 2105 print(" cmdclass=versioneer.get_cmdclass(), ...)") 2106 print("") 2107 errors += 1 2108 if setters: 2109 print("You should remove lines like 'versioneer.VCS = ' and") 2110 print("'versioneer.versionfile_source = ' . This configuration") 2111 print("now lives in setup.cfg, and should be removed from setup.py") 2112 print("") 2113 errors += 1 2114 return errors 2115 2116 2117if __name__ == "__main__": 2118 cmd = sys.argv[1] 2119 if cmd == "setup": 2120 errors = do_setup() 2121 errors += scan_setup_py() 2122 if errors: 2123 sys.exit(1) 2124