summaryrefslogtreecommitdiff
path: root/src/tox/session/cmd/run/parallel.py
blob: c1cd6680f34291d44cf9e8bd18fc13af64bbefb6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
"""
Run tox environments in parallel.
"""
from __future__ import annotations

import logging
from argparse import ArgumentParser, ArgumentTypeError

from tox.config.cli.parser import ToxParser
from tox.plugin import impl
from tox.session.state import State
from tox.util.cpu import auto_detect_cpus

from ...env_select import CliEnv, register_env_select_flags
from .common import env_run_create_flags, execute

logger = logging.getLogger(__name__)

ENV_VAR_KEY = "TOX_PARALLEL_ENV"
OFF_VALUE = 0
DEFAULT_PARALLEL = OFF_VALUE


@impl
def tox_add_option(parser: ToxParser) -> None:
    our = parser.add_command("run-parallel", ["p"], "run environments in parallel", run_parallel)
    register_env_select_flags(our, default=CliEnv())
    env_run_create_flags(our, mode="run-parallel")
    parallel_flags(our, default_parallel=auto_detect_cpus())


def parse_num_processes(str_value: str) -> int | None:
    if str_value == "all":
        return None
    if str_value == "auto":
        return auto_detect_cpus()
    try:
        value = int(str_value)
    except ValueError as exc:
        raise ArgumentTypeError(f"value must be a positive number, is {str_value!r}") from exc
    if value < 0:
        raise ArgumentTypeError(f"value must be positive, is {value!r}")
    return value


def parallel_flags(our: ArgumentParser, default_parallel: int, no_args: bool = False) -> None:
    our.add_argument(
        "-p",
        "--parallel",
        dest="parallel",
        help="run tox environments in parallel, the argument controls limit: all,"
        " auto - cpu count, some positive number, zero is turn off",
        action="store",
        type=parse_num_processes,  # type: ignore # nargs confuses it
        default=default_parallel,
        metavar="VAL",
        **({"nargs": "?"} if no_args else {}),  # type: ignore # type checker can't unroll it
    )
    our.add_argument(
        "-o",
        "--parallel-live",
        action="store_true",
        dest="parallel_live",
        help="connect to stdout while running environments",
    )
    our.add_argument(
        "--parallel-no-spinner",
        action="store_true",
        dest="parallel_no_spinner",
        help="do not show the spinner",
    )


def run_parallel(state: State) -> int:
    """here we'll just start parallel sub-processes"""
    option = state.conf.options
    return execute(
        state,
        max_workers=option.parallel,
        has_spinner=option.parallel_no_spinner is False and option.parallel_live is False,
        live=option.parallel_live,
    )