Skip to content

Commit

Permalink
Merge pull request #27 from noirbizarre/feature/reuse-scripts
Browse files Browse the repository at this point in the history
Feature: reuse PDM scripts
  • Loading branch information
frostming authored Jun 6, 2022
2 parents a4a4a86 + 3af55a7 commit e258a1a
Show file tree
Hide file tree
Showing 11 changed files with 494 additions and 348 deletions.
15 changes: 8 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, "3.10"]
tox-version: ["<4", ">=4.0.0b0"]
python-version: [3.7, 3.8, 3.9, "3.10"]
tox-version: [3, 4]

steps:
- uses: actions/checkout@v3
Expand All @@ -21,12 +21,13 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install "tox${{ matrix.tox-version }}" .
python -m pip install "tox>=${{ matrix.tox-version }}.0.0b0,<${{ matrix.tox-version }}.999" .
- name: Test with tox
run: |
tox --version
pyversion="${{ matrix.python-version }}"
if which tox4 > /dev/null
then
mv pdm.tox4.lock pdm.lock
toxversion="${{ matrix.tox-version }}"
if [[ $toxversion == "4" ]]; then
mv pdm.tox4.lock pdm.lock;
fi
tox -e py${pyversion/./}
tox -e py${pyversion/./}-tox${toxversion}
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ commands =
pytest test/
```

Here is another one installing the `test` dependencies and executing the `test` PDM script

```ini
[tox]
envlist = py3{8,9,10}
isolated_build = True ; This is required for a pyproject.toml based project.

