summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonny Pfannschmidt <ronny.pfannschmidt@redhat.com>2020-12-11 22:52:22 +0100
committerRonny Pfannschmidt <ronny.pfannschmidt@redhat.com>2020-12-11 22:52:22 +0100
commit7afad49aaaac94604682735cf32de226c41c28f6 (patch)
treec792cd3ce958b1cd99698920f380651c7a58cd46
parent5e1a9694f1231f668988a491923b3ca7e7851718 (diff)
parentbda1b523f77e15e62a788641c7e6dd41c24a1576 (diff)
downloadsetuptools-scm-7afad49aaaac94604682735cf32de226c41c28f6.tar.gz
Merge branch 'master' into hbasria/master
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.github/workflows/pre-commit.yml20
-rw-r--r--.github/workflows/python-tests.yml155
-rw-r--r--.gitignore3
-rw-r--r--.pre-commit-config.yaml10
-rw-r--r--.travis.yml98
-rw-r--r--CHANGELOG.rst103
-rw-r--r--MANIFEST.in4
-rw-r--r--README.rst247
-rw-r--r--appveyor.yml45
-rw-r--r--default.nix14
-rw-r--r--pyproject.toml3
-rw-r--r--setup.cfg87
-rw-r--r--setup.py73
-rw-r--r--src/setuptools_scm/__init__.py71
-rw-r--r--src/setuptools_scm/config.py66
-rw-r--r--src/setuptools_scm/file_finder.py23
-rw-r--r--src/setuptools_scm/file_finder_git.py11
-rw-r--r--src/setuptools_scm/file_finder_hg.py3
-rw-r--r--src/setuptools_scm/git.py9
-rw-r--r--src/setuptools_scm/hacks.py10
-rw-r--r--src/setuptools_scm/hg.py18
-rw-r--r--src/setuptools_scm/integration.py31
-rw-r--r--src/setuptools_scm/utils.py41
-rw-r--r--src/setuptools_scm/version.py116
-rw-r--r--testing/check_self_install.py5
-rw-r--r--testing/conftest.py18
-rwxr-xr-xtesting/play_out_381.bash23
-rw-r--r--testing/runtests_travis.py21
-rw-r--r--testing/test_basic_api.py21
-rw-r--r--testing/test_config.py21
-rw-r--r--testing/test_file_finder.py141
-rw-r--r--testing/test_functions.py20
-rw-r--r--testing/test_git.py111
-rw-r--r--testing/test_integration.py59
-rw-r--r--testing/test_mercurial.py39
-rw-r--r--testing/test_regressions.py10
-rw-r--r--testing/test_version.py115
-rw-r--r--tox.ini24
39 files changed, 1350 insertions, 540 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..ac779ab
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+tidelift: pypi/setuptools-scm
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000..1756a18
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,20 @@
+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@v2
+ - name: set PY
+ run: echo "PY=$(python --version --version | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
+ - 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
diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
new file mode 100644
index 0000000..ec2390b
--- /dev/null
+++ b/.github/workflows/python-tests.yml
@@ -0,0 +1,155 @@
+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@v2.0.1
+ 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@v2.0.1
+ 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@v2
+ 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 }}
diff --git a/.gitignore b/.gitignore
index 54ef3e6..66b1ad7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,8 @@ __pycache__/
# Distribution / packaging
.env/
env/
+.venv/
+venv/
build/
dist/
.eggs/
@@ -30,6 +32,7 @@ lib64/
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
+pip-wheel-metadata
# Unit test / coverage reports
htmlcov/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a80f778..0063a11 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,20 +1,22 @@
exclude: setuptools_scm/win_py31_compat.py
repos:
- repo: https://github.com/ambv/black
- rev: 18.4a4
+ rev: 20.8b1
hooks:
- id: black
args: [--safe, --quiet]
- python_version: python3.6
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v1.2.3
+ rev: v3.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: debug-statements
+- repo: https://gitlab.com/pycqa/flake8
+ rev: 3.8.4
+ hooks:
- id: flake8
- repo: https://github.com/asottile/pyupgrade
- rev: v1.2.0
+ rev: v2.7.4
hooks:
- id: pyupgrade
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 75c3c0e..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,98 +0,0 @@
-language: python
-sudo: false
-
-stages:
-- linting
-- test
-- deploy
-
-
-credentials:
- - &pypi
- provider: pypi
- user: ronny
- # use when testing, may require recreation of the user and its credentials
- # server: https://test.pypi.org/legacy/ # Remove for deployment to official PyPi repo
- password:
- secure: QGJhDXmfFDKysMJJV/ONGaHHzG/aImhU3DdhEP63d657iQSn/Cb4EG/l9YmVnRzpJ94nSDXZB8YwptR7rid0bOtidb32lxN8n6UiWILCXWeAN2FE+tT9/0xIct4HUJZ8OttD1gft/Di722Gy+s9PzFwjwrV4efkxCzgjfYOjkMeq3aO6NoG3ur0iZXJh7ODwLp4sRFep2NpIEaXm2qMdnnXpck6bJ1q/NtvPx9CAZivd9HYa0evg5j1ENTz1mXXafhgF+0vRCBXA33xJuysO6CKtk+2mizL1QHfosOERiKl9+zPyZw+VvSchbCVwgxrMSiRcpGag+4SegyHrj1M/2YqfFzMF/yuFGcqXl2VkEqlnBQOVMNW3Kdcmnm+caNbddnv+M384WFz4nV8nWjcsD5l27+XlMWfuvskDIvZKtVCXmmbtqgwM4tqoYd6uxbnooRfwINTGx8sNzKP10xkaesB3ZBCEpecOKA1AXUAZ74RfYWWExv6eIuVGwyIJmOcD8M/17N8g58GxxO+88gx50EuhyNiRjYZDUipfVydfJwBwpD+p695NixUMITuksucQftjHsQp+laGWJlDIPvFwI85wDJUYAyrzn6L1W+smkm1bGomuliW2MJfxeSZAmSk4CE5VOpIWQTBmDLR3pxBhcaqzwdd4mAWvMi/fpM4yJJI=
- on:
- tags: yes
-python:
-- '2.7'
-- '3.4'
-- '3.5'
-- '3.6'
-- '3.7'
-- '3.8-dev'
-dist: xenial # needed for 3.7+
-env:
-- TOXENV=py-test
-
-jobs:
- include:
- - stage: linting
- name: check readme
- python: '3.6'
- env: TOXENV=check_readme
- # - stage: test
- # python: '3.7'
- # dist: xenial
- - stage: test
- python: '2.7'
- env: SELFINSTALL=1
- - stage: test
- python: '3.6'
- env: SELFINSTALL=1
-
- - stage: linting
- python: '3.6'
- name: validate pre-commit
- env:
- install:
- - pip install pre-commit
- - pre-commit install-hooks
- script:
- - pre-commit run --all-files
-
- - &deploy
- stage: deploy
- name: "modern distributions"
- python: '3.6'
- install:
- - pip install -U pip setuptools wheel
- script: skip
- deploy:
- <<: *pypi
- distributions: "sdist bdist_wheel"
-
- - &bdist_egg
- <<: *deploy
- name: "python eggs 2.7"
- python: '2.7'
- deploy:
- <<: *pypi
- distributions: "bdist_egg"
- - <<: *bdist_egg
- name: "python eggs 3.4"
- python: '3.4'
- - <<: *bdist_egg
- name: "python eggs 3.5"
- python: '3.5'
- - <<: *bdist_egg
- name: "python eggs 3.6"
- python: '3.6'
- - <<: *bdist_egg
- name: "python eggs 3.7"
- python: '3.7'
- - <<: *bdist_egg
- name: "python eggs 3.8"
- python: '3.8-dev'
-
-cache:
- directories:
- - $HOME/.cache/pip
- - $HOME/.cache/pre-commit
-
-install: pip install tox
-script:
-- python testing/runtests_travis.py
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c6a1968..83666ae 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,5 +1,108 @@
+v5.0.0
+======
+
+
+Breaking changes:
+* fix #339: strict errors on missing scms when parsing a scm dir to avoid false version lookups
+
+Bugfixes:
+
+* fix #352: add support for generally ignoring specific vcs roots
+* fix #471: better error for version bump failing on complex but accepted tag
+* fix #479: raise indicative error when tags carry non-parsable information
+* Add `no-guess-dev` which does no next version guessing, just adds `.post1.devN` in
+ case there are new commits after the tag
+* add python3.9
+* enhance documentation
+* consider SOURCE_DATE_EPOCH for versioning
+* add a version_tuple to write_to templates
+* fix #321: add suppport for the ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DISTRIBUTION_NAME}`` env var to target the pretend key
+* fix #142: clearly list supported scm
+* fix #213: better error message for non-zero dev numbers in tags
+
+
+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_prefix_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
======
diff --git a/MANIFEST.in b/MANIFEST.in
index df70a56..b74fbe0 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,9 +1,9 @@
exclude *.nix
-exclude appveyor.yml
-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
diff --git a/README.rst b/README.rst
index 2292385..f9193c1 100644
--- a/README.rst
+++ b/README.rst
@@ -1,21 +1,86 @@
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.
-It also handles file finders for the supported SCMs.
+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
+``setuptools_scm`` support the following scm out of the box:
+
+* git
+* mercurial
+
+
+
+.. image:: https://github.com/pypa/setuptools_scm/workflows/python%20tests+artifacts+release/badge.svg
+ :target: https://github.com/pypa/setuptools_scm/actions
+
+.. 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"
-.. image:: https://tidelift.com/badges/github/pypa/setuptools_scm
- :target: https://tidelift.com/subscription/pkg/pypi-setuptools_scm?utm_source=pypi-setuptools_scm&utm_medium=readme
``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:
@@ -42,25 +107,16 @@ Arguments to ``get_version()`` (see below) may be passed as a dictionary to
from setuptools import setup
setup(
...,
- use_scm_version = {"root": "..", "relative_to": __file__},
+ use_scm_version = {
+ "root": "..",
+ "relative_to": __file__,
+ "local_scheme": "node-and-timestamp"
+ },
setup_requires=['setuptools_scm'],
...,
)
-Once configured, you can access the version number in your package via
-``pkg_resources`` (`PEP-0396 <https://www.python.org/dev/peps/pep-0396>`_). For
-example:
-
-.. code:: python
-
- from pkg_resources import get_distribution, DistributionNotFound
- try:
- __version__ = get_distribution(__name__).version
- except DistributionNotFound:
- # package is not installed
- pass
-
-You can also confirm the version number locally via ``setup.py``:
+You can confirm the version number locally via ``setup.py``:
.. code-block:: shell
@@ -73,8 +129,8 @@ You can also confirm the version number locally via ``setup.py``:
not defined in ``setup.cfg``.
-``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>`_
@@ -133,6 +189,43 @@ than the project's root, you can use:
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 (added in Python 3.8)
+or the `importlib_metadata`_ backport:
+
+.. code:: python
+
+ from importlib.metadata import version, PackageNotFoundError
+
+ try:
+ __version__ = version("package-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("package-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
-----------------
@@ -152,7 +245,7 @@ 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
@@ -162,7 +255,7 @@ Notable Plugins
Default versioning scheme
---------------------------
+-------------------------
In the standard configuration ``setuptools_scm`` takes a look at three things:
@@ -177,13 +270,14 @@ no distance and clean:
distance and clean:
``{next_version}.dev{distance}+{scm letter}{revision hash}``
no distance and not clean:
- ``{tag}+dYYYMMMDD``
+ ``{tag}+dYYYYMMDD``
distance and not clean:
- ``{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYMMMDD``
+ ``{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}``.
@@ -204,7 +298,7 @@ accordingly.
Builtin mechanisms for obtaining version numbers
---------------------------------------------------
+------------------------------------------------
1. the SCM itself (git/hg)
2. ``.hg_archival`` files (mercurial archives)
@@ -220,7 +314,7 @@ 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 by your SCM. This eliminates
+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:
@@ -277,16 +371,28 @@ The currently supported configuration keys are:
supplying ``__file__``.
:tag_regex:
- A Python regex string to extract the version part from any SCM tag.
- The regex needs to contain three named groups prefix, version and suffix,
- where ``version`` captures the actual version information.
+ 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
+ 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.
@@ -337,11 +443,31 @@ Environment variables
its used as the primary source for the version number
in which case it will be a unparsed string
+
+:SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${UPPERCASED_DIST_NAME}:
+ when defined and not empty,
+ its used as the primary source for the version number
+ in which case it will be a unparsed string
+
+ it takes precedence over ``SETUPTOOLS_SCM_PRETEND_VERSION``
+
+
:SETUPTOOLS_SCM_DEBUG:
when defined and not empty,
a lot of debug information will be printed as part of ``setuptools_scm``
operating
+:SOURCE_DATE_EPOCH:
+ when defined, used as the timestamp from which the
+ ``node-and-date`` and ``node-and-timestamp`` local parts are
+ derived, otherwise the current time is used
+ (https://reproducible-builds.org/docs/source-date-epoch/)
+
+
+:SETUPTOOLS_SCM_IGNORE_VCS_ROOTS:
+ when defined, a ``os.pathsep`` separated list
+ of directory names to ignore for root finding
+
Extending setuptools_scm
------------------------
@@ -359,7 +485,7 @@ Adding a new SCM
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.version.ScmVersion`` instance
+ The return value MUST be a ``setuptools_scm.version.ScmVersion`` instance
created by the function ``setuptools_scm.version:meta``.
``setuptools_scm.files_command``
@@ -374,18 +500,35 @@ Version number construction
``setuptools_scm.version_scheme``
Configures how the version number is constructed given a
- ``setuptools.version.ScmVersion`` instance and should return a string
+ ``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)
- :post-release: generates post release versions (adds :code:`postN`)
+ :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`.
+ In case the tag ends with ``.dev0`` the version is not bumped
+ and custom ``.devN`` versions will trigger a error.
+ :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`.
+ :no-guess-dev: Does no next version guessing, just adds :code:`.post1.devN`
``setuptools_scm.local_scheme``
Configures how the local part of a version is rendered given a
- ``setuptools.version.ScmVersion`` instance and should return a string
+ ``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:
@@ -394,6 +537,8 @@ Version number construction
: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``
@@ -426,7 +571,7 @@ The callable must return the configuration.
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,
@@ -437,15 +582,35 @@ some environments require a test prior to install,
$ 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`_.
+`PSF Code of Conduct`_.
-.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
+.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
Security Contact
================
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 995e4b4..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-environment:
- matrix:
- - PYTHON: "C:\\Python27"
- TOXENV: "py-test"
-
- - PYTHON: "C:\\Python27-x64"
- TOXENV: "py-test"
-
- - PYTHON: "C:\\Python34"
- TOXENV: "py-test"
-
- - PYTHON: "C:\\Python34-x64"
- TOXENV: "py-test"
-
-init:
- - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
-
- - ECHO "Updating Environment"
- - python -m pip install -U setuptools
- - python -m pip install -U pip
- - python -m pip install -U wheel
- - python -m pip install -U tox
-
-
-install:
- # Check that we have the expected version and architecture for Python
- - python -c "import sys, os;sys.stdout.write(str(sys.version) + os.linesep)"
- - "python -c \"import struct; print(struct.calcsize('P') * 8)\""
- - python -m pip list
-
-build: false # Not a C# project, build stuff at the test step instead.
-
-test_script:
- # Build the compiled extension and run the project tests
- - python -m tox
-
-after_test:
- # If tests are successful, create a whl package for the project.
- - "%CMD_IN_ENV% python setup.py bdist_wheel"
- - ps: "ls dist"
-
-artifacts:
- # Archive the generated wheel package in the ci.appveyor.com build report.
- - path: dist\*
-
diff --git a/default.nix b/default.nix
deleted file mode 100644
index 5305bec..0000000
--- a/default.nix
+++ /dev/null
@@ -1,14 +0,0 @@
-{pkgs ? import <nixpkgs> {}}:
-with pkgs.pythonPackages;
-buildPythonPackage {
- name = "setuptools_scm";
- src = ./.;
- version = "git";
- buildInputs = [
- setuptools
- pip
- pytest
- pkgs.git
- pkgs.mercurial
- ];
-}
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..8fe2f47
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools>=42", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/setup.cfg
index 224b998..c0a58c8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,86 @@
-[bdist_wheel]
-universal=1
-
[metadata]
# ensure that the LICENSE file is included in the built wheels
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
+long_description_content_type=text/x-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
-[devpi:upload]
-formats=sdist,bdist_wheel
+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
+ no-guess-dev = setuptools_scm.version:no_guess_dev_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
diff --git a/setup.py b/setup.py
index 2eb533b..0b3e0c7 100644
--- a/setup.py
+++ b/setup.py
@@ -24,6 +24,9 @@ def scm_config():
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
@@ -46,73 +49,5 @@ def scm_config():
return dict(version=get_version(root=here, parse=parse, **config))
-with open("README.rst") as fp:
- long_description = fp.read()
-
-
-arguments = dict(
- name="setuptools_scm",
- url="https://github.com/pypa/setuptools_scm/",
- zip_safe=True,
- author="Ronny Pfannschmidt",
- author_email="opensource@ronnypfannschmidt.de",
- description=("the blessed package to manage your versions by scm tags"),
- long_description=long_description,
- license="MIT",
- packages=["setuptools_scm"],
- package_dir={"": "src"},
- entry_points="""
- [distutils.setup_keywords]
- use_scm_version = setuptools_scm.integration:version_keyword
-
- [setuptools.file_finders]
- setuptools_scm = setuptools_scm.integration:find_files
-
- [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
-
- [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
- """,
- classifiers=[
- "Development Status :: 4 - Beta",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python",
- "Programming Language :: Python :: 2",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 2.7",
- "Programming Language :: Python :: 3.4",
- "Programming Language :: Python :: 3.5",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Topic :: Software Development :: Libraries",
- "Topic :: Software Development :: Version Control",
- "Topic :: System :: Software Distribution",
- "Topic :: Utilities",
- ],
- python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
-)
-
if __name__ == "__main__":
- arguments.update(scm_config())
- setuptools.setup(**arguments)
+ setuptools.setup(**scm_config())
diff --git a/src/setuptools_scm/__init__.py b/src/setuptools_scm/__init__.py
index f3886ed..47b9e44 100644
--- a/src/setuptools_scm/__init__.py
+++ b/src/setuptools_scm/__init__.py
@@ -5,12 +5,18 @@
import os
import warnings
-from .config import Configuration
-from .utils import function_has_arg, string_types
+from .config import (
+ Configuration,
+ DEFAULT_VERSION_SCHEME,
+ DEFAULT_LOCAL_SCHEME,
+ DEFAULT_TAG_REGEX,
+)
+from .utils import function_has_arg, string_types, trace
from .version import format_version, meta
from .discover import iter_matching_entrypoints
PRETEND_KEY = "SETUPTOOLS_SCM_PRETEND_VERSION"
+PRETEND_KEY_NAMED = PRETEND_KEY + "_FOR_{name}"
TEMPLATES = {
".py": """\
@@ -18,6 +24,7 @@ TEMPLATES = {
# file generated by setuptools_scm
# don't change, don't track in version control
version = {version!r}
+version_tuple = {version_tuple!r}
""",
".txt": "{version}",
}
@@ -75,12 +82,34 @@ def dump_version(root, version, write_to, template=None):
os.path.splitext(target)[1], target
)
)
+
+ # version_tuple: each field is converted to an int if possible or kept as string
+ fields = tuple(version.split("."))
+ version_fields = []
+ for field in fields:
+ try:
+ v = int(field)
+ except ValueError:
+ v = field
+ version_fields.append(v)
+
with open(target, "w") as fp:
- fp.write(template.format(version=version))
+ fp.write(template.format(version=version, version_tuple=tuple(version_fields)))
def _do_parse(config):
- pretended = os.environ.get(PRETEND_KEY)
+
+ trace("dist name:", config.dist_name)
+ if config.dist_name is not None:
+ pretended = os.environ.get(
+ PRETEND_KEY_NAMED.format(name=config.dist_name.upper())
+ )
+ else:
+ pretended = None
+
+ if pretended is None:
+ pretended = os.environ.get(PRETEND_KEY)
+
if pretended:
# we use meta here since the pretended version
# must adhere to the pep to begin with
@@ -116,16 +145,18 @@ def _do_parse(config):
def get_version(
root=".",
- version_scheme="guess-next-dev",
- local_scheme="node-and-date",
+ version_scheme=DEFAULT_VERSION_SCHEME,
+ local_scheme=DEFAULT_LOCAL_SCHEME,
write_to=None,
write_to_template=None,
relative_to=None,
- tag_regex=None,
+ tag_regex=DEFAULT_TAG_REGEX,
+ parentdir_prefix_version=None,
fallback_version=None,
fallback_root=".",
parse=None,
git_describe_command=None,
+ dist_name=None,
):
"""
If supplied, relative_to should be a file from which root may
@@ -134,30 +165,24 @@ def get_version(
root of the repository by supplying ``__file__``.
"""
- config = Configuration()
- config.root = root
- config.fallback_root = fallback_root
- config.version_scheme = version_scheme
- config.local_scheme = local_scheme
- config.write_to = write_to
- config.write_to_template = write_to_template
- config.relative_to = relative_to
- config.tag_regex = tag_regex
- config.fallback_version = fallback_version
- config.parse = parse
- config.git_describe_command = git_describe_command
+ 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=version_scheme, local_scheme=local_scheme
+ parsed_version,
+ version_scheme=config.version_scheme,
+ local_scheme=config.local_scheme,
)
dump_version(
- root=root,
+ root=config.root,
version=version_string,
- write_to=write_to,
- template=write_to_template,
+ write_to=config.write_to,
+ template=config.write_to_template,
)
return version_string
diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py
index 38f79ae..f0d9243 100644
--- a/src/setuptools_scm/config.py
+++ b/src/setuptools_scm/config.py
@@ -6,8 +6,9 @@ import warnings
from .utils import trace
-DEFAULT_TAG_REGEX = r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]+)(?:\+.*)?$"
-DEFAULT_VERSION_SCHEME = "version_scheme"
+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):
@@ -39,32 +40,38 @@ def _check_absolute_root(root, relative_to):
class Configuration(object):
""" Global configuration model """
- _root = None
- version_scheme = None
- local_scheme = None
- write_to = None
- write_to_template = None
- fallback_version = None
- _relative_to = None
- parse = None
- _tag_regex = None
- _absolute_root = None
-
- def __init__(self, relative_to=None, root="."):
+ 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,
+ dist_name=None,
+ ):
# TODO:
self._relative_to = relative_to
self._root = "."
self.root = root
- self.version_scheme = DEFAULT_VERSION_SCHEME
- self.local_scheme = "node-and-date"
- self.write_to = ""
- self.write_to_template = None
- self.fallback_version = None
- self.fallback_root = "."
- self.parse = None
- self.tag_regex = DEFAULT_TAG_REGEX
- self.git_describe_command = None
+ 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
+ self.dist_name = dist_name
@property
def fallback_root(self):
@@ -105,3 +112,16 @@ class Configuration(object):
@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)
diff --git a/src/setuptools_scm/file_finder.py b/src/setuptools_scm/file_finder.py
index 18712bf..5b85117 100644
--- a/src/setuptools_scm/file_finder.py
+++ b/src/setuptools_scm/file_finder.py
@@ -1,4 +1,5 @@
import os
+from .utils import trace
def scm_find_files(path, scm_files, scm_dirs):
@@ -31,10 +32,9 @@ def scm_find_files(path, scm_files, 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)
- ):
+ 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)))
@@ -51,6 +51,19 @@ def scm_find_files(path, scm_files, scm_dirs):
# 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, path)))
+ res.append(os.path.join(path, os.path.relpath(fullfilename, realpath)))
seen.add(realdirpath)
return res
+
+
+def is_toplevel_acceptable(toplevel):
+ ""
+ if toplevel is None:
+ return False
+
+ ignored = os.environ.get("SETUPTOOLS_SCM_IGNORE_VCS_ROOTS", "").split(os.pathsep)
+ ignored = [os.path.normcase(p) for p in ignored]
+
+ trace(toplevel, ignored)
+
+ return toplevel not in ignored
diff --git a/src/setuptools_scm/file_finder_git.py b/src/setuptools_scm/file_finder_git.py
index 5cda162..1d3e69b 100644
--- a/src/setuptools_scm/file_finder_git.py
+++ b/src/setuptools_scm/file_finder_git.py
@@ -3,6 +3,7 @@ import subprocess
import tarfile
import logging
from .file_finder import scm_find_files
+from .file_finder import is_toplevel_acceptable
from .utils import trace
log = logging.getLogger(__name__)
@@ -49,8 +50,9 @@ def _git_ls_files_and_dirs(toplevel):
try:
return _git_interpret_archive(proc.stdout, toplevel)
finally:
- # ensure we avoid ressource warnings by cleaning up the pocess
- proc.wait()
+ # 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")
@@ -59,7 +61,10 @@ def _git_ls_files_and_dirs(toplevel):
def git_find_files(path=""):
toplevel = _git_toplevel(path)
- if not toplevel:
+ if not is_toplevel_acceptable(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)
diff --git a/src/setuptools_scm/file_finder_hg.py b/src/setuptools_scm/file_finder_hg.py
index 2aa1e16..816560d 100644
--- a/src/setuptools_scm/file_finder_hg.py
+++ b/src/setuptools_scm/file_finder_hg.py
@@ -2,6 +2,7 @@ import os
import subprocess
from .file_finder import scm_find_files
+from .file_finder import is_toplevel_acceptable
def _hg_toplevel(path):
@@ -41,7 +42,7 @@ def _hg_ls_files_and_dirs(toplevel):
def hg_find_files(path=""):
toplevel = _hg_toplevel(path)
- if not toplevel:
+ if not is_toplevel_acceptable(toplevel):
return []
hg_files, hg_dirs = _hg_ls_files_and_dirs(toplevel)
return scm_find_files(path, hg_files, hg_dirs)
diff --git a/src/setuptools_scm/git.py b/src/setuptools_scm/git.py
index d7a524d..a193f93 100644
--- a/src/setuptools_scm/git.py
+++ b/src/setuptools_scm/git.py
@@ -1,5 +1,5 @@
from .config import Configuration
-from .utils import do_ex, trace, has_command
+from .utils import do_ex, trace, require_command
from .version import meta
from os.path import isfile, join
@@ -12,7 +12,7 @@ except ImportError:
from .win_py31_compat import samefile
-DEFAULT_DESCRIBE = "git describe --dirty --tags --long --match *.* --first-parent"
+DEFAULT_DESCRIBE = "git describe --dirty --tags --long --match *[0-9]*"
class GitWorkdir(object):
@@ -65,7 +65,7 @@ class GitWorkdir(object):
def warn_on_shallow(wd):
"""experimental, may change at any time"""
if wd.is_shallow():
- warnings.warn('"%s" is shallow and may cause errors' % (wd.path,))
+ warnings.warn('"{}" is shallow and may cause errors'.format(wd.path))
def fetch_on_shallow(wd):
@@ -92,8 +92,7 @@ def parse(
if not config:
config = Configuration(root=root)
- if not has_command("git"):
- return
+ require_command("git")
wd = GitWorkdir.from_potential_worktree(config.absolute_root)
if wd is None:
diff --git a/src/setuptools_scm/hacks.py b/src/setuptools_scm/hacks.py
index 1ef0bb4..349d26f 100644
--- a/src/setuptools_scm/hacks.py
+++ b/src/setuptools_scm/hacks.py
@@ -1,6 +1,6 @@
import os
from .utils import data_from_mime, trace
-from .version import meta
+from .version import tag_to_version, meta
def parse_pkginfo(root, config=None):
@@ -25,5 +25,13 @@ def parse_pip_egg_info(root, config=None):
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)
diff --git a/src/setuptools_scm/hg.py b/src/setuptools_scm/hg.py
index 8fedd68..2ac9141 100644
--- a/src/setuptools_scm/hg.py
+++ b/src/setuptools_scm/hg.py
@@ -1,6 +1,6 @@
import os
from .config import Configuration
-from .utils import do, trace, data_from_mime, has_command
+from .utils import do, trace, data_from_mime, require_command
from .version import meta, tags_to_versions
@@ -15,9 +15,7 @@ def _hg_tagdist_normalize_tagcommit(config, tag, dist, node, branch):
# 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
- )
+ ).format(tag=tag)
if tag != "0.0":
commits = do(
["hg", "log", "-r", revset, "--template", "{node|short}"],
@@ -38,8 +36,7 @@ def parse(root, config=None):
if not config:
config = Configuration(root=root)
- if not has_command("hg"):
- return
+ require_command("hg")
identity_data = do("hg id -i -b -t", config.absolute_root).split()
if not identity_data:
return
@@ -71,7 +68,12 @@ def parse(root, config=None):
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"
+ "hg",
+ "log",
+ "-r",
+ "ancestors(.) and tag('re:\\.')",
+ "--template",
+ "{tags}\n",
]
outlines = do(cmd, root).split()
if not outlines:
@@ -81,7 +83,7 @@ def get_latest_normalizable_tag(root):
def get_graph_distance(root, rev1, rev2="."):
- cmd = ["hg", "log", "-q", "-r", "%s::%s" % (rev1, rev2)]
+ cmd = ["hg", "log", "-q", "-r", "{}::{}".format(rev1, rev2)]
out = do(cmd, root)
return len(out.strip().splitlines()) - 1
diff --git a/src/setuptools_scm/integration.py b/src/setuptools_scm/integration.py
index e18b3e5..ffd4521 100644
--- a/src/setuptools_scm/integration.py
+++ b/src/setuptools_scm/integration.py
@@ -1,8 +1,8 @@
from pkg_resources import iter_entry_points
from .version import _warn_if_setuptools_outdated
-from .utils import do
-from . import get_version
+from .utils import do, trace_exception, trace
+from . import _get_version, Configuration
def version_keyword(dist, keyword, value):
@@ -13,8 +13,13 @@ def version_keyword(dist, keyword, value):
value = {}
if getattr(value, "__call__", None):
value = value()
-
- dist.metadata.version = get_version(**value)
+ assert (
+ "dist_name" not in value
+ ), "dist_name may not be specified in the setup keyword "
+ trace("dist name", dist, dist.name)
+ dist_name = dist.name if dist.name != 0 else None
+ config = Configuration(dist_name=dist_name, **value)
+ dist.metadata.version = _get_version(config)
def find_files(path=""):
@@ -28,3 +33,21 @@ def find_files(path=""):
if res:
return res
return []
+
+
+def _args_from_toml(name="pyproject.toml"):
+ # todo: more sensible config initialization
+ # move this helper 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)
diff --git a/src/setuptools_scm/utils.py b/src/setuptools_scm/utils.py
index 5b59005..413e98a 100644
--- a/src/setuptools_scm/utils.py
+++ b/src/setuptools_scm/utils.py
@@ -10,6 +10,7 @@ import subprocess
import os
import io
import platform
+import traceback
DEBUG = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG"))
@@ -19,12 +20,37 @@ 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()
@@ -43,7 +69,6 @@ def _always_strings(env_dict):
def _popen_pipes(cmd, cwd):
-
return subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
@@ -51,7 +76,8 @@ def _popen_pipes(cmd, cwd):
cwd=str(cwd),
env=_always_strings(
dict(
- os.environ,
+ no_git_env(os.environ),
+ # os.environ,
# try to disable i18n
LC_ALL="C",
LANGUAGE="",
@@ -106,7 +132,7 @@ def function_has_arg(fn, argname):
return argname in argspec
-def has_command(name):
+def has_command(name, warn=True):
try:
p = _popen_pipes([name, "help"], ".")
except OSError:
@@ -115,6 +141,11 @@ def has_command(name):
else:
p.communicate()
res = not p.returncode
- if not res:
- warnings.warn("%r was not found" % name)
+ if not res and warn:
+ warnings.warn("%r was not found" % name, category=RuntimeWarning)
return res
+
+
+def require_command(name):
+ if not has_command(name, warn=False):
+ raise EnvironmentError("%r was not found" % name)
diff --git a/src/setuptools_scm/version.py b/src/setuptools_scm/version.py
index 4f46331..f97dca5 100644
--- a/src/setuptools_scm/version.py
+++ b/src/setuptools_scm/version.py
@@ -2,7 +2,8 @@ from __future__ import print_function
import datetime
import warnings
import re
-from itertools import chain, repeat, islice
+import time
+import os
from .config import Configuration
from .utils import trace, string_types
@@ -16,11 +17,6 @@ SEMVER_PATCH = 3
SEMVER_LEN = 3
-def _pad(iterable, size, padding=None):
- padded = chain(iterable, repeat(padding))
- return list(islice(padded, size))
-
-
def _parse_version_tag(tag, config):
tagstring = tag if not isinstance(tag, string_types) else str(tag)
match = config.tag_regex.match(tagstring)
@@ -34,11 +30,11 @@ def _parse_version_tag(tag, config):
result = {
"version": match.group(key),
- "prefix": match.group(0)[:match.start(key)],
- "suffix": match.group(0)[match.end(key):],
+ "prefix": match.group(0)[: match.start(key)],
+ "suffix": match.group(0)[match.end(key) :],
}
- trace("tag '%s' parsed to %s" % (tag, result))
+ trace("tag '{}' parsed to {}".format(tag, result))
return result
@@ -89,7 +85,7 @@ def tag_to_version(tag, config=None):
tagdict = _parse_version_tag(tag, config)
if not isinstance(tagdict, dict) or not tagdict.get("version", None):
- warnings.warn("tag %r no version found" % (tag,))
+ warnings.warn("tag {!r} no version found".format(tag))
return None
version = tagdict["version"]
@@ -97,7 +93,9 @@ def tag_to_version(tag, config=None):
if tagdict.get("suffix", ""):
warnings.warn(
- "tag %r will be stripped of its suffix '%s'" % (tag, tagdict["suffix"])
+ "tag {!r} will be stripped of its suffix '{}'".format(
+ tag, tagdict["suffix"]
+ )
)
if VERSION_CLASS is not None:
@@ -122,7 +120,6 @@ def tags_to_versions(tags, config=None):
class ScmVersion(object):
-
def __init__(
self,
tag_version,
@@ -131,6 +128,7 @@ class ScmVersion(object):
dirty=False,
preformatted=False,
branch=None,
+ config=None,
**kw
):
if kw:
@@ -140,11 +138,14 @@ class ScmVersion(object):
distance = 0
self.distance = distance
self.node = node
- self.time = datetime.datetime.now()
+ self.time = datetime.datetime.utcfromtimestamp(
+ int(os.environ.get("SOURCE_DATE_EPOCH", time.time()))
+ )
self._extra = kw
self.dirty = dirty
self.preformatted = preformatted
self.branch = branch
+ self.config = config
@property
def extra(self):
@@ -192,7 +193,14 @@ def _parse_tag(tag, preformatted, config):
def meta(
- tag, distance=None, dirty=False, node=None, preformatted=False, config=None, **kw
+ tag,
+ distance=None,
+ dirty=False,
+ node=None,
+ preformatted=False,
+ branch=None,
+ config=None,
+ **kw
):
if not config:
warnings.warn(
@@ -201,8 +209,10 @@ def meta(
)
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, **kw)
+ assert parsed_version is not None, "Can't parse version %s" % tag
+ return ScmVersion(
+ parsed_version, distance, node, dirty, preformatted, branch, config, **kw
+ )
def guess_next_version(tag_version):
@@ -220,13 +230,26 @@ def _bump_dev(version):
return
prefix, tail = version.rsplit(".dev", 1)
- assert tail == "0", "own dev numbers are unsupported"
+ if tail != "0":
+ raise ValueError(
+ "choosing custom numbers for the `.devX` distance "
+ "is not supported.\n "
+ "The {version} can't be bumped\n"
+ "Please drop the tag or create a new supported one".format(version=version)
+ )
return prefix
def _bump_regex(version):
- prefix, tail = re.match(r"(.*?)(\d+)$", version).groups()
- return "%s%d" % (prefix, int(tail) + 1)
+ match = re.match(r"(.*?)(\d+)$", version)
+ if match is None:
+ raise ValueError(
+ "{version} does not end with a number to bump, "
+ "please correct or use a custom version scheme".format(version=version)
+ )
+ else:
+ prefix, tail = match.groups()
+ return "%s%d" % (prefix, int(tail) + 1)
def guess_next_dev_version(version):
@@ -237,12 +260,19 @@ def guess_next_dev_version(version):
def guess_next_simple_semver(version, retain, increment=True):
- parts = map(int, str(version).split("."))
- parts = _pad(parts, retain, 0)
+ try:
+ parts = [int(i) for i in str(version).split(".")[:retain]]
+ except ValueError:
+ raise ValueError(
+ "{version} can't be parsed as numeric version".format(version=version)
+ )
+ while len(parts) < retain:
+ parts.append(0)
if increment:
parts[-1] += 1
- parts = _pad(parts, SEMVER_LEN, 0)
- return ".".join(map(str, parts))
+ while len(parts) < SEMVER_LEN:
+ parts.append(0)
+ return ".".join(str(i) for i in parts)
def simplified_semver_version(version):
@@ -259,6 +289,42 @@ def simplified_semver_version(version):
)
+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 no_guess_dev_version(version):
+ if version.exact:
+ return version.format_with("{tag}")
+ else:
+ return version.format_with("{tag}.post1.dev{distance}")
+
+
def _format_local_with_time(version, time_format):
if version.exact or version.node is None:
@@ -283,6 +349,10 @@ 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}")
diff --git a/testing/check_self_install.py b/testing/check_self_install.py
new file mode 100644
index 0000000..de3ac79
--- /dev/null
+++ b/testing/check_self_install.py
@@ -0,0 +1,5 @@
+import pkg_resources
+import setuptools_scm
+
+dist = pkg_resources.get_distribution("setuptools_scm")
+assert dist.version == setuptools_scm.get_version(), dist.version
diff --git a/testing/conftest.py b/testing/conftest.py
index 0d34731..5f6cdd5 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -2,6 +2,8 @@ import os
import itertools
import pytest
+# 2009-02-13T23:31:30+00:00
+os.environ["SOURCE_DATE_EPOCH"] = "1234567890"
os.environ["SETUPTOOLS_SCM_DEBUG"] = "1"
VERSION_PKGS = ["setuptools", "setuptools_scm"]
@@ -21,6 +23,9 @@ 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()
@@ -33,10 +38,13 @@ class Wd(object):
return do(cmd, self.cwd)
def write(self, name, value, **kw):
- filename = self.cwd.join(name)
+ filename = self.cwd / name
if kw:
value = value.format(**kw)
- filename.write(value)
+ if isinstance(value, bytes):
+ filename.write_bytes(value)
+ else:
+ filename.write_text(value)
return filename
def _reason(self, given_reason):
@@ -83,5 +91,7 @@ def debug_mode():
@pytest.fixture
-def wd(tmpdir):
- return Wd(tmpdir.ensure("wd", dir=True))
+def wd(tmp_path):
+ target_wd = tmp_path.resolve() / "wd"
+ target_wd.mkdir()
+ return Wd(target_wd)
diff --git a/testing/play_out_381.bash b/testing/play_out_381.bash
new file mode 100755
index 0000000..be9d23c
--- /dev/null
+++ b/testing/play_out_381.bash
@@ -0,0 +1,23 @@
+#!/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"
diff --git a/testing/runtests_travis.py b/testing/runtests_travis.py
deleted file mode 100644
index d2d5f9e..0000000
--- a/testing/runtests_travis.py
+++ /dev/null
@@ -1,21 +0,0 @@
-
-from subprocess import call
-
-import os
-
-if os.environ.get("TOXENV"):
- # normal tox run, lets jsut have tox do its job
- import tox
-
- tox.cmdline()
-elif os.environ.get("SELFINSTALL"):
- # self install testing needs some clarity
- # so its being executed without any other tools running
- call("python setup.py sdist", shell=True)
- call("easy_install dist/*", shell=True)
- import pkg_resources
-
- dist = pkg_resources.get_distribution("setuptools_scm")
- import setuptools_scm
-
- assert dist.version == setuptools_scm.get_version(), dist.version
diff --git a/testing/test_basic_api.py b/testing/test_basic_api.py
index f342e23..5111542 100644
--- a/testing/test_basic_api.py
+++ b/testing/test_basic_api.py
@@ -58,6 +58,18 @@ def test_root_parameter_pass_by(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)
@@ -89,16 +101,19 @@ def test_dump_version(tmpdir):
dump_version(sp, "1.0", "first.txt")
assert tmpdir.join("first.txt").read() == "1.0"
- dump_version(sp, "1.0", "first.py")
+
+ dump_version(sp, "1.0.dev42", "first.py")
content = tmpdir.join("first.py").read()
- assert repr("1.0") in content
+ lines = content.splitlines()
+ assert "version = '1.0.dev42'" in lines
+ assert "version_tuple = (1, 0, 'dev42')" in lines
+
import ast
ast.parse(content)
def test_parse_plain_fails(recwarn):
-
def parse(root):
return "tricked you"
diff --git a/testing/test_config.py b/testing/test_config.py
index eadbaf3..49f1d7a 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -1,5 +1,7 @@
-from setuptools_scm.config import Configuration
+from __future__ import unicode_literals
+from setuptools_scm.config import Configuration
+import re
import pytest
@@ -9,6 +11,10 @@ import pytest
("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"),
],
@@ -16,5 +22,18 @@ import pytest
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
diff --git a/testing/test_file_finder.py b/testing/test_file_finder.py
index 0bc9a7f..55b9cea 100644
--- a/testing/test_file_finder.py
+++ b/testing/test_file_finder.py
@@ -7,25 +7,35 @@ from setuptools_scm.integration import find_files
@pytest.fixture(params=["git", "hg"])
-def inwd(request, wd):
+def inwd(request, wd, monkeypatch):
if request.param == "git":
- wd("git init")
+ 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":
- wd("hg init")
+ 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").ensure(file=True)
- adir = (wd.cwd / "adir").ensure(dir=True)
- (adir / "filea").ensure(file=True)
- bdir = (wd.cwd / "bdir").ensure(dir=True)
- (bdir / "fileb").ensure(file=True)
+ (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()
- with wd.cwd.as_cwd():
- yield wd
+ monkeypatch.chdir(wd.cwd)
+ yield wd
def _sep(paths):
@@ -39,31 +49,30 @@ def test_basic(inwd):
def test_whitespace(inwd):
- (inwd.cwd / "adir" / "space file").ensure(file=True)
+ (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").ensure(file=True)
- (inwd.cwd / "file2").ensure(file=True)
+ (inwd.cwd / "CamelFile").touch()
+ (inwd.cwd / "file2").touch()
inwd.add_and_commit()
- assert (
- set(find_files())
- == _sep({"CamelFile", "file2", "file1", "adir/filea", "bdir/fileb"})
+ 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").mksymlinkto("../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_source_not_in_scm(inwd):
- (inwd.cwd / "adir" / "bdirlink").mksymlinkto("../bdir")
+ (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
assert set(find_files("adir")) == _sep({"adir/filea"})
@@ -71,39 +80,39 @@ def test_symlink_dir_source_not_in_scm(inwd):
sys.platform == "win32", reason="symlinks to files not supported on windows"
)
def test_symlink_file(inwd):
- (inwd.cwd / "adir" / "file1link").mksymlinkto("../file1")
+ (inwd.cwd / "adir" / "file1link").symlink_to("../file1")
inwd.add_and_commit()
- assert (
- set(find_files("adir")) == _sep({"adir/filea", "adir/file1link"}) # -> ../file1
- )
+ 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").mksymlinkto("../file1")
+ (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").mksymlinkto("../adir")
+ (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").mksymlinkto("../bdir")
- (inwd.cwd / "adir" / "bdirlink").mksymlinkto("../bdir")
+ (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").mksymlinkto(os.path.join(__file__, ".."))
+ (inwd.cwd / "adir" / "outsidedirlink").symlink_to(os.path.join(__file__, ".."))
inwd.add_and_commit()
assert set(find_files("adir")) == _sep({"adir/filea"})
@@ -112,68 +121,68 @@ def test_symlink_dir_out_of_git(inwd):
sys.platform == "win32", reason="symlinks to files not supported on windows"
)
def test_symlink_file_out_of_git(inwd):
- (inwd.cwd / "adir" / "outsidefilelink").mksymlinkto(__file__)
+ (inwd.cwd / "adir" / "outsidefilelink").symlink_to(__file__)
inwd.add_and_commit()
assert set(find_files("adir")) == _sep({"adir/filea"})
+@pytest.mark.parametrize("path_add", ["{cwd}", "{cwd}" + os.pathsep + "broken"])
+def test_ignore_root(inwd, monkeypatch, path_add):
+ monkeypatch.setenv("SETUPTOOLS_SCM_IGNORE_VCS_ROOTS", path_add.format(cwd=inwd.cwd))
+ assert find_files() == []
+
+
def test_empty_root(inwd):
subdir = inwd.cwd / "cdir" / "subdir"
- subdir.ensure(dir=True)
- (subdir / "filec").ensure(file=True)
+ 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.ensure(dir=True)
- (subdir / "xfile").ensure(file=True)
+ subdir.mkdir(parents=True)
+ (subdir / "xfile").touch()
inwd.add_and_commit()
- assert (
- set(find_files("adir")) == _sep({"adir/filea", "adir/emptysubdir/subdir/xfile"})
+ 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").ensure(dir=True)
- (inwd.cwd / "data" / "datafile").ensure(file=True)
- (inwd.cwd / "adir" / "datalink").mksymlinkto("../data")
- (inwd.cwd / "adir" / "filealink").mksymlinkto("filea")
+ (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",
- }
- )
+ 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").ensure(dir=True)
- (inwd.cwd / "data" / "datafile").ensure(file=True)
+ (inwd.cwd / "data").mkdir()
+ (inwd.cwd / "data" / "datafile").touch()
inwd.add_and_commit()
- (inwd.cwd / "adir" / "datalink").mksymlinkto("../data")
- (inwd.cwd / "adir" / "filealink").mksymlinkto("filea")
- assert (
- set(find_files())
- == _sep(
- {
- "file1",
- "adir/filea",
- # adir/datalink and adir/afilelink not included
- # because the symlink themselves are not in scm
- "bdir/fileb",
- "data/datafile",
- }
- )
+ (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",
+ }
)
diff --git a/testing/test_functions.py b/testing/test_functions.py
index c3d78b6..ffc8081 100644
--- a/testing/test_functions.py
+++ b/testing/test_functions.py
@@ -15,12 +15,6 @@ 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",
[
@@ -51,19 +45,21 @@ VERSIONS = {
[
("exact", "guess-next-dev node-and-date", "1.1"),
("zerodistance", "guess-next-dev node-and-date", "1.2.dev0"),
- ("dirty", "guess-next-dev node-and-date", "1.2.dev0+dtime"),
+ ("zerodistance", "guess-next-dev no-local-version", "1.2.dev0"),
+ ("dirty", "guess-next-dev node-and-date", "1.2.dev0+d20090213"),
+ ("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 node-and-date", "1.2.dev3+d20090213"),
+ ("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"),
+ ("dirty", "post-release node-and-date", "1.1.post0+d20090213"),
("distance", "post-release node-and-date", "1.1.post3"),
- ("distancedirty", "post-release node-and-date", "1.1.post3+dtime"),
+ ("distancedirty", "post-release node-and-date", "1.1.post3+d20090213"),
],
)
-def test_format_version(version, monkeypatch, scheme, expected):
+def test_format_version(version, 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
diff --git a/testing/test_git.py b/testing/test_git.py
index 9307850..1b57fed 100644
--- a/testing/test_git.py
+++ b/testing/test_git.py
@@ -1,16 +1,28 @@
import sys
from setuptools_scm import integration
-from setuptools_scm.utils import do
+from setuptools_scm.utils import do, has_command
from setuptools_scm import git
import pytest
-from datetime import date
+from datetime import datetime
from os.path import join as opj
from setuptools_scm.file_finder_git import git_find_files
+skip_if_win_27 = pytest.mark.skipif(
+ sys.platform == "win32" and sys.version_info[0] < 3,
+ reason="Not supported on Windows + Python 2.7",
+)
+
+
+pytestmark = pytest.mark.skipif(
+ not has_command("git", warn=False), reason="git executable not found"
+)
+
+
@pytest.fixture
-def wd(wd):
+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"')
@@ -33,9 +45,10 @@ def test_parse_describe_output(given, tag, number, node, dirty):
def test_root_relative_to(tmpdir, wd, monkeypatch):
monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
- p = wd.cwd.ensure("sub/package", dir=1)
- p.join("setup.py").write(
- """from setuptools import setup
+ 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__})
"""
@@ -44,7 +57,14 @@ setup(use_scm_version={"root": "../..",
assert res == "0.1.dev0"
+def test_git_gone(wd, monkeypatch):
+ monkeypatch.setenv("PATH", str(wd.cwd / "not-existing"))
+ with pytest.raises(EnvironmentError, match="'git' was not found"):
+ git.parse(str(wd.cwd), git.DEFAULT_DESCRIBE)
+
+
@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 == []
@@ -105,14 +125,21 @@ def test_git_worktree(wd):
@pytest.mark.issue(86)
-def test_git_dirty_notag(wd):
+@pytest.mark.parametrize("today", [False, True])
+def test_git_dirty_notag(today, wd, monkeypatch):
+ if today:
+ monkeypatch.delenv("SOURCE_DATE_EPOCH", raising=False)
wd.commit_testfile()
wd.write("test.txt", "test2")
wd("git add test.txt")
assert wd.version.startswith("0.1.dev1")
- today = date.today()
+ if today:
+ # the date on the tag is in UTC
+ tag = datetime.utcnow().date().strftime(".d%Y%m%d")
+ else:
+ tag = ".d20090213"
# we are dirty, check for the tag
- assert today.strftime(".d%Y%m%d") in wd.version
+ assert tag in wd.version
@pytest.mark.issue(193)
@@ -157,8 +184,10 @@ def test_git_shallow_autocorrect(shallow_wd, recwarn):
def test_find_files_stop_at_root_git(wd):
wd.commit_testfile()
- wd.cwd.ensure("project/setup.cfg")
- assert integration.find_files(str(wd.cwd / "project")) == []
+ project = wd.cwd / "project"
+ project.mkdir()
+ project.joinpath("setup.cfg").touch()
+ assert integration.find_files(str(project)) == []
@pytest.mark.issue(128)
@@ -173,7 +202,8 @@ def test_alphanumeric_tags_match(wd):
assert wd.version.startswith("0.1.dev1+g")
-def test_git_archive_export_ignore(wd):
+@skip_if_win_27
+def test_git_archive_export_ignore(wd, monkeypatch):
wd.write("test1.txt", "test")
wd.write("test2.txt", "test")
wd.write(
@@ -184,28 +214,30 @@ def test_git_archive_export_ignore(wd):
)
wd("git add test1.txt test2.txt")
wd.commit()
- with wd.cwd.as_cwd():
- assert integration.find_files(".") == [opj(".", "test1.txt")]
+ 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):
+def test_git_archive_subdirectory(wd, monkeypatch):
wd("mkdir foobar")
wd.write("foobar/test1.txt", "test")
wd("git add foobar")
wd.commit()
- with wd.cwd.as_cwd():
- assert integration.find_files(".") == [opj(".", "foobar", "test1.txt")]
+ 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):
+def test_git_archive_run_from_subdirectory(wd, monkeypatch):
wd("mkdir foobar")
wd.write("foobar/test1.txt", "test")
wd("git add foobar")
wd.commit()
- with (wd.cwd / "foobar").as_cwd():
- assert integration.find_files(".") == [opj(".", "test1.txt")]
+ monkeypatch.chdir(wd.cwd / "foobar")
+ assert integration.find_files(".") == [opj(".", "test1.txt")]
def test_git_feature_branch_increments_major(wd):
@@ -228,6 +260,39 @@ def test_not_matching_tags(wd):
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"
- )
+ ).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")
+
+
+def test_non_dotted_version_with_updated_regex(wd):
+ wd.commit_testfile()
+ wd("git tag apache-arrow-1")
+ wd.commit_testfile()
+ assert wd.get_version(tag_regex=r"^apache-arrow-([\.0-9]+)$").startswith("2")
+
+
+def test_non_dotted_tag_no_version_match(wd):
+ wd.commit_testfile()
+ wd("git tag apache-arrow-0.11.1")
+ wd.commit_testfile()
+ wd("git tag apache-arrow")
+ wd.commit_testfile()
+ assert wd.get_version().startswith("0.11.2.dev2")
+
+
+@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
diff --git a/testing/test_integration.py b/testing/test_integration.py
new file mode 100644
index 0000000..446aac0
--- /dev/null
+++ b/testing/test_integration.py
@@ -0,0 +1,59 @@
+import sys
+
+import pytest
+
+from setuptools_scm.utils import do
+from setuptools_scm import PRETEND_KEY, PRETEND_KEY_NAMED
+
+
+@pytest.fixture
+def wd(wd):
+ 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
+
+
+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"
+
+
+def test_pretend_version(tmpdir, monkeypatch, wd):
+ monkeypatch.setenv(PRETEND_KEY, "1.0.0")
+
+ assert wd.get_version() == "1.0.0"
+ assert wd.get_version(dist_name="ignored") == "1.0.0"
+
+
+def test_pretend_version_named(tmpdir, monkeypatch, wd):
+ monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test".upper()), "1.0.0")
+ monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test2".upper()), "2.0.0")
+ assert wd.get_version(dist_name="test") == "1.0.0"
+ assert wd.get_version(dist_name="test2") == "2.0.0"
+
+
+def test_pretend_version_name_takes_precedence(tmpdir, monkeypatch, wd):
+ monkeypatch.setenv(PRETEND_KEY_NAMED.format(name="test".upper()), "1.0.0")
+ monkeypatch.setenv(PRETEND_KEY, "2.0.0")
+ assert wd.get_version(dist_name="test") == "1.0.0"
diff --git a/testing/test_mercurial.py b/testing/test_mercurial.py
index e25bfe1..265e207 100644
--- a/testing/test_mercurial.py
+++ b/testing/test_mercurial.py
@@ -2,9 +2,15 @@ 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
+pytestmark = pytest.mark.skipif(
+ not has_command("hg", warn=False), reason="hg executable not found"
+)
+
+
@pytest.fixture
def wd(wd):
wd("hg init")
@@ -16,7 +22,9 @@ def wd(wd):
archival_mapping = {
"1.0": {"tag": "1.0"},
"1.1.dev3+h000000000000": {
- "latesttag": "1.0", "latesttagdistance": "3", "node": "0" * 20
+ "latesttag": "1.0",
+ "latesttagdistance": "3",
+ "node": "0" * 20,
},
"0.0": {"node": "0" * 20},
"1.2.2": {"tag": "release-1.2.2"},
@@ -36,15 +44,23 @@ def test_archival_to_version(expected, data):
)
-def test_find_files_stop_at_root_hg(wd):
+def test_hg_gone(wd, monkeypatch):
+ monkeypatch.setenv("PATH", str(wd.cwd / "not-existing"))
+ with pytest.raises(EnvironmentError, match="'hg' was not found"):
+ parse(str(wd.cwd))
+
+
+def test_find_files_stop_at_root_hg(wd, monkeypatch):
wd.commit_testfile()
- wd.cwd.ensure("project/setup.cfg")
+ project = wd.cwd / "project"
+ project.mkdir()
+ project.joinpath("setup.cfg").touch()
# setup.cfg has not been committed
- assert integration.find_files(str(wd.cwd / "project")) == []
+ assert integration.find_files(str(project)) == []
# issue 251
wd.add_and_commit()
- with (wd.cwd / "project").as_cwd():
- assert integration.find_files() == ["setup.cfg"]
+ monkeypatch.chdir(project)
+ assert integration.find_files() == ["setup.cfg"]
# XXX: better tests for tag prefixes
@@ -79,7 +95,7 @@ def test_version_from_hg_id(wd):
def test_version_from_archival(wd):
# entrypoints are unordered,
# cleaning the wd ensure this test wont break randomly
- wd.cwd.join(".hg").remove()
+ 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"
@@ -140,10 +156,9 @@ def test_version_bump_from_merge_commit(wd):
@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.join(".hgtags").open("a") as tagfile:
- tagfile.write("0 0\n")
+ """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
@@ -154,7 +169,7 @@ def test_version_bump_from_commit_including_hgtag_mods(wd):
@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.
+ """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"')
diff --git a/testing/test_regressions.py b/testing/test_regressions.py
index f3b0fc6..8bde373 100644
--- a/testing/test_regressions.py
+++ b/testing/test_regressions.py
@@ -27,9 +27,13 @@ def test_pkginfo_noscmroot(tmpdir, monkeypatch):
res = do((sys.executable, "setup.py", "--version"), p)
assert res == "1.0"
- do("git init", p.dirpath())
- res = do((sys.executable, "setup.py", "--version"), p)
- assert res == "0.1.dev0"
+ 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):
diff --git a/testing/test_version.py b/testing/test_version.py
index a287a0d..ee13801 100644
--- a/testing/test_version.py
+++ b/testing/test_version.py
@@ -1,6 +1,13 @@
import pytest
from setuptools_scm.config import Configuration
-from setuptools_scm.version import meta, simplified_semver_version, tags_to_versions
+from setuptools_scm.version import (
+ meta,
+ simplified_semver_version,
+ release_branch_semver_version,
+ tags_to_versions,
+ no_guess_dev_version,
+ guess_next_version,
+)
c = Configuration()
@@ -43,6 +50,92 @@ def test_next_semver(version, expected_next):
assert computed == expected_next
+def test_next_semver_bad_tag():
+
+ version = meta("1.0.0-foo", config=c)
+ with pytest.raises(
+ ValueError, match="1.0.0-foo can't be parsed as numeric version"
+ ):
+ simplified_semver_version(version)
+
+
+@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(
+ "version, expected_next",
+ [
+ pytest.param(
+ meta("1.0.0", distance=2, branch="default", config=c),
+ "1.0.0.post1.dev2",
+ id="dev_distance",
+ ),
+ pytest.param(
+ meta("1.0", distance=2, branch="default", config=c),
+ "1.0.post1.dev2",
+ id="dev_distance_short_tag",
+ ),
+ pytest.param(
+ meta("1.0.0", distance=None, branch="default", config=c),
+ "1.0.0",
+ id="no_dev_distance",
+ ),
+ ],
+)
+def test_no_guess_version(version, expected_next):
+ computed = no_guess_dev_version(version)
+ assert computed == expected_next
+
+
+def test_bump_dev_version_zero():
+ guess_next_version("1.0.dev0")
+
+
+def test_bump_dev_version_nonzero_raises():
+ with pytest.raises(ValueError) as excinfo:
+ guess_next_version("1.0.dev1")
+
+ assert str(excinfo.value) == (
+ "choosing custom numbers for the `.devX` distance "
+ "is not supported.\n "
+ "The 1.0.dev1 can't be bumped\n"
+ "Please drop the tag or create a new supported one"
+ )
+
+
@pytest.mark.parametrize(
"tag, expected",
[
@@ -52,20 +145,28 @@ def test_next_semver(version, expected_next):
],
)
def test_tag_regex1(tag, expected):
- config = Configuration()
- config.tag_regex = r"^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$"
if "+" in tag:
# pytest bug wrt cardinality
with pytest.warns(UserWarning):
- result = meta(tag, config=config)
+ result = meta(tag, config=c)
else:
- result = meta(tag, config=config)
+ result = meta(tag, config=c)
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)
+ versions = tags_to_versions(["1.0", "2.0", "3.0"], config=c)
assert isinstance(versions, list) # enable subscription
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/471")
+def test_version_bump_bad():
+ with pytest.raises(
+ ValueError,
+ match=".*does not end with a number to bump, "
+ "please correct or use a custom version scheme",
+ ):
+
+ guess_next_version(tag_version="2.0.0-alpha.5-PMC")
diff --git a/tox.ini b/tox.ini
index 129f9b4..030dbd4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,8 @@
[tox]
-envlist=py{27,34,35,36,37,38}-test,flake8,check_readme,py{27,37}-selfcheck
+envlist=py{27,34,35,36,37,38,39}-test,flake8,check_readme,check-dist,py{27,37}-selfcheck,docs
[pytest]
+testpaths=testing
filterwarnings=error
markers=
issue(id): reference to github issue
@@ -9,6 +10,7 @@ markers=
[flake8]
max-complexity = 10
max-line-length = 88
+ignore=E203,W503
exclude=
.git,
.tox,
@@ -16,7 +18,7 @@ exclude=
.venv,
.pytest_cache,
__pycache__,
- ./setuptools_scm/win_py31_compat.py
+ ./src/setuptools_scm/win_py31_compat.py
[testenv]
usedevelop=True
@@ -25,9 +27,12 @@ skip_install=
test: False
deps=
pytest
+ setuptools >= 42
commands=
- test: py.test []
+ test: pytest []
selfcheck: python setup.py --version
+extras =
+ toml
[testenv:flake8]
skip_install=True
@@ -35,27 +40,28 @@ deps=
flake8
mccabe
commands =
- flake8 setuptools_scm/ testing/ setup.py --exclude=setuptools_scm/win_py31_compat.py
+ flake8 src/setuptools_scm/ testing/ setup.py --exclude=src/setuptools_scm/win_py31_compat.py
[testenv:check_readme]
skip_install=True
setenv = SETUPTOOLS_SCM_PRETEND_VERSION=2.0
deps=
- readme
check-manifest
+ docutils
+ pygments
commands=
- python setup.py check -r
rst2html.py README.rst {envlogdir}/README.html --strict []
check-manifest
-[testenv:upload]
+[testenv:check_dist]
deps=
wheel
twine
commands=
- python setup.py clean --all rotate -k - -m .whl,.tar.gz,.zip
+ python setup.py clean --all rotate -k 0 -m .whl,.tar.gz,.zip
python setup.py -q egg_info
- python setup.py -q sdist --formats zip bdist_wheel register
+ python setup.py -q sdist --formats zip bdist_wheel
+ twine check dist/*