rembg

Форк
0
/
versioneer.py 
2163 строки · 78.2 Кб
1
# Version: 0.21
2

3
"""The Versioneer - like a rocketeer, but for versions.
4

5
The 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

16
This is a tool for managing a recorded version number in distutils-based
17
python projects. The goal is to remove the tedious and error-prone "update
18
the embedded version string" step from your release process. Making a new
19
release should be as easy as recording a new tag in your version-control
20
system, 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

32
Source 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

40
Within each source tree, the version identifier (either a string or a number,
41
this tool is format-agnostic) can come from a variety of places:
42

43
* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows
44
  about recent "tags" and an absolute revision-id
45
* the name of the directory into which the tarball was unpacked
46
* an expanded VCS keyword ($Id$, etc)
47
* a `_version.py` created by some earlier build step
48

49
For released software, the version identifier is closely related to a VCS
50
tag. Some projects use tag names that include more than just the version
51
string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool
52
needs to strip the tag prefix to extract the version identifier. For
53
unreleased software (between tags), the version identifier should provide
54
enough information to help developers recreate the same tree, while also
55
giving them an idea of roughly how old the tree is (after version 1.2, before
56
version 1.3). Many VCS systems can report a description that captures this,
57
for 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
59
0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
60
uncommitted changes).
61

62
The 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

69
Versioneer works by adding a special `_version.py` file into your source
70
tree, where your `__init__.py` can import it. This `_version.py` knows how to
71
dynamically ask the VCS tool for version information at import time.
72

73
`_version.py` also contains `$Revision$` markers, and the installation
74
process marks `_version.py` to have this marker rewritten with a tag name
75
during the `git archive` command. As a result, generated tarballs will
76
contain enough information to get the proper version.
77

78
To allow `setup.py` to compute a version too, a `versioneer.py` is added to
79
the top level of your source tree, next to `setup.py` and the `setup.cfg`
80
that configures it. This overrides several distutils/setuptools commands to
81
compute the version when invoked, and changes `setup.py build` and `setup.py
82
sdist` to replace `_version.py` with a small static file that contains just
83
the generated version data.
84

85
## Installation
86

87
See [INSTALL.md](./INSTALL.md) for detailed installation instructions.
88

89
## Version-String Flavors
90

91
Code which uses Versioneer can learn about its version string at runtime by
92
importing `_version` from your main `__init__.py` file and running the
93
`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can
94
import the top-level `versioneer.py` and run `get_versions()`.
95

96
Both functions return a dictionary with different flavors of version
97
information:
98

99
* `['version']`: A condensed version string, rendered using the selected
100
  style. This is the most commonly used value for the project's version
101
  string. The default "pep440" style yields strings like `0.11`,
102
  `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section
103
  below for alternative styles.
104

105
* `['full-revisionid']`: detailed revision identifier. For Git, this is the
106
  full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".
107

108
* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
109
  commit date in ISO 8601 format. This will be None if the date is not
110
  available.
111

112
* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
113
  this is only accurate if run in a VCS checkout, otherwise it is likely to
114
  be False or None
115

116
* `['error']`: if the version string could not be computed, this will be set
117
  to a string describing the problem, otherwise it will be None. It may be
118
  useful to throw an exception in setup.py if this is set, to avoid e.g.
119
  creating tarballs with a version string of "unknown".
120

121
Some variants are more useful than others. Including `full-revisionid` in a
122
bug 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
124
developers). `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
126
of bugs fixed in various releases.
127

128
The installer adds the following text to your `__init__.py` to place a basic
129
version in `YOURPROJECT.__version__`:
130

131
    from ._version import get_versions
132
    __version__ = get_versions()['version']
133
    del get_versions
134

135
## Styles
136

137
The setup.cfg `style=` configuration controls how the VCS information is
138
rendered into a version string.
139

140
The default style, "pep440", produces a PEP440-compliant string, equal to the
141
un-prefixed tag name for actual releases, and containing an additional "local
142
version" section with more detail for in-between builds. For Git, this is
143
TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags
144
--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the
145
tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and
146
that this commit is two revisions ("+2") beyond the "0.11" tag. For released
147
software (exactly equal to a known tag), the identifier will only contain the
148
stripped tag, e.g. "0.11".
149

150
Other styles are available. See [details.md](details.md) in the Versioneer
151
source tree for descriptions.
152

153
## Debugging
154

155
Versioneer tries to avoid fatal errors: if something goes wrong, it will tend
156
to return a version of "0+unknown". To investigate the problem, run `setup.py
157
version`, which will run the version-lookup code in a verbose mode, and will
158
display the full contents of `get_versions()` (including the `error` string,
159
which may help identify what went wrong).
160

161
## Known Limitations
162

163
Some situations are known to cause problems for Versioneer. This details the
164
most significant ones. More can be found on Github
165
[issues page](https://github.com/python-versioneer/python-versioneer/issues).
166

167
### Subprojects
168

169
Versioneer has limited support for source trees in which `setup.py` is not in
170
the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
171
two common reasons why `setup.py` might not be in the root:
172

173
* Source trees which contain multiple subprojects, such as
174
  [Buildbot](https://github.com/buildbot/buildbot), which contains both
175
  "master" and "slave" subprojects, each with their own `setup.py`,
176
  `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
177
  distributions (and upload multiple independently-installable tarballs).
178
* Source trees whose main purpose is to contain a C library, but which also
179
  provide bindings to Python (and perhaps other languages) in subdirectories.
180

181
Versioneer will look for `.git` in parent directories, and most operations
182
should get the right version string. However `pip` and `setuptools` have bugs
183
and implementation details which frequently cause `pip install .` from a
184
subproject directory to fail to find a correct version string (so it usually
185
defaults to `0+unknown`).
186

187
`pip install --editable .` should work correctly. `setup.py install` might
188
work too.
189

190
Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
191
some later version.
192

193
[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
194
this issue. The discussion in
195
[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
196
issue 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
199
pip to let Versioneer work correctly.
200

201
Versioneer-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
207
project into a virtualenv once, then continue editing the source code (and
208
test) without re-installing after every change.
209

210
"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
211
convenient way to specify executable scripts that should be installed along
212
with the python package.
213

214
These both work as expected when using modern setuptools. When using
215
setuptools-18.5 or earlier, however, certain operations will cause
216
`pkg_resources.DistributionNotFound` errors when running the entrypoint
217
script, which must be resolved by re-installing the package. This happens
218
when the install happens with one version, then the egg_info data is
219
regenerated while a different version is checked out. Many setup.py commands
220
cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
221
a different virtualenv), so this can be surprising.
222

223
[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
224
this one, but upgrading to a newer version of setuptools should probably
225
resolve it.
226

227

228
## Updating Versioneer
229

230
To upgrade your project to a new release of Versioneer, do the following:
231

232
* install the new Versioneer (`pip install -U versioneer` or equivalent)
233
* edit `setup.cfg`, if necessary, to include any new configuration settings
234
  indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details.
235
* re-run `versioneer install` in your source tree, to replace
236
  `SRC/_version.py`
237
* commit any changed files
238

239
## Future Directions
240

241
This tool is designed to make it easily extended to other version-control
242
systems: all VCS-specific components are in separate directories like
243
src/git/ . The top-level `versioneer.py` script is assembled from these
244
components by running make-versioneer.py . In the future, make-versioneer.py
245
will 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
247
configuration arguments that are currently provided manually during
248
installation by editing setup.py . Alternatively, it might go the other
249
direction and include code from all supported VCS systems, reducing the
250
number of intermediate scripts.
251

252
## Similar projects
253

254
* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
255
  dependency
256
* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
257
  versioneer
258
* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
259
  plugin
260

261
## License
262

263
To make Versioneer easier to embed, all its code is dedicated to the public
264
domain. The `_version.py` that it creates is also in the public domain.
265
Specifically, both are released under the Creative Commons "Public Domain
266
Dedication" license (CC0-1.0), as described in
267
https://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]:
272
https://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

282
import configparser
283
import errno
284
import json
285
import os
286
import re
287
import subprocess
288
import sys
289
from typing import Callable, Dict
290

291

292
class VersioneerConfig:
293
    """Container for Versioneer configuration parameters."""
294

295

296
def get_root():
297
    """Get the project root directory.
