├── requirements_ci.txt
├── .gitattributes
├── swiglpk
├── __init__.py
└── glpk.i
├── MANIFEST.in
├── setup.cfg
├── .gitignore
├── .github
├── dependabot.yml
└── workflows
│ ├── cron.yml
│ ├── main.yml
│ ├── versions.yml
│ ├── build-sdist.yml
│ ├── release.yml
│ └── build-wheels.yml
├── tox.ini
├── scripts
├── find_newest_glpk_release.py
├── find_swiglpk_version.py
└── build_glpk.py
├── examples
└── example.py
├── config.sh
├── test_swiglpk.py
├── README.rst
├── setup.py
├── _version.py
├── LICENSE
└── versioneer.py
/requirements_ci.txt:
--------------------------------------------------------------------------------
1 | requests
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | _version.py export-subst
2 |
--------------------------------------------------------------------------------
/swiglpk/__init__.py:
--------------------------------------------------------------------------------
1 | from .swiglpk import *
2 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include versioneer.py
3 | include _version.py
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [versioneer]
2 | VCS = git
3 | style = pep440
4 | versionfile_source = _version.py
5 | versionfile_build =
6 | tag_prefix =
7 | parentdir_prefix = swiglpk-
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | build/
4 | ext/glpk-*
5 | glpk_wrap.c
6 | swiglpk.py
7 | _swiglpk.so
8 | glpk.h
9 | glpk_clean.h
10 | .idea
11 | swiglpk.egg-info/
12 | dist/
13 | .tox*
14 | *.so
15 | *~
16 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Set update schedule for GitHub Actions
2 |
3 | version: 2
4 | updates:
5 |
6 | - package-ecosystem: "github-actions"
7 | directory: "/"
8 | schedule:
9 | # Check for updates to GitHub Actions every month
10 | interval: "monthly"
11 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = flake8, py27, py36, py37, py38, py39
3 |
4 | [testenv:flake8]
5 | basepython=python
6 | deps=flake8
7 | commands=flake8 swiglpk
8 |
9 | [testenv]
10 | setenv =
11 | PYTHONPATH = {toxinidir}:{toxinidir}/swiglpk
12 | deps=nose
13 | commands =
14 | pip install -U pip
15 | python setup.py nosetests
16 |
--------------------------------------------------------------------------------
/.github/workflows/cron.yml:
--------------------------------------------------------------------------------
1 | name: Cron
2 |
3 | on:
4 | schedule:
5 | # Run every Monday at 08:30.
6 | - cron: "30 8 * * 1"
7 |
8 | jobs:
9 | versions:
10 | uses: ./.github/workflows/versions.yml
11 |
12 | build_sdist:
13 | needs: versions
14 | uses: ./.github/workflows/build-sdist.yml
15 | with:
16 | glpk_version: ${{ needs.versions.outputs.glpk_version }}
17 |
18 | build_wheels:
19 | needs: versions
20 | uses: ./.github/workflows/build-wheels.yml
21 | with:
22 | glpk_version: ${{ needs.versions.outputs.glpk_version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - devel
8 | pull_request:
9 | branches:
10 | - master
11 | - devel
12 |
13 | jobs:
14 | versions:
15 | uses: ./.github/workflows/versions.yml
16 |
17 | build_sdist:
18 | needs: versions
19 | uses: ./.github/workflows/build-sdist.yml
20 | with:
21 | glpk_version: ${{ needs.versions.outputs.glpk_version }}
22 |
23 | build_wheels:
24 | needs: versions
25 | uses: ./.github/workflows/build-wheels.yml
26 | with:
27 | glpk_version: ${{ needs.versions.outputs.glpk_version }}
28 |
--------------------------------------------------------------------------------
/scripts/find_newest_glpk_release.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import re
3 | import requests
4 |
5 | response = requests.get("http://ftp.gnu.org/gnu/glpk/", timeout=30)
6 | if response.ok:
7 | major, minor = 0, 0
8 | for item in set(re.findall('glpk-\d+\.\d+\.tar\.gz', response.text)):
9 | match = re.findall('(\d+)\.(\d+)', item)
10 | assert len(match) == 1
11 | assert len(match[0]) == 2
12 | new_major, new_minor = int(match[0][0]), int(match[0][1])
13 | if new_major > major:
14 | major = new_major
15 | minor = new_minor
16 | if new_major >= major and new_minor > minor:
17 | minor = new_minor
18 |
19 | print('{}.{}'.format(major, minor), end='')
20 | else:
21 | print("Couldn't reaction GNU FTP server. Status code {}".format(response.status))
22 |
23 |
--------------------------------------------------------------------------------
/scripts/find_swiglpk_version.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import requests
3 |
4 | try:
5 | from packaging.version import parse
6 | except ImportError:
7 | from pip._vendor.packaging.version import parse
8 |
9 |
10 | URL_PATTERN = 'https://pypi.python.org/pypi/{package}/json'
11 |
12 |
13 | def get_version(package, url_pattern=URL_PATTERN):
14 | """Return version of package on pypi.python.org using json. Adapted from https://stackoverflow.com/a/34366589"""
15 | req = requests.get(url_pattern.format(package=package))
16 | version = parse('0')
17 | if req.status_code == requests.codes.ok:
18 | # j = json.loads(req.text.encode(req.encoding))
19 | j = req.json()
20 | releases = j.get('releases', [])
21 | for release in releases:
22 | ver = parse(release)
23 | if not ver.is_prerelease:
24 | version = max(version, ver)
25 | return version
26 |
27 | version = get_version('swiglpk')
28 | major, minor, patch = version.base_version.split('.')
29 | print('{}.{}'.format(major, minor), end='')
--------------------------------------------------------------------------------
/.github/workflows/versions.yml:
--------------------------------------------------------------------------------
1 | name: Get Latest Versions
2 |
3 | on:
4 | workflow_dispatch: {}
5 | workflow_call:
6 | outputs:
7 | glpk_version:
8 | description: "The latest released GLPK (Major.Minor) version."
9 | value: ${{ jobs.versions.outputs.glpk_version }}
10 |
11 | swigplk_version:
12 | description: "The latest published swiglpk (Major.Minor) version."
13 | value: ${{ jobs.versions.outputs.swigplk_version }}
14 |
15 | jobs:
16 | versions:
17 | strategy:
18 | matrix:
19 | os: [ubuntu-latest]
20 | python-version: ["3.11"]
21 |
22 | runs-on: ${{ matrix.os }}
23 | outputs:
24 | glpk_version: ${{ steps.glpk.outputs.glpk_version }}
25 | swigplk_version: ${{ steps.swiglpk.outputs.swigplk_version }}
26 |
27 | steps:
28 | - uses: actions/checkout@v6
29 |
30 | - name: Set up Python ${{ matrix.python-version }}
31 | uses: actions/setup-python@v6
32 | with:
33 | python-version: ${{ matrix.python-version }}
34 |
35 | - name: Install dependencies
36 | run: |
37 | python -m pip install --upgrade pip setuptools wheel
38 | python -m pip install requests
39 |
40 | - name: Get latest GLPK version
41 | id: glpk
42 | run: |
43 | echo "glpk_version=$(python scripts/find_newest_glpk_release.py)" >> $GITHUB_OUTPUT
44 |
45 | - name: Get latest SWIGLPK version
46 | id: swiglpk
47 | run: |
48 | echo "swiglpk_version=$(python scripts/find_swiglpk_version.py)" >> $GITHUB_OUTPUT
49 |
--------------------------------------------------------------------------------
/examples/example.py:
--------------------------------------------------------------------------------
1 | from swiglpk import *
2 |
3 | ia = intArray(1+1000); ja = intArray(1+1000);
4 | ar = doubleArray(1+1000);
5 | lp = glp_create_prob();
6 | glp_set_prob_name(lp, "sample");
7 | glp_set_obj_dir(lp, GLP_MAX);
8 | glp_add_rows(lp, 3);
9 | glp_set_row_name(lp, 1, "p");
10 | glp_set_row_bnds(lp, 1, GLP_UP, 0.0, 100.0);
11 | glp_set_row_name(lp, 2, "q");
12 | glp_set_row_bnds(lp, 2, GLP_UP, 0.0, 600.0);
13 | glp_set_row_name(lp, 3, "r");
14 | glp_set_row_bnds(lp, 3, GLP_UP, 0.0, 300.0);
15 | glp_add_cols(lp, 3);
16 | glp_set_col_name(lp, 1, "x1");
17 | glp_set_col_bnds(lp, 1, GLP_LO, 0.0, 0.0);
18 | glp_set_obj_coef(lp, 1, 10.0);
19 | glp_set_col_name(lp, 2, "x2");
20 | glp_set_col_bnds(lp, 2, GLP_LO, 0.0, 0.0);
21 | glp_set_obj_coef(lp, 2, 6.0);
22 | glp_set_col_name(lp, 3, "x3");
23 | glp_set_col_bnds(lp, 3, GLP_LO, 0.0, 0.0);
24 | glp_set_obj_coef(lp, 3, 4.0);
25 | ia[1] = 1; ja[1] = 1; ar[1] = 1.0; # a[1,1] = 1
26 | ia[2] = 1; ja[2] = 2; ar[2] = 1.0; # a[1,2] = 1
27 | ia[3] = 1; ja[3] = 3; ar[3] = 1.0; # a[1,3] = 1
28 | ia[4] = 2; ja[4] = 1; ar[4] = 10.0; # a[2,1] = 10
29 | ia[5] = 3; ja[5] = 1; ar[5] = 2.0; # a[3,1] = 2
30 | ia[6] = 2; ja[6] = 2; ar[6] = 4.0; # a[2,2] = 4
31 | ia[7] = 3; ja[7] = 2; ar[7] = 2.0; # a[3,2] = 2
32 | ia[8] = 2; ja[8] = 3; ar[8] = 5.0; # a[2,3] = 5
33 | ia[9] = 3; ja[9] = 3; ar[9] = 6.0; # a[3,3] = 6
34 | glp_load_matrix(lp, 9, ia, ja, ar);
35 | glp_simplex(lp, None);
36 | Z = glp_get_obj_val(lp);
37 | x1 = glp_get_col_prim(lp, 1);
38 | x2 = glp_get_col_prim(lp, 2);
39 | x3 = glp_get_col_prim(lp, 3);
40 | print "\nZ = %g; x1 = %g; x2 = %g; x3 = %g\n" % (Z, x1, x2, x3);
41 | glp_delete_prob(lp);
42 |
--------------------------------------------------------------------------------
/.github/workflows/build-sdist.yml:
--------------------------------------------------------------------------------
1 | name: Build Source Distribution
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | glpk_version:
7 | required: true
8 | description: "The latest released GLPK (Major.Minor) version."
9 | type: string
10 |
11 | workflow_call:
12 | inputs:
13 | glpk_version:
14 | required: true
15 | description: "The latest released GLPK (Major.Minor) version."
16 | type: string
17 |
18 | jobs:
19 | build_sdist:
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | os: [ubuntu-latest]
24 | python: ["3.11"]
25 |
26 | name: Build the source distribution
27 | runs-on: ${{ matrix.os }}
28 |
29 | steps:
30 | - uses: actions/checkout@v6
31 |
32 | - uses: actions/setup-python@v6
33 | name: Set up Python ${{ matrix.python }}
34 | with:
35 | python-version: ${{ matrix.python }}
36 |
37 | - name: Install systems dependencies
38 | run: sudo apt install libgmp-dev swig wget
39 |
40 | - name: Download and unpack GLPK
41 | env:
42 | NEW_GLPK_VERSION: ${{ inputs.glpk_version }}
43 | run: |
44 | wget "http://ftp.gnu.org/gnu/glpk/glpk-$NEW_GLPK_VERSION.tar.gz"
45 | tar -xf glpk-$NEW_GLPK_VERSION.tar.gz
46 |
47 | - name: Build source distribution
48 | env:
49 | NEW_GLPK_VERSION: ${{ inputs.glpk_version}}
50 | run: GLPK_HEADER_PATH=glpk-$NEW_GLPK_VERSION/src python setup.py sdist --dist-dir=./wheelhouse
51 |
52 | - uses: actions/upload-artifact@v5
53 | with:
54 | name: sdist
55 | path: ./wheelhouse/*.tar.gz
56 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: CD
2 |
3 | on:
4 | push:
5 | tags:
6 | - '[0-9]+.[0-9]+.[0-9]+'
7 | - '[0-9]+.[0-9]+.[0-9]+(a|b|rc|post|dev)[0-9]+'
8 |
9 | jobs:
10 | versions:
11 | uses: ./.github/workflows/versions.yml
12 |
13 | build_sdist:
14 | needs: versions
15 | uses: ./.github/workflows/build-sdist.yml
16 | with:
17 | glpk_version: ${{ needs.versions.outputs.glpk_version }}
18 |
19 | build_wheels:
20 | needs: versions
21 | uses: ./.github/workflows/build-wheels.yml
22 | with:
23 | glpk_version: ${{ needs.versions.outputs.glpk_version }}
24 |
25 | release:
26 | needs: [versions, build_sdist, build_wheels]
27 | runs-on: ubuntu-latest
28 | permissions:
29 | # Write permissions are needed to create OIDC tokens.
30 | id-token: write
31 | # Write permissions are needed to make GitHub releases.
32 | contents: write
33 |
34 | steps:
35 | - uses: actions/checkout@v6
36 |
37 | # Download all artifacts to the same directory (sdist and wheels).
38 | - uses: actions/download-artifact@v6
39 | with:
40 | path: ./wheelhouse
41 | merge-multiple: true
42 |
43 | # We rely on a trusted publisher configuration being present on PyPI,
44 | # see https://docs.pypi.org/trusted-publishers/.
45 | - name: Publish to PyPI
46 | uses: pypa/gh-action-pypi-publish@release/v1
47 | with:
48 | packages-dir: wheelhouse/
49 |
50 | - name: Create GitHub release
51 | if: needs.versions.outputs.glpk_version != needs.versions.outputs.swigplk_version
52 | uses: softprops/action-gh-release@v2
53 | with:
54 | body: >
55 | Release of GLPK version ${{ needs.versions.outputs.glpk_version }}.
56 | draft: false
57 | prerelease: false
58 |
59 |
--------------------------------------------------------------------------------
/scripts/build_glpk.py:
--------------------------------------------------------------------------------
1 | # taken from https://github.com/opencobra/cobrapy/blob/devel/appveyor/build_glpk.py
2 |
3 | import os
4 | import tarfile
5 | import struct
6 | import shutil
7 | import urllib.request as urllib2
8 | import subprocess
9 |
10 | GUESS_VCVARS = (
11 | "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\"
12 | "VC\\Auxiliary\\Build\\vcvarsall.bat"
13 | )
14 |
15 | # these need to be set to the latest glpk version
16 | glpk_version = os.getenv('NEW_GLPK_VERSION')
17 |
18 | glpk_build_dir = "glpk_build/glpk-%s" % glpk_version
19 | url = "http://ftp.gnu.org/gnu/glpk/glpk-%s.tar.gz" % glpk_version
20 | bitness = struct.calcsize("P") * 8
21 | arch = "x86_amd64" if bitness == 64 else "x86"
22 |
23 | def find_vcvarsall():
24 | if os.path.isfile(GUESS_VCVARS):
25 | return(GUESS_VCVARS)
26 | for root, _, files in os.walk("C:\\Program Files\\Microsoft Visual Studio\\"):
27 | for f in files:
28 | if f == "vcvarsall.bat":
29 | return(os.path.join(root, f))
30 | raise RuntimeError("Could not find vcvarsall.bat :(")
31 |
32 |
33 | if not os.path.isdir("glpk_build/"):
34 | os.mkdir("glpk_build")
35 | if not os.path.isdir(glpk_build_dir):
36 | response = urllib2.urlopen(url)
37 | with open("glpk-download.tar.gz", "wb") as outfile:
38 | outfile.write(response.read())
39 | # assert md5("glpk-download.tar.gz") == glpk_md5
40 | with tarfile.open("glpk-download.tar.gz") as infile:
41 | infile.extractall("glpk_build")
42 |
43 | os.chdir("%s/w%d" % (glpk_build_dir, bitness))
44 | if not os.path.isfile("glpk.lib"):
45 | shutil.copy2("config_VC", "config.h")
46 | vc_setup = find_vcvarsall()
47 | subprocess.run(
48 | f'"{vc_setup}" {arch} & nmake /f Makefile_VC',
49 | check=True, shell=True
50 | )
51 | shutil.copy2("glpk.lib", "../../..")
52 | os.chdir("../../..")
53 | shutil.copy2(glpk_build_dir + "/src/glpk.h", ".")
54 |
--------------------------------------------------------------------------------
/.github/workflows/build-wheels.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test Wheels
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | glpk_version:
7 | required: true
8 | description: "The latest released GLPK (Major.Minor) version."
9 | type: string
10 |
11 | workflow_call:
12 | inputs:
13 | glpk_version:
14 | required: true
15 | description: "The latest released GLPK (Major.Minor) version."
16 | type: string
17 |
18 | jobs:
19 | build_wheels:
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13]
24 |
25 | name: Build wheels on ${{ matrix.os }}
26 | runs-on: ${{ matrix.os }}
27 |
28 | steps:
29 | - uses: actions/checkout@v6
30 |
31 | - name: Install SWIG
32 | if: runner.os == 'Windows'
33 | run: choco install swig -f -y
34 |
35 | - name: Build wheels
36 | uses: pypa/cibuildwheel@v3.3.0
37 | env:
38 | NEW_GLPK_VERSION: ${{ inputs.glpk_version }}
39 | GLPK_HEADER_PATH: glpk-${{ inputs.glpk_version }}/src
40 | CIBW_ENVIRONMENT_LINUX: GLPK_HEADER_PATH=/include
41 | CIBW_ENVIRONMENT_MACOS: PATH=$PATH:/usr/local/bin LDFLAGS="-L/usr/local/lib $LDFLAGS" LD_LIBRARY_PATH="/usr/local/lib"
42 | # install swig before build in each python environment
43 | # each job runs a python environment so this is equivalent to CIBW_BEFORE_ALL
44 | CIBW_BEFORE_BUILD_LINUX: source {project}/config.sh && pre_build
45 | CIBW_BEFORE_BUILD_MACOS: source {project}/config.sh && IS_OSX=true pre_build
46 | CIBW_BEFORE_BUILD_WINDOWS: rm -rf glpk_build && python -m pip install setuptools && python scripts/build_glpk.py
47 | CIBW_ARCHS_MACOS: "arm64 x86_64"
48 | CIBW_ARCHS_LINUX: "auto"
49 | CIBW_SKIP: pp* *-musllinux* cp36-* cp37-*
50 | # install before tests
51 | CIBW_TEST_COMMAND: cp {project}/test_swiglpk.py . && python test_swiglpk.py
52 |
53 | - uses: actions/upload-artifact@v5
54 | with:
55 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
56 | path: ./wheelhouse/*.whl
57 |
--------------------------------------------------------------------------------
/config.sh:
--------------------------------------------------------------------------------
1 | # Define custom utilities
2 | # Test for OSX with [ -n "$IS_OSX" ]
3 |
4 | function pre_build {
5 | # Any stuff that you need to do before you start building the wheels
6 | # Runs in the root directory of this repository.
7 | ADD_CFLAGS=""
8 | ADD_CONFIG_FLAGS=""
9 | GMP_VERSION="6.3.0"
10 | if [ -n "$IS_OSX" ]; then
11 | export CC=clang
12 | export CXX=clang++
13 | export BUILD_PREFIX="${BUILD_PREFIX:-/usr/local}"
14 | brew update
15 | brew install swig
16 | # Avoid mixing compiled files from different archs.
17 | rm -rf glpk-* /usr/local/lib/libgmp*.*
18 | rm -rf gmp-* /usr/local/lib/libglpk*.*
19 | if [[ "$ARCHFLAGS" == *"arm64"* ]]; then
20 | echo "Looks like we are cross-compiling, adjusting compiler flags."
21 | export ADD_CFLAGS="--target=arm64-apple-macos"
22 | export ADD_CONFIG_FLAGS="--host=aarch64-apple-darwin --build=x86_64-apple-darwin"
23 | fi
24 | export CFLAGS="-I/usr/local/include $ADD_CFLAGS $CFLAGS"
25 | export LDFLAGS="-L/usr/local/lib $ARCHFLAGS"
26 | echo "Downloading GMP"
27 | curl -O https://ftp.gnu.org/gnu/gmp/gmp-${GMP_VERSION}.tar.lz
28 | tar xzf gmp-$GMP_VERSION.tar.lz
29 | (cd gmp-$GMP_VERSION \
30 | && ./configure --prefix=$BUILD_PREFIX $ADD_CONFIG_FLAGS \
31 | && make install -j 2
32 | )
33 | else
34 | yum install -y pcre-devel gmp-devel
35 | # yum install automake
36 | curl -O -L http://downloads.sourceforge.net/swig/swig-3.0.10.tar.gz
37 | tar xzf swig-3.0.10.tar.gz
38 | (cd swig-3.0.10 \
39 | && ./configure --prefix=$BUILD_PREFIX \
40 | && make \
41 | && make install)
42 | pip install requests
43 |
44 | # Check for latest GLP and compile it for the target platform
45 | export NEW_GLPK_VERSION=$(python scripts/find_newest_glpk_release.py)
46 | fi
47 | echo "Downloading http://ftp.gnu.org/gnu/glpk/glpk-$NEW_GLPK_VERSION.tar.gz"
48 | curl -O "http://ftp.gnu.org/gnu/glpk/glpk-$NEW_GLPK_VERSION.tar.gz"
49 | tar xzf "glpk-$NEW_GLPK_VERSION.tar.gz"
50 | (cd "glpk-$NEW_GLPK_VERSION" \
51 | && ./configure --disable-reentrant --prefix=$BUILD_PREFIX --with-gmp $ADD_CONFIG_FLAGS \
52 | && make install -j 2) || cat "glpk-$NEW_GLPK_VERSION/config.log"
53 | echo "Installed to $BUILD_PREFIX"
54 | }
55 |
--------------------------------------------------------------------------------
/test_swiglpk.py:
--------------------------------------------------------------------------------
1 | # swiglpk - Swig Python bindings for the GNU Linear Programming Kit (GLPK)
2 | # Copyright (C) 2015 The Novo Nordisk Foundation Center for Biosustainability
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | from swiglpk import *
18 | import unittest
19 |
20 |
21 | class TestSwiglpk(unittest.TestCase):
22 |
23 | def test_swiglpk(self):
24 | print(glp_version())
25 | ia = intArray(1+1000); ja = intArray(1+1000);
26 | ar = doubleArray(1+1000)
27 | lp = glp_create_prob()
28 | glp_set_prob_name(lp, "sample")
29 | glp_set_obj_dir(lp, GLP_MAX)
30 | glp_add_rows(lp, 3)
31 | glp_set_row_name(lp, 1, "p")
32 | glp_set_row_bnds(lp, 1, GLP_UP, 0.0, 100.0)
33 | glp_set_row_name(lp, 2, "q")
34 | glp_set_row_bnds(lp, 2, GLP_UP, 0.0, 600.0)
35 | glp_set_row_name(lp, 3, "r")
36 | glp_set_row_bnds(lp, 3, GLP_UP, 0.0, 300.0)
37 | glp_add_cols(lp, 3)
38 | glp_set_col_name(lp, 1, "x1")
39 | glp_set_col_bnds(lp, 1, GLP_LO, 0.0, 0.0)
40 | glp_set_obj_coef(lp, 1, 10.0)
41 | glp_set_col_name(lp, 2, "x2")
42 | glp_set_col_bnds(lp, 2, GLP_LO, 0.0, 0.0)
43 | glp_set_obj_coef(lp, 2, 6.0)
44 | glp_set_col_name(lp, 3, "x3")
45 | glp_set_col_bnds(lp, 3, GLP_LO, 0.0, 0.0)
46 | glp_set_obj_coef(lp, 3, 4.0)
47 | ia[1] = 1; ja[1] = 1; ar[1] = 1.0; # a[1,1] = 1
48 | ia[2] = 1; ja[2] = 2; ar[2] = 1.0; # a[1,2] = 1
49 | ia[3] = 1; ja[3] = 3; ar[3] = 1.0; # a[1,3] = 1
50 | ia[4] = 2; ja[4] = 1; ar[4] = 10.0; # a[2,1] = 10
51 | ia[5] = 3; ja[5] = 1; ar[5] = 2.0; # a[3,1] = 2
52 | ia[6] = 2; ja[6] = 2; ar[6] = 4.0; # a[2,2] = 4
53 | ia[7] = 3; ja[7] = 2; ar[7] = 2.0; # a[3,2] = 2
54 | ia[8] = 2; ja[8] = 3; ar[8] = 5.0; # a[2,3] = 5
55 | ia[9] = 3; ja[9] = 3; ar[9] = 6.0; # a[3,3] = 6
56 | glp_load_matrix(lp, 9, ia, ja, ar)
57 | glp_simplex(lp, None)
58 | Z = glp_get_obj_val(lp)
59 | x1 = glp_get_col_prim(lp, 1)
60 | x2 = glp_get_col_prim(lp, 2)
61 | x3 = glp_get_col_prim(lp, 3)
62 | print("\nZ = %g; x1 = %g; x2 = %g; x3 = %g\n" % (Z, x1, x2, x3))
63 |
64 | self.assertAlmostEqual(Z, 733.3333333333333)
65 | self.assertAlmostEqual(x1, 33.333333333333336)
66 | self.assertAlmostEqual(x2, 66.66666666666666)
67 | self.assertAlmostEqual(x3, 0)
68 |
69 | glp_delete_prob(lp);
70 |
71 |
72 | if __name__ == "__main__":
73 | unittest.main()
74 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | swiglpk
2 | =======
3 |
4 | *Plain python bindings for the GNU Linear Programming Kit (GLPK)*
5 |
6 | |PyPI| |License| |Build Status|
7 |
8 | Why?
9 | ~~~~
10 |
11 | *swiglpk* is not a high-level wrapper for GLPK (take a look at
12 | `optlang `__ if you are
13 | interested in a python-based mathematical programming language). It just
14 | provides plain vanilla `swig `__ bindings to the
15 | underlying C library. In constrast to other GLPK wrappers for python
16 | (e.g. `PyGLPK `__,
17 | `Python-GLPK `__,
18 | `ctypes-glpk `__,
19 | `ecyglpki `__ etc.) it is fairly
20 | version agnostic: it will try to guess the location of the glpk.h header
21 | file (using ``which glpsol``) and then compile the extension for your
22 | particular GLPK installation. Furthermore, swiglpk provides binary wheels
23 | for all major platforms, which are always up-to-date with the most
24 | recent GLPK version (swiglpk versions follow GLPK versioning in the major
25 | and minor version digits to emphasize that).
26 |
27 | Please show us some love by staring this repo if you find swiglpk useful!
28 |
29 | Installation
30 | ~~~~~~~~~~~~
31 |
32 | ::
33 |
34 | pip install swiglpk
35 |
36 | That's it. swiglpk comes with binary wheels for Windows, Mac, and Linux. No installation of third-party dependencies necessary.
37 |
38 | Example
39 | ~~~~~~~
40 |
41 | Running the following (slightly adapted) example from the `GLPK
42 | manual `__ ...
43 |
44 | ::
45 |
46 | from swiglpk import *
47 |
48 | ia = intArray(1+1000); ja = intArray(1+1000);
49 | ar = doubleArray(1+1000);
50 | lp = glp_create_prob();
51 | glp_set_prob_name(lp, "sample");
52 | glp_set_obj_dir(lp, GLP_MAX);
53 | glp_add_rows(lp, 3);
54 | glp_set_row_name(lp, 1, "p");
55 | glp_set_row_bnds(lp, 1, GLP_UP, 0.0, 100.0);
56 | glp_set_row_name(lp, 2, "q");
57 | glp_set_row_bnds(lp, 2, GLP_UP, 0.0, 600.0);
58 | glp_set_row_name(lp, 3, "r");
59 | glp_set_row_bnds(lp, 3, GLP_UP, 0.0, 300.0);
60 | glp_add_cols(lp, 3);
61 | glp_set_col_name(lp, 1, "x1");
62 | glp_set_col_bnds(lp, 1, GLP_LO, 0.0, 0.0);
63 | glp_set_obj_coef(lp, 1, 10.0);
64 | glp_set_col_name(lp, 2, "x2");
65 | glp_set_col_bnds(lp, 2, GLP_LO, 0.0, 0.0);
66 | glp_set_obj_coef(lp, 2, 6.0);
67 | glp_set_col_name(lp, 3, "x3");
68 | glp_set_col_bnds(lp, 3, GLP_LO, 0.0, 0.0);
69 | glp_set_obj_coef(lp, 3, 4.0);
70 | ia[1] = 1; ja[1] = 1; ar[1] = 1.0; # a[1,1] = 1
71 | ia[2] = 1; ja[2] = 2; ar[2] = 1.0; # a[1,2] = 1
72 | ia[3] = 1; ja[3] = 3; ar[3] = 1.0; # a[1,3] = 1
73 | ia[4] = 2; ja[4] = 1; ar[4] = 10.0; # a[2,1] = 10
74 | ia[5] = 3; ja[5] = 1; ar[5] = 2.0; # a[3,1] = 2
75 | ia[6] = 2; ja[6] = 2; ar[6] = 4.0; # a[2,2] = 4
76 | ia[7] = 3; ja[7] = 2; ar[7] = 2.0; # a[3,2] = 2
77 | ia[8] = 2; ja[8] = 3; ar[8] = 5.0; # a[2,3] = 5
78 | ia[9] = 3; ja[9] = 3; ar[9] = 6.0; # a[3,3] = 6
79 | glp_load_matrix(lp, 9, ia, ja, ar);
80 | glp_simplex(lp, None);
81 | Z = glp_get_obj_val(lp);
82 | x1 = glp_get_col_prim(lp, 1);
83 | x2 = glp_get_col_prim(lp, 2);
84 | x3 = glp_get_col_prim(lp, 3);
85 | print("\nZ = %g; x1 = %g; x2 = %g; x3 = %g\n" % (Z, x1, x2, x3))
86 | glp_delete_prob(lp);
87 |
88 | ... will produce the following output (the example can also be found at
89 | examples/example.py):
90 |
91 | ::
92 |
93 | GLPK Simplex Optimizer, v4.52
94 | 3 rows, 3 columns, 9 non-zeros
95 | * 0: obj = 0.000000000e+00 infeas = 0.000e+00 (0)
96 | * 2: obj = 7.333333333e+02 infeas = 0.000e+00 (0)
97 | OPTIMAL LP SOLUTION FOUND
98 |
99 | Z = 733.333; x1 = 33.3333; x2 = 66.6667; x3 = 0
100 |
101 | Pretty ugly right? Consider using `optlang `__ for formulating and solving your optimization problems.
102 |
103 | Documentation
104 | ~~~~~~~~~~~~~
105 |
106 | You can find documentation on GLPK's C API `here `__
107 |
108 | Development
109 | ~~~~~~~~~~~
110 |
111 | You still want to install it from source? Then you'll need to install the following
112 | dependencies first.
113 |
114 | - GLPK
115 | - swig
116 |
117 | If you're on OS X, swig and GLPK can easily be installed with
118 | `homebrew `__.
119 |
120 | ::
121 |
122 | brew install swig glpk
123 |
124 | If you're using ubuntu linux, you can install swig and GLPK using
125 | ``apt-get``.
126 |
127 | ::
128 |
129 | apt-get install glpk-utils libglpk-dev swig
130 |
131 | If you're on Windows, you are on your own (checkout the `appveyor.yml `_ config file for directions).
132 |
133 | Then clone the repo and run the following.
134 | ::
135 |
136 | python setup.py install
137 |
138 |
139 | .. |PyPI| image:: https://img.shields.io/pypi/v/swiglpk.svg
140 | :target: https://pypi.python.org/pypi/swiglpk
141 | .. |License| image:: https://img.shields.io/badge/License-GPL%20v3-blue.svg
142 | :target: http://www.gnu.org/licenses/gpl-3.0
143 | .. |Build Status| image:: https://github.com/biosustain/swiglpk/actions/workflows/main.yml/badge.svg
144 | :target: https://github.com/biosustain/swiglpk/actions/workflows/main.yml
145 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # swiglpk - Swig Python bindings for the GNU Linear Programming Kit (GLPK)
2 | # Copyright (C) 2015 The Novo Nordisk Foundation Center for Biosustainability
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | # http://stackoverflow.com/questions/12491328/python-distutils-not-include-the-swig-generated-module
18 |
19 | import os
20 | import warnings
21 | from setuptools import setup, Extension
22 | from distutils.command.build import build
23 | import versioneer
24 |
25 |
26 | ENV_GLPK_HEADER_PATH = 'GLPK_HEADER_PATH'
27 | GLPK_HEADER_NAME = 'glpk.h'
28 | GLPSOL_BINARY_NAME = 'glpsol'
29 | INCLUDE_DIRS = (
30 | '/usr/local/include',
31 | '/usr/include',
32 | '/include',
33 | )
34 |
35 |
36 | class CustomBuild(build):
37 | sub_commands = [
38 | ('build_ext', build.has_ext_modules),
39 | ('build_py', build.has_pure_modules),
40 | ('build_clib', build.has_c_libraries),
41 | ('build_scripts', build.has_scripts),
42 | ]
43 |
44 |
45 | def find_glpk_header():
46 | # If a path is provded by the environment, expect the GLPK header there.
47 | given_path = os.environ.get(ENV_GLPK_HEADER_PATH, None)
48 | if given_path:
49 | if os.path.isfile(given_path):
50 | if os.path.basename(given_path) == GLPK_HEADER_NAME:
51 | return os.path.abspath(os.path.dirname(given_path))
52 | else:
53 | warnings.warn(
54 | 'The environment variable {}="{}" points to a file not '
55 | 'named {}.'.format(ENV_GLPK_HEADER_PATH, given_path,
56 | GLPK_HEADER_NAME), stacklevel=2
57 | )
58 | elif os.path.isdir(given_path):
59 | header = os.path.join(given_path, GLPK_HEADER_NAME)
60 |
61 | if os.path.isfile(header):
62 | return os.path.abspath(given_path)
63 | else:
64 | warnings.warn(
65 | 'The environment variable {}="{}" is set to a directory '
66 | 'that does not contain {}.'.format(ENV_GLPK_HEADER_PATH,
67 | given_path, GLPK_HEADER_NAME), stacklevel=2
68 | )
69 | else:
70 | warnings.warn(
71 | 'The environment variable {}="{}" does not point to a '
72 | 'directory or file.'.format(ENV_GLPK_HEADER_PATH, given_path),
73 | stacklevel=2
74 | )
75 |
76 | # Look for a drop-in header file next.
77 | if os.path.isfile(GLPK_HEADER_NAME):
78 | return os.getcwd()
79 |
80 | include_dirs = list(INCLUDE_DIRS)
81 |
82 | # If glpsol is found, look for an include directory in its vicinity.
83 | try:
84 | from shutil import which
85 | except ImportError: # Python < 3.3.
86 | pass
87 | else:
88 | glpsol = which(GLPSOL_BINARY_NAME)
89 |
90 | if glpsol:
91 | glpsol_path = os.path.dirname(glpsol)
92 | glpsol_root = os.path.dirname(glpsol_path)
93 | glpsol_include_path = os.path.join(glpsol_root, "include")
94 | include_dirs.insert(0, glpsol_include_path)
95 |
96 | # Look at common places.
97 | for path in include_dirs:
98 | if os.path.isdir(path):
99 | header = os.path.join(path, GLPK_HEADER_NAME)
100 |
101 | if os.path.isfile(header):
102 | return os.path.abspath(path)
103 |
104 | raise FileNotFoundError('Failed to locate {}.'.format(GLPK_HEADER_NAME))
105 |
106 |
107 | # Warn users installing via pip that this is a source build.
108 | print(
109 | '='*30,
110 | 'BUILDING SWIGLPK FROM SOURCE.',
111 | 'If you are installing SWIGLPK via pip, this means that no wheels are'
112 | ' offered for your platform or Python version yet.',
113 | 'This can be the case if you adopt a new Python version early.',
114 | 'A source build requires GLPK, SWIG, and GMP (Linux/Mac) to be installed!',
115 | '='*30,
116 | sep='\n'
117 | )
118 |
119 | # Find the GLPK header.
120 | try:
121 | print('Looking for {}...'.format(GLPK_HEADER_NAME))
122 |
123 | glpk_header_dirname = find_glpk_header()
124 | except FileNotFoundError as error:
125 | raise RuntimeError(
126 | 'A source build of SWIGLPK requires GLPK to be installed but we could'
127 | ' not find {0}. You may put {0} inside the current directory or link'
128 | ' to its parent folder via {1}.'
129 | .format(GLPK_HEADER_NAME, ENV_GLPK_HEADER_PATH)
130 | ) from error
131 | else:
132 | print('Found {} in {}.'.format(GLPK_HEADER_NAME, glpk_header_dirname))
133 |
134 | # Make sure SWIG is available.
135 | try:
136 | from shutil import which
137 | except ImportError:
138 | pass # This check is not critical given the warning above.
139 | else:
140 | print('Making sure SWIG is available...')
141 |
142 | if which('swig'):
143 | print('Found the swig executable.')
144 | else:
145 | raise RuntimeError(
146 | 'A source build of SWIGLPK requires SWIG to be installed but we '
147 | 'could not find the swig executable.')
148 |
149 | # Assemble custom_cmd_class.
150 | custom_cmd_class = versioneer.get_cmdclass()
151 | custom_cmd_class['build'] = CustomBuild
152 |
153 | try:
154 | from wheel.bdist_wheel import bdist_wheel
155 |
156 | class CustomBdistWheel(bdist_wheel):
157 | def run(self):
158 | self.run_command('build_ext')
159 | bdist_wheel.run(self)
160 |
161 | custom_cmd_class['bdist_wheel'] = CustomBdistWheel
162 | except ImportError:
163 | pass # Custom command not needed if wheel is not installed.
164 |
165 | # Read long description from README.rst.
166 | try:
167 | with open('README.rst', 'r') as f:
168 | long_description = f.read()
169 | except Exception:
170 | long_description = ''
171 |
172 | # Run setup.
173 | setup(
174 | name='swiglpk',
175 | version=versioneer.get_version(),
176 | cmdclass=custom_cmd_class,
177 | packages=['swiglpk'],
178 | package_dir={'swiglpk': 'swiglpk'},
179 | author='Nikolaus Sonnenschein',
180 | author_email='niko.sonnenschein@gmail.com',
181 | description='swiglpk - Simple swig bindings for the GNU Linear Programming Kit',
182 | license='GPL v3',
183 | keywords='optimization swig glpk',
184 | url='https://github.com/biosustain/swiglpk',
185 | long_description=long_description,
186 | test_suite='nose.collector',
187 | classifiers=[
188 | 'Development Status :: 5 - Production/Stable',
189 | 'Topic :: Scientific/Engineering',
190 | 'Topic :: Software Development',
191 | 'Intended Audience :: Science/Research',
192 | 'Programming Language :: Python :: 2.7',
193 | 'Programming Language :: Python :: 3.4',
194 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)'
195 | ],
196 | ext_modules=[
197 | Extension(
198 | "swiglpk._swiglpk",
199 | sources=["swiglpk/glpk.i"],
200 | include_dirs=[glpk_header_dirname],
201 | swig_opts=["-I"+glpk_header_dirname],
202 | libraries=['glpk'])
203 | ],
204 | include_package_data=True
205 | )
206 |
--------------------------------------------------------------------------------
/swiglpk/glpk.i:
--------------------------------------------------------------------------------
1 | /*
2 | * swiglpk - Swig Python bindings for the GNU Linear Programming Kit (GLPK)
3 | * Copyright (C) 2015 The Novo Nordisk Foundation Center for Biosustainability
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | %ignore glp_vprintf;
20 | %ignore glp_netgen_prob;
21 |
22 | %module swiglpk
23 |
24 | %{
25 | #define SWIG_FILE_WITH_INIT
26 | #include "glpk.h"
27 |
28 | int wrap_glp_term_hook_cb(void *info, const char *s)
29 | {
30 | PyObject *callback, *args, *r;
31 |
32 | callback = (PyObject *)info;
33 |
34 | args = Py_BuildValue("(s)", s);
35 | if (args == NULL) {
36 | PyErr_Print();
37 | goto out;
38 | }
39 |
40 | r = PyObject_Call(callback, args, NULL);
41 | if (r == NULL) {
42 | PyErr_Print();
43 | goto out;
44 | }
45 |
46 | out:
47 | Py_XDECREF(r);
48 | Py_XDECREF(args);
49 | return 1;
50 | }
51 | %}
52 |
53 | %rename(glp_term_hook) wrap_glp_term_hook;
54 | %inline %{
55 | PyObject *wrap_glp_term_hook(PyObject *callback)
56 | {
57 | if (callback == Py_None) {
58 | glp_term_hook(NULL, NULL);
59 | } else {
60 | glp_term_hook(wrap_glp_term_hook_cb, callback);
61 | }
62 |
63 | Py_RETURN_NONE;
64 | }
65 | %}
66 |
67 | %include glpk.h
68 |
69 | %include "carrays.i"
70 | %array_class(int, intArray);
71 | %array_class(double, doubleArray);
72 |
73 | %inline %{
74 | PyObject* get_col_primals(glp_prob *P) {
75 | int n = glp_get_num_cols(P);
76 | PyObject* list = PyList_New(n);
77 | double prim = 0.0;
78 | int n_int = glp_get_num_int(P);
79 | int i = 0;
80 |
81 | if (n_int == 0) {
82 | for(i=1; i<=n; i++) {
83 | prim = glp_get_col_prim(P, i);
84 | PyList_SetItem(list, i-1, PyFloat_FromDouble(prim));
85 | }
86 | } else {
87 | for(i=1; i<=n; i++) {
88 | prim = glp_mip_col_val(P, i);
89 | PyList_SetItem(list, i-1, PyFloat_FromDouble(prim));
90 | }
91 | }
92 |
93 | return list;
94 | }
95 |
96 | PyObject* get_col_duals(glp_prob *P) {
97 | int n = glp_get_num_cols(P);
98 | PyObject* list = PyList_New(n);
99 | double dual = 0.0;
100 | int i = 0;
101 | for(i=1; i<=n; i++) {
102 | dual = glp_get_col_dual(P, i);
103 | PyList_SetItem(list, i-1, PyFloat_FromDouble(dual));
104 | }
105 |
106 | return list;
107 | }
108 |
109 | PyObject* get_row_primals(glp_prob *P) {
110 | int n = glp_get_num_rows(P);
111 | PyObject* list = PyList_New(n);
112 | double prim = 0.0;
113 | int n_int = glp_get_num_int(P);
114 | int i = 0;
115 |
116 | if (n_int == 0) {
117 | for(i=1; i<=n; i++) {
118 | prim = glp_get_row_prim(P, i);
119 | PyList_SetItem(list, i-1, PyFloat_FromDouble(prim));
120 | }
121 | } else {
122 | for(i=1; i<=n; i++) {
123 | prim = glp_mip_row_val(P, i);
124 | PyList_SetItem(list, i-1, PyFloat_FromDouble(prim));
125 | }
126 | }
127 |
128 | return list;
129 | }
130 |
131 | PyObject* get_row_duals(glp_prob *P) {
132 | int n = glp_get_num_rows(P);
133 | PyObject* list = PyList_New(n);
134 | double dual = 0.0;
135 | int i = 0;
136 | for(i=1; i<=n; i++){
137 | dual = glp_get_row_dual(P, i);
138 | PyList_SetItem(list, i-1, PyFloat_FromDouble(dual));
139 | }
140 |
141 | return list;
142 | }
143 |
144 | intArray* as_intArray(PyObject *list) {
145 | if (!PyList_Check(list))
146 | {
147 | PyErr_SetString(PyExc_TypeError, "not a list");
148 | return NULL;
149 | }
150 |
151 | PyObject *pmod = PyImport_ImportModule("swiglpk");
152 | if (!pmod)
153 | {
154 | PyErr_SetString(PyExc_ImportError, "swiglpk could not be imported");
155 | return NULL;
156 | }
157 |
158 | PyObject *pclass = PyObject_GetAttrString(pmod, "intArray");
159 | Py_DECREF(pmod);
160 | if (!pclass)
161 | {
162 | PyErr_SetString(PyExc_AttributeError, "swiglpk does not contain intArray");
163 | return NULL;
164 | }
165 |
166 | // Call doubleArray constructor with size + 1.
167 | Py_ssize_t size = PyList_Size(list);
168 | PyObject *pargs = Py_BuildValue("(i)", size + 1);
169 | if (!pargs)
170 | {
171 | Py_DECREF(pclass);
172 | PyErr_SetString(PyExc_RuntimeError, "building arguments list for intArray constructor failed");
173 | return NULL;
174 | }
175 |
176 | PyObject *pinst = PyObject_CallObject(pclass, pargs);
177 | Py_DECREF(pclass);
178 | Py_DECREF(pargs);
179 | if (!pinst)
180 | {
181 | PyErr_SetString(PyExc_RuntimeError, "creating intArray failed");
182 | return NULL;
183 | }
184 |
185 | PyObject* pthis = PyObject_GetAttrString(pinst, "this");
186 | if (!pthis)
187 | {
188 | Py_DECREF(pinst);
189 | PyErr_SetString(PyExc_AttributeError, "intArray 'this' attribute not found");
190 | return NULL;
191 | }
192 |
193 | // Convert 'this' to a C-style pointer.
194 | intArray* int_arr = 0;
195 | int res = SWIG_ConvertPtr(pthis, (void**)&int_arr, SWIGTYPE_p_intArray, 0);
196 | Py_DECREF(pthis);
197 | if (!SWIG_IsOK(res))
198 | {
199 | Py_DECREF(pinst);
200 | PyErr_SetString(PyExc_RuntimeError, "SWIG_ConvertPtr failed");
201 | return NULL;
202 | }
203 |
204 | PyObject *item;
205 | for (Py_ssize_t idx=0; idx Dict[str, str]:
24 | """Get the keywords needed to look up the version information."""
25 | # these strings will be replaced by git during git-archive.
26 | # setup.py/versioneer.py will grep for the variable names, so they must
27 | # each be defined on a line of their own. _version.py will just call
28 | # get_keywords().
29 | git_refnames = " (HEAD -> master)"
30 | git_full = "5fc641f42b5d5ae858bea4eb42f9d33f23dd06c2"
31 | git_date = "2025-12-11 14:57:32 +0100"
32 | keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
33 | return keywords
34 |
35 |
36 | class VersioneerConfig:
37 | """Container for Versioneer configuration parameters."""
38 |
39 | VCS: str
40 | style: str
41 | tag_prefix: str
42 | parentdir_prefix: str
43 | versionfile_source: str
44 | verbose: bool
45 |
46 |
47 | def get_config() -> VersioneerConfig:
48 | """Create, populate and return the VersioneerConfig() object."""
49 | # these strings are filled in when 'setup.py versioneer' creates
50 | # _version.py
51 | cfg = VersioneerConfig()
52 | cfg.VCS = "git"
53 | cfg.style = "pep440"
54 | cfg.tag_prefix = ""
55 | cfg.parentdir_prefix = "swiglpk-"
56 | cfg.versionfile_source = "_version.py"
57 | cfg.verbose = False
58 | return cfg
59 |
60 |
61 | class NotThisMethod(Exception):
62 | """Exception raised if a method is not valid for the current scenario."""
63 |
64 |
65 | LONG_VERSION_PY: Dict[str, str] = {}
66 | HANDLERS: Dict[str, Dict[str, Callable]] = {}
67 |
68 |
69 | def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator
70 | """Create decorator to mark a method as the handler of a VCS."""
71 | def decorate(f: Callable) -> Callable:
72 | """Store f in HANDLERS[vcs][method]."""
73 | if vcs not in HANDLERS:
74 | HANDLERS[vcs] = {}
75 | HANDLERS[vcs][method] = f
76 | return f
77 | return decorate
78 |
79 |
80 | def run_command(
81 | commands: List[str],
82 | args: List[str],
83 | cwd: Optional[str] = None,
84 | verbose: bool = False,
85 | hide_stderr: bool = False,
86 | env: Optional[Dict[str, str]] = None,
87 | ) -> Tuple[Optional[str], Optional[int]]:
88 | """Call the given command(s)."""
89 | assert isinstance(commands, list)
90 | process = None
91 |
92 | popen_kwargs: Dict[str, Any] = {}
93 | if sys.platform == "win32":
94 | # This hides the console window if pythonw.exe is used
95 | startupinfo = subprocess.STARTUPINFO()
96 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
97 | popen_kwargs["startupinfo"] = startupinfo
98 |
99 | for command in commands:
100 | try:
101 | dispcmd = str([command] + args)
102 | # remember shell=False, so use git.cmd on windows, not just git
103 | process = subprocess.Popen([command] + args, cwd=cwd, env=env,
104 | stdout=subprocess.PIPE,
105 | stderr=(subprocess.PIPE if hide_stderr
106 | else None), **popen_kwargs)
107 | break
108 | except OSError as e:
109 | if e.errno == errno.ENOENT:
110 | continue
111 | if verbose:
112 | print("unable to run %s" % dispcmd)
113 | print(e)
114 | return None, None
115 | else:
116 | if verbose:
117 | print("unable to find command, tried %s" % (commands,))
118 | return None, None
119 | stdout = process.communicate()[0].strip().decode()
120 | if process.returncode != 0:
121 | if verbose:
122 | print("unable to run %s (error)" % dispcmd)
123 | print("stdout was %s" % stdout)
124 | return None, process.returncode
125 | return stdout, process.returncode
126 |
127 |
128 | def versions_from_parentdir(
129 | parentdir_prefix: str,
130 | root: str,
131 | verbose: bool,
132 | ) -> Dict[str, Any]:
133 | """Try to determine the version from the parent directory name.
134 |
135 | Source tarballs conventionally unpack into a directory that includes both
136 | the project name and a version string. We will also support searching up
137 | two directory levels for an appropriately named parent directory
138 | """
139 | rootdirs = []
140 |
141 | for _ in range(3):
142 | dirname = os.path.basename(root)
143 | if dirname.startswith(parentdir_prefix):
144 | return {"version": dirname[len(parentdir_prefix):],
145 | "full-revisionid": None,
146 | "dirty": False, "error": None, "date": None}
147 | rootdirs.append(root)
148 | root = os.path.dirname(root) # up a level
149 |
150 | if verbose:
151 | print("Tried directories %s but none started with prefix %s" %
152 | (str(rootdirs), parentdir_prefix))
153 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
154 |
155 |
156 | @register_vcs_handler("git", "get_keywords")
157 | def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
158 | """Extract version information from the given file."""
159 | # the code embedded in _version.py can just fetch the value of these
160 | # keywords. When used from setup.py, we don't want to import _version.py,
161 | # so we do it with a regexp instead. This function is not used from
162 | # _version.py.
163 | keywords: Dict[str, str] = {}
164 | try:
165 | with open(versionfile_abs, "r") as fobj:
166 | for line in fobj:
167 | if line.strip().startswith("git_refnames ="):
168 | mo = re.search(r'=\s*"(.*)"', line)
169 | if mo:
170 | keywords["refnames"] = mo.group(1)
171 | if line.strip().startswith("git_full ="):
172 | mo = re.search(r'=\s*"(.*)"', line)
173 | if mo:
174 | keywords["full"] = mo.group(1)
175 | if line.strip().startswith("git_date ="):
176 | mo = re.search(r'=\s*"(.*)"', line)
177 | if mo:
178 | keywords["date"] = mo.group(1)
179 | except OSError:
180 | pass
181 | return keywords
182 |
183 |
184 | @register_vcs_handler("git", "keywords")
185 | def git_versions_from_keywords(
186 | keywords: Dict[str, str],
187 | tag_prefix: str,
188 | verbose: bool,
189 | ) -> Dict[str, Any]:
190 | """Get version information from git keywords."""
191 | if "refnames" not in keywords:
192 | raise NotThisMethod("Short version file found")
193 | date = keywords.get("date")
194 | if date is not None:
195 | # Use only the last line. Previous lines may contain GPG signature
196 | # information.
197 | date = date.splitlines()[-1]
198 |
199 | # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
200 | # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
201 | # -like" string, which we must then edit to make compliant), because
202 | # it's been around since git-1.5.3, and it's too difficult to
203 | # discover which version we're using, or to work around using an
204 | # older one.
205 | date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
206 | refnames = keywords["refnames"].strip()
207 | if refnames.startswith("$Format"):
208 | if verbose:
209 | print("keywords are unexpanded, not using")
210 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
211 | refs = {r.strip() for r in refnames.strip("()").split(",")}
212 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
213 | # just "foo-1.0". If we see a "tag: " prefix, prefer those.
214 | TAG = "tag: "
215 | tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
216 | if not tags:
217 | # Either we're using git < 1.8.3, or there really are no tags. We use
218 | # a heuristic: assume all version tags have a digit. The old git %d
219 | # expansion behaves like git log --decorate=short and strips out the
220 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish
221 | # between branches and tags. By ignoring refnames without digits, we
222 | # filter out many common branch names like "release" and
223 | # "stabilization", as well as "HEAD" and "master".
224 | tags = {r for r in refs if re.search(r'\d', r)}
225 | if verbose:
226 | print("discarding '%s', no digits" % ",".join(refs - tags))
227 | if verbose:
228 | print("likely tags: %s" % ",".join(sorted(tags)))
229 | for ref in sorted(tags):
230 | # sorting will prefer e.g. "2.0" over "2.0rc1"
231 | if ref.startswith(tag_prefix):
232 | r = ref[len(tag_prefix):]
233 | # Filter out refs that exactly match prefix or that don't start
234 | # with a number once the prefix is stripped (mostly a concern
235 | # when prefix is '')
236 | if not re.match(r'\d', r):
237 | continue
238 | if verbose:
239 | print("picking %s" % r)
240 | return {"version": r,
241 | "full-revisionid": keywords["full"].strip(),
242 | "dirty": False, "error": None,
243 | "date": date}
244 | # no suitable tags, so version is "0+unknown", but full hex is still there
245 | if verbose:
246 | print("no suitable tags, using unknown + full revision id")
247 | return {"version": "0+unknown",
248 | "full-revisionid": keywords["full"].strip(),
249 | "dirty": False, "error": "no suitable tags", "date": None}
250 |
251 |
252 | @register_vcs_handler("git", "pieces_from_vcs")
253 | def git_pieces_from_vcs(
254 | tag_prefix: str,
255 | root: str,
256 | verbose: bool,
257 | runner: Callable = run_command
258 | ) -> Dict[str, Any]:
259 | """Get version from 'git describe' in the root of the source tree.
260 |
261 | This only gets called if the git-archive 'subst' keywords were *not*
262 | expanded, and _version.py hasn't already been rewritten with a short
263 | version string, meaning we're inside a checked out source tree.
264 | """
265 | GITS = ["git"]
266 | if sys.platform == "win32":
267 | GITS = ["git.cmd", "git.exe"]
268 |
269 | # GIT_DIR can interfere with correct operation of Versioneer.
270 | # It may be intended to be passed to the Versioneer-versioned project,
271 | # but that should not change where we get our version from.
272 | env = os.environ.copy()
273 | env.pop("GIT_DIR", None)
274 | runner = functools.partial(runner, env=env)
275 |
276 | _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
277 | hide_stderr=not verbose)
278 | if rc != 0:
279 | if verbose:
280 | print("Directory %s not under git control" % root)
281 | raise NotThisMethod("'git rev-parse --git-dir' returned error")
282 |
283 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
284 | # if there isn't one, this yields HEX[-dirty] (no NUM)
285 | describe_out, rc = runner(GITS, [
286 | "describe", "--tags", "--dirty", "--always", "--long",
287 | "--match", f"{tag_prefix}[[:digit:]]*"
288 | ], cwd=root)
289 | # --long was added in git-1.5.5
290 | if describe_out is None:
291 | raise NotThisMethod("'git describe' failed")
292 | describe_out = describe_out.strip()
293 | full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
294 | if full_out is None:
295 | raise NotThisMethod("'git rev-parse' failed")
296 | full_out = full_out.strip()
297 |
298 | pieces: Dict[str, Any] = {}
299 | pieces["long"] = full_out
300 | pieces["short"] = full_out[:7] # maybe improved later
301 | pieces["error"] = None
302 |
303 | branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
304 | cwd=root)
305 | # --abbrev-ref was added in git-1.6.3
306 | if rc != 0 or branch_name is None:
307 | raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
308 | branch_name = branch_name.strip()
309 |
310 | if branch_name == "HEAD":
311 | # If we aren't exactly on a branch, pick a branch which represents
312 | # the current commit. If all else fails, we are on a branchless
313 | # commit.
314 | branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
315 | # --contains was added in git-1.5.4
316 | if rc != 0 or branches is None:
317 | raise NotThisMethod("'git branch --contains' returned error")
318 | branches = branches.split("\n")
319 |
320 | # Remove the first line if we're running detached
321 | if "(" in branches[0]:
322 | branches.pop(0)
323 |
324 | # Strip off the leading "* " from the list of branches.
325 | branches = [branch[2:] for branch in branches]
326 | if "master" in branches:
327 | branch_name = "master"
328 | elif not branches:
329 | branch_name = None
330 | else:
331 | # Pick the first branch that is returned. Good or bad.
332 | branch_name = branches[0]
333 |
334 | pieces["branch"] = branch_name
335 |
336 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
337 | # TAG might have hyphens.
338 | git_describe = describe_out
339 |
340 | # look for -dirty suffix
341 | dirty = git_describe.endswith("-dirty")
342 | pieces["dirty"] = dirty
343 | if dirty:
344 | git_describe = git_describe[:git_describe.rindex("-dirty")]
345 |
346 | # now we have TAG-NUM-gHEX or HEX
347 |
348 | if "-" in git_describe:
349 | # TAG-NUM-gHEX
350 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
351 | if not mo:
352 | # unparsable. Maybe git-describe is misbehaving?
353 | pieces["error"] = ("unable to parse git-describe output: '%s'"
354 | % describe_out)
355 | return pieces
356 |
357 | # tag
358 | full_tag = mo.group(1)
359 | if not full_tag.startswith(tag_prefix):
360 | if verbose:
361 | fmt = "tag '%s' doesn't start with prefix '%s'"
362 | print(fmt % (full_tag, tag_prefix))
363 | pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
364 | % (full_tag, tag_prefix))
365 | return pieces
366 | pieces["closest-tag"] = full_tag[len(tag_prefix):]
367 |
368 | # distance: number of commits since tag
369 | pieces["distance"] = int(mo.group(2))
370 |
371 | # commit: short hex revision ID
372 | pieces["short"] = mo.group(3)
373 |
374 | else:
375 | # HEX: no tags
376 | pieces["closest-tag"] = None
377 | out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
378 | pieces["distance"] = len(out.split()) # total number of commits
379 |
380 | # commit date: see ISO-8601 comment in git_versions_from_keywords()
381 | date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
382 | # Use only the last line. Previous lines may contain GPG signature
383 | # information.
384 | date = date.splitlines()[-1]
385 | pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
386 |
387 | return pieces
388 |
389 |
390 | def plus_or_dot(pieces: Dict[str, Any]) -> str:
391 | """Return a + if we don't already have one, else return a ."""
392 | if "+" in pieces.get("closest-tag", ""):
393 | return "."
394 | return "+"
395 |
396 |
397 | def render_pep440(pieces: Dict[str, Any]) -> str:
398 | """Build up version string, with post-release "local version identifier".
399 |
400 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
401 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
402 |
403 | Exceptions:
404 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
405 | """
406 | if pieces["closest-tag"]:
407 | rendered = pieces["closest-tag"]
408 | if pieces["distance"] or pieces["dirty"]:
409 | rendered += plus_or_dot(pieces)
410 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
411 | if pieces["dirty"]:
412 | rendered += ".dirty"
413 | else:
414 | # exception #1
415 | rendered = "0+untagged.%d.g%s" % (pieces["distance"],
416 | pieces["short"])
417 | if pieces["dirty"]:
418 | rendered += ".dirty"
419 | return rendered
420 |
421 |
422 | def render_pep440_branch(pieces: Dict[str, Any]) -> str:
423 | """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
424 |
425 | The ".dev0" means not master branch. Note that .dev0 sorts backwards
426 | (a feature branch will appear "older" than the master branch).
427 |
428 | Exceptions:
429 | 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
430 | """
431 | if pieces["closest-tag"]:
432 | rendered = pieces["closest-tag"]
433 | if pieces["distance"] or pieces["dirty"]:
434 | if pieces["branch"] != "master":
435 | rendered += ".dev0"
436 | rendered += plus_or_dot(pieces)
437 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
438 | if pieces["dirty"]:
439 | rendered += ".dirty"
440 | else:
441 | # exception #1
442 | rendered = "0"
443 | if pieces["branch"] != "master":
444 | rendered += ".dev0"
445 | rendered += "+untagged.%d.g%s" % (pieces["distance"],
446 | pieces["short"])
447 | if pieces["dirty"]:
448 | rendered += ".dirty"
449 | return rendered
450 |
451 |
452 | def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
453 | """Split pep440 version string at the post-release segment.
454 |
455 | Returns the release segments before the post-release and the
456 | post-release version number (or -1 if no post-release segment is present).
457 | """
458 | vc = str.split(ver, ".post")
459 | return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
460 |
461 |
462 | def render_pep440_pre(pieces: Dict[str, Any]) -> str:
463 | """TAG[.postN.devDISTANCE] -- No -dirty.
464 |
465 | Exceptions:
466 | 1: no tags. 0.post0.devDISTANCE
467 | """
468 | if pieces["closest-tag"]:
469 | if pieces["distance"]:
470 | # update the post release segment
471 | tag_version, post_version = pep440_split_post(pieces["closest-tag"])
472 | rendered = tag_version
473 | if post_version is not None:
474 | rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
475 | else:
476 | rendered += ".post0.dev%d" % (pieces["distance"])
477 | else:
478 | # no commits, use the tag as the version
479 | rendered = pieces["closest-tag"]
480 | else:
481 | # exception #1
482 | rendered = "0.post0.dev%d" % pieces["distance"]
483 | return rendered
484 |
485 |
486 | def render_pep440_post(pieces: Dict[str, Any]) -> str:
487 | """TAG[.postDISTANCE[.dev0]+gHEX] .
488 |
489 | The ".dev0" means dirty. Note that .dev0 sorts backwards
490 | (a dirty tree will appear "older" than the corresponding clean one),
491 | but you shouldn't be releasing software with -dirty anyways.
492 |
493 | Exceptions:
494 | 1: no tags. 0.postDISTANCE[.dev0]
495 | """
496 | if pieces["closest-tag"]:
497 | rendered = pieces["closest-tag"]
498 | if pieces["distance"] or pieces["dirty"]:
499 | rendered += ".post%d" % pieces["distance"]
500 | if pieces["dirty"]:
501 | rendered += ".dev0"
502 | rendered += plus_or_dot(pieces)
503 | rendered += "g%s" % pieces["short"]
504 | else:
505 | # exception #1
506 | rendered = "0.post%d" % pieces["distance"]
507 | if pieces["dirty"]:
508 | rendered += ".dev0"
509 | rendered += "+g%s" % pieces["short"]
510 | return rendered
511 |
512 |
513 | def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
514 | """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
515 |
516 | The ".dev0" means not master branch.
517 |
518 | Exceptions:
519 | 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
520 | """
521 | if pieces["closest-tag"]:
522 | rendered = pieces["closest-tag"]
523 | if pieces["distance"] or pieces["dirty"]:
524 | rendered += ".post%d" % pieces["distance"]
525 | if pieces["branch"] != "master":
526 | rendered += ".dev0"
527 | rendered += plus_or_dot(pieces)
528 | rendered += "g%s" % pieces["short"]
529 | if pieces["dirty"]:
530 | rendered += ".dirty"
531 | else:
532 | # exception #1
533 | rendered = "0.post%d" % pieces["distance"]
534 | if pieces["branch"] != "master":
535 | rendered += ".dev0"
536 | rendered += "+g%s" % pieces["short"]
537 | if pieces["dirty"]:
538 | rendered += ".dirty"
539 | return rendered
540 |
541 |
542 | def render_pep440_old(pieces: Dict[str, Any]) -> str:
543 | """TAG[.postDISTANCE[.dev0]] .
544 |
545 | The ".dev0" means dirty.
546 |
547 | Exceptions:
548 | 1: no tags. 0.postDISTANCE[.dev0]
549 | """
550 | if pieces["closest-tag"]:
551 | rendered = pieces["closest-tag"]
552 | if pieces["distance"] or pieces["dirty"]:
553 | rendered += ".post%d" % pieces["distance"]
554 | if pieces["dirty"]:
555 | rendered += ".dev0"
556 | else:
557 | # exception #1
558 | rendered = "0.post%d" % pieces["distance"]
559 | if pieces["dirty"]:
560 | rendered += ".dev0"
561 | return rendered
562 |
563 |
564 | def render_git_describe(pieces: Dict[str, Any]) -> str:
565 | """TAG[-DISTANCE-gHEX][-dirty].
566 |
567 | Like 'git describe --tags --dirty --always'.
568 |
569 | Exceptions:
570 | 1: no tags. HEX[-dirty] (note: no 'g' prefix)
571 | """
572 | if pieces["closest-tag"]:
573 | rendered = pieces["closest-tag"]
574 | if pieces["distance"]:
575 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
576 | else:
577 | # exception #1
578 | rendered = pieces["short"]
579 | if pieces["dirty"]:
580 | rendered += "-dirty"
581 | return rendered
582 |
583 |
584 | def render_git_describe_long(pieces: Dict[str, Any]) -> str:
585 | """TAG-DISTANCE-gHEX[-dirty].
586 |
587 | Like 'git describe --tags --dirty --always -long'.
588 | The distance/hash is unconditional.
589 |
590 | Exceptions:
591 | 1: no tags. HEX[-dirty] (note: no 'g' prefix)
592 | """
593 | if pieces["closest-tag"]:
594 | rendered = pieces["closest-tag"]
595 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
596 | else:
597 | # exception #1
598 | rendered = pieces["short"]
599 | if pieces["dirty"]:
600 | rendered += "-dirty"
601 | return rendered
602 |
603 |
604 | def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
605 | """Render the given version pieces into the requested style."""
606 | if pieces["error"]:
607 | return {"version": "unknown",
608 | "full-revisionid": pieces.get("long"),
609 | "dirty": None,
610 | "error": pieces["error"],
611 | "date": None}
612 |
613 | if not style or style == "default":
614 | style = "pep440" # the default
615 |
616 | if style == "pep440":
617 | rendered = render_pep440(pieces)
618 | elif style == "pep440-branch":
619 | rendered = render_pep440_branch(pieces)
620 | elif style == "pep440-pre":
621 | rendered = render_pep440_pre(pieces)
622 | elif style == "pep440-post":
623 | rendered = render_pep440_post(pieces)
624 | elif style == "pep440-post-branch":
625 | rendered = render_pep440_post_branch(pieces)
626 | elif style == "pep440-old":
627 | rendered = render_pep440_old(pieces)
628 | elif style == "git-describe":
629 | rendered = render_git_describe(pieces)
630 | elif style == "git-describe-long":
631 | rendered = render_git_describe_long(pieces)
632 | else:
633 | raise ValueError("unknown style '%s'" % style)
634 |
635 | return {"version": rendered, "full-revisionid": pieces["long"],
636 | "dirty": pieces["dirty"], "error": None,
637 | "date": pieces.get("date")}
638 |
639 |
640 | def get_versions() -> Dict[str, Any]:
641 | """Get version information or return default if unable to do so."""
642 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
643 | # __file__, we can work backwards from there to the root. Some
644 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
645 | # case we can only use expanded keywords.
646 |
647 | cfg = get_config()
648 | verbose = cfg.verbose
649 |
650 | try:
651 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
652 | verbose)
653 | except NotThisMethod:
654 | pass
655 |
656 | try:
657 | root = os.path.realpath(__file__)
658 | # versionfile_source is the relative path from the top of the source
659 | # tree (where the .git directory might live) to this file. Invert
660 | # this to find the root from __file__.
661 | for _ in cfg.versionfile_source.split('/'):
662 | root = os.path.dirname(root)
663 | except NameError:
664 | return {"version": "0+unknown", "full-revisionid": None,
665 | "dirty": None,
666 | "error": "unable to find root of source tree",
667 | "date": None}
668 |
669 | try:
670 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
671 | return render(pieces, cfg.style)
672 | except NotThisMethod:
673 | pass
674 |
675 | try:
676 | if cfg.parentdir_prefix:
677 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
678 | except NotThisMethod:
679 | pass
680 |
681 | return {"version": "0+unknown", "full-revisionid": None,
682 | "dirty": None,
683 | "error": "unable to compute version", "date": None}
684 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------
/versioneer.py:
--------------------------------------------------------------------------------
1 |
2 | # Version: 0.29
3 |
4 | """The Versioneer - like a rocketeer, but for versions.
5 |
6 | The Versioneer
7 | ==============
8 |
9 | * like a rocketeer, but for versions!
10 | * https://github.com/python-versioneer/python-versioneer
11 | * Brian Warner
12 | * License: Public Domain (Unlicense)
13 | * Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
14 | * [![Latest Version][pypi-image]][pypi-url]
15 | * [![Build Status][travis-image]][travis-url]
16 |
17 | This is a tool for managing a recorded version number in setuptools-based
18 | python projects. The goal is to remove the tedious and error-prone "update
19 | the embedded version string" step from your release process. Making a new
20 | release should be as easy as recording a new tag in your version-control
21 | system, and maybe making new tarballs.
22 |
23 |
24 | ## Quick Install
25 |
26 | Versioneer provides two installation modes. The "classic" vendored mode installs
27 | a copy of versioneer into your repository. The experimental build-time dependency mode
28 | is intended to allow you to skip this step and simplify the process of upgrading.
29 |
30 | ### Vendored mode
31 |
32 | * `pip install versioneer` to somewhere in your $PATH
33 | * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
34 | available, so you can also use `conda install -c conda-forge versioneer`
35 | * add a `[tool.versioneer]` section to your `pyproject.toml` or a
36 | `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
37 | * Note that you will need to add `tomli; python_version < "3.11"` to your
38 | build-time dependencies if you use `pyproject.toml`
39 | * run `versioneer install --vendor` in your source tree, commit the results
40 | * verify version information with `python setup.py version`
41 |
42 | ### Build-time dependency mode
43 |
44 | * `pip install versioneer` to somewhere in your $PATH
45 | * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
46 | available, so you can also use `conda install -c conda-forge versioneer`
47 | * add a `[tool.versioneer]` section to your `pyproject.toml` or a
48 | `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
49 | * add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
50 | to the `requires` key of the `build-system` table in `pyproject.toml`:
51 | ```toml
52 | [build-system]
53 | requires = ["setuptools", "versioneer[toml]"]
54 | build-backend = "setuptools.build_meta"
55 | ```
56 | * run `versioneer install --no-vendor` in your source tree, commit the results
57 | * verify version information with `python setup.py version`
58 |
59 | ## Version Identifiers
60 |
61 | Source trees come from a variety of places:
62 |
63 | * a version-control system checkout (mostly used by developers)
64 | * a nightly tarball, produced by build automation
65 | * a snapshot tarball, produced by a web-based VCS browser, like github's
66 | "tarball from tag" feature
67 | * a release tarball, produced by "setup.py sdist", distributed through PyPI
68 |
69 | Within each source tree, the version identifier (either a string or a number,
70 | this tool is format-agnostic) can come from a variety of places:
71 |
72 | * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows
73 | about recent "tags" and an absolute revision-id
74 | * the name of the directory into which the tarball was unpacked
75 | * an expanded VCS keyword ($Id$, etc)
76 | * a `_version.py` created by some earlier build step
77 |
78 | For released software, the version identifier is closely related to a VCS
79 | tag. Some projects use tag names that include more than just the version
80 | string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool
81 | needs to strip the tag prefix to extract the version identifier. For
82 | unreleased software (between tags), the version identifier should provide
83 | enough information to help developers recreate the same tree, while also
84 | giving them an idea of roughly how old the tree is (after version 1.2, before
85 | version 1.3). Many VCS systems can report a description that captures this,
86 | for example `git describe --tags --dirty --always` reports things like
87 | "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
88 | 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
89 | uncommitted changes).
90 |
91 | The version identifier is used for multiple purposes:
92 |
93 | * to allow the module to self-identify its version: `myproject.__version__`
94 | * to choose a name and prefix for a 'setup.py sdist' tarball
95 |
96 | ## Theory of Operation
97 |
98 | Versioneer works by adding a special `_version.py` file into your source
99 | tree, where your `__init__.py` can import it. This `_version.py` knows how to
100 | dynamically ask the VCS tool for version information at import time.
101 |
102 | `_version.py` also contains `$Revision$` markers, and the installation
103 | process marks `_version.py` to have this marker rewritten with a tag name
104 | during the `git archive` command. As a result, generated tarballs will
105 | contain enough information to get the proper version.
106 |
107 | To allow `setup.py` to compute a version too, a `versioneer.py` is added to
108 | the top level of your source tree, next to `setup.py` and the `setup.cfg`
109 | that configures it. This overrides several distutils/setuptools commands to
110 | compute the version when invoked, and changes `setup.py build` and `setup.py
111 | sdist` to replace `_version.py` with a small static file that contains just
112 | the generated version data.
113 |
114 | ## Installation
115 |
116 | See [INSTALL.md](./INSTALL.md) for detailed installation instructions.
117 |
118 | ## Version-String Flavors
119 |
120 | Code which uses Versioneer can learn about its version string at runtime by
121 | importing `_version` from your main `__init__.py` file and running the
122 | `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can
123 | import the top-level `versioneer.py` and run `get_versions()`.
124 |
125 | Both functions return a dictionary with different flavors of version
126 | information:
127 |
128 | * `['version']`: A condensed version string, rendered using the selected
129 | style. This is the most commonly used value for the project's version
130 | string. The default "pep440" style yields strings like `0.11`,
131 | `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section
132 | below for alternative styles.
133 |
134 | * `['full-revisionid']`: detailed revision identifier. For Git, this is the
135 | full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".
136 |
137 | * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
138 | commit date in ISO 8601 format. This will be None if the date is not
139 | available.
140 |
141 | * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
142 | this is only accurate if run in a VCS checkout, otherwise it is likely to
143 | be False or None
144 |
145 | * `['error']`: if the version string could not be computed, this will be set
146 | to a string describing the problem, otherwise it will be None. It may be
147 | useful to throw an exception in setup.py if this is set, to avoid e.g.
148 | creating tarballs with a version string of "unknown".
149 |
150 | Some variants are more useful than others. Including `full-revisionid` in a
151 | bug report should allow developers to reconstruct the exact code being tested
152 | (or indicate the presence of local changes that should be shared with the
153 | developers). `version` is suitable for display in an "about" box or a CLI
154 | `--version` output: it can be easily compared against release notes and lists
155 | of bugs fixed in various releases.
156 |
157 | The installer adds the following text to your `__init__.py` to place a basic
158 | version in `YOURPROJECT.__version__`:
159 |
160 | from ._version import get_versions
161 | __version__ = get_versions()['version']
162 | del get_versions
163 |
164 | ## Styles
165 |
166 | The setup.cfg `style=` configuration controls how the VCS information is
167 | rendered into a version string.
168 |
169 | The default style, "pep440", produces a PEP440-compliant string, equal to the
170 | un-prefixed tag name for actual releases, and containing an additional "local
171 | version" section with more detail for in-between builds. For Git, this is
172 | TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags
173 | --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the
174 | tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and
175 | that this commit is two revisions ("+2") beyond the "0.11" tag. For released
176 | software (exactly equal to a known tag), the identifier will only contain the
177 | stripped tag, e.g. "0.11".
178 |
179 | Other styles are available. See [details.md](details.md) in the Versioneer
180 | source tree for descriptions.
181 |
182 | ## Debugging
183 |
184 | Versioneer tries to avoid fatal errors: if something goes wrong, it will tend
185 | to return a version of "0+unknown". To investigate the problem, run `setup.py
186 | version`, which will run the version-lookup code in a verbose mode, and will
187 | display the full contents of `get_versions()` (including the `error` string,
188 | which may help identify what went wrong).
189 |
190 | ## Known Limitations
191 |
192 | Some situations are known to cause problems for Versioneer. This details the
193 | most significant ones. More can be found on Github
194 | [issues page](https://github.com/python-versioneer/python-versioneer/issues).
195 |
196 | ### Subprojects
197 |
198 | Versioneer has limited support for source trees in which `setup.py` is not in
199 | the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
200 | two common reasons why `setup.py` might not be in the root:
201 |
202 | * Source trees which contain multiple subprojects, such as
203 | [Buildbot](https://github.com/buildbot/buildbot), which contains both
204 | "master" and "slave" subprojects, each with their own `setup.py`,
205 | `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
206 | distributions (and upload multiple independently-installable tarballs).
207 | * Source trees whose main purpose is to contain a C library, but which also
208 | provide bindings to Python (and perhaps other languages) in subdirectories.
209 |
210 | Versioneer will look for `.git` in parent directories, and most operations
211 | should get the right version string. However `pip` and `setuptools` have bugs
212 | and implementation details which frequently cause `pip install .` from a
213 | subproject directory to fail to find a correct version string (so it usually
214 | defaults to `0+unknown`).
215 |
216 | `pip install --editable .` should work correctly. `setup.py install` might
217 | work too.
218 |
219 | Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
220 | some later version.
221 |
222 | [Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
223 | this issue. The discussion in
224 | [PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
225 | issue from the Versioneer side in more detail.
226 | [pip PR#3176](https://github.com/pypa/pip/pull/3176) and
227 | [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
228 | pip to let Versioneer work correctly.
229 |
230 | Versioneer-0.16 and earlier only looked for a `.git` directory next to the
231 | `setup.cfg`, so subprojects were completely unsupported with those releases.
232 |
233 | ### Editable installs with setuptools <= 18.5
234 |
235 | `setup.py develop` and `pip install --editable .` allow you to install a
236 | project into a virtualenv once, then continue editing the source code (and
237 | test) without re-installing after every change.
238 |
239 | "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
240 | convenient way to specify executable scripts that should be installed along
241 | with the python package.
242 |
243 | These both work as expected when using modern setuptools. When using
244 | setuptools-18.5 or earlier, however, certain operations will cause
245 | `pkg_resources.DistributionNotFound` errors when running the entrypoint
246 | script, which must be resolved by re-installing the package. This happens
247 | when the install happens with one version, then the egg_info data is
248 | regenerated while a different version is checked out. Many setup.py commands
249 | cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
250 | a different virtualenv), so this can be surprising.
251 |
252 | [Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
253 | this one, but upgrading to a newer version of setuptools should probably
254 | resolve it.
255 |
256 |
257 | ## Updating Versioneer
258 |
259 | To upgrade your project to a new release of Versioneer, do the following:
260 |
261 | * install the new Versioneer (`pip install -U versioneer` or equivalent)
262 | * edit `setup.cfg` and `pyproject.toml`, if necessary,
263 | to include any new configuration settings indicated by the release notes.
264 | See [UPGRADING](./UPGRADING.md) for details.
265 | * re-run `versioneer install --[no-]vendor` in your source tree, to replace
266 | `SRC/_version.py`
267 | * commit any changed files
268 |
269 | ## Future Directions
270 |
271 | This tool is designed to make it easily extended to other version-control
272 | systems: all VCS-specific components are in separate directories like
273 | src/git/ . The top-level `versioneer.py` script is assembled from these
274 | components by running make-versioneer.py . In the future, make-versioneer.py
275 | will take a VCS name as an argument, and will construct a version of
276 | `versioneer.py` that is specific to the given VCS. It might also take the
277 | configuration arguments that are currently provided manually during
278 | installation by editing setup.py . Alternatively, it might go the other
279 | direction and include code from all supported VCS systems, reducing the
280 | number of intermediate scripts.
281 |
282 | ## Similar projects
283 |
284 | * [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
285 | dependency
286 | * [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
287 | versioneer
288 | * [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
289 | plugin
290 |
291 | ## License
292 |
293 | To make Versioneer easier to embed, all its code is dedicated to the public
294 | domain. The `_version.py` that it creates is also in the public domain.
295 | Specifically, both are released under the "Unlicense", as described in
296 | https://unlicense.org/.
297 |
298 | [pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
299 | [pypi-url]: https://pypi.python.org/pypi/versioneer/
300 | [travis-image]:
301 | https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
302 | [travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
303 |
304 | """
305 | # pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
306 | # pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
307 | # pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
308 | # pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
309 | # pylint:disable=attribute-defined-outside-init,too-many-arguments
310 |
311 | import configparser
312 | import errno
313 | import json
314 | import os
315 | import re
316 | import subprocess
317 | import sys
318 | from pathlib import Path
319 | from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
320 | from typing import NoReturn
321 | import functools
322 |
323 | have_tomllib = True
324 | if sys.version_info >= (3, 11):
325 | import tomllib
326 | else:
327 | try:
328 | import tomli as tomllib
329 | except ImportError:
330 | have_tomllib = False
331 |
332 |
333 | class VersioneerConfig:
334 | """Container for Versioneer configuration parameters."""
335 |
336 | VCS: str
337 | style: str
338 | tag_prefix: str
339 | versionfile_source: str
340 | versionfile_build: Optional[str]
341 | parentdir_prefix: Optional[str]
342 | verbose: Optional[bool]
343 |
344 |
345 | def get_root() -> str:
346 | """Get the project root directory.
347 |
348 | We require that all commands are run from the project root, i.e. the
349 | directory that contains setup.py, setup.cfg, and versioneer.py .
350 | """
351 | root = os.path.realpath(os.path.abspath(os.getcwd()))
352 | setup_py = os.path.join(root, "setup.py")
353 | pyproject_toml = os.path.join(root, "pyproject.toml")
354 | versioneer_py = os.path.join(root, "versioneer.py")
355 | if not (
356 | os.path.exists(setup_py)
357 | or os.path.exists(pyproject_toml)
358 | or os.path.exists(versioneer_py)
359 | ):
360 | # allow 'python path/to/setup.py COMMAND'
361 | root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
362 | setup_py = os.path.join(root, "setup.py")
363 | pyproject_toml = os.path.join(root, "pyproject.toml")
364 | versioneer_py = os.path.join(root, "versioneer.py")
365 | if not (
366 | os.path.exists(setup_py)
367 | or os.path.exists(pyproject_toml)
368 | or os.path.exists(versioneer_py)
369 | ):
370 | err = ("Versioneer was unable to run the project root directory. "
371 | "Versioneer requires setup.py to be executed from "
372 | "its immediate directory (like 'python setup.py COMMAND'), "
373 | "or in a way that lets it use sys.argv[0] to find the root "
374 | "(like 'python path/to/setup.py COMMAND').")
375 | raise VersioneerBadRootError(err)
376 | try:
377 | # Certain runtime workflows (setup.py install/develop in a setuptools
378 | # tree) execute all dependencies in a single python process, so
379 | # "versioneer" may be imported multiple times, and python's shared
380 | # module-import table will cache the first one. So we can't use
381 | # os.path.dirname(__file__), as that will find whichever
382 | # versioneer.py was first imported, even in later projects.
383 | my_path = os.path.realpath(os.path.abspath(__file__))
384 | me_dir = os.path.normcase(os.path.splitext(my_path)[0])
385 | vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
386 | if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
387 | print("Warning: build in %s is using versioneer.py from %s"
388 | % (os.path.dirname(my_path), versioneer_py))
389 | except NameError:
390 | pass
391 | return root
392 |
393 |
394 | def get_config_from_root(root: str) -> VersioneerConfig:
395 | """Read the project setup.cfg file to determine Versioneer config."""
396 | # This might raise OSError (if setup.cfg is missing), or
397 | # configparser.NoSectionError (if it lacks a [versioneer] section), or
398 | # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
399 | # the top of versioneer.py for instructions on writing your setup.cfg .
400 | root_pth = Path(root)
401 | pyproject_toml = root_pth / "pyproject.toml"
402 | setup_cfg = root_pth / "setup.cfg"
403 | section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
404 | if pyproject_toml.exists() and have_tomllib:
405 | try:
406 | with open(pyproject_toml, 'rb') as fobj:
407 | pp = tomllib.load(fobj)
408 | section = pp['tool']['versioneer']
409 | except (tomllib.TOMLDecodeError, KeyError) as e:
410 | print(f"Failed to load config from {pyproject_toml}: {e}")
411 | print("Try to load it from setup.cfg")
412 | if not section:
413 | parser = configparser.ConfigParser()
414 | with open(setup_cfg) as cfg_file:
415 | parser.read_file(cfg_file)
416 | parser.get("versioneer", "VCS") # raise error if missing
417 |
418 | section = parser["versioneer"]
419 |
420 | # `cast`` really shouldn't be used, but its simplest for the
421 | # common VersioneerConfig users at the moment. We verify against
422 | # `None` values elsewhere where it matters
423 |
424 | cfg = VersioneerConfig()
425 | cfg.VCS = section['VCS']
426 | cfg.style = section.get("style", "")
427 | cfg.versionfile_source = cast(str, section.get("versionfile_source"))
428 | cfg.versionfile_build = section.get("versionfile_build")
429 | cfg.tag_prefix = cast(str, section.get("tag_prefix"))
430 | if cfg.tag_prefix in ("''", '""', None):
431 | cfg.tag_prefix = ""
432 | cfg.parentdir_prefix = section.get("parentdir_prefix")
433 | if isinstance(section, configparser.SectionProxy):
434 | # Make sure configparser translates to bool
435 | cfg.verbose = section.getboolean("verbose")
436 | else:
437 | cfg.verbose = section.get("verbose")
438 |
439 | return cfg
440 |
441 |
442 | class NotThisMethod(Exception):
443 | """Exception raised if a method is not valid for the current scenario."""
444 |
445 |
446 | # these dictionaries contain VCS-specific tools
447 | LONG_VERSION_PY: Dict[str, str] = {}
448 | HANDLERS: Dict[str, Dict[str, Callable]] = {}
449 |
450 |
451 | def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator
452 | """Create decorator to mark a method as the handler of a VCS."""
453 | def decorate(f: Callable) -> Callable:
454 | """Store f in HANDLERS[vcs][method]."""
455 | HANDLERS.setdefault(vcs, {})[method] = f
456 | return f
457 | return decorate
458 |
459 |
460 | def run_command(
461 | commands: List[str],
462 | args: List[str],
463 | cwd: Optional[str] = None,
464 | verbose: bool = False,
465 | hide_stderr: bool = False,
466 | env: Optional[Dict[str, str]] = None,
467 | ) -> Tuple[Optional[str], Optional[int]]:
468 | """Call the given command(s)."""
469 | assert isinstance(commands, list)
470 | process = None
471 |
472 | popen_kwargs: Dict[str, Any] = {}
473 | if sys.platform == "win32":
474 | # This hides the console window if pythonw.exe is used
475 | startupinfo = subprocess.STARTUPINFO()
476 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
477 | popen_kwargs["startupinfo"] = startupinfo
478 |
479 | for command in commands:
480 | try:
481 | dispcmd = str([command] + args)
482 | # remember shell=False, so use git.cmd on windows, not just git
483 | process = subprocess.Popen([command] + args, cwd=cwd, env=env,
484 | stdout=subprocess.PIPE,
485 | stderr=(subprocess.PIPE if hide_stderr
486 | else None), **popen_kwargs)
487 | break
488 | except OSError as e:
489 | if e.errno == errno.ENOENT:
490 | continue
491 | if verbose:
492 | print("unable to run %s" % dispcmd)
493 | print(e)
494 | return None, None
495 | else:
496 | if verbose:
497 | print("unable to find command, tried %s" % (commands,))
498 | return None, None
499 | stdout = process.communicate()[0].strip().decode()
500 | if process.returncode != 0:
501 | if verbose:
502 | print("unable to run %s (error)" % dispcmd)
503 | print("stdout was %s" % stdout)
504 | return None, process.returncode
505 | return stdout, process.returncode
506 |
507 |
508 | LONG_VERSION_PY['git'] = r'''
509 | # This file helps to compute a version number in source trees obtained from
510 | # git-archive tarball (such as those provided by githubs download-from-tag
511 | # feature). Distribution tarballs (built by setup.py sdist) and build
512 | # directories (produced by setup.py build) will contain a much shorter file
513 | # that just contains the computed version number.
514 |
515 | # This file is released into the public domain.
516 | # Generated by versioneer-0.29
517 | # https://github.com/python-versioneer/python-versioneer
518 |
519 | """Git implementation of _version.py."""
520 |
521 | import errno
522 | import os
523 | import re
524 | import subprocess
525 | import sys
526 | from typing import Any, Callable, Dict, List, Optional, Tuple
527 | import functools
528 |
529 |
530 | def get_keywords() -> Dict[str, str]:
531 | """Get the keywords needed to look up the version information."""
532 | # these strings will be replaced by git during git-archive.
533 | # setup.py/versioneer.py will grep for the variable names, so they must
534 | # each be defined on a line of their own. _version.py will just call
535 | # get_keywords().
536 | git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
537 | git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
538 | git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
539 | keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
540 | return keywords
541 |
542 |
543 | class VersioneerConfig:
544 | """Container for Versioneer configuration parameters."""
545 |
546 | VCS: str
547 | style: str
548 | tag_prefix: str
549 | parentdir_prefix: str
550 | versionfile_source: str
551 | verbose: bool
552 |
553 |
554 | def get_config() -> VersioneerConfig:
555 | """Create, populate and return the VersioneerConfig() object."""
556 | # these strings are filled in when 'setup.py versioneer' creates
557 | # _version.py
558 | cfg = VersioneerConfig()
559 | cfg.VCS = "git"
560 | cfg.style = "%(STYLE)s"
561 | cfg.tag_prefix = "%(TAG_PREFIX)s"
562 | cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s"
563 | cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s"
564 | cfg.verbose = False
565 | return cfg
566 |
567 |
568 | class NotThisMethod(Exception):
569 | """Exception raised if a method is not valid for the current scenario."""
570 |
571 |
572 | LONG_VERSION_PY: Dict[str, str] = {}
573 | HANDLERS: Dict[str, Dict[str, Callable]] = {}
574 |
575 |
576 | def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator
577 | """Create decorator to mark a method as the handler of a VCS."""
578 | def decorate(f: Callable) -> Callable:
579 | """Store f in HANDLERS[vcs][method]."""
580 | if vcs not in HANDLERS:
581 | HANDLERS[vcs] = {}
582 | HANDLERS[vcs][method] = f
583 | return f
584 | return decorate
585 |
586 |
587 | def run_command(
588 | commands: List[str],
589 | args: List[str],
590 | cwd: Optional[str] = None,
591 | verbose: bool = False,
592 | hide_stderr: bool = False,
593 | env: Optional[Dict[str, str]] = None,
594 | ) -> Tuple[Optional[str], Optional[int]]:
595 | """Call the given command(s)."""
596 | assert isinstance(commands, list)
597 | process = None
598 |
599 | popen_kwargs: Dict[str, Any] = {}
600 | if sys.platform == "win32":
601 | # This hides the console window if pythonw.exe is used
602 | startupinfo = subprocess.STARTUPINFO()
603 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
604 | popen_kwargs["startupinfo"] = startupinfo
605 |
606 | for command in commands:
607 | try:
608 | dispcmd = str([command] + args)
609 | # remember shell=False, so use git.cmd on windows, not just git
610 | process = subprocess.Popen([command] + args, cwd=cwd, env=env,
611 | stdout=subprocess.PIPE,
612 | stderr=(subprocess.PIPE if hide_stderr
613 | else None), **popen_kwargs)
614 | break
615 | except OSError as e:
616 | if e.errno == errno.ENOENT:
617 | continue
618 | if verbose:
619 | print("unable to run %%s" %% dispcmd)
620 | print(e)
621 | return None, None
622 | else:
623 | if verbose:
624 | print("unable to find command, tried %%s" %% (commands,))
625 | return None, None
626 | stdout = process.communicate()[0].strip().decode()
627 | if process.returncode != 0:
628 | if verbose:
629 | print("unable to run %%s (error)" %% dispcmd)
630 | print("stdout was %%s" %% stdout)
631 | return None, process.returncode
632 | return stdout, process.returncode
633 |
634 |
635 | def versions_from_parentdir(
636 | parentdir_prefix: str,
637 | root: str,
638 | verbose: bool,
639 | ) -> Dict[str, Any]:
640 | """Try to determine the version from the parent directory name.
641 |
642 | Source tarballs conventionally unpack into a directory that includes both
643 | the project name and a version string. We will also support searching up
644 | two directory levels for an appropriately named parent directory
645 | """
646 | rootdirs = []
647 |
648 | for _ in range(3):
649 | dirname = os.path.basename(root)
650 | if dirname.startswith(parentdir_prefix):
651 | return {"version": dirname[len(parentdir_prefix):],
652 | "full-revisionid": None,
653 | "dirty": False, "error": None, "date": None}
654 | rootdirs.append(root)
655 | root = os.path.dirname(root) # up a level
656 |
657 | if verbose:
658 | print("Tried directories %%s but none started with prefix %%s" %%
659 | (str(rootdirs), parentdir_prefix))
660 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
661 |
662 |
663 | @register_vcs_handler("git", "get_keywords")
664 | def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
665 | """Extract version information from the given file."""
666 | # the code embedded in _version.py can just fetch the value of these
667 | # keywords. When used from setup.py, we don't want to import _version.py,
668 | # so we do it with a regexp instead. This function is not used from
669 | # _version.py.
670 | keywords: Dict[str, str] = {}
671 | try:
672 | with open(versionfile_abs, "r") as fobj:
673 | for line in fobj:
674 | if line.strip().startswith("git_refnames ="):
675 | mo = re.search(r'=\s*"(.*)"', line)
676 | if mo:
677 | keywords["refnames"] = mo.group(1)
678 | if line.strip().startswith("git_full ="):
679 | mo = re.search(r'=\s*"(.*)"', line)
680 | if mo:
681 | keywords["full"] = mo.group(1)
682 | if line.strip().startswith("git_date ="):
683 | mo = re.search(r'=\s*"(.*)"', line)
684 | if mo:
685 | keywords["date"] = mo.group(1)
686 | except OSError:
687 | pass
688 | return keywords
689 |
690 |
691 | @register_vcs_handler("git", "keywords")
692 | def git_versions_from_keywords(
693 | keywords: Dict[str, str],
694 | tag_prefix: str,
695 | verbose: bool,
696 | ) -> Dict[str, Any]:
697 | """Get version information from git keywords."""
698 | if "refnames" not in keywords:
699 | raise NotThisMethod("Short version file found")
700 | date = keywords.get("date")
701 | if date is not None:
702 | # Use only the last line. Previous lines may contain GPG signature
703 | # information.
704 | date = date.splitlines()[-1]
705 |
706 | # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
707 | # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
708 | # -like" string, which we must then edit to make compliant), because
709 | # it's been around since git-1.5.3, and it's too difficult to
710 | # discover which version we're using, or to work around using an
711 | # older one.
712 | date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
713 | refnames = keywords["refnames"].strip()
714 | if refnames.startswith("$Format"):
715 | if verbose:
716 | print("keywords are unexpanded, not using")
717 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
718 | refs = {r.strip() for r in refnames.strip("()").split(",")}
719 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
720 | # just "foo-1.0". If we see a "tag: " prefix, prefer those.
721 | TAG = "tag: "
722 | tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
723 | if not tags:
724 | # Either we're using git < 1.8.3, or there really are no tags. We use
725 | # a heuristic: assume all version tags have a digit. The old git %%d
726 | # expansion behaves like git log --decorate=short and strips out the
727 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish
728 | # between branches and tags. By ignoring refnames without digits, we
729 | # filter out many common branch names like "release" and
730 | # "stabilization", as well as "HEAD" and "master".
731 | tags = {r for r in refs if re.search(r'\d', r)}
732 | if verbose:
733 | print("discarding '%%s', no digits" %% ",".join(refs - tags))
734 | if verbose:
735 | print("likely tags: %%s" %% ",".join(sorted(tags)))
736 | for ref in sorted(tags):
737 | # sorting will prefer e.g. "2.0" over "2.0rc1"
738 | if ref.startswith(tag_prefix):
739 | r = ref[len(tag_prefix):]
740 | # Filter out refs that exactly match prefix or that don't start
741 | # with a number once the prefix is stripped (mostly a concern
742 | # when prefix is '')
743 | if not re.match(r'\d', r):
744 | continue
745 | if verbose:
746 | print("picking %%s" %% r)
747 | return {"version": r,
748 | "full-revisionid": keywords["full"].strip(),
749 | "dirty": False, "error": None,
750 | "date": date}
751 | # no suitable tags, so version is "0+unknown", but full hex is still there
752 | if verbose:
753 | print("no suitable tags, using unknown + full revision id")
754 | return {"version": "0+unknown",
755 | "full-revisionid": keywords["full"].strip(),
756 | "dirty": False, "error": "no suitable tags", "date": None}
757 |
758 |
759 | @register_vcs_handler("git", "pieces_from_vcs")
760 | def git_pieces_from_vcs(
761 | tag_prefix: str,
762 | root: str,
763 | verbose: bool,
764 | runner: Callable = run_command
765 | ) -> Dict[str, Any]:
766 | """Get version from 'git describe' in the root of the source tree.
767 |
768 | This only gets called if the git-archive 'subst' keywords were *not*
769 | expanded, and _version.py hasn't already been rewritten with a short
770 | version string, meaning we're inside a checked out source tree.
771 | """
772 | GITS = ["git"]
773 | if sys.platform == "win32":
774 | GITS = ["git.cmd", "git.exe"]
775 |
776 | # GIT_DIR can interfere with correct operation of Versioneer.
777 | # It may be intended to be passed to the Versioneer-versioned project,
778 | # but that should not change where we get our version from.
779 | env = os.environ.copy()
780 | env.pop("GIT_DIR", None)
781 | runner = functools.partial(runner, env=env)
782 |
783 | _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
784 | hide_stderr=not verbose)
785 | if rc != 0:
786 | if verbose:
787 | print("Directory %%s not under git control" %% root)
788 | raise NotThisMethod("'git rev-parse --git-dir' returned error")
789 |
790 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
791 | # if there isn't one, this yields HEX[-dirty] (no NUM)
792 | describe_out, rc = runner(GITS, [
793 | "describe", "--tags", "--dirty", "--always", "--long",
794 | "--match", f"{tag_prefix}[[:digit:]]*"
795 | ], cwd=root)
796 | # --long was added in git-1.5.5
797 | if describe_out is None:
798 | raise NotThisMethod("'git describe' failed")
799 | describe_out = describe_out.strip()
800 | full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
801 | if full_out is None:
802 | raise NotThisMethod("'git rev-parse' failed")
803 | full_out = full_out.strip()
804 |
805 | pieces: Dict[str, Any] = {}
806 | pieces["long"] = full_out
807 | pieces["short"] = full_out[:7] # maybe improved later
808 | pieces["error"] = None
809 |
810 | branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
811 | cwd=root)
812 | # --abbrev-ref was added in git-1.6.3
813 | if rc != 0 or branch_name is None:
814 | raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
815 | branch_name = branch_name.strip()
816 |
817 | if branch_name == "HEAD":
818 | # If we aren't exactly on a branch, pick a branch which represents
819 | # the current commit. If all else fails, we are on a branchless
820 | # commit.
821 | branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
822 | # --contains was added in git-1.5.4
823 | if rc != 0 or branches is None:
824 | raise NotThisMethod("'git branch --contains' returned error")
825 | branches = branches.split("\n")
826 |
827 | # Remove the first line if we're running detached
828 | if "(" in branches[0]:
829 | branches.pop(0)
830 |
831 | # Strip off the leading "* " from the list of branches.
832 | branches = [branch[2:] for branch in branches]
833 | if "master" in branches:
834 | branch_name = "master"
835 | elif not branches:
836 | branch_name = None
837 | else:
838 | # Pick the first branch that is returned. Good or bad.
839 | branch_name = branches[0]
840 |
841 | pieces["branch"] = branch_name
842 |
843 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
844 | # TAG might have hyphens.
845 | git_describe = describe_out
846 |
847 | # look for -dirty suffix
848 | dirty = git_describe.endswith("-dirty")
849 | pieces["dirty"] = dirty
850 | if dirty:
851 | git_describe = git_describe[:git_describe.rindex("-dirty")]
852 |
853 | # now we have TAG-NUM-gHEX or HEX
854 |
855 | if "-" in git_describe:
856 | # TAG-NUM-gHEX
857 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
858 | if not mo:
859 | # unparsable. Maybe git-describe is misbehaving?
860 | pieces["error"] = ("unable to parse git-describe output: '%%s'"
861 | %% describe_out)
862 | return pieces
863 |
864 | # tag
865 | full_tag = mo.group(1)
866 | if not full_tag.startswith(tag_prefix):
867 | if verbose:
868 | fmt = "tag '%%s' doesn't start with prefix '%%s'"
869 | print(fmt %% (full_tag, tag_prefix))
870 | pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'"
871 | %% (full_tag, tag_prefix))
872 | return pieces
873 | pieces["closest-tag"] = full_tag[len(tag_prefix):]
874 |
875 | # distance: number of commits since tag
876 | pieces["distance"] = int(mo.group(2))
877 |
878 | # commit: short hex revision ID
879 | pieces["short"] = mo.group(3)
880 |
881 | else:
882 | # HEX: no tags
883 | pieces["closest-tag"] = None
884 | out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
885 | pieces["distance"] = len(out.split()) # total number of commits
886 |
887 | # commit date: see ISO-8601 comment in git_versions_from_keywords()
888 | date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
889 | # Use only the last line. Previous lines may contain GPG signature
890 | # information.
891 | date = date.splitlines()[-1]
892 | pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
893 |
894 | return pieces
895 |
896 |
897 | def plus_or_dot(pieces: Dict[str, Any]) -> str:
898 | """Return a + if we don't already have one, else return a ."""
899 | if "+" in pieces.get("closest-tag", ""):
900 | return "."
901 | return "+"
902 |
903 |
904 | def render_pep440(pieces: Dict[str, Any]) -> str:
905 | """Build up version string, with post-release "local version identifier".
906 |
907 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
908 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
909 |
910 | Exceptions:
911 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
912 | """
913 | if pieces["closest-tag"]:
914 | rendered = pieces["closest-tag"]
915 | if pieces["distance"] or pieces["dirty"]:
916 | rendered += plus_or_dot(pieces)
917 | rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
918 | if pieces["dirty"]:
919 | rendered += ".dirty"
920 | else:
921 | # exception #1
922 | rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"],
923 | pieces["short"])
924 | if pieces["dirty"]:
925 | rendered += ".dirty"
926 | return rendered
927 |
928 |
929 | def render_pep440_branch(pieces: Dict[str, Any]) -> str:
930 | """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
931 |
932 | The ".dev0" means not master branch. Note that .dev0 sorts backwards
933 | (a feature branch will appear "older" than the master branch).
934 |
935 | Exceptions:
936 | 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
937 | """
938 | if pieces["closest-tag"]:
939 | rendered = pieces["closest-tag"]
940 | if pieces["distance"] or pieces["dirty"]:
941 | if pieces["branch"] != "master":
942 | rendered += ".dev0"
943 | rendered += plus_or_dot(pieces)
944 | rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
945 | if pieces["dirty"]:
946 | rendered += ".dirty"
947 | else:
948 | # exception #1
949 | rendered = "0"
950 | if pieces["branch"] != "master":
951 | rendered += ".dev0"
952 | rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
953 | pieces["short"])
954 | if pieces["dirty"]:
955 | rendered += ".dirty"
956 | return rendered
957 |
958 |
959 | def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
960 | """Split pep440 version string at the post-release segment.
961 |
962 | Returns the release segments before the post-release and the
963 | post-release version number (or -1 if no post-release segment is present).
964 | """
965 | vc = str.split(ver, ".post")
966 | return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
967 |
968 |
969 | def render_pep440_pre(pieces: Dict[str, Any]) -> str:
970 | """TAG[.postN.devDISTANCE] -- No -dirty.
971 |
972 | Exceptions:
973 | 1: no tags. 0.post0.devDISTANCE
974 | """
975 | if pieces["closest-tag"]:
976 | if pieces["distance"]:
977 | # update the post release segment
978 | tag_version, post_version = pep440_split_post(pieces["closest-tag"])
979 | rendered = tag_version
980 | if post_version is not None:
981 | rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
982 | else:
983 | rendered += ".post0.dev%%d" %% (pieces["distance"])
984 | else:
985 | # no commits, use the tag as the version
986 | rendered = pieces["closest-tag"]
987 | else:
988 | # exception #1
989 | rendered = "0.post0.dev%%d" %% pieces["distance"]
990 | return rendered
991 |
992 |
993 | def render_pep440_post(pieces: Dict[str, Any]) -> str:
994 | """TAG[.postDISTANCE[.dev0]+gHEX] .
995 |
996 | The ".dev0" means dirty. Note that .dev0 sorts backwards
997 | (a dirty tree will appear "older" than the corresponding clean one),
998 | but you shouldn't be releasing software with -dirty anyways.
999 |
1000 | Exceptions:
1001 | 1: no tags. 0.postDISTANCE[.dev0]
1002 | """
1003 | if pieces["closest-tag"]:
1004 | rendered = pieces["closest-tag"]
1005 | if pieces["distance"] or pieces["dirty"]:
1006 | rendered += ".post%%d" %% pieces["distance"]
1007 | if pieces["dirty"]:
1008 | rendered += ".dev0"
1009 | rendered += plus_or_dot(pieces)
1010 | rendered += "g%%s" %% pieces["short"]
1011 | else:
1012 | # exception #1
1013 | rendered = "0.post%%d" %% pieces["distance"]
1014 | if pieces["dirty"]:
1015 | rendered += ".dev0"
1016 | rendered += "+g%%s" %% pieces["short"]
1017 | return rendered
1018 |
1019 |
1020 | def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
1021 | """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
1022 |
1023 | The ".dev0" means not master branch.
1024 |
1025 | Exceptions:
1026 | 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
1027 | """
1028 | if pieces["closest-tag"]:
1029 | rendered = pieces["closest-tag"]
1030 | if pieces["distance"] or pieces["dirty"]:
1031 | rendered += ".post%%d" %% pieces["distance"]
1032 | if pieces["branch"] != "master":
1033 | rendered += ".dev0"
1034 | rendered += plus_or_dot(pieces)
1035 | rendered += "g%%s" %% pieces["short"]
1036 | if pieces["dirty"]:
1037 | rendered += ".dirty"
1038 | else:
1039 | # exception #1
1040 | rendered = "0.post%%d" %% pieces["distance"]
1041 | if pieces["branch"] != "master":
1042 | rendered += ".dev0"
1043 | rendered += "+g%%s" %% pieces["short"]
1044 | if pieces["dirty"]:
1045 | rendered += ".dirty"
1046 | return rendered
1047 |
1048 |
1049 | def render_pep440_old(pieces: Dict[str, Any]) -> str:
1050 | """TAG[.postDISTANCE[.dev0]] .
1051 |
1052 | The ".dev0" means dirty.
1053 |
1054 | Exceptions:
1055 | 1: no tags. 0.postDISTANCE[.dev0]
1056 | """
1057 | if pieces["closest-tag"]:
1058 | rendered = pieces["closest-tag"]
1059 | if pieces["distance"] or pieces["dirty"]:
1060 | rendered += ".post%%d" %% pieces["distance"]
1061 | if pieces["dirty"]:
1062 | rendered += ".dev0"
1063 | else:
1064 | # exception #1
1065 | rendered = "0.post%%d" %% pieces["distance"]
1066 | if pieces["dirty"]:
1067 | rendered += ".dev0"
1068 | return rendered
1069 |
1070 |
1071 | def render_git_describe(pieces: Dict[str, Any]) -> str:
1072 | """TAG[-DISTANCE-gHEX][-dirty].
1073 |
1074 | Like 'git describe --tags --dirty --always'.
1075 |
1076 | Exceptions:
1077 | 1: no tags. HEX[-dirty] (note: no 'g' prefix)
1078 | """
1079 | if pieces["closest-tag"]:
1080 | rendered = pieces["closest-tag"]
1081 | if pieces["distance"]:
1082 | rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
1083 | else:
1084 | # exception #1
1085 | rendered = pieces["short"]
1086 | if pieces["dirty"]:
1087 | rendered += "-dirty"
1088 | return rendered
1089 |
1090 |
1091 | def render_git_describe_long(pieces: Dict[str, Any]) -> str:
1092 | """TAG-DISTANCE-gHEX[-dirty].
1093 |
1094 | Like 'git describe --tags --dirty --always -long'.
1095 | The distance/hash is unconditional.
1096 |
1097 | Exceptions:
1098 | 1: no tags. HEX[-dirty] (note: no 'g' prefix)
1099 | """
1100 | if pieces["closest-tag"]:
1101 | rendered = pieces["closest-tag"]
1102 | rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
1103 | else:
1104 | # exception #1
1105 | rendered = pieces["short"]
1106 | if pieces["dirty"]:
1107 | rendered += "-dirty"
1108 | return rendered
1109 |
1110 |
1111 | def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
1112 | """Render the given version pieces into the requested style."""
1113 | if pieces["error"]:
1114 | return {"version": "unknown",
1115 | "full-revisionid": pieces.get("long"),
1116 | "dirty": None,
1117 | "error": pieces["error"],
1118 | "date": None}
1119 |
1120 | if not style or style == "default":
1121 | style = "pep440" # the default
1122 |
1123 | if style == "pep440":
1124 | rendered = render_pep440(pieces)
1125 | elif style == "pep440-branch":
1126 | rendered = render_pep440_branch(pieces)
1127 | elif style == "pep440-pre":
1128 | rendered = render_pep440_pre(pieces)
1129 | elif style == "pep440-post":
1130 | rendered = render_pep440_post(pieces)
1131 | elif style == "pep440-post-branch":
1132 | rendered = render_pep440_post_branch(pieces)
1133 | elif style == "pep440-old":
1134 | rendered = render_pep440_old(pieces)
1135 | elif style == "git-describe":
1136 | rendered = render_git_describe(pieces)
1137 | elif style == "git-describe-long":
1138 | rendered = render_git_describe_long(pieces)
1139 | else:
1140 | raise ValueError("unknown style '%%s'" %% style)
1141 |
1142 | return {"version": rendered, "full-revisionid": pieces["long"],
1143 | "dirty": pieces["dirty"], "error": None,
1144 | "date": pieces.get("date")}
1145 |
1146 |
1147 | def get_versions() -> Dict[str, Any]:
1148 | """Get version information or return default if unable to do so."""
1149 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
1150 | # __file__, we can work backwards from there to the root. Some
1151 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
1152 | # case we can only use expanded keywords.
1153 |
1154 | cfg = get_config()
1155 | verbose = cfg.verbose
1156 |
1157 | try:
1158 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
1159 | verbose)
1160 | except NotThisMethod:
1161 | pass
1162 |
1163 | try:
1164 | root = os.path.realpath(__file__)
1165 | # versionfile_source is the relative path from the top of the source
1166 | # tree (where the .git directory might live) to this file. Invert
1167 | # this to find the root from __file__.
1168 | for _ in cfg.versionfile_source.split('/'):
1169 | root = os.path.dirname(root)
1170 | except NameError:
1171 | return {"version": "0+unknown", "full-revisionid": None,
1172 | "dirty": None,
1173 | "error": "unable to find root of source tree",
1174 | "date": None}
1175 |
1176 | try:
1177 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
1178 | return render(pieces, cfg.style)
1179 | except NotThisMethod:
1180 | pass
1181 |
1182 | try:
1183 | if cfg.parentdir_prefix:
1184 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
1185 | except NotThisMethod:
1186 | pass
1187 |
1188 | return {"version": "0+unknown", "full-revisionid": None,
1189 | "dirty": None,
1190 | "error": "unable to compute version", "date": None}
1191 | '''
1192 |
1193 |
1194 | @register_vcs_handler("git", "get_keywords")
1195 | def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
1196 | """Extract version information from the given file."""
1197 | # the code embedded in _version.py can just fetch the value of these
1198 | # keywords. When used from setup.py, we don't want to import _version.py,
1199 | # so we do it with a regexp instead. This function is not used from
1200 | # _version.py.
1201 | keywords: Dict[str, str] = {}
1202 | try:
1203 | with open(versionfile_abs, "r") as fobj:
1204 | for line in fobj:
1205 | if line.strip().startswith("git_refnames ="):
1206 | mo = re.search(r'=\s*"(.*)"', line)
1207 | if mo:
1208 | keywords["refnames"] = mo.group(1)
1209 | if line.strip().startswith("git_full ="):
1210 | mo = re.search(r'=\s*"(.*)"', line)
1211 | if mo:
1212 | keywords["full"] = mo.group(1)
1213 | if line.strip().startswith("git_date ="):
1214 | mo = re.search(r'=\s*"(.*)"', line)
1215 | if mo:
1216 | keywords["date"] = mo.group(1)
1217 | except OSError:
1218 | pass
1219 | return keywords
1220 |
1221 |
1222 | @register_vcs_handler("git", "keywords")
1223 | def git_versions_from_keywords(
1224 | keywords: Dict[str, str],
1225 | tag_prefix: str,
1226 | verbose: bool,
1227 | ) -> Dict[str, Any]:
1228 | """Get version information from git keywords."""
1229 | if "refnames" not in keywords:
1230 | raise NotThisMethod("Short version file found")
1231 | date = keywords.get("date")
1232 | if date is not None:
1233 | # Use only the last line. Previous lines may contain GPG signature
1234 | # information.
1235 | date = date.splitlines()[-1]
1236 |
1237 | # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
1238 | # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
1239 | # -like" string, which we must then edit to make compliant), because
1240 | # it's been around since git-1.5.3, and it's too difficult to
1241 | # discover which version we're using, or to work around using an
1242 | # older one.
1243 | date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1244 | refnames = keywords["refnames"].strip()
1245 | if refnames.startswith("$Format"):
1246 | if verbose:
1247 | print("keywords are unexpanded, not using")
1248 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
1249 | refs = {r.strip() for r in refnames.strip("()").split(",")}
1250 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
1251 | # just "foo-1.0". If we see a "tag: " prefix, prefer those.
1252 | TAG = "tag: "
1253 | tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
1254 | if not tags:
1255 | # Either we're using git < 1.8.3, or there really are no tags. We use
1256 | # a heuristic: assume all version tags have a digit. The old git %d
1257 | # expansion behaves like git log --decorate=short and strips out the
1258 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish
1259 | # between branches and tags. By ignoring refnames without digits, we
1260 | # filter out many common branch names like "release" and
1261 | # "stabilization", as well as "HEAD" and "master".
1262 | tags = {r for r in refs if re.search(r'\d', r)}
1263 | if verbose:
1264 | print("discarding '%s', no digits" % ",".join(refs - tags))
1265 | if verbose:
1266 | print("likely tags: %s" % ",".join(sorted(tags)))
1267 | for ref in sorted(tags):
1268 | # sorting will prefer e.g. "2.0" over "2.0rc1"
1269 | if ref.startswith(tag_prefix):
1270 | r = ref[len(tag_prefix):]
1271 | # Filter out refs that exactly match prefix or that don't start
1272 | # with a number once the prefix is stripped (mostly a concern
1273 | # when prefix is '')
1274 | if not re.match(r'\d', r):
1275 | continue
1276 | if verbose:
1277 | print("picking %s" % r)
1278 | return {"version": r,
1279 | "full-revisionid": keywords["full"].strip(),
1280 | "dirty": False, "error": None,
1281 | "date": date}
1282 | # no suitable tags, so version is "0+unknown", but full hex is still there
1283 | if verbose:
1284 | print("no suitable tags, using unknown + full revision id")
1285 | return {"version": "0+unknown",
1286 | "full-revisionid": keywords["full"].strip(),
1287 | "dirty": False, "error": "no suitable tags", "date": None}
1288 |
1289 |
1290 | @register_vcs_handler("git", "pieces_from_vcs")
1291 | def git_pieces_from_vcs(
1292 | tag_prefix: str,
1293 | root: str,
1294 | verbose: bool,
1295 | runner: Callable = run_command
1296 | ) -> Dict[str, Any]:
1297 | """Get version from 'git describe' in the root of the source tree.
1298 |
1299 | This only gets called if the git-archive 'subst' keywords were *not*
1300 | expanded, and _version.py hasn't already been rewritten with a short
1301 | version string, meaning we're inside a checked out source tree.
1302 | """
1303 | GITS = ["git"]
1304 | if sys.platform == "win32":
1305 | GITS = ["git.cmd", "git.exe"]
1306 |
1307 | # GIT_DIR can interfere with correct operation of Versioneer.
1308 | # It may be intended to be passed to the Versioneer-versioned project,
1309 | # but that should not change where we get our version from.
1310 | env = os.environ.copy()
1311 | env.pop("GIT_DIR", None)
1312 | runner = functools.partial(runner, env=env)
1313 |
1314 | _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
1315 | hide_stderr=not verbose)
1316 | if rc != 0:
1317 | if verbose:
1318 | print("Directory %s not under git control" % root)
1319 | raise NotThisMethod("'git rev-parse --git-dir' returned error")
1320 |
1321 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
1322 | # if there isn't one, this yields HEX[-dirty] (no NUM)
1323 | describe_out, rc = runner(GITS, [
1324 | "describe", "--tags", "--dirty", "--always", "--long",
1325 | "--match", f"{tag_prefix}[[:digit:]]*"
1326 | ], cwd=root)
1327 | # --long was added in git-1.5.5
1328 | if describe_out is None:
1329 | raise NotThisMethod("'git describe' failed")
1330 | describe_out = describe_out.strip()
1331 | full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
1332 | if full_out is None:
1333 | raise NotThisMethod("'git rev-parse' failed")
1334 | full_out = full_out.strip()
1335 |
1336 | pieces: Dict[str, Any] = {}
1337 | pieces["long"] = full_out
1338 | pieces["short"] = full_out[:7] # maybe improved later
1339 | pieces["error"] = None
1340 |
1341 | branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
1342 | cwd=root)
1343 | # --abbrev-ref was added in git-1.6.3
1344 | if rc != 0 or branch_name is None:
1345 | raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
1346 | branch_name = branch_name.strip()
1347 |
1348 | if branch_name == "HEAD":
1349 | # If we aren't exactly on a branch, pick a branch which represents
1350 | # the current commit. If all else fails, we are on a branchless
1351 | # commit.
1352 | branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
1353 | # --contains was added in git-1.5.4
1354 | if rc != 0 or branches is None:
1355 | raise NotThisMethod("'git branch --contains' returned error")
1356 | branches = branches.split("\n")
1357 |
1358 | # Remove the first line if we're running detached
1359 | if "(" in branches[0]:
1360 | branches.pop(0)
1361 |
1362 | # Strip off the leading "* " from the list of branches.
1363 | branches = [branch[2:] for branch in branches]
1364 | if "master" in branches:
1365 | branch_name = "master"
1366 | elif not branches:
1367 | branch_name = None
1368 | else:
1369 | # Pick the first branch that is returned. Good or bad.
1370 | branch_name = branches[0]
1371 |
1372 | pieces["branch"] = branch_name
1373 |
1374 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
1375 | # TAG might have hyphens.
1376 | git_describe = describe_out
1377 |
1378 | # look for -dirty suffix
1379 | dirty = git_describe.endswith("-dirty")
1380 | pieces["dirty"] = dirty
1381 | if dirty:
1382 | git_describe = git_describe[:git_describe.rindex("-dirty")]
1383 |
1384 | # now we have TAG-NUM-gHEX or HEX
1385 |
1386 | if "-" in git_describe:
1387 | # TAG-NUM-gHEX
1388 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
1389 | if not mo:
1390 | # unparsable. Maybe git-describe is misbehaving?
1391 | pieces["error"] = ("unable to parse git-describe output: '%s'"
1392 | % describe_out)
1393 | return pieces
1394 |
1395 | # tag
1396 | full_tag = mo.group(1)
1397 | if not full_tag.startswith(tag_prefix):
1398 | if verbose:
1399 | fmt = "tag '%s' doesn't start with prefix '%s'"
1400 | print(fmt % (full_tag, tag_prefix))
1401 | pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
1402 | % (full_tag, tag_prefix))
1403 | return pieces
1404 | pieces["closest-tag"] = full_tag[len(tag_prefix):]
1405 |
1406 | # distance: number of commits since tag
1407 | pieces["distance"] = int(mo.group(2))
1408 |
1409 | # commit: short hex revision ID
1410 | pieces["short"] = mo.group(3)
1411 |
1412 | else:
1413 | # HEX: no tags
1414 | pieces["closest-tag"] = None
1415 | out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
1416 | pieces["distance"] = len(out.split()) # total number of commits
1417 |
1418 | # commit date: see ISO-8601 comment in git_versions_from_keywords()
1419 | date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
1420 | # Use only the last line. Previous lines may contain GPG signature
1421 | # information.
1422 | date = date.splitlines()[-1]
1423 | pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
1424 |
1425 | return pieces
1426 |
1427 |
1428 | def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
1429 | """Git-specific installation logic for Versioneer.
1430 |
1431 | For Git, this means creating/changing .gitattributes to mark _version.py
1432 | for export-subst keyword substitution.
1433 | """
1434 | GITS = ["git"]
1435 | if sys.platform == "win32":
1436 | GITS = ["git.cmd", "git.exe"]
1437 | files = [versionfile_source]
1438 | if ipy:
1439 | files.append(ipy)
1440 | if "VERSIONEER_PEP518" not in globals():
1441 | try:
1442 | my_path = __file__
1443 | if my_path.endswith((".pyc", ".pyo")):
1444 | my_path = os.path.splitext(my_path)[0] + ".py"
1445 | versioneer_file = os.path.relpath(my_path)
1446 | except NameError:
1447 | versioneer_file = "versioneer.py"
1448 | files.append(versioneer_file)
1449 | present = False
1450 | try:
1451 | with open(".gitattributes", "r") as fobj:
1452 | for line in fobj:
1453 | if line.strip().startswith(versionfile_source):
1454 | if "export-subst" in line.strip().split()[1:]:
1455 | present = True
1456 | break
1457 | except OSError:
1458 | pass
1459 | if not present:
1460 | with open(".gitattributes", "a+") as fobj:
1461 | fobj.write(f"{versionfile_source} export-subst\n")
1462 | files.append(".gitattributes")
1463 | run_command(GITS, ["add", "--"] + files)
1464 |
1465 |
1466 | def versions_from_parentdir(
1467 | parentdir_prefix: str,
1468 | root: str,
1469 | verbose: bool,
1470 | ) -> Dict[str, Any]:
1471 | """Try to determine the version from the parent directory name.
1472 |
1473 | Source tarballs conventionally unpack into a directory that includes both
1474 | the project name and a version string. We will also support searching up
1475 | two directory levels for an appropriately named parent directory
1476 | """
1477 | rootdirs = []
1478 |
1479 | for _ in range(3):
1480 | dirname = os.path.basename(root)
1481 | if dirname.startswith(parentdir_prefix):
1482 | return {"version": dirname[len(parentdir_prefix):],
1483 | "full-revisionid": None,
1484 | "dirty": False, "error": None, "date": None}
1485 | rootdirs.append(root)
1486 | root = os.path.dirname(root) # up a level
1487 |
1488 | if verbose:
1489 | print("Tried directories %s but none started with prefix %s" %
1490 | (str(rootdirs), parentdir_prefix))
1491 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
1492 |
1493 |
1494 | SHORT_VERSION_PY = """
1495 | # This file was generated by 'versioneer.py' (0.29) from
1496 | # revision-control system data, or from the parent directory name of an
1497 | # unpacked source archive. Distribution tarballs contain a pre-generated copy
1498 | # of this file.
1499 |
1500 | import json
1501 |
1502 | version_json = '''
1503 | %s
1504 | ''' # END VERSION_JSON
1505 |
1506 |
1507 | def get_versions():
1508 | return json.loads(version_json)
1509 | """
1510 |
1511 |
1512 | def versions_from_file(filename: str) -> Dict[str, Any]:
1513 | """Try to determine the version from _version.py if present."""
1514 | try:
1515 | with open(filename) as f:
1516 | contents = f.read()
1517 | except OSError:
1518 | raise NotThisMethod("unable to read _version.py")
1519 | mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON",
1520 | contents, re.M | re.S)
1521 | if not mo:
1522 | mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON",
1523 | contents, re.M | re.S)
1524 | if not mo:
1525 | raise NotThisMethod("no version_json in _version.py")
1526 | return json.loads(mo.group(1))
1527 |
1528 |
1529 | def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
1530 | """Write the given version number to the given _version.py file."""
1531 | contents = json.dumps(versions, sort_keys=True,
1532 | indent=1, separators=(",", ": "))
1533 | with open(filename, "w") as f:
1534 | f.write(SHORT_VERSION_PY % contents)
1535 |
1536 | print("set %s to '%s'" % (filename, versions["version"]))
1537 |
1538 |
1539 | def plus_or_dot(pieces: Dict[str, Any]) -> str:
1540 | """Return a + if we don't already have one, else return a ."""
1541 | if "+" in pieces.get("closest-tag", ""):
1542 | return "."
1543 | return "+"
1544 |
1545 |
1546 | def render_pep440(pieces: Dict[str, Any]) -> str:
1547 | """Build up version string, with post-release "local version identifier".
1548 |
1549 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
1550 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
1551 |
1552 | Exceptions:
1553 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
1554 | """
1555 | if pieces["closest-tag"]:
1556 | rendered = pieces["closest-tag"]
1557 | if pieces["distance"] or pieces["dirty"]:
1558 | rendered += plus_or_dot(pieces)
1559 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
1560 | if pieces["dirty"]:
1561 | rendered += ".dirty"
1562 | else:
1563 | # exception #1
1564 | rendered = "0+untagged.%d.g%s" % (pieces["distance"],
1565 | pieces["short"])
1566 | if pieces["dirty"]:
1567 | rendered += ".dirty"
1568 | return rendered
1569 |
1570 |
1571 | def render_pep440_branch(pieces: Dict[str, Any]) -> str:
1572 | """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
1573 |
1574 | The ".dev0" means not master branch. Note that .dev0 sorts backwards
1575 | (a feature branch will appear "older" than the master branch).
1576 |
1577 | Exceptions:
1578 | 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
1579 | """
1580 | if pieces["closest-tag"]:
1581 | rendered = pieces["closest-tag"]
1582 | if pieces["distance"] or pieces["dirty"]:
1583 | if pieces["branch"] != "master":
1584 | rendered += ".dev0"
1585 | rendered += plus_or_dot(pieces)
1586 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
1587 | if pieces["dirty"]:
1588 | rendered += ".dirty"
1589 | else:
1590 | # exception #1
1591 | rendered = "0"
1592 | if pieces["branch"] != "master":
1593 | rendered += ".dev0"
1594 | rendered += "+untagged.%d.g%s" % (pieces["distance"],
1595 | pieces["short"])
1596 | if pieces["dirty"]:
1597 | rendered += ".dirty"
1598 | return rendered
1599 |
1600 |
1601 | def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
1602 | """Split pep440 version string at the post-release segment.
1603 |
1604 | Returns the release segments before the post-release and the
1605 | post-release version number (or -1 if no post-release segment is present).
1606 | """
1607 | vc = str.split(ver, ".post")
1608 | return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
1609 |
1610 |
1611 | def render_pep440_pre(pieces: Dict[str, Any]) -> str:
1612 | """TAG[.postN.devDISTANCE] -- No -dirty.
1613 |
1614 | Exceptions:
1615 | 1: no tags. 0.post0.devDISTANCE
1616 | """
1617 | if pieces["closest-tag"]:
1618 | if pieces["distance"]:
1619 | # update the post release segment
1620 | tag_version, post_version = pep440_split_post(pieces["closest-tag"])
1621 | rendered = tag_version
1622 | if post_version is not None:
1623 | rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
1624 | else:
1625 | rendered += ".post0.dev%d" % (pieces["distance"])
1626 | else:
1627 | # no commits, use the tag as the version
1628 | rendered = pieces["closest-tag"]
1629 | else:
1630 | # exception #1
1631 | rendered = "0.post0.dev%d" % pieces["distance"]
1632 | return rendered
1633 |
1634 |
1635 | def render_pep440_post(pieces: Dict[str, Any]) -> str:
1636 | """TAG[.postDISTANCE[.dev0]+gHEX] .
1637 |
1638 | The ".dev0" means dirty. Note that .dev0 sorts backwards
1639 | (a dirty tree will appear "older" than the corresponding clean one),
1640 | but you shouldn't be releasing software with -dirty anyways.
1641 |
1642 | Exceptions:
1643 | 1: no tags. 0.postDISTANCE[.dev0]
1644 | """
1645 | if pieces["closest-tag"]:
1646 | rendered = pieces["closest-tag"]
1647 | if pieces["distance"] or pieces["dirty"]:
1648 | rendered += ".post%d" % pieces["distance"]
1649 | if pieces["dirty"]:
1650 | rendered += ".dev0"
1651 | rendered += plus_or_dot(pieces)
1652 | rendered += "g%s" % pieces["short"]
1653 | else:
1654 | # exception #1
1655 | rendered = "0.post%d" % pieces["distance"]
1656 | if pieces["dirty"]:
1657 | rendered += ".dev0"
1658 | rendered += "+g%s" % pieces["short"]
1659 | return rendered
1660 |
1661 |
1662 | def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
1663 | """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
1664 |
1665 | The ".dev0" means not master branch.
1666 |
1667 | Exceptions:
1668 | 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
1669 | """
1670 | if pieces["closest-tag"]:
1671 | rendered = pieces["closest-tag"]
1672 | if pieces["distance"] or pieces["dirty"]:
1673 | rendered += ".post%d" % pieces["distance"]
1674 | if pieces["branch"] != "master":
1675 | rendered += ".dev0"
1676 | rendered += plus_or_dot(pieces)
1677 | rendered += "g%s" % pieces["short"]
1678 | if pieces["dirty"]:
1679 | rendered += ".dirty"
1680 | else:
1681 | # exception #1
1682 | rendered = "0.post%d" % pieces["distance"]
1683 | if pieces["branch"] != "master":
1684 | rendered += ".dev0"
1685 | rendered += "+g%s" % pieces["short"]
1686 | if pieces["dirty"]:
1687 | rendered += ".dirty"
1688 | return rendered
1689 |
1690 |
1691 | def render_pep440_old(pieces: Dict[str, Any]) -> str:
1692 | """TAG[.postDISTANCE[.dev0]] .
1693 |
1694 | The ".dev0" means dirty.
1695 |
1696 | Exceptions:
1697 | 1: no tags. 0.postDISTANCE[.dev0]
1698 | """
1699 | if pieces["closest-tag"]:
1700 | rendered = pieces["closest-tag"]
1701 | if pieces["distance"] or pieces["dirty"]:
1702 | rendered += ".post%d" % pieces["distance"]
1703 | if pieces["dirty"]:
1704 | rendered += ".dev0"
1705 | else:
1706 | # exception #1
1707 | rendered = "0.post%d" % pieces["distance"]
1708 | if pieces["dirty"]:
1709 | rendered += ".dev0"
1710 | return rendered
1711 |
1712 |
1713 | def render_git_describe(pieces: Dict[str, Any]) -> str:
1714 | """TAG[-DISTANCE-gHEX][-dirty].
1715 |
1716 | Like 'git describe --tags --dirty --always'.
1717 |
1718 | Exceptions:
1719 | 1: no tags. HEX[-dirty] (note: no 'g' prefix)
1720 | """
1721 | if pieces["closest-tag"]:
1722 | rendered = pieces["closest-tag"]
1723 | if pieces["distance"]:
1724 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
1725 | else:
1726 | # exception #1
1727 | rendered = pieces["short"]
1728 | if pieces["dirty"]:
1729 | rendered += "-dirty"
1730 | return rendered
1731 |
1732 |
1733 | def render_git_describe_long(pieces: Dict[str, Any]) -> str:
1734 | """TAG-DISTANCE-gHEX[-dirty].
1735 |
1736 | Like 'git describe --tags --dirty --always -long'.
1737 | The distance/hash is unconditional.
1738 |
1739 | Exceptions:
1740 | 1: no tags. HEX[-dirty] (note: no 'g' prefix)
1741 | """
1742 | if pieces["closest-tag"]:
1743 | rendered = pieces["closest-tag"]
1744 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
1745 | else:
1746 | # exception #1
1747 | rendered = pieces["short"]
1748 | if pieces["dirty"]:
1749 | rendered += "-dirty"
1750 | return rendered
1751 |
1752 |
1753 | def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
1754 | """Render the given version pieces into the requested style."""
1755 | if pieces["error"]:
1756 | return {"version": "unknown",
1757 | "full-revisionid": pieces.get("long"),
1758 | "dirty": None,
1759 | "error": pieces["error"],
1760 | "date": None}
1761 |
1762 | if not style or style == "default":
1763 | style = "pep440" # the default
1764 |
1765 | if style == "pep440":
1766 | rendered = render_pep440(pieces)
1767 | elif style == "pep440-branch":
1768 | rendered = render_pep440_branch(pieces)
1769 | elif style == "pep440-pre":
1770 | rendered = render_pep440_pre(pieces)
1771 | elif style == "pep440-post":
1772 | rendered = render_pep440_post(pieces)
1773 | elif style == "pep440-post-branch":
1774 | rendered = render_pep440_post_branch(pieces)
1775 | elif style == "pep440-old":
1776 | rendered = render_pep440_old(pieces)
1777 | elif style == "git-describe":
1778 | rendered = render_git_describe(pieces)
1779 | elif style == "git-describe-long":
1780 | rendered = render_git_describe_long(pieces)
1781 | else:
1782 | raise ValueError("unknown style '%s'" % style)
1783 |
1784 | return {"version": rendered, "full-revisionid": pieces["long"],
1785 | "dirty": pieces["dirty"], "error": None,
1786 | "date": pieces.get("date")}
1787 |
1788 |
1789 | class VersioneerBadRootError(Exception):
1790 | """The project root directory is unknown or missing key files."""
1791 |
1792 |
1793 | def get_versions(verbose: bool = False) -> Dict[str, Any]:
1794 | """Get the project version from whatever source is available.
1795 |
1796 | Returns dict with two keys: 'version' and 'full'.
1797 | """
1798 | if "versioneer" in sys.modules:
1799 | # see the discussion in cmdclass.py:get_cmdclass()
1800 | del sys.modules["versioneer"]
1801 |
1802 | root = get_root()
1803 | cfg = get_config_from_root(root)
1804 |
1805 | assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
1806 | handlers = HANDLERS.get(cfg.VCS)
1807 | assert handlers, "unrecognized VCS '%s'" % cfg.VCS
1808 | verbose = verbose or bool(cfg.verbose) # `bool()` used to avoid `None`
1809 | assert cfg.versionfile_source is not None, \
1810 | "please set versioneer.versionfile_source"
1811 | assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
1812 |
1813 | versionfile_abs = os.path.join(root, cfg.versionfile_source)
1814 |
1815 | # extract version from first of: _version.py, VCS command (e.g. 'git
1816 | # describe'), parentdir. This is meant to work for developers using a
1817 | # source checkout, for users of a tarball created by 'setup.py sdist',
1818 | # and for users of a tarball/zipball created by 'git archive' or github's
1819 | # download-from-tag feature or the equivalent in other VCSes.
1820 |
1821 | get_keywords_f = handlers.get("get_keywords")
1822 | from_keywords_f = handlers.get("keywords")
1823 | if get_keywords_f and from_keywords_f:
1824 | try:
1825 | keywords = get_keywords_f(versionfile_abs)
1826 | ver = from_keywords_f(keywords, cfg.tag_prefix, verbose)
1827 | if verbose:
1828 | print("got version from expanded keyword %s" % ver)
1829 | return ver
1830 | except NotThisMethod:
1831 | pass
1832 |
1833 | try:
1834 | ver = versions_from_file(versionfile_abs)
1835 | if verbose:
1836 | print("got version from file %s %s" % (versionfile_abs, ver))
1837 | return ver
1838 | except NotThisMethod:
1839 | pass
1840 |
1841 | from_vcs_f = handlers.get("pieces_from_vcs")
1842 | if from_vcs_f:
1843 | try:
1844 | pieces = from_vcs_f(cfg.tag_prefix, root, verbose)
1845 | ver = render(pieces, cfg.style)
1846 | if verbose:
1847 | print("got version from VCS %s" % ver)
1848 | return ver
1849 | except NotThisMethod:
1850 | pass
1851 |
1852 | try:
1853 | if cfg.parentdir_prefix:
1854 | ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
1855 | if verbose:
1856 | print("got version from parentdir %s" % ver)
1857 | return ver
1858 | except NotThisMethod:
1859 | pass
1860 |
1861 | if verbose:
1862 | print("unable to compute version")
1863 |
1864 | return {"version": "0+unknown", "full-revisionid": None,
1865 | "dirty": None, "error": "unable to compute version",
1866 | "date": None}
1867 |
1868 |
1869 | def get_version() -> str:
1870 | """Get the short version string for this project."""
1871 | return get_versions()["version"]
1872 |
1873 |
1874 | def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
1875 | """Get the custom setuptools subclasses used by Versioneer.
1876 |
1877 | If the package uses a different cmdclass (e.g. one from numpy), it
1878 | should be provide as an argument.
1879 | """
1880 | if "versioneer" in sys.modules:
1881 | del sys.modules["versioneer"]
1882 | # this fixes the "python setup.py develop" case (also 'install' and
1883 | # 'easy_install .'), in which subdependencies of the main project are
1884 | # built (using setup.py bdist_egg) in the same python process. Assume
1885 | # a main project A and a dependency B, which use different versions
1886 | # of Versioneer. A's setup.py imports A's Versioneer, leaving it in
1887 | # sys.modules by the time B's setup.py is executed, causing B to run
1888 | # with the wrong versioneer. Setuptools wraps the sub-dep builds in a
1889 | # sandbox that restores sys.modules to it's pre-build state, so the
1890 | # parent is protected against the child's "import versioneer". By
1891 | # removing ourselves from sys.modules here, before the child build
1892 | # happens, we protect the child from the parent's versioneer too.
1893 | # Also see https://github.com/python-versioneer/python-versioneer/issues/52
1894 |
1895 | cmds = {} if cmdclass is None else cmdclass.copy()
1896 |
1897 | # we add "version" to setuptools
1898 | from setuptools import Command
1899 |
1900 | class cmd_version(Command):
1901 | description = "report generated version string"
1902 | user_options: List[Tuple[str, str, str]] = []
1903 | boolean_options: List[str] = []
1904 |
1905 | def initialize_options(self) -> None:
1906 | pass
1907 |
1908 | def finalize_options(self) -> None:
1909 | pass
1910 |
1911 | def run(self) -> None:
1912 | vers = get_versions(verbose=True)
1913 | print("Version: %s" % vers["version"])
1914 | print(" full-revisionid: %s" % vers.get("full-revisionid"))
1915 | print(" dirty: %s" % vers.get("dirty"))
1916 | print(" date: %s" % vers.get("date"))
1917 | if vers["error"]:
1918 | print(" error: %s" % vers["error"])
1919 | cmds["version"] = cmd_version
1920 |
1921 | # we override "build_py" in setuptools
1922 | #
1923 | # most invocation pathways end up running build_py:
1924 | # distutils/build -> build_py
1925 | # distutils/install -> distutils/build ->..
1926 | # setuptools/bdist_wheel -> distutils/install ->..
1927 | # setuptools/bdist_egg -> distutils/install_lib -> build_py
1928 | # setuptools/install -> bdist_egg ->..
1929 | # setuptools/develop -> ?
1930 | # pip install:
1931 | # copies source tree to a tempdir before running egg_info/etc
1932 | # if .git isn't copied too, 'git describe' will fail
1933 | # then does setup.py bdist_wheel, or sometimes setup.py install
1934 | # setup.py egg_info -> ?
1935 |
1936 | # pip install -e . and setuptool/editable_wheel will invoke build_py
1937 | # but the build_py command is not expected to copy any files.
1938 |
1939 | # we override different "build_py" commands for both environments
1940 | if 'build_py' in cmds:
1941 | _build_py: Any = cmds['build_py']
1942 | else:
1943 | from setuptools.command.build_py import build_py as _build_py
1944 |
1945 | class cmd_build_py(_build_py):
1946 | def run(self) -> None:
1947 | root = get_root()
1948 | cfg = get_config_from_root(root)
1949 | versions = get_versions()
1950 | _build_py.run(self)
1951 | if getattr(self, "editable_mode", False):
1952 | # During editable installs `.py` and data files are
1953 | # not copied to build_lib
1954 | return
1955 | # now locate _version.py in the new build/ directory and replace
1956 | # it with an updated value
1957 | if cfg.versionfile_build:
1958 | target_versionfile = os.path.join(self.build_lib,
1959 | cfg.versionfile_build)
1960 | print("UPDATING %s" % target_versionfile)
1961 | write_to_version_file(target_versionfile, versions)
1962 | cmds["build_py"] = cmd_build_py
1963 |
1964 | if 'build_ext' in cmds:
1965 | _build_ext: Any = cmds['build_ext']
1966 | else:
1967 | from setuptools.command.build_ext import build_ext as _build_ext
1968 |
1969 | class cmd_build_ext(_build_ext):
1970 | def run(self) -> None:
1971 | root = get_root()
1972 | cfg = get_config_from_root(root)
1973 | versions = get_versions()
1974 | _build_ext.run(self)
1975 | if self.inplace:
1976 | # build_ext --inplace will only build extensions in
1977 | # build/lib<..> dir with no _version.py to write to.
1978 | # As in place builds will already have a _version.py
1979 | # in the module dir, we do not need to write one.
1980 | return
1981 | # now locate _version.py in the new build/ directory and replace
1982 | # it with an updated value
1983 | if not cfg.versionfile_build:
1984 | return
1985 | target_versionfile = os.path.join(self.build_lib,
1986 | cfg.versionfile_build)
1987 | if not os.path.exists(target_versionfile):
1988 | print(f"Warning: {target_versionfile} does not exist, skipping "
1989 | "version update. This can happen if you are running build_ext "
1990 | "without first running build_py.")
1991 | return
1992 | print("UPDATING %s" % target_versionfile)
1993 | write_to_version_file(target_versionfile, versions)
1994 | cmds["build_ext"] = cmd_build_ext
1995 |
1996 | if "cx_Freeze" in sys.modules: # cx_freeze enabled?
1997 | from cx_Freeze.dist import build_exe as _build_exe # type: ignore
1998 | # nczeczulin reports that py2exe won't like the pep440-style string
1999 | # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
2000 | # setup(console=[{
2001 | # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION
2002 | # "product_version": versioneer.get_version(),
2003 | # ...
2004 |
2005 | class cmd_build_exe(_build_exe):
2006 | def run(self) -> None:
2007 | root = get_root()
2008 | cfg = get_config_from_root(root)
2009 | versions = get_versions()
2010 | target_versionfile = cfg.versionfile_source
2011 | print("UPDATING %s" % target_versionfile)
2012 | write_to_version_file(target_versionfile, versions)
2013 |
2014 | _build_exe.run(self)
2015 | os.unlink(target_versionfile)
2016 | with open(cfg.versionfile_source, "w") as f:
2017 | LONG = LONG_VERSION_PY[cfg.VCS]
2018 | f.write(LONG %
2019 | {"DOLLAR": "$",
2020 | "STYLE": cfg.style,
2021 | "TAG_PREFIX": cfg.tag_prefix,
2022 | "PARENTDIR_PREFIX": cfg.parentdir_prefix,
2023 | "VERSIONFILE_SOURCE": cfg.versionfile_source,
2024 | })
2025 | cmds["build_exe"] = cmd_build_exe
2026 | del cmds["build_py"]
2027 |
2028 | if 'py2exe' in sys.modules: # py2exe enabled?
2029 | try:
2030 | from py2exe.setuptools_buildexe import py2exe as _py2exe # type: ignore
2031 | except ImportError:
2032 | from py2exe.distutils_buildexe import py2exe as _py2exe # type: ignore
2033 |
2034 | class cmd_py2exe(_py2exe):
2035 | def run(self) -> None:
2036 | root = get_root()
2037 | cfg = get_config_from_root(root)
2038 | versions = get_versions()
2039 | target_versionfile = cfg.versionfile_source
2040 | print("UPDATING %s" % target_versionfile)
2041 | write_to_version_file(target_versionfile, versions)
2042 |
2043 | _py2exe.run(self)
2044 | os.unlink(target_versionfile)
2045 | with open(cfg.versionfile_source, "w") as f:
2046 | LONG = LONG_VERSION_PY[cfg.VCS]
2047 | f.write(LONG %
2048 | {"DOLLAR": "$",
2049 | "STYLE": cfg.style,
2050 | "TAG_PREFIX": cfg.tag_prefix,
2051 | "PARENTDIR_PREFIX": cfg.parentdir_prefix,
2052 | "VERSIONFILE_SOURCE": cfg.versionfile_source,
2053 | })
2054 | cmds["py2exe"] = cmd_py2exe
2055 |
2056 | # sdist farms its file list building out to egg_info
2057 | if 'egg_info' in cmds:
2058 | _egg_info: Any = cmds['egg_info']
2059 | else:
2060 | from setuptools.command.egg_info import egg_info as _egg_info
2061 |
2062 | class cmd_egg_info(_egg_info):
2063 | def find_sources(self) -> None:
2064 | # egg_info.find_sources builds the manifest list and writes it
2065 | # in one shot
2066 | super().find_sources()
2067 |
2068 | # Modify the filelist and normalize it
2069 | root = get_root()
2070 | cfg = get_config_from_root(root)
2071 | self.filelist.append('versioneer.py')
2072 | if cfg.versionfile_source:
2073 | # There are rare cases where versionfile_source might not be
2074 | # included by default, so we must be explicit
2075 | self.filelist.append(cfg.versionfile_source)
2076 | self.filelist.sort()
2077 | self.filelist.remove_duplicates()
2078 |
2079 | # The write method is hidden in the manifest_maker instance that
2080 | # generated the filelist and was thrown away
2081 | # We will instead replicate their final normalization (to unicode,
2082 | # and POSIX-style paths)
2083 | from setuptools import unicode_utils
2084 | normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/')
2085 | for f in self.filelist.files]
2086 |
2087 | manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt')
2088 | with open(manifest_filename, 'w') as fobj:
2089 | fobj.write('\n'.join(normalized))
2090 |
2091 | cmds['egg_info'] = cmd_egg_info
2092 |
2093 | # we override different "sdist" commands for both environments
2094 | if 'sdist' in cmds:
2095 | _sdist: Any = cmds['sdist']
2096 | else:
2097 | from setuptools.command.sdist import sdist as _sdist
2098 |
2099 | class cmd_sdist(_sdist):
2100 | def run(self) -> None:
2101 | versions = get_versions()
2102 | self._versioneer_generated_versions = versions
2103 | # unless we update this, the command will keep using the old
2104 | # version
2105 | self.distribution.metadata.version = versions["version"]
2106 | return _sdist.run(self)
2107 |
2108 | def make_release_tree(self, base_dir: str, files: List[str]) -> None:
2109 | root = get_root()
2110 | cfg = get_config_from_root(root)
2111 | _sdist.make_release_tree(self, base_dir, files)
2112 | # now locate _version.py in the new base_dir directory
2113 | # (remembering that it may be a hardlink) and replace it with an
2114 | # updated value
2115 | target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
2116 | print("UPDATING %s" % target_versionfile)
2117 | write_to_version_file(target_versionfile,
2118 | self._versioneer_generated_versions)
2119 | cmds["sdist"] = cmd_sdist
2120 |
2121 | return cmds
2122 |
2123 |
2124 | CONFIG_ERROR = """
2125 | setup.cfg is missing the necessary Versioneer configuration. You need
2126 | a section like:
2127 |
2128 | [versioneer]
2129 | VCS = git
2130 | style = pep440
2131 | versionfile_source = src/myproject/_version.py
2132 | versionfile_build = myproject/_version.py
2133 | tag_prefix =
2134 | parentdir_prefix = myproject-
2135 |
2136 | You will also need to edit your setup.py to use the results:
2137 |
2138 | import versioneer
2139 | setup(version=versioneer.get_version(),
2140 | cmdclass=versioneer.get_cmdclass(), ...)
2141 |
2142 | Please read the docstring in ./versioneer.py for configuration instructions,
2143 | edit setup.cfg, and re-run the installer or 'python versioneer.py setup'.
2144 | """
2145 |
2146 | SAMPLE_CONFIG = """
2147 | # See the docstring in versioneer.py for instructions. Note that you must
2148 | # re-run 'versioneer.py setup' after changing this section, and commit the
2149 | # resulting files.
2150 |
2151 | [versioneer]
2152 | #VCS = git
2153 | #style = pep440
2154 | #versionfile_source =
2155 | #versionfile_build =
2156 | #tag_prefix =
2157 | #parentdir_prefix =
2158 |
2159 | """
2160 |
2161 | OLD_SNIPPET = """
2162 | from ._version import get_versions
2163 | __version__ = get_versions()['version']
2164 | del get_versions
2165 | """
2166 |
2167 | INIT_PY_SNIPPET = """
2168 | from . import {0}
2169 | __version__ = {0}.get_versions()['version']
2170 | """
2171 |
2172 |
2173 | def do_setup() -> int:
2174 | """Do main VCS-independent setup function for installing Versioneer."""
2175 | root = get_root()
2176 | try:
2177 | cfg = get_config_from_root(root)
2178 | except (OSError, configparser.NoSectionError,
2179 | configparser.NoOptionError) as e:
2180 | if isinstance(e, (OSError, configparser.NoSectionError)):
2181 | print("Adding sample versioneer config to setup.cfg",
2182 | file=sys.stderr)
2183 | with open(os.path.join(root, "setup.cfg"), "a") as f:
2184 | f.write(SAMPLE_CONFIG)
2185 | print(CONFIG_ERROR, file=sys.stderr)
2186 | return 1
2187 |
2188 | print(" creating %s" % cfg.versionfile_source)
2189 | with open(cfg.versionfile_source, "w") as f:
2190 | LONG = LONG_VERSION_PY[cfg.VCS]
2191 | f.write(LONG % {"DOLLAR": "$",
2192 | "STYLE": cfg.style,
2193 | "TAG_PREFIX": cfg.tag_prefix,
2194 | "PARENTDIR_PREFIX": cfg.parentdir_prefix,
2195 | "VERSIONFILE_SOURCE": cfg.versionfile_source,
2196 | })
2197 |
2198 | ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
2199 | "__init__.py")
2200 | maybe_ipy: Optional[str] = ipy
2201 | if os.path.exists(ipy):
2202 | try:
2203 | with open(ipy, "r") as f:
2204 | old = f.read()
2205 | except OSError:
2206 | old = ""
2207 | module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
2208 | snippet = INIT_PY_SNIPPET.format(module)
2209 | if OLD_SNIPPET in old:
2210 | print(" replacing boilerplate in %s" % ipy)
2211 | with open(ipy, "w") as f:
2212 | f.write(old.replace(OLD_SNIPPET, snippet))
2213 | elif snippet not in old:
2214 | print(" appending to %s" % ipy)
2215 | with open(ipy, "a") as f:
2216 | f.write(snippet)
2217 | else:
2218 | print(" %s unmodified" % ipy)
2219 | else:
2220 | print(" %s doesn't exist, ok" % ipy)
2221 | maybe_ipy = None
2222 |
2223 | # Make VCS-specific changes. For git, this means creating/changing
2224 | # .gitattributes to mark _version.py for export-subst keyword
2225 | # substitution.
2226 | do_vcs_install(cfg.versionfile_source, maybe_ipy)
2227 | return 0
2228 |
2229 |
2230 | def scan_setup_py() -> int:
2231 | """Validate the contents of setup.py against Versioneer's expectations."""
2232 | found = set()
2233 | setters = False
2234 | errors = 0
2235 | with open("setup.py", "r") as f:
2236 | for line in f.readlines():
2237 | if "import versioneer" in line:
2238 | found.add("import")
2239 | if "versioneer.get_cmdclass()" in line:
2240 | found.add("cmdclass")
2241 | if "versioneer.get_version()" in line:
2242 | found.add("get_version")
2243 | if "versioneer.VCS" in line:
2244 | setters = True
2245 | if "versioneer.versionfile_source" in line:
2246 | setters = True
2247 | if len(found) != 3:
2248 | print("")
2249 | print("Your setup.py appears to be missing some important items")
2250 | print("(but I might be wrong). Please make sure it has something")
2251 | print("roughly like the following:")
2252 | print("")
2253 | print(" import versioneer")
2254 | print(" setup( version=versioneer.get_version(),")
2255 | print(" cmdclass=versioneer.get_cmdclass(), ...)")
2256 | print("")
2257 | errors += 1
2258 | if setters:
2259 | print("You should remove lines like 'versioneer.VCS = ' and")
2260 | print("'versioneer.versionfile_source = ' . This configuration")
2261 | print("now lives in setup.cfg, and should be removed from setup.py")
2262 | print("")
2263 | errors += 1
2264 | return errors
2265 |
2266 |
2267 | def setup_command() -> NoReturn:
2268 | """Set up Versioneer and exit with appropriate error code."""
2269 | errors = do_setup()
2270 | errors += scan_setup_py()
2271 | sys.exit(1 if errors else 0)
2272 |
2273 |
2274 | if __name__ == "__main__":
2275 | cmd = sys.argv[1]
2276 | if cmd == "setup":
2277 | setup_command()
2278 |
--------------------------------------------------------------------------------