diff --git a/README.rst b/README.rst index 514decd..6242866 100644 --- a/README.rst +++ b/README.rst @@ -14,11 +14,11 @@ The ``tox-current-env`` plugin adds two options: An attempt to run this with a Python version that doesn't match will fail (if ``tox`` is invoked from an Python 3.7 environment, any non 3.7 testenv will fail). -``tox --print-deps-only`` / ``--print-deps-to-file`` +``tox --print-deps-to=FILE`` Instead of running any ``commands``, - simply prints the declared dependencies in ``deps`` to the standard output or specified file. + simply prints the declared dependencies in ``deps`` to the specified ``FILE``. This is useful for preparing the current environment for the above. - ``--print-deps-to-file`` will overwrite the file if it already exists. + Use ``-`` for ``FILE`` to print to standard output. Invoking ``tox`` without any of the above options should behave as regular ``tox`` invocation without this plugin. Any deviation from this behavior is considered a bug. @@ -82,7 +82,7 @@ and ``pip``-installing locally: Usage ----- -When the plugin is installed, use ``tox`` with ``--current-env`` or ``--print-deps-only`` and all the other options as usual. Assuming your ``tox`` is installed on Python 3.7: +When the plugin is installed, use ``tox`` with ``--current-env`` or ``--print-deps-to`` and all the other options as usual. Assuming your ``tox`` is installed on Python 3.7: .. code-block:: console @@ -114,7 +114,7 @@ To get list of test dependencies, run: .. code-block:: console - $ tox -e py37 --print-deps-only + $ tox -e py37 --print-deps-to - py37 create: /home/pythonista/projects/holy-grail/tests/.tox/py37 py37 installed: ...you can see almost anything here... py37 run-test-pre: PYTHONHASHSEED='3333333333' @@ -159,7 +159,7 @@ Don't mix current-env and regular tox runs Tox caches the virtualenvs it creates, and doesn't distinguish between regular virtualenvs and ``--current-env``. -Don't mix ``tox --current-env`` or ``tox --print-deps-only`` runs +Don't mix ``tox --current-env`` or ``tox --print-deps-to`` runs and regular ``tox`` runs (without the flag). If you ever need to do this, use tox's ``--recreate/-r`` flag to clear the cache. @@ -183,7 +183,7 @@ Read `the documentation for passing environment variables to tox Other limitations and known bugs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``installed:`` line in the output of ``tox --print-deps-only`` shows irrelevant output +The ``installed:`` line in the output of ``tox --print-deps-to`` shows irrelevant output (based on the content of the real or faked virtual environment). Regardless of any `Python flags `_ used in the shebang of ``tox``, diff --git a/src/tox_current_env/hooks.py b/src/tox_current_env/hooks.py index 6e74ee1..4759491 100644 --- a/src/tox_current_env/hooks.py +++ b/src/tox_current_env/hooks.py @@ -3,6 +3,8 @@ import shutil import subprocess import sys import tox +import warnings +import argparse try: import importlib.metadata as importlib_metadata @@ -24,30 +26,37 @@ def tox_addoption(parser): action="store_true", dest="print_deps_only", default=False, - help="Don't run tests, only print the dependencies to stdout", + help="Deprecated, equivalent to `--print-deps-to -`", ) parser.add_argument( + "--print-deps-to", "--print-deps-to-file", action="store", - dest="print_deps_path", - metavar="PATH", + dest="print_deps_to", + type=argparse.FileType('w'), + metavar="FILE", default=None, - help="Like --print-deps-only, but to a file. Overwrites the file if it exists.", + help="Don't run tests, only print the dependencies to the given file " + + "(use `-` for stdout)", ) @tox.hookimpl def tox_configure(config): """Stores options in the config. Makes all commands external and skips sdist""" - if config.option.print_deps_only and config.option.print_deps_path: - raise tox.exception.ConfigError( - "--print-deps-only cannot be used together with --print-deps-to-file" + if config.option.print_deps_only: + warnings.warn( + "--print-deps-only is deprecated; use `--print-deps-to -`", + DeprecationWarning, ) - if config.option.print_deps_path is not None: - config.option.print_deps_only = True - with open(config.option.print_deps_path, "w", encoding="utf-8") as f: - f.write("") - if config.option.current_env or config.option.print_deps_only: + if not config.option.print_deps_to: + config.option.print_deps_to = sys.stdout + else: + raise tox.exception.ConfigError( + "--print-deps-only cannot be used together " + + "with --print-deps-to" + ) + if config.option.current_env or config.option.print_deps_to: config.skipsdist = True for testenv in config.envconfigs: config.envconfigs[testenv].whitelist_externals = "*" @@ -89,16 +98,16 @@ def rm_venv(venv): def unsupported_raise(config, venv): if config.option.recreate: return - regular = not (config.option.current_env or config.option.print_deps_only) + regular = not (config.option.current_env or config.option.print_deps_to) if regular and is_current_env_link(venv): if hasattr(tox.hookspecs, "tox_cleanup"): raise tox.exception.ConfigError( - "Looks like previous --current-env or --print-deps-only tox run didn't finish the cleanup. " + "Looks like previous --current-env or --print-deps-to tox run didn't finish the cleanup. " "Run tox run with --recreate (-r) or manually remove the environment in .tox." ) else: raise tox.exception.ConfigError( - "Regular tox run after --current-env or --print-deps-only tox run is not supported without --recreate (-r)." + "Regular tox run after --current-env or --print-deps-to tox run is not supported without --recreate (-r)." ) elif config.option.current_env and is_proper_venv(venv): raise tox.exception.ConfigError( @@ -111,7 +120,7 @@ def tox_testenv_create(venv, action): """We create a fake virtualenv with just the symbolic link""" config = venv.envconfig.config create_fake_env = check_version = config.option.current_env - if config.option.print_deps_only: + if config.option.print_deps_to: if is_any_env(venv): # We don't need anything return True @@ -122,7 +131,7 @@ def tox_testenv_create(venv, action): # because it's cheaper, faster and won't install stuff create_fake_env = True if check_version: - # With real --current-env, we check this, but not with --print-deps-only only + # With real --current-env, we check this, but not with --print-deps-to only version_info = venv.envconfig.python_info.version_info if version_info is None: raise tox.exception.InterpreterNotFound(venv.envconfig.basepython) @@ -164,21 +173,21 @@ def tox_testenv_install_deps(venv, action): """We don't install anything""" config = venv.envconfig.config unsupported_raise(config, venv) - if config.option.current_env or config.option.print_deps_only: + if config.option.current_env or config.option.print_deps_to: return True @tox.hookimpl def tox_runtest(venv, redirect): - """If --print-deps-only, prints deps instead of running tests""" + """If --print-deps-to, prints deps instead of running tests""" config = venv.envconfig.config unsupported_raise(config, venv) - if config.option.print_deps_path is not None: - with open(config.option.print_deps_path, "a", encoding="utf-8") as f: - print(*venv.get_resolved_dependencies(), sep="\n", file=f) - return True - if config.option.print_deps_only: - print(*venv.get_resolved_dependencies(), sep="\n") + if config.option.print_deps_to: + print( + *venv.get_resolved_dependencies(), + sep="\n", + file=config.option.print_deps_to, + ) return True diff --git a/tests/test_integration.py b/tests/test_integration.py index 203058a..c876d54 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -6,6 +6,7 @@ import shutil import subprocess import sys import textwrap +import warnings from packaging import version @@ -29,6 +30,12 @@ def projdir(tmp_path, monkeypatch): monkeypatch.chdir(pwd) +@pytest.fixture(params=('--print-deps-only', '--print-deps-to-file=-', '--print-deps-to=-')) +def print_deps_stdout_arg(request): + """Argument for printing deps to stdout""" + return request.param + + def tox(*args, quiet=True, **kwargs): kwargs.setdefault("encoding", "utf-8") kwargs.setdefault("stdout", subprocess.PIPE) @@ -95,8 +102,8 @@ def test_all_toxenv_current_env_skip_missing(): @pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) -def test_print_deps_only(toxenv): - result = tox("-e", toxenv, "--print-deps-only") +def test_print_deps(toxenv, print_deps_stdout_arg): + result = tox("-e", toxenv, print_deps_stdout_arg) expected = textwrap.dedent( f""" six @@ -109,8 +116,21 @@ def test_print_deps_only(toxenv): assert result.stdout == expected -def test_allenvs_print_deps_only(): - result = tox("--print-deps-only") +@pytest.mark.parametrize("toxenv", ["py36", "py37", "py38", "py39"]) +def test_print_deps_only_deprecated(toxenv): + result = tox( + "-e", toxenv, '--print-deps-only', + env={**os.environ, 'PYTHONWARNINGS': 'always'}, + ) + waring_text = ( + "DeprecationWarning: --print-deps-only is deprecated; " + + "use `--print-deps-to -`" + ) + assert waring_text in result.stderr + + +def test_allenvs_print_deps(print_deps_stdout_arg): + result = tox(print_deps_stdout_arg) expected = textwrap.dedent( """ six @@ -229,7 +249,7 @@ def test_regular_after_current_is_supported(): assert "--recreate" not in result.stderr -def test_regular_after_killed_current_is_not_supported(): +def test_regular_after_killed_current_is_not_supported(print_deps_stdout_arg): # fake broken tox run shutil.rmtree(DOT_TOX, ignore_errors=True) (DOT_TOX / NATIVE_TOXENV / "bin").mkdir(parents=True) @@ -240,8 +260,8 @@ def test_regular_after_killed_current_is_not_supported(): assert "--recreate" in result.stderr -def test_regular_after_first_deps_only_is_supported(): - result = tox("-e", NATIVE_TOXENV, "--print-deps-only") +def test_regular_after_first_print_deps_is_supported(print_deps_stdout_arg): + result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg) assert result.stdout.splitlines()[0] == "six" result = tox("-e", NATIVE_TOXENV) lines = sorted(result.stdout.splitlines()[:1]) @@ -283,18 +303,18 @@ def test_current_recreate_after_regular(): assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG -def test_current_after_deps_only(): +def test_current_after_print_deps(print_deps_stdout_arg): # this is quite fast, so we can do it several times for _ in range(3): - result = tox("-e", NATIVE_TOXENV, "--print-deps-only") + result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg) assert "bin/python" not in result.stdout assert "six" in result.stdout result = tox("-re", NATIVE_TOXENV, "--current-env") assert result.stdout.splitlines()[0] == NATIVE_EXEC_PREFIX_MSG -def test_regular_recreate_after_deps_only(): - result = tox("-e", NATIVE_TOXENV, "--print-deps-only") +def test_regular_recreate_after_print_deps(print_deps_stdout_arg): + result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg) assert "bin/python" not in result.stdout assert "six" in result.stdout @@ -304,12 +324,12 @@ def test_regular_recreate_after_deps_only(): assert sitelib.is_dir() assert len(list(sitelib.glob("test-*.dist-info"))) == 1 - result = tox("-e", NATIVE_TOXENV, "--print-deps-only") + result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg) assert "bin/python" not in result.stdout assert "six" in result.stdout -def test_print_deps_without_python_command(tmp_path): +def test_print_deps_without_python_command(tmp_path, print_deps_stdout_arg): bin = tmp_path / "bin" bin.mkdir() tox_link = bin / "tox" @@ -317,7 +337,7 @@ def test_print_deps_without_python_command(tmp_path): tox_link.symlink_to(tox_path) env = {**os.environ, "PATH": str(bin)} - result = tox("-e", NATIVE_TOXENV, "--print-deps-only", env=env) + result = tox("-e", NATIVE_TOXENV, print_deps_stdout_arg, env=env) expected = textwrap.dedent( f""" six