298

299
    We require that all commands are run from the project root, i.e. the
300
    directory that contains setup.py, setup.cfg, and versioneer.py .
301
    """
302
    root = os.path.realpath(os.path.abspath(os.getcwd()))
303
    setup_py = os.path.join(root, "setup.py")
304
    versioneer_py = os.path.join(root, "versioneer.py")
305
    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
306
        # allow 'python path/to/setup.py COMMAND'
307
        root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
308
        setup_py = os.path.join(root, "setup.py")
309
        versioneer_py = os.path.join(root, "versioneer.py")
310
    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
311
        err = (
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
        )
318
        raise VersioneerBadRootError(err)
319
    try:
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.
326
        my_path = os.path.realpath(os.path.abspath(__file__))
327
        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
328
        vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
329
        if me_dir != vsr_dir:
330
            print(
331
                "Warning: build in %s is using versioneer.py from %s"
332
                % (os.path.dirname(my_path), versioneer_py)
333
            )
334
    except NameError:
335
        pass
336
    return root
337

338

339
def 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 .
345
    setup_cfg = os.path.join(root, "setup.cfg")
346
    parser = configparser.ConfigParser()
347
    with open(setup_cfg, "r") as cfg_file:
348
        parser.read_file(cfg_file)
349
    VCS = parser.get("versioneer", "VCS")  # mandatory
350

351
    # Dict-like interface for non-mandatory entries
352
    section = parser["versioneer"]
353

354
    cfg = VersioneerConfig()
355
    cfg.VCS = VCS
356
    cfg.style = section.get("style", "")
357
    cfg.versionfile_source = section.get("versionfile_source")
358
    cfg.versionfile_build = section.get("versionfile_build")
359
    cfg.tag_prefix = section.get("tag_prefix")
360
    if cfg.tag_prefix in ("''", '""'):
361
        cfg.tag_prefix = ""
362
    cfg.parentdir_prefix = section.get("parentdir_prefix")
363
    cfg.verbose = section.get("verbose")
364
    return cfg
365

366

367
class NotThisMethod(Exception):
368
    """Exception raised if a method is not valid for the current scenario."""
369

370

371
# these dictionaries contain VCS-specific tools
372
LONG_VERSION_PY: Dict[str, str] = {}
373
HANDLERS: Dict[str, Dict[str, Callable]] = {}
374

375

376
def register_vcs_handler(vcs, method):  # decorator
377
    """Create decorator to mark a method as the handler of a VCS."""
378

379
    def decorate(f):
380
        """Store f in HANDLERS[vcs][method]."""
381
        HANDLERS.setdefault(vcs, {})[method] = f
382
        return f
383

384
    return decorate
385

386

387
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
388
    """Call the given command(s)."""
389
    assert isinstance(commands, list)
390
    process = None
391
    for command in commands:
392
        try:
393
            dispcmd = str([command] + args)
394
            # remember shell=False, so use git.cmd on windows, not just git
395
            process = subprocess.Popen(
396
                [command] + args,
397
                cwd=cwd,
398
                env=env,
399
                stdout=subprocess.PIPE,
400
                stderr=(subprocess.PIPE if hide_stderr else None),
401
            )
402
            break
403
        except OSError:
404
            e = sys.exc_info()[1]
405
            if e.errno == errno.ENOENT:
406
                continue
407
            if verbose:
408
                print("unable to run %s" % dispcmd)
409
                print(e)
410
            return None, None
411
    else:
412
        if verbose:
413
            print("unable to find command, tried %s" % (commands,))
414
        return None, None
415
    stdout = process.communicate()[0].strip().decode()
416
    if process.returncode != 0:
417
        if verbose:
418
            print("unable to run %s (error)" % dispcmd)
419
            print("stdout was %s" % stdout)
420
        return None, process.returncode
421
    return stdout, process.returncode
422

423

424
LONG_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

438
import errno
439
import os
440
import re
441
import subprocess
442
import sys
443
from typing import Callable, Dict
444

445

446
def 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().
452
    git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
453
    git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
454
    git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
455
    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
456
    return keywords
457

458

459
class VersioneerConfig:
460
    """Container for Versioneer configuration parameters."""
461

462

463
def get_config():
464
    """Create, populate and return the VersioneerConfig() object."""
465
    # these strings are filled in when 'setup.py versioneer' creates
466
    # _version.py
467
    cfg = VersioneerConfig()
468
    cfg.VCS = "git"
469
    cfg.style = "%(STYLE)s"
470
    cfg.tag_prefix = "%(TAG_PREFIX)s"
471
    cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s"
472
    cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s"
473
    cfg.verbose = False
474
    return cfg
475

476

477
class NotThisMethod(Exception):
478
    """Exception raised if a method is not valid for the current scenario."""
479

480

481
LONG_VERSION_PY: Dict[str, str] = {}
482
HANDLERS: Dict[str, Dict[str, Callable]] = {}
483

484

485
def register_vcs_handler(vcs, method):  # decorator
486
    """Create decorator to mark a method as the handler of a VCS."""
487
    def decorate(f):
488
        """Store f in HANDLERS[vcs][method]."""
489
        if vcs not in HANDLERS:
490
            HANDLERS[vcs] = {}
491
        HANDLERS[vcs][method] = f
492
        return f
493
    return decorate
494

495

496
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
497
                env=None):
498
    """Call the given command(s)."""
499
    assert isinstance(commands, list)
500
    process = None
501
    for command in commands:
502
        try:
503
            dispcmd = str([command] + args)
504
            # remember shell=False, so use git.cmd on windows, not just git
505
            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
506
                                       stdout=subprocess.PIPE,
507
                                       stderr=(subprocess.PIPE if hide_stderr
508
                                               else None))
509
            break
510
        except OSError:
511
            e = sys.exc_info()[1]
512
            if e.errno == errno.ENOENT:
513
                continue
514
            if verbose:
515
                print("unable to run %%s" %% dispcmd)
516
                print(e)
517
            return None, None
518
    else:
519
        if verbose:
520
            print("unable to find command, tried %%s" %% (commands,))
521
        return None, None
522
    stdout = process.communicate()[0].strip().decode()
523
    if process.returncode != 0:
524
        if verbose:
525
            print("unable to run %%s (error)" %% dispcmd)
526
            print("stdout was %%s" %% stdout)
527
        return None, process.returncode
528
    return stdout, process.returncode
529

530

531
def versions_from_parentdir(parentdir_prefix, root, verbose):
532
    """Try to determine the version from the parent directory name.
533

534
    Source tarballs conventionally unpack into a directory that includes both
535
    the project name and a version string. We will also support searching up
536
    two directory levels for an appropriately named parent directory
