Completed
Push — master ( 24b1ca...f9318a )
by John
01:18
created

versioneer.py (7 issues)

1
# pylint: skip-file
2
# Version: 0.16
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.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
First, decide on values for the following configuration variables:
92
93
* `VCS`: the version control system you use. Currently accepts "git".
94
95
* `style`: the style of version string to be produced. See "Styles" below for
96
  details. Defaults to "pep440", which looks like
97
  `TAG[+DISTANCE.gSHORTHASH[.dirty]]`.
98
99
* `versionfile_source`:
100
101
  A project-relative pathname into which the generated version strings should
102
  be written. This is usually a `_version.py` next to your project's main
103
  `__init__.py` file, so it can be imported at runtime. If your project uses
104
  `src/myproject/__init__.py`, this should be `src/myproject/_version.py`.
105
  This file should be checked in to your VCS as usual: the copy created below
106
  by `setup.py setup_versioneer` will include code that parses expanded VCS
107
  keywords in generated tarballs. The 'build' and 'sdist' commands will
108
  replace it with a copy that has just the calculated version string.
109
110
  This must be set even if your project does not have any modules (and will
111
  therefore never import `_version.py`), since "setup.py sdist" -based trees
112
  still need somewhere to record the pre-calculated version strings. Anywhere
113
  in the source tree should do. If there is a `__init__.py` next to your
114
  `_version.py`, the `setup.py setup_versioneer` command (described below)
115
  will append some `__version__`-setting assignments, if they aren't already
116
  present.
117
118
* `versionfile_build`:
119
120
  Like `versionfile_source`, but relative to the build directory instead of
121
  the source directory. These will differ when your setup.py uses
122
  'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`,
123
  then you will probably have `versionfile_build='myproject/_version.py'` and
124
  `versionfile_source='src/myproject/_version.py'`.
125
126
  If this is set to None, then `setup.py build` will not attempt to rewrite
127
  any `_version.py` in the built tree. If your project does not have any
128
  libraries (e.g. if it only builds a script), then you should use
129
  `versionfile_build = None`. To actually use the computed version string,
130
  your `setup.py` will need to override `distutils.command.build_scripts`
131
  with a subclass that explicitly inserts a copy of
132
  `versioneer.get_version()` into your script file. See
133
  `test/demoapp-script-only/setup.py` for an example.
134
135
* `tag_prefix`:
136
137
  a string, like 'PROJECTNAME-', which appears at the start of all VCS tags.
138
  If your tags look like 'myproject-1.2.0', then you should use
139
  tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this
140
  should be an empty string, using either `tag_prefix=` or `tag_prefix=''`.
141
142
* `parentdir_prefix`:
143
144
  a optional string, frequently the same as tag_prefix, which appears at the
145
  start of all unpacked tarball filenames. If your tarball unpacks into
146
  'myproject-1.2.0', this should be 'myproject-'. To disable this feature,
147
  just omit the field from your `setup.cfg`.
148
149
This tool provides one script, named `versioneer`. That script has one mode,
150
"install", which writes a copy of `versioneer.py` into the current directory
151
and runs `versioneer.py setup` to finish the installation.
152
153
To versioneer-enable your project:
154
155
* 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and
156
  populating it with the configuration values you decided earlier (note that
157
  the option names are not case-sensitive):
158
159
  ````
160
  [versioneer]
161
  VCS = git
162
  style = pep440
163
  versionfile_source = src/myproject/_version.py
164
  versionfile_build = myproject/_version.py
165
  tag_prefix =
166
  parentdir_prefix = myproject-
167
  ````
168
169
* 2: Run `versioneer install`. This will do the following:
170
171
  * copy `versioneer.py` into the top of your source tree
172
  * create `_version.py` in the right place (`versionfile_source`)
173
  * modify your `__init__.py` (if one exists next to `_version.py`) to define
174
    `__version__` (by calling a function from `_version.py`)
175
  * modify your `MANIFEST.in` to include both `versioneer.py` and the
176
    generated `_version.py` in sdist tarballs
177
178
  `versioneer install` will complain about any problems it finds with your
179
  `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all
180
  the problems.
181
182
* 3: add a `import versioneer` to your setup.py, and add the following
183
  arguments to the setup() call:
184
185
        version=versioneer.get_version(),
186
        cmdclass=versioneer.get_cmdclass(),
187
188
* 4: commit these changes to your VCS. To make sure you won't forget,
189
  `versioneer install` will mark everything it touched for addition using
190
  `git add`. Don't forget to add `setup.py` and `setup.cfg` too.
191
192
## Post-Installation Usage
193
194
Once established, all uses of your tree from a VCS checkout should get the
195
current version string. All generated tarballs should include an embedded
196
version string (so users who unpack them will not need a VCS tool installed).
197
198
If you distribute your project through PyPI, then the release process should
199
boil down to two steps:
200
201
* 1: git tag 1.0
202
* 2: python setup.py register sdist upload
203
204
If you distribute it through github (i.e. users use github to generate
205
tarballs with `git archive`), the process is:
206
207
* 1: git tag 1.0
208
* 2: git push; git push --tags
209
210
Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at
211
least one tag in its history.
212
213
## Version-String Flavors
214
215
Code which uses Versioneer can learn about its version string at runtime by
216
importing `_version` from your main `__init__.py` file and running the
217
`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can
218
import the top-level `versioneer.py` and run `get_versions()`.
219
220
Both functions return a dictionary with different flavors of version
221
information:
222
223
* `['version']`: A condensed version string, rendered using the selected
224
  style. This is the most commonly used value for the project's version
225
  string. The default "pep440" style yields strings like `0.11`,