[testenv]
groups = test
commands = test
```

A real-world example can be found at this repository's [tox.ini](https://github.com/pdm-project/tox-pdm/blob/main/tox.ini) and
[GitHub Action workflow](https://github.com/pdm-project/tox-pdm/blob/main/.github/workflows/ci.yml).

Expand All @@ -54,3 +66,4 @@ A real-world example can be found at this repository's [tox.ini](https://github.
1. `pdm` executable must be exposed in `PATH`, if it is not the case, give the absolute path to tox by `tox --pdm <path_to_pdm>`.
2. Make sure you have generated `pdm.lock` before running the test, it will greatly accelerate the testing.
3. If you don't set `skip_install = true`, the current package will be built and installed into the testing environment together with the `dependencies` from `pyproject.toml`.
4. Reuse your PDM scripts to avoid duplication
345 changes: 188 additions & 157 deletions pdm.lock

Large diffs are not rendered by default.

277 changes: 154 additions & 123 deletions pdm.tox4.lock

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pdm = "tox_pdm.plugin"
[project.optional-dependencies]
test = [
"pytest>=6.2",
"coverage>=5.5",
"coverage[toml]>=5.5,<6.4",
]
lint = [
"flake8>=3.8",
Expand All @@ -43,6 +43,22 @@ build-backend = "pdm.pep517.api"
[tool.pdm]
version = {use_scm = true}

[tool.pdm.scripts]
test = "pytest -v tests/"

[tool.pdm.scripts.lint]
shell = """
flake8 tox_pdm
isort --check tox_pdm tests
black --check tox_pdm tests
"""

[tool.pdm.scripts.format]
shell = """
isort tox_pdm tests
black tox_pdm tests
"""

[tool.pytest.ini_options]
filterwarnings = ["ignore::DeprecationWarning"]

Expand Down
3 changes: 3 additions & 0 deletions tests/fixture-project/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ build-backend = "pdm.pep517.api"

[tool]
[tool.pdm]
[tool.pdm.scripts]
lint = "flake8 demo.py"
lint-shell = {shell = "flake8 demo.py"}
71 changes: 56 additions & 15 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,35 @@ def setup_project(tmpdir, tox_config):
)


def test_install_conditional_deps(tmpdir):
def execute_config(tmpdir, config: str):
__tracebackhide__ = True
if IS_TOX_4:
from tox.run import main
from tox.run import run as main
else:
from tox.session import main

test_config = textwrap.dedent(
setup_project(tmpdir, textwrap.dedent(config))

code = -1
with tmpdir.as_cwd():
try:
main([])
except SystemExit as e:
print("e", e)
code = e.code
if code != 0:
pytest.fail(f"non-zero exit code: {code}")

if TOX_VERSION[0] == "4":
package = tmpdir.join(".tox/.pkg/dist/demo-0.1.0.tar.gz")
else:
package = tmpdir.join(".tox/dist/demo-0.1.0.tar.gz")
assert package.exists()


def test_install_conditional_deps(tmpdir):
execute_config(
tmpdir,
"""
[tox]
envlist = django{2,3}
Expand All @@ -53,18 +75,37 @@ def test_install_conditional_deps(tmpdir):
commands =
django-admin --version
flake8 --version
""",
)


def test_use_pdm_scripts(tmpdir):
execute_config(
tmpdir,
"""
[tox]
envlist = py3
passenv = LD_PRELOAD
isolated_build = True
[testenv]
groups = lint
commands = lint
""",
)
setup_project(tmpdir, test_config)
with tmpdir.as_cwd():
try:
main([])
except SystemExit as e:
if e.code != 0:
raise RuntimeError(f"non-zero exit code: {e.code}")

if TOX_VERSION[0] == "4":
package = tmpdir.join(".tox/.pkg/dist/demo-0.1.0.tar.gz")
else:
package = tmpdir.join(".tox/dist/demo-0.1.0.tar.gz")
assert package.exists()

def test_use_pdm_shell_scripts(tmpdir):
execute_config(
tmpdir,
"""
[tox]
envlist = py3
passenv = LD_PRELOAD
isolated_build = True
[testenv]
groups = lint
commands = lint-shell
""",
)
12 changes: 7 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ ignore =
max-line-length = 88

[tox]
envlist = py{38,39,310}, lint
envlist = py3{7,8,9,10}-tox{3,4}, lint
passenv = LD_PRELOAD
isolated_build = True

[testenv]
groups = test
commands = pytest tests/
commands_pre =
tox4: pip install tox>=4.0.0b2
commands =
tox --version
test {posargs}

[testenv:lint]
groups = lint
skip_install = true
commands =
flake8 tox_pdm
black --check tox_pdm
commands = lint
23 changes: 22 additions & 1 deletion tox_pdm/plugin3.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
"""Plugin specification for Tox 3"""
import os
from typing import Any

from tox import action, config, hookimpl
from tox.venv import VirtualEnv

from tox_pdm.utils import setup_env
from .utils import pdm_scripts


def setup_env() -> None:
os.environ.update({"PDM_IGNORE_SAVED_PYTHON": "1", "PDM_USE_VENV": "1"})
old_passenv = os.getenv("TOX_TESTENV_PASSENV")
new_env = ["PDM_*"]
if old_passenv:
new_env.append(old_passenv)
os.environ["TOX_TESTENV_PASSENV"] = " ".join(new_env)


@hookimpl
Expand All @@ -16,6 +26,17 @@ def tox_addoption(parser: config.Parser) -> Any:
parser.add_argument("--pdm", default="pdm", help="The executable path of PDM")


@hookimpl
def tox_configure(config: config.Config):
scripts = pdm_scripts(config.toxinidir)
if scripts:
for cfg in config.envconfigs.values():
cfg.allowlist_externals.append("pdm")
for lineno, cmd in enumerate(cfg.commands):
if cmd[0] in scripts:
cfg.commands[lineno] = ["pdm", "run", *cmd]


@hookimpl
def tox_testenv_install_deps(venv: VirtualEnv, action: action.Action) -> Any:
groups = venv.envconfig.groups or []
Expand Down
19 changes: 19 additions & 0 deletions tox_pdm/plugin4.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
from __future__ import annotations

import typing as t
from pathlib import Path

from tox.config.set_env import SetEnv
from tox.execute.request import StdinSource
from tox.plugin import impl
from tox.tox_env.python.virtual_env.runner import VirtualEnvRunner

from .utils import pdm_scripts

if t.TYPE_CHECKING:
from argparse import ArgumentParser

from tox.execute.api import Execute, Outcome
from tox.tox_env.register import ToxEnvRegister


Expand Down Expand Up @@ -58,3 +62,18 @@ def register_config(self) -> None:
@staticmethod
def id() -> str:
return "pdm"

def execute(
self,
cmd: t.Sequence[Path | str],
stdin: StdinSource,
show: bool | None = None,
cwd: Path | None = None,
run_id: str = "",
executor: Execute | None = None,
) -> Outcome:
scripts = pdm_scripts(self.core["tox_root"])
if scripts:
if cmd[0] in scripts:
cmd = ["pdm", "run", *cmd]
return super().execute(cmd, stdin, show, cwd, run_id, executor)
46 changes: 7 additions & 39 deletions tox_pdm/utils.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,14 @@
from __future__ import annotations

import os
import shutil
from pathlib import Path
from typing import Any

import toml


def setup_env() -> None:
os.environ.update({"PDM_IGNORE_SAVED_PYTHON": "1", "PDM_USE_VENV": "1"})
old_passenv = os.getenv("TOX_TESTENV_PASSENV")
new_env = ["PDM_*"]
if old_passenv:
new_env.append(old_passenv)
os.environ["TOX_TESTENV_PASSENV"] = " ".join(new_env)


def clone_pdm_files(env_path: str | Path, root: str | Path) -> None:
"""Initialize the PDM project for the given VirtualEnv by cloning
the pyproject.toml and pdm.lock files to the venv path.
"""
env_path = Path(env_path)
if not env_path.exists():
env_path.mkdir(parents=True)
root = Path(root)
if not root.joinpath("pyproject.toml").exists():
return
old_pyproject = toml.load(root.joinpath("pyproject.toml").open("r"))
if "name" in old_pyproject.get("project", {}):
del old_pyproject["project"]["name"]
with env_path.joinpath("pyproject.toml").open("w") as f:
toml.dump(old_pyproject, f)

if root.joinpath("pdm.lock").exists():
shutil.copy2(root.joinpath("pdm.lock"), env_path.joinpath("pdm.lock"))


def detect_pdm_files(root: str | Path) -> bool:
root = Path(root)
pyproject = root.joinpath("pyproject.toml")
if not pyproject.exists():
return False
with pyproject.open("r") as f:
toml_data = toml.load(f)
return "project" in toml_data
def pdm_scripts(root: Path) -> dict[str, Any]:
pyproject_toml = root / "pyproject.toml"
if pyproject_toml.exists():
pyproject = toml.load(pyproject_toml.open("r"))
return pyproject.get("tool", {}).get("pdm", {}).get("scripts", {})
return {}

0 comments on commit e258a1a

Please sign in to comment.