537
    """
538
    rootdirs = []
539

540
    for _ in range(3):
541
        dirname = os.path.basename(root)
542
        if dirname.startswith(parentdir_prefix):
543
            return {"version": dirname[len(parentdir_prefix):],
544
                    "full-revisionid": None,
545
                    "dirty": False, "error": None, "date": None}
546
        rootdirs.append(root)
547
        root = os.path.dirname(root)  # up a level
548

549
    if verbose:
550
        print("Tried directories %%s but none started with prefix %%s" %%
551
              (str(rootdirs), parentdir_prefix))
552
    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
553

554

555
@register_vcs_handler("git", "get_keywords")
556
def 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.
562
    keywords = {}
563
    try:
564
        with open(versionfile_abs, "r") as fobj:
565
            for line in fobj:
566
                if line.strip().startswith("git_refnames ="):
567
                    mo = re.search(r'=\s*"(.*)"', line)
568
                    if mo:
569
                        keywords["refnames"] = mo.group(1)
570
                if line.strip().startswith("git_full ="):
571
                    mo = re.search(r'=\s*"(.*)"', line)
572
                    if mo:
573
                        keywords["full"] = mo.group(1)
574
                if line.strip().startswith("git_date ="):
575
                    mo = re.search(r'=\s*"(.*)"', line)
576
                    if mo:
577
                        keywords["date"] = mo.group(1)
578
    except OSError:
579
        pass
580
    return keywords
581

582

583
@register_vcs_handler("git", "keywords")
584
def git_versions_from_keywords(keywords, tag_prefix, verbose):
585
    """Get version information from git keywords."""
586
    if "refnames" not in keywords:
587
        raise NotThisMethod("Short version file found")
588
    date = keywords.get("date")
589
    if date is not None:
590
        # Use only the last line.  Previous lines may contain GPG signature
591
        # information.
592
        date = 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.
600
        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
601
    refnames = keywords["refnames"].strip()
602
    if refnames.startswith("$Format"):
603
        if verbose:
604
            print("keywords are unexpanded, not using")
605
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
606
    refs = {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.
609
    TAG = "tag: "
610
    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
611
    if 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".
619
        tags = {r for r in refs if re.search(r'\d', r)}
620
        if verbose:
621
            print("discarding '%%s', no digits" %% ",".join(refs - tags))
622
    if verbose:
623
        print("likely tags: %%s" %% ",".join(sorted(tags)))
624
    for ref in sorted(tags):
625
        # sorting will prefer e.g. "2.0" over "2.0rc1"
626
        if ref.startswith(tag_prefix):
627
            r = 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 '')
631
            if not re.match(r'\d', r):
632
                continue
633
            if verbose:
634
                print("picking %%s" %% r)
635
            return {"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
640
    if verbose:
641
        print("no suitable tags, using unknown + full revision id")
642
    return {"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")
648
def 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

651
    This only gets called if the git-archive 'subst' keywords were *not*
652
    expanded, and _version.py hasn't already been rewritten with a short
653
    version string, meaning we're inside a checked out source tree.
654
    """
655
    GITS = ["git"]
656
    TAG_PREFIX_REGEX = "*"
657
    if sys.platform == "win32":
658
        GITS = ["git.cmd", "git.exe"]
659
        TAG_PREFIX_REGEX = r"\*"
660

661
    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
662
                   hide_stderr=True)
663
    if rc != 0:
664
        if verbose:
665
            print("Directory %%s not under git control" %% root)
666
        raise 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)
670
    describe_out, rc = runner(GITS, ["describe", "--tags", "--dirty",
671
                                     "--always", "--long",
672
                                     "--match",
673
                                     "%%s%%s" %% (tag_prefix, TAG_PREFIX_REGEX)],
674
                              cwd=root)
675
    # --long was added in git-1.5.5
676
    if describe_out is None:
677
        raise NotThisMethod("'git describe' failed")
678
    describe_out = describe_out.strip()
679
    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
680
    if full_out is None:
681
        raise NotThisMethod("'git rev-parse' failed")
682
    full_out = full_out.strip()
683

684
    pieces = {}
685
    pieces["long"] = full_out
686
    pieces["short"] = full_out[:7]  # maybe improved later
687
    pieces["error"] = None
688

689
    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
690
                             cwd=root)
691
    # --abbrev-ref was added in git-1.6.3
692
    if rc != 0 or branch_name is None:
693
        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
694
    branch_name = branch_name.strip()
695

696
    if 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.
700
        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
701
        # --contains was added in git-1.5.4
702
        if rc != 0 or branches is None:
703
            raise NotThisMethod("'git branch --contains' returned error")
704
        branches = branches.split("\n")
705

706
        # Remove the first line if we're running detached
707
        if "(" in branches[0]:
708
            branches.pop(0)
709

710
        # Strip off the leading "* " from the list of branches.
711
        branches = [branch[2:] for branch in branches]
712
        if "master" in branches:
713
            branch_name = "master"
714
        elif not branches:
715
            branch_name = None
716
        else:
717
            # Pick the first branch that is returned. Good or bad.
718
            branch_name = branches[0]
719

720
    pieces["branch"] = branch_name
721

722
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
723
    # TAG might have hyphens.
724
    git_describe = describe_out
725

726
    # look for -dirty suffix
727
    dirty = git_describe.endswith("-dirty")
728
    pieces["dirty"] = dirty
729
    if dirty:
730
        git_describe = git_describe[:git_describe.rindex("-dirty")]
731

732
    # now we have TAG-NUM-gHEX or HEX
733

734
    if "-" in git_describe:
735
        # TAG-NUM-gHEX
736
        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
737
        if not mo:
738
            # unparsable. Maybe git-describe is misbehaving?
739
            pieces["error"] = ("unable to parse git-describe output: '%%s'"
740
                               %% describe_out)
741
            return pieces
742

743
        # tag
744
        full_tag = mo.group(1)
745
        if not full_tag.startswith(tag_prefix):
746
            if verbose:
747
                fmt = "tag '%%s' doesn't start with prefix '%%s'"
748
                print(fmt %% (full_tag, tag_prefix))
749
            pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'"
750
                               %% (full_tag, tag_prefix))
751
            return pieces
752
        pieces["closest-tag"] = full_tag[len(tag_prefix):]
753

754
        # distance: number of commits since tag
755
        pieces["distance"] = int(mo.group(2))
756

757
        # commit: short hex revision ID
758
        pieces["short"] = mo.group(3)
759

760
    else:
761
        # HEX: no tags
762
        pieces["closest-tag"] = None
763
        count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
764
        pieces["distance"] = int(count_out)  # total number of commits
765

766
    # commit date: see ISO-8601 comment in git_versions_from_keywords()
767
    date = 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.
770
    date = date.splitlines()[-1]
771
    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
772

773
    return pieces
774

775

776
def plus_or_dot(pieces):
777
    """Return a + if we don't already have one, else return a ."""
778
    if "+" in pieces.get("closest-tag", ""):
779
        return "."
780
    return "+"
781

782

783
def render_pep440(pieces):
784
    """Build up version string, with post-release "local version identifier".
785

786
    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
787
    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
788

789
    Exceptions:
790
    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
791
    """
792
    if pieces["closest-tag"]:
793
        rendered = pieces["closest-tag"]
794
        if pieces["distance"] or pieces["dirty"]:
795
            rendered += plus_or_dot(pieces)
796
            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
797
            if pieces["dirty"]:
798
                rendered += ".dirty"
799
    else:
800
        # exception #1
801
        rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"],
802
                                          pieces["short"])
803
        if pieces["dirty"]:
804
            rendered += ".dirty"
805
    return rendered
806

807

808
def render_pep440_branch(pieces):
809
    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
810

811
    The ".dev0" means not master branch. Note that .dev0 sorts backwards
812
    (a feature branch will appear "older" than the master branch).
813

814
    Exceptions:
815
    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
816
    """
817
    if pieces["closest-tag"]:
818
        rendered = pieces["closest-tag"]
819
        if pieces["distance"] or pieces["dirty"]:
820
            if pieces["branch"] != "master":
821
                rendered += ".dev0"
822
            rendered += plus_or_dot(pieces)
823
            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
824
            if pieces["dirty"]:
825
                rendered += ".dirty"
826
    else:
827
        # exception #1
828
        rendered = "0"
829
        if pieces["branch"] != "master":
830
            rendered += ".dev0"
831
        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
832
                                          pieces["short"])
833
        if pieces["dirty"]:
834
            rendered += ".dirty"
835
    return rendered
836

837

838
def pep440_split_post(ver):
839
    """Split pep440 version string at the post-release segment.
840

841
    Returns the release segments before the post-release and the
842
    post-release version number (or -1 if no post-release segment is present).
