From bd98349950d2ac80db716349660976ae71444ccb Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 14 Dec 2022 18:25:11 -0500 Subject: [PATCH] Migrate Makefile to tox 4 * Add tox >= 4.0 as a dev dependency * Add tox configuration to setup.cfg - I chose setup.cfg because pyproject.toml support in tox is awkward (https://github.com/tox-dev/tox/issues/999) - Specified flake8 < 6.0.0 until we can get around to replacing type comments with type annotations. https://github.com/PyCQA/pyflakes/issues/747 * Modify Makefile to use tox instead of running the commands directly * Update .gitignore for relevant new files and directories * Update requirements files - Newer tarball in freeze-requirements.sh because the older one was sometimes breaking in tox. * Set pylint configuration to ignore tests/legacy rather than manually excluding it A future commit will configure tox in Github actions. --- .gitignore | 4 + Makefile | 27 +++---- pyproject.toml | 31 ++++++++ requirements-devel.txt | 95 +++++++++++++----------- requirements.txt | 44 +++++------ setup.cfg | 140 +++++++++++++++++++++++++++++++++-- setup.py | 15 +++- tools/freeze-requirements.sh | 2 +- 8 files changed, 269 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index 0d8e9104d40..e20d72cf8b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ build Cargo.lock .coverage** +coverage-* demos/*/parts/ demos/*/prime/ demos/**/*.snap @@ -21,6 +22,7 @@ pip-wheel-metadata/ prime *.pyc __pycache__ +.pytest_cache *.snap snap/.snapcraft/ .spread-reuse.* @@ -29,6 +31,8 @@ stage target tests/unit/snap/ tests/unit/stage/ +test-results* +.tox .vscode venv .venv diff --git a/Makefile b/Makefile index 9448225d85b..b25def4aa91 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SOURCES_LEGACY=snapcraft_legacy tests/legacy .PHONY: autoformat-black autoformat-black: - black $(SOURCES) $(SOURCES_LEGACY) + tox run -e format .PHONY: freeze-requirements freeze-requirements: @@ -11,50 +11,47 @@ freeze-requirements: .PHONY: test-black test-black: - black --check --diff $(SOURCES) $(SOURCES_LEGACY) + tox run -e black .PHONY: test-codespell test-codespell: - codespell --quiet-level 4 --ignore-words-list crate,keyserver,comandos,ro --skip '*.tar,*.xz,*.zip,*.bz2,*.7z,*.gz,*.deb,*.rpm,*.snap,*.gpg,*.pyc,*.png,*.ico,*.jar,*.so,changelog,.git,.hg,.mypy_cache,.tox,.venv,venv,_build,buck-out,__pycache__,build,dist,.vscode,parts,stage,prime,test_appstream.py,./snapcraft.spec,./.direnv,./.pytest_cache' + tox run -e codespell .PHONY: test-flake8 test-flake8: - python3 -m flake8 $(SOURCES) $(SOURCES_LEGACY) + tox run -e flake .PHONY: test-isort test-isort: - isort --check $(SOURCES) $(SOURCES_LEGACY) + tox run -e isort .PHONY: test-mypy test-mypy: - mypy $(SOURCES) + tox run -e mypy .PHONY: test-pydocstyle test-pydocstyle: - pydocstyle snapcraft + tox run -e docstyle .PHONY: test-pylint test-pylint: - pylint snapcraft - pylint tests/*.py tests/unit --disable=invalid-name,missing-module-docstring,missing-function-docstring,duplicate-code,protected-access,unspecified-encoding,too-many-public-methods,too-many-arguments,too-many-lines + tox run -e pylint .PHONY: test-pyright test-pyright: - pyright $(SOURCES) + tox run -e pyright .PHONY: test-shellcheck test-shellcheck: -# Skip third-party gradlew script. - find . \( -name .git -o -name gradlew \) -prune -o -print0 | xargs -0 file -N | grep shell.script | cut -f1 -d: | xargs shellcheck - ./tools/spread-shellcheck.py spread.yaml tests/spread/ + tox run -e spellcheck .PHONY: test-legacy-units test-legacy-units: - pytest --cov-report=xml --cov=snapcraft tests/legacy/unit + tox run -e py38-dev-legacy .PHONY: test-units test-units: test-legacy-units - pytest --cov-report=xml --cov=snapcraft tests/unit + tox run -e py38-dev-unit .PHONY: tests tests: tests-static test-units diff --git a/pyproject.toml b/pyproject.toml index c563dc6ebd9..d9a681c71d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,9 @@ exclude = ''' | ^/prime )/ ''' +# Targeting future versions as well so we don't have black reformatting code +# en masse later. +target_version = ["py38", "py310", "py311"] [tool.isort] # black-compatible isort configuration @@ -30,6 +33,9 @@ use_parentheses = true ensure_newline_before_comments = true line_length = 88 +[tool.pylint.main] +ignore-paths = ["tests/legacy"] + [tool.pylint.messages_control] # duplicate-code can't be disabled locally: https://github.com/PyCQA/pylint/issues/214 disable = "too-few-public-methods,fixme,use-implicit-booleaness-not-comparison,duplicate-code,unnecessary-lambda-assignment" @@ -51,3 +57,28 @@ load-plugins = "pylint_fixme_info,pylint_pytest" [tool.pylint.SIMILARITIES] min-similarity-lines=10 + +[tool.mypy] +python_version = 3.8 +ignore_missing_imports = true +follow_imports = "silent" +exclude = [ + "build", + "snapcraft_legacy", + "tests/spread", + "tests/legacy", + "tools", +] + +[tool.pyright] +include = ["snapcraft", "tests"] +exclude = ["tests/legacy", "tests/spread", "build"] +pythonVersion = "3.8" + +[tool.pytest.ini_options] +minversion = 7.0 +required_plugins = ["pytest-cov>=4.0", "pytest-mock>=3.10", "pytest-subprocess>=1.4"] + +[tool.coverage.run] +branch = true +source = ["snapcraft"] diff --git a/requirements-devel.txt b/requirements-devel.txt index 59f1e10186e..2cb9b997a4d 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,63 +1,68 @@ -astroid==2.12.11 +astroid==2.12.13 attrs==22.1.0 -black==22.10.0 +black==22.12.0 +cachetools==5.2.0 catkin-pkg==0.5.2 -certifi==2022.9.24 +certifi==2022.12.7 cffi==1.15.1 -chardet==5.0.0 +chardet==5.1.0 charset-normalizer==2.1.1 click==8.1.3 -codespell==2.2.1 +codespell==2.2.2 +colorama==0.4.6 coverage==6.5.0 craft-cli==1.2.0 craft-grammar==1.1.1 craft-parts==1.17.1 -craft-providers==1.6.1 +craft-providers==1.6.2 craft-store==2.3.0 cryptography==3.4 Deprecated==1.2.13 -dill==0.3.5.1 +dill==0.3.6 +distlib==0.3.6 distro==1.8.0 docutils==0.19 +exceptiongroup==1.0.4 extras==1.0.0 +filelock==3.8.2 fixtures==4.0.1 flake8==5.0.4 gnupg==2.3.1 -httplib2==0.20.4 +httplib2==0.21.0 hupper==1.10.3 idna==3.4 +importlib-metadata==5.1.0 iniconfig==1.1.1 -isort==5.10.1 +isort==5.11.2 jaraco.classes==3.2.3 jeepney==0.8.0 jsonschema==2.5.1 -keyring==23.9.3 -launchpadlib==1.10.16 -lazr.restfulclient==0.14.4 +keyring==23.11.0 +launchpadlib==1.10.18 +lazr.restfulclient==0.14.5 lazr.uri==1.0.6 -lazy-object-proxy==1.7.1 -lxml==4.9.1 +lazy-object-proxy==1.8.0 +lxml==4.9.2 macaroonbakery==1.3.1 mccabe==0.7.0 -more-itertools==8.14.0 -mypy==0.982 +more-itertools==9.0.0 +mypy==0.991 mypy-extensions==0.4.3 -oauthlib==3.2.1 -overrides==7.0.0 -packaging==21.3 -PasteDeploy==2.1.1 -pathspec==0.10.1 -pbr==5.10.0 +oauthlib==3.2.2 +overrides==7.3.1 +packaging==22.0 +PasteDeploy==3.0.1 +pathspec==0.10.3 +pbr==5.11.0 pexpect==4.8.0 -plaster==1.0 -plaster-pastedeploy==0.7 -platformdirs==2.5.2 +plaster==1.1.2 +plaster-pastedeploy==1.0.1 +platformdirs==2.6.0 pluggy==1.0.0 progressbar==2.5 protobuf==3.20.3 -psutil==5.9.2 +psutil==5.9.4 ptyprocess==0.7.0 -py==1.11.0 pycodestyle==2.9.1 pycparser==2.21 pydantic==1.9.0 @@ -66,33 +71,34 @@ pydocstyle==6.1.1 pyelftools==0.29 pyflakes==2.5.0 pyftpdlib==1.5.7 -pylint==2.15.4 +pylint==2.15.8 pylint-fixme-info==1.0.3 pylint-pytest==1.1.2 pylxd==2.3.1 pymacaroons==0.13.0 pyparsing==3.0.9 +pyproject_api==1.2.1 pyramid==2.0 pyRFC3339==1.1 -pytest==7.1.3 +pytest==7.2.0 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-subprocess==1.4.2 python-dateutil==2.8.2 -python-debian==0.1.47 -pytz==2022.4 +python-debian==0.1.49 +pytz==2022.6 pyxdg==0.28 PyYAML==6.0 raven==6.10.0 requests==2.28.1 -requests-toolbelt==0.10.0 +requests-toolbelt==0.10.1 requests-unixsocket==0.3.0 SecretStorage==3.3.3 semantic-version==2.10.0 semver==2.13.0 -simplejson==3.17.6 +simplejson==3.18.0 six==1.16.0 -snap-helpers==0.2.0 +snap-helpers==0.3.1 snowballstemmer==2.2.0 tabulate==0.9.0 testscenarios==0.5.0 @@ -100,25 +106,28 @@ testtools==2.5.0 tinydb==4.7.0 toml==0.10.2 tomli==2.0.1 -tomlkit==0.11.5 +tomlkit==0.11.6 +tox==4.0.10 translationstring==1.4 types-Deprecated==1.2.9 -types-PyYAML==6.0.12 -types-requests==2.28.11.2 -types-setuptools==65.4.0.0 +types-PyYAML==6.0.12.2 +types-requests==2.28.11.5 +types-setuptools==65.6.0.2 types-tabulate==0.9.0.0 -types-urllib3==1.26.25 +types-urllib3==1.26.25.4 typing_extensions==4.4.0 -urllib3==1.26.12 +urllib3==1.26.13 venusian==3.0.0 +virtualenv==20.17.1 wadllib==1.3.6 WebOb==1.8.7 wrapt==1.14.1 ws4py==0.5.1 +zipp==3.11.0 zope.deprecation==4.4.0 -zope.interface==5.5.0 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.0ubuntu0.20.04.6/python-apt_2.0.0ubuntu0.20.04.6.tar.xz; sys.platform == "linux" +zope.interface==5.5.2 +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.0ubuntu0.20.04.8/python-apt_2.0.0ubuntu0.20.04.8.tar.xz; sys.platform == "linux" PyNaCl==1.4.0; sys.platform != "linux" PyNaCl @ https://files.pythonhosted.org/packages/61/ab/2ac6dea8489fa713e2b4c6c5b549cc962dd4a842b5998d9e80cf8440b7cd/PyNaCl-1.3.0.tar.gz; sys.platform == "linux" setuptools==49.6.0 -pyinstaller==4.10; sys.platform == "win32" +pyinstaller==4.3; sys.platform == "win32" diff --git a/requirements.txt b/requirements.txt index b3185802f4e..913f5aa6d5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,40 +1,41 @@ attrs==22.1.0 catkin-pkg==0.5.2 -certifi==2022.9.24 +certifi==2022.12.7 cffi==1.15.1 -chardet==5.0.0 +chardet==5.1.0 charset-normalizer==2.1.1 click==8.1.3 craft-cli==1.2.0 craft-grammar==1.1.1 craft-parts==1.17.1 -craft-providers==1.6.1 +craft-providers==1.6.2 craft-store==2.3.0 cryptography==3.4 Deprecated==1.2.13 distro==1.8.0 docutils==0.19 gnupg==2.3.1 -httplib2==0.20.4 +httplib2==0.21.0 idna==3.4 +importlib-metadata==5.1.0 jaraco.classes==3.2.3 jeepney==0.8.0 jsonschema==2.5.1 -keyring==23.9.3 -launchpadlib==1.10.16 -lazr.restfulclient==0.14.4 +keyring==23.11.0 +launchpadlib==1.10.18 +lazr.restfulclient==0.14.5 lazr.uri==1.0.6 -lxml==4.9.1 +lxml==4.9.2 macaroonbakery==1.3.1 -more-itertools==8.14.0 +more-itertools==9.0.0 mypy-extensions==0.4.3 -oauthlib==3.2.1 -overrides==7.0.0 -packaging==21.3 -platformdirs==2.5.2 +oauthlib==3.2.2 +overrides==7.3.1 +packaging==22.0 +platformdirs==2.6.0 progressbar==2.5 protobuf==3.20.3 -psutil==5.9.2 +psutil==5.9.4 pycparser==2.21 pydantic==1.9.0 pydantic-yaml==0.8.1 @@ -44,30 +45,31 @@ pymacaroons==0.13.0 pyparsing==3.0.9 pyRFC3339==1.1 python-dateutil==2.8.2 -python-debian==0.1.47 -pytz==2022.4 +python-debian==0.1.49 +pytz==2022.6 pyxdg==0.28 PyYAML==6.0 raven==6.10.0 requests==2.28.1 -requests-toolbelt==0.10.0 +requests-toolbelt==0.10.1 requests-unixsocket==0.3.0 SecretStorage==3.3.3 semantic-version==2.10.0 semver==2.13.0 -simplejson==3.17.6 +simplejson==3.18.0 six==1.16.0 -snap-helpers==0.2.0 +snap-helpers==0.3.1 tabulate==0.9.0 tinydb==4.7.0 toml==0.10.2 types-Deprecated==1.2.9 typing_extensions==4.4.0 -urllib3==1.26.12 +urllib3==1.26.13 wadllib==1.3.6 wrapt==1.14.1 ws4py==0.5.1 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.0ubuntu0.20.04.6/python-apt_2.0.0ubuntu0.20.04.6.tar.xz; sys.platform == "linux" +zipp==3.11.0 +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.0ubuntu0.20.04.8/python-apt_2.0.0ubuntu0.20.04.8.tar.xz; sys.platform == "linux" PyNaCl==1.4.0; sys.platform != "linux" PyNaCl @ https://files.pythonhosted.org/packages/61/ab/2ac6dea8489fa713e2b4c6c5b549cc962dd4a842b5998d9e80cf8440b7cd/PyNaCl-1.3.0.tar.gz; sys.platform == "linux" setuptools==49.6.0 diff --git a/setup.cfg b/setup.cfg index a80dbe21d52..db9fad1d030 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,11 +25,6 @@ exclude = stage, prime -[mypy] -python_version = 3.8 -ignore_missing_imports = True -follow_imports = silent - [pycodestyle] max-line-length = 88 ignore = E203,E501 @@ -40,3 +35,138 @@ ignore = E203,E501 # D213 Multi-line docstring summary should start at the second line (reason: pep257 default) ignore = D107, D203, D213 ignore_decorators = overrides + +[tox:tox] +min_version = 4.0 +env_list = + # Parametrized environments. + # First parameter allows us to choose Python 3.8 or 3.10. + # Second parameter chooses how to define the environment: + # dev: using requirements-devel.txt + # prod: using requirements.txt (but additionally installing pytest, pytest-cov, pytest-mock and pytest-subprocess) + # noreq: Without either requirements file (but including dev requirements) + # Third parameter selects the current unit tests (unit) or the legacy unit tests (legacy) + py{38,310}-{dev,prod,noreq}-{unit,legacy} +skip_missing_interpreters = true +labels = + # Minimal testing environments. run with `tox run-parallel -m test` + test = py38-dev-unit,py38-dev-legacy + # Test in Python 3.10 from an empty environment. + future-test = py310-noreq-unit,py310-noreq-legacy + +[testenv] +package = wheel +wheel_build_env = .pkg +deps = + dev,pylint: -r{tox_root}/requirements-devel.txt + prod: -r{tox_root}/requirements.txt + noreq,pyright: python-apt@ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.0ubuntu0.20.04.8/python-apt_2.0.0ubuntu0.20.04.8.tar.xz + +[testenv:py{38,310}-noreq-unit] +description = Run the unit tests based on only the package's specifications +platform = linux +install_command = pip install --no-binary PyNaCl {opts} {packages} +extras = dev +commands = + pytest {tty:--color=yes} --cov-report=xml:coverage-{env_name}.xml --junit-xml=test-results-{env_name}.xml tests/unit + +[testenv:py{38,310}-noreq-legacy] +description = Run the legacy unit tests based on only the package's specifications +platform = linux +install_command = pip install --no-binary PyNaCl {opts} {packages} +extras = dev +commands = + pytest {tty:--color=yes} --cov-report=xml:coverage-{env_name}.xml --junit-xml=test-results-{env_name}.xml tests/legacy/unit {posargs} + +[testenv:py{38,310}-{dev,prod}-unit] +description = Run the unit tests based on the relevant requirements file +commands = + pytest {tty:--color=yes} --cov-report=xml:coverage-{env_name}.xml --junit-xml=test-results-{env_name}.xml tests/unit {posargs} + +[testenv:py{38,310}-{dev,prod}-legacy] +description = Run the legacy unit tests based on the relevant requirements file +commands = + pytest {tty:--color=yes} --cov-report=xml:coverage-{env_name}.xml --junit-xml=test-results-{env_name}.xml tests/legacy/unit {posargs} + +[testenv:format] +description = Format with black +labels = fix +deps = black +skip_install = true +# Note: this does not include `snapcraft_legacy` as it contains several files that need reformatting. +commands = black setup.py snapcraft tests + +[testenv:mypy] +description = Run mypy +skip_install = true +labels = type, lint +deps = mypy +commands = mypy --install-types --non-interactive . + +[testenv:pyright] +description = run PyRight +labels = type, lint +use_develop = true +extras = dev +allowlist_externals = pyright +commands = pyright snapcraft tests + +[testenv:black] +description = run black in checking mode +skip_install = true +labels = lint +deps = black +commands = black --check --diff setup.py snapcraft tests + +[testenv:codespell] +description = Check spelling with codespell +skip_install = true +labels = lint +deps = codespell +commands = codespell --quiet-level 4 --ignore-words-list crate,keyserver,comandos,ro,buildd --skip '*.tar,*.xz,*.zip,*.bz2,*.7z,*.gz,*.deb,*.rpm,*.snap,*.gpg,*.pyc,*.png,*.ico,*.jar,*.so,changelog,.git,.hg,.mypy_cache,.tox,.venv,venv,_build,buck-out,__pycache__,build,dist,.vscode,parts,stage,prime,test_appstream.py,./snapcraft.spec,./.direnv,./.pytest_cache,./tests/spread/plugins/v1/waf/snaps/waf-hello/waf' + +[testenv:flake] +description = Lint with flake8 +skip_install = true +labels = lint +deps = flake8<6.0 +commands = + flake8 . + +[testenv:isort] +description = Check import order with isort +skip_install = true +labels = lint +deps = isort +commands = isort --check . + +[testenv:docstyle] +description = Check documentation style with pydocstyle +skip_install = true +labels = lint +deps = pydocstyle +commands = pydocstyle snapcraft + +[testenv:pylint] +description = Lint with pylint +labels = lint +skip_install = true +commands = + pylint -j 0 snapcraft + pylint -j 0 tests --disable=invalid-name,missing-module-docstring,missing-function-docstring,duplicate-code,protected-access,unspecified-encoding,too-many-public-methods,too-many-arguments,too-many-lines,redefined-outer-name + +[testenv:spellcheck] +description = Check spelling using spread-shellcheck.py +labels = lint +deps = pyyaml +skip_install = true +commands = + python3 tools/spread-shellcheck.py spread.yaml tests/spread/ + +[testenv:shellcheck] +description = Check spelling with shellcheck +labels = lint +skip_install = true +allowlist_externals = bash +commands = + bash -c "find . \( -name .git -o -name gradlew -o -name .tox \) -prune -o -print0 | xargs -0 file -N | grep shell.script | cut -f1 -d: | xargs shellcheck" diff --git a/setup.py b/setup.py index f9908deaaca..b265a2cc6e6 100755 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ # along with this program. If not, see . import os +import re import sys from setuptools import find_namespace_packages, setup @@ -61,8 +62,8 @@ def recursive_data_files(directory, install_directory): dev_requires = [ "black", "codespell", - "coverage", - "flake8", + "coverage[toml]", + "flake8<6.0.0", # TODO: Eliminate type comments and upgrade this "pyflakes", "fixtures", "isort", @@ -82,6 +83,7 @@ def recursive_data_files(directory, install_directory): "pytest-cov", "pytest-mock", "pytest-subprocess", + "tox>=4.0", "types-PyYAML", "types-requests", "types-setuptools", @@ -126,8 +128,13 @@ def recursive_data_files(directory, install_directory): ] try: - os_release = open("/etc/os-release").read() - ubuntu = "ID=ubuntu" in os_release + ubuntu = bool( + re.search( + r"^ID(?:_LIKE)?=.*\bubuntu\b.*$", + open("/etc/os-release").read(), + re.MULTILINE, + ) + ) except FileNotFoundError: ubuntu = False diff --git a/tools/freeze-requirements.sh b/tools/freeze-requirements.sh index 5f0279c9af8..7863db1acd9 100755 --- a/tools/freeze-requirements.sh +++ b/tools/freeze-requirements.sh @@ -5,7 +5,7 @@ requirements_fixups() { # Python apt library pinned to source. sed -i '/python-apt=*/d' "$req_file" - echo 'python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.0ubuntu0.20.04.6/python-apt_2.0.0ubuntu0.20.04.6.tar.xz; sys.platform == "linux"' >> "$req_file" + echo 'python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.0ubuntu0.20.04.8/python-apt_2.0.0ubuntu0.20.04.8.tar.xz; sys.platform == "linux"' >> "$req_file" # PyNaCl 1.4.0 has crypto related symbol issues when using the system # provided sodium. Ensure it is compiled on linux by pointing to source.