diff --git a/docs/changelog/1326.feature.rst b/docs/changelog/1326.feature.rst new file mode 100644 index 000000000..19a5e3052 --- /dev/null +++ b/docs/changelog/1326.feature.rst @@ -0,0 +1 @@ +Add the ``--devenv ENVDIR`` option for creating development environments from ``[testenv]`` configurations - by :user:`asottile`. diff --git a/docs/example/devenv.rst b/docs/example/devenv.rst index 2d62987b9..57e9ffaaf 100644 --- a/docs/example/devenv.rst +++ b/docs/example/devenv.rst @@ -10,16 +10,44 @@ environments. It can also be used for setting up normalized project development environments and thus help reduce the risk of different team members using mismatched development environments. + +Creating development environments using the ``--devenv`` option +=============================================================== + +The easiest way to set up a development environment is to use the ``--devenv`` +option along with your existing configured ``testenv``s. The ``--devenv`` +option accepts a single argument, the location you want to create a development +environment at. + +For example, if I wanted to replicate the ``py36`` environment, I could run:: + + $ tox --devenv venv-py36 -e py36 + ... + $ source venv-py36/bin/activate + (venv-py36) $ python --version + Python 3.6.7 + +The ``--devenv`` option skips the ``commands=`` section of that configured +test environment and always sets ``usedevelop=true`` for the environment that +is created. + +If you don't specify an environment with ``-e``, the devenv feature will +default to ``-e py`` -- usually taking the interpreter you're running ``tox`` +with and the default ``[testenv]`` configuration. + +Creating development environments using configuration +===================================================== + Here are some examples illustrating how to set up a project's development environment using tox. For illustration purposes, let us call the development environment ``dev``. Example 1: Basic scenario -========================= +------------------------- Step 1 - Configure the development environment ----------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First, we prepare the tox configuration for our development environment by defining a ``[testenv:dev]`` section in the project's ``tox.ini`` @@ -54,7 +82,7 @@ configuration: Step 2 - Create the development environment -------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once the ``[testenv:dev]`` configuration section has been defined, we create the actual development environment by running the following: @@ -68,7 +96,7 @@ This creates the environment at the path specified by the environment's Example 2: A more complex scenario -================================== +---------------------------------- Let us say we want our project development environment to: diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py index f42f60088..dc57f775e 100644 --- a/src/tox/config/__init__.py +++ b/src/tox/config/__init__.py @@ -415,6 +415,9 @@ def tox_addoption(parser): metavar="envlist", help="work against specified environments (ALL selects all).", ) + parser.add_argument( + "--devenv", help="sets up a development environment based on the tox configuration." + ) parser.add_argument("--notest", action="store_true", help="skip invoking test commands.") parser.add_argument( "--sdistonly", action="store_true", help="only perform the sdist packaging activity." @@ -497,12 +500,19 @@ def tox_addoption(parser): "args", nargs="*", help="additional arguments available to command positional substitution" ) + def _set_envdir_from_devenv(testenv_config, value): + if testenv_config.config.option.devenv: + return py.path.local(testenv_config.config.option.devenv) + else: + return value + parser.add_testenv_attribute( name="envdir", type="path", default="{toxworkdir}/{envname}", help="set venv directory -- be very careful when changing this as tox " "will remove this directory when recreating an environment", + postprocess=_set_envdir_from_devenv, ) # add various core venv interpreter attributes @@ -751,7 +761,7 @@ def pip_pre(testenv_config, value): def develop(testenv_config, value): option = testenv_config.config.option - return not option.installpkg and (value or option.develop) + return not option.installpkg and (value or option.develop or bool(option.devenv)) parser.add_testenv_attribute( name="usedevelop", @@ -1106,6 +1116,12 @@ def run(name, section, subs, config): config.skipsdist = reader.getbool("skipsdist", all_develop) + if config.option.devenv: + config.option.notest = True + + if config.option.devenv and len(config.envlist) != 1: + feedback("--devenv requires only a single -e", sysexit=True) + def handle_provision(self, config, reader): requires_list = reader.getlist("requires") config.minversion = reader.getstring("minversion", None) @@ -1251,6 +1267,7 @@ def _getenvdata(self, reader, config): (os.environ.get(PARALLEL_ENV_VAR_KEY), True), (from_option, True), (from_environ, True), + ("py" if self.config.option.devenv else None, False), (from_config, False), ) env_str, envlist_explicit = next(((i, e) for i, e in candidates if i), ([], False)) diff --git a/tests/unit/test_z_cmdline.py b/tests/unit/test_z_cmdline.py index 60941971c..f193df4dc 100644 --- a/tests/unit/test_z_cmdline.py +++ b/tests/unit/test_z_cmdline.py @@ -789,6 +789,52 @@ def test_notest_setup_py_error(initproj, cmd): assert re.search("ERROR:.*InvocationError", result.out) +def test_devenv(initproj, cmd): + initproj( + "example123", + filedefs={ + "setup.py": """\ + from setuptools import setup + setup(name='x') + """, + "tox.ini": """\ + [tox] + # envlist is ignored for --devenv + envlist = foo,bar,baz + + [testenv] + # --devenv implies --notest + commands = python -c "exit(1)" + """, + }, + ) + result = cmd("--devenv", "venv") + result.assert_success() + # `--devenv` defaults to the `py` environment and a develop install + assert "py develop-inst:" in result.out + assert re.search("py create:.*venv", result.out) + + +def test_devenv_does_not_allow_multiple_environments(initproj, cmd): + initproj( + "example123", + filedefs={ + "setup.py": """\ + from setuptools import setup + setup(name='x') + """, + "tox.ini": """\ + [tox] + envlist=foo,bar,baz + """, + }, + ) + + result = cmd("--devenv", "venv", "-e", "foo,bar") + result.assert_fail() + assert result.err == "ERROR: --devenv requires only a single -e\n" + + def test_PYC(initproj, cmd, monkeypatch): initproj("example123", filedefs={"tox.ini": ""}) monkeypatch.setenv("PYTHONDOWNWRITEBYTECODE", "1")