diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 497d5ca88..091a32c89 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: hooks: - id: pyupgrade - repo: https://github.com/PyCQA/isort - rev: 5.5.4 + rev: 5.6.3 hooks: - id: isort - repo: https://github.com/ambv/black @@ -36,11 +36,12 @@ repos: hooks: - id: rst-backticks - repo: https://github.com/tox-dev/tox-ini-fmt - rev: "0.2.0" + rev: "0.5.0" hooks: - id: tox-ini-fmt + args: ["-p", "fix_lint"] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.11.0 + rev: v1.15.0 hooks: - id: setup-cfg-fmt args: [--min-py3-version, "3.4"] diff --git a/docs/changelog/1782.bugfix.rst b/docs/changelog/1782.bugfix.rst new file mode 100644 index 000000000..416cb1730 --- /dev/null +++ b/docs/changelog/1782.bugfix.rst @@ -0,0 +1,2 @@ +Align with venv module when creating virtual environments with builtin creator on Windows 3.7 and later +- by :user:`gaborbernat`. diff --git a/setup.cfg b/setup.cfg index f16950f9a..9f5fb2318 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,9 @@ long_description = file: README.md long_description_content_type = text/markdown url = https://virtualenv.pypa.io/ author = Bernat Gabor +author_email = gaborjbernat@gmail.com maintainer = Bernat Gabor +maintainer_email = gaborjbernat@gmail.com license = MIT license_file = LICENSE platforms = any @@ -24,14 +26,13 @@ classifiers = Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries Topic :: Software Development :: Testing Topic :: Utilities -author-email = gaborjbernat@gmail.com keywords = virtual, environments, isolated -maintainer-email = gaborjbernat@gmail.com project_urls = Source=https://github.com/pypa/virtualenv Tracker=https://github.com/pypa/virtualenv/issues diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/common.py b/src/virtualenv/create/via_global_ref/builtin/cpython/common.py index ac251b9db..39c56cb3e 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/common.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/common.py @@ -6,6 +6,7 @@ from six import add_metaclass from virtualenv.create.describe import PosixSupports, WindowsSupports +from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen from virtualenv.util.path import Path from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin @@ -33,19 +34,26 @@ def _executables(cls, interpreter): targets = OrderedDict( (i, None) for i in ["python", "python{}".format(major), "python{}.{}".format(major, minor), host_exe.name] ) - yield host_exe, list(targets.keys()) + must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA + yield host_exe, list(targets.keys()), must, RefWhen.ANY @add_metaclass(ABCMeta) class CPythonWindows(CPython, WindowsSupports): @classmethod def _executables(cls, interpreter): - host = Path(interpreter.system_executable) + executables = cls._win_executables(Path(interpreter.system_executable), interpreter, RefWhen.ANY) + for src, targets, must, when in executables: + yield src, targets, must, when + + @classmethod + def _win_executables(cls, host, interpreter, when): + must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA for path in (host.parent / n for n in {"python.exe", host.name}): - yield host, [path.name] + yield host, [path.name], must, when # for more info on pythonw.exe see https://stackoverflow.com/a/30313091 python_w = host.parent / "pythonw.exe" - yield python_w, [python_w.name] + yield python_w, [python_w.name], must, when def is_mac_os_framework(interpreter): diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py index dd2143639..149e8edd4 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py @@ -1,12 +1,13 @@ from __future__ import absolute_import, unicode_literals import abc +from itertools import chain from textwrap import dedent from six import add_metaclass from virtualenv.create.describe import Python3Supports -from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen from virtualenv.create.via_global_ref.store import is_store_python from virtualenv.util.path import Path @@ -55,8 +56,29 @@ def setup_meta(cls, interpreter): def sources(cls, interpreter): for src in super(CPython3Windows, cls).sources(interpreter): yield src - for src in cls.include_dll_and_pyd(interpreter): - yield src + if cls.venv_37p(interpreter): + for dll in (i for i in Path(interpreter.system_executable).parent.iterdir() if i.suffix == ".dll"): + yield PathRefToDest(dll, cls.to_bin, RefMust.SYMLINK, RefWhen.SYMLINK) + else: + for src in cls.include_dll_and_pyd(interpreter): + yield src + + @classmethod + def _executables(cls, interpreter): + system_exe = Path(interpreter.system_executable) + if cls.venv_37p(interpreter): + # starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies + launcher = Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt" / "python.exe" + executables = cls._win_executables(launcher, interpreter, RefWhen.COPY) + executables = chain(executables, cls._win_executables(system_exe, interpreter, RefWhen.SYMLINK)) + else: + executables = cls._win_executables(system_exe, interpreter, RefWhen.ANY) + for src, targets, must, when in executables: + yield src, targets, must, when + + @staticmethod + def venv_37p(interpreter): + return interpreter.version_info.minor > 6 @classmethod def include_dll_and_pyd(cls, interpreter): diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py b/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py index 1b971f3e7..53f65e341 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py @@ -9,7 +9,7 @@ from six import add_metaclass -from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest +from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefMust from virtualenv.util.path import Path from virtualenv.util.six import ensure_text @@ -29,7 +29,8 @@ def sources(cls, interpreter): for src in super(CPythonmacOsFramework, cls).sources(interpreter): yield src # add a symlink to the host python image - ref = PathRefToDest(cls.image_ref(interpreter), dest=lambda self, _: self.dest / ".Python", must_symlink=True) + exe = cls.image_ref(interpreter) + ref = PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK) yield ref def create(self): @@ -40,7 +41,7 @@ def create(self): current = self.current_mach_o_image_path() for src in self._sources: if isinstance(src, ExePathRefToDest): - if src.must_copy or not self.symlinks: + if src.must == RefMust.COPY or not self.symlinks: exes = [self.bin_dir / src.base] if not self.symlinks: exes.extend(self.bin_dir / a for a in src.aliases) @@ -49,12 +50,12 @@ def create(self): @classmethod def _executables(cls, interpreter): - for _, targets in super(CPythonmacOsFramework, cls)._executables(interpreter): + for _, targets, must, when in super(CPythonmacOsFramework, cls)._executables(interpreter): # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the # stub executable in ${sys.prefix}/bin. # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951 fixed_host_exe = Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python" - yield fixed_host_exe, targets + yield fixed_host_exe, targets, must, when @abstractmethod def current_mach_o_image_path(self): diff --git a/src/virtualenv/create/via_global_ref/builtin/pypy/common.py b/src/virtualenv/create/via_global_ref/builtin/pypy/common.py index 90da51fe0..cc03b4293 100644 --- a/src/virtualenv/create/via_global_ref/builtin/pypy/common.py +++ b/src/virtualenv/create/via_global_ref/builtin/pypy/common.py @@ -4,7 +4,7 @@ from six import add_metaclass -from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen from virtualenv.util.path import Path from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin @@ -20,7 +20,8 @@ def can_describe(cls, interpreter): def _executables(cls, interpreter): host = Path(interpreter.system_executable) targets = sorted("{}{}".format(name, PyPy.suffix) for name in cls.exe_names(interpreter)) - yield host, targets + must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA + yield host, targets, must, RefWhen.ANY @classmethod def exe_names(cls, interpreter): diff --git a/src/virtualenv/create/via_global_ref/builtin/ref.py b/src/virtualenv/create/via_global_ref/builtin/ref.py index 263da3b1f..69f243bf9 100644 --- a/src/virtualenv/create/via_global_ref/builtin/ref.py +++ b/src/virtualenv/create/via_global_ref/builtin/ref.py @@ -17,6 +17,18 @@ from virtualenv.util.six import ensure_text +class RefMust(object): + NA = "NA" + COPY = "copy" + SYMLINK = "symlink" + + +class RefWhen(object): + ANY = "ANY" + COPY = "copy" + SYMLINK = "symlink" + + @add_metaclass(ABCMeta) class PathRef(object): """Base class that checks if a file reference can be symlink/copied""" @@ -24,9 +36,9 @@ class PathRef(object): FS_SUPPORTS_SYMLINK = fs_supports_symlink() FS_CASE_SENSITIVE = fs_is_case_sensitive() - def __init__(self, src, must_symlink, must_copy): - self.must_symlink = must_symlink - self.must_copy = must_copy + def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY): + self.must = must + self.when = when self.src = src try: self.exists = src.exists() @@ -35,8 +47,6 @@ def __init__(self, src, must_symlink, must_copy): self._can_read = None if self.exists else False self._can_copy = None if self.exists else False self._can_symlink = None if self.exists else False - if self.must_copy is True and self.must_symlink is True: - raise ValueError("can copy and symlink at the same time") def __repr__(self): return "{}(src={})".format(self.__class__.__name__, self.src) @@ -57,7 +67,7 @@ def can_read(self): @property def can_copy(self): if self._can_copy is None: - if self.must_symlink: + if self.must == RefMust.SYMLINK: self._can_copy = self.can_symlink else: self._can_copy = self.can_read @@ -66,7 +76,7 @@ def can_copy(self): @property def can_symlink(self): if self._can_symlink is None: - if self.must_copy: + if self.must == RefMust.COPY: self._can_symlink = self.can_copy else: self._can_symlink = self.FS_SUPPORTS_SYMLINK and self.can_read @@ -77,9 +87,9 @@ def run(self, creator, symlinks): raise NotImplementedError def method(self, symlinks): - if self.must_symlink: + if self.must == RefMust.SYMLINK: return symlink - if self.must_copy: + if self.must == RefMust.COPY: return copy return symlink if symlinks else copy @@ -88,8 +98,8 @@ def method(self, symlinks): class ExePathRef(PathRef): """Base class that checks if a executable can be references via symlink/copy""" - def __init__(self, src, must_symlink, must_copy): - super(ExePathRef, self).__init__(src, must_symlink, must_copy) + def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY): + super(ExePathRef, self).__init__(src, must, when) self._can_run = None @property @@ -114,8 +124,8 @@ def can_run(self): class PathRefToDest(PathRef): """Link a path on the file system""" - def __init__(self, src, dest, must_symlink=False, must_copy=False): - super(PathRefToDest, self).__init__(src, must_symlink, must_copy) + def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY): + super(PathRefToDest, self).__init__(src, must, when) self.dest = dest def run(self, creator, symlinks): @@ -131,15 +141,14 @@ def run(self, creator, symlinks): class ExePathRefToDest(PathRefToDest, ExePathRef): """Link a exe path on the file system""" - def __init__(self, src, targets, dest, must_symlink=False, must_copy=False): - ExePathRef.__init__(self, src, must_symlink, must_copy) - PathRefToDest.__init__(self, src, dest, must_symlink, must_copy) + def __init__(self, src, targets, dest, must=RefMust.NA, when=RefWhen.ANY): + ExePathRef.__init__(self, src, must, when) + PathRefToDest.__init__(self, src, dest, must, when) if not self.FS_CASE_SENSITIVE: targets = list(OrderedDict((i.lower(), None) for i in targets).keys()) self.base = targets[0] self.aliases = targets[1:] self.dest = dest - self.must_copy = must_copy def run(self, creator, symlinks): bin_dir = self.dest(creator, self.src).parent diff --git a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py index 7de4fe13f..a00b97aae 100644 --- a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py +++ b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py @@ -4,7 +4,7 @@ from six import add_metaclass -from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest +from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, RefMust from virtualenv.util.path import ensure_dir from ..api import ViaGlobalRefApi, ViaGlobalRefMeta @@ -27,27 +27,37 @@ def __init__(self, options, interpreter): def can_create(cls, interpreter): """By default all built-in methods assume that if we can describe it we can create it""" # first we must be able to describe it - if cls.can_describe(interpreter): - meta = cls.setup_meta(interpreter) - if meta is not None and meta: - for src in cls.sources(interpreter): - if src.exists: - if meta.can_copy and not src.can_copy: - meta.copy_error = "cannot copy {}".format(src) - if meta.can_symlink and not src.can_symlink: - meta.symlink_error = "cannot symlink {}".format(src) - if not meta.can_copy and not meta.can_symlink: - meta.error = "neither copy or symlink supported, copy: {} symlink: {}".format( - meta.copy_error, - meta.symlink_error, - ) - else: - meta.error = "missing required file {}".format(src) - if meta.error: - break - meta.sources.append(src) - return meta - return None + if not cls.can_describe(interpreter): + return None + meta = cls.setup_meta(interpreter) + if meta is not None and meta: + cls._sources_can_be_applied(interpreter, meta) + return meta + + @classmethod + def _sources_can_be_applied(cls, interpreter, meta): + for src in cls.sources(interpreter): + if src.exists: + if meta.can_copy and not src.can_copy: + meta.copy_error = "cannot copy {}".format(src) + if meta.can_symlink and not src.can_symlink: + meta.symlink_error = "cannot symlink {}".format(src) + else: + msg = "missing required file {}".format(src) + if src.when == RefMust.NA: + meta.error = msg + elif src.when == RefMust.COPY: + meta.copy_error = msg + elif src.when == RefMust.SYMLINK: + meta.symlink_error = msg + if not meta.can_copy and not meta.can_symlink: + meta.error = "neither copy or symlink supported, copy: {} symlink: {}".format( + meta.copy_error, + meta.symlink_error, + ) + if meta.error: + break + meta.sources.append(src) @classmethod def setup_meta(cls, interpreter): @@ -55,9 +65,8 @@ def setup_meta(cls, interpreter): @classmethod def sources(cls, interpreter): - is_py2 = interpreter.version_info.major == 2 - for host_exe, targets in cls._executables(interpreter): - yield ExePathRefToDest(host_exe, dest=cls.to_bin, targets=targets, must_copy=is_py2) + for host_exe, targets, must, when in cls._executables(interpreter): + yield ExePathRefToDest(host_exe, dest=cls.to_bin, targets=targets, must=must, when=when) def to_bin(self, src): return self.bin_dir / src.name diff --git a/src/virtualenv/run/plugin/creators.py b/src/virtualenv/run/plugin/creators.py index 31d03cfc4..ef4177a59 100644 --- a/src/virtualenv/run/plugin/creators.py +++ b/src/virtualenv/run/plugin/creators.py @@ -18,14 +18,14 @@ def __init__(self, interpreter, parser): @classmethod def for_interpreter(cls, interpreter): key_to_class, key_to_meta, builtin_key, describe = OrderedDict(), {}, None, None - errored = defaultdict(list) + errors = defaultdict(list) for key, creator_class in cls.options("virtualenv.create").items(): if key == "builtin": raise RuntimeError("builtin creator is a reserved name") meta = creator_class.can_create(interpreter) if meta: if meta.error: - errored[meta.error].append(creator_class) + errors[meta.error].append(creator_class) else: if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin): builtin_key = key @@ -36,12 +36,9 @@ def for_interpreter(cls, interpreter): if describe is None and issubclass(creator_class, Describe) and creator_class.can_describe(interpreter): describe = creator_class if not key_to_meta: - if errored: - raise RuntimeError( - "\n".join( - "{} for creators {}".format(k, ", ".join(i.__name__ for i in v)) for k, v in errored.items() - ), - ) + if errors: + rows = ["{} for creators {}".format(k, ", ".join(i.__name__ for i in v)) for k, v in errors.items()] + raise RuntimeError("\n".join(rows)) else: raise RuntimeError("No virtualenv implementation for {}".format(interpreter)) return CreatorInfo( diff --git a/tox.ini b/tox.ini index 353032c53..bf9617919 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,18 @@ [tox] envlist = - py38 - py37 - py36 - py35 - py34 - py27 - fix_lint - pypy3 - pypy - coverage - readme - docs + fix_lint + py39 + py38 + py37 + py36 + py35 + py34 + py27 + pypy3 + pypy2 + coverage + readme + docs isolated_build = true skip_missing_interpreters = true minversion = 3.14 @@ -19,142 +20,140 @@ minversion = 3.14 [testenv] description = run tests with {basepython} passenv = - CI_RUN - HOME - PIP_* - PYTEST_* - TERM - http_proxy - https_proxy - no_proxy + CI_RUN + HOME + PIP_* + PYTEST_* + TERM setenv = - COVERAGE_FILE = {toxworkdir}/.coverage.{envname} - COVERAGE_PROCESS_START = {toxinidir}/.coveragerc - PYTHONIOENCODING = utf-8 - _COVERAGE_SRC = {envsitepackagesdir}/virtualenv - {py34,py27,pypy, upgrade}: PYTHONWARNINGS = ignore:DEPRECATION::pip._internal.cli.base_command - {py34,pypy,py27}: PYTEST_XDIST = 0 + COVERAGE_FILE = {toxworkdir}/.coverage.{envname} + COVERAGE_PROCESS_START = {toxinidir}/.coveragerc + PYTHONIOENCODING = utf-8 + _COVERAGE_SRC = {envsitepackagesdir}/virtualenv + {py34,py27,pypy2, upgrade}: PYTHONWARNINGS = ignore:DEPRECATION::pip._internal.cli.base_command + {py34,pypy2,py27}: PYTEST_XDIST = 0 extras = - testing + testing commands = - python -m coverage erase - python -m coverage run -m pytest \ - --junitxml {toxworkdir}/junit.{envname}.xml \ - {posargs:tests --int --timeout 600 -n {env:PYTEST_XDIST:auto}} - python -m coverage combine - python -m coverage report --skip-covered --show-missing - python -m coverage xml -o {toxworkdir}/coverage.{envname}.xml - python -m coverage html -d {envtmpdir}/htmlcov \ - !py34: --show-contexts \ - --title virtualenv-{envname}-coverage + python -m coverage erase + python -m coverage run -m pytest \ + --junitxml {toxworkdir}/junit.{envname}.xml \ + {posargs:tests --int --timeout 600 -n {env:PYTEST_XDIST:auto}} + python -m coverage combine + python -m coverage report --skip-covered --show-missing + python -m coverage xml -o {toxworkdir}/coverage.{envname}.xml + python -m coverage html -d {envtmpdir}/htmlcov \ + !py34: --show-contexts \ + --title virtualenv-{envname}-coverage install_command = python -m pip install {opts} {packages} --disable-pip-version-check [testenv:fix_lint] description = format the code base to adhere to our styles, and complain about what we cannot do automatically passenv = - * -basepython = python3.8 + * +basepython = python3.9 skip_install = true deps = - pre-commit>=2 + pre-commit>=2 commands = - pre-commit run --all-files --show-diff-on-failure - python -c 'import pathlib; print("hint: run \{\} install to add checks as pre-commit hook".format(pathlib.Path(r"{envdir}") / "bin" / "pre-commit"))' + pre-commit run --all-files --show-diff-on-failure + python -c 'import pathlib; print("hint: run \{\} install to add checks as pre-commit hook".format(pathlib.Path(r"{envdir}") / "bin" / "pre-commit"))' [testenv:coverage] description = [run locally after tests]: combine coverage data and create report; - generates a diff coverage against origin/main (can be changed by setting DIFF_AGAINST env var) + generates a diff coverage against origin/main (can be changed by setting DIFF_AGAINST env var) passenv = - DIFF_AGAINST + DIFF_AGAINST setenv = - COVERAGE_FILE = {toxworkdir}/.coverage + COVERAGE_FILE = {toxworkdir}/.coverage skip_install = true deps = - coverage>=5.0.1 - diff_cover>=3 + coverage>=5.0.1 + diff_cover>=3 extras = parallel_show_output = true commands = - python -m coverage combine - python -m coverage report --skip-covered --show-missing - python -m coverage xml -o {toxworkdir}/coverage.xml - python -m coverage html -d {toxworkdir}/htmlcov - python -m diff_cover.diff_cover_tool --compare-branch {env:DIFF_AGAINST:origin/main} {toxworkdir}/coverage.xml + python -m coverage combine + python -m coverage report --skip-covered --show-missing + python -m coverage xml -o {toxworkdir}/coverage.xml + python -m coverage html -d {toxworkdir}/htmlcov + python -m diff_cover.diff_cover_tool --compare-branch {env:DIFF_AGAINST:origin/main} {toxworkdir}/coverage.xml depends = - py38 - py37 - py36 - py35 - py34 - py27 - pypy - pypy3 + py39 + py38 + py37 + py36 + py35 + py34 + py27 + pypy + pypy3 [testenv:readme] description = check that the long description is valid (need for PyPI) skip_install = true deps = - pep517>=0.8.2 - twine>=1.12.1 + build>=0.0.4 + twine>=3 extras = commands = - python -m pep517.build -o {envtmpdir} -b -s . - twine check {envtmpdir}/* + python -m build -o {envtmpdir} --wheel --sdist . + twine check {envtmpdir}/* [testenv:docs] description = build documentation -basepython = python3.8 +basepython = python3.9 extras = - docs + docs commands = - python -c 'import glob; import subprocess; subprocess.call(["proselint"] + glob.glob("docs/*.rst") + glob.glob("docs/**/*.rst"))' - sphinx-build -d "{envtmpdir}/doctree" docs "{toxworkdir}/docs_out" --color -b html {posargs} - python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))' + python -c 'import glob; import subprocess; subprocess.call(["proselint"] + glob.glob("docs/*.rst") + glob.glob("docs/**/*.rst"))' + sphinx-build -d "{envtmpdir}/doctree" docs "{toxworkdir}/docs_out" --color -b html {posargs} + python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))' [testenv:upgrade] description = upgrade pip/wheels/setuptools to latest passenv = - UPGRADE_ADVISORY + UPGRADE_ADVISORY skip_install = true deps = - black -commands = - python upgrade_wheels.py + black changedir = {toxinidir}/tasks +commands = + python upgrade_wheels.py [testenv:release] description = do a release, required posarg of the version number passenv = - * -basepython = python3.8 + * +basepython = python3.9 deps = - gitpython>=3 - packaging>=17.1 - towncrier>=19.9.0rc1 -commands = - python release.py --version {posargs} + gitpython>=3 + packaging>=17.1 + towncrier>=19.9.0rc1 changedir = {toxinidir}/tasks +commands = + python release.py --version {posargs} [testenv:dev] description = generate a DEV environment usedevelop = true deps = - setuptools_scm[toml]>=3.4 - {[testenv:release]deps} + {[testenv:release]deps} + setuptools_scm[toml]>=3.4 extras = - docs - testing + docs + testing commands = - python -m pip list --format=columns - python -c 'import sys; print(sys.executable)' + python -m pip list --format=columns + python -c 'import sys; print(sys.executable)' [testenv:zipapp] description = generate a zipapp skip_install = true deps = - packaging>=20 + packaging>=20 commands = - python tasks/make_zipapp.py + python tasks/make_zipapp.py [isort] profile = black