5 (like a rocketeer, but for versions)
7 * https://github.com/warner/python-versioneer
9 * License: Public Domain
12 This file helps distutils-based projects manage their version number by just
13 creating version-control tags.
15 For developers who work from a VCS-generated tree (e.g. 'git clone' etc),
16 each 'setup.py version', 'setup.py build', 'setup.py sdist' will compute a
17 version number by asking your version-control tool about the current
18 checkout. The version number will be written into a generated _version.py
19 file of your choosing, where it can be included by your __init__.py
21 For users who work from a VCS-generated tarball (e.g. 'git archive'), it will
22 compute a version number by looking at the name of the directory created when
23 te tarball is unpacked. This conventionally includes both the name of the
24 project and a version number.
26 For users who work from a tarball built by 'setup.py sdist', it will get a
27 version number from a previously-generated _version.py file.
29 As a result, loading code directly from the source tree will not result in a
30 real version. If you want real versions from VCS trees (where you frequently
31 update from the upstream repository, or do new development), you will need to
32 do a 'setup.py version' after each update, and load code from the build/
35 You need to provide this code with a few configuration values:
38 A project-relative pathname into which the generated version strings
39 should be written. This is usually a _version.py next to your project's
40 main __init__.py file. If your project uses src/myproject/__init__.py,
41 this should be 'src/myproject/_version.py'. This file should be checked
42 in to your VCS as usual: the copy created below by 'setup.py
43 update_files' will include code that parses expanded VCS keywords in
44 generated tarballs. The 'build' and 'sdist' commands will replace it with
45 a copy that has just the calculated version string.
48 Like versionfile_source, but relative to the build directory instead of
49 the source directory. These will differ when your setup.py uses
50 'package_dir='. If you have package_dir={'myproject': 'src/myproject'},
51 then you will probably have versionfile_build='myproject/_version.py' and
52 versionfile_source='src/myproject/_version.py'.
54 tag_prefix: a string, like 'PROJECTNAME-', which appears at the start of all
55 VCS tags. If your tags look like 'myproject-1.2.0', then you
56 should use tag_prefix='myproject-'. If you use unprefixed tags
57 like '1.2.0', this should be an empty string.
59 parentdir_prefix: a string, frequently the same as tag_prefix, which
60 appears at the start of all unpacked tarball filenames. If
61 your tarball unpacks into 'myproject-1.2.0', this should
66 1: include this file in the top level of your project
67 2: make the following changes to the top of your setup.py:
69 versioneer.versionfile_source = 'src/myproject/_version.py'
70 versioneer.versionfile_build = 'myproject/_version.py'
71 versioneer.tag_prefix = '' # tags are like 1.2.0
72 versioneer.parentdir_prefix = 'myproject-' # dirname like 'myproject-1.2.0'
73 3: add the following arguments to the setup() call in your setup.py:
74 version=versioneer.get_version(),
75 cmdclass=versioneer.get_cmdclass(),
76 4: run 'setup.py update_files', which will create _version.py, and will
77 modify your __init__.py to define __version__ (by calling a function
79 5: modify your MANIFEST.in to include versioneer.py
80 6: add both versioneer.py and the generated _version.py to your VCS
84 from distutils.core import Command
85 from distutils.command.sdist import sdist as _sdist
86 from distutils.command.build import build as _build
88 versionfile_source = None
89 versionfile_build = None
91 parentdir_prefix = None
94 IN_LONG_VERSION_PY = False
98 IN_LONG_VERSION_PY = True
99 # This file helps to compute a version number in source trees obtained from
100 # git-archive tarball (such as those provided by githubs download-from-tag
101 # feature). Distribution tarballs (build by setup.py sdist) and build
102 # directories (produced by setup.py build) will contain a much shorter file
103 # that just contains the computed version number.
105 # This file is released into the public domain. Generated by
106 # versioneer-0.7+ (https://github.com/warner/python-versioneer)
108 # these strings will be replaced by git during git-archive
109 git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
110 git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
116 def run_command(args, cwd=None, verbose=False):
118 # remember shell=False, so use git.cmd on windows, not just git
119 p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
120 except EnvironmentError:
121 e = sys.exc_info()[1]
123 print("unable to run %%s" %% args[0])
126 stdout = p.communicate()[0].strip()
127 if sys.version >= '3':
128 stdout = stdout.decode()
129 if p.returncode != 0:
131 print("unable to run %%s (error)" %% args[0])
140 def get_expanded_variables(versionfile_source):
141 # the code embedded in _version.py can just fetch the value of these
142 # variables. When used from setup.py, we don't want to import
143 # _version.py, so we do it with a regexp instead. This function is not
144 # used from _version.py.
147 f = open(versionfile_source,"r")
148 for line in f.readlines():
149 if line.strip().startswith("git_refnames ="):
150 mo = re.search(r'=\s*"(.*)"', line)
152 variables["refnames"] = mo.group(1)
153 if line.strip().startswith("git_full ="):
154 mo = re.search(r'=\s*"(.*)"', line)
156 variables["full"] = mo.group(1)
158 except EnvironmentError:
162 def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
163 refnames = variables["refnames"].strip()
164 if refnames.startswith("$Format"):
166 print("variables are unexpanded, not using")
167 return {} # unexpanded, so not in an unpacked git-archive tarball
168 refs = set([r.strip() for r in refnames.strip("()").split(",")])
169 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
170 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
172 tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
174 # Either we're using git < 1.8.3, or there really are no tags. We use
175 # a heuristic: assume all version tags have a digit. The old git %%d
176 # expansion behaves like git log --decorate=short and strips out the
177 # refs/heads/ and refs/tags/ prefixes that would let us distinguish
178 # between branches and tags. By ignoring refnames without digits, we
179 # filter out many common branch names like "release" and
180 # "stabilization", as well as "HEAD" and "master".
181 tags = set([r for r in refs if re.search(r'\d', r)])
183 print("discarding '%%s', no digits" %% ",".join(refs-tags))
185 print("likely tags: %%s" %% ",".join(sorted(tags)))
186 for ref in sorted(tags):
187 # sorting will prefer e.g. "2.0" over "2.0rc1"
188 if ref.startswith(tag_prefix):
189 r = ref[len(tag_prefix):]
191 print("picking %%s" %% r)
192 return { "version": r,
193 "full": variables["full"].strip() }
194 # no suitable tags, so we use the full revision id
196 print("no suitable tags, using full revision id")
197 return { "version": variables["full"].strip(),
198 "full": variables["full"].strip() }
200 def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
201 # this runs 'git' from the root of the source tree. That either means
202 # someone ran a setup.py command (and this code is in versioneer.py, so
203 # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
204 # the source tree), or someone ran a project-specific entry point (and
205 # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
206 # containing directory is somewhere deeper in the source tree). This only
207 # gets called if the git-archive 'subst' variables were *not* expanded,
208 # and _version.py hasn't already been rewritten with a short version
209 # string, meaning we're inside a checked out source tree.
212 here = os.path.abspath(__file__)
214 # some py2exe/bbfreeze/non-CPython implementations don't do __file__
215 return {} # not always correct
217 # versionfile_source is the relative path from the top of the source tree
218 # (where the .git directory might live) to this file. Invert this to find
219 # the root from __file__.
221 if IN_LONG_VERSION_PY:
222 for i in range(len(versionfile_source.split("/"))):
223 root = os.path.dirname(root)
225 root = os.path.dirname(here)
226 if not os.path.exists(os.path.join(root, ".git")):
228 print("no .git in %%s" %% root)
232 if sys.platform == "win32":
234 stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
238 if not stdout.startswith(tag_prefix):
240 print("tag '%%s' doesn't start with prefix '%%s'" %% (stdout, tag_prefix))
242 tag = stdout[len(tag_prefix):]
243 stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
246 full = stdout.strip()
247 if tag.endswith("-dirty"):
249 return {"version": tag, "full": full}
252 def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
253 if IN_LONG_VERSION_PY:
254 # We're running from _version.py. If it's from a source tree
255 # (execute-in-place), we can work upwards to find the root of the
256 # tree, and then check the parent directory for a version string. If
257 # it's in an installed application, there's no hope.
259 here = os.path.abspath(__file__)
261 # py2exe/bbfreeze/non-CPython don't have __file__
262 return {} # without __file__, we have no hope
263 # versionfile_source is the relative path from the top of the source
264 # tree to _version.py. Invert this to find the root from __file__.
266 for i in range(len(versionfile_source.split("/"))):
267 root = os.path.dirname(root)
269 # we're running from versioneer.py, which means we're running from
270 # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
271 here = os.path.abspath(sys.argv[0])
272 root = os.path.dirname(here)
274 # Source tarballs conventionally unpack into a directory that includes
275 # both the project name and a version string.
276 dirname = os.path.basename(root)
277 if not dirname.startswith(parentdir_prefix):
279 print("guessing rootdir is '%%s', but '%%s' doesn't start with prefix '%%s'" %%
280 (root, dirname, parentdir_prefix))
282 return {"version": dirname[len(parentdir_prefix):], "full": ""}
284 tag_prefix = "%(TAG_PREFIX)s"
285 parentdir_prefix = "%(PARENTDIR_PREFIX)s"
286 versionfile_source = "%(VERSIONFILE_SOURCE)s"
288 def get_versions(default={"version": "unknown", "full": ""}, verbose=False):
289 variables = { "refnames": git_refnames, "full": git_full }
290 ver = versions_from_expanded_variables(variables, tag_prefix, verbose)
292 ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
294 ver = versions_from_parentdir(parentdir_prefix, versionfile_source,
306 def run_command(args, cwd=None, verbose=False):
308 # remember shell=False, so use git.cmd on windows, not just git
309 p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
310 except EnvironmentError:
311 e = sys.exc_info()[1]
313 print("unable to run %s" % args[0])
316 stdout = p.communicate()[0].strip()
317 if sys.version >= '3':
318 stdout = stdout.decode()
319 if p.returncode != 0:
321 print("unable to run %s (error)" % args[0])
330 def get_expanded_variables(versionfile_source):
331 # the code embedded in _version.py can just fetch the value of these
332 # variables. When used from setup.py, we don't want to import
333 # _version.py, so we do it with a regexp instead. This function is not
334 # used from _version.py.
337 f = open(versionfile_source,"r")
338 for line in f.readlines():
339 if line.strip().startswith("git_refnames ="):
340 mo = re.search(r'=\s*"(.*)"', line)
342 variables["refnames"] = mo.group(1)
343 if line.strip().startswith("git_full ="):
344 mo = re.search(r'=\s*"(.*)"', line)
346 variables["full"] = mo.group(1)
348 except EnvironmentError:
352 def versions_from_expanded_variables(variables, tag_prefix, verbose=False):
353 refnames = variables["refnames"].strip()
354 if refnames.startswith("$Format"):
356 print("variables are unexpanded, not using")
357 return {} # unexpanded, so not in an unpacked git-archive tarball
358 refs = set([r.strip() for r in refnames.strip("()").split(",")])
359 # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
360 # just "foo-1.0". If we see a "tag: " prefix, prefer those.
362 tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
364 # Either we're using git < 1.8.3, or there really are no tags. We use
365 # a heuristic: assume all version tags have a digit. The old git %d
366 # expansion behaves like git log --decorate=short and strips out the
367 # refs/heads/ and refs/tags/ prefixes that would let us distinguish
368 # between branches and tags. By ignoring refnames without digits, we
369 # filter out many common branch names like "release" and
370 # "stabilization", as well as "HEAD" and "master".
371 tags = set([r for r in refs if re.search(r'\d', r)])
373 print("discarding '%s', no digits" % ",".join(refs-tags))
375 print("likely tags: %s" % ",".join(sorted(tags)))
376 for ref in sorted(tags):
377 # sorting will prefer e.g. "2.0" over "2.0rc1"
378 if ref.startswith(tag_prefix):
379 r = ref[len(tag_prefix):]
381 print("picking %s" % r)
382 return { "version": r,
383 "full": variables["full"].strip() }
384 # no suitable tags, so we use the full revision id
386 print("no suitable tags, using full revision id")
387 return { "version": variables["full"].strip(),
388 "full": variables["full"].strip() }
390 def versions_from_vcs(tag_prefix, versionfile_source, verbose=False):
391 # this runs 'git' from the root of the source tree. That either means
392 # someone ran a setup.py command (and this code is in versioneer.py, so
393 # IN_LONG_VERSION_PY=False, thus the containing directory is the root of
394 # the source tree), or someone ran a project-specific entry point (and
395 # this code is in _version.py, so IN_LONG_VERSION_PY=True, thus the
396 # containing directory is somewhere deeper in the source tree). This only
397 # gets called if the git-archive 'subst' variables were *not* expanded,
398 # and _version.py hasn't already been rewritten with a short version
399 # string, meaning we're inside a checked out source tree.
402 here = os.path.abspath(__file__)
404 # some py2exe/bbfreeze/non-CPython implementations don't do __file__
405 return {} # not always correct
407 # versionfile_source is the relative path from the top of the source tree
408 # (where the .git directory might live) to this file. Invert this to find
409 # the root from __file__.
411 if IN_LONG_VERSION_PY:
412 for i in range(len(versionfile_source.split("/"))):
413 root = os.path.dirname(root)
415 root = os.path.dirname(here)
416 if not os.path.exists(os.path.join(root, ".git")):
418 print("no .git in %s" % root)
422 if sys.platform == "win32":
424 stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
428 if not stdout.startswith(tag_prefix):
430 print("tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix))
432 tag = stdout[len(tag_prefix):]
433 stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root)
436 full = stdout.strip()
437 if tag.endswith("-dirty"):
439 return {"version": tag, "full": full}
442 def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False):
443 if IN_LONG_VERSION_PY:
444 # We're running from _version.py. If it's from a source tree
445 # (execute-in-place), we can work upwards to find the root of the
446 # tree, and then check the parent directory for a version string. If
447 # it's in an installed application, there's no hope.
449 here = os.path.abspath(__file__)
451 # py2exe/bbfreeze/non-CPython don't have __file__
452 return {} # without __file__, we have no hope
453 # versionfile_source is the relative path from the top of the source
454 # tree to _version.py. Invert this to find the root from __file__.
456 for i in range(len(versionfile_source.split("/"))):
457 root = os.path.dirname(root)
459 # we're running from versioneer.py, which means we're running from
460 # the setup.py in a source tree. sys.argv[0] is setup.py in the root.
461 here = os.path.abspath(sys.argv[0])
462 root = os.path.dirname(here)
464 # Source tarballs conventionally unpack into a directory that includes
465 # both the project name and a version string.
466 dirname = os.path.basename(root)
467 if not dirname.startswith(parentdir_prefix):
469 print("guessing rootdir is '%s', but '%s' doesn't start with prefix '%s'" %
470 (root, dirname, parentdir_prefix))
472 return {"version": dirname[len(parentdir_prefix):], "full": ""}
476 def do_vcs_install(versionfile_source, ipy):
478 if sys.platform == "win32":
480 run_command([GIT, "add", "versioneer.py"])
481 run_command([GIT, "add", versionfile_source])
482 run_command([GIT, "add", ipy])
485 f = open(".gitattributes", "r")
486 for line in f.readlines():
487 if line.strip().startswith(versionfile_source):
488 if "export-subst" in line.strip().split()[1:]:
491 except EnvironmentError:
494 f = open(".gitattributes", "a+")
495 f.write("%s export-subst\n" % versionfile_source)
497 run_command([GIT, "add", ".gitattributes"])
500 SHORT_VERSION_PY = """
501 # This file was generated by 'versioneer.py' (0.7+) from
502 # revision-control system data, or from the parent directory name of an
503 # unpacked source archive. Distribution tarballs contain a pre-generated copy
506 version_version = '%(version)s'
507 version_full = '%(full)s'
508 def get_versions(default={}, verbose=False):
509 return {'version': version_version, 'full': version_full}
513 DEFAULT = {"version": "unknown", "full": "unknown"}
515 def versions_from_file(filename):
519 except EnvironmentError:
521 for line in f.readlines():
522 mo = re.match("version_version = '([^']+)'", line)
524 versions["version"] = mo.group(1)
525 mo = re.match("version_full = '([^']+)'", line)
527 versions["full"] = mo.group(1)
531 def write_to_version_file(filename, versions):
532 f = open(filename, "w")
533 f.write(SHORT_VERSION_PY % versions)
535 print("set %s to '%s'" % (filename, versions["version"]))
538 def get_best_versions(versionfile, tag_prefix, parentdir_prefix,
539 default=DEFAULT, verbose=False):
540 # returns dict with two keys: 'version' and 'full'
542 # extract version from first of _version.py, 'git describe', parentdir.
543 # This is meant to work for developers using a source checkout, for users
544 # of a tarball created by 'setup.py sdist', and for users of a
545 # tarball/zipball created by 'git archive' or github's download-from-tag
548 variables = get_expanded_variables(versionfile_source)
550 ver = versions_from_expanded_variables(variables, tag_prefix)
552 if verbose: print("got version from expanded variable %s" % ver)
555 ver = versions_from_file(versionfile)
557 if verbose: print("got version from file %s %s" % (versionfile, ver))
560 ver = versions_from_vcs(tag_prefix, versionfile_source, verbose)
562 if verbose: print("got version from git %s" % ver)
565 ver = versions_from_parentdir(parentdir_prefix, versionfile_source, verbose)
567 if verbose: print("got version from parentdir %s" % ver)
570 if verbose: print("got version from default %s" % ver)
573 def get_versions(default=DEFAULT, verbose=False):
574 assert versionfile_source is not None, "please set versioneer.versionfile_source"
575 assert tag_prefix is not None, "please set versioneer.tag_prefix"
576 assert parentdir_prefix is not None, "please set versioneer.parentdir_prefix"
577 return get_best_versions(versionfile_source, tag_prefix, parentdir_prefix,
578 default=default, verbose=verbose)
579 def get_version(verbose=False):
580 return get_versions(verbose=verbose)["version"]
582 class cmd_version(Command):
583 description = "report generated version string"
586 def initialize_options(self):
588 def finalize_options(self):
591 ver = get_version(verbose=True)
592 print("Version is currently: %s" % ver)
595 class cmd_build(_build):
597 versions = get_versions(verbose=True)
599 # now locate _version.py in the new build/ directory and replace it
600 # with an updated value
601 target_versionfile = os.path.join(self.build_lib, versionfile_build)
602 print("UPDATING %s" % target_versionfile)
603 os.unlink(target_versionfile)
604 f = open(target_versionfile, "w")
605 f.write(SHORT_VERSION_PY % versions)
608 class cmd_sdist(_sdist):
610 versions = get_versions(verbose=True)
611 self._versioneer_generated_versions = versions
612 # unless we update this, the command will keep using the old version
613 self.distribution.metadata.version = versions["version"]
614 return _sdist.run(self)
616 def make_release_tree(self, base_dir, files):
617 _sdist.make_release_tree(self, base_dir, files)
618 # now locate _version.py in the new base_dir directory (remembering
619 # that it may be a hardlink) and replace it with an updated value
620 target_versionfile = os.path.join(base_dir, versionfile_source)
621 print("UPDATING %s" % target_versionfile)
622 os.unlink(target_versionfile)
623 f = open(target_versionfile, "w")
624 f.write(SHORT_VERSION_PY % self._versioneer_generated_versions)
627 INIT_PY_SNIPPET = """
628 from ._version import get_versions
629 __version__ = get_versions()['version']
633 class cmd_update_files(Command):
634 description = "modify __init__.py and create _version.py"
637 def initialize_options(self):
639 def finalize_options(self):
642 ipy = os.path.join(os.path.dirname(versionfile_source), "__init__.py")
643 print(" creating %s" % versionfile_source)
644 f = open(versionfile_source, "w")
645 f.write(LONG_VERSION_PY % {"DOLLAR": "$",
646 "TAG_PREFIX": tag_prefix,
647 "PARENTDIR_PREFIX": parentdir_prefix,
648 "VERSIONFILE_SOURCE": versionfile_source,
652 old = open(ipy, "r").read()
653 except EnvironmentError:
655 if INIT_PY_SNIPPET not in old:
656 print(" appending to %s" % ipy)
658 f.write(INIT_PY_SNIPPET)
661 print(" %s unmodified" % ipy)
662 do_vcs_install(versionfile_source, ipy)
665 return {'version': cmd_version,
666 'update_files': cmd_update_files,