diff --git a/.github/workflows/CI-workflow.yml b/.github/workflows/CI-workflow.yml index 570d9d3..3f1666b 100644 --- a/.github/workflows/CI-workflow.yml +++ b/.github/workflows/CI-workflow.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: python-version: ['3.7', '3.8', '3.9'] - os: [macos-latest, ubuntu-latest] + os: [macos-latest, ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/deploy_pipy.yml b/.github/workflows/deploy_pipy.yml new file mode 100644 index 0000000..7e79958 --- /dev/null +++ b/.github/workflows/deploy_pipy.yml @@ -0,0 +1,34 @@ +name: Release to PyPI + +on: + push: + tags: + - '*' + +jobs: + deploy: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: ['3.7', '3.8', '3.9'] + os: [macos-latest, ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + pip install -e . + - name: Build and publish + env: + TWINE_USERNAME: PinkShnack + TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_TOKEN }} + run: | + python setup.py sdist bdist_wheel + twine upload --skip-existing dist/* diff --git a/CHANGELOG b/CHANGELOG index 336bd9a..d810d31 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ 0.1.4 + - ci: add auto pypi push actions - ref, doc: fix api doc link references - add initial (useable) tests in test directory - add CHANGELOG diff --git a/temul/_version.py b/temul/_version.py new file mode 100644 index 0000000..0830c0f --- /dev/null +++ b/temul/_version.py @@ -0,0 +1,195 @@ +"""Determine package version from git repository tag + +Each time this file is imported it checks whether the package version +can be determined using `git describe`. If this fails (because either +this file is not located at the 1st level down the repository root or +it is not under version control), the version is read from the script +"_version_save.py" which is not versioned by git, but always included +in the final distribution archive (e.g. via PyPI). If the git version +does not match the saved version, then "_version_save.py" is updated. + + +Usage +----- +1. Put this file in your main module directory: + + REPO_ROOT/package_name/_version.py + +2. Add this line to REPO_ROOT/package_name/__init__.py + + from ._version import version as __version__ # noqa: F401 + +3. (Optional) Add this line to REPO_ROOT/.gitignore + + _version_save.py + +Features +-------- +- supports Python 2 and 3 +- supports frozen applications (e.g. PyInstaller) +- supports installing into a virtual environment that is located in + a git repository +- saved version is located in a python file and therefore no other + files (e.g. MANIFEST.in) need be edited +- fallback version is the creation date +- excluded from code coverage via "pragma: no cover" + +Changelog +--------- +2019-11-06.2 + - use os.path.split instead of counting os.path.sep (Windows) +2019-11-06 + - remove deprecated imp dependency (replace with parser) + - check whether this file is versioned and its location is correct + - code cleanup and docs update +""" + +# Put the entire script into a `True` statement and add the hint +# `pragma: no cover` to ignore code coverage here. +if True: # pragma: no cover + import os + from os.path import abspath, basename, dirname, join, split + import subprocess + import sys + import time + import traceback + import warnings + + def git_describe(): + """ + Return a string describing the version returned by the + command `git describe --tags HEAD`. + If it is not possible to determine the correct version, + then an empty string is returned. + """ + # Make sure we are in a directory that belongs to the correct + # repository. + ourdir = dirname(abspath(__file__)) + + def _minimal_ext_cmd(cmd): + # Construct minimal environment + env = {} + for k in ['SYSTEMROOT', 'PATH']: + v = os.environ.get(k) + if v is not None: + env[k] = v + # LANGUAGE is used on win32 + env['LANGUAGE'] = 'C' + env['LANG'] = 'C' + env['LC_ALL'] = 'C' + pop = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env) + out = pop.communicate()[0] + return out.strip().decode('ascii', errors="ignore") + + # change directory + olddir = abspath(os.curdir) + os.chdir(ourdir) + + # Make sure that we are getting "git describe" from our own + # repository (and not from a repository where we just happen + # to be in the directory tree). + git_revision = "" + try: + # If this file is not under version control, "loc" will + # be empty. + loc = _minimal_ext_cmd(['git', 'ls-files', '--full-name', + __file__]) + # If it is under version control, it should be located + # one hierarchy down from the repository root (either + # __file__ is "docs/conf.py" or "package_name/_version.py". + if len(split(loc)) == 2: + try: + git_revision = _minimal_ext_cmd(['git', 'describe', + '--tags', 'HEAD']) + except OSError: + pass + except OSError: + pass + # Go back to original directory + os.chdir(olddir) + + return git_revision + + def load_version(versionfile): + """load version from version_save.py""" + longversion = "" + try: + with open(versionfile, "r") as fd: + data = fd.readlines() + for line in data: + if line.startswith("longversion"): + longversion = line.split("=")[1].strip().strip("'") + except BaseException: + try: + from ._version_save import longversion + except BaseException: + try: + from _version_save import longversion + except BaseException: + pass + + return longversion + + def write_version(version, versionfile): + """save version to version_save.py""" + data = "#!/usr/bin/env python\n" \ + + "# This file was created automatically\n" \ + + "longversion = '{VERSION}'\n" + try: + with open(versionfile, "w") as fd: + fd.write(data.format(VERSION=version)) + except BaseException: + if not os.path.exists(versionfile): + # Only issue a warning if the file does not exist. + msg = "Could not write package version to {}.".format( + versionfile) + warnings.warn(msg) + + hdir = dirname(abspath(__file__)) + if basename(__file__) == "conf.py" and "name" in locals(): + # This script is executed in conf.py from the docs directory + versionfile = join(join(join(hdir, ".."), + name), # noqa: F821 + "_version_save.py") + else: + # This script is imported as a module + versionfile = join(hdir, "_version_save.py") + + # Determine the accurate version + longversion = "" + + # 1. git describe + try: + # Get the version using `git describe` + longversion = git_describe() + except BaseException: + pass + + # 2. previously created version file + if longversion == "": + # Either this is not a git repository or we are in the + # wrong git repository. + # Get the version from the previously generated `_version_save.py` + longversion = load_version(versionfile) + + # 3. last resort: date + if longversion == "": + print("Could not determine version. Reason:") + print(traceback.format_exc()) + ctime = os.stat(__file__)[8] + longversion = time.strftime("%Y.%m.%d-%H-%M-%S", time.gmtime(ctime)) + print("Using creation time as version: {}".format(longversion)) + + if not hasattr(sys, 'frozen'): + # Save the version to `_version_save.py` to allow distribution using + # `python setup.py sdist`. + # This is only done if the program is not frozen (with e.g. + # pyinstaller), + if longversion != load_version(versionfile): + write_version(longversion, versionfile) + + # PEP 440-conform development version: + version = ".post".join(longversion.split("-")[:2])