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