diff --git a/.appveyor.yml b/.appveyor.yml index 8af60b9b..7661aa63 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,14 +9,6 @@ environment: - TOXENV: py35-optional - TOXENV: py36-base - TOXENV: py36-optional - - TOXENV: py37-base - - TOXENV: py37-optional - - TOXENV: py38-base - - TOXENV: py38-optional - - TOXENV: py39-base - - TOXENV: py39-optional - - TOXENV: py310-base - - TOXENV: py310-optional install: - git submodule update --init --recursive diff --git a/.github/workflows/python-tox.yml b/.github/workflows/python-tox.yml index 0e3e46db..cfcc42e6 100644 --- a/.github/workflows/python-tox.yml +++ b/.github/workflows/python-tox.yml @@ -4,21 +4,62 @@ jobs: # Prevent duplicate builds for 'internal' pull requests on existing commits # Credit: https://github.community/t/duplicate-checks-on-push-and-pull-request-simultaneous-event/18012 if: github.event.push || github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python: ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy-2.7", "pypy-3.8"] + # 2.7, 3.5, and 3.6 run on Windows via AppVeyor + python: ["3.7", "3.8", "3.9", "3.10", "3.11"] + os: [ubuntu-latest, windows-latest] + deps: [base, optional] + include: + - python: "pypy-2.7" + os: ubuntu-latest + deps: base + - python: "pypy-3.8" + os: ubuntu-latest + deps: base + - python: "2.7" + os: ubuntu-latest + deps: oldest + - python: "3.7" + os: ubuntu-latest + deps: oldest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 with: submodules: true - - uses: actions/setup-python@v4 + - if: ${{ matrix.deps == 'base' }} + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} cache: pip - cache-dependency-path: "requirements*.txt" + cache-dependency-path: | + requirements.txt + requirements-test.txt + - if: ${{ matrix.deps == 'optional' }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + cache: pip + cache-dependency-path: | + requirements.txt + requirements-optional.txt + requirements-test.txt + - if: ${{ matrix.deps == 'oldest' }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + cache: pip + cache-dependency-path: | + requirements-oldest.txt + - if: ${{ matrix.os == 'windows-latest' }} + name: Determine environment name for Tox (PowerShell) + run: python toxver.py ${{ matrix.python }} ${{ matrix.deps }} >> $env:GITHUB_ENV + - if: ${{ matrix.os == 'ubuntu-latest' }} + name: Determine environment name for Tox (Bash) + run: python toxver.py ${{ matrix.python }} ${{ matrix.deps }} >> $GITHUB_ENV - run: pip install tox - - run: tox -e py + - run: tox - if: ${{ always() }} run: python debug-info.py diff --git a/requirements-install.sh b/requirements-install.sh deleted file mode 100755 index b7a8d96d..00000000 --- a/requirements-install.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -ex - -if [[ $SIX_VERSION ]]; then - pip install six==$SIX_VERSION -fi - -pip install -r requirements-test.txt - -if [[ $USE_OPTIONAL == "true" ]]; then - pip install -r requirements-optional.txt -fi - -if [[ $CI == "true" ]]; then - pip install codecov -fi diff --git a/requirements-oldest.txt b/requirements-oldest.txt new file mode 100644 index 00000000..68d0f13d --- /dev/null +++ b/requirements-oldest.txt @@ -0,0 +1,29 @@ +# This allows us to install the actually oldest supported dependencies and test whether that works. + +# requirements.txt +six==1.9 +webencodings==0.5.1 + +# requirements-optional.txt +genshi==0.7.1 ; python_version < '3.8' +genshi==0.7.6 ; python_version >= '3.8' +chardet==2.2.1 +# this should be 3.4.0 but there are no Linux +# binary wheels for older releases +lxml==3.8.0 ; python_version < '3.7' +# minimums for 3.x are actually different: +# - 3.7 is actually 4.1.1 +# - 3.8 is actually 4.3.5 +# - 3.9-3.10 is actually 4.5.2 +# - 3.11 is actually 4.9.0 +lxml==4.9.0 ; python_version >= '3.7' + +# requirements-test.txt +flake8==3.9.2 ; python_version < '3.6' +flake8==5.0.4; python_version >= '3.6' +pytest==4.6.10 ; python_version < '3' +pytest==5.4.2 ; python_version >= '3' +coverage==5.1 +pytest-expect==1.1.0 +mock==3.0.5 ; python_version < '3.6' +mock==4.0.2 ; python_version >= '3.6' \ No newline at end of file diff --git a/requirements-optional.txt b/requirements-optional.txt index 2e78c952..2e112e95 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -2,12 +2,12 @@ # We support a Genshi treewalker that can be used to serialize Genshi # streams. -genshi +genshi>=0.7.1 # chardet can be used as a fallback in case we are unable to determine # the encoding of a document. -chardet>=2.2 +chardet>=2.2.1 # lxml is supported with its own treebuilder ("lxml") and otherwise # uses the standard ElementTree support -lxml ; platform_python_implementation == 'CPython' +lxml>=3.4.0 ; platform_python_implementation == 'CPython' diff --git a/requirements-test.txt b/requirements-test.txt index 8c0ca7c7..27866e59 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,7 +1,7 @@ -r requirements.txt -tox>=3.15.1,<4 -flake8>=3.8.1,<6 +flake8==3.9.2 ; python_version < '3.6' +flake8>=5.0.4; python_version >= '3.6' pytest>=4.6.10,<5 ; python_version < '3' pytest>=5.4.2,<7 ; python_version >= '3' coverage>=5.1,<6 diff --git a/setup.py b/setup.py index b4c11811..30ee0575 100644 --- a/setup.py +++ b/setup.py @@ -108,23 +108,23 @@ def default_environment(): packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), install_requires=[ 'six>=1.9', - 'webencodings', + 'webencodings>=0.5.1', ], python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", extras_require={ # A conditional extra will only install these items when the extra is # requested and the condition matches. - "lxml:platform_python_implementation == 'CPython'": ["lxml"], + "lxml:platform_python_implementation == 'CPython'": ["lxml>=3.4.0"], # Standard extras, will be installed when the extra is requested. - "genshi": ["genshi"], - "chardet": ["chardet>=2.2"], + "genshi": ["genshi>=0.7.1"], + "chardet": ["chardet>=2.2.1"], # The all extra combines a standard extra which will be used anytime # the all extra is requested, and it extends it with a conditional # extra that will be installed whenever the condition matches and the # all extra is requested. - "all": ["genshi", "chardet>=2.2"], - "all:platform_python_implementation == 'CPython'": ["lxml"], + "all": ["genshi>=0.7.1", "chardet>=2.2.1"], + "all:platform_python_implementation == 'CPython'": ["lxml>=3.4.0"], }, ) diff --git a/tox.ini b/tox.ini index 42790f48..fb228e96 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,18 @@ [tox] -envlist = py{27,35,36,37,38,39,310,311,py,py3}-{base,six19,optional} +envlist = py{27,35,36,37,38,39,310,311,py,py3}-{base,optional,oldest} [testenv] deps = + base: -r{toxinidir}/requirements-test.txt + optional: -r{toxinidir}/requirements-test.txt optional: -r{toxinidir}/requirements-optional.txt - -r{toxinidir}/requirements-test.txt + oldest: -r{toxinidir}/requirements-oldest.txt doc: Sphinx passenv = PYTEST_COMMAND # this is maintained so one can, e.g., PYTEST_COMMAND="coverage run -m pytest" COVERAGE_RUN_OPTIONS commands = - six19: pip install six==1.9 {env:PYTEST_COMMAND:{envbindir}/pytest} {posargs} flake8 {toxinidir} diff --git a/toxver.py b/toxver.py new file mode 100755 index 00000000..68eb71ec --- /dev/null +++ b/toxver.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +""" +usage: toxver.py [python-version] [deps] + +Returns a Tox environment name given a GHA matrix Python version and dependencies. +Many GHA configurations do this with inline Bash scripts but we want our solution +to be cross-platform and work on Windows workers, too. + +Examples: + + $ toxver.py pypy-3.8 base + TOXENV=pypy3-base + + $ toxver.py 2.7 oldest + TOXENV=py27-oldest + + $ toxver.py ~3.12.0-0 optional + TOXENV=py312-optional + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import sys + + +def main(argv): + if len(argv) != 3: + print(__doc__.strip(), file=sys.stderr) + return 1 + + deps = argv[2] + + if argv[1].startswith("pypy-2"): + print("TOXENV=pypy-" + deps) + return 0 + + if argv[1].startswith("pypy-3"): + print("TOXENV=pypy3-" + deps) + return 0 + + if argv[1].startswith("~"): + ver = argv[1][1:5] + else: + ver = argv[1] + + ver = ver.replace(".", "") + print("TOXENV=py" + ver + "-" + deps) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv))