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