226
  `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section
227
  below for alternative styles.
228
229
* `['full-revisionid']`: detailed revision identifier. For Git, this is the
230
  full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".
231
232
* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
233
  this is only accurate if run in a VCS checkout, otherwise it is likely to
234
  be False or None
235
236
* `['error']`: if the version string could not be computed, this will be set
237
  to a string describing the problem, otherwise it will be None. It may be
238
  useful to throw an exception in setup.py if this is set, to avoid e.g.
239
  creating tarballs with a version string of "unknown".
240
241
Some variants are more useful than others. Including `full-revisionid` in a
242
bug report should allow developers to reconstruct the exact code being tested
243
(or indicate the presence of local changes that should be shared with the
244
developers). `version` is suitable for display in an "about" box or a CLI
245
`--version` output: it can be easily compared against release notes and lists
246
of bugs fixed in various releases.
247
248
The installer adds the following text to your `__init__.py` to place a basic
249
version in `YOURPROJECT.__version__`:
250
251
    from ._version import get_versions
252
    __version__ = get_versions()['version']
253
    del get_versions
254
255
## Styles
256
257
The setup.cfg `style=` configuration controls how the VCS information is
258
rendered into a version string.
259
260
The default style, "pep440", produces a PEP440-compliant string, equal to the
261
un-prefixed tag name for actual releases, and containing an additional "local
262
version" section with more detail for in-between builds. For Git, this is
263
TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags
264
--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the
265
tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and
266
that this commit is two revisions ("+2") beyond the "0.11" tag. For released
267
software (exactly equal to a known tag), the identifier will only contain the
268
stripped tag, e.g. "0.11".
269
270
Other styles are available. See details.md in the Versioneer source tree for
271
descriptions.
272
273
## Debugging
274
275
Versioneer tries to avoid fatal errors: if something goes wrong, it will tend
276
to return a version of "0+unknown". To investigate the problem, run `setup.py
277
version`, which will run the version-lookup code in a verbose mode, and will
278
display the full contents of `get_versions()` (including the `error` string,
279
which may help identify what went wrong).
280
281
## Updating Versioneer
282
283
To upgrade your project to a new release of Versioneer, do the following:
284
285
* install the new Versioneer (`pip install -U versioneer` or equivalent)
286
* edit `setup.cfg`, if necessary, to include any new configuration settings
287
  indicated by the release notes
288
* re-run `versioneer install` in your source tree, to replace
289
  `SRC/_version.py`
290
* commit any changed files
291
292
### Upgrading to 0.16
293
294
Nothing special.
295
296
### Upgrading to 0.15
297
298
Starting with this version, Versioneer is configured with a `[versioneer]`
299
section in your `setup.cfg` file. Earlier versions required the `setup.py` to
300
set attributes on the `versioneer` module immediately after import. The new
301
version will refuse to run (raising an exception during import) until you
302
have provided the necessary `setup.cfg` section.
303
304
In addition, the Versioneer package provides an executable named
305
`versioneer`, and the installation process is driven by running `versioneer
306
install`. In 0.14 and earlier, the executable was named
307
`versioneer-installer` and was run without an argument.
308
309
### Upgrading to 0.14
310
311
0.14 changes the format of the version string. 0.13 and earlier used
312
hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a
313
plus-separated "local version" section strings, with dot-separated
314
components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old
315
format, but should be ok with the new one.
316
317
### Upgrading from 0.11 to 0.12
318
319
Nothing special.
320
321
### Upgrading from 0.10 to 0.11
322
323
You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running
324
`setup.py setup_versioneer`. This will enable the use of additional
325
version-control systems (SVN, etc) in the future.
326
327
## Future Directions
328
329
This tool is designed to make it easily extended to other version-control
330
systems: all VCS-specific components are in separate directories like
331
src/git/ . The top-level `versioneer.py` script is assembled from these
332
components by running make-versioneer.py . In the future, make-versioneer.py
333
will take a VCS name as an argument, and will construct a version of
334
`versioneer.py` that is specific to the given VCS. It might also take the
335
configuration arguments that are currently provided manually during
336
installation by editing setup.py . Alternatively, it might go the other
337
direction and include code from all supported VCS systems, reducing the
338
number of intermediate scripts.
339
340
341
## License
342
343
To make Versioneer easier to embed, all its code is dedicated to the public
344
domain. The `_version.py` that it creates is also in the public domain.
345
Specifically, both are released under the Creative Commons "Public Domain
346
Dedication" license (CC0-1.0), as described in
347
https://creativecommons.org/publicdomain/zero/1.0/ .
348
349
"""
350
351
from __future__ import print_function
352
try:
353
    import configparser
354
except ImportError:
355
    import ConfigParser as configparser
356
import errno
357
import json
358
import os
359
import re
360
import subprocess
361
import sys
362
363
364
class VersioneerConfig:
365
    """Container for Versioneer configuration parameters."""
366
367
368
def get_root():
369
    """Get the project root directory.
370
371
    We require that all commands are run from the project root, i.e. the
372
    directory that contains setup.py, setup.cfg, and versioneer.py .
373
    """
374
    root = os.path.realpath(os.path.abspath(os.getcwd()))
375
    setup_py = os.path.join(root, "setup.py")
376
    versioneer_py = os.path.join(root, "versioneer.py")
377
    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
378
        # allow 'python path/to/setup.py COMMAND'
379
        root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
380
        setup_py = os.path.join(root, "setup.py")
381
        versioneer_py = os.path.join(root, "versioneer.py")
382
    if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)):
383
        err = ("Versioneer was unable to run the project root directory. "
384
               "Versioneer requires setup.py to be executed from "
385
               "its immediate directory (like 'python setup.py COMMAND'), "
386
               "or in a way that lets it use sys.argv[0] to find the root "
387
               "(like 'python path/to/setup.py COMMAND').")
388
        raise VersioneerBadRootError(err)
389
    try:
390
        # Certain runtime workflows (setup.py install/develop in a setuptools
391
        # tree) execute all dependencies in a single python process, so
392
        # "versioneer" may be imported multiple times, and python's shared
393
        # module-import table will cache the first one. So we can't use
394
        # os.path.dirname(__file__), as that will find whichever
395
        # versioneer.py was first imported, even in later projects.
396
        me = os.path.realpath(os.path.abspath(__file__))
397
        if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]:
398
            print("Warning: build in %s is using versioneer.py from %s"
399
                  % (os.path.dirname(me), versioneer_py))
400
    except NameError:
401
        pass
402
    return root
403
404
405
def get_config_from_root(root):
406
    """Read the project setup.cfg file to determine Versioneer config."""
407
    # This might raise EnvironmentError (if setup.cfg is missing), or
408
    # configparser.NoSectionError (if it lacks a [versioneer] section), or
409
    # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
410
    # the top of versioneer.py for instructions on writing your setup.cfg .
411
    setup_cfg = os.path.join(root, "setup.cfg")
412
    parser = configparser.SafeConfigParser()
413
    with open(setup_cfg, "r") as f:
414
        parser.readfp(f)
415
    VCS = parser.get("versioneer", "VCS")  # mandatory
416
417
    def get(parser, name):
418
        if parser.has_option("versioneer", name):
419
            return parser.get("versioneer", name)
420
        return None
421
    cfg = VersioneerConfig()
422
    cfg.VCS = VCS
423
    cfg.style = get(parser, "style") or ""
424
    cfg.versionfile_source = get(parser, "versionfile_source")
425
    cfg.versionfile_build = get(parser, "versionfile_build")
426
    cfg.tag_prefix = get(parser, "tag_prefix")
427
    if cfg.tag_prefix in ("''", '""'):
428
        cfg.tag_prefix = ""
429
    cfg.parentdir_prefix = get(parser, "parentdir_prefix")
430
    cfg.verbose = get(parser, "verbose")
431
    return cfg
432
433
434
class NotThisMethod(Exception):
435
    """Exception raised if a method is not valid for the current scenario."""
436
437
# these dictionaries contain VCS-specific tools
438
LONG_VERSION_PY = {}
439
HANDLERS = {}
440
441
442
def register_vcs_handler(vcs, method):  # decorator
443
    """Decorator to mark a method as the handler for a particular VCS."""
444
    def decorate(f):
445
        """Store f in HANDLERS[vcs][method]."""
446
        if vcs not in HANDLERS:
447
            HANDLERS[vcs] = {}
448
        HANDLERS[vcs][method] = f
449
        return f
450
    return decorate
451
452
453 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...
454
    """Call the given command(s)."""
455
    assert isinstance(commands, list)
456
    p = None
457
    for c in commands:
458
        try:
459
            dispcmd = str([c] + args)
460
            # remember shell=False, so use git.cmd on windows, not just git
461
            p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
462
                                 stderr=(subprocess.PIPE if hide_stderr
463
                                         else None))
464
            break
465
        except EnvironmentError:
466
            e = sys.exc_info()[1]
467
            if e.errno == errno.ENOENT:
468
                continue
469
            if verbose:
470
                print("unable to run %s" % dispcmd)
471
                print(e)
472
            return None
473
    else:
474
        if verbose:
475
            print("unable to find command, tried %s" % (commands,))
476
        return None
477
    stdout = p.communicate()[0].strip()
478
    if sys.version_info[0] >= 3:
479
        stdout = stdout.decode()
480
    if p.returncode != 0:
481
        if verbose:
482
            print("unable to run %s (error)" % dispcmd)
483
        return None
484
    return stdout
485
LONG_VERSION_PY['git'] = '''
486
# This file helps to compute a version number in source trees obtained from
487
# git-archive tarball (such as those provided by githubs download-from-tag
488
# feature). Distribution tarballs (built by setup.py sdist) and build
489
# directories (produced by setup.py build) will contain a much shorter file
490
# that just contains the computed version number.
491
492
# This file is released into the public domain. Generated by
493
# versioneer-0.16 (https://github.com/warner/python-versioneer)
494
495
"""Git implementation of _version.py."""
496
497
import errno
498
import os
499
import re
500
import subprocess
501
import sys
502
503
504
def get_keywords():
505
    """Get the keywords needed to look up the version information."""
506
    # these strings will be replaced by git during git-archive.
507
    # setup.py/versioneer.py will grep for the variable names, so they must
508
    # each be defined on a line of their own. _version.py will just call
509
    # get_keywords().
510
    git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
511
    git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
512
    keywords = {"refnames": git_refnames, "full": git_full}
513
    return keywords
514
515
516
class VersioneerConfig:
517
    """Container for Versioneer configuration parameters."""
518
519
520
def get_config():
521
    """Create, populate and return the VersioneerConfig() object."""
522
    # these strings are filled in when 'setup.py versioneer' creates
523
    # _version.py
524
    cfg = VersioneerConfig()
525
    cfg.VCS = "git"
526
    cfg.style = "%(STYLE)s"
527
    cfg.tag_prefix = "%(TAG_PREFIX)s"
528
    cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s"
529
    cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s"
530
    cfg.verbose = False
531
    return cfg
532
533
534
class NotThisMethod(Exception):
535
    """Exception raised if a method is not valid for the current scenario."""
536
537
538
LONG_VERSION_PY = {}
539
HANDLERS = {}
540
541
542
def register_vcs_handler(vcs, method):  # decorator
543
    """Decorator to mark a method as the handler for a particular VCS."""
544
    def decorate(f):
545
        """Store f in HANDLERS[vcs][method]."""
546
        if vcs not in HANDLERS:
547
            HANDLERS[vcs] = {}
548
        HANDLERS[vcs][method] = f
549
        return f
550
    return decorate
551
552
553
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
554
    """Call the given command(s)."""
555
    assert isinstance(commands, list)
556
    p = None
557
    for c in commands:
558
        try:
559
            dispcmd = str([c] + args)
560
            # remember shell=False, so use git.cmd on windows, not just git
561
            p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
562
                                 stderr=(subprocess.PIPE if hide_stderr
563
                                         else None))
564
            break
565
        except EnvironmentError:
566
            e = sys.exc_info()[1]
567
            if e.errno == errno.ENOENT:
568
                continue
569
            if verbose:
570
                print("unable to run %%s" %% dispcmd)
571
                print(e)
572
            return None
573
    else:
574
        if verbose:
575
            print("unable to find command, tried %%s" %% (commands,))
576
        return None
577
    stdout = p.communicate()[0].strip()
578
    if sys.version_info[0] >= 3:
579
        stdout = stdout.decode()
580
    if p.returncode != 0:
581
        if verbose:
582
            print("unable to run %%s (error)" %% dispcmd)
583
        return None
584
    return stdout
585
586
587
def versions_from_parentdir(parentdir_prefix, root, verbose):
588
    """Try to determine the version from the parent directory name.
589
590
    Source tarballs conventionally unpack into a directory that includes
591
    both the project name and a version string.
592
    """
593
    dirname = os.path.basename(root)
594
    if not dirname.startswith(parentdir_prefix):
595
        if verbose:
596
            print("guessing rootdir is '%%s', but '%%s' doesn't start with "
597
                  "prefix '%%s'" %% (root, dirname, parentdir_prefix))
598
        raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
599
    return {"version": dirname[len(parentdir_prefix):],
600
            "full-revisionid": None,
601
            "dirty": False, "error": None}
602
603
604
@register_vcs_handler("git", "get_keywords")
605
def git_get_keywords(versionfile_abs):
606
    """Extract version information from the given file."""
607
    # the code embedded in _version.py can just fetch the value of these
608
    # keywords. When used from setup.py, we don't want to import _version.py,
609
    # so we do it with a regexp instead. This function is not used from
610
    # _version.py.
611
    keywords = {}
612
    try:
613
        f = open(versionfile_abs, "r")
614
        for line in f.readlines():
615
            if line.strip().startswith("git_refnames ="):
616
                mo = re.search(r'=\s*"(.*)"', line)
617
                if mo:
618
                    keywords["refnames"] = mo.group(1)
619
            if line.strip().startswith("git_full ="):
620
                mo = re.search(r'=\s*"(.*)"', line)
621
                if mo:
622
                    keywords["full"] = mo.group(1)
623
        f.close()
624
    except EnvironmentError:
625
        pass
626
    return keywords
627
628
629
@register_vcs_handler("git", "keywords")
630
def git_versions_from_keywords(keywords, tag_prefix, verbose):
631
    """Get version information from git keywords."""
632
    if not keywords:
633
        raise NotThisMethod("no keywords at all, weird")
634
    refnames = keywords["refnames"].strip()
635
    if refnames.startswith("$Format"):
636
        if verbose:
637
            print("keywords are unexpanded, not using")
638
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
639
    refs = set([r.strip() for r in refnames.strip("()").split(",")])
640
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
641
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
642
    TAG = "tag: "
643
    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
644
    if not tags:
645
        # Either we're using git < 1.8.3, or there really are no tags. We use
646
        # a heuristic: assume all version tags have a digit. The old git %%d
647
        # expansion behaves like git log --decorate=short and strips out the
648
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
649
        # between branches and tags. By ignoring refnames without digits, we
650
        # filter out many common branch names like "release" and
651
        # "stabilization", as well as "HEAD" and "master".
652
        tags = set([r for r in refs if re.search(r'\d', r)])
653
        if verbose:
654
            print("discarding '%%s', no digits" %% ",".join(refs-tags))
655
    if verbose:
656
        print("likely tags: %%s" %% ",".join(sorted(tags)))
657
    for ref in sorted(tags):
658
        # sorting will prefer e.g. "2.0" over "2.0rc1"
659
        if ref.startswith(tag_prefix):
660
            r = ref[len(tag_prefix):]
661
            if verbose:
662
                print("picking %%s" %% r)
663
            return {"version": r,
664
                    "full-revisionid": keywords["full"].strip(),
665
                    "dirty": False, "error": None
666
                    }
667
    # no suitable tags, so version is "0+unknown", but full hex is still there
668
    if verbose:
669
        print("no suitable tags, using unknown + full revision id")
670
    return {"version": "0+unknown",
671
            "full-revisionid": keywords["full"].strip(),
672
            "dirty": False, "error": "no suitable tags"}
673
674
675
@register_vcs_handler("git", "pieces_from_vcs")
676
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
677
    """Get version from 'git describe' in the root of the source tree.
678
679
    This only gets called if the git-archive 'subst' keywords were *not*
680
    expanded, and _version.py hasn't already been rewritten with a short
681
    version string, meaning we're inside a checked out source tree.
682
    """
683
    if not os.path.exists(os.path.join(root, ".git")):
684
        if verbose:
685
            print("no .git in %%s" %% root)
686
        raise NotThisMethod("no .git directory")
687
688
    GITS = ["git"]
689
    if sys.platform == "win32":
690
        GITS = ["git.cmd", "git.exe"]
691
    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
692
    # if there isn't one, this yields HEX[-dirty] (no NUM)
693
    describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
694
                                      "--always", "--long",
695
                                      "--match", "%%s*" %% tag_prefix],
696
                               cwd=root)
697
    # --long was added in git-1.5.5
698
    if describe_out is None:
699
        raise NotThisMethod("'git describe' failed")
700
    describe_out = describe_out.strip()
701
    full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
702
    if full_out is None:
703
        raise NotThisMethod("'git rev-parse' failed")
704
    full_out = full_out.strip()
705
706
    pieces = {}
707
    pieces["long"] = full_out
708
    pieces["short"] = full_out[:7]  # maybe improved later
709
    pieces["error"] = None
710
711
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
712
    # TAG might have hyphens.
713
    git_describe = describe_out
714
715
    # look for -dirty suffix
716
    dirty = git_describe.endswith("-dirty")
717
    pieces["dirty"] = dirty
718
    if dirty:
719
        git_describe = git_describe[:git_describe.rindex("-dirty")]
720
721
    # now we have TAG-NUM-gHEX or HEX
722
723
    if "-" in git_describe:
724
        # TAG-NUM-gHEX
725
        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
726
        if not mo:
727
            # unparseable. Maybe git-describe is misbehaving?
728
            pieces["error"] = ("unable to parse git-describe output: '%%s'"
729
                               %% describe_out)
730
            return pieces
731
732
        # tag
733
        full_tag = mo.group(1)
734
        if not full_tag.startswith(tag_prefix):
735
            if verbose:
736
                fmt = "tag '%%s' doesn't start with prefix '%%s'"
737
                print(fmt %% (full_tag, tag_prefix))
738
            pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'"
739
                               %% (full_tag, tag_prefix))
740
            return pieces
741
        pieces["closest-tag"] = full_tag[len(tag_prefix):]
742
743
        # distance: number of commits since tag
744
        pieces["distance"] = int(mo.group(2))
745
746
        # commit: short hex revision ID
747
        pieces["short"] = mo.group(3)
748
749
    else:
750
        # HEX: no tags
751
        pieces["closest-tag"] = None
752
        count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
753
                                cwd=root)
754
        pieces["distance"] = int(count_out)  # total number of commits
755
756
    return pieces
757
758
759
def plus_or_dot(pieces):
760
    """Return a + if we don't already have one, else return a ."""
761
    if "+" in pieces.get("closest-tag", ""):
762
        return "."
763
    return "+"
764
765
766
def render_pep440(pieces):
767
    """Build up version string, with post-release "local version identifier".
768
769
    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
770
    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
771
772
    Exceptions:
773
    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
774
    """
775
    if pieces["closest-tag"]:
776
        rendered = pieces["closest-tag"]
777
        if pieces["distance"] or pieces["dirty"]:
778
            rendered += plus_or_dot(pieces)
779
            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
780
            if pieces["dirty"]:
781
                rendered += ".dirty"
782
    else:
783
        # exception #1
784
        rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"],
785
                                          pieces["short"])
786
        if pieces["dirty"]:
787
            rendered += ".dirty"
788
    return rendered
789
790
791
def render_pep440_pre(pieces):
792
    """TAG[.post.devDISTANCE] -- No -dirty.
793
794
    Exceptions:
795
    1: no tags. 0.post.devDISTANCE
796
    """
797
    if pieces["closest-tag"]:
798
        rendered = pieces["closest-tag"]
799
        if pieces["distance"]:
800
            rendered += ".post.dev%%d" %% pieces["distance"]
801
    else:
802
        # exception #1
803
        rendered = "0.post.dev%%d" %% pieces["distance"]
804
    return rendered
805
806
807
def render_pep440_post(pieces):
808
    """TAG[.postDISTANCE[.dev0]+gHEX] .
809
810
    The ".dev0" means dirty. Note that .dev0 sorts backwards
811
    (a dirty tree will appear "older" than the corresponding clean one),
812
    but you shouldn't be releasing software with -dirty anyways.
813
814
    Exceptions:
815
    1: no tags. 0.postDISTANCE[.dev0]
816
    """
817
    if pieces["closest-tag"]:
818
        rendered = pieces["closest-tag"]
819
        if pieces["distance"] or pieces["dirty"]:
820
            rendered += ".post%%d" %% pieces["distance"]
821
            if pieces["dirty"]:
822
                rendered += ".dev0"
823
            rendered += plus_or_dot(pieces)
824
            rendered += "g%%s" %% pieces["short"]
825
    else:
826
        # exception #1
827
        rendered = "0.post%%d" %% pieces["distance"]
828
        if pieces["dirty"]:
829
            rendered += ".dev0"
830
        rendered += "+g%%s" %% pieces["short"]
831
    return rendered
832
833
834
def render_pep440_old(pieces):
835
    """TAG[.postDISTANCE[.dev0]] .
836
837
    The ".dev0" means dirty.
838
839
    Eexceptions:
840
    1: no tags. 0.postDISTANCE[.dev0]
841
    """
842
    if pieces["closest-tag"]:
843
        rendered = pieces["closest-tag"]
844
        if pieces["distance"] or pieces["dirty"]:
845
            rendered += ".post%%d" %% pieces["distance"]
846
            if pieces["dirty"]:
847
                rendered += ".dev0"
848
    else:
849
        # exception #1
850
        rendered = "0.post%%d" %% pieces["distance"]
851
        if pieces["dirty"]:
852
            rendered += ".dev0"
853
    return rendered
854
855
856
def render_git_describe(pieces):
857
    """TAG[-DISTANCE-gHEX][-dirty].
858
859
    Like 'git describe --tags --dirty --always'.
860
861
    Exceptions:
862
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
863
    """
864
    if pieces["closest-tag"]:
865
        rendered = pieces["closest-tag"]
866
        if pieces["distance"]:
867
            rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
868
    else:
869
        # exception #1
870
        rendered = pieces["short"]
871
    if pieces["dirty"]:
872
        rendered += "-dirty"
873
    return rendered
874
875
876
def render_git_describe_long(pieces):
877
    """TAG-DISTANCE-gHEX[-dirty].
878
879
    Like 'git describe --tags --dirty --always -long'.
880
    The distance/hash is unconditional.
881
882
    Exceptions:
883
    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
884
    """
885
    if pieces["closest-tag"]:
886
        rendered = pieces["closest-tag"]
887
        rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
888
    else:
889
        # exception #1
890
        rendered = pieces["short"]
891
    if pieces["dirty"]:
892
        rendered += "-dirty"
893
    return rendered
894
895
896
def render(pieces, style):
897
    """Render the given version pieces into the requested style."""
898
    if pieces["error"]:
899
        return {"version": "unknown",
900
                "full-revisionid": pieces.get("long"),
901
                "dirty": None,
902
                "error": pieces["error"]}
903
904
    if not style or style == "default":
905
        style = "pep440"  # the default
906
907
    if style == "pep440":
908
        rendered = render_pep440(pieces)
909
    elif style == "pep440-pre":
910
        rendered = render_pep440_pre(pieces)
911
    elif style == "pep440-post":
912
        rendered = render_pep440_post(pieces)
913
    elif style == "pep440-old":
914
        rendered = render_pep440_old(pieces)
915
    elif style == "git-describe":
916
        rendered = render_git_describe(pieces)
917
    elif style == "git-describe-long":
918
        rendered = render_git_describe_long(pieces)
919
    else:
920
        raise ValueError("unknown style '%%s'" %% style)
921
922
    return {"version": rendered, "full-revisionid": pieces["long"],
923
            "dirty": pieces["dirty"], "error": None}
924
925
926
def get_versions():
927
    """Get version information or return default if unable to do so."""
928
    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
929
    # __file__, we can work backwards from there to the root. Some
930
    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
931
    # case we can only use expanded keywords.
932
933
    cfg = get_config()
934
    verbose = cfg.verbose
935
936
    try:
937
        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
938
                                          verbose)
939
    except NotThisMethod:
940
        pass
941
942
    try:
943
        root = os.path.realpath(__file__)
944
        # versionfile_source is the relative path from the top of the source
945
        # tree (where the .git directory might live) to this file. Invert
946
        # this to find the root from __file__.
947
        for i in cfg.versionfile_source.split('/'):
948
            root = os.path.dirname(root)
949
    except NameError:
950
        return {"version": "0+unknown", "full-revisionid": None,
951
                "dirty": None,
952
                "error": "unable to find root of source tree"}
953
954
    try:
955
        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
956
        return render(pieces, cfg.style)
957
    except NotThisMethod:
958
        pass
959
960
    try:
961
        if cfg.parentdir_prefix:
962
            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
963
    except NotThisMethod:
964
        pass
965
966
    return {"version": "0+unknown", "full-revisionid": None,
967
            "dirty": None,
968
            "error": "unable to compute version"}
969
'''
970
971
972 View Code Duplication
@register_vcs_handler("git", "get_keywords")
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
973
def git_get_keywords(versionfile_abs):
974
    """Extract version information from the given file."""
975
    # the code embedded in _version.py can just fetch the value of these
976
    # keywords. When used from setup.py, we don't want to import _version.py,
977
    # so we do it with a regexp instead. This function is not used from
978
    # _version.py.
979
    keywords = {}
980
    try:
981
        f = open(versionfile_abs, "r")
982
        for line in f.readlines():
983
            if line.strip().startswith("git_refnames ="):
984
                mo = re.search(r'=\s*"(.*)"', line)
985
                if mo:
986
                    keywords["refnames"] = mo.group(1)
987
            if line.strip().startswith("git_full ="):
988
                mo = re.search(r'=\s*"(.*)"', line)
989
                if mo:
990
                    keywords["full"] = mo.group(1)
991
        f.close()
992
    except EnvironmentError:
993
        pass
994
    return keywords
995
996
997 View Code Duplication
@register_vcs_handler("git", "keywords")
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
998
def git_versions_from_keywords(keywords, tag_prefix, verbose):
999
    """Get version information from git keywords."""
1000
    if not keywords:
1001
        raise NotThisMethod("no keywords at all, weird")
1002
    refnames = keywords["refnames"].strip()
1003
    if refnames.startswith("$Format"):
1004
        if verbose:
1005
            print("keywords are unexpanded, not using")
1006
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
1007
    refs = set([r.strip() for r in refnames.strip("()").split(",")])
1008
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
1009
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
1010
    TAG = "tag: "
1011
    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
1012
    if not tags:
1013
        # Either we're using git < 1.8.3, or there really are no tags. We use
1014
        # a heuristic: assume all version tags have a digit. The old git %d
1015
        # expansion behaves like git log --decorate=short and strips out the
1016
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
1017
        # between branches and tags. By ignoring refnames without digits, we
1018
        # filter out many common branch names like "release" and
1019
        # "stabilization", as well as "HEAD" and "master".
1020
        tags = set([r for r in refs if re.search(r'\d', r)])
1021
        if verbose:
1022
            print("discarding '%s', no digits" % ",".join(refs-tags))
1023
    if verbose:
1024
        print("likely tags: %s" % ",".join(sorted(tags)))
1025
    for ref in sorted(tags):
1026
        # sorting will prefer e.g. "2.0" over "2.0rc1"
1027
        if ref.startswith(tag_prefix):
1028
            r = ref[len(tag_prefix):]
1029
            if verbose:
1030
                print("picking %s" % r)
1031
            return {"version": r,
1032
                    "full-revisionid": keywords["full"].strip(),
1033
                    "dirty": False, "error": None
1034
                    }
1035
    # no suitable tags, so version is "0+unknown", but full hex is still there
1036
    if verbose:
1037
        print("no suitable tags, using unknown + full revision id")
1038
    return {"version": "0+unknown",
1039
            "full-revisionid": keywords["full"].strip(),
1040
            "dirty": False, "error": "no suitable tags"}
1041
1042
1043 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...
1044
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
1045
    """Get version from 'git describe' in the root of the source tree.
1046
1047
    This only gets called if the git-archive 'subst' keywords were *not*
1048
    expanded, and _version.py hasn't already been rewritten with a short
1049
    version string, meaning we're inside a checked out source tree.
1050
    """
1051
    if not os.path.exists(os.path.join(root, ".git")):
1052
        if verbose:
1053
            print("no .git in %s" % root)
1054
        raise NotThisMethod("no .git directory")
1055
1056
    GITS = ["git"]
1057
    if sys.platform == "win32":
1058
        GITS = ["git.cmd", "git.exe"]
1059
    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
1060
    # if there isn't one, this yields HEX[-dirty] (no NUM)
1061
    describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
1062
                                      "--always", "--long",
1063
                                      "--match", "%s*" % tag_prefix],
1064
                               cwd=root)
1065
    # --long was added in git-1.5.5
1066
    if describe_out is None:
1067
        raise NotThisMethod("'git describe' failed")
1068
    describe_out = describe_out.strip()
1069
    full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
1070
    if full_out is None:
1071
        raise NotThisMethod("'git rev-parse' failed")
1072
    full_out = full_out.strip()
1073
1074
    pieces = {}
1075
    pieces["long"] = full_out
1076
    pieces["short"] = full_out[:7]  # maybe improved later
1077
    pieces["error"] = None
1078
1079
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
1080
    # TAG might have hyphens.
1081
    git_describe = describe_out
1082
1083
    # look for -dirty suffix
1084
    dirty = git_describe.endswith("-dirty")
1085
    pieces["dirty"] = dirty
1086
    if dirty:
1087
        git_describe = git_describe[:git_describe.rindex("-dirty")]
1088
1089
    # now we have TAG-NUM-gHEX or HEX
1090
1091
    if "-" in git_describe:
1092
        # TAG-NUM-gHEX
1093
        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
1094
        if not mo:
1095
            # unparseable. Maybe git-describe is misbehaving?
1096
            pieces["error"] = ("unable to parse git-describe output: '%s'"
1097
                               % describe_out)
1098
            return pieces
1099
1100
        # tag
1101
        full_tag = mo.group(1)
1102
        if not full_tag.startswith(tag_prefix):
1103
            if verbose:
1104
                fmt = "tag '%s' doesn't start with prefix '%s'"
1105
                print(fmt % (full_tag, tag_prefix))
1106
            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
1107
                               % (full_tag, tag_prefix))
1108
            return pieces
1109
        pieces["closest-tag"] = full_tag[len(tag_prefix):]
1110
1111
        # distance: number of commits since tag
1112
        pieces["distance"] = int(mo.group(2))
1113
1114
        # commit: short hex revision ID
1115
        pieces["short"] = mo.group(3)
1116
1117
    else:
1118
        # HEX: no tags
1119
        pieces["closest-tag"] = None
1120
        count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
1121
                                cwd=root)
1122
        pieces["distance"] = int(count_out)  # total number of commits
1123
1124
    return pieces
1125
1126
1127
def do_vcs_install(manifest_in, versionfile_source, ipy):
1128
    """Git-specific installation logic for Versioneer.
1129
1130
    For Git, this means creating/changing .gitattributes to mark _version.py
1131
    for export-time keyword substitution.
1132
    """
1133
    GITS = ["git"]
1134
    if sys.platform == "win32":
1135
        GITS = ["git.cmd", "git.exe"]
1136
    files = [manifest_in, versionfile_source]
1137
    if ipy:
1138
        files.append(ipy)
1139
    try:
1140
        me = __file__
1141
        if me.endswith(".pyc") or me.endswith(".pyo"):
1142
            me = os.path.splitext(me)[0] + ".py"
1143
        versioneer_file = os.path.relpath(me)
1144
    except NameError:
1145
        versioneer_file = "versioneer.py"
1146
    files.append(versioneer_file)
1147
    present = False
1148
    try:
1149
        f = open(".gitattributes", "r")
1150
        for line in f.readlines():
1151
            if line.strip().startswith(versionfile_source):
1152
                if "export-subst" in line.strip().split()[1:]:
1153
                    present = True
1154
        f.close()
1155
    except EnvironmentError:
1156
        pass
1157
    if not present:
1158
        f = open(".gitattributes", "a+")
1159
        f.write("%s export-subst\n" % versionfile_source)
1160
        f.close()
1161
        files.append(".gitattributes")
1162
    run_command(GITS, ["add", "--"] + files)
1163
1164
1165
def versions_from_parentdir(parentdir_prefix, root, verbose):
1166
    """Try to determine the version from the parent directory name.
1167
1168
    Source tarballs conventionally unpack into a directory that includes
1169
    both the project name and a version string.
1170
    """
1171
    dirname = os.path.basename(root)
1172
    if not dirname.startswith(parentdir_prefix):
1173
        if verbose:
1174
            print("guessing rootdir is '%s', but '%s' doesn't start with "
1175
                  "prefix '%s'" % (root, dirname, parentdir_prefix))
1176
        raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
1177
    return {"version": dirname[len(parentdir_prefix):],
1178
            "full-revisionid": None,
1179
            "dirty": False, "error": None}
1180
1181
SHORT_VERSION_PY = """
1182
# This file was generated by 'versioneer.py' (0.16) from
1183
# revision-control system data, or from the parent directory name of an
1184
# unpacked source archive. Distribution tarballs contain a pre-generated copy
1185
# of this file.
1186
1187
import json
1188
import sys
1189
1190
version_json = '''
1191
%s
1192
'''  # END VERSION_JSON
1193
1194
1195
def get_versions():
1196
    return json.loads(version_json)
1197
"""
1198
1199
1200
def versions_from_file(filename):
1201
    """Try to determine the version from _version.py if present."""
1202
    try:
1203
        with open(filename) as f:
1204
            contents = f.read()
1205
    except EnvironmentError:
1206
        raise NotThisMethod("unable to read _version.py")
1207
    mo = re.search(r"version_json = '''\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 View Code Duplication
def render_pep440_post(pieces):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
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
1370
    if not style or style == "default":
1371
        style = "pep440"  # the default
1372
1373
    if style == "pep440":
1374
        rendered = render_pep440(pieces)
1375
    elif style == "pep440-pre":
1376
        rendered = render_pep440_pre(pieces)
1377
    elif style == "pep440-post":
1378
        rendered = render_pep440_post(pieces)
1379
    elif style == "pep440-old":
1380
        rendered = render_pep440_old(pieces)
1381
    elif style == "git-describe":
1382
        rendered = render_git_describe(pieces)
1383
    elif style == "git-describe-long":
1384
        rendered = render_git_describe_long(pieces)
1385
    else:
1386
        raise ValueError("unknown style '%s'" % style)
1387
1388
    return {"version": rendered, "full-revisionid": pieces["long"],
1389
            "dirty": pieces["dirty"], "error": None}
1390
1391
1392
class VersioneerBadRootError(Exception):
1393
    """The project root directory is unknown or missing key files."""
1394
1395
1396
def get_versions(verbose=False):
1397
    """Get the project version from whatever source is available.
1398
1399
    Returns dict with two keys: 'version' and 'full'.
1400
    """
1401
    if "versioneer" in sys.modules:
1402
        # see the discussion in cmdclass.py:get_cmdclass()
1403
        del sys.modules["versioneer"]
1404
1405
    root = get_root()
1406
    cfg = get_config_from_root(root)
1407
1408
    assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
1409
    handlers = HANDLERS.get(cfg.VCS)
1410
    assert handlers, "unrecognized VCS '%s'" % cfg.VCS
1411
    verbose = verbose or cfg.verbose
1412
    assert cfg.versionfile_source is not None, \
1413
        "please set versioneer.versionfile_source"
1414
    assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
1415
1416
    versionfile_abs = os.path.join(root, cfg.versionfile_source)
1417
1418
    # extract version from first of: _version.py, VCS command (e.g. 'git
1419
    # describe'), parentdir. This is meant to work for developers using a
1420
    # source checkout, for users of a tarball created by 'setup.py sdist',
1421
    # and for users of a tarball/zipball created by 'git archive' or github's
1422
    # download-from-tag feature or the equivalent in other VCSes.
1423
1424
    get_keywords_f = handlers.get("get_keywords")
1425
    from_keywords_f = handlers.get("keywords")
1426
    if get_keywords_f and from_keywords_f:
1427
        try:
1428
            keywords = get_keywords_f(versionfile_abs)
1429
            ver = from_keywords_f(keywords, cfg.tag_prefix, verbose)
1430
            if verbose:
1431
                print("got version from expanded keyword %s" % ver)
1432
            return ver
1433
        except NotThisMethod:
1434
            pass
1435
1436
    try:
1437
        ver = versions_from_file(versionfile_abs)
1438
        if verbose:
1439
            print("got version from file %s %s" % (versionfile_abs, ver))
1440
        return ver
1441
    except NotThisMethod:
1442
        pass
1443
1444
    from_vcs_f = handlers.get("pieces_from_vcs")
1445
    if from_vcs_f:
1446
        try:
1447
            pieces = from_vcs_f(cfg.tag_prefix, root, verbose)
1448
            ver = render(pieces, cfg.style)
1449
            if verbose:
1450
                print("got version from VCS %s" % ver)
1451
            return ver
1452
        except NotThisMethod:
1453
            pass
1454
1455
    try:
1456
        if cfg.parentdir_prefix:
1457
            ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
1458
            if verbose:
1459
                print("got version from parentdir %s" % ver)
1460
            return ver
1461
    except NotThisMethod:
1462
        pass
1463
1464
    if verbose:
1465
        print("unable to compute version")
1466
1467
    return {"version": "0+unknown", "full-revisionid": None,
1468
            "dirty": None, "error": "unable to compute version"}
1469
1470
1471
def get_version():
1472
    """Get the short version string for this project."""
1473
    return get_versions()["version"]
1474
1475
1476
def get_cmdclass():
1477
    """Get the custom setuptools/distutils subclasses used by Versioneer."""
1478
    if "versioneer" in sys.modules:
1479
        del sys.modules["versioneer"]
1480
        # this fixes the "python setup.py develop" case (also 'install' and
1481
        # 'easy_install .'), in which subdependencies of the main project are
1482
        # built (using setup.py bdist_egg) in the same python process. Assume
1483
        # a main project A and a dependency B, which use different versions
1484
        # of Versioneer. A's setup.py imports A's Versioneer, leaving it in
1485
        # sys.modules by the time B's setup.py is executed, causing B to run
1486
        # with the wrong versioneer. Setuptools wraps the sub-dep builds in a
1487
        # sandbox that restores sys.modules to it's pre-build state, so the
1488
        # parent is protected against the child's "import versioneer". By
1489
        # removing ourselves from sys.modules here, before the child build
1490
        # happens, we protect the child from the parent's versioneer too.
1491
        # Also see https://github.com/warner/python-versioneer/issues/52
1492
1493
    cmds = {}
1494
1495
    # we add "version" to both distutils and setuptools
1496
    from distutils.core import Command
1497
1498
    class cmd_version(Command):
1499
        description = "report generated version string"
1500
        user_options = []
1501
        boolean_options = []
1502
1503
        def initialize_options(self):
1504
            pass
1505
1506
        def finalize_options(self):
1507
            pass
1508
1509
        def run(self):
1510
            vers = get_versions(verbose=True)
1511
            print("Version: %s" % vers["version"])
1512
            print(" full-revisionid: %s" % vers.get("full-revisionid"))
1513
            print(" dirty: %s" % vers.get("dirty"))
1514
            if vers["error"]:
1515
                print(" error: %s" % vers["error"])
1516
    cmds["version"] = cmd_version
1517
1518
    # we override "build_py" in both distutils and setuptools
1519
    #
1520
    # most invocation pathways end up running build_py:
1521
    #  distutils/build -> build_py
1522
    #  distutils/install -> distutils/build ->..
1523
    #  setuptools/bdist_wheel -> distutils/install ->..
1524
    #  setuptools/bdist_egg -> distutils/install_lib -> build_py
1525
    #  setuptools/install -> bdist_egg ->..
1526
    #  setuptools/develop -> ?
1527
1528
    # we override different "build_py" commands for both environments
1529
    if "setuptools" in sys.modules:
1530
        from setuptools.command.build_py import build_py as _build_py
1531
    else:
1532
        from distutils.command.build_py import build_py as _build_py
1533
1534
    class cmd_build_py(_build_py):
1535
        def run(self):
1536
            root = get_root()
1537
            cfg = get_config_from_root(root)
1538
            versions = get_versions()
1539
            _build_py.run(self)
1540
            # now locate _version.py in the new build/ directory and replace
1541
            # it with an updated value
1542
            if cfg.versionfile_build:
1543
                target_versionfile = os.path.join(self.build_lib,
1544
                                                  cfg.versionfile_build)
1545
                print("UPDATING %s" % target_versionfile)
1546
                write_to_version_file(target_versionfile, versions)
1547
    cmds["build_py"] = cmd_build_py
1548
1549
    if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
1550
        from cx_Freeze.dist import build_exe as _build_exe
1551
1552
        class cmd_build_exe(_build_exe):
1553
            def run(self):
1554
                root = get_root()
1555
                cfg = get_config_from_root(root)
1556
                versions = get_versions()
1557
                target_versionfile = cfg.versionfile_source
1558
                print("UPDATING %s" % target_versionfile)
1559
                write_to_version_file(target_versionfile, versions)
1560
1561
                _build_exe.run(self)
1562
                os.unlink(target_versionfile)
1563
                with open(cfg.versionfile_source, "w") as f:
1564
                    LONG = LONG_VERSION_PY[cfg.VCS]
1565
                    f.write(LONG %
1566
                            {"DOLLAR": "$",
1567
                             "STYLE": cfg.style,
1568
                             "TAG_PREFIX": cfg.tag_prefix,
1569
                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
1570
                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
1571
                             })
1572
        cmds["build_exe"] = cmd_build_exe
1573
        del cmds["build_py"]
1574
1575
    # we override different "sdist" commands for both environments
1576
    if "setuptools" in sys.modules:
1577
        from setuptools.command.sdist import sdist as _sdist
1578
    else:
1579
        from distutils.command.sdist import sdist as _sdist
1580
1581
    class cmd_sdist(_sdist):
1582
        def run(self):
1583
            versions = get_versions()
1584
            self._versioneer_generated_versions = versions
1585
            # unless we update this, the command will keep using the old
1586
            # version
1587
            self.distribution.metadata.version = versions["version"]
1588
            return _sdist.run(self)
1589
1590
        def make_release_tree(self, base_dir, files):
1591
            root = get_root()
1592
            cfg = get_config_from_root(root)
1593
            _sdist.make_release_tree(self, base_dir, files)
1594
            # now locate _version.py in the new base_dir directory
1595
            # (remembering that it may be a hardlink) and replace it with an
1596
            # updated value
1597
            target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
1598
            print("UPDATING %s" % target_versionfile)
1599
            write_to_version_file(target_versionfile,
1600
                                  self._versioneer_generated_versions)
1601
    cmds["sdist"] = cmd_sdist
1602
1603
    return cmds
1604
1605
1606
CONFIG_ERROR = """
1607
setup.cfg is missing the necessary Versioneer configuration. You need
1608
a section like:
1609
1610
 [versioneer]
1611
 VCS = git
1612
 style = pep440
1613
 versionfile_source = src/myproject/_version.py
1614
 versionfile_build = myproject/_version.py
1615
 tag_prefix =
1616
 parentdir_prefix = myproject-
1617
1618
You will also need to edit your setup.py to use the results:
1619
1620
 import versioneer
1621
 setup(version=versioneer.get_version(),
1622
       cmdclass=versioneer.get_cmdclass(), ...)
1623
1624
Please read the docstring in ./versioneer.py for configuration instructions,
1625
edit setup.cfg, and re-run the installer or 'python versioneer.py setup'.
1626
"""
1627
1628
SAMPLE_CONFIG = """
1629
# See the docstring in versioneer.py for instructions. Note that you must
1630
# re-run 'versioneer.py setup' after changing this section, and commit the
1631
# resulting files.
1632
1633
[versioneer]
1634
#VCS = git
1635
#style = pep440
1636
#versionfile_source =
1637
#versionfile_build =
1638
#tag_prefix =
1639
#parentdir_prefix =
1640
1641
"""
1642
1643
INIT_PY_SNIPPET = """
1644
from ._version import get_versions
1645
__version__ = get_versions()['version']
1646
del get_versions
1647
"""
1648
1649
1650
def do_setup():
1651
    """Main VCS-independent setup function for installing Versioneer."""
1652
    root = get_root()
1653
    try:
1654
        cfg = get_config_from_root(root)
1655
    except (EnvironmentError, configparser.NoSectionError,
1656
            configparser.NoOptionError) as e:
1657
        if isinstance(e, (EnvironmentError, configparser.NoSectionError)):
1658
            print("Adding sample versioneer config to setup.cfg",
1659
                  file=sys.stderr)
1660
            with open(os.path.join(root, "setup.cfg"), "a") as f:
1661
                f.write(SAMPLE_CONFIG)
1662
        print(CONFIG_ERROR, file=sys.stderr)
1663
        return 1
1664
1665
    print(" creating %s" % cfg.versionfile_source)
1666
    with open(cfg.versionfile_source, "w") as f:
1667
        LONG = LONG_VERSION_PY[cfg.VCS]
1668
        f.write(LONG % {"DOLLAR": "$",
1669
                        "STYLE": cfg.style,
1670
                        "TAG_PREFIX": cfg.tag_prefix,
1671
                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
1672
                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
1673
                        })
1674
1675
    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
1676
                       "__init__.py")
1677
    if os.path.exists(ipy):
1678
        try:
1679
            with open(ipy, "r") as f:
1680
                old = f.read()
1681
        except EnvironmentError:
1682
            old = ""
1683
        if INIT_PY_SNIPPET not in old:
1684
            print(" appending to %s" % ipy)
1685
            with open(ipy, "a") as f:
1686
                f.write(INIT_PY_SNIPPET)
1687
        else:
1688
            print(" %s unmodified" % ipy)
1689
    else:
1690
        print(" %s doesn't exist, ok" % ipy)
1691
        ipy = None
1692
1693
    # Make sure both the top-level "versioneer.py" and versionfile_source
1694
    # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so
1695
    # they'll be copied into source distributions. Pip won't be able to
1696
    # install the package without this.
1697
    manifest_in = os.path.join(root, "MANIFEST.in")
1698
    simple_includes = set()
1699
    try:
1700
        with open(manifest_in, "r") as f:
1701
            for line in f:
1702
                if line.startswith("include "):
1703
                    for include in line.split()[1:]:
1704
                        simple_includes.add(include)
1705
    except EnvironmentError:
1706
        pass
1707
    # That doesn't cover everything MANIFEST.in can do
1708
    # (http://docs.python.org/2/distutils/sourcedist.html#commands), so
1709
    # it might give some false negatives. Appending redundant 'include'
1710
    # lines is safe, though.
1711
    if "versioneer.py" not in simple_includes:
1712
        print(" appending 'versioneer.py' to MANIFEST.in")
1713
        with open(manifest_in, "a") as f:
1714
            f.write("include versioneer.py\n")
1715
    else:
1716
        print(" 'versioneer.py' already in MANIFEST.in")
1717
    if cfg.versionfile_source not in simple_includes:
1718
        print(" appending versionfile_source ('%s') to MANIFEST.in" %
1719
              cfg.versionfile_source)
1720
        with open(manifest_in, "a") as f:
1721
            f.write("include %s\n" % cfg.versionfile_source)
1722
    else:
1723
        print(" versionfile_source already in MANIFEST.in")
1724
1725
    # Make VCS-specific changes. For git, this means creating/changing
1726
    # .gitattributes to mark _version.py for export-time keyword
1727
    # substitution.
1728
    do_vcs_install(manifest_in, cfg.versionfile_source, ipy)
1729
    return 0
1730
1731
1732
def scan_setup_py():
1733
    """Validate the contents of setup.py against Versioneer's expectations."""
1734
    found = set()
1735
    setters = False
1736
    errors = 0
1737
    with open("setup.py", "r") as f:
1738
        for line in f.readlines():
1739
            if "import versioneer" in line:
1740
                found.add("import")
1741
            if "versioneer.get_cmdclass()" in line:
1742
                found.add("cmdclass")
1743
            if "versioneer.get_version()" in line:
1744
                found.add("get_version")
1745
            if "versioneer.VCS" in line:
1746
                setters = True
1747
            if "versioneer.versionfile_source" in line:
1748
                setters = True
1749
    if len(found) != 3:
1750
        print("")
1751
        print("Your setup.py appears to be missing some important items")
1752
        print("(but I might be wrong). Please make sure it has something")
1753
        print("roughly like the following:")
1754
        print("")
1755
        print(" import versioneer")
1756
        print(" setup( version=versioneer.get_version(),")
1757
        print("        cmdclass=versioneer.get_cmdclass(),  ...)")
1758
        print("")
1759
        errors += 1
1760
    if setters:
1761
        print("You should remove lines like 'versioneer.VCS = ' and")
1762
        print("'versioneer.versionfile_source = ' . This configuration")
1763
        print("now lives in setup.cfg, and should be removed from setup.py")
1764
        print("")
1765
        errors += 1
1766
    return errors
1767
1768
if __name__ == "__main__":
1769
    cmd = sys.argv[1]
1770
    if cmd == "setup":
1771
        errors = do_setup()
1772
        errors += scan_setup_py()
1773
        if errors:
1774
            sys.exit(1)
1775