843
    """
844
    vc = str.split(ver, ".post")
845
    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
846

847

848
def render_pep440_pre(pieces):
849
    """TAG[.postN.devDISTANCE] -- No -dirty.
850

851
    Exceptions:
852
    1: no tags. 0.post0.devDISTANCE
853
    """
854
    if pieces["closest-tag"]:
855
        if pieces["distance"]:
856
            # update the post release segment
857
            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
858
            rendered = tag_version
859
            if post_version is not None:
860
                rendered += ".post%%d.dev%%d" %% (post_version+1, pieces["distance"])
861
            else:
862
                rendered += ".post0.dev%%d" %% (pieces["distance"])
863
        else:
864
            # no commits, use the tag as the version
865
            rendered = pieces["closest-tag"]
866
    else:
867
        # exception #1
868
        rendered = "0.post0.dev%%d" %% pieces["distance"]
869
    return rendered
870

871

872
def render_pep440_post(pieces):
873
    """TAG[.postDISTANCE[.dev0]+gHEX] .
874

875
    The ".dev0" means dirty. Note that .dev0 sorts backwards
876
    (a dirty tree will appear "older" than the corresponding clean one),
877
    but you shouldn't be releasing software with -dirty anyways.
878

879
    Exceptions:
880
    1: no tags. 0.postDISTANCE[.dev0]
881
    """
882
    if pieces["closest-tag"]:
883
        rendered = pieces["closest-tag"]
884
        if pieces["distance"] or pieces["dirty"]:
885
            rendered += ".post%%d" %% pieces["distance"]
886
            if pieces["dirty"]:
887
                rendered += ".dev0"
888
            rendered += plus_or_dot(pieces)
889
            rendered += "g%%s" %% pieces["short"]
890
    else:
891
        # exception #1
892
        rendered = "0.post%%d" %% pieces["distance"]
893
        if pieces["dirty"]:
894
            rendered += ".dev0"
895
        rendered += "+g%%s" %% pieces["short"]
896
    return rendered
897

898

899
def render_pep440_post_branch(pieces):
900
    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
901

902
    The ".dev0" means not master branch.
903

904
    Exceptions:
905
    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
906
    """
907
    if pieces["closest-tag"]:
908
        rendered = pieces["closest-tag"]
909
        if pieces["distance"] or pieces["dirty"]:
910
            rendered += ".post%%d" %% pieces["distance"]
911
            if pieces["branch"] != "master":
912
                rendered += ".dev0"
913
            rendered += plus_or_dot(pieces)
914
            rendered += "g%%s" %% pieces["short"]
915
            if pieces["dirty"]:
916
                rendered += ".dirty"
917
    else:
918
        # exception #1
919
        rendered = "0.post%%d" %% pieces["distance"]
920
        if pieces["branch"] != "master":
921
            rendered += ".dev0"
922
        rendered += "+g%%s" %% pieces["short"]
923
        if pieces["dirty"]:
924
            rendered += ".dirty"
925
    return rendered
926

927

928
def render_pep440_old(pieces):
929
    """TAG[.postDISTANCE[.dev0]] .
930

931
    The ".dev0" means dirty.
932

933
    Exceptions:
934
    1: no tags. 0.postDISTANCE[.dev0]
935
    """
936
    if pieces["closest-tag"]:
937
        rendered = pieces["closest-tag"]
938
        if pieces["distance"] or pieces["dirty"]:
939
            rendered += ".post%%d" %% pieces["distance"]
940
            if pieces["dirty"]:
941
                rendered += ".dev0"
942
    else:
943
        # exception #1
944
        rendered = "0.post%%d" %% pieces["distance"]
945
        if pieces["dirty"]:
946
            rendered += ".dev0"
947
    return rendered
948

949

950
def render_git_describe(pieces):
951
    """TAG[-DISTANCE-gHEX][-dirty].
952

953
    Like 'git describe --tags --dirty --always'.
954

955
    Exceptions:
956
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
957
    """
958
    if pieces["closest-tag"]:
959
        rendered = pieces["closest-tag"]
960
        if pieces["distance"]:
961
            rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
962
    else:
963
        # exception #1
964
        rendered = pieces["short"]
965
    if pieces["dirty"]:
966
        rendered += "-dirty"
967
    return rendered
968

969

970
def render_git_describe_long(pieces):
971
    """TAG-DISTANCE-gHEX[-dirty].
972

973
    Like 'git describe --tags --dirty --always -long'.
974
    The distance/hash is unconditional.
975

976
    Exceptions:
977
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
978
    """
979
    if pieces["closest-tag"]:
980
        rendered = pieces["closest-tag"]
981
        rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
982
    else:
983
        # exception #1
984
        rendered = pieces["short"]
985
    if pieces["dirty"]:
986
        rendered += "-dirty"
987
    return rendered
988

989

990
def render(pieces, style):
991
    """Render the given version pieces into the requested style."""
992
    if pieces["error"]:
993
        return {"version": "unknown",
994
                "full-revisionid": pieces.get("long"),
995
                "dirty": None,
996
                "error": pieces["error"],
997
                "date": None}
998

999
    if not style or style == "default":
1000
        style = "pep440"  # the default
1001

1002
    if style == "pep440":
1003
        rendered = render_pep440(pieces)
1004
    elif style == "pep440-branch":
1005
        rendered = render_pep440_branch(pieces)
1006
    elif style == "pep440-pre":
1007
        rendered = render_pep440_pre(pieces)
1008
    elif style == "pep440-post":
1009
        rendered = render_pep440_post(pieces)
1010
    elif style == "pep440-post-branch":
1011
        rendered = render_pep440_post_branch(pieces)
1012
    elif style == "pep440-old":
1013
        rendered = render_pep440_old(pieces)
1014
    elif style == "git-describe":
1015
        rendered = render_git_describe(pieces)
1016
    elif style == "git-describe-long":
1017
        rendered = render_git_describe_long(pieces)
1018
    else:
1019
        raise ValueError("unknown style '%%s'" %% style)
1020

1021
    return {"version": rendered, "full-revisionid": pieces["long"],
1022
            "dirty": pieces["dirty"], "error": None,
1023
            "date": pieces.get("date")}
1024

1025

1026
def 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

1033
    cfg = get_config()
1034
    verbose = cfg.verbose
1035

1036
    try:
1037
        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
1038
                                          verbose)
1039
    except NotThisMethod:
1040
        pass
1041

1042
    try:
1043
        root = 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__.
1047
        for _ in cfg.versionfile_source.split('/'):
1048
            root = os.path.dirname(root)
1049
    except NameError:
1050
        return {"version": "0+unknown", "full-revisionid": None,
1051
                "dirty": None,
1052
                "error": "unable to find root of source tree",
1053
                "date": None}
1054

1055
    try:
1056
        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
1057
        return render(pieces, cfg.style)
1058
    except NotThisMethod:
1059
        pass
1060

1061
    try:
1062
        if cfg.parentdir_prefix:
1063
            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
1064
    except NotThisMethod:
1065
        pass
1066

1067
    return {"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")
1074
def 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.
1080
    keywords = {}
1081
    try:
1082
        with open(versionfile_abs, "r") as fobj:
1083
            for line in fobj:
1084
                if line.strip().startswith("git_refnames ="):
1085
                    mo = re.search(r'=\s*"(.*)"', line)
1086
                    if mo:
1087
                        keywords["refnames"] = mo.group(1)
1088
                if line.strip().startswith("git_full ="):
1089
                    mo = re.search(r'=\s*"(.*)"', line)
1090
                    if mo:
1091
                        keywords["full"] = mo.group(1)
1092
                if line.strip().startswith("git_date ="):
1093
                    mo = re.search(r'=\s*"(.*)"', line)
1094
                    if mo:
1095
                        keywords["date"] = mo.group(1)
1096
    except OSError:
1097
        pass
1098
    return keywords
1099

1100

