versioneer.py 85 KB

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