Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First implementation of tox_to_nox for tox 4 #687

Merged
merged 7 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
matrix:
os: [ubuntu-20.04, windows-latest, macos-latest]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
tox-version: ["latest", "<4"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -35,7 +36,33 @@ jobs:
run: |
python -m pip install --disable-pip-version-check .
- name: Run tests on ${{ matrix.os }}
run: nox --non-interactive --error-on-missing-interpreter --session "tests-${{ matrix.python-version }}" -- --full-trace
run: nox --non-interactive --error-on-missing-interpreter --session "tests(python='${{ matrix.python-version }}', tox_version='${{ matrix.tox-version }}')" -- --full-trace
- name: Save coverage report
uses: actions/upload-artifact@v3
with:
name: coverage
path: .coverage.*

coverage:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Nox-under-test
run: |
python -m pip install --disable-pip-version-check .
- name: Download individual coverage reports
uses: actions/download-artifact@v3
with:
name: coverage
- name: Display structure of downloaded files
run: ls -aR
- name: Run coverage
run: nox --non-interactive --session "cover"

lint:
runs-on: ubuntu-latest
Expand Down
33 changes: 33 additions & 0 deletions nox/tox4_to_nox.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import nox

{% for envname, envconfig in config.items()|sort: %}
@nox.session({%- if envconfig.base_python %}python='{{envconfig.base_python}}'{%- endif %})
def {{fixname(envname)}}(session):
{%- if envconfig.description != '' %}
"""{{envconfig.description}}"""
{%- endif %}
{%- set envs = envconfig.get('set_env', {}) -%}
{%- for key, value in envs.items()|sort: %}
session.env['{{key}}'] = '{{value}}'
{%- endfor %}

{%- if envconfig.deps %}
session.install({{envconfig.deps}})
{%- endif %}

{%- if not envconfig.skip_install %}
{%- if envconfig.use_develop %}
session.install('-e', '.')
{%- else %}
session.install('.')
{%- endif -%}
{%- endif %}

{%- if envconfig.change_dir %}
session.chdir('{{envconfig.change_dir}}')
{%- endif %}

{%- for command in envconfig.commands %}
session.run({{command}})
{%- endfor %}
{% endfor %}
85 changes: 76 additions & 9 deletions nox/tox_to_nox.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,40 @@
from __future__ import annotations

import argparse
import os
import pkgutil
from collections.abc import Iterator
from typing import Any
import re
from configparser import ConfigParser
from pathlib import Path
from subprocess import check_output
from typing import Any, Iterable

import jinja2
import tox.config
from tox import __version__ as TOX_VERSION

_TEMPLATE = jinja2.Template(
pkgutil.get_data(__name__, "tox_to_nox.jinja2").decode("utf-8"), # type: ignore[union-attr]
extensions=["jinja2.ext.do"],
)
TOX4 = TOX_VERSION[0] == "4"

if TOX4:
_TEMPLATE = jinja2.Template(
pkgutil.get_data(__name__, "tox4_to_nox.jinja2").decode("utf-8"), # type: ignore[union-attr]
extensions=["jinja2.ext.do"],
)
else:
_TEMPLATE = jinja2.Template(
pkgutil.get_data(__name__, "tox_to_nox.jinja2").decode("utf-8"), # type: ignore[union-attr]
extensions=["jinja2.ext.do"],
)

def wrapjoin(seq: Iterator[Any]) -> str:

def wrapjoin(seq: Iterable[Any]) -> str:
"""Wrap each item in single quotes and join them with a comma."""
return ", ".join([f"'{item}'" for item in seq])


def fixname(envname: str) -> str:
"""Replace dashes with underscores and check if the result is a valid identifier."""
envname = envname.replace("-", "_")
envname = envname.replace("-", "_").replace("testenv:", "")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this is needed, and if it would have a negative effect on old tox (tox <= 3)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified purpose.

if not envname.isidentifier():
print(
f"Environment {envname!r} is not a valid nox session name.\n"
Expand All @@ -58,7 +71,61 @@ def main() -> None:

args = parser.parse_args()

config = tox.config.parseconfig([])
if TOX4:
output = check_output(["tox", "config"], text=True)
original_config = ConfigParser()
original_config.read_string(output)
config: dict[str, dict[str, Any]] = {}

for name, section in original_config.items():
if name == "DEFAULT":
continue

config[name] = dict(section)
# Convert set_env from string to dict
set_env = {}
for var in section.get("set_env", "").strip().splitlines():
k, v = var.split("=")
if k not in (
"PYTHONHASHSEED",
"PIP_DISABLE_PIP_VERSION_CHECK",
"PYTHONIOENCODING",
):
set_env[k] = v

config[name]["set_env"] = set_env

config[name]["commands"] = [
wrapjoin(c.split()) for c in section["commands"].strip().splitlines()
]

config[name]["deps"] = wrapjoin(section["deps"].strip().splitlines())

for option in "skip_install", "use_develop":
if section.get(option):
if section[option] == "False":
config[name][option] = False
else:
config[name][option] = True

if os.path.isabs(section["base_python"]) or re.match(
r"py\d+", section["base_python"]
):
impl = (
"python" if section["py_impl"] == "cpython" else section["py_impl"]
)
config[name]["base_python"] = impl + section["py_dot_ver"]

change_dir = Path(section.get("change_dir"))
rel_to_cwd = change_dir.relative_to(Path.cwd())
if str(rel_to_cwd) == ".":
config[name]["change_dir"] = None
else:
config[name]["change_dir"] = rel_to_cwd

else:
config = tox.config.parseconfig([])

output = _TEMPLATE.render(config=config, wrapjoin=wrapjoin, fixname=fixname)

write_output_to_file(output, args.output)
26 changes: 22 additions & 4 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,40 @@
nox.options.sessions.append("conda_tests")


@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"])
def tests(session: nox.Session) -> None:
@nox.session
@nox.parametrize(
"python, tox_version",
[
(python, tox_version)
for python in ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12")
for tox_version in ("latest", "<4")
],
)
def tests(session: nox.Session, tox_version: str) -> None:
"""Run test suite with pytest."""
# Because there is a dependency conflict between
# argcomplete and the latest tox (both depend on
# a different version of importlibmetadata for Py 3.7)
# pip installs tox 3 as the latest one for Py 3.7.
if session.python == "3.7" and tox_version == "latest":
return

session.create_tmp() # Fixes permission errors on Windows
session.install("-r", "requirements-test.txt")
session.install("-e", ".[tox_to_nox]")
if tox_version != "latest":
session.install(f"tox{tox_version}")
session.run(
"pytest",
"--cov=nox",
"--cov-config",
"pyproject.toml",
"--cov-report=",
*session.posargs,
env={"COVERAGE_FILE": f".coverage.{session.python}"},
env={
"COVERAGE_FILE": f".coverage.{session.python}.tox.{tox_version.lstrip('<')}"
},
)
session.notify("cover")


@nox.session(python=["3.7", "3.8", "3.9", "3.10"], venv_backend="conda")
Expand Down
12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies = [
[project.optional-dependencies]
tox_to_nox = [
"jinja2",
"tox<4",
"tox",
]
[project.urls]
bug-tracker = "https://github.com/wntrblm/nox/issues"
Expand All @@ -64,9 +64,6 @@ tox-to-nox = "nox.tox_to_nox:main"
[tool.hatch]
metadata.allow-ambiguous-features = true # disable normalization (tox-to-nox) for back-compat

[tool.ruff]
target-version = "py37"

[tool.ruff.lint]
extend-select = [
"B", # flake8-bugbear
Expand All @@ -85,6 +82,12 @@ ignore = [
"ISC001", # Conflicts with formatter
]

[tool.ruff]
target-version = "py37"

[tool.isort]
profile = "black"

[tool.pytest.ini_options]
minversion = "6.0"
addopts = [ "-ra", "--strict-markers", "--strict-config" ]
Expand All @@ -95,6 +98,7 @@ testpaths = [ "tests" ]

[tool.coverage.run]
branch = true
relative_files = true
omit = [ "nox/_typing.py" ]

[tool.coverage.report]
Expand Down
Loading
Loading