1101
@register_vcs_handler("git", "keywords")
1102
def git_versions_from_keywords(keywords, tag_prefix, verbose):
1103
    """Get version information from git keywords."""
1104
    if "refnames" not in keywords:
1105
        raise NotThisMethod("Short version file found")
1106
    date = keywords.get("date")
1107
    if date is not None:
1108
        # Use only the last line.  Previous lines may contain GPG signature
1109
        # information.
1110
        date = 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.
1118
        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1119
    refnames = keywords["refnames"].strip()
1120
    if refnames.startswith("$Format"):
1121
        if verbose:
1122
            print("keywords are unexpanded, not using")
1123
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
1124
    refs = {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.
1127
    TAG = "tag: "
1128
    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
1129
    if 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".
1137
        tags = {r for r in refs if re.search(r"\d", r)}
1138
        if verbose:
1139
            print("discarding '%s', no digits" % ",".join(refs - tags))
1140
    if verbose:
1141
        print("likely tags: %s" % ",".join(sorted(tags)))
1142
    for ref in sorted(tags):
1143
        # sorting will prefer e.g. "2.0" over "2.0rc1"
1144
        if ref.startswith(tag_prefix):
1145
            r = 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 '')
1149
            if not re.match(r"\d", r):
1150
                continue
1151
            if verbose:
1152
                print("picking %s" % r)
1153
            return {
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
1161
    if verbose:
1162
        print("no suitable tags, using unknown + full revision id")
1163
    return {
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")
1173
def 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

1176
    This only gets called if the git-archive 'subst' keywords were *not*
1177
    expanded, and _version.py hasn't already been rewritten with a short
1178
    version string, meaning we're inside a checked out source tree.
1179
    """
1180
    GITS = ["git"]
1181
    TAG_PREFIX_REGEX = "*"
1182
    if sys.platform == "win32":
1183
        GITS = ["git.cmd", "git.exe"]
1184
        TAG_PREFIX_REGEX = r"\*"
1185

1186
    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True)
1187
    if rc != 0:
1188
        if verbose:
1189
            print("Directory %s not under git control" % root)
1190
        raise 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)
1194
    describe_out, rc = runner(
1195
        GITS,
1196
        [
1197
            "describe",
1198
            "--tags",
1199
            "--dirty",
1200
            "--always",
1201
            "--long",
1202
            "--match",
1203
            "%s%s" % (tag_prefix, TAG_PREFIX_REGEX),
1204
        ],
1205
        cwd=root,
1206
    )
1207
    # --long was added in git-1.5.5
1208
    if describe_out is None:
1209
        raise NotThisMethod("'git describe' failed")
1210
    describe_out = describe_out.strip()
1211
    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
1212
    if full_out is None:
1213
        raise NotThisMethod("'git rev-parse' failed")
1214
    full_out = full_out.strip()
1215

1216
    pieces = {}
1217
    pieces["long"] = full_out
1218
    pieces["short"] = full_out[:7]  # maybe improved later
1219
    pieces["error"] = None
1220

1221
    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
1222
    # --abbrev-ref was added in git-1.6.3
1223
    if rc != 0 or branch_name is None:
1224
        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
1225
    branch_name = branch_name.strip()
1226

1227
    if 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.
1231
        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
1232
        # --contains was added in git-1.5.4
1233
        if rc != 0 or branches is None:
1234
            raise NotThisMethod("'git branch --contains' returned error")
1235
        branches = branches.split("\n")
1236

1237
        # Remove the first line if we're running detached
1238
        if "(" in branches[0]:
1239
            branches.pop(0)
1240

1241
        # Strip off the leading "* " from the list of branches.
1242
        branches = [branch[2:] for branch in branches]
1243
        if "master" in branches:
1244
            branch_name = "master"
1245
        elif not branches:
1246
            branch_name = None
1247
        else:
1248
            # Pick the first branch that is returned. Good or bad.
1249
            branch_name = branches[0]
1250

1251
    pieces["branch"] = branch_name
1252

1253
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
1254
    # TAG might have hyphens.
1255
    git_describe = describe_out
1256

1257
    # look for -dirty suffix
1258
    dirty = git_describe.endswith("-dirty")
1259
    pieces["dirty"] = dirty
1260
    if dirty:
1261
        git_describe = git_describe[: git_describe.rindex("-dirty")]
1262

1263
    # now we have TAG-NUM-gHEX or HEX
1264

1265
    if "-" in git_describe:
1266
        # TAG-NUM-gHEX
1267
        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
1268
        if not mo:
1269
            # unparsable. Maybe git-describe is misbehaving?
1270
            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
1271
            return pieces
1272

1273
        # tag
1274
        full_tag = mo.group(1)
1275
        if not full_tag.startswith(tag_prefix):
1276
            if verbose:
1277
                fmt = "tag '%s' doesn't start with prefix '%s'"
1278
                print(fmt % (full_tag, tag_prefix))
1279
            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
1280
                full_tag,
1281
                tag_prefix,
1282
            )
1283
            return pieces
1284
        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
1285

1286
        # distance: number of commits since tag
1287
        pieces["distance"] = int(mo.group(2))
1288

1289
        # commit: short hex revision ID
1290
        pieces["short"] = mo.group(3)
1291

1292
    else:
1293
        # HEX: no tags
1294
        pieces["closest-tag"] = None
1295
        count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
1296
        pieces["distance"] = int(count_out)  # total number of commits
1297

1298
    # commit date: see ISO-8601 comment in git_versions_from_keywords()
1299
    date = 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.
1302
    date = date.splitlines()[-1]
1303
    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1304

1305
    return pieces
1306

1307

1308
def do_vcs_install(manifest_in, versionfile_source, ipy):
1309
    """Git-specific installation logic for Versioneer.
1310

1311
    For Git, this means creating/changing .gitattributes to mark _version.py
1312
    for export-subst keyword substitution.
1313
    """
1314
    GITS = ["git"]
1315
    if sys.platform == "win32":
1316
        GITS = ["git.cmd", "git.exe"]
1317
    files = [manifest_in, versionfile_source]
1318
    if ipy:
1319
        files.append(ipy)
1320
    try:
1321
        my_path = __file__
1322
        if my_path.endswith(".pyc") or my_path.endswith(".pyo"):
1323
            my_path = os.path.splitext(my_path)[0] + ".py"
1324
        versioneer_file = os.path.relpath(my_path)
1325
    except NameError:
1326
        versioneer_file = "versioneer.py"
1327
    files.append(versioneer_file)
1328
    present = False
1329
    try:
1330
        with open(".gitattributes", "r") as fobj:
1331
            for line in fobj:
1332
                if line.strip().startswith(versionfile_source):
1333
                    if "export-subst" in line.strip().split()[1:]:
1334
                        present = True
1335
                        break
1336
    except OSError:
1337
        pass
1338
    if not present:
1339
        with open(".gitattributes", "a+") as fobj:
1340
            fobj.write(f"{versionfile_source} export-subst\n")
1341
        files.append(".gitattributes")
1342
    run_command(GITS, ["add", "--"] + files)
1343

1344

1345
def versions_from_parentdir(parentdir_prefix, root, verbose):
1346
    """Try to determine the version from the parent directory name.
1347

1348
    Source tarballs conventionally unpack into a directory that includes both
1349
    the project name and a version string. We will also support searching up
1350
    two directory levels for an appropriately named parent directory
1351
    """
1352
    rootdirs = []
1353

1354
    for _ in range(3):
1355
        dirname = os.path.basename(root)
1356
        if dirname.startswith(parentdir_prefix):
1357
            return {
1358
                "version": dirname[len(parentdir_prefix) :],
1359
                "full-revisionid": None,
1360
                "dirty": False,
1361
                "error": None,
1362
                "date": None,
1363
            }
1364
        rootdirs.append(root)
1365
        root = os.path.dirname(root)  # up a level
1366

1367
    if verbose:
1368
        print(
1369
            "Tried directories %s but none started with prefix %s"
1370
            % (str(rootdirs), parentdir_prefix)
1371
        )
1372
    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
1373

1374

1375
SHORT_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

1381
import json
1382

1383
version_json = '''
1384
%s
1385
'''  # END VERSION_JSON
1386

1387

1388
def get_versions():
1389
    return json.loads(version_json)
1390
"""
1391

1392

1393
def versions_from_file(filename):
1394
    """Try to determine the version from _version.py if present."""
1395
    try:
1396
        with open(filename) as f:
1397
            contents = f.read()
1398
    except OSError:
1399
        raise NotThisMethod("unable to read _version.py")
1400
    mo = re.search(
1401
        r"version_json = '''\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
1402
    )
1403
    if not mo:
1404
        mo = re.search(
1405
            r"version_json = '''\r\n(.*)'''  # END VERSION_JSON", contents, re.M | re.S
1406
        )
1407
    if not mo:
1408
        raise NotThisMethod("no version_json in _version.py")
1409
    return json.loads(mo.group(1))
1410

1411

1412
def write_to_version_file(filename, versions):
1413
    """Write the given version number to the given _version.py file."""
1414
    os.unlink(filename)
1415
    contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": "))
