From da20957847908b699d58ddb5cc495323e45f114a Mon Sep 17 00:00:00 2001 From: Bernat Gabor Date: Thu, 9 Aug 2018 12:20:21 +0100 Subject: [PATCH] Ability to specify package requirements for the tox run #783 Done via the ``tox.ini`` (``tox`` section under key ``requires`` - PEP-508 style). Can be used to specify both plugin requirements or build dependencies until we'll have PEP-517 support. Using ``pkg_resources.Requirement``. Note tox will refuse to run if cannot satisfy dependencies. It's the users responsibility to ensure dependencies are satisfied. ``pkg_resources`` is part of setuptools so added setuptools as dependency. Realized pkg_resources contain everything we used from packaging, dropped packaging as dependency. --- changelog/783.feature.rst | 1 + doc/config.rst | 11 +++++++++++ setup.py | 2 +- src/tox/config.py | 24 +++++++++++++++++++++--- src/tox/session.py | 7 ++++--- tests/test_config.py | 22 ++++++++++++++++++++++ 6 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 changelog/783.feature.rst diff --git a/changelog/783.feature.rst b/changelog/783.feature.rst new file mode 100644 index 000000000..c49c8e356 --- /dev/null +++ b/changelog/783.feature.rst @@ -0,0 +1 @@ +Ability to specify package requirements for the tox run via the ``tox.ini`` (``tox`` section under key ``requires`` - PEP-508 style): can be used to specify both plugin requirements or build dependencies. - by :user:`gaborbernat` diff --git a/doc/config.rst b/doc/config.rst index e4c5a0a8a..ed4b9d420 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -77,6 +77,17 @@ and will first lookup global tox settings in this section: is identified. In a future version of tox, this warning will become an error. +.. confval:: requires=LIST + + Specify python packages that need to exist alongside the tox installation for the tox build + to be able to start. Use this to specify plugin requirements and build dependencies. + + .. code-block:: ini + + [tox] + requires = setuptools >= 30.0.0 + py + Virtualenv test environment settings ------------------------------------ diff --git a/setup.py b/setup.py index 9504708ef..100cc0017 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ def main(): python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", setup_requires=["setuptools_scm"], install_requires=[ - "packaging >= 17.1", + "setuptools >= 30.0.0", "pluggy >= 0.3.0, <1", "py >= 1.4.17, <2", "six >= 1.0.0, <2", diff --git a/src/tox/config.py b/src/tox/config.py index d3e6e764e..143dbdec7 100755 --- a/src/tox/config.py +++ b/src/tox/config.py @@ -15,7 +15,6 @@ import pkg_resources import pluggy import py -from packaging.version import parse import tox from tox.interpreters import Interpreters @@ -901,14 +900,17 @@ def __init__(self, config, inipath): # prevent parsing of tox.ini this must be the first thing checked. config.minversion = reader.getstring("minversion", None) if config.minversion: - tox_version = parse(tox.__version__) - config_min_version = parse(self.config.minversion) + tox_version = pkg_resources.parse_version(tox.__version__) + config_min_version = pkg_resources.parse_version(self.config.minversion) if config_min_version > tox_version: raise tox.exception.MinVersionError( "tox version is {}, required is at least {}".format( tox.__version__, self.config.minversion ) ) + + self.ensure_requires_satisfied(reader.getlist("requires")) + if config.option.workdir is None: config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox") else: @@ -989,6 +991,22 @@ def __init__(self, config, inipath): config.skipsdist = reader.getbool("skipsdist", all_develop) + @staticmethod + def ensure_requires_satisfied(specified): + fail = False + for s in specified: + try: + pkg_resources.get_distribution(s) + except pkg_resources.RequirementParseError: + raise + except Exception: + fail = True + print( + "requirement missing {}".format(pkg_resources.Requirement(s)), file=sys.stderr + ) + if fail: + raise RuntimeError("not all requirements satisfied, install them alongside tox") + def _list_section_factors(self, section): factors = set() if section in self._cfg: diff --git a/src/tox/session.py b/src/tox/session.py index 8ff104af9..ef1708099 100644 --- a/src/tox/session.py +++ b/src/tox/session.py @@ -13,8 +13,8 @@ import sys import time +import pkg_resources import py -from packaging.version import InvalidVersion, Version import tox from tox.config import parseconfig @@ -775,6 +775,7 @@ def get_version_from_filename(basename): return None version = m.group(1) try: - return Version(version) - except InvalidVersion: + + return pkg_resources.packaging.version.Version(version) + except pkg_resources.packaging.version.InvalidVersion: return None diff --git a/tests/test_config.py b/tests/test_config.py index 496c4a355..a21abd85e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2716,3 +2716,25 @@ def test_commands_with_backslash(self, newconfig): ) envconfig = config.envconfigs["py36"] assert envconfig.commands[0] == ["some", r"hello\world"] + + +def test_plugin_require(newconfig, capsys): + inisource = """ + [tox] + requires = tox + name[foo,bar]>=2,<3; python_version>"2.0" and os_name=='a' + b + """ + with pytest.raises( + RuntimeError, match="not all requirements satisfied, install them alongside tox" + ): + newconfig([], inisource) + + out, err = capsys.readouterr() + assert err.strip() == "\n".join( + [ + 'requirement missing name[bar,foo]<3,>=2; python_version > "2.0" and os_name == "a"', + "requirement missing b", + ] + ) + assert not out