--- /dev/null
+tidelift: pypi/setuptools-scm
--- /dev/null
+name: pre-commit
+
+on:
+ pull_request:
+ push:
+ branches: [master]
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - uses: actions/setup-python@v1
+ - name: set PY
+ run: echo "::set-env name=PY::$(python --version --version | sha256sum | cut -d' ' -f1)"
+ - uses: actions/cache@v1
+ with:
+ path: ~/.cache/pre-commit
+ key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
+ - uses: pre-commit/action@v1.0.0
--- /dev/null
+name: python tests+artifacts+release
+
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ tags:
+ - "v*"
+ release:
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ python_version: [ '2.7', '3.5', '3.6', '3.7', '3.8', 'pypy2', 'pypy3' ]
+ os: [windows-latest, ubuntu-latest] #, macos-latest]
+ exclude:
+ - os: windows-latest
+ python_version: "pypy2"
+ include:
+ - os: ubuntu-latest
+ python_version: '3.9-dev'
+
+ name: ${{ matrix.os }} - Python ${{ matrix.python_version }}
+ steps:
+ - uses: actions/checkout@v1
+ - name: Setup python
+ uses: actions/setup-python@v2
+ if: matrix.python_version != '3.9-dev'
+ with:
+ python-version: ${{ matrix.python_version }}
+ architecture: x64
+ - name: Set up Python ${{ matrix.python_version }} (deadsnakes)
+ uses: deadsnakes/action@v1.0.0
+ if: matrix.python_version == '3.9-dev'
+ with:
+ python-version: ${{ matrix.python_version }}
+ architecture: x64
+ - run: pip install -U setuptools
+ - run: pip install -e .[toml] pytest
+ - run: pytest
+
+ check_selfinstall:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python_version: [ '2.7', '3.5', '3.6', '3.7', '3.8', 'pypy2', 'pypy3' ]
+ name: check self install - Python ${{ matrix.python_version }}
+ steps:
+ - uses: actions/checkout@v1
+ - name: Setup python
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python_version }}
+ architecture: x64
+ # self install testing needs some clarity
+ # so its being executed without any other tools running
+ - run: pip install -U setuptools
+ - run: python setup.py egg_info
+ - run: python setup.py sdist
+ - run: easy_install dist/*
+ - run: python testing/check_self_install.py
+
+
+ eggs:
+ runs-on: ubuntu-latest
+
+ needs: [test]
+ name: Python ${{ matrix.python_version }} eggs
+ strategy:
+ matrix:
+ python_version: ['2.7', '3.5', '3.6', '3.7', '3.8', '3.9-dev']
+ steps:
+ - uses: actions/checkout@v1
+ - name: Setup python
+ uses: actions/setup-python@v2
+ if: matrix.python_version != '3.9-dev'
+ with:
+ python-version: ${{ matrix.python_version }}
+ architecture: x64
+ - name: Set up Python ${{ matrix.python_version }} (deadsnakes)
+ uses: deadsnakes/action@v1.0.0
+ if: matrix.python_version == '3.9-dev'
+ with:
+ python-version: ${{ matrix.python_version }}
+ architecture: x64
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install --upgrade wheel setuptools
+ - run: python setup.py egg_info
+ - name: Build package
+ run: python setup.py bdist_egg
+ - uses: actions/upload-artifact@v2
+ with:
+ name: dist
+ path: dist
+
+ dist:
+ runs-on: ubuntu-latest
+
+ needs: [test]
+ name: Python bdist/wheel
+ steps:
+ - uses: actions/checkout@v1
+ - uses: actions/setup-python@v1
+ with:
+ python-version: "3.8"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install --upgrade wheel setuptools
+ - run: python setup.py egg_info
+ - name: Build package
+ run: python setup.py bdist_wheel sdist
+ - uses: actions/upload-artifact@v2
+ with:
+ name: dist
+ path: dist
+
+
+ dist_check:
+ runs-on: ubuntu-latest
+ needs: [eggs, dist]
+ steps:
+ - uses: actions/setup-python@v2
+ with:
+ python-version: "3.8"
+ - name: Install dependencies
+ run: pip install twine
+ - uses: actions/download-artifact@v2
+ with:
+ name: dist
+ path: dist
+ - run: twine check dist/*
+
+ dist_upload:
+
+ runs-on: ubuntu-latest
+ if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+ needs: [dist_check]
+ steps:
+ - uses: actions/download-artifact@v2
+ with:
+ name: dist
+ path: dist
+ - name: Publish package to PyPI
+ uses: pypa/gh-action-pypi-publish@master
+ with:
+ user: __token__
+ password: ${{ secrets.pypi_token }}
--- /dev/null
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion
+
+*.iml
+
+## Directory-based project format:
+.idea/
+
+### Other editors
+.*.swp
+
+
+### Python template
+# Byte-compiled / optimized
+__pycache__/
+*.py[cod]
+*$py.class
+
+
+# Distribution / packaging
+.env/
+env/
+.venv/
+venv/
+build/
+dist/
+.eggs/
+lib/
+lib64/
+*.egg-info/
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+.pytest_cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Sphinx documentation
+docs/_build/
--- /dev/null
+v4.1.2
+=======
+
+* disallow git tags without dots by default again - #449
+
+v4.1.1
+=======
+
+* drop jaraco.windows from pyproject.toml, allows for wheel builds on python2
+
+
+v4.1.0
+=======
+
+* include python 3.9 via the deadsnakes action
+* return release_branch_semver scheme (it got dropped in a bad rebase)
+* undo the devendoring of the samefile backport for python2.7 on windows
+* re-enable the building of universal wheels
+* fix handling of missing git/hg on python2.7 (python 3 exceptions where used)
+* correct the tox flake8 invocation
+* trigger builds on tags again
+
+v4.0.0
+======
+
+* Add ``parentdir_project_version`` to support installs from GitHub release
+ tarballs.
+* use Coordinated Universal Time (UTC)
+* switch to github actions for ci
+* fix documentation for ``tag_regex`` and add support for single digit versions
+* document handling of enterprise distros with unsupported setuptools versions #312
+* switch to declarative metadata
+* drop the internal copy of samefile and use a dependency on jaraco.windows on legacy systems
+* select git tags based on the presence of numbers instead of dots
+* enable getting a version form a parent folder prefix
+* add release-branch-semver version scheme
+* make global configuration available to version metadata
+* drop official support for python 3.4
+
+v3.5.0
+======
+
+* add ``no-local-version`` local scheme and improve documentation for schemes
+
+v3.4.4
+======
+
+* fix #403: also sort out resource warnings when dealing with git file finding
+
+v3.4.3
+======
+
+* fix #399: ensure the git file finder terminates subprocess after reading archive
+
+v3.4.2
+======
+
+* fix #395: correctly transfer tag regex in the Configuration constructor
+* rollback --first-parent for git describe as it turns out to be a regression for some users
+
+v3.4.1
+======
+
+* pull in #377 to fix #374: correctly set up the default version scheme for pyproject usage.
+ this bugfix got missed when ruushing the release.
+
+v3.4.0
+======
+
+* fix #181 - add support for projects built under setuptools declarative config
+ by way of the setuptools.finalize_distribution_options hook in Setuptools 42.
+
+* fix #305 - ensure the git file finder closes filedescriptors even when errors happen
+
+* fix #381 - clean out env vars from the git hook system to ensure correct function from within
+
+* modernize docs wrt importlib.metadata
+
+*edited*
+
+* use --first-parent for git describe
+
+v3.3.3
+======
+
+* add eggs for python3.7 and 3.8 to the deploy
+
+v3.3.2
+======
+
+
+* fix #335 - fix python3.8 support and add builds for up to python3.8
+
+v3.3.1
+======
+
+* fix #333 (regression from #198) - use a specific fallback root when calling fallbacks. Remove old
+ hack that resets the root when fallback entrypoints are present.
+
+v3.3.0
+======
+
+* fix #198 by adding the ``fallback_version`` option, which sets the version to be used when everything else fails.
+
+v3.2.0
+======
+
+* fix #303 and #283 by adding the option ``git_describe_command`` to allow the user to control the
+way that `git describe` is called.
+
+v3.1.0
+=======
+
+* fix #297 - correct the invocation in version_from_scm and deprecate it as its exposed by accident
+* fix #298 - handle git file listing on empty repositories
+* fix #268 - deprecate ScmVersion.extra
+
+
+v3.0.6
+======
+* fix #295 - correctly handle selfinstall from tarballs
+
+v3.0.5
+======
+
+* fix #292 - match leading ``V`` character as well
+
+ https://www.python.org/dev/peps/pep-0440/#preceding-v-character
+
+v3.0.4
+=======
+
+* rerelease of 3.0.3 after fixing the release process
+
+v3.0.3 (pulled from pypi due to a packaging issue)
+======
+
+* fix #286 - duo an oversight a helper functio nwas returning a generator instead of a list
+
+
+v3.0.2
+======
+
+* fix a regression from tag parsing - support for multi-dashed prefixes - #284
+
+
+v3.0.1
+=======
+
+* fix a regression in setuptools_scm.git.parse - reorder arguments so the positional invocation from before works as expected #281
+
+v3.0.0
+=======
+
+* introduce pre-commit and use black
+* print the origin module to help testing
+* switch to src layout (breaking change)
+* no longer alias tag and parsed_version in order to support understanding a version parse failure
+* require parse results to be ScmVersion or None (breaking change)
+* fix #266 by requiring the prefix word to be a word again
+ (breaking change as the bug allowed arbitrary prefixes while the original feature only allowed words")
+* introduce a internal config object to allow the configruation fo tag parsing and prefixes
+ (thanks to @punkadiddle for introducing it and passing it trough)
+
+v2.1.0
+======
+
+* enhance docs for sphinx usage
+* add symlink support to file finder for git #247
+ (thanks Stéphane Bidoul)
+* enhance tests handling win32
+ (thanks Stéphane Bidoul)
+
+v2.0.0
+========
+
+* fix #237 - correct imports in code examples
+* improve mercurial commit detection (thanks Aaron)
+* breaking change: remove support for setuptools before parsed versions
+* reintroduce manifest as the travis deploy cant use the file finder
+* reconfigure flake8 for future compatibility with black
+* introduce support for branch name in version metadata and support a opt-in simplified semver version scheme
+
+v1.17.0
+========
+
+* fix regression in git support - use a function to ensure it works in egg isntalled mode
+* actually fail if file finding fails in order to see broken setups instead of generating broken dists
+
+ (thanks Mehdi ABAAKOUK for both)
+
+
+v1.16.2
+========
+
+* fix regression in handling git export ignores
+ (thanks Mehdi ABAAKOUK)
+
+v1.16.1
+=======
+
+* fix regression in support for old setuptools versions
+ (thanks Marco Clemencic)
+
+
+v1.16.0
+=======
+
+* drop support for eol python versions
+* #214 - fix missuse in surogate-escape api
+* add the node-and-timestamp local version sheme
+* respect git export ignores
+* avoid shlex.split on windows
+* fix #218 - better handling of mercurial edge-cases with tag commits
+ being considered as the tagged commit
+* fix #223 - remove the dependency on the interal SetupttoolsVersion
+ as it was removed after long-standing deprecation
+
+v1.15.7
+======
+
+* Fix #174 with #207: Re-use samefile backport as developed in
+ jaraco.windows, and only use the backport where samefile is
+ not available.
+
+v1.15.6
+=======
+
+* fix #171 by unpinning the py version to allow a fixed one to get installed
+
+v1.15.5
+=======
+
+* fix #167 by correctly respecting preformatted version metadata
+ from PKG-INFO/EGG-INFO
+
+v1.15.4
+=======
+
+* fix issue #164: iterate all found entry points to avoid erros when pip remakes egg-info
+* enhance self-use to enable pip install from github again
+
+v1.15.3
+=======
+
+* bring back correctly getting our version in the own sdist, finalizes #114
+* fix issue #150: strip local components of tags
+
+v1.15.2
+=======
+
+* fix issue #128: return None when a scm specific parse fails in a worktree to ease parse reuse
+
+
+v1.15.1
+=======
+
+* fix issue #126: the local part of any tags is discarded
+ when guessing new versions
+* minor performance optimization by doing fewer git calls
+ in the usual cases
+
+
+v1.15.0
+=======
+
+* more sophisticated ignoring of mercurial tag commits
+ when considering distance in commits
+ (thanks Petre Mierlutiu)
+* fix issue #114: stop trying to be smart for the sdist
+ and ensure its always correctly usign itself
+* update trove classifiers
+* fix issue #84: document using the installed package metadata for sphinx
+* fix issue #81: fail more gracious when git/hg are missing
+* address issue #93: provide an experimental api to customize behaviour on shallow git repos
+ a custom parse function may pick pre parse actions to do when using git
+
+
+v1.14.1
+=======
+
+* fix #109: when detecting a dirty git workdir
+ don't consider untracked file
+ (this was a regression due to #86 in v1.13.1)
+* consider the distance 0 when the git node is unknown
+ (happens when you haven't commited anything)
+
+v1.14.0
+=======
+
+* publish bdist_egg for python 2.6, 2.7 and 3.3-3.5
+* fix issue #107 - dont use node if it is None
+
+v1.13.1
+=======
+
+* fix issue #86 - detect dirty git workdir without tags
+
+v1.13.0
+=======
+
+* fix regression caused by the fix of #101
+ * assert types for version dumping
+ * strictly pass all versions trough parsed version metadata
+
+v1.12.0
+=======
+
+* fix issue #97 - add support for mercurial plugins
+* fix issue #101 - write version cache even for pretend version
+ (thanks anarcat for reporting and fixing)
+
+v1.11.1
+========
+
+* fix issue #88 - better docs for sphinx usage (thanks Jason)
+* fix issue #89 - use normpath to deal with windows
+ (thanks Te-jé Rodgers for reporting and fixing)
+
+v1.11.0
+=======
+
+* always run tag_to_version so in order to handle prefixes on old setuptools
+ (thanks to Brian May)
+* drop support for python 3.2
+* extend the error message on missing scm metadata
+ (thanks Markus Unterwaditzer)
+* fix bug when using callable version_scheme
+ (thanks Esben Haabendal)
+
+v1.10.1
+=======
+
+* fix issue #73 - in hg pre commit merge, consider parent1 instead of failing
+
+v1.10.0
+=======
+
+* add support for overriding the version number via the
+ environment variable SETUPTOOLS_SCM_PRETEND_VERSION
+
+* fix isssue #63 by adding the --match parameter to the git describe call
+ and prepare the possibility of passing more options to scm backends
+
+* fix issue #70 and #71 by introducing the parse keyword
+ to specify custom scm parsing, its an expert feature,
+ use with caution
+
+ this change also introduces the setuptools_scm.parse_scm_fallback
+ entrypoint which can be used to register custom archive fallbacks
+
+
+v1.9.0
+======
+
+* Add :code:`relative_to` parameter to :code:`get_version` function;
+ fixes #44 per #45.
+
+v1.8.0
+======
+
+* fix issue with setuptools wrong version warnings being printed to standard
+ out. User is informed now by distutils-warnings.
+* restructure root finding, we now reliably ignore outer scm
+ and prefer PKG-INFO over scm, fixes #43 and #45
+
+v1.7.0
+======
+
+* correct the url to github
+ thanks David Szotten
+* enhance scm not found errors with a note on git tarballs
+ thanks Markus
+* add support for :code:`write_to_template`
+
+v1.6.0
+======
+
+* bail out early if the scm is missing
+
+ this brings issues with git tarballs and
+ older devpi-client releases to light,
+ before we would let the setup stay at version 0.0,
+ now there is a ValueError
+
+* propperly raise errors on write_to missuse (thanks Te-jé Rodgers)
+
+v1.5.5
+======
+
+* Fix bug on Python 2 on Windows when environment has unicode fields.
+
+v1.5.4
+======
+
+* Fix bug on Python 2 when version is loaded from existing metadata.
+
+v1.5.3
+======
+
+* #28: Fix decoding error when PKG-INFO contains non-ASCII.
+
+v1.5.2
+======
+
+* add zip_safe flag
+
+v1.5.1
+======
+
+* fix file access bug i missed in 1.5
+
+v1.5.0
+======
+
+* moved setuptools integration related code to own file
+* support storing version strings into a module/text file
+ using the :code:`write_to` coniguration parameter
+
+v1.4.0
+======
+
+* propper handling for sdist
+* fix file-finder failure from windows
+* resuffle docs
+
+v1.3.0
+======
+
+* support setuptools easy_install egg creation details
+ by hardwireing the version in the sdist
+
+v1.2.0
+======
+
+* enhance self-use
+
+v1.1.0
+======
+
+* enable self-use
+
+v1.0.0
+======
+
+* documentation enhancements
+
+v0.26
+=====
+
+* rename to setuptools_scm
+* split into package, add lots of entry points for extension
+* pluggable version schemes
+
+v0.25
+=====
+
+* fix pep440 support
+ this reshuffles the complete code for version guessing
+
+v0.24
+=====
+
+* dont drop dirty flag on node finding
+* fix distance for dirty flagged versions
+* use dashes for time again,
+ its normalisation with setuptools
+* remove the own version attribute,
+ it was too fragile to test for
+* include file finding
+* handle edge cases around dirty tagged versions
+
+v0.23
+=====
+
+* windows compatibility fix (thanks stefan)
+ drop samefile since its missing in
+ some python2 versions on windows
+* add tests to the source tarballs
+
+
+v0.22
+=====
+
+* windows compatibility fix (thanks stefan)
+ use samefile since it does path normalisation
+
+v0.21
+=====
+
+* fix the own version attribute (thanks stefan)
+
+v0.20
+=====
+
+* fix issue 11: always take git describe long format
+ to avoid the source of the ambiguity
+* fix issue 12: add a __version__ attribute via pkginfo
+
+v0.19
+=====
+
+* configurable next version guessing
+* fix distance guessing (thanks stefan)
--- /dev/null
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+exclude *.nix
+exclude .travis.yaml
+exclude .pre-commit-config.yaml
+include *.py
+include testing/*.py
+include tox.ini
+include *.rst
+include LICENSE
+include *.toml
+recursive-include testing *.bash
--- /dev/null
+Metadata-Version: 2.1
+Name: setuptools_scm
+Version: 4.1.2
+Summary: the blessed package to manage your versions by scm tags
+Home-page: https://github.com/pypa/setuptools_scm/
+Author: Ronny Pfannschmidt
+Author-email: opensource@ronnypfannschmidt.de
+License: MIT
+Description: setuptools_scm
+ ===============
+
+ ``setuptools_scm`` handles managing your Python package versions
+ in SCM metadata instead of declaring them as the version argument
+ or in a SCM managed file.
+
+ Additionally ``setuptools_scm`` provides setuptools with a list of files that are managed by the SCM
+ (i.e. it automatically adds all of the SCM-managed files to the sdist).
+ Unwanted files must be excluded by discarding them via ``MANIFEST.in``.
+
+ .. image:: https://travis-ci.org/pypa/setuptools_scm.svg?branch=master
+ :target: https://travis-ci.org/pypa/setuptools_scm
+
+ .. image:: https://tidelift.com/badges/package/pypi/setuptools-scm
+ :target: https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme
+
+
+ ``pyproject.toml`` usage
+ ------------------------
+
+ The preferred way to configure ``setuptools_scm`` is to author
+ settings in a ``tool.setuptools_scm`` section of ``pyproject.toml``.
+
+ This feature requires Setuptools 42 or later, released in Nov, 2019.
+ If your project needs to support build from sdist on older versions
+ of Setuptools, you will need to also implement the ``setup.py usage``
+ for those legacy environments.
+
+ First, ensure that ``setuptools_scm`` is present during the project's
+ built step by specifying it as one of the build requirements.
+
+ .. code:: toml
+
+ # pyproject.toml
+ [build-system]
+ requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+
+ Note that the ``toml`` extra must be supplied.
+
+ That will be sufficient to require ``setuptools_scm`` for projects
+ that support PEP 518 (`pip <https://pypi.org/project/pip>`_ and
+ `pep517 <https://pypi.org/project/pep517/>`_). Many tools,
+ especially those that invoke ``setup.py`` for any reason, may
+ continue to rely on ``setup_requires``. For maximum compatibility
+ with those uses, consider also including a ``setup_requires`` directive
+ (described below in ``setup.py usage`` and ``setup.cfg``).
+
+ To enable version inference, add this section to your pyproject.toml:
+
+ .. code:: toml
+
+ # pyproject.toml
+ [tool.setuptools_scm]
+
+ Including this section is comparable to supplying
+ ``use_scm_version=True`` in ``setup.py``. Additionally,
+ include arbitrary keyword arguments in that section
+ to be supplied to ``get_version()``. For example:
+
+ .. code:: toml
+
+ # pyproject.toml
+
+ [tool.setuptools_scm]
+ write_to = "pkg/version.py"
+
+
+ ``setup.py`` usage
+ ------------------
+
+ The following settings are considered legacy behavior and
+ superseded by the ``pyproject.toml`` usage, but for maximal
+ compatibility, projects may also supply the configuration in
+ this older form.
+
+ To use ``setuptools_scm`` just modify your project's ``setup.py`` file
+ like this:
+
+ * Add ``setuptools_scm`` to the ``setup_requires`` parameter.
+ * Add the ``use_scm_version`` parameter and set it to ``True``.
+
+ For example:
+
+ .. code:: python
+
+ from setuptools import setup
+ setup(
+ ...,
+ use_scm_version=True,
+ setup_requires=['setuptools_scm'],
+ ...,
+ )
+
+ Arguments to ``get_version()`` (see below) may be passed as a dictionary to
+ ``use_scm_version``. For example:
+
+ .. code:: python
+
+ from setuptools import setup
+ setup(
+ ...,
+ use_scm_version = {
+ "root": "..",
+ "relative_to": __file__,
+ "local_scheme": "node-and-timestamp"
+ },
+ setup_requires=['setuptools_scm'],
+ ...,
+ )
+
+ You can confirm the version number locally via ``setup.py``:
+
+ .. code-block:: shell
+
+ $ python setup.py --version
+
+ .. note::
+
+ If you see unusual version numbers for packages but ``python setup.py
+ --version`` reports the expected version number, ensure ``[egg_info]`` is
+ not defined in ``setup.cfg``.
+
+
+ ``setup.cfg`` usage
+ -------------------
+
+ If using `setuptools 30.3.0
+ <https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files>`_
+ or greater, you can store ``setup_requires`` configuration in ``setup.cfg``.
+ However, ``use_scm_version`` must still be placed in ``setup.py``. For example:
+
+ .. code:: python
+
+ # setup.py
+ from setuptools import setup
+ setup(
+ use_scm_version=True,
+ )
+
+ .. code:: ini
+
+ # setup.cfg
+ [metadata]
+ ...
+
+ [options]
+ setup_requires =
+ setuptools_scm
+ ...
+
+ .. important::
+
+ Ensure neither the ``[metadata]`` ``version`` option nor the ``[egg_info]``
+ section are defined, as these will interfere with ``setuptools_scm``.
+
+ You may also need to define a ``pyproject.toml`` file (`PEP-0518
+ <https://www.python.org/dev/peps/pep-0518>`_) to ensure you have the required
+ version of ``setuptools``:
+
+ .. code:: ini
+
+ # pyproject.toml
+ [build-system]
+ requires = ["setuptools>=30.3.0", "wheel", "setuptools_scm"]
+
+ For more information, refer to the `setuptools issue #1002
+ <https://github.com/pypa/setuptools/issues/1002>`_.
+
+
+ Programmatic usage
+ ------------------
+
+ In order to use ``setuptools_scm`` from code that is one directory deeper
+ than the project's root, you can use:
+
+ .. code:: python
+
+ from setuptools_scm import get_version
+ version = get_version(root='..', relative_to=__file__)
+
+ See `setup.py Usage`_ above for how to use this within ``setup.py``.
+
+
+ Retrieving package version at runtime
+ -------------------------------------
+
+ If you have opted not to hardcode the version number inside the package,
+ you can retrieve it at runtime from PEP-0566_ metadata using
+ ``importlib.metadata`` from the standard library
+ or the `importlib_metadata`_ backport:
+
+ .. code:: python
+
+ from importlib.metadata import version, PackageNotFoundError
+
+ try:
+ __version__ = version(__name__)
+ except PackageNotFoundError:
+ # package is not installed
+ pass
+
+ Alternatively, you can use ``pkg_resources`` which is included in
+ ``setuptools``:
+
+ .. code:: python
+
+ from pkg_resources import get_distribution, DistributionNotFound
+
+ try:
+ __version__ = get_distribution(__name__).version
+ except DistributionNotFound:
+ # package is not installed
+ pass
+
+ This does place a runtime dependency on ``setuptools``.
+
+ .. _PEP-0566: https://www.python.org/dev/peps/pep-0566/
+ .. _importlib_metadata: https://pypi.org/project/importlib-metadata/
+
+
+ Usage from Sphinx
+ -----------------
+
+ It is discouraged to use ``setuptools_scm`` from Sphinx itself,
+ instead use ``pkg_resources`` after editable/real installation:
+
+ .. code:: python
+
+ # contents of docs/conf.py
+ from pkg_resources import get_distribution
+ release = get_distribution('myproject').version
+ # for example take major/minor
+ version = '.'.join(release.split('.')[:2])
+
+ The underlying reason is, that services like *Read the Docs* sometimes change
+ the working directory for good reasons and using the installed metadata
+ prevents using needless volatile data there.
+
+ Notable Plugins
+ ----------------
+
+ `setuptools_scm_git_archive <https://pypi.python.org/pypi/setuptools_scm_git_archive>`_
+ Provides partial support for obtaining versions from git archives that
+ belong to tagged versions. The only reason for not including it in
+ ``setuptools_scm`` itself is Git/GitHub not supporting sufficient metadata
+ for untagged/followup commits, which is preventing a consistent UX.
+
+
+ Default versioning scheme
+ --------------------------
+
+ In the standard configuration ``setuptools_scm`` takes a look at three things:
+
+ 1. latest tag (with a version number)
+ 2. the distance to this tag (e.g. number of revisions since latest tag)
+ 3. workdir state (e.g. uncommitted changes since latest tag)
+
+ and uses roughly the following logic to render the version:
+
+ no distance and clean:
+ ``{tag}``
+ distance and clean:
+ ``{next_version}.dev{distance}+{scm letter}{revision hash}``
+ no distance and not clean:
+ ``{tag}+dYYYYMMDD``
+ distance and not clean:
+ ``{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD``
+
+ The next version is calculated by adding ``1`` to the last numeric component of
+ the tag.
+
+ For Git projects, the version relies on `git describe <https://git-scm.com/docs/git-describe>`_,
+ so you will see an additional ``g`` prepended to the ``{revision hash}``.
+
+ Semantic Versioning (SemVer)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Due to the default behavior it's necessary to always include a
+ patch version (the ``3`` in ``1.2.3``), or else the automatic guessing
+ will increment the wrong part of the SemVer (e.g. tag ``2.0`` results in
+ ``2.1.devX`` instead of ``2.0.1.devX``). So please make sure to tag
+ accordingly.
+
+ .. note::
+
+ Future versions of ``setuptools_scm`` will switch to `SemVer
+ <http://semver.org/>`_ by default hiding the the old behavior as an
+ configurable option.
+
+
+ Builtin mechanisms for obtaining version numbers
+ --------------------------------------------------
+
+ 1. the SCM itself (git/hg)
+ 2. ``.hg_archival`` files (mercurial archives)
+ 3. ``PKG-INFO``
+
+ .. note::
+
+ Git archives are not supported due to Git shortcomings
+
+
+ File finders hook makes most of MANIFEST.in unnecessary
+ -------------------------------------------------------
+
+ ``setuptools_scm`` implements a `file_finders
+ <https://setuptools.readthedocs.io/en/latest/setuptools.html#adding-support-for-revision-control-systems>`_
+ entry point which returns all files tracked by your SCM. This eliminates
+ the need for a manually constructed ``MANIFEST.in`` in most cases where this
+ would be required when not using ``setuptools_scm``, namely:
+
+ * To ensure all relevant files are packaged when running the ``sdist`` command.
+
+ * When using `include_package_data <https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files>`_
+ to include package data as part of the ``build`` or ``bdist_wheel``.
+
+ ``MANIFEST.in`` may still be used: anything defined there overrides the hook.
+ This is mostly useful to exclude files tracked in your SCM from packages,
+ although in principle it can be used to explicitly include non-tracked files
+ too.
+
+
+ Configuration parameters
+ ------------------------
+
+ In order to configure the way ``use_scm_version`` works you can provide
+ a mapping with options instead of a boolean value.
+
+ The currently supported configuration keys are:
+
+ :root:
+ Relative path to cwd, used for finding the SCM root; defaults to ``.``
+
+ :version_scheme:
+ Configures how the local version number is constructed; either an
+ entrypoint name or a callable.
+
+ :local_scheme:
+ Configures how the local component of the version is constructed; either an
+ entrypoint name or a callable.
+
+ :write_to:
+ A path to a file that gets replaced with a file containing the current
+ version. It is ideal for creating a ``version.py`` file within the
+ package, typically used to avoid using `pkg_resources.get_distribution`
+ (which adds some overhead).
+
+ .. warning::
+
+ Only files with :code:`.py` and :code:`.txt` extensions have builtin
+ templates, for other file types it is necessary to provide
+ :code:`write_to_template`.
+
+ :write_to_template:
+ A newstyle format string that is given the current version as
+ the ``version`` keyword argument for formatting.
+
+ :relative_to:
+ A file from which the root can be resolved.
+ Typically called by a script or module that is not in the root of the
+ repository to point ``setuptools_scm`` at the root of the repository by
+ supplying ``__file__``.
+
+ :tag_regex:
+ A Python regex string to extract the version part from any SCM tag.
+ The regex needs to contain either a single match group, or a group
+ named ``version``, that captures the actual version information.
+
+ Defaults to the value of ``setuptools_scm.config.DEFAULT_TAG_REGEX``
+ (see `config.py <src/setuptools_scm/config.py>`_).
+
+ :parentdir_prefix_version:
+ If the normal methods for detecting the version (SCM version,
+ sdist metadata) fail, and the parent directory name starts with
+ ``parentdir_prefix_version``, then this prefix is stripped and the rest of
+ the parent directory name is matched with ``tag_regex`` to get a version
+ string. If this parameter is unset (the default), then this fallback is
+ not used.
+
+ This is intended to cover GitHub's "release tarballs", which extract into
+ directories named ``projectname-tag/`` (in which case
+ ``parentdir_prefix_version`` can be set e.g. to ``projectname-``).
+
+ :fallback_version:
+ A version string that will be used if no other method for detecting the
+ version worked (e.g., when using a tarball with no metadata). If this is
+ unset (the default), setuptools_scm will error if it fails to detect the
+ version.
+
+ :parse:
+ A function that will be used instead of the discovered SCM for parsing the
+ version.
+ Use with caution, this is a function for advanced use, and you should be
+ familiar with the ``setuptools_scm`` internals to use it.
+
+ :git_describe_command:
+ This command will be used instead the default ``git describe`` command.
+ Use with caution, this is a function for advanced use, and you should be
+ familiar with the ``setuptools_scm`` internals to use it.
+
+ Defaults to the value set by ``setuptools_scm.git.DEFAULT_DESCRIBE``
+ (see `git.py <src/setuptools_scm/git.py>`_).
+
+ To use ``setuptools_scm`` in other Python code you can use the ``get_version``
+ function:
+
+ .. code:: python
+
+ from setuptools_scm import get_version
+ my_version = get_version()
+
+ It optionally accepts the keys of the ``use_scm_version`` parameter as
+ keyword arguments.
+
+ Example configuration in ``setup.py`` format:
+
+ .. code:: python
+
+ from setuptools import setup
+
+ setup(
+ use_scm_version={
+ 'write_to': 'version.py',
+ 'write_to_template': '__version__ = "{version}"',
+ 'tag_regex': r'^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$',
+ }
+ )
+
+ Environment variables
+ ---------------------
+
+ :SETUPTOOLS_SCM_PRETEND_VERSION:
+ when defined and not empty,
+ its used as the primary source for the version number
+ in which case it will be a unparsed string
+
+ :SETUPTOOLS_SCM_DEBUG:
+ when defined and not empty,
+ a lot of debug information will be printed as part of ``setuptools_scm``
+ operating
+
+ Extending setuptools_scm
+ ------------------------
+
+ ``setuptools_scm`` ships with a few ``setuptools`` entrypoints based hooks to
+ extend its default capabilities.
+
+ Adding a new SCM
+ ~~~~~~~~~~~~~~~~
+
+ ``setuptools_scm`` provides two entrypoints for adding new SCMs:
+
+ ``setuptools_scm.parse_scm``
+ A function used to parse the metadata of the current workdir
+ using the name of the control directory/file of your SCM as the
+ entrypoint's name. E.g. for the built-in entrypoint for git the
+ entrypoint is named ``.git`` and references ``setuptools_scm.git:parse``
+
+ The return value MUST be a ``setuptools_scm.version.ScmVersion`` instance
+ created by the function ``setuptools_scm.version:meta``.
+
+ ``setuptools_scm.files_command``
+ Either a string containing a shell command that prints all SCM managed
+ files in its current working directory or a callable, that given a
+ pathname will return that list.
+
+ Also use then name of your SCM control directory as name of the entrypoint.
+
+ Version number construction
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ ``setuptools_scm.version_scheme``
+ Configures how the version number is constructed given a
+ ``setuptools_scm.version.ScmVersion`` instance and should return a string
+ representing the version.
+
+ Available implementations:
+
+ :guess-next-dev: Automatically guesses the next development version (default).
+ Guesses the upcoming release by incrementing the pre-release segment if present,
+ otherwise by incrementing the micro segment. Then appends :code:`.devN`.
+ :post-release: generates post release versions (adds :code:`.postN`)
+ :python-simplified-semver: Basic semantic versioning. Guesses the upcoming release
+ by incrementing the minor segment and setting the micro segment to zero if the
+ current branch contains the string ``'feature'``, otherwise by incrementing the
+ micro version. Then appends :code:`.devN`. Not compatible with pre-releases.
+ :release-branch-semver: Semantic versioning for projects with release branches. The
+ same as ``guess-next-dev`` (incrementing the pre-release or micro segment) if on
+ a release branch: a branch whose name (ignoring namespace) parses as a version
+ that matches the most recent tag up to the minor segment. Otherwise if on a
+ non-release branch, increments the minor segment and sets the micro segment to
+ zero, then appends :code:`.devN`.
+
+ ``setuptools_scm.local_scheme``
+ Configures how the local part of a version is rendered given a
+ ``setuptools_scm.version.ScmVersion`` instance and should return a string
+ representing the local version.
+ Dates and times are in Coordinated Universal Time (UTC), because as part
+ of the version, they should be location independent.
+
+ Available implementations:
+
+ :node-and-date: adds the node on dev versions and the date on dirty
+ workdir (default)
+ :node-and-timestamp: like ``node-and-date`` but with a timestamp of
+ the form ``{:%Y%m%d%H%M%S}`` instead
+ :dirty-tag: adds ``+dirty`` if the current workdir has changes
+ :no-local-version: omits local version, useful e.g. because pypi does
+ not support it
+
+
+ Importing in ``setup.py``
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ To support usage in ``setup.py`` passing a callable into ``use_scm_version``
+ is supported.
+
+ Within that callable, ``setuptools_scm`` is available for import.
+ The callable must return the configuration.
+
+
+ .. code:: python
+
+ # content of setup.py
+ import setuptools
+
+ def myversion():
+ from setuptools_scm.version import get_local_dirty_tag
+ def clean_scheme(version):
+ return get_local_dirty_tag(version) if version.dirty else '+clean'
+
+ return {'local_scheme': clean_scheme}
+
+ setup(
+ ...,
+ use_scm_version=myversion,
+ ...
+ )
+
+
+ Note on testing non-installed versions
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ While the general advice is to test against a installed version,
+ some environments require a test prior to install,
+
+ .. code::
+
+ $ python setup.py egg_info
+ $ PYTHONPATH=$PWD:$PWD/src pytest
+
+
+ Interaction with Enterprise Distributions
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Some enterprise distributions like RHEL7 and others
+ ship rather old setuptools versions due to various release management details.
+
+ On such distributions one might observe errors like:
+
+ :code:``setuptools_scm.version.SetuptoolsOutdatedWarning: your setuptools is too old (<12)``
+
+ In those case its typically possible to build by using a sdist against ``setuptools_scm<2.0``.
+ As those old setuptools versions lack sensible types for versions,
+ modern setuptools_scm is unable to support them sensibly.
+
+ In case the project you need to build can not be patched to either use old setuptools_scm,
+ its still possible to install a more recent version of setuptools in order to handle the build
+ and/or install the package by using wheels or eggs.
+
+
+
+
+ Code of Conduct
+ ---------------
+
+ Everyone interacting in the ``setuptools_scm`` project's codebases, issue
+ trackers, chat rooms, and mailing lists is expected to follow the
+ `PyPA Code of Conduct`_.
+
+ .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
+
+ Security Contact
+ ================
+
+ To report a security vulnerability, please use the
+ `Tidelift security contact <https://tidelift.com/security>`_.
+ Tidelift will coordinate the fix and disclosure.
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Version Control
+Classifier: Topic :: System :: Software Distribution
+Classifier: Topic :: Utilities
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
+Provides-Extra: toml
--- /dev/null
+setuptools_scm
+===============
+
+``setuptools_scm`` handles managing your Python package versions
+in SCM metadata instead of declaring them as the version argument
+or in a SCM managed file.
+
+Additionally ``setuptools_scm`` provides setuptools with a list of files that are managed by the SCM
+(i.e. it automatically adds all of the SCM-managed files to the sdist).
+Unwanted files must be excluded by discarding them via ``MANIFEST.in``.
+
+.. image:: https://travis-ci.org/pypa/setuptools_scm.svg?branch=master
+ :target: https://travis-ci.org/pypa/setuptools_scm
+
+.. image:: https://tidelift.com/badges/package/pypi/setuptools-scm
+ :target: https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme
+
+
+``pyproject.toml`` usage
+------------------------
+
+The preferred way to configure ``setuptools_scm`` is to author
+settings in a ``tool.setuptools_scm`` section of ``pyproject.toml``.
+
+This feature requires Setuptools 42 or later, released in Nov, 2019.
+If your project needs to support build from sdist on older versions
+of Setuptools, you will need to also implement the ``setup.py usage``
+for those legacy environments.
+
+First, ensure that ``setuptools_scm`` is present during the project's
+built step by specifying it as one of the build requirements.
+
+.. code:: toml
+
+ # pyproject.toml
+ [build-system]
+ requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+
+Note that the ``toml`` extra must be supplied.
+
+That will be sufficient to require ``setuptools_scm`` for projects
+that support PEP 518 (`pip <https://pypi.org/project/pip>`_ and
+`pep517 <https://pypi.org/project/pep517/>`_). Many tools,
+especially those that invoke ``setup.py`` for any reason, may
+continue to rely on ``setup_requires``. For maximum compatibility
+with those uses, consider also including a ``setup_requires`` directive
+(described below in ``setup.py usage`` and ``setup.cfg``).
+
+To enable version inference, add this section to your pyproject.toml:
+
+.. code:: toml
+
+ # pyproject.toml
+ [tool.setuptools_scm]
+
+Including this section is comparable to supplying
+``use_scm_version=True`` in ``setup.py``. Additionally,
+include arbitrary keyword arguments in that section
+to be supplied to ``get_version()``. For example:
+
+.. code:: toml
+
+ # pyproject.toml
+
+ [tool.setuptools_scm]
+ write_to = "pkg/version.py"
+
+
+``setup.py`` usage
+------------------
+
+The following settings are considered legacy behavior and
+superseded by the ``pyproject.toml`` usage, but for maximal
+compatibility, projects may also supply the configuration in
+this older form.
+
+To use ``setuptools_scm`` just modify your project's ``setup.py`` file
+like this:
+
+* Add ``setuptools_scm`` to the ``setup_requires`` parameter.
+* Add the ``use_scm_version`` parameter and set it to ``True``.
+
+For example:
+
+.. code:: python
+
+ from setuptools import setup
+ setup(
+ ...,
+ use_scm_version=True,
+ setup_requires=['setuptools_scm'],
+ ...,
+ )
+
+Arguments to ``get_version()`` (see below) may be passed as a dictionary to
+``use_scm_version``. For example:
+
+.. code:: python
+
+ from setuptools import setup
+ setup(
+ ...,
+ use_scm_version = {
+ "root": "..",
+ "relative_to": __file__,
+ "local_scheme": "node-and-timestamp"
+ },
+ setup_requires=['setuptools_scm'],
+ ...,
+ )
+
+You can confirm the version number locally via ``setup.py``:
+
+.. code-block:: shell
+
+ $ python setup.py --version
+
+.. note::
+
+ If you see unusual version numbers for packages but ``python setup.py
+ --version`` reports the expected version number, ensure ``[egg_info]`` is
+ not defined in ``setup.cfg``.
+
+
+``setup.cfg`` usage
+-------------------
+
+If using `setuptools 30.3.0
+<https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files>`_
+or greater, you can store ``setup_requires`` configuration in ``setup.cfg``.
+However, ``use_scm_version`` must still be placed in ``setup.py``. For example:
+
+.. code:: python
+
+ # setup.py
+ from setuptools import setup
+ setup(
+ use_scm_version=True,
+ )
+
+.. code:: ini
+
+ # setup.cfg
+ [metadata]
+ ...
+
+ [options]
+ setup_requires =
+ setuptools_scm
+ ...
+
+.. important::
+
+ Ensure neither the ``[metadata]`` ``version`` option nor the ``[egg_info]``
+ section are defined, as these will interfere with ``setuptools_scm``.
+
+You may also need to define a ``pyproject.toml`` file (`PEP-0518
+<https://www.python.org/dev/peps/pep-0518>`_) to ensure you have the required
+version of ``setuptools``:
+
+.. code:: ini
+
+ # pyproject.toml
+ [build-system]
+ requires = ["setuptools>=30.3.0", "wheel", "setuptools_scm"]
+
+For more information, refer to the `setuptools issue #1002
+<https://github.com/pypa/setuptools/issues/1002>`_.
+
+
+Programmatic usage
+------------------
+
+In order to use ``setuptools_scm`` from code that is one directory deeper
+than the project's root, you can use:
+
+.. code:: python
+
+ from setuptools_scm import get_version
+ version = get_version(root='..', relative_to=__file__)
+
+See `setup.py Usage`_ above for how to use this within ``setup.py``.
+
+
+Retrieving package version at runtime
+-------------------------------------
+
+If you have opted not to hardcode the version number inside the package,
+you can retrieve it at runtime from PEP-0566_ metadata using
+``importlib.metadata`` from the standard library
+or the `importlib_metadata`_ backport:
+
+.. code:: python
+
+ from importlib.metadata import version, PackageNotFoundError
+
+ try:
+ __version__ = version(__name__)
+ except PackageNotFoundError:
+ # package is not installed
+ pass
+
+Alternatively, you can use ``pkg_resources`` which is included in
+``setuptools``:
+
+.. code:: python
+
+ from pkg_resources import get_distribution, DistributionNotFound
+
+ try:
+ __version__ = get_distribution(__name__).version
+ except DistributionNotFound:
+ # package is not installed
+ pass
+
+This does place a runtime dependency on ``setuptools``.
+
+.. _PEP-0566: https://www.python.org/dev/peps/pep-0566/
+.. _importlib_metadata: https://pypi.org/project/importlib-metadata/
+
+
+Usage from Sphinx
+-----------------
+
+It is discouraged to use ``setuptools_scm`` from Sphinx itself,
+instead use ``pkg_resources`` after editable/real installation:
+
+.. code:: python
+
+ # contents of docs/conf.py
+ from pkg_resources import get_distribution
+ release = get_distribution('myproject').version
+ # for example take major/minor
+ version = '.'.join(release.split('.')[:2])
+
+The underlying reason is, that services like *Read the Docs* sometimes change
+the working directory for good reasons and using the installed metadata
+prevents using needless volatile data there.
+
+Notable Plugins
+----------------
+
+`setuptools_scm_git_archive <https://pypi.python.org/pypi/setuptools_scm_git_archive>`_
+ Provides partial support for obtaining versions from git archives that
+ belong to tagged versions. The only reason for not including it in
+ ``setuptools_scm`` itself is Git/GitHub not supporting sufficient metadata
+ for untagged/followup commits, which is preventing a consistent UX.
+
+
+Default versioning scheme
+--------------------------
+
+In the standard configuration ``setuptools_scm`` takes a look at three things:
+
+1. latest tag (with a version number)
+2. the distance to this tag (e.g. number of revisions since latest tag)
+3. workdir state (e.g. uncommitted changes since latest tag)
+
+and uses roughly the following logic to render the version:
+
+no distance and clean:
+ ``{tag}``
+distance and clean:
+ ``{next_version}.dev{distance}+{scm letter}{revision hash}``
+no distance and not clean:
+ ``{tag}+dYYYYMMDD``
+distance and not clean:
+ ``{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD``
+
+The next version is calculated by adding ``1`` to the last numeric component of
+the tag.
+
+For Git projects, the version relies on `git describe <https://git-scm.com/docs/git-describe>`_,
+so you will see an additional ``g`` prepended to the ``{revision hash}``.
+
+Semantic Versioning (SemVer)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Due to the default behavior it's necessary to always include a
+patch version (the ``3`` in ``1.2.3``), or else the automatic guessing
+will increment the wrong part of the SemVer (e.g. tag ``2.0`` results in
+``2.1.devX`` instead of ``2.0.1.devX``). So please make sure to tag
+accordingly.
+
+.. note::
+
+ Future versions of ``setuptools_scm`` will switch to `SemVer
+ <http://semver.org/>`_ by default hiding the the old behavior as an
+ configurable option.
+
+
+Builtin mechanisms for obtaining version numbers
+--------------------------------------------------
+
+1. the SCM itself (git/hg)
+2. ``.hg_archival`` files (mercurial archives)
+3. ``PKG-INFO``
+
+.. note::
+
+ Git archives are not supported due to Git shortcomings
+
+
+File finders hook makes most of MANIFEST.in unnecessary
+-------------------------------------------------------
+
+``setuptools_scm`` implements a `file_finders
+<https://setuptools.readthedocs.io/en/latest/setuptools.html#adding-support-for-revision-control-systems>`_
+entry point which returns all files tracked by your SCM. This eliminates
+the need for a manually constructed ``MANIFEST.in`` in most cases where this
+would be required when not using ``setuptools_scm``, namely:
+
+* To ensure all relevant files are packaged when running the ``sdist`` command.
+
+* When using `include_package_data <https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files>`_
+ to include package data as part of the ``build`` or ``bdist_wheel``.
+
+``MANIFEST.in`` may still be used: anything defined there overrides the hook.
+This is mostly useful to exclude files tracked in your SCM from packages,
+although in principle it can be used to explicitly include non-tracked files
+too.
+
+
+Configuration parameters
+------------------------
+
+In order to configure the way ``use_scm_version`` works you can provide
+a mapping with options instead of a boolean value.
+
+The currently supported configuration keys are:
+
+:root:
+ Relative path to cwd, used for finding the SCM root; defaults to ``.``
+
+:version_scheme:
+ Configures how the local version number is constructed; either an
+ entrypoint name or a callable.
+
+:local_scheme:
+ Configures how the local component of the version is constructed; either an
+ entrypoint name or a callable.
+
+:write_to:
+ A path to a file that gets replaced with a file containing the current
+ version. It is ideal for creating a ``version.py`` file within the
+ package, typically used to avoid using `pkg_resources.get_distribution`
+ (which adds some overhead).
+
+ .. warning::
+
+ Only files with :code:`.py` and :code:`.txt` extensions have builtin
+ templates, for other file types it is necessary to provide
+ :code:`write_to_template`.
+
+:write_to_template:
+ A newstyle format string that is given the current version as
+ the ``version`` keyword argument for formatting.
+
+:relative_to:
+ A file from which the root can be resolved.
+ Typically called by a script or module that is not in the root of the
+ repository to point ``setuptools_scm`` at the root of the repository by
+ supplying ``__file__``.
+
+:tag_regex:
+ A Python regex string to extract the version part from any SCM tag.
+ The regex needs to contain either a single match group, or a group
+ named ``version``, that captures the actual version information.
+
+ Defaults to the value of ``setuptools_scm.config.DEFAULT_TAG_REGEX``
+ (see `config.py <src/setuptools_scm/config.py>`_).
+
+:parentdir_prefix_version:
+ If the normal methods for detecting the version (SCM version,
+ sdist metadata) fail, and the parent directory name starts with
+ ``parentdir_prefix_version``, then this prefix is stripped and the rest of
+ the parent directory name is matched with ``tag_regex`` to get a version
+ string. If this parameter is unset (the default), then this fallback is
+ not used.
+
+ This is intended to cover GitHub's "release tarballs", which extract into
+ directories named ``projectname-tag/`` (in which case
+ ``parentdir_prefix_version`` can be set e.g. to ``projectname-``).
+
+:fallback_version:
+ A version string that will be used if no other method for detecting the
+ version worked (e.g., when using a tarball with no metadata). If this is
+ unset (the default), setuptools_scm will error if it fails to detect the
+ version.
+
+:parse:
+ A function that will be used instead of the discovered SCM for parsing the
+ version.
+ Use with caution, this is a function for advanced use, and you should be
+ familiar with the ``setuptools_scm`` internals to use it.
+
+:git_describe_command:
+ This command will be used instead the default ``git describe`` command.
+ Use with caution, this is a function for advanced use, and you should be
+ familiar with the ``setuptools_scm`` internals to use it.
+
+ Defaults to the value set by ``setuptools_scm.git.DEFAULT_DESCRIBE``
+ (see `git.py <src/setuptools_scm/git.py>`_).
+
+To use ``setuptools_scm`` in other Python code you can use the ``get_version``
+function:
+
+.. code:: python
+
+ from setuptools_scm import get_version
+ my_version = get_version()
+
+It optionally accepts the keys of the ``use_scm_version`` parameter as
+keyword arguments.
+
+Example configuration in ``setup.py`` format:
+
+.. code:: python
+
+ from setuptools import setup
+
+ setup(
+ use_scm_version={
+ 'write_to': 'version.py',
+ 'write_to_template': '__version__ = "{version}"',
+ 'tag_regex': r'^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$',
+ }
+ )
+
+Environment variables
+---------------------
+
+:SETUPTOOLS_SCM_PRETEND_VERSION:
+ when defined and not empty,
+ its used as the primary source for the version number
+ in which case it will be a unparsed string
+
+:SETUPTOOLS_SCM_DEBUG:
+ when defined and not empty,
+ a lot of debug information will be printed as part of ``setuptools_scm``
+ operating
+
+Extending setuptools_scm
+------------------------
+
+``setuptools_scm`` ships with a few ``setuptools`` entrypoints based hooks to
+extend its default capabilities.
+
+Adding a new SCM
+~~~~~~~~~~~~~~~~
+
+``setuptools_scm`` provides two entrypoints for adding new SCMs:
+
+``setuptools_scm.parse_scm``
+ A function used to parse the metadata of the current workdir
+ using the name of the control directory/file of your SCM as the
+ entrypoint's name. E.g. for the built-in entrypoint for git the
+ entrypoint is named ``.git`` and references ``setuptools_scm.git:parse``
+
+ The return value MUST be a ``setuptools_scm.version.ScmVersion`` instance
+ created by the function ``setuptools_scm.version:meta``.
+
+``setuptools_scm.files_command``
+ Either a string containing a shell command that prints all SCM managed
+ files in its current working directory or a callable, that given a
+ pathname will return that list.
+
+ Also use then name of your SCM control directory as name of the entrypoint.
+
+Version number construction
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``setuptools_scm.version_scheme``
+ Configures how the version number is constructed given a
+ ``setuptools_scm.version.ScmVersion`` instance and should return a string
+ representing the version.
+
+ Available implementations:
+
+ :guess-next-dev: Automatically guesses the next development version (default).
+ Guesses the upcoming release by incrementing the pre-release segment if present,
+ otherwise by incrementing the micro segment. Then appends :code:`.devN`.
+ :post-release: generates post release versions (adds :code:`.postN`)
+ :python-simplified-semver: Basic semantic versioning. Guesses the upcoming release
+ by incrementing the minor segment and setting the micro segment to zero if the
+ current branch contains the string ``'feature'``, otherwise by incrementing the
+ micro version. Then appends :code:`.devN`. Not compatible with pre-releases.
+ :release-branch-semver: Semantic versioning for projects with release branches. The
+ same as ``guess-next-dev`` (incrementing the pre-release or micro segment) if on
+ a release branch: a branch whose name (ignoring namespace) parses as a version
+ that matches the most recent tag up to the minor segment. Otherwise if on a
+ non-release branch, increments the minor segment and sets the micro segment to
+ zero, then appends :code:`.devN`.
+
+``setuptools_scm.local_scheme``
+ Configures how the local part of a version is rendered given a
+ ``setuptools_scm.version.ScmVersion`` instance and should return a string
+ representing the local version.
+ Dates and times are in Coordinated Universal Time (UTC), because as part
+ of the version, they should be location independent.
+
+ Available implementations:
+
+ :node-and-date: adds the node on dev versions and the date on dirty
+ workdir (default)
+ :node-and-timestamp: like ``node-and-date`` but with a timestamp of
+ the form ``{:%Y%m%d%H%M%S}`` instead
+ :dirty-tag: adds ``+dirty`` if the current workdir has changes
+ :no-local-version: omits local version, useful e.g. because pypi does
+ not support it
+
+
+Importing in ``setup.py``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To support usage in ``setup.py`` passing a callable into ``use_scm_version``
+is supported.
+
+Within that callable, ``setuptools_scm`` is available for import.
+The callable must return the configuration.
+
+
+.. code:: python
+
+ # content of setup.py
+ import setuptools
+
+ def myversion():
+ from setuptools_scm.version import get_local_dirty_tag
+ def clean_scheme(version):
+ return get_local_dirty_tag(version) if version.dirty else '+clean'
+
+ return {'local_scheme': clean_scheme}
+
+ setup(
+ ...,
+ use_scm_version=myversion,
+ ...
+ )
+
+
+Note on testing non-installed versions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+While the general advice is to test against a installed version,
+some environments require a test prior to install,
+
+.. code::
+
+ $ python setup.py egg_info
+ $ PYTHONPATH=$PWD:$PWD/src pytest
+
+
+Interaction with Enterprise Distributions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some enterprise distributions like RHEL7 and others
+ship rather old setuptools versions due to various release management details.
+
+On such distributions one might observe errors like:
+
+:code:``setuptools_scm.version.SetuptoolsOutdatedWarning: your setuptools is too old (<12)``
+
+In those case its typically possible to build by using a sdist against ``setuptools_scm<2.0``.
+As those old setuptools versions lack sensible types for versions,
+modern setuptools_scm is unable to support them sensibly.
+
+In case the project you need to build can not be patched to either use old setuptools_scm,
+its still possible to install a more recent version of setuptools in order to handle the build
+and/or install the package by using wheels or eggs.
+
+
+
+
+Code of Conduct
+---------------
+
+Everyone interacting in the ``setuptools_scm`` project's codebases, issue
+trackers, chat rooms, and mailing lists is expected to follow the
+`PyPA Code of Conduct`_.
+
+.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
+
+Security Contact
+================
+
+To report a security vulnerability, please use the
+`Tidelift security contact <https://tidelift.com/security>`_.
+Tidelift will coordinate the fix and disclosure.
--- /dev/null
+[build-system]
+requires = ["setuptools>=34.4", "wheel"]
+build-backend = "setuptools.build_meta"
--- /dev/null
+[metadata]
+license_file = LICENSE
+license = MIT
+name = setuptools_scm
+url = https://github.com/pypa/setuptools_scm/
+author = Ronny Pfannschmidt
+author_email = opensource@ronnypfannschmidt.de
+description = the blessed package to manage your versions by scm tags
+long_description = file:README.rst
+classifiers =
+ Development Status :: 5 - Production/Stable
+ Intended Audience :: Developers
+ License :: OSI Approved :: MIT License
+ Programming Language :: Python
+ Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.5
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
+ Topic :: Software Development :: Libraries
+ Topic :: Software Development :: Version Control
+ Topic :: System :: Software Distribution
+ Topic :: Utilities
+
+[options]
+zip_safe = true
+python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
+install_requires =
+ setuptools
+packages = find:
+package_dir =
+ =src
+
+[options.packages.find]
+where = src
+
+[options.extras_require]
+toml = toml
+
+[options.entry_points]
+distutils.setup_keywords =
+ use_scm_version = setuptools_scm.integration:version_keyword
+setuptools.file_finders =
+ setuptools_scm = setuptools_scm.integration:find_files
+setuptools.finalize_distribution_options =
+ setuptools_scm = setuptools_scm.integration:infer_version
+setuptools_scm.parse_scm =
+ .hg = setuptools_scm.hg:parse
+ .git = setuptools_scm.git:parse
+setuptools_scm.parse_scm_fallback =
+ .hg_archival.txt = setuptools_scm.hg:parse_archival
+ PKG-INFO = setuptools_scm.hacks:parse_pkginfo
+ pip-egg-info = setuptools_scm.hacks:parse_pip_egg_info
+ setup.py = setuptools_scm.hacks:fallback_version
+setuptools_scm.files_command =
+ .hg = setuptools_scm.file_finder_hg:hg_find_files
+ .git = setuptools_scm.file_finder_git:git_find_files
+setuptools_scm.version_scheme =
+ guess-next-dev = setuptools_scm.version:guess_next_dev_version
+ post-release = setuptools_scm.version:postrelease_version
+ python-simplified-semver = setuptools_scm.version:simplified_semver_version
+ release-branch-semver = setuptools_scm.version:release_branch_semver_version
+setuptools_scm.local_scheme =
+ node-and-date = setuptools_scm.version:get_local_node_and_date
+ node-and-timestamp = setuptools_scm.version:get_local_node_and_timestamp
+ dirty-tag = setuptools_scm.version:get_local_dirty_tag
+ no-local-version = setuptools_scm.version:get_no_local_node
+
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build =
+tag_date = 0
+
--- /dev/null
+"""\
+important note:
+
+the setup of setuptools_scm is self-using,
+the first execution of `python setup.py egg_info`
+will generate partial data
+its critical to run `python setup.py egg_info`
+once before running sdist or easy_install on a fresh checkouts
+
+pip usage is recommended
+"""
+from __future__ import print_function
+import os
+import sys
+import setuptools
+
+
+def scm_config():
+ here = os.path.dirname(os.path.abspath(__file__))
+ src = os.path.join(here, "src")
+ egg_info = os.path.join(src, "setuptools_scm.egg-info")
+ has_entrypoints = os.path.isdir(egg_info)
+ import pkg_resources
+
+ sys.path.insert(0, src)
+ pkg_resources.working_set.add_entry(src)
+ # FIXME: remove debug
+ print(src)
+ print(pkg_resources.working_set)
+ from setuptools_scm.hacks import parse_pkginfo
+ from setuptools_scm.git import parse as parse_git
+ from setuptools_scm.version import guess_next_dev_version, get_local_node_and_date
+
+ def parse(root):
+ try:
+ return parse_pkginfo(root)
+ except IOError:
+ return parse_git(root)
+
+ config = dict(
+ version_scheme=guess_next_dev_version, local_scheme=get_local_node_and_date
+ )
+
+ if has_entrypoints:
+ return dict(use_scm_version=config)
+ else:
+ from setuptools_scm import get_version
+
+ return dict(version=get_version(root=here, parse=parse, **config))
+
+
+if __name__ == "__main__":
+ setuptools.setup(**scm_config())
--- /dev/null
+Metadata-Version: 2.1
+Name: setuptools-scm
+Version: 4.1.2
+Summary: the blessed package to manage your versions by scm tags
+Home-page: https://github.com/pypa/setuptools_scm/
+Author: Ronny Pfannschmidt
+Author-email: opensource@ronnypfannschmidt.de
+License: MIT
+Description: setuptools_scm
+ ===============
+
+ ``setuptools_scm`` handles managing your Python package versions
+ in SCM metadata instead of declaring them as the version argument
+ or in a SCM managed file.
+
+ Additionally ``setuptools_scm`` provides setuptools with a list of files that are managed by the SCM
+ (i.e. it automatically adds all of the SCM-managed files to the sdist).
+ Unwanted files must be excluded by discarding them via ``MANIFEST.in``.
+
+ .. image:: https://travis-ci.org/pypa/setuptools_scm.svg?branch=master
+ :target: https://travis-ci.org/pypa/setuptools_scm
+
+ .. image:: https://tidelift.com/badges/package/pypi/setuptools-scm
+ :target: https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme
+
+
+ ``pyproject.toml`` usage
+ ------------------------
+
+ The preferred way to configure ``setuptools_scm`` is to author
+ settings in a ``tool.setuptools_scm`` section of ``pyproject.toml``.
+
+ This feature requires Setuptools 42 or later, released in Nov, 2019.
+ If your project needs to support build from sdist on older versions
+ of Setuptools, you will need to also implement the ``setup.py usage``
+ for those legacy environments.
+
+ First, ensure that ``setuptools_scm`` is present during the project's
+ built step by specifying it as one of the build requirements.
+
+ .. code:: toml
+
+ # pyproject.toml
+ [build-system]
+ requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+
+ Note that the ``toml`` extra must be supplied.
+
+ That will be sufficient to require ``setuptools_scm`` for projects
+ that support PEP 518 (`pip <https://pypi.org/project/pip>`_ and
+ `pep517 <https://pypi.org/project/pep517/>`_). Many tools,
+ especially those that invoke ``setup.py`` for any reason, may
+ continue to rely on ``setup_requires``. For maximum compatibility
+ with those uses, consider also including a ``setup_requires`` directive
+ (described below in ``setup.py usage`` and ``setup.cfg``).
+
+ To enable version inference, add this section to your pyproject.toml:
+
+ .. code:: toml
+
+ # pyproject.toml
+ [tool.setuptools_scm]
+
+ Including this section is comparable to supplying
+ ``use_scm_version=True`` in ``setup.py``. Additionally,
+ include arbitrary keyword arguments in that section
+ to be supplied to ``get_version()``. For example:
+
+ .. code:: toml
+
+ # pyproject.toml
+
+ [tool.setuptools_scm]
+ write_to = "pkg/version.py"
+
+
+ ``setup.py`` usage
+ ------------------
+
+ The following settings are considered legacy behavior and
+ superseded by the ``pyproject.toml`` usage, but for maximal
+ compatibility, projects may also supply the configuration in
+ this older form.
+
+ To use ``setuptools_scm`` just modify your project's ``setup.py`` file
+ like this:
+
+ * Add ``setuptools_scm`` to the ``setup_requires`` parameter.
+ * Add the ``use_scm_version`` parameter and set it to ``True``.
+
+ For example:
+
+ .. code:: python
+
+ from setuptools import setup
+ setup(
+ ...,
+ use_scm_version=True,
+ setup_requires=['setuptools_scm'],
+ ...,
+ )
+
+ Arguments to ``get_version()`` (see below) may be passed as a dictionary to
+ ``use_scm_version``. For example:
+
+ .. code:: python
+
+ from setuptools import setup
+ setup(
+ ...,
+ use_scm_version = {
+ "root": "..",
+ "relative_to": __file__,
+ "local_scheme": "node-and-timestamp"
+ },
+ setup_requires=['setuptools_scm'],
+ ...,
+ )
+
+ You can confirm the version number locally via ``setup.py``:
+
+ .. code-block:: shell
+
+ $ python setup.py --version
+
+ .. note::
+
+ If you see unusual version numbers for packages but ``python setup.py
+ --version`` reports the expected version number, ensure ``[egg_info]`` is
+ not defined in ``setup.cfg``.
+
+
+ ``setup.cfg`` usage
+ -------------------
+
+ If using `setuptools 30.3.0
+ <https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files>`_
+ or greater, you can store ``setup_requires`` configuration in ``setup.cfg``.
+ However, ``use_scm_version`` must still be placed in ``setup.py``. For example:
+
+ .. code:: python
+
+ # setup.py
+ from setuptools import setup
+ setup(
+ use_scm_version=True,
+ )
+
+ .. code:: ini
+
+ # setup.cfg
+ [metadata]
+ ...
+
+ [options]
+ setup_requires =
+ setuptools_scm
+ ...
+
+ .. important::
+
+ Ensure neither the ``[metadata]`` ``version`` option nor the ``[egg_info]``
+ section are defined, as these will interfere with ``setuptools_scm``.
+
+ You may also need to define a ``pyproject.toml`` file (`PEP-0518
+ <https://www.python.org/dev/peps/pep-0518>`_) to ensure you have the required
+ version of ``setuptools``:
+
+ .. code:: ini
+
+ # pyproject.toml
+ [build-system]
+ requires = ["setuptools>=30.3.0", "wheel", "setuptools_scm"]
+
+ For more information, refer to the `setuptools issue #1002
+ <https://github.com/pypa/setuptools/issues/1002>`_.
+
+
+ Programmatic usage
+ ------------------
+
+ In order to use ``setuptools_scm`` from code that is one directory deeper
+ than the project's root, you can use:
+
+ .. code:: python
+
+ from setuptools_scm import get_version
+ version = get_version(root='..', relative_to=__file__)
+
+ See `setup.py Usage`_ above for how to use this within ``setup.py``.
+
+
+ Retrieving package version at runtime
+ -------------------------------------
+
+ If you have opted not to hardcode the version number inside the package,
+ you can retrieve it at runtime from PEP-0566_ metadata using
+ ``importlib.metadata`` from the standard library
+ or the `importlib_metadata`_ backport:
+
+ .. code:: python
+
+ from importlib.metadata import version, PackageNotFoundError
+
+ try:
+ __version__ = version(__name__)
+ except PackageNotFoundError:
+ # package is not installed
+ pass
+
+ Alternatively, you can use ``pkg_resources`` which is included in
+ ``setuptools``:
+
+ .. code:: python
+
+ from pkg_resources import get_distribution, DistributionNotFound
+
+ try:
+ __version__ = get_distribution(__name__).version
+ except DistributionNotFound:
+ # package is not installed
+ pass
+
+ This does place a runtime dependency on ``setuptools``.
+
+ .. _PEP-0566: https://www.python.org/dev/peps/pep-0566/
+ .. _importlib_metadata: https://pypi.org/project/importlib-metadata/
+
+
+ Usage from Sphinx
+ -----------------
+
+ It is discouraged to use ``setuptools_scm`` from Sphinx itself,
+ instead use ``pkg_resources`` after editable/real installation:
+
+ .. code:: python
+
+ # contents of docs/conf.py
+ from pkg_resources import get_distribution
+ release = get_distribution('myproject').version
+ # for example take major/minor
+ version = '.'.join(release.split('.')[:2])
+
+ The underlying reason is, that services like *Read the Docs* sometimes change
+ the working directory for good reasons and using the installed metadata
+ prevents using needless volatile data there.
+
+ Notable Plugins
+ ----------------
+
+ `setuptools_scm_git_archive <https://pypi.python.org/pypi/setuptools_scm_git_archive>`_
+ Provides partial support for obtaining versions from git archives that
+ belong to tagged versions. The only reason for not including it in
+ ``setuptools_scm`` itself is Git/GitHub not supporting sufficient metadata
+ for untagged/followup commits, which is preventing a consistent UX.
+
+
+ Default versioning scheme
+ --------------------------
+
+ In the standard configuration ``setuptools_scm`` takes a look at three things:
+
+ 1. latest tag (with a version number)
+ 2. the distance to this tag (e.g. number of revisions since latest tag)
+ 3. workdir state (e.g. uncommitted changes since latest tag)
+
+ and uses roughly the following logic to render the version:
+
+ no distance and clean:
+ ``{tag}``
+ distance and clean:
+ ``{next_version}.dev{distance}+{scm letter}{revision hash}``
+ no distance and not clean:
+ ``{tag}+dYYYYMMDD``
+ distance and not clean:
+ ``{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD``
+
+ The next version is calculated by adding ``1`` to the last numeric component of
+ the tag.
+
+ For Git projects, the version relies on `git describe <https://git-scm.com/docs/git-describe>`_,
+ so you will see an additional ``g`` prepended to the ``{revision hash}``.
+
+ Semantic Versioning (SemVer)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Due to the default behavior it's necessary to always include a
+ patch version (the ``3`` in ``1.2.3``), or else the automatic guessing
+ will increment the wrong part of the SemVer (e.g. tag ``2.0`` results in
+ ``2.1.devX`` instead of ``2.0.1.devX``). So please make sure to tag
+ accordingly.
+
+ .. note::
+
+ Future versions of ``setuptools_scm`` will switch to `SemVer
+ <http://semver.org/>`_ by default hiding the the old behavior as an
+ configurable option.
+
+
+ Builtin mechanisms for obtaining version numbers
+ --------------------------------------------------
+
+ 1. the SCM itself (git/hg)
+ 2. ``.hg_archival`` files (mercurial archives)
+ 3. ``PKG-INFO``
+
+ .. note::
+
+ Git archives are not supported due to Git shortcomings
+
+
+ File finders hook makes most of MANIFEST.in unnecessary
+ -------------------------------------------------------
+
+ ``setuptools_scm`` implements a `file_finders
+ <https://setuptools.readthedocs.io/en/latest/setuptools.html#adding-support-for-revision-control-systems>`_
+ entry point which returns all files tracked by your SCM. This eliminates
+ the need for a manually constructed ``MANIFEST.in`` in most cases where this
+ would be required when not using ``setuptools_scm``, namely:
+
+ * To ensure all relevant files are packaged when running the ``sdist`` command.
+
+ * When using `include_package_data <https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files>`_
+ to include package data as part of the ``build`` or ``bdist_wheel``.
+
+ ``MANIFEST.in`` may still be used: anything defined there overrides the hook.
+ This is mostly useful to exclude files tracked in your SCM from packages,
+ although in principle it can be used to explicitly include non-tracked files
+ too.
+
+
+ Configuration parameters
+ ------------------------
+
+ In order to configure the way ``use_scm_version`` works you can provide
+ a mapping with options instead of a boolean value.
+
+ The currently supported configuration keys are:
+
+ :root:
+ Relative path to cwd, used for finding the SCM root; defaults to ``.``
+
+ :version_scheme:
+ Configures how the local version number is constructed; either an
+ entrypoint name or a callable.
+
+ :local_scheme:
+ Configures how the local component of the version is constructed; either an
+ entrypoint name or a callable.
+
+ :write_to:
+ A path to a file that gets replaced with a file containing the current
+ version. It is ideal for creating a ``version.py`` file within the
+ package, typically used to avoid using `pkg_resources.get_distribution`
+ (which adds some overhead).
+
+ .. warning::
+
+ Only files with :code:`.py` and :code:`.txt` extensions have builtin
+ templates, for other file types it is necessary to provide
+ :code:`write_to_template`.
+
+ :write_to_template:
+ A newstyle format string that is given the current version as
+ the ``version`` keyword argument for formatting.
+
+ :relative_to:
+ A file from which the root can be resolved.
+ Typically called by a script or module that is not in the root of the
+ repository to point ``setuptools_scm`` at the root of the repository by
+ supplying ``__file__``.
+
+ :tag_regex:
+ A Python regex string to extract the version part from any SCM tag.
+ The regex needs to contain either a single match group, or a group
+ named ``version``, that captures the actual version information.
+
+ Defaults to the value of ``setuptools_scm.config.DEFAULT_TAG_REGEX``
+ (see `config.py <src/setuptools_scm/config.py>`_).
+
+ :parentdir_prefix_version:
+ If the normal methods for detecting the version (SCM version,
+ sdist metadata) fail, and the parent directory name starts with
+ ``parentdir_prefix_version``, then this prefix is stripped and the rest of
+ the parent directory name is matched with ``tag_regex`` to get a version
+ string. If this parameter is unset (the default), then this fallback is
+ not used.
+
+ This is intended to cover GitHub's "release tarballs", which extract into
+ directories named ``projectname-tag/`` (in which case
+ ``parentdir_prefix_version`` can be set e.g. to ``projectname-``).
+
+ :fallback_version:
+ A version string that will be used if no other method for detecting the
+ version worked (e.g., when using a tarball with no metadata). If this is
+ unset (the default), setuptools_scm will error if it fails to detect the
+ version.
+
+ :parse:
+ A function that will be used instead of the discovered SCM for parsing the
+ version.
+ Use with caution, this is a function for advanced use, and you should be
+ familiar with the ``setuptools_scm`` internals to use it.
+
+ :git_describe_command:
+ This command will be used instead the default ``git describe`` command.
+ Use with caution, this is a function for advanced use, and you should be
+ familiar with the ``setuptools_scm`` internals to use it.
+
+ Defaults to the value set by ``setuptools_scm.git.DEFAULT_DESCRIBE``
+ (see `git.py <src/setuptools_scm/git.py>`_).
+
+ To use ``setuptools_scm`` in other Python code you can use the ``get_version``
+ function:
+
+ .. code:: python
+
+ from setuptools_scm import get_version
+ my_version = get_version()
+
+ It optionally accepts the keys of the ``use_scm_version`` parameter as
+ keyword arguments.
+
+ Example configuration in ``setup.py`` format:
+
+ .. code:: python
+
+ from setuptools import setup
+
+ setup(
+ use_scm_version={
+ 'write_to': 'version.py',
+ 'write_to_template': '__version__ = "{version}"',
+ 'tag_regex': r'^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$',
+ }
+ )
+
+ Environment variables
+ ---------------------
+
+ :SETUPTOOLS_SCM_PRETEND_VERSION:
+ when defined and not empty,
+ its used as the primary source for the version number
+ in which case it will be a unparsed string
+
+ :SETUPTOOLS_SCM_DEBUG:
+ when defined and not empty,
+ a lot of debug information will be printed as part of ``setuptools_scm``
+ operating
+
+ Extending setuptools_scm
+ ------------------------
+
+ ``setuptools_scm`` ships with a few ``setuptools`` entrypoints based hooks to
+ extend its default capabilities.
+
+ Adding a new SCM
+ ~~~~~~~~~~~~~~~~
+
+ ``setuptools_scm`` provides two entrypoints for adding new SCMs:
+
+ ``setuptools_scm.parse_scm``
+ A function used to parse the metadata of the current workdir
+ using the name of the control directory/file of your SCM as the
+ entrypoint's name. E.g. for the built-in entrypoint for git the
+ entrypoint is named ``.git`` and references ``setuptools_scm.git:parse``
+
+ The return value MUST be a ``setuptools_scm.version.ScmVersion`` instance
+ created by the function ``setuptools_scm.version:meta``.
+
+ ``setuptools_scm.files_command``
+ Either a string containing a shell command that prints all SCM managed
+ files in its current working directory or a callable, that given a
+ pathname will return that list.
+
+ Also use then name of your SCM control directory as name of the entrypoint.
+
+ Version number construction
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ ``setuptools_scm.version_scheme``
+ Configures how the version number is constructed given a
+ ``setuptools_scm.version.ScmVersion`` instance and should return a string
+ representing the version.
+
+ Available implementations:
+
+ :guess-next-dev: Automatically guesses the next development version (default).
+ Guesses the upcoming release by incrementing the pre-release segment if present,
+ otherwise by incrementing the micro segment. Then appends :code:`.devN`.
+ :post-release: generates post release versions (adds :code:`.postN`)
+ :python-simplified-semver: Basic semantic versioning. Guesses the upcoming release
+ by incrementing the minor segment and setting the micro segment to zero if the
+ current branch contains the string ``'feature'``, otherwise by incrementing the
+ micro version. Then appends :code:`.devN`. Not compatible with pre-releases.
+ :release-branch-semver: Semantic versioning for projects with release branches. The
+ same as ``guess-next-dev`` (incrementing the pre-release or micro segment) if on
+ a release branch: a branch whose name (ignoring namespace) parses as a version
+ that matches the most recent tag up to the minor segment. Otherwise if on a
+ non-release branch, increments the minor segment and sets the micro segment to
+ zero, then appends :code:`.devN`.
+
+ ``setuptools_scm.local_scheme``
+ Configures how the local part of a version is rendered given a
+ ``setuptools_scm.version.ScmVersion`` instance and should return a string
+ representing the local version.
+ Dates and times are in Coordinated Universal Time (UTC), because as part
+ of the version, they should be location independent.
+
+ Available implementations:
+
+ :node-and-date: adds the node on dev versions and the date on dirty
+ workdir (default)
+ :node-and-timestamp: like ``node-and-date`` but with a timestamp of
+ the form ``{:%Y%m%d%H%M%S}`` instead
+ :dirty-tag: adds ``+dirty`` if the current workdir has changes
+ :no-local-version: omits local version, useful e.g. because pypi does
+ not support it
+
+
+ Importing in ``setup.py``
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ To support usage in ``setup.py`` passing a callable into ``use_scm_version``
+ is supported.
+
+ Within that callable, ``setuptools_scm`` is available for import.
+ The callable must return the configuration.
+
+
+ .. code:: python
+
+ # content of setup.py
+ import setuptools
+
+ def myversion():
+ from setuptools_scm.version import get_local_dirty_tag
+ def clean_scheme(version):
+ return get_local_dirty_tag(version) if version.dirty else '+clean'
+
+ return {'local_scheme': clean_scheme}
+
+ setup(
+ ...,
+ use_scm_version=myversion,
+ ...
+ )
+
+
+ Note on testing non-installed versions
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ While the general advice is to test against a installed version,
+ some environments require a test prior to install,
+
+ .. code::
+
+ $ python setup.py egg_info
+ $ PYTHONPATH=$PWD:$PWD/src pytest
+
+
+ Interaction with Enterprise Distributions
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Some enterprise distributions like RHEL7 and others
+ ship rather old setuptools versions due to various release management details.
+
+ On such distributions one might observe errors like:
+
+ :code:``setuptools_scm.version.SetuptoolsOutdatedWarning: your setuptools is too old (<12)``
+
+ In those case its typically possible to build by using a sdist against ``setuptools_scm<2.0``.
+ As those old setuptools versions lack sensible types for versions,
+ modern setuptools_scm is unable to support them sensibly.
+
+ In case the project you need to build can not be patched to either use old setuptools_scm,
+ its still possible to install a more recent version of setuptools in order to handle the build
+ and/or install the package by using wheels or eggs.
+
+
+
+
+ Code of Conduct
+ ---------------
+
+ Everyone interacting in the ``setuptools_scm`` project's codebases, issue
+ trackers, chat rooms, and mailing lists is expected to follow the
+ `PyPA Code of Conduct`_.
+
+ .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
+
+ Security Contact
+ ================
+
+ To report a security vulnerability, please use the
+ `Tidelift security contact <https://tidelift.com/security>`_.
+ Tidelift will coordinate the fix and disclosure.
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Version Control
+Classifier: Topic :: System :: Software Distribution
+Classifier: Topic :: Utilities
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
+Provides-Extra: toml
--- /dev/null
+.gitignore
+CHANGELOG.rst
+LICENSE
+MANIFEST.in
+README.rst
+pyproject.toml
+setup.cfg
+setup.py
+tox.ini
+.github/FUNDING.yml
+.github/workflows/pre-commit.yml
+.github/workflows/python-tests.yml
+src/setuptools_scm/__init__.py
+src/setuptools_scm/__main__.py
+src/setuptools_scm/config.py
+src/setuptools_scm/discover.py
+src/setuptools_scm/file_finder.py
+src/setuptools_scm/file_finder_git.py
+src/setuptools_scm/file_finder_hg.py
+src/setuptools_scm/git.py
+src/setuptools_scm/hacks.py
+src/setuptools_scm/hg.py
+src/setuptools_scm/integration.py
+src/setuptools_scm/utils.py
+src/setuptools_scm/version.py
+src/setuptools_scm/win_py31_compat.py
+src/setuptools_scm.egg-info/PKG-INFO
+src/setuptools_scm.egg-info/SOURCES.txt
+src/setuptools_scm.egg-info/dependency_links.txt
+src/setuptools_scm.egg-info/entry_points.txt
+src/setuptools_scm.egg-info/requires.txt
+src/setuptools_scm.egg-info/top_level.txt
+src/setuptools_scm.egg-info/zip-safe
+testing/check_self_install.py
+testing/conftest.py
+testing/play_out_381.bash
+testing/test_basic_api.py
+testing/test_config.py
+testing/test_file_finder.py
+testing/test_functions.py
+testing/test_git.py
+testing/test_integration.py
+testing/test_main.py
+testing/test_mercurial.py
+testing/test_regressions.py
+testing/test_setuptools_support.py
+testing/test_version.py
\ No newline at end of file
--- /dev/null
+[distutils.setup_keywords]
+use_scm_version = setuptools_scm.integration:version_keyword
+
+[setuptools.file_finders]
+setuptools_scm = setuptools_scm.integration:find_files
+
+[setuptools.finalize_distribution_options]
+setuptools_scm = setuptools_scm.integration:infer_version
+
+[setuptools_scm.files_command]
+.git = setuptools_scm.file_finder_git:git_find_files
+.hg = setuptools_scm.file_finder_hg:hg_find_files
+
+[setuptools_scm.local_scheme]
+dirty-tag = setuptools_scm.version:get_local_dirty_tag
+no-local-version = setuptools_scm.version:get_no_local_node
+node-and-date = setuptools_scm.version:get_local_node_and_date
+node-and-timestamp = setuptools_scm.version:get_local_node_and_timestamp
+
+[setuptools_scm.parse_scm]
+.git = setuptools_scm.git:parse
+.hg = setuptools_scm.hg:parse
+
+[setuptools_scm.parse_scm_fallback]
+.hg_archival.txt = setuptools_scm.hg:parse_archival
+PKG-INFO = setuptools_scm.hacks:parse_pkginfo
+pip-egg-info = setuptools_scm.hacks:parse_pip_egg_info
+setup.py = setuptools_scm.hacks:fallback_version
+
+[setuptools_scm.version_scheme]
+guess-next-dev = setuptools_scm.version:guess_next_dev_version
+post-release = setuptools_scm.version:postrelease_version
+python-simplified-semver = setuptools_scm.version:simplified_semver_version
+release-branch-semver = setuptools_scm.version:release_branch_semver_version
+
--- /dev/null
+setuptools
+
+[toml]
+toml
--- /dev/null
+setuptools_scm
--- /dev/null
+"""
+:copyright: 2010-2015 by Ronny Pfannschmidt
+:license: MIT
+"""
+import os
+import warnings
+
+from .config import (
+ Configuration,
+ DEFAULT_VERSION_SCHEME,
+ DEFAULT_LOCAL_SCHEME,
+ DEFAULT_TAG_REGEX,
+)
+from .utils import function_has_arg, string_types
+from .version import format_version, meta
+from .discover import iter_matching_entrypoints
+
+PRETEND_KEY = "SETUPTOOLS_SCM_PRETEND_VERSION"
+
+TEMPLATES = {
+ ".py": """\
+# coding: utf-8
+# file generated by setuptools_scm
+# don't change, don't track in version control
+version = {version!r}
+""",
+ ".txt": "{version}",
+}
+
+
+def version_from_scm(root):
+ warnings.warn(
+ "version_from_scm is deprecated please use get_version",
+ category=DeprecationWarning,
+ )
+ config = Configuration()
+ config.root = root
+ # TODO: Is it API?
+ return _version_from_entrypoints(config)
+
+
+def _call_entrypoint_fn(root, config, fn):
+ if function_has_arg(fn, "config"):
+ return fn(root, config=config)
+ else:
+ warnings.warn(
+ "parse functions are required to provide a named argument"
+ " 'config' in the future.",
+ category=PendingDeprecationWarning,
+ stacklevel=2,
+ )
+ return fn(root)
+
+
+def _version_from_entrypoints(config, fallback=False):
+ if fallback:
+ entrypoint = "setuptools_scm.parse_scm_fallback"
+ root = config.fallback_root
+ else:
+ entrypoint = "setuptools_scm.parse_scm"
+ root = config.absolute_root
+ for ep in iter_matching_entrypoints(root, entrypoint):
+ version = _call_entrypoint_fn(root, config, ep.load())
+
+ if version:
+ return version
+
+
+def dump_version(root, version, write_to, template=None):
+ assert isinstance(version, string_types)
+ if not write_to:
+ return
+ target = os.path.normpath(os.path.join(root, write_to))
+ ext = os.path.splitext(target)[1]
+ template = template or TEMPLATES.get(ext)
+
+ if template is None:
+ raise ValueError(
+ "bad file format: '{}' (of {}) \nonly *.txt and *.py are supported".format(
+ os.path.splitext(target)[1], target
+ )
+ )
+ with open(target, "w") as fp:
+ fp.write(template.format(version=version))
+
+
+def _do_parse(config):
+ pretended = os.environ.get(PRETEND_KEY)
+ if pretended:
+ # we use meta here since the pretended version
+ # must adhere to the pep to begin with
+ return meta(tag=pretended, preformatted=True, config=config)
+
+ if config.parse:
+ parse_result = _call_entrypoint_fn(config.absolute_root, config, config.parse)
+ if isinstance(parse_result, string_types):
+ raise TypeError(
+ "version parse result was a string\nplease return a parsed version"
+ )
+ version = parse_result or _version_from_entrypoints(config, fallback=True)
+ else:
+ # include fallbacks after dropping them from the main entrypoint
+ version = _version_from_entrypoints(config) or _version_from_entrypoints(
+ config, fallback=True
+ )
+
+ if version:
+ return version
+
+ raise LookupError(
+ "setuptools-scm was unable to detect version for %r.\n\n"
+ "Make sure you're either building from a fully intact git repository "
+ "or PyPI tarballs. Most other sources (such as GitHub's tarballs, a "
+ "git checkout without the .git folder) don't contain the necessary "
+ "metadata and will not work.\n\n"
+ "For example, if you're using pip, instead of "
+ "https://github.com/user/proj/archive/master.zip "
+ "use git+https://github.com/user/proj.git#egg=proj" % config.absolute_root
+ )
+
+
+def get_version(
+ root=".",
+ version_scheme=DEFAULT_VERSION_SCHEME,
+ local_scheme=DEFAULT_LOCAL_SCHEME,
+ write_to=None,
+ write_to_template=None,
+ relative_to=None,
+ tag_regex=DEFAULT_TAG_REGEX,
+ parentdir_prefix_version=None,
+ fallback_version=None,
+ fallback_root=".",
+ parse=None,
+ git_describe_command=None,
+):
+ """
+ If supplied, relative_to should be a file from which root may
+ be resolved. Typically called by a script or module that is not
+ in the root of the repository to direct setuptools_scm to the
+ root of the repository by supplying ``__file__``.
+ """
+
+ config = Configuration(**locals())
+ return _get_version(config)
+
+
+def _get_version(config):
+ parsed_version = _do_parse(config)
+
+ if parsed_version:
+ version_string = format_version(
+ parsed_version,
+ version_scheme=config.version_scheme,
+ local_scheme=config.local_scheme,
+ )
+ dump_version(
+ root=config.root,
+ version=version_string,
+ write_to=config.write_to,
+ template=config.write_to_template,
+ )
+
+ return version_string
--- /dev/null
+from __future__ import print_function
+import sys
+from setuptools_scm import get_version
+from setuptools_scm.integration import find_files
+from setuptools_scm.version import _warn_if_setuptools_outdated
+
+
+def main():
+ _warn_if_setuptools_outdated()
+ print("Guessed Version", get_version())
+ if "ls" in sys.argv:
+ for fname in find_files("."):
+ print(fname)
+
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+""" configuration """
+from __future__ import print_function, unicode_literals
+import os
+import re
+import warnings
+
+from .utils import trace
+
+DEFAULT_TAG_REGEX = r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$"
+DEFAULT_VERSION_SCHEME = "guess-next-dev"
+DEFAULT_LOCAL_SCHEME = "node-and-date"
+
+
+def _check_tag_regex(value):
+ if not value:
+ value = DEFAULT_TAG_REGEX
+ regex = re.compile(value)
+
+ group_names = regex.groupindex.keys()
+ if regex.groups == 0 or (regex.groups > 1 and "version" not in group_names):
+ warnings.warn(
+ "Expected tag_regex to contain a single match group or a group named"
+ " 'version' to identify the version part of any tag."
+ )
+
+ return regex
+
+
+def _check_absolute_root(root, relative_to):
+ if relative_to:
+ if os.path.isabs(root) and not root.startswith(relative_to):
+ warnings.warn(
+ "absolute root path '%s' overrides relative_to '%s'"
+ % (root, relative_to)
+ )
+ root = os.path.join(os.path.dirname(relative_to), root)
+ return os.path.abspath(root)
+
+
+class Configuration(object):
+ """ Global configuration model """
+
+ def __init__(
+ self,
+ relative_to=None,
+ root=".",
+ version_scheme=DEFAULT_VERSION_SCHEME,
+ local_scheme=DEFAULT_LOCAL_SCHEME,
+ write_to=None,
+ write_to_template=None,
+ tag_regex=DEFAULT_TAG_REGEX,
+ parentdir_prefix_version=None,
+ fallback_version=None,
+ fallback_root=".",
+ parse=None,
+ git_describe_command=None,
+ ):
+ # TODO:
+ self._relative_to = relative_to
+ self._root = "."
+
+ self.root = root
+ self.version_scheme = version_scheme
+ self.local_scheme = local_scheme
+ self.write_to = write_to
+ self.write_to_template = write_to_template
+ self.parentdir_prefix_version = parentdir_prefix_version
+ self.fallback_version = fallback_version
+ self.fallback_root = fallback_root
+ self.parse = parse
+ self.tag_regex = tag_regex
+ self.git_describe_command = git_describe_command
+
+ @property
+ def fallback_root(self):
+ return self._fallback_root
+
+ @fallback_root.setter
+ def fallback_root(self, value):
+ self._fallback_root = os.path.abspath(value)
+
+ @property
+ def absolute_root(self):
+ return self._absolute_root
+
+ @property
+ def relative_to(self):
+ return self._relative_to
+
+ @relative_to.setter
+ def relative_to(self, value):
+ self._absolute_root = _check_absolute_root(self._root, value)
+ self._relative_to = value
+ trace("root", repr(self._absolute_root))
+
+ @property
+ def root(self):
+ return self._root
+
+ @root.setter
+ def root(self, value):
+ self._absolute_root = _check_absolute_root(value, self._relative_to)
+ self._root = value
+ trace("root", repr(self._absolute_root))
+
+ @property
+ def tag_regex(self):
+ return self._tag_regex
+
+ @tag_regex.setter
+ def tag_regex(self, value):
+ self._tag_regex = _check_tag_regex(value)
+
+ @classmethod
+ def from_file(cls, name="pyproject.toml"):
+ """
+ Read Configuration from pyproject.toml (or similar).
+ Raises exceptions when file is not found or toml is
+ not installed or the file has invalid format or does
+ not contain the [tool.setuptools_scm] section.
+ """
+ with open(name) as strm:
+ defn = __import__("toml").load(strm)
+ section = defn.get("tool", {})["setuptools_scm"]
+ return cls(**section)
--- /dev/null
+import os
+from pkg_resources import iter_entry_points
+from .utils import trace
+
+
+def iter_matching_entrypoints(path, entrypoint):
+ trace("looking for ep", entrypoint, path)
+ for ep in iter_entry_points(entrypoint):
+ if os.path.exists(os.path.join(path, ep.name)):
+ if os.path.isabs(ep.name):
+ trace("ignoring bad ep", ep)
+ trace("found ep", ep)
+ yield ep
--- /dev/null
+import os
+
+
+def scm_find_files(path, scm_files, scm_dirs):
+ """ setuptools compatible file finder that follows symlinks
+
+ - path: the root directory from which to search
+ - scm_files: set of scm controlled files and symlinks
+ (including symlinks to directories)
+ - scm_dirs: set of scm controlled directories
+ (including directories containing no scm controlled files)
+
+ scm_files and scm_dirs must be absolute with symlinks resolved (realpath),
+ with normalized case (normcase)
+
+ Spec here: http://setuptools.readthedocs.io/en/latest/setuptools.html#\
+ adding-support-for-revision-control-systems
+ """
+ realpath = os.path.normcase(os.path.realpath(path))
+ seen = set()
+ res = []
+ for dirpath, dirnames, filenames in os.walk(realpath, followlinks=True):
+ # dirpath with symlinks resolved
+ realdirpath = os.path.normcase(os.path.realpath(dirpath))
+
+ def _link_not_in_scm(n):
+ fn = os.path.join(realdirpath, os.path.normcase(n))
+ return os.path.islink(fn) and fn not in scm_files
+
+ if realdirpath not in scm_dirs:
+ # directory not in scm, don't walk it's content
+ dirnames[:] = []
+ continue
+ if os.path.islink(dirpath) and not os.path.relpath(
+ realdirpath, realpath
+ ).startswith(os.pardir):
+ # a symlink to a directory not outside path:
+ # we keep it in the result and don't walk its content
+ res.append(os.path.join(path, os.path.relpath(dirpath, path)))
+ dirnames[:] = []
+ continue
+ if realdirpath in seen:
+ # symlink loop protection
+ dirnames[:] = []
+ continue
+ dirnames[:] = [dn for dn in dirnames if not _link_not_in_scm(dn)]
+ for filename in filenames:
+ if _link_not_in_scm(filename):
+ continue
+ # dirpath + filename with symlinks preserved
+ fullfilename = os.path.join(dirpath, filename)
+ if os.path.normcase(os.path.realpath(fullfilename)) in scm_files:
+ res.append(os.path.join(path, os.path.relpath(fullfilename, realpath)))
+ seen.add(realdirpath)
+ return res
--- /dev/null
+import os
+import subprocess
+import tarfile
+import logging
+from .file_finder import scm_find_files
+from .utils import trace
+
+log = logging.getLogger(__name__)
+
+
+def _git_toplevel(path):
+ try:
+ with open(os.devnull, "wb") as devnull:
+ out = subprocess.check_output(
+ ["git", "rev-parse", "--show-toplevel"],
+ cwd=(path or "."),
+ universal_newlines=True,
+ stderr=devnull,
+ )
+ trace("find files toplevel", out)
+ return os.path.normcase(os.path.realpath(out.strip()))
+ except subprocess.CalledProcessError:
+ # git returned error, we are not in a git repo
+ return None
+ except OSError:
+ # git command not found, probably
+ return None
+
+
+def _git_interpret_archive(fd, toplevel):
+ with tarfile.open(fileobj=fd, mode="r|*") as tf:
+ git_files = set()
+ git_dirs = {toplevel}
+ for member in tf.getmembers():
+ name = os.path.normcase(member.name).replace("/", os.path.sep)
+ if member.type == tarfile.DIRTYPE:
+ git_dirs.add(name)
+ else:
+ git_files.add(name)
+ return git_files, git_dirs
+
+
+def _git_ls_files_and_dirs(toplevel):
+ # use git archive instead of git ls-file to honor
+ # export-ignore git attribute
+ cmd = ["git", "archive", "--prefix", toplevel + os.path.sep, "HEAD"]
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=toplevel)
+ try:
+ try:
+ return _git_interpret_archive(proc.stdout, toplevel)
+ finally:
+ # ensure we avoid resource warnings by cleaning up the process
+ proc.stdout.close()
+ proc.terminate()
+ except Exception:
+ if proc.wait() != 0:
+ log.exception("listing git files failed - pretending there aren't any")
+ return (), ()
+
+
+def git_find_files(path=""):
+ toplevel = _git_toplevel(path)
+ if not toplevel:
+ return []
+ fullpath = os.path.abspath(os.path.normpath(path))
+ if not fullpath.startswith(toplevel):
+ trace("toplevel mismatch", toplevel, fullpath)
+ git_files, git_dirs = _git_ls_files_and_dirs(toplevel)
+ return scm_find_files(path, git_files, git_dirs)
--- /dev/null
+import os
+import subprocess
+
+from .file_finder import scm_find_files
+
+
+def _hg_toplevel(path):
+ try:
+ with open(os.devnull, "wb") as devnull:
+ out = subprocess.check_output(
+ ["hg", "root"],
+ cwd=(path or "."),
+ universal_newlines=True,
+ stderr=devnull,
+ )
+ return os.path.normcase(os.path.realpath(out.strip()))
+ except subprocess.CalledProcessError:
+ # hg returned error, we are not in a mercurial repo
+ return None
+ except OSError:
+ # hg command not found, probably
+ return None
+
+
+def _hg_ls_files_and_dirs(toplevel):
+ hg_files = set()
+ hg_dirs = {toplevel}
+ out = subprocess.check_output(
+ ["hg", "files"], cwd=toplevel, universal_newlines=True
+ )
+ for name in out.splitlines():
+ name = os.path.normcase(name).replace("/", os.path.sep)
+ fullname = os.path.join(toplevel, name)
+ hg_files.add(fullname)
+ dirname = os.path.dirname(fullname)
+ while len(dirname) > len(toplevel) and dirname not in hg_dirs:
+ hg_dirs.add(dirname)
+ dirname = os.path.dirname(dirname)
+ return hg_files, hg_dirs
+
+
+def hg_find_files(path=""):
+ toplevel = _hg_toplevel(path)
+ if not toplevel:
+ return []
+ hg_files, hg_dirs = _hg_ls_files_and_dirs(toplevel)
+ return scm_find_files(path, hg_files, hg_dirs)
--- /dev/null
+from .config import Configuration
+from .utils import do_ex, trace, has_command
+from .version import meta
+
+from os.path import isfile, join
+import warnings
+
+
+try:
+ from os.path import samefile
+except ImportError:
+ from .win_py31_compat import samefile
+
+
+DEFAULT_DESCRIBE = "git describe --dirty --tags --long --match *.*"
+
+
+class GitWorkdir(object):
+ """experimental, may change at any time"""
+
+ def __init__(self, path):
+ self.path = path
+
+ def do_ex(self, cmd):
+ return do_ex(cmd, cwd=self.path)
+
+ @classmethod
+ def from_potential_worktree(cls, wd):
+ real_wd, _, ret = do_ex("git rev-parse --show-toplevel", wd)
+ if ret:
+ return
+ trace("real root", real_wd)
+ if not samefile(real_wd, wd):
+ return
+
+ return cls(real_wd)
+
+ def is_dirty(self):
+ out, _, _ = self.do_ex("git status --porcelain --untracked-files=no")
+ return bool(out)
+
+ def get_branch(self):
+ branch, err, ret = self.do_ex("git rev-parse --abbrev-ref HEAD")
+ if ret:
+ trace("branch err", branch, err, ret)
+ return
+ return branch
+
+ def is_shallow(self):
+ return isfile(join(self.path, ".git/shallow"))
+
+ def fetch_shallow(self):
+ self.do_ex("git fetch --unshallow")
+
+ def node(self):
+ rev_node, _, ret = self.do_ex("git rev-parse --verify --quiet HEAD")
+ if not ret:
+ return rev_node[:7]
+
+ def count_all_nodes(self):
+ revs, _, _ = self.do_ex("git rev-list HEAD")
+ return revs.count("\n") + 1
+
+
+def warn_on_shallow(wd):
+ """experimental, may change at any time"""
+ if wd.is_shallow():
+ warnings.warn('"{}" is shallow and may cause errors'.format(wd.path))
+
+
+def fetch_on_shallow(wd):
+ """experimental, may change at any time"""
+ if wd.is_shallow():
+ warnings.warn('"%s" was shallow, git fetch was used to rectify')
+ wd.fetch_shallow()
+
+
+def fail_on_shallow(wd):
+ """experimental, may change at any time"""
+ if wd.is_shallow():
+ raise ValueError(
+ "%r is shallow, please correct with " '"git fetch --unshallow"' % wd.path
+ )
+
+
+def parse(
+ root, describe_command=DEFAULT_DESCRIBE, pre_parse=warn_on_shallow, config=None
+):
+ """
+ :param pre_parse: experimental pre_parse action, may change at any time
+ """
+ if not config:
+ config = Configuration(root=root)
+
+ if not has_command("git"):
+ return
+
+ wd = GitWorkdir.from_potential_worktree(config.absolute_root)
+ if wd is None:
+ return
+ if pre_parse:
+ pre_parse(wd)
+
+ if config.git_describe_command:
+ describe_command = config.git_describe_command
+
+ out, unused_err, ret = wd.do_ex(describe_command)
+ if ret:
+ # If 'git git_describe_command' failed, try to get the information otherwise.
+ rev_node = wd.node()
+ dirty = wd.is_dirty()
+
+ if rev_node is None:
+ return meta("0.0", distance=0, dirty=dirty, config=config)
+
+ return meta(
+ "0.0",
+ distance=wd.count_all_nodes(),
+ node="g" + rev_node,
+ dirty=dirty,
+ branch=wd.get_branch(),
+ config=config,
+ )
+ else:
+ tag, number, node, dirty = _git_parse_describe(out)
+
+ branch = wd.get_branch()
+ if number:
+ return meta(
+ tag,
+ config=config,
+ distance=number,
+ node=node,
+ dirty=dirty,
+ branch=branch,
+ )
+ else:
+ return meta(tag, config=config, node=node, dirty=dirty, branch=branch)
+
+
+def _git_parse_describe(describe_output):
+ # 'describe_output' looks e.g. like 'v1.5.0-0-g4060507' or
+ # 'v1.15.1rc1-37-g9bd1298-dirty'.
+
+ if describe_output.endswith("-dirty"):
+ dirty = True
+ describe_output = describe_output[:-6]
+ else:
+ dirty = False
+
+ tag, number, node = describe_output.rsplit("-", 2)
+ number = int(number)
+ return tag, number, node, dirty
--- /dev/null
+import os
+from .utils import data_from_mime, trace
+from .version import tag_to_version, meta
+
+
+def parse_pkginfo(root, config=None):
+
+ pkginfo = os.path.join(root, "PKG-INFO")
+ trace("pkginfo", pkginfo)
+ data = data_from_mime(pkginfo)
+ version = data.get("Version")
+ if version != "UNKNOWN":
+ return meta(version, preformatted=True, config=config)
+
+
+def parse_pip_egg_info(root, config=None):
+ pipdir = os.path.join(root, "pip-egg-info")
+ if not os.path.isdir(pipdir):
+ return
+ items = os.listdir(pipdir)
+ trace("pip-egg-info", pipdir, items)
+ if not items:
+ return
+ return parse_pkginfo(os.path.join(pipdir, items[0]), config=config)
+
+
+def fallback_version(root, config=None):
+ if config.parentdir_prefix_version is not None:
+ _, parent_name = os.path.split(os.path.abspath(root))
+ if parent_name.startswith(config.parentdir_prefix_version):
+ version = tag_to_version(
+ parent_name[len(config.parentdir_prefix_version) :], config
+ )
+ if version is not None:
+ return meta(str(version), preformatted=True, config=config)
+ if config.fallback_version is not None:
+ return meta(config.fallback_version, preformatted=True, config=config)
--- /dev/null
+import os
+from .config import Configuration
+from .utils import do, trace, data_from_mime, has_command
+from .version import meta, tags_to_versions
+
+
+def _hg_tagdist_normalize_tagcommit(config, tag, dist, node, branch):
+ dirty = node.endswith("+")
+ node = "h" + node.strip("+")
+
+ # Detect changes since the specified tag
+ revset = (
+ "(branch(.)" # look for revisions in this branch only
+ " and tag({tag!r})::." # after the last tag
+ # ignore commits that only modify .hgtags and nothing else:
+ " and (merge() or file('re:^(?!\\.hgtags).*$'))"
+ " and not tag({tag!r}))" # ignore the tagged commit itself
+ ).format(tag=tag)
+ if tag != "0.0":
+ commits = do(
+ ["hg", "log", "-r", revset, "--template", "{node|short}"],
+ config.absolute_root,
+ )
+ else:
+ commits = True
+ trace("normalize", locals())
+ if commits or dirty:
+ return meta(
+ tag, distance=dist, node=node, dirty=dirty, branch=branch, config=config
+ )
+ else:
+ return meta(tag, config=config)
+
+
+def parse(root, config=None):
+ if not config:
+ config = Configuration(root=root)
+
+ if not has_command("hg"):
+ return
+ identity_data = do("hg id -i -b -t", config.absolute_root).split()
+ if not identity_data:
+ return
+ node = identity_data.pop(0)
+ branch = identity_data.pop(0)
+ if "tip" in identity_data:
+ # tip is not a real tag
+ identity_data.remove("tip")
+ tags = tags_to_versions(identity_data)
+ dirty = node[-1] == "+"
+ if tags:
+ return meta(tags[0], dirty=dirty, branch=branch, config=config)
+
+ if node.strip("+") == "0" * 12:
+ trace("initial node", config.absolute_root)
+ return meta("0.0", config=config, dirty=dirty, branch=branch)
+
+ try:
+ tag = get_latest_normalizable_tag(config.absolute_root)
+ dist = get_graph_distance(config.absolute_root, tag)
+ if tag == "null":
+ tag = "0.0"
+ dist = int(dist) + 1
+ return _hg_tagdist_normalize_tagcommit(config, tag, dist, node, branch)
+ except ValueError:
+ pass # unpacking failed, old hg
+
+
+def get_latest_normalizable_tag(root):
+ # Gets all tags containing a '.' (see #229) from oldest to newest
+ cmd = [
+ "hg",
+ "log",
+ "-r",
+ "ancestors(.) and tag('re:\\.')",
+ "--template",
+ "{tags}\n",
+ ]
+ outlines = do(cmd, root).split()
+ if not outlines:
+ return "null"
+ tag = outlines[-1].split()[-1]
+ return tag
+
+
+def get_graph_distance(root, rev1, rev2="."):
+ cmd = ["hg", "log", "-q", "-r", "{}::{}".format(rev1, rev2)]
+ out = do(cmd, root)
+ return len(out.strip().splitlines()) - 1
+
+
+def archival_to_version(data, config=None):
+ trace("data", data)
+ node = data.get("node", "")[:12]
+ if node:
+ node = "h" + node
+ if "tag" in data:
+ return meta(data["tag"], config=config)
+ elif "latesttag" in data:
+ return meta(
+ data["latesttag"],
+ distance=data["latesttagdistance"],
+ node=node,
+ config=config,
+ )
+ else:
+ return meta("0.0", node=node, config=config)
+
+
+def parse_archival(root, config=None):
+ archival = os.path.join(root, ".hg_archival.txt")
+ data = data_from_mime(archival)
+ return archival_to_version(data, config=config)
--- /dev/null
+from pkg_resources import iter_entry_points
+
+from .version import _warn_if_setuptools_outdated
+from .utils import do, trace_exception
+from . import _get_version, Configuration
+
+
+def version_keyword(dist, keyword, value):
+ _warn_if_setuptools_outdated()
+ if not value:
+ return
+ if value is True:
+ value = {}
+ if getattr(value, "__call__", None):
+ value = value()
+ config = Configuration(**value)
+ dist.metadata.version = _get_version(config)
+
+
+def find_files(path=""):
+ for ep in iter_entry_points("setuptools_scm.files_command"):
+ command = ep.load()
+ if isinstance(command, str):
+ # this technique is deprecated
+ res = do(ep.load(), path or ".").splitlines()
+ else:
+ res = command(path)
+ if res:
+ return res
+ return []
+
+
+def _args_from_toml(name="pyproject.toml"):
+ # todo: more sensible config initialization
+ # move this elper back to config and unify it with the code from get_config
+
+ with open(name) as strm:
+ defn = __import__("toml").load(strm)
+ return defn.get("tool", {})["setuptools_scm"]
+
+
+def infer_version(dist):
+
+ try:
+ config = Configuration.from_file()
+ except Exception:
+ return trace_exception()
+ dist.metadata.version = _get_version(config)
--- /dev/null
+"""
+utils
+"""
+from __future__ import print_function, unicode_literals
+import inspect
+import warnings
+import sys
+import shlex
+import subprocess
+import os
+import io
+import platform
+import traceback
+import datetime
+
+
+DEBUG = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG"))
+IS_WINDOWS = platform.system() == "Windows"
+PY2 = sys.version_info < (3,)
+PY3 = sys.version_info > (3,)
+string_types = (str,) if PY3 else (str, unicode) # noqa
+
+
+def no_git_env(env):
+ # adapted from pre-commit
+ # Too many bugs dealing with environment variables and GIT:
+ # https://github.com/pre-commit/pre-commit/issues/300
+ # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
+ # pre-commit hooks
+ # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
+ # while running pre-commit hooks in submodules.
+ # GIT_DIR: Causes git clone to clone wrong thing
+ # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
+ for k, v in env.items():
+ if k.startswith("GIT_"):
+ trace(k, v)
+ return {
+ k: v
+ for k, v in env.items()
+ if not k.startswith("GIT_")
+ or k in ("GIT_EXEC_PATH", "GIT_SSH", "GIT_SSH_COMMAND")
+ }
+
+
+def trace(*k):
+ if DEBUG:
+ print(*k)
+ sys.stdout.flush()
+
+
+def trace_exception():
+ DEBUG and traceback.print_exc()
+
+
+def ensure_stripped_str(str_or_bytes):
+ if isinstance(str_or_bytes, str):
+ return str_or_bytes.strip()
+ else:
+ return str_or_bytes.decode("utf-8", "surrogateescape").strip()
+
+
+def _always_strings(env_dict):
+ """
+ On Windows and Python 2, environment dictionaries must be strings
+ and not unicode.
+ """
+ if IS_WINDOWS or PY2:
+ env_dict.update((key, str(value)) for (key, value) in env_dict.items())
+ return env_dict
+
+
+def _popen_pipes(cmd, cwd):
+ return subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=str(cwd),
+ env=_always_strings(
+ dict(
+ no_git_env(os.environ),
+ # os.environ,
+ # try to disable i18n
+ LC_ALL="C",
+ LANGUAGE="",
+ HGPLAIN="1",
+ )
+ ),
+ )
+
+
+def do_ex(cmd, cwd="."):
+ trace("cmd", repr(cmd))
+ if os.name == "posix" and not isinstance(cmd, (list, tuple)):
+ cmd = shlex.split(cmd)
+
+ p = _popen_pipes(cmd, cwd)
+ out, err = p.communicate()
+ if out:
+ trace("out", repr(out))
+ if err:
+ trace("err", repr(err))
+ if p.returncode:
+ trace("ret", p.returncode)
+ return ensure_stripped_str(out), ensure_stripped_str(err), p.returncode
+
+
+def do(cmd, cwd="."):
+ out, err, ret = do_ex(cmd, cwd)
+ if ret:
+ print(err)
+ return out
+
+
+def data_from_mime(path):
+ with io.open(path, encoding="utf-8") as fp:
+ content = fp.read()
+ trace("content", repr(content))
+ # the complex conditions come from reading pseudo-mime-messages
+ data = dict(x.split(": ", 1) for x in content.splitlines() if ": " in x)
+ trace("data", data)
+ return data
+
+
+class UTC(datetime.tzinfo):
+ _ZERO = datetime.timedelta(0)
+
+ def utcoffset(self, dt):
+ return self._ZERO
+
+ def tzname(self, dt):
+ return "UTC"
+
+ def dst(self, dt):
+ return self._ZERO
+
+
+utc = UTC()
+
+
+def function_has_arg(fn, argname):
+ assert inspect.isfunction(fn)
+
+ if PY2:
+ argspec = inspect.getargspec(fn).args
+ else:
+
+ argspec = inspect.signature(fn).parameters
+
+ return argname in argspec
+
+
+def has_command(name):
+ try:
+ p = _popen_pipes([name, "help"], ".")
+ except OSError:
+ trace(*sys.exc_info())
+ res = False
+ else:
+ p.communicate()
+ res = not p.returncode
+ if not res:
+ warnings.warn("%r was not found" % name)
+ return res
--- /dev/null
+from __future__ import print_function
+import datetime
+import warnings
+import re
+
+from .config import Configuration
+from .utils import trace, string_types, utc
+
+from pkg_resources import iter_entry_points
+
+from pkg_resources import parse_version as pkg_parse_version
+
+SEMVER_MINOR = 2
+SEMVER_PATCH = 3
+SEMVER_LEN = 3
+
+
+def _parse_version_tag(tag, config):
+ tagstring = tag if not isinstance(tag, string_types) else str(tag)
+ match = config.tag_regex.match(tagstring)
+
+ result = None
+ if match:
+ if len(match.groups()) == 1:
+ key = 1
+ else:
+ key = "version"
+
+ result = {
+ "version": match.group(key),
+ "prefix": match.group(0)[: match.start(key)],
+ "suffix": match.group(0)[match.end(key) :],
+ }
+
+ trace("tag '{}' parsed to {}".format(tag, result))
+ return result
+
+
+def _get_version_class():
+ modern_version = pkg_parse_version("1.0")
+ if isinstance(modern_version, tuple):
+ return None
+ else:
+ return type(modern_version)
+
+
+VERSION_CLASS = _get_version_class()
+
+
+class SetuptoolsOutdatedWarning(Warning):
+ pass
+
+
+# append so integrators can disable the warning
+warnings.simplefilter("error", SetuptoolsOutdatedWarning, append=True)
+
+
+def _warn_if_setuptools_outdated():
+ if VERSION_CLASS is None:
+ warnings.warn("your setuptools is too old (<12)", SetuptoolsOutdatedWarning)
+
+
+def callable_or_entrypoint(group, callable_or_name):
+ trace("ep", (group, callable_or_name))
+
+ if callable(callable_or_name):
+ return callable_or_name
+
+ for ep in iter_entry_points(group, callable_or_name):
+ trace("ep found:", ep.name)
+ return ep.load()
+
+
+def tag_to_version(tag, config=None):
+ """
+ take a tag that might be prefixed with a keyword and return only the version part
+ :param config: optional configuration object
+ """
+ trace("tag", tag)
+
+ if not config:
+ config = Configuration()
+
+ tagdict = _parse_version_tag(tag, config)
+ if not isinstance(tagdict, dict) or not tagdict.get("version", None):
+ warnings.warn("tag {!r} no version found".format(tag))
+ return None
+
+ version = tagdict["version"]
+ trace("version pre parse", version)
+
+ if tagdict.get("suffix", ""):
+ warnings.warn(
+ "tag {!r} will be stripped of its suffix '{}'".format(
+ tag, tagdict["suffix"]
+ )
+ )
+
+ if VERSION_CLASS is not None:
+ version = pkg_parse_version(version)
+ trace("version", repr(version))
+
+ return version
+
+
+def tags_to_versions(tags, config=None):
+ """
+ take tags that might be prefixed with a keyword and return only the version part
+ :param tags: an iterable of tags
+ :param config: optional configuration object
+ """
+ result = []
+ for tag in tags:
+ tag = tag_to_version(tag, config=config)
+ if tag:
+ result.append(tag)
+ return result
+
+
+class ScmVersion(object):
+ def __init__(
+ self,
+ tag_version,
+ distance=None,
+ node=None,
+ dirty=False,
+ preformatted=False,
+ branch=None,
+ config=None,
+ **kw
+ ):
+ if kw:
+ trace("unknown args", kw)
+ self.tag = tag_version
+ if dirty and distance is None:
+ distance = 0
+ self.distance = distance
+ self.node = node
+ self.time = datetime.datetime.now(utc)
+ self._extra = kw
+ self.dirty = dirty
+ self.preformatted = preformatted
+ self.branch = branch
+ self.config = config
+
+ @property
+ def extra(self):
+ warnings.warn(
+ "ScmVersion.extra is deprecated and will be removed in future",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+ return self._extra
+
+ @property
+ def exact(self):
+ return self.distance is None
+
+ def __repr__(self):
+ return self.format_with(
+ "<ScmVersion {tag} d={distance} n={node} d={dirty} b={branch}>"
+ )
+
+ def format_with(self, fmt, **kw):
+ return fmt.format(
+ time=self.time,
+ tag=self.tag,
+ distance=self.distance,
+ node=self.node,
+ dirty=self.dirty,
+ branch=self.branch,
+ **kw
+ )
+
+ def format_choice(self, clean_format, dirty_format, **kw):
+ return self.format_with(dirty_format if self.dirty else clean_format, **kw)
+
+ def format_next_version(self, guess_next, fmt="{guessed}.dev{distance}", **kw):
+ guessed = guess_next(self.tag, **kw)
+ return self.format_with(fmt, guessed=guessed)
+
+
+def _parse_tag(tag, preformatted, config):
+ if preformatted:
+ return tag
+ if VERSION_CLASS is None or not isinstance(tag, VERSION_CLASS):
+ tag = tag_to_version(tag, config)
+ return tag
+
+
+def meta(
+ tag,
+ distance=None,
+ dirty=False,
+ node=None,
+ preformatted=False,
+ branch=None,
+ config=None,
+ **kw
+):
+ if not config:
+ warnings.warn(
+ "meta invoked without explicit configuration,"
+ " will use defaults where required."
+ )
+ parsed_version = _parse_tag(tag, preformatted, config)
+ trace("version", tag, "->", parsed_version)
+ assert parsed_version is not None, "cant parse version %s" % tag
+ return ScmVersion(
+ parsed_version, distance, node, dirty, preformatted, branch, config, **kw
+ )
+
+
+def guess_next_version(tag_version):
+ version = _strip_local(str(tag_version))
+ return _bump_dev(version) or _bump_regex(version)
+
+
+def _strip_local(version_string):
+ public, sep, local = version_string.partition("+")
+ return public
+
+
+def _bump_dev(version):
+ if ".dev" not in version:
+ return
+
+ prefix, tail = version.rsplit(".dev", 1)
+ assert tail == "0", "own dev numbers are unsupported"
+ return prefix
+
+
+def _bump_regex(version):
+ prefix, tail = re.match(r"(.*?)(\d+)$", version).groups()
+ return "%s%d" % (prefix, int(tail) + 1)
+
+
+def guess_next_dev_version(version):
+ if version.exact:
+ return version.format_with("{tag}")
+ else:
+ return version.format_next_version(guess_next_version)
+
+
+def guess_next_simple_semver(version, retain, increment=True):
+ parts = [int(i) for i in str(version).split(".")[:retain]]
+ while len(parts) < retain:
+ parts.append(0)
+ if increment:
+ parts[-1] += 1
+ while len(parts) < SEMVER_LEN:
+ parts.append(0)
+ return ".".join(str(i) for i in parts)
+
+
+def simplified_semver_version(version):
+ if version.exact:
+ return guess_next_simple_semver(version.tag, retain=SEMVER_LEN, increment=False)
+ else:
+ if version.branch is not None and "feature" in version.branch:
+ return version.format_next_version(
+ guess_next_simple_semver, retain=SEMVER_MINOR
+ )
+ else:
+ return version.format_next_version(
+ guess_next_simple_semver, retain=SEMVER_PATCH
+ )
+
+
+def release_branch_semver_version(version):
+ if version.exact:
+ return version.format_with("{tag}")
+ if version.branch is not None:
+ # Does the branch name (stripped of namespace) parse as a version?
+ branch_ver = _parse_version_tag(version.branch.split("/")[-1], version.config)
+ if branch_ver is not None:
+ # Does the branch version up to the minor part match the tag? If not it
+ # might be like, an issue number or something and not a version number, so
+ # we only want to use it if it matches.
+ tag_ver_up_to_minor = str(version.tag).split(".")[:SEMVER_MINOR]
+ branch_ver_up_to_minor = branch_ver["version"].split(".")[:SEMVER_MINOR]
+ if branch_ver_up_to_minor == tag_ver_up_to_minor:
+ # We're in a release/maintenance branch, next is a patch/rc/beta bump:
+ return version.format_next_version(guess_next_version)
+ # We're in a development branch, next is a minor bump:
+ return version.format_next_version(guess_next_simple_semver, retain=SEMVER_MINOR)
+
+
+def release_branch_semver(version):
+ warnings.warn(
+ "release_branch_semver is deprecated and will be removed in future. "
+ + "Use release_branch_semver_version instead",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+ return release_branch_semver_version(version)
+
+
+def _format_local_with_time(version, time_format):
+
+ if version.exact or version.node is None:
+ return version.format_choice(
+ "", "+d{time:{time_format}}", time_format=time_format
+ )
+ else:
+ return version.format_choice(
+ "+{node}", "+{node}.d{time:{time_format}}", time_format=time_format
+ )
+
+
+def get_local_node_and_date(version):
+ return _format_local_with_time(version, time_format="%Y%m%d")
+
+
+def get_local_node_and_timestamp(version, fmt="%Y%m%d%H%M%S"):
+ return _format_local_with_time(version, time_format=fmt)
+
+
+def get_local_dirty_tag(version):
+ return version.format_choice("", "+dirty")
+
+
+def get_no_local_node(_):
+ return ""
+
+
+def postrelease_version(version):
+ if version.exact:
+ return version.format_with("{tag}")
+ else:
+ return version.format_with("{tag}.post{distance}")
+
+
+def format_version(version, **config):
+ trace("scm version", version)
+ trace("config", config)
+ if version.preformatted:
+ return version.tag
+ version_scheme = callable_or_entrypoint(
+ "setuptools_scm.version_scheme", config["version_scheme"]
+ )
+ local_scheme = callable_or_entrypoint(
+ "setuptools_scm.local_scheme", config["local_scheme"]
+ )
+ main_version = version_scheme(version)
+ trace("version", main_version)
+ local_version = local_scheme(version)
+ trace("local_version", local_version)
+ return version_scheme(version) + local_scheme(version)
--- /dev/null
+"""
+Backport of os.path.samefile for Python prior to 3.2
+on Windows from jaraco.windows 3.8.
+
+DON'T EDIT THIS FILE!
+
+Instead, file tickets and PR's with `jaraco.windows
+<https://github.com/jaraco/jaraco.windows>`_ and request
+a port to setuptools_scm.
+"""
+
+import os
+import nt
+import posixpath
+import ctypes.wintypes
+import sys
+import __builtin__ as builtins
+
+
+##
+# From jaraco.windows.error
+
+def format_system_message(errno):
+ """
+ Call FormatMessage with a system error number to retrieve
+ the descriptive error message.
+ """
+ # first some flags used by FormatMessageW
+ ALLOCATE_BUFFER = 0x100
+ FROM_SYSTEM = 0x1000
+
+ # Let FormatMessageW allocate the buffer (we'll free it below)
+ # Also, let it know we want a system error message.
+ flags = ALLOCATE_BUFFER | FROM_SYSTEM
+ source = None
+ message_id = errno
+ language_id = 0
+ result_buffer = ctypes.wintypes.LPWSTR()
+ buffer_size = 0
+ arguments = None
+ bytes = ctypes.windll.kernel32.FormatMessageW(
+ flags,
+ source,
+ message_id,
+ language_id,
+ ctypes.byref(result_buffer),
+ buffer_size,
+ arguments,
+ )
+ # note the following will cause an infinite loop if GetLastError
+ # repeatedly returns an error that cannot be formatted, although
+ # this should not happen.
+ handle_nonzero_success(bytes)
+ message = result_buffer.value
+ ctypes.windll.kernel32.LocalFree(result_buffer)
+ return message
+
+
+class WindowsError(builtins.WindowsError):
+ """
+ More info about errors at
+ http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
+ """
+
+ def __init__(self, value=None):
+ if value is None:
+ value = ctypes.windll.kernel32.GetLastError()
+ strerror = format_system_message(value)
+ if sys.version_info > (3, 3):
+ args = 0, strerror, None, value
+ else:
+ args = value, strerror
+ super(WindowsError, self).__init__(*args)
+
+ @property
+ def message(self):
+ return self.strerror
+
+ @property
+ def code(self):
+ return self.winerror
+
+ def __str__(self):
+ return self.message
+
+ def __repr__(self):
+ return '{self.__class__.__name__}({self.winerror})'.format(**vars())
+
+
+def handle_nonzero_success(result):
+ if result == 0:
+ raise WindowsError()
+
+
+##
+# From jaraco.windows.api.filesystem
+
+FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
+FILE_FLAG_BACKUP_SEMANTICS = 0x2000000
+OPEN_EXISTING = 3
+FILE_ATTRIBUTE_NORMAL = 0x80
+FILE_READ_ATTRIBUTES = 0x80
+INVALID_HANDLE_VALUE = ctypes.wintypes.HANDLE(-1).value
+
+
+class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
+ _fields_ = [
+ ('file_attributes', ctypes.wintypes.DWORD),
+ ('creation_time', ctypes.wintypes.FILETIME),
+ ('last_access_time', ctypes.wintypes.FILETIME),
+ ('last_write_time', ctypes.wintypes.FILETIME),
+ ('volume_serial_number', ctypes.wintypes.DWORD),
+ ('file_size_high', ctypes.wintypes.DWORD),
+ ('file_size_low', ctypes.wintypes.DWORD),
+ ('number_of_links', ctypes.wintypes.DWORD),
+ ('file_index_high', ctypes.wintypes.DWORD),
+ ('file_index_low', ctypes.wintypes.DWORD),
+ ]
+
+ @property
+ def file_size(self):
+ return (self.file_size_high << 32) + self.file_size_low
+
+ @property
+ def file_index(self):
+ return (self.file_index_high << 32) + self.file_index_low
+
+
+class SECURITY_ATTRIBUTES(ctypes.Structure):
+ _fields_ = (
+ ('length', ctypes.wintypes.DWORD),
+ ('p_security_descriptor', ctypes.wintypes.LPVOID),
+ ('inherit_handle', ctypes.wintypes.BOOLEAN),
+ )
+
+
+LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
+
+
+CreateFile = ctypes.windll.kernel32.CreateFileW
+CreateFile.argtypes = (
+ ctypes.wintypes.LPWSTR,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.DWORD,
+ LPSECURITY_ATTRIBUTES,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.HANDLE,
+)
+CreateFile.restype = ctypes.wintypes.HANDLE
+
+GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
+GetFileInformationByHandle.restype = ctypes.wintypes.BOOL
+GetFileInformationByHandle.argtypes = (
+ ctypes.wintypes.HANDLE,
+ ctypes.POINTER(BY_HANDLE_FILE_INFORMATION),
+)
+
+
+##
+# From jaraco.windows.filesystem
+
+def compat_stat(path):
+ """
+ Generate stat as found on Python 3.2 and later.
+ """
+ stat = os.stat(path)
+ info = get_file_info(path)
+ # rewrite st_ino, st_dev, and st_nlink based on file info
+ return nt.stat_result(
+ (stat.st_mode,) +
+ (info.file_index, info.volume_serial_number, info.number_of_links) +
+ stat[4:]
+ )
+
+
+def samefile(f1, f2):
+ """
+ Backport of samefile from Python 3.2 with support for Windows.
+ """
+ return posixpath.samestat(compat_stat(f1), compat_stat(f2))
+
+
+def get_file_info(path):
+ # open the file the same way CPython does in posixmodule.c
+ desired_access = FILE_READ_ATTRIBUTES
+ share_mode = 0
+ security_attributes = None
+ creation_disposition = OPEN_EXISTING
+ flags_and_attributes = (
+ FILE_ATTRIBUTE_NORMAL |
+ FILE_FLAG_BACKUP_SEMANTICS |
+ FILE_FLAG_OPEN_REPARSE_POINT
+ )
+ template_file = None
+
+ handle = CreateFile(
+ path,
+ desired_access,
+ share_mode,
+ security_attributes,
+ creation_disposition,
+ flags_and_attributes,
+ template_file,
+ )
+
+ if handle == INVALID_HANDLE_VALUE:
+ raise WindowsError()
+
+ info = BY_HANDLE_FILE_INFORMATION()
+ res = GetFileInformationByHandle(handle, info)
+ handle_nonzero_success(res)
+
+ return info
--- /dev/null
+import pkg_resources
+import setuptools_scm
+
+dist = pkg_resources.get_distribution("setuptools_scm")
+assert dist.version == setuptools_scm.get_version(), dist.version
--- /dev/null
+import os
+import itertools
+import pytest
+import six
+
+os.environ["SETUPTOOLS_SCM_DEBUG"] = "1"
+VERSION_PKGS = ["setuptools", "setuptools_scm"]
+
+
+def pytest_report_header():
+ import pkg_resources
+
+ res = []
+ for pkg in VERSION_PKGS:
+ version = pkg_resources.get_distribution(pkg).version
+ path = __import__(pkg).__file__
+ res.append("{} version {} from {!r}".format(pkg, version, path))
+ return res
+
+
+class Wd(object):
+ commit_command = None
+ add_command = None
+
+ def __repr__(self):
+ return "<WD {cwd}>".format(cwd=self.cwd)
+
+ def __init__(self, cwd):
+ self.cwd = cwd
+ self.__counter = itertools.count()
+
+ def __call__(self, cmd, **kw):
+ if kw:
+ cmd = cmd.format(**kw)
+ from setuptools_scm.utils import do
+
+ return do(cmd, self.cwd)
+
+ def write(self, name, value, **kw):
+ filename = self.cwd / name
+ if kw:
+ value = value.format(**kw)
+ if isinstance(value, six.text_type):
+ filename.write_text(value)
+ else:
+ filename.write_bytes(value)
+ return filename
+
+ def _reason(self, given_reason):
+ if given_reason is None:
+ return "number-{c}".format(c=next(self.__counter))
+ else:
+ return given_reason
+
+ def add_and_commit(self, reason=None):
+ self(self.add_command)
+ self.commit(reason)
+
+ def commit(self, reason=None):
+ reason = self._reason(reason)
+ self(self.commit_command, reason=reason)
+
+ def commit_testfile(self, reason=None):
+ reason = self._reason(reason)
+ self.write("test.txt", "test {reason}", reason=reason)
+ self(self.add_command)
+ self.commit(reason=reason)
+
+ def get_version(self, **kw):
+ __tracebackhide__ = True
+ from setuptools_scm import get_version
+
+ version = get_version(root=str(self.cwd), fallback_root=str(self.cwd), **kw)
+ print(version)
+ return version
+
+ @property
+ def version(self):
+ __tracebackhide__ = True
+ return self.get_version()
+
+
+@pytest.yield_fixture(autouse=True)
+def debug_mode():
+ from setuptools_scm import utils
+
+ utils.DEBUG = True
+ yield
+ utils.DEBUG = False
+
+
+@pytest.fixture
+def wd(tmp_path):
+ target_wd = tmp_path.resolve() / "wd"
+ target_wd.mkdir()
+ return Wd(target_wd)
--- /dev/null
+#!/usr/bin/env bash
+set -euxo pipefail
+
+rm -rf y z home venv tmp
+
+[ ! -d black ] && git clone https://github.com/psf/black
+export SETUPTOOLS_SCM_DEBUG=1
+export PRE_COMMIT_HOME="$PWD/home"
+export TMPDIR="$PWD/tmp"
+
+git init y
+git init z
+git -C z commit --allow-empty -m 'commit!'
+git -C y submodule add "$PWD/z"
+cat > "$PWD/y/.git/modules/z/hooks/pre-commit" <<EOF
+#!/usr/bin/env bash
+virtualenv "$PWD/venv"
+"$PWD/venv/bin/pip" install -e "$1"
+"$PWD/venv/bin/pip" install --no-clean "$PWD/black"
+EOF
+chmod +x "$PWD/y/.git/modules/z/hooks/pre-commit"
+cd y/z
+git commit -m "test"
--- /dev/null
+import os
+import sys
+import py
+import pytest
+
+import setuptools_scm
+from setuptools_scm import dump_version
+from setuptools_scm.utils import data_from_mime, do
+
+
+@pytest.mark.parametrize("cmd", ["ls", "dir"])
+def test_do(cmd, tmpdir):
+ if not py.path.local.sysfind(cmd):
+ pytest.skip(cmd + " not found")
+ do(cmd, str(tmpdir))
+
+
+def test_data_from_mime(tmpdir):
+ tmpfile = tmpdir.join("test.archival")
+ tmpfile.write("name: test\nrevision: 1")
+
+ res = data_from_mime(str(tmpfile))
+ assert res == {"name": "test", "revision": "1"}
+
+
+def test_version_from_pkginfo(wd, monkeypatch):
+ wd.write("PKG-INFO", "Version: 0.1")
+
+ assert wd.version == "0.1"
+
+ # replicate issue 167
+ assert wd.get_version(version_scheme="1.{0.distance}.0".format) == "0.1"
+
+
+def assert_root(monkeypatch, expected_root):
+ """
+ Patch version_from_scm to simply assert that root is expected root
+ """
+
+ def assertion(config):
+ assert config.absolute_root == expected_root
+
+ monkeypatch.setattr(setuptools_scm, "_do_parse", assertion)
+
+
+def test_root_parameter_creation(monkeypatch):
+ assert_root(monkeypatch, os.getcwd())
+ setuptools_scm.get_version()
+
+
+def test_version_from_scm(wd):
+ with pytest.warns(DeprecationWarning, match=".*version_from_scm.*"):
+ setuptools_scm.version_from_scm(str(wd))
+
+
+def test_root_parameter_pass_by(monkeypatch, tmpdir):
+ assert_root(monkeypatch, tmpdir)
+ setuptools_scm.get_version(root=tmpdir.strpath)
+
+
+def test_parentdir_prefix(tmpdir, monkeypatch):
+ monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+ p = tmpdir.ensure("projectname-v12.34", dir=True)
+ p.join("setup.py").write(
+ """from setuptools import setup
+setup(use_scm_version={"parentdir_prefix_version": "projectname-"})
+"""
+ )
+ res = do((sys.executable, "setup.py", "--version"), p)
+ assert res == "12.34"
+
+
+def test_fallback(tmpdir, monkeypatch):
+ monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+ p = tmpdir.ensure("sub/package", dir=1)
+ p.join("setup.py").write(
+ """from setuptools import setup
+setup(use_scm_version={"fallback_version": "12.34"})
+"""
+ )
+ res = do((sys.executable, "setup.py", "--version"), p)
+ assert res == "12.34"
+
+
+@pytest.mark.parametrize(
+ "version", ["1.0", "1.2.3.dev1+ge871260", "1.2.3.dev15+ge871260.d20180625", "2345"]
+)
+def test_pretended(version, monkeypatch):
+ monkeypatch.setenv(setuptools_scm.PRETEND_KEY, version)
+ assert setuptools_scm.get_version() == version
+
+
+def test_root_relative_to(monkeypatch, tmpdir):
+ assert_root(monkeypatch, tmpdir.join("alt").strpath)
+ __file__ = tmpdir.join("module/file.py").strpath
+ setuptools_scm.get_version(root="../alt", relative_to=__file__)
+
+
+def test_dump_version(tmpdir):
+ sp = tmpdir.strpath
+
+ dump_version(sp, "1.0", "first.txt")
+ assert tmpdir.join("first.txt").read() == "1.0"
+ dump_version(sp, "1.0", "first.py")
+ content = tmpdir.join("first.py").read()
+ assert repr("1.0") in content
+ import ast
+
+ ast.parse(content)
+
+
+def test_parse_plain_fails(recwarn):
+ def parse(root):
+ return "tricked you"
+
+ with pytest.raises(TypeError):
+ setuptools_scm.get_version(parse=parse)
--- /dev/null
+from __future__ import unicode_literals
+
+from setuptools_scm.config import Configuration
+import re
+import pytest
+
+
+@pytest.mark.parametrize(
+ "tag, expected_version",
+ [
+ ("apache-arrow-0.9.0", "0.9.0"),
+ ("arrow-0.9.0", "0.9.0"),
+ ("arrow-0.9.0-rc", "0.9.0-rc"),
+ ("arrow-1", "1"),
+ ("arrow-1+", "1"),
+ ("arrow-1+foo", "1"),
+ ("arrow-1.1+foo", "1.1"),
+ ("v1.1", "v1.1"),
+ ("V1.1", "V1.1"),
+ ],
+)
+def test_tag_regex(tag, expected_version):
+ config = Configuration()
+ match = config.tag_regex.match(tag)
+ assert match
+ version = match.group("version")
+ assert version == expected_version
+
+
+def test_config_from_pyproject(tmpdir):
+ fn = tmpdir / "pyproject.toml"
+ fn.write_text("[tool.setuptools_scm]\n", encoding="utf-8")
+ assert Configuration.from_file(str(fn))
+
+
+def test_config_regex_init():
+ tag_regex = re.compile(r"v(\d+)")
+ conf = Configuration(tag_regex=tag_regex)
+ assert conf.tag_regex is tag_regex
--- /dev/null
+import os
+import sys
+
+import pytest
+
+from setuptools_scm.integration import find_files
+
+
+@pytest.fixture(params=["git", "hg"])
+def inwd(request, wd, monkeypatch):
+ if request.param == "git":
+ if sys.platform == "win32" and sys.version_info[0] < 3:
+ pytest.skip("Long/short path names supported on Windows Python 2.7")
+ try:
+ wd("git init")
+ except OSError:
+ pytest.skip("git executable not found")
+ wd("git config user.email test@example.com")
+ wd('git config user.name "a test"')
+ wd.add_command = "git add ."
+ wd.commit_command = "git commit -m test-{reason}"
+ elif request.param == "hg":
+ try:
+ wd("hg init")
+ except OSError:
+ pytest.skip("hg executable not found")
+ wd.add_command = "hg add ."
+ wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"'
+ (wd.cwd / "file1").touch()
+ adir = wd.cwd / "adir"
+ adir.mkdir()
+ (adir / "filea").touch()
+ bdir = wd.cwd / "bdir"
+ bdir.mkdir()
+ (bdir / "fileb").touch()
+ wd.add_and_commit()
+ monkeypatch.chdir(wd.cwd)
+ yield wd
+
+
+def _sep(paths):
+ return {path.replace("/", os.path.sep) for path in paths}
+
+
+def test_basic(inwd):
+ assert set(find_files()) == _sep({"file1", "adir/filea", "bdir/fileb"})
+ assert set(find_files(".")) == _sep({"./file1", "./adir/filea", "./bdir/fileb"})
+ assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+def test_whitespace(inwd):
+ (inwd.cwd / "adir" / "space file").touch()
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep({"adir/space file", "adir/filea"})
+
+
+def test_case(inwd):
+ (inwd.cwd / "CamelFile").touch()
+ (inwd.cwd / "file2").touch()
+ inwd.add_and_commit()
+ assert set(find_files()) == _sep(
+ {"CamelFile", "file2", "file1", "adir/filea", "bdir/fileb"}
+ )
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_dir(inwd):
+ (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"})
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_dir_source_not_in_scm(inwd):
+ (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
+ assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+@pytest.mark.skipif(
+ sys.platform == "win32", reason="symlinks to files not supported on windows"
+)
+def test_symlink_file(inwd):
+ (inwd.cwd / "adir" / "file1link").symlink_to("../file1")
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep(
+ {"adir/filea", "adir/file1link"}
+ ) # -> ../file1
+
+
+@pytest.mark.skipif(
+ sys.platform == "win32", reason="symlinks to files not supported on windows"
+)
+def test_symlink_file_source_not_in_scm(inwd):
+ (inwd.cwd / "adir" / "file1link").symlink_to("../file1")
+ assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_loop(inwd):
+ (inwd.cwd / "adir" / "loop").symlink_to("../adir")
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep({"adir/filea", "adir/loop"}) # -> ../adir
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_loop_outside_path(inwd):
+ (inwd.cwd / "bdir" / "loop").symlink_to("../bdir")
+ (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"})
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_dir_out_of_git(inwd):
+ (inwd.cwd / "adir" / "outsidedirlink").symlink_to(os.path.join(__file__, ".."))
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+@pytest.mark.skipif(
+ sys.platform == "win32", reason="symlinks to files not supported on windows"
+)
+def test_symlink_file_out_of_git(inwd):
+ (inwd.cwd / "adir" / "outsidefilelink").symlink_to(__file__)
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+def test_empty_root(inwd):
+ subdir = inwd.cwd / "cdir" / "subdir"
+ subdir.mkdir(parents=True)
+ (subdir / "filec").touch()
+ inwd.add_and_commit()
+ assert set(find_files("cdir")) == _sep({"cdir/subdir/filec"})
+
+
+def test_empty_subdir(inwd):
+ subdir = inwd.cwd / "adir" / "emptysubdir" / "subdir"
+ subdir.mkdir(parents=True)
+ (subdir / "xfile").touch()
+ inwd.add_and_commit()
+ assert set(find_files("adir")) == _sep(
+ {"adir/filea", "adir/emptysubdir/subdir/xfile"}
+ )
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows")
+def test_double_include_through_symlink(inwd):
+ (inwd.cwd / "data").mkdir()
+ (inwd.cwd / "data" / "datafile").touch()
+ (inwd.cwd / "adir" / "datalink").symlink_to("../data")
+ (inwd.cwd / "adir" / "filealink").symlink_to("filea")
+ inwd.add_and_commit()
+ assert set(find_files()) == _sep(
+ {
+ "file1",
+ "adir/datalink", # -> ../data
+ "adir/filealink", # -> filea
+ "adir/filea",
+ "bdir/fileb",
+ "data/datafile",
+ }
+ )
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows")
+def test_symlink_not_in_scm_while_target_is(inwd):
+ (inwd.cwd / "data").mkdir()
+ (inwd.cwd / "data" / "datafile").touch()
+ inwd.add_and_commit()
+ (inwd.cwd / "adir" / "datalink").symlink_to("../data")
+ (inwd.cwd / "adir" / "filealink").symlink_to("filea")
+ assert set(find_files()) == _sep(
+ {
+ "file1",
+ "adir/filea",
+ # adir/datalink and adir/afilelink not included
+ # because the symlink_to themselves are not in scm
+ "bdir/fileb",
+ "data/datafile",
+ }
+ )
--- /dev/null
+import pytest
+import sys
+import pkg_resources
+from setuptools_scm import dump_version, get_version, PRETEND_KEY
+from setuptools_scm.version import (
+ guess_next_version,
+ meta,
+ format_version,
+ tag_to_version,
+)
+
+from setuptools_scm.config import Configuration
+from setuptools_scm.utils import has_command
+
+PY3 = sys.version_info > (2,)
+
+
+class MockTime(object):
+ def __format__(self, *k):
+ return "time"
+
+
+@pytest.mark.parametrize(
+ "tag, expected",
+ [
+ ("1.1", "1.2"),
+ ("1.2.dev", "1.2"),
+ ("1.1a2", "1.1a3"),
+ ("23.24.post2+deadbeef", "23.24.post3"),
+ ],
+)
+def test_next_tag(tag, expected):
+ version = pkg_resources.parse_version(tag)
+ assert guess_next_version(version) == expected
+
+
+c = Configuration()
+
+VERSIONS = {
+ "exact": meta("1.1", distance=None, dirty=False, config=c),
+ "zerodistance": meta("1.1", distance=0, dirty=False, config=c),
+ "dirty": meta("1.1", distance=None, dirty=True, config=c),
+ "distance": meta("1.1", distance=3, dirty=False, config=c),
+ "distancedirty": meta("1.1", distance=3, dirty=True, config=c),
+}
+
+
+@pytest.mark.parametrize(
+ "version,scheme,expected",
+ [
+ ("exact", "guess-next-dev node-and-date", "1.1"),
+ ("zerodistance", "guess-next-dev node-and-date", "1.2.dev0"),
+ ("zerodistance", "guess-next-dev no-local-version", "1.2.dev0"),
+ ("dirty", "guess-next-dev node-and-date", "1.2.dev0+dtime"),
+ ("dirty", "guess-next-dev no-local-version", "1.2.dev0"),
+ ("distance", "guess-next-dev node-and-date", "1.2.dev3"),
+ ("distancedirty", "guess-next-dev node-and-date", "1.2.dev3+dtime"),
+ ("distancedirty", "guess-next-dev no-local-version", "1.2.dev3"),
+ ("exact", "post-release node-and-date", "1.1"),
+ ("zerodistance", "post-release node-and-date", "1.1.post0"),
+ ("dirty", "post-release node-and-date", "1.1.post0+dtime"),
+ ("distance", "post-release node-and-date", "1.1.post3"),
+ ("distancedirty", "post-release node-and-date", "1.1.post3+dtime"),
+ ],
+)
+def test_format_version(version, monkeypatch, scheme, expected):
+ version = VERSIONS[version]
+ monkeypatch.setattr(version, "time", MockTime())
+ vs, ls = scheme.split()
+ assert format_version(version, version_scheme=vs, local_scheme=ls) == expected
+
+
+def test_dump_version_doesnt_bail_on_value_error(tmpdir):
+ write_to = "VERSION"
+ version = str(VERSIONS["exact"].tag)
+ with pytest.raises(ValueError) as exc_info:
+ dump_version(tmpdir.strpath, version, write_to)
+ assert str(exc_info.value).startswith("bad file format:")
+
+
+@pytest.mark.parametrize(
+ "version", ["1.0", "1.2.3.dev1+ge871260", "1.2.3.dev15+ge871260.d20180625"]
+)
+def test_dump_version_works_with_pretend(version, tmpdir, monkeypatch):
+ monkeypatch.setenv(PRETEND_KEY, version)
+ get_version(write_to=str(tmpdir.join("VERSION.txt")))
+ assert tmpdir.join("VERSION.txt").read() == version
+
+
+def test_has_command(recwarn):
+ assert not has_command("yadayada_setuptools_aint_ne")
+ msg = recwarn.pop()
+ assert "yadayada" in str(msg.message)
+
+
+@pytest.mark.parametrize(
+ "tag, expected_version",
+ [
+ ("1.1", "1.1"),
+ ("release-1.1", "1.1"),
+ pytest.param("3.3.1-rc26", "3.3.1rc26", marks=pytest.mark.issue(266)),
+ ],
+)
+def test_tag_to_version(tag, expected_version):
+ version = str(tag_to_version(tag))
+ assert version == expected_version
--- /dev/null
+import sys
+
+from setuptools_scm import integration
+from setuptools_scm.utils import do, has_command
+from setuptools_scm import git
+import pytest
+from datetime import datetime
+from os.path import join as opj
+from setuptools_scm.file_finder_git import git_find_files
+import warnings
+
+
+skip_if_win_27 = pytest.mark.skipif(
+ sys.platform == "win32" and sys.version_info[0] < 3,
+ reason="Not supported on Windows + Python 2.7",
+)
+
+
+with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ if not has_command("git"):
+ pytestmark = pytest.mark.skip(reason="git executable not found")
+
+
+@pytest.fixture
+def wd(wd, monkeypatch):
+ monkeypatch.delenv("HOME", raising=False)
+ wd("git init")
+ wd("git config user.email test@example.com")
+ wd('git config user.name "a test"')
+ wd.add_command = "git add ."
+ wd.commit_command = "git commit -m test-{reason}"
+ return wd
+
+
+@pytest.mark.parametrize(
+ "given, tag, number, node, dirty",
+ [
+ ("3.3.1-rc26-0-g9df187b", "3.3.1-rc26", 0, "g9df187b", False),
+ ("17.33.0-rc-17-g38c3047c0", "17.33.0-rc", 17, "g38c3047c0", False),
+ ],
+)
+def test_parse_describe_output(given, tag, number, node, dirty):
+ parsed = git._git_parse_describe(given)
+ assert parsed == (tag, number, node, dirty)
+
+
+def test_root_relative_to(tmpdir, wd, monkeypatch):
+ monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+ p = wd.cwd.joinpath("sub/package")
+ p.mkdir(parents=True)
+ p.joinpath("setup.py").write_text(
+ u"""from setuptools import setup
+setup(use_scm_version={"root": "../..",
+ "relative_to": __file__})
+"""
+ )
+ res = do((sys.executable, "setup.py", "--version"), p)
+ assert res == "0.1.dev0"
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/298")
+@pytest.mark.issue(403)
+def test_file_finder_no_history(wd, caplog):
+ file_list = git_find_files(str(wd.cwd))
+ assert file_list == []
+
+ assert "listing git files failed - pretending there aren't any" in caplog.text
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/281")
+def test_parse_call_order(wd):
+ git.parse(str(wd.cwd), git.DEFAULT_DESCRIBE)
+
+
+def test_version_from_git(wd):
+ assert wd.version == "0.1.dev0"
+
+ wd.commit_testfile()
+ assert wd.version.startswith("0.1.dev1+g")
+ assert not wd.version.endswith("1-")
+
+ wd("git tag v0.1")
+ assert wd.version == "0.1"
+
+ wd.write("test.txt", "test2")
+ assert wd.version.startswith("0.2.dev0+g")
+
+ wd.commit_testfile()
+ assert wd.version.startswith("0.2.dev1+g")
+
+ wd("git tag version-0.2")
+ assert wd.version.startswith("0.2")
+
+ wd.commit_testfile()
+ wd("git tag version-0.2.post210+gbe48adfpost3+g0cc25f2")
+ with pytest.warns(
+ UserWarning, match="tag '.*' will be stripped of its suffix '.*'"
+ ):
+ assert wd.version.startswith("0.2")
+
+ wd.commit_testfile()
+ wd("git tag 17.33.0-rc")
+ assert wd.version == "17.33.0rc0"
+
+
+@pytest.mark.issue(179)
+def test_unicode_version_scheme(wd):
+ scheme = b"guess-next-dev".decode("ascii")
+ assert wd.get_version(version_scheme=scheme)
+
+
+@pytest.mark.issue(108)
+@pytest.mark.issue(109)
+def test_git_worktree(wd):
+ wd.write("test.txt", "test2")
+ # untracked files dont change the state
+ assert wd.version == "0.1.dev0"
+ wd("git add test.txt")
+ assert wd.version.startswith("0.1.dev0+d")
+
+
+@pytest.mark.issue(86)
+def test_git_dirty_notag(wd):
+ wd.commit_testfile()
+ wd.write("test.txt", "test2")
+ wd("git add test.txt")
+ assert wd.version.startswith("0.1.dev1")
+ # the date on the tag is in UTC
+ today = datetime.utcnow().date()
+ # we are dirty, check for the tag
+ assert today.strftime(".d%Y%m%d") in wd.version
+
+
+@pytest.mark.issue(193)
+def test_git_worktree_support(wd, tmpdir):
+ wd.commit_testfile()
+ worktree = tmpdir.join("work_tree")
+ wd("git worktree add -b work-tree %s" % worktree)
+
+ res = do([sys.executable, "-m", "setuptools_scm", "ls"], cwd=worktree)
+ assert str(worktree) in res
+
+
+@pytest.fixture
+def shallow_wd(wd, tmpdir):
+ wd.commit_testfile()
+ wd.commit_testfile()
+ wd.commit_testfile()
+ target = tmpdir.join("wd_shallow")
+ do(["git", "clone", "file://%s" % wd.cwd, str(target), "--depth=1"])
+ return target
+
+
+def test_git_parse_shallow_warns(shallow_wd, recwarn):
+ git.parse(str(shallow_wd))
+ msg = recwarn.pop()
+ assert "is shallow and may cause errors" in str(msg.message)
+
+
+def test_git_parse_shallow_fail(shallow_wd):
+ with pytest.raises(ValueError) as einfo:
+ git.parse(str(shallow_wd), pre_parse=git.fail_on_shallow)
+
+ assert "git fetch" in str(einfo.value)
+
+
+def test_git_shallow_autocorrect(shallow_wd, recwarn):
+ git.parse(str(shallow_wd), pre_parse=git.fetch_on_shallow)
+ msg = recwarn.pop()
+ assert "git fetch was used to rectify" in str(msg.message)
+ git.parse(str(shallow_wd), pre_parse=git.fail_on_shallow)
+
+
+def test_find_files_stop_at_root_git(wd):
+ wd.commit_testfile()
+ project = wd.cwd / "project"
+ project.mkdir()
+ project.joinpath("setup.cfg").touch()
+ assert integration.find_files(str(project)) == []
+
+
+@pytest.mark.issue(128)
+def test_parse_no_worktree(tmpdir):
+ ret = git.parse(str(tmpdir))
+ assert ret is None
+
+
+def test_alphanumeric_tags_match(wd):
+ wd.commit_testfile()
+ wd("git tag newstyle-development-started")
+ assert wd.version.startswith("0.1.dev1+g")
+
+
+@skip_if_win_27
+def test_git_archive_export_ignore(wd, monkeypatch):
+ wd.write("test1.txt", "test")
+ wd.write("test2.txt", "test")
+ wd.write(
+ ".git/info/attributes",
+ # Explicitly include test1.txt so that the test is not affected by
+ # a potentially global gitattributes file on the test machine.
+ "/test1.txt -export-ignore\n/test2.txt export-ignore",
+ )
+ wd("git add test1.txt test2.txt")
+ wd.commit()
+ monkeypatch.chdir(wd.cwd)
+ assert integration.find_files(".") == [opj(".", "test1.txt")]
+
+
+@skip_if_win_27
+@pytest.mark.issue(228)
+def test_git_archive_subdirectory(wd, monkeypatch):
+ wd("mkdir foobar")
+ wd.write("foobar/test1.txt", "test")
+ wd("git add foobar")
+ wd.commit()
+ monkeypatch.chdir(wd.cwd)
+ assert integration.find_files(".") == [opj(".", "foobar", "test1.txt")]
+
+
+@skip_if_win_27
+@pytest.mark.issue(251)
+def test_git_archive_run_from_subdirectory(wd, monkeypatch):
+ wd("mkdir foobar")
+ wd.write("foobar/test1.txt", "test")
+ wd("git add foobar")
+ wd.commit()
+ monkeypatch.chdir(wd.cwd / "foobar")
+ assert integration.find_files(".") == [opj(".", "test1.txt")]
+
+
+def test_git_feature_branch_increments_major(wd):
+ wd.commit_testfile()
+ wd("git tag 1.0.0")
+ wd.commit_testfile()
+ assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1")
+ wd("git checkout -b feature/fun")
+ wd.commit_testfile()
+ assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0")
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/303")
+def test_not_matching_tags(wd):
+ wd.commit_testfile()
+ wd("git tag apache-arrow-0.11.1")
+ wd.commit_testfile()
+ wd("git tag apache-arrow-js-0.9.9")
+ wd.commit_testfile()
+ assert wd.get_version(
+ tag_regex=r"^apache-arrow-([\.0-9]+)$",
+ git_describe_command="git describe --dirty --tags --long --exclude *js* ",
+ ).startswith("0.11.2")
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/411")
+@pytest.mark.xfail(reason="https://github.com/pypa/setuptools_scm/issues/449")
+def test_non_dotted_version(wd):
+ wd.commit_testfile()
+ wd("git tag apache-arrow-1")
+ wd.commit_testfile()
+ assert wd.get_version().startswith("2")
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/381")
+def test_gitdir(monkeypatch, wd):
+ """
+ """
+ wd.commit_testfile()
+ normal = wd.version
+ # git hooks set this and break subsequent setuptools_scm unless we clean
+ monkeypatch.setenv("GIT_DIR", __file__)
+ assert wd.version == normal
--- /dev/null
+import sys
+
+import pytest
+
+from setuptools_scm.utils import do
+
+
+@pytest.fixture
+def wd(wd):
+ try:
+ wd("git init")
+ except OSError:
+ pytest.skip("git executable not found")
+
+ wd("git config user.email test@example.com")
+ wd('git config user.name "a test"')
+ wd.add_command = "git add ."
+ wd.commit_command = "git commit -m test-{reason}"
+ return wd
+
+
+def test_pyproject_support(tmpdir, monkeypatch):
+ pytest.importorskip("toml")
+ monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+ pkg = tmpdir.ensure("package", dir=42)
+ pkg.join("pyproject.toml").write(
+ """[tool.setuptools_scm]
+fallback_version = "12.34"
+"""
+ )
+ pkg.join("setup.py").write("__import__('setuptools').setup()")
+ res = do((sys.executable, "setup.py", "--version"), pkg)
+ assert res == "12.34"
+
+
+def test_pyproject_support_with_git(tmpdir, monkeypatch, wd):
+ monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+ pkg = tmpdir.join("wd")
+ pkg.join("pyproject.toml").write("""[tool.setuptools_scm]""")
+ pkg.join("setup.py").write("__import__('setuptools').setup()")
+ res = do((sys.executable, "setup.py", "--version"), pkg)
+ assert res == "0.1.dev0"
--- /dev/null
+import os.path
+
+
+def test_main():
+ mainfile = os.path.join(
+ os.path.dirname(__file__), "..", "src", "setuptools_scm", "__main__.py"
+ )
+ with open(mainfile) as f:
+ code = compile(f.read(), "__main__.py", "exec")
+ exec(code)
--- /dev/null
+from setuptools_scm import format_version
+from setuptools_scm.hg import archival_to_version, parse
+from setuptools_scm import integration
+from setuptools_scm.config import Configuration
+from setuptools_scm.utils import has_command
+import pytest
+import warnings
+
+
+with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ if not has_command("hg"):
+ pytestmark = pytest.mark.skip(reason="hg executable not found")
+
+
+@pytest.fixture
+def wd(wd):
+ wd("hg init")
+ wd.add_command = "hg add ."
+ wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"'
+ return wd
+
+
+archival_mapping = {
+ "1.0": {"tag": "1.0"},
+ "1.1.dev3+h000000000000": {
+ "latesttag": "1.0",
+ "latesttagdistance": "3",
+ "node": "0" * 20,
+ },
+ "0.0": {"node": "0" * 20},
+ "1.2.2": {"tag": "release-1.2.2"},
+ "1.2.2.dev0": {"tag": "release-1.2.2.dev"},
+}
+
+
+@pytest.mark.parametrize("expected,data", sorted(archival_mapping.items()))
+def test_archival_to_version(expected, data):
+ config = Configuration()
+ version = archival_to_version(data, config=config)
+ assert (
+ format_version(
+ version, version_scheme="guess-next-dev", local_scheme="node-and-date"
+ )
+ == expected
+ )
+
+
+def test_find_files_stop_at_root_hg(wd, monkeypatch):
+ wd.commit_testfile()
+ project = wd.cwd / "project"
+ project.mkdir()
+ project.joinpath("setup.cfg").touch()
+ # setup.cfg has not been committed
+ assert integration.find_files(str(project)) == []
+ # issue 251
+ wd.add_and_commit()
+ monkeypatch.chdir(project)
+ assert integration.find_files() == ["setup.cfg"]
+
+
+# XXX: better tests for tag prefixes
+def test_version_from_hg_id(wd):
+ assert wd.version == "0.0"
+
+ wd.commit_testfile()
+ assert wd.version.startswith("0.1.dev2+")
+
+ # tagging commit is considered the tag
+ wd('hg tag v0.1 -u test -d "0 0"')
+ assert wd.version == "0.1"
+
+ wd.commit_testfile()
+ assert wd.version.startswith("0.2.dev2")
+
+ wd("hg up v0.1")
+ assert wd.version == "0.1"
+
+ # commit originating from the taged revision
+ # that is not a actual tag
+ wd.commit_testfile()
+ assert wd.version.startswith("0.2.dev1+")
+
+ # several tags
+ wd("hg up")
+ wd('hg tag v0.2 -u test -d "0 0"')
+ wd('hg tag v0.3 -u test -d "0 0" -r v0.2')
+ assert wd.version == "0.3"
+
+
+def test_version_from_archival(wd):
+ # entrypoints are unordered,
+ # cleaning the wd ensure this test wont break randomly
+ wd.cwd.joinpath(".hg").rename(wd.cwd / ".nothg")
+ wd.write(".hg_archival.txt", "node: 000000000000\n" "tag: 0.1\n")
+ assert wd.version == "0.1"
+
+ wd.write(
+ ".hg_archival.txt",
+ "node: 000000000000\n" "latesttag: 0.1\n" "latesttagdistance: 3\n",
+ )
+
+ assert wd.version == "0.2.dev3+h000000000000"
+
+
+@pytest.mark.issue("#72")
+def test_version_in_merge(wd):
+ wd.commit_testfile()
+ wd.commit_testfile()
+ wd("hg up 0")
+ wd.commit_testfile()
+ wd("hg merge --tool :merge")
+ assert wd.version is not None
+
+
+@pytest.mark.issue(128)
+def test_parse_no_worktree(tmpdir):
+ ret = parse(str(tmpdir))
+ assert ret is None
+
+
+@pytest.fixture
+def version_1_0(wd):
+ wd("hg branch default")
+ wd.commit_testfile()
+ wd('hg tag 1.0.0 -u test -d "0 0"')
+ return wd
+
+
+@pytest.fixture
+def pre_merge_commit_after_tag(wd, version_1_0):
+ wd("hg branch testbranch")
+ wd.write("branchfile", "branchtext")
+ wd(wd.add_command)
+ wd.commit()
+ wd("hg update default")
+ wd("hg merge testbranch")
+ return wd
+
+
+@pytest.mark.usefixtures("pre_merge_commit_after_tag")
+def test_version_bump_before_merge_commit(wd):
+ assert wd.version.startswith("1.0.1.dev1+")
+
+
+@pytest.mark.issue(219)
+@pytest.mark.usefixtures("pre_merge_commit_after_tag")
+def test_version_bump_from_merge_commit(wd):
+ wd.commit()
+ assert wd.version.startswith("1.0.1.dev3+") # issue 219
+
+
+@pytest.mark.usefixtures("version_1_0")
+def test_version_bump_from_commit_including_hgtag_mods(wd):
+ """ Test the case where a commit includes changes to .hgtags and other files
+ """
+ with wd.cwd.joinpath(".hgtags").open("ab") as tagfile:
+ tagfile.write(b"0 0\n")
+ wd.write("branchfile", "branchtext")
+ wd(wd.add_command)
+ assert wd.version.startswith("1.0.1.dev1+") # bump from dirty version
+ wd.commit() # commits both the testfile _and_ .hgtags
+ assert wd.version.startswith("1.0.1.dev2+")
+
+
+@pytest.mark.issue(229)
+@pytest.mark.usefixtures("version_1_0")
+def test_latest_tag_detection(wd):
+ """ Tests that tags not containing a "." are ignored, the same as for git.
+ Note that will be superceded by the fix for pypa/setuptools_scm/issues/235
+ """
+ wd('hg tag some-random-tag -u test -d "0 0"')
+ assert wd.version == "1.0.0"
+
+
+@pytest.mark.usefixtures("version_1_0")
+def test_feature_branch_increments_major(wd):
+
+ wd.commit_testfile()
+ assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1")
+ wd("hg branch feature/fun")
+ assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0")
--- /dev/null
+import sys
+import subprocess
+
+from setuptools_scm import get_version
+from setuptools_scm.git import parse
+from setuptools_scm.utils import do_ex, do
+
+import pytest
+
+
+def test_pkginfo_noscmroot(tmpdir, monkeypatch):
+ """if we are indeed a sdist, the root does not apply"""
+ monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+
+ # we should get the version from pkg-info if git is broken
+ p = tmpdir.ensure("sub/package", dir=1)
+ tmpdir.mkdir(".git")
+ p.join("setup.py").write(
+ "from setuptools import setup;" 'setup(use_scm_version={"root": ".."})'
+ )
+
+ _, stderr, ret = do_ex((sys.executable, "setup.py", "--version"), p)
+ assert "setuptools-scm was unable to detect version for" in stderr
+ assert ret == 1
+
+ p.join("PKG-INFO").write("Version: 1.0")
+ res = do((sys.executable, "setup.py", "--version"), p)
+ assert res == "1.0"
+
+ try:
+ do("git init", p.dirpath())
+ except OSError:
+ pass
+ else:
+ res = do((sys.executable, "setup.py", "--version"), p)
+ assert res == "0.1.dev0"
+
+
+def test_pip_egg_info(tmpdir, monkeypatch):
+ """if we are indeed a sdist, the root does not apply"""
+
+ # we should get the version from pkg-info if git is broken
+ p = tmpdir.ensure("sub/package", dir=1)
+ tmpdir.mkdir(".git")
+ p.join("setup.py").write(
+ "from setuptools import setup;" 'setup(use_scm_version={"root": ".."})'
+ )
+
+ with pytest.raises(LookupError):
+ get_version(root=p.strpath, fallback_root=p.strpath)
+
+ p.ensure("pip-egg-info/random.egg-info/PKG-INFO").write("Version: 1.0")
+ assert get_version(root=p.strpath, fallback_root=p.strpath) == "1.0"
+
+
+@pytest.mark.issue(164)
+def test_pip_download(tmpdir, monkeypatch):
+ monkeypatch.chdir(tmpdir)
+ subprocess.check_call([sys.executable, "-m", "pip", "download", "lz4==0.9.0"])
+
+
+def test_use_scm_version_callable(tmpdir, monkeypatch):
+ """use of callable as use_scm_version argument"""
+ monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+
+ p = tmpdir.ensure("sub/package", dir=1)
+ p.join("setup.py").write(
+ """from setuptools import setup
+def vcfg():
+ from setuptools_scm.version import guess_next_dev_version
+ def vs(v):
+ return guess_next_dev_version(v)
+ return {"version_scheme": vs}
+setup(use_scm_version=vcfg)
+"""
+ )
+ p.join("PKG-INFO").write("Version: 1.0")
+
+ res = do((sys.executable, "setup.py", "--version"), p)
+ assert res == "1.0"
+
+
+@pytest.mark.skipif(sys.platform != "win32", reason="this bug is only valid on windows")
+def test_case_mismatch_on_windows_git(tmpdir):
+ """Case insensitive path checks on Windows"""
+ p = tmpdir.ensure("CapitalizedDir", dir=1)
+
+ do("git init", p)
+ res = parse(str(p).lower())
+ assert res is not None
--- /dev/null
+"""
+integration tests that check setuptools version support
+"""
+import sys
+import os
+import subprocess
+import pytest
+
+pytestmark = [
+ pytest.mark.skipif(
+ "sys.version_info >= (3,6,0)",
+ reason="integration with old versions no longer needed on py3.6+",
+ ),
+ pytest.mark.xfail(
+ sys.platform == "win32", reason="path behaves unexpected on windows ci"
+ ),
+]
+
+
+@pytest.fixture(scope="session")
+def get_setuptools_packagedir(request):
+ targets = request.config.cache.makedir("setuptools_installs")
+
+ def makeinstall(version):
+ target = targets.ensure(version, dir=1)
+ subprocess.check_call(
+ [
+ sys.executable,
+ "-m",
+ "pip",
+ "install",
+ "--no-binary",
+ "setuptools",
+ "setuptools==" + version,
+ "-t",
+ str(target),
+ ]
+ )
+ return target
+
+ return makeinstall
+
+
+SCRIPT = """
+from __future__ import print_function
+import sys
+import setuptools
+print(setuptools.__version__, 'expected', sys.argv[1])
+import setuptools_scm.version
+from setuptools_scm.__main__ import main
+main()
+"""
+
+
+def check(packagedir, expected_version, **env):
+
+ old_pythonpath = os.environ.get("PYTHONPATH")
+ if old_pythonpath:
+ pythonpath = "{}:{}".format(old_pythonpath, packagedir)
+ else:
+ pythonpath = str(packagedir)
+ subprocess.check_call(
+ [sys.executable, "-c", SCRIPT, expected_version],
+ env=dict(os.environ, PYTHONPATH=pythonpath, **env),
+ )
+
+
+def test_old_setuptools_fails(get_setuptools_packagedir):
+ packagedir = get_setuptools_packagedir("0.9.8")
+ with pytest.raises(subprocess.CalledProcessError):
+ check(packagedir, "0.9.8")
+
+
+def test_old_setuptools_allows_with_warnings(get_setuptools_packagedir):
+
+ packagedir = get_setuptools_packagedir("0.9.8")
+ # filter using warning since in the early python startup
+ check(packagedir, "0.9.8", PYTHONWARNINGS="once::Warning")
+
+
+def test_distlib_setuptools_works(get_setuptools_packagedir):
+ packagedir = get_setuptools_packagedir("12.0.1")
+ check(packagedir, "12.0.1")
--- /dev/null
+import pytest
+from setuptools_scm.config import Configuration
+from setuptools_scm.version import (
+ meta,
+ simplified_semver_version,
+ release_branch_semver_version,
+ tags_to_versions,
+)
+
+
+c = Configuration()
+
+
+@pytest.mark.parametrize(
+ "version, expected_next",
+ [
+ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"),
+ pytest.param(meta("1.0", config=c), "1.0.0", id="short_tag"),
+ pytest.param(
+ meta("1.0.0", distance=2, branch="default", config=c),
+ "1.0.1.dev2",
+ id="normal_branch",
+ ),
+ pytest.param(
+ meta("1.0", distance=2, branch="default", config=c),
+ "1.0.1.dev2",
+ id="normal_branch_short_tag",
+ ),
+ pytest.param(
+ meta("1.0.0", distance=2, branch="feature", config=c),
+ "1.1.0.dev2",
+ id="feature_branch",
+ ),
+ pytest.param(
+ meta("1.0", distance=2, branch="feature", config=c),
+ "1.1.0.dev2",
+ id="feature_branch_short_tag",
+ ),
+ pytest.param(
+ meta("1.0.0", distance=2, branch="features/test", config=c),
+ "1.1.0.dev2",
+ id="feature_in_branch",
+ ),
+ ],
+)
+def test_next_semver(version, expected_next):
+ computed = simplified_semver_version(version)
+ assert computed == expected_next
+
+
+@pytest.mark.parametrize(
+ "version, expected_next",
+ [
+ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"),
+ pytest.param(
+ meta("1.0.0", distance=2, branch="master", config=c),
+ "1.1.0.dev2",
+ id="development_branch",
+ ),
+ pytest.param(
+ meta("1.0.0rc1", distance=2, branch="master", config=c),
+ "1.1.0.dev2",
+ id="development_branch_release_candidate",
+ ),
+ pytest.param(
+ meta("1.0.0", distance=2, branch="maintenance/1.0.x", config=c),
+ "1.0.1.dev2",
+ id="release_branch_legacy_version",
+ ),
+ pytest.param(
+ meta("1.0.0", distance=2, branch="release-1.0", config=c),
+ "1.0.1.dev2",
+ id="release_branch_with_prefix",
+ ),
+ pytest.param(
+ meta("1.0.0", distance=2, branch="bugfix/3434", config=c),
+ "1.1.0.dev2",
+ id="false_positive_release_branch",
+ ),
+ ],
+)
+def test_next_release_branch_semver(version, expected_next):
+ computed = release_branch_semver_version(version)
+ assert computed == expected_next
+
+
+@pytest.mark.parametrize(
+ "tag, expected",
+ [
+ pytest.param("v1.0.0", "1.0.0"),
+ pytest.param("v1.0.0-rc.1", "1.0.0rc1"),
+ pytest.param("v1.0.0-rc.1+-25259o4382757gjurh54", "1.0.0rc1"),
+ ],
+)
+def test_tag_regex1(tag, expected):
+ config = Configuration()
+ if "+" in tag:
+ # pytest bug wrt cardinality
+ with pytest.warns(UserWarning):
+ result = meta(tag, config=config)
+ else:
+ result = meta(tag, config=config)
+
+ assert result.tag.public == expected
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/286")
+def test_tags_to_versions():
+ config = Configuration()
+ versions = tags_to_versions(["1.0", "2.0", "3.0"], config=config)
+ assert isinstance(versions, list) # enable subscription
--- /dev/null
+[tox]
+envlist=py{27,34,35,36,37,38}-test,flake8,check_readme,py{27,37}-selfcheck
+
+[pytest]
+testpaths=testing
+filterwarnings=error
+markers=
+ issue(id): reference to github issue
+
+[flake8]
+max-complexity = 10
+max-line-length = 88
+ignore=E203,W503
+exclude=
+ .git,
+ .tox,
+ .env,
+ .venv,
+ .pytest_cache,
+ __pycache__,
+ ./setuptools_scm/win_py31_compat.py
+
+[testenv]
+usedevelop=True
+skip_install=
+ selfcheck: True
+ test: False
+deps=
+ pytest
+ setuptools >= 42
+commands=
+ test: pytest []
+ selfcheck: python setup.py --version
+extras =
+ toml
+
+[testenv:flake8]
+skip_install=True
+deps=
+ flake8
+ mccabe
+commands =
+ flake8 src/setuptools_scm/ testing/ setup.py --exclude=setuptools_scm/win_py31_compat.py
+
+[testenv:check_readme]
+skip_install=True
+setenv = SETUPTOOLS_SCM_PRETEND_VERSION=2.0
+deps=
+ readme
+ check-manifest
+commands=
+ python setup.py check -r
+ rst2html.py README.rst {envlogdir}/README.html --strict []
+ check-manifest
+
+[testenv:upload]
+deps=
+ wheel
+ twine
+commands=
+ python setup.py clean --all rotate -k - -m .whl,.tar.gz,.zip
+ python setup.py -q egg_info
+ python setup.py -q sdist --formats zip bdist_wheel register
+
+
+
+[testenv:dist]
+deps= wheel
+whitelist_externals = rm
+commands=
+ python setup.py -q clean --all
+ python setup.py -q rotate -k 0 -m .egg,.zip,.whl,.tar.gz
+ python setup.py -q egg_info
+ python setup.py -q sdist --formats zip,bztar bdist_wheel upload
+
+[testenv:devpi]
+deps=
+ devpi-client
+commands =
+ python setup.py -q egg_info
+ devpi upload --from-dir dist
+
+#XXX: envs for hg versions