1416
    with open(filename, "w") as f:
1417
        f.write(SHORT_VERSION_PY % contents)
1418

1419
    print("set %s to '%s'" % (filename, versions["version"]))
1420

1421

1422
def plus_or_dot(pieces):
1423
    """Return a + if we don't already have one, else return a ."""
1424
    if "+" in pieces.get("closest-tag", ""):
1425
        return "."
1426
    return "+"
1427

1428

1429
def render_pep440(pieces):
1430
    """Build up version string, with post-release "local version identifier".
1431

1432
    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
1433
    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
1434

1435
    Exceptions:
1436
    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
1437
    """
1438
    if pieces["closest-tag"]:
1439
        rendered = pieces["closest-tag"]
1440
        if pieces["distance"] or pieces["dirty"]:
1441
            rendered += plus_or_dot(pieces)
1442
            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
1443
            if pieces["dirty"]:
1444
                rendered += ".dirty"
1445
    else:
1446
        # exception #1
1447
        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
1448
        if pieces["dirty"]:
1449
            rendered += ".dirty"
1450
    return rendered
1451

1452

1453
def render_pep440_branch(pieces):
1454
    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
1455

1456
    The ".dev0" means not master branch. Note that .dev0 sorts backwards
1457
    (a feature branch will appear "older" than the master branch).
1458

1459
    Exceptions:
1460
    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
1461
    """
1462
    if pieces["closest-tag"]:
1463
        rendered = pieces["closest-tag"]
1464
        if pieces["distance"] or pieces["dirty"]:
1465
            if pieces["branch"] != "master":
1466
                rendered += ".dev0"
1467
            rendered += plus_or_dot(pieces)
1468
            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
1469
            if pieces["dirty"]:
1470
                rendered += ".dirty"
1471
    else:
1472
        # exception #1
1473
        rendered = "0"
1474
        if pieces["branch"] != "master":
1475
            rendered += ".dev0"
1476
        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
1477
        if pieces["dirty"]:
1478
            rendered += ".dirty"
1479
    return rendered
1480

1481

1482
def pep440_split_post(ver):
1483
    """Split pep440 version string at the post-release segment.
1484

1485
    Returns the release segments before the post-release and the
1486
    post-release version number (or -1 if no post-release segment is present).
1487
    """
1488
    vc = str.split(ver, ".post")
1489
    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
1490

1491

1492
def render_pep440_pre(pieces):
1493
    """TAG[.postN.devDISTANCE] -- No -dirty.
1494

1495
    Exceptions:
1496
    1: no tags. 0.post0.devDISTANCE
1497
    """
1498
    if pieces["closest-tag"]:
1499
        if pieces["distance"]:
1500
            # update the post release segment
1501
            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
1502
            rendered = tag_version
1503
            if post_version is not None:
1504
                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
1505
            else:
1506
                rendered += ".post0.dev%d" % (pieces["distance"])
1507
        else:
1508
            # no commits, use the tag as the version
1509
            rendered = pieces["closest-tag"]
1510
    else:
1511
        # exception #1
1512
        rendered = "0.post0.dev%d" % pieces["distance"]
1513
    return rendered
1514

1515

1516
def render_pep440_post(pieces):
1517
    """TAG[.postDISTANCE[.dev0]+gHEX] .
1518

1519
    The ".dev0" means dirty. Note that .dev0 sorts backwards
1520
    (a dirty tree will appear "older" than the corresponding clean one),
1521
    but you shouldn't be releasing software with -dirty anyways.
1522

1523
    Exceptions:
1524
    1: no tags. 0.postDISTANCE[.dev0]
1525
    """
1526
    if pieces["closest-tag"]:
1527
        rendered = pieces["closest-tag"]
1528
        if pieces["distance"] or pieces["dirty"]:
1529
            rendered += ".post%d" % pieces["distance"]
1530
            if pieces["dirty"]:
1531
                rendered += ".dev0"
1532
            rendered += plus_or_dot(pieces)
1533
            rendered += "g%s" % pieces["short"]
1534
    else:
1535
        # exception #1
1536
        rendered = "0.post%d" % pieces["distance"]
1537
        if pieces["dirty"]:
1538
            rendered += ".dev0"
1539
        rendered += "+g%s" % pieces["short"]
1540
    return rendered
1541

1542

1543
def render_pep440_post_branch(pieces):
1544
    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
1545

1546
    The ".dev0" means not master branch.
1547

1548
    Exceptions:
1549
    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
1550
    """
1551
    if pieces["closest-tag"]:
1552
        rendered = pieces["closest-tag"]
1553
        if pieces["distance"] or pieces["dirty"]:
1554
            rendered += ".post%d" % pieces["distance"]
1555
            if pieces["branch"] != "master":
1556
                rendered += ".dev0"
1557
            rendered += plus_or_dot(pieces)
1558
            rendered += "g%s" % pieces["short"]
1559
            if pieces["dirty"]:
1560
                rendered += ".dirty"
1561
    else:
1562
        # exception #1
1563
        rendered = "0.post%d" % pieces["distance"]
1564
        if pieces["branch"] != "master":
1565
            rendered += ".dev0"
1566
        rendered += "+g%s" % pieces["short"]
1567
        if pieces["dirty"]:
1568
            rendered += ".dirty"
1569
    return rendered
1570

1571

1572
def render_pep440_old(pieces):
1573
    """TAG[.postDISTANCE[.dev0]] .
1574

1575
    The ".dev0" means dirty.
1576

1577
    Exceptions:
1578
    1: no tags. 0.postDISTANCE[.dev0]
1579
    """
1580
    if pieces["closest-tag"]:
1581
        rendered = pieces["closest-tag"]
1582
        if pieces["distance"] or pieces["dirty"]:
1583
            rendered += ".post%d" % pieces["distance"]
1584
            if pieces["dirty"]:
1585
                rendered += ".dev0"
1586
    else:
1587
        # exception #1
1588
        rendered = "0.post%d" % pieces["distance"]
1589
        if pieces["dirty"]:
1590
            rendered += ".dev0"
1591
    return rendered
1592

1593

1594
def render_git_describe(pieces):
1595
    """TAG[-DISTANCE-gHEX][-dirty].
1596

1597
    Like 'git describe --tags --dirty --always'.
1598

1599
    Exceptions:
1600
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
1601
    """
1602
    if pieces["closest-tag"]:
1603
        rendered = pieces["closest-tag"]
1604
        if pieces["distance"]:
1605
            rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
1606
    else:
1607
        # exception #1
1608
        rendered = pieces["short"]
1609
    if pieces["dirty"]:
1610
        rendered += "-dirty"
1611
    return rendered
1612

1613

