Completed
Push — master ( 8b3414...5729ab )
by Ryan
01:13
created

versioneer.py (9 issues)

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