1614
def render_git_describe_long(pieces):
1615
    """TAG-DISTANCE-gHEX[-dirty].
1616

1617
    Like 'git describe --tags --dirty --always -long'.
1618
    The distance/hash is unconditional.
1619

1620
    Exceptions:
1621
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
1622
    """
1623
    if pieces["closest-tag"]:
1624
        rendered = pieces["closest-tag"]
1625
        rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
1626
    else:
1627
        # exception #1
1628
        rendered = pieces["short"]
1629
    if pieces["dirty"]:
1630
        rendered += "-dirty"
1631
    return rendered
1632

1633

1634
def render(pieces, style):
1635
    """Render the given version pieces into the requested style."""
1636
    if pieces["error"]:
1637
        return {
1638
            "version": "unknown",
1639
            "full-revisionid": pieces.get("long"),
1640
            "dirty": None,
1641
            "error": pieces["error"],
1642
            "date": None,
1643
        }
1644

1645
    if not style or style == "default":
1646
        style = "pep440"  # the default
1647

1648
    if style == "pep440":
1649
        rendered = render_pep440(pieces)
1650
    elif style == "pep440-branch":
1651
        rendered = render_pep440_branch(pieces)
1652
    elif style == "pep440-pre":
1653
        rendered = render_pep440_pre(pieces)
1654
    elif style == "pep440-post":
1655
        rendered = render_pep440_post(pieces)
1656
    elif style == "pep440-post-branch":
1657
        rendered = render_pep440_post_branch(pieces)
1658
    elif style == "pep440-old":
1659
        rendered = render_pep440_old(pieces)
1660
    elif style == "git-describe":
1661
        rendered = render_git_describe(pieces)
1662
    elif style == "git-describe-long":
1663
        rendered = render_git_describe_long(pieces)
1664
    else:
1665
        raise ValueError("unknown style '%s'" % style)
1666

1667
    return {
1668
        "version": rendered,
1669
        "full-revisionid": pieces["long"],
1670
        "dirty": pieces["dirty"],
1671
        "error": None,
1672
        "date": pieces.get("date"),
1673
    }
1674

1675

1676
class VersioneerBadRootError(Exception):
1677
    """The project root directory is unknown or missing key files."""
1678

1679

1680
def get_versions(verbose=False):
1681
    """Get the project version from whatever source is available.
1682

1683
    Returns dict with two keys: 'version' and 'full'.
1684
    """
1685
    if "versioneer" in sys.modules:
1686
        # see the discussion in cmdclass.py:get_cmdclass()
1687
        del sys.modules["versioneer"]
1688

1689
    root = get_root()
1690
    cfg = get_config_from_root(root)
1691

1692
    assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
1693
    handlers = HANDLERS.get(cfg.VCS)
1694
    assert handlers, "unrecognized VCS '%s'" % cfg.VCS
1695
    verbose = verbose or cfg.verbose
1696
    assert (
1697
        cfg.versionfile_source is not None
1698
    ), "please set versioneer.versionfile_source"
1699
    assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
1700

1701
    versionfile_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

1709
    get_keywords_f = handlers.get("get_keywords")
1710
    from_keywords_f = handlers.get("keywords")
1711
    if get_keywords_f and from_keywords_f:
1712
        try:
1713
            keywords = get_keywords_f(versionfile_abs)
1714
            ver = from_keywords_f(keywords, cfg.tag_prefix, verbose)
1715
            if verbose:
1716
                print("got version from expanded keyword %s" % ver)
1717
            return ver
1718
        except NotThisMethod:
1719
            pass
1720

1721
    try:
1722
        ver = versions_from_file(versionfile_abs)
1723
        if verbose:
1724
            print("got version from file %s %s" % (versionfile_abs, ver))
1725
        return ver
1726
    except NotThisMethod:
1727
        pass
1728

1729
    from_vcs_f = handlers.get("pieces_from_vcs")
1730
    if from_vcs_f:
1731
        try:
1732
            pieces = from_vcs_f(cfg.tag_prefix, root, verbose)
1733
            ver = render(pieces, cfg.style)
1734
            if verbose:
1735
                print("got version from VCS %s" % ver)
1736
            return ver
1737
        except NotThisMethod:
1738
            pass
1739

1740
    try:
1741
        if cfg.parentdir_prefix:
1742
            ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
1743
            if verbose:
1744
                print("got version from parentdir %s" % ver)
1745
            return ver
1746
    except NotThisMethod:
1747
        pass
1748

1749
    if verbose:
1750
        print("unable to compute version")
1751

1752
    return {
1753
        "version": "0+unknown",
1754
        "full-revisionid": None,
1755
        "dirty": None,
1756
        "error": "unable to compute version",
1757
        "date": None,
1758
    }
1759

1760

1761
def get_version():
1762
    """Get the short version string for this project."""
1763
    return get_versions()["version"]
1764

1765

1766
def get_cmdclass(cmdclass=None):
1767
    """Get the custom setuptools/distutils subclasses used by Versioneer.
1768

1769
    If the package uses a different cmdclass (e.g. one from numpy), it
1770
    should be provide as an argument.
1771
    """
1772
    if "versioneer" in sys.modules:
1773
        del 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

1787
    cmds = {} if cmdclass is None else cmdclass.copy()
1788

1789
    # we add "version" to both distutils and setuptools
1790
    from distutils.core import Command
1791

1792
    class cmd_version(Command):
1793
        description = "report generated version string"
1794
        user_options = []
1795
        boolean_options = []
1796

1797
        def initialize_options(self):
1798
            pass
1799

1800
        def finalize_options(self):
1801
            pass
1802

1803
        def run(self):
1804
            vers = get_versions(verbose=True)
1805
            print("Version: %s" % vers["version"])
1806
            print(" full-revisionid: %s" % vers.get("full-revisionid"))
1807
            print(" dirty: %s" % vers.get("dirty"))
1808
            print(" date: %s" % vers.get("date"))
1809
            if vers["error"]:
1810
                print(" error: %s" % vers["error"])
1811

1812
    cmds["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
1830
    if "build_py" in cmds:
1831
        _build_py = cmds["build_py"]
1832
    elif "setuptools" in sys.modules:
1833
        from setuptools.command.build_py import build_py as _build_py
1834
    else:
1835
        from distutils.command.build_py import build_py as _build_py
1836

1837
    class cmd_build_py(_build_py):
1838
        def run(self):
1839
            root = get_root()
1840
            cfg = get_config_from_root(root)
1841
            versions = 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
1845
            if cfg.versionfile_build:
1846
                target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
1847
                print("UPDATING %s" % target_versionfile)
1848
                write_to_version_file(target_versionfile, versions)
1849

1850
    cmds["build_py"] = cmd_build_py
1851

1852
    if "build_ext" in cmds:
1853
        _build_ext = cmds["build_ext"]
1854
    elif "setuptools" in sys.modules:
1855
        from setuptools.command.build_ext import build_ext as _build_ext
1856
    else:
1857
        from distutils.command.build_ext import build_ext as _build_ext
1858

1859
    class cmd_build_ext(_build_ext):
1860
        def run(self):
1861
            root = get_root()
1862
            cfg = get_config_from_root(root)
1863
            versions = get_versions()
1864
            _build_ext.run(self)
1865
            if 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.
1870
                return
1871
            # now locate _version.py in the new build/ directory and replace
1872
            # it with an updated value
1873
            target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build)
1874
            print("UPDATING %s" % target_versionfile)
1875
            write_to_version_file(target_versionfile, versions)
1876

1877
    cmds["build_ext"] = cmd_build_ext
1878

1879
    if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
1880
        from 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

1889
        class cmd_build_exe(_build_exe):
1890
            def run(self):
1891
                root = get_root()
1892
                cfg = get_config_from_root(root)
1893
                versions = get_versions()
1894
                target_versionfile = cfg.versionfile_source
1895
                print("UPDATING %s" % target_versionfile)
1896
                write_to_version_file(target_versionfile, versions)
1897

1898
                _build_exe.run(self)
1899
                os.unlink(target_versionfile)
1900
                with open(cfg.versionfile_source, "w") as f:
1901
                    LONG = LONG_VERSION_PY[cfg.VCS]
1902
                    f.write(
1903
                        LONG
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

1913
        cmds["build_exe"] = cmd_build_exe
1914
        del cmds["build_py"]
1915

1916
    if "py2exe" in sys.modules:  # py2exe enabled?
1917
        from py2exe.distutils_buildexe import py2exe as _py2exe
1918

1919
        class cmd_py2exe(_py2exe):
1920
            def run(self):
1921
                root = get_root()
1922
                cfg = get_config_from_root(root)
1923
                versions = get_versions()
1924
                target_versionfile = cfg.versionfile_source
1925
                print("UPDATING %s" % target_versionfile)
1926
                write_to_version_file(target_versionfile, versions)
1927

1928
                _py2exe.run(self)
1929
                os.unlink(target_versionfile)
1930
                with open(cfg.versionfile_source, "w") as f:
1931
                    LONG = LONG_VERSION_PY[cfg.VCS]
1932
                    f.write(
1933
                        LONG
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

1943
        cmds["py2exe"] = cmd_py2exe
1944

1945
    # we override different "sdist" commands for both environments
1946
    if "sdist" in cmds:
1947
        _sdist = cmds["sdist"]
1948
    elif "setuptools" in sys.modules:
1949
        from setuptools.command.sdist import sdist as _sdist
1950
    else:
1951
        from distutils.command.sdist import sdist as _sdist
1952

1953
    class cmd_sdist(_sdist):
1954
        def run(self):
1955
            versions = get_versions()
1956
            self._versioneer_generated_versions = versions
1957
            # unless we update this, the command will keep using the old
1958
            # version
1959
            self.distribution.metadata.version = versions["version"]
1960
            return _sdist.run(self)
1961

1962
        def make_release_tree(self, base_dir, files):
1963
            root = get_root()
1964
            cfg = 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
1969
            target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
1970
            print("UPDATING %s" % target_versionfile)
1971
            write_to_version_file(
1972
                target_versionfile, self._versioneer_generated_versions
1973
            )
1974

1975
    cmds["sdist"] = cmd_sdist
1976

1977
    return cmds
1978

1979

1980
CONFIG_ERROR = """
1981
setup.cfg is missing the necessary Versioneer configuration. You need
1982
a section like:
1983

1984
 [versioneer]
1985
 VCS = git
1986
 style = pep440
1987
 versionfile_source = src/myproject/_version.py
1988
 versionfile_build = myproject/_version.py
1989
 tag_prefix =
1990
 parentdir_prefix = myproject-
1991

1992
You will also need to edit your setup.py to use the results:
1993

1994
 import versioneer
1995
 setup(version=versioneer.get_version(),
1996
       cmdclass=versioneer.get_cmdclass(), ...)
1997

1998
Please read the docstring in ./versioneer.py for configuration instructions,
1999
edit setup.cfg, and re-run the installer or 'python versioneer.py setup'.
2000
"""
2001

2002
SAMPLE_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

2017
OLD_SNIPPET = """
2018
from ._version import get_versions
2019
__version__ = get_versions()['version']
2020
del get_versions
2021
"""
2022

2023
INIT_PY_SNIPPET = """
2024
from . import {0}
2025
__version__ = {0}.get_versions()['version']
2026
"""
2027

2028

2029
def do_setup():
2030
    """Do main VCS-independent setup function for installing Versioneer."""
2031
    root = get_root()
2032
    try:
2033
        cfg = get_config_from_root(root)
2034
    except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e:
2035
        if isinstance(e, (OSError, configparser.NoSectionError)):
2036
            print("Adding sample versioneer config to setup.cfg", file=sys.stderr)
2037
            with open(os.path.join(root, "setup.cfg"), "a") as f:
2038
                f.write(SAMPLE_CONFIG)
2039
        print(CONFIG_ERROR, file=sys.stderr)
2040
        return 1
2041

2042
    print(" creating %s" % cfg.versionfile_source)
2043
    with open(cfg.versionfile_source, "w") as f:
2044
        LONG = LONG_VERSION_PY[cfg.VCS]
2045
        f.write(
2046
            LONG
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

2056
    ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py")
2057
    if os.path.exists(ipy):
2058
        try:
2059
            with open(ipy, "r") as f:
2060
                old = f.read()
2061
        except OSError:
2062
            old = ""
2063
        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
2064
        snippet = INIT_PY_SNIPPET.format(module)
2065
        if OLD_SNIPPET in old:
2066
            print(" replacing boilerplate in %s" % ipy)
2067
            with open(ipy, "w") as f:
2068
                f.write(old.replace(OLD_SNIPPET, snippet))
2069
        elif snippet not in old:
2070
            print(" appending to %s" % ipy)
2071
            with open(ipy, "a") as f:
2072
                f.write(snippet)
2073
        else:
2074
            print(" %s unmodified" % ipy)
2075
    else:
2076
        print(" %s doesn't exist, ok" % ipy)
2077
        ipy = 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.
2083
    manifest_in = os.path.join(root, "MANIFEST.in")
2084
    simple_includes = set()
2085
    try:
2086
        with open(manifest_in, "r") as f:
2087
            for line in f:
2088
                if line.startswith("include "):
2089
                    for include in line.split()[1:]:
2090
                        simple_includes.add(include)
2091
    except OSError:
2092
        pass
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.
2097
    if "versioneer.py" not in simple_includes:
2098
        print(" appending 'versioneer.py' to MANIFEST.in")
2099
        with open(manifest_in, "a") as f:
2100
            f.write("include versioneer.py\n")
2101
    else:
2102
        print(" 'versioneer.py' already in MANIFEST.in")
2103
    if cfg.versionfile_source not in simple_includes:
2104
        print(
2105
            " appending versionfile_source ('%s') to MANIFEST.in"
2106
            % cfg.versionfile_source
2107
        )
2108
        with open(manifest_in, "a") as f:
2109
            f.write("include %s\n" % cfg.versionfile_source)
2110
    else:
2111
        print(" 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.
2116
    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
2117
    return 0
2118

2119

2120
def scan_setup_py():
2121
    """Validate the contents of setup.py against Versioneer's expectations."""
2122
    found = set()
2123
    setters = False
2124
    errors = 0
2125
    with open("setup.py", "r") as f:
2126
        for line in f.readlines():
2127
            if "import versioneer" in line:
2128
                found.add("import")
2129
            if "versioneer.get_cmdclass()" in line:
2130
                found.add("cmdclass")
2131
            if "versioneer.get_version()" in line:
2132
                found.add("get_version")
2133
            if "versioneer.VCS" in line:
2134
                setters = True
2135
            if "versioneer.versionfile_source" in line:
2136
                setters = True
2137
    if len(found) != 3:
2138
        print("")
2139
        print("Your setup.py appears to be missing some important items")
2140
        print("(but I might be wrong). Please make sure it has something")
2141
        print("roughly like the following:")
2142
        print("")
2143
        print(" import versioneer")
2144
        print(" setup( version=versioneer.get_version(),")
2145
        print("        cmdclass=versioneer.get_cmdclass(),  ...)")
2146
        print("")
2147
        errors += 1
2148
    if setters:
2149
        print("You should remove lines like 'versioneer.VCS = ' and")
2150
        print("'versioneer.versionfile_source = ' . This configuration")
2151
        print("now lives in setup.cfg, and should be removed from setup.py")
2152
        print("")
2153
        errors += 1
2154
    return errors
2155

2156

2157
if __name__ == "__main__":
2158
    cmd = sys.argv[1]
2159
    if cmd == "setup":
2160
        errors = do_setup()
2161
        errors += scan_setup_py()
2162
        if errors:
2163
            sys.exit(1)
2164

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.