Skip to content

Commit 83f0e44

Browse files
committed
tatt: add --enable-prefixes and --disable-prefixes
Two USE flag prefix options, matched the same way as --ignore-prefixes: --enable-prefixes flags are always on, --disable-prefixes flags always off, in every combination and in every mode. They feed force_true/force_false, so the constraint solver never proposes them and the randomized modes never draw them, rather than merely biasing a preference the way --ignore-prefixes does. The profile keeps the last word. A flag it forces on cannot be disabled and one it masks cannot be enabled; either is kept as the profile has it and warned about, since portage would ignore the request anyway. "test" is left to --test alone. A flag matched by both options is contradictory and errors out, as does a package whose REQUIRED_USE admits no combination once the options are applied, previously such a package was dropped from the job script without a word. Relates: #164 Relates: #149 (comment) Signed-off-by: Arthur Zamarin <arthurzam@gentoo.org>
1 parent e7c412f commit 83f0e44

4 files changed

Lines changed: 105 additions & 9 deletions

File tree

‎NEWS.rst‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ pkgdev 0.2.17 (unreleased)
3939
- tatt: fix ebuild ``IUSE`` defaults overriding the profile when deciding a
4040
package's default USE configuration (Arthur Zamarin, #183)
4141

42+
- tatt: add ``--enable-prefixes`` and ``--disable-prefixes``, USE flag prefixes
43+
which are always resp. never enabled, in every combination and every mode.
44+
The profile has the last word: flags it forces on cannot be disabled and
45+
flags it masks cannot be enabled, both kept as the profile has them with a
46+
warning. A package whose ``REQUIRED_USE`` admits no combination once the
47+
options are applied raises an error (Arthur Zamarin, #164)
48+
4249
pkgdev 0.2.16 (2026-07-31)
4350
--------------------------
4451

‎data/share/bash-completion/completions/pkgdev‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ _pkgdev() {
230230
-t --test
231231
-u --use-combos
232232
--ignore-prefixes
233+
--enable-prefixes
234+
--disable-prefixes
233235
--use-default
234236
--use-random
235237
--use-expand-random

‎data/share/zsh/site-functions/_pkgdev‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ case $state in
110110
{'(--test)-t','(-t)--test'}'[run test phase for the packages]' \
111111
{'(--use-combos)-u','(-u)--use-combos'}'[maximal number USE combinations to be tested]:number' \
112112
'--ignore-prefixes[USE flags prefixes that won'\''t be randomized]:prefixes' \
113+
'--enable-prefixes[USE flags prefixes that will always be enabled]:prefixes' \
114+
'--disable-prefixes[USE flags prefixes that will never be enabled]:prefixes' \
113115
'(--use-random --use-expand-random)--use-default[prefer to use default use flags configuration]' \
114116
'(--use-default --use-expand-random)--use-random[turn on random use flags, with default USE_EXPAND]' \
115117
'(--use-default --use-random)--use-expand-random[turn on random use flags, including USE_EXPAND]' \

‎src/pkgdev/scripts/pkgdev_tatt.py‎

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
from importlib.resources import read_text
88
from itertools import islice
99
from pathlib import Path
10+
from typing import NamedTuple
1011

1112
from pkgcore.bugzilla.apikey import BugzillaApiKey
1213
from pkgcore.restrictions import boolean, packages, values
1314
from pkgcore.restrictions.required_use import find_constraint_satisfaction, iter_flags
1415
from pkgcore.util import commandline
1516
from pkgcore.util import packages as pkgutils
1617
from snakeoil.cli import arghparse
18+
from snakeoil.cli.exceptions import UserException
19+
from snakeoil.strings import pluralism
1720

1821
from ..cli import ArgumentParser
1922

@@ -70,6 +73,36 @@
7073
still be changed from defaults.
7174
""",
7275
)
76+
use_opts.add_argument(
77+
"--enable-prefixes",
78+
default=[],
79+
action=arghparse.CommaSeparatedValuesAppend,
80+
help="USE flags prefixes that will always be enabled",
81+
docs="""
82+
Comma separated USE flags prefixes which are always enabled, in every
83+
USE combination and in every mode.
84+
85+
Flags the profile masks cannot be enabled, and are kept disabled with
86+
a warning. ``test`` is never affected, use ``--test`` for it.
87+
""",
88+
)
89+
use_opts.add_argument(
90+
"--disable-prefixes",
91+
default=[],
92+
action=arghparse.CommaSeparatedValuesAppend,
93+
help="USE flags prefixes that will never be enabled",
94+
docs="""
95+
Comma separated USE flags prefixes which are never enabled, in every
96+
USE combination and in every mode. This is useful for expensive or
97+
known broken USE flags such as ``lto`` and ``pgo``.
98+
99+
Flags the profile forces on cannot be disabled, and are kept enabled
100+
with a warning. ``test`` is never affected, use ``--test`` for it.
101+
102+
If ``REQUIRED_USE`` admits no combination once these options are
103+
applied, the package cannot be tested and an error is raised.
104+
""",
105+
)
73106
random_use_opts = use_opts.add_mutually_exclusive_group()
74107
random_use_opts.add_argument(
75108
"--use-default",
@@ -278,13 +311,57 @@ def _groupby_use_expand(
278311
return use_flags, use_expand_dict
279312

280313

281-
def _build_job(namespace, pkg, is_test: bool):
282-
use_expand_prefixes = tuple(s.lower() + "_" for s in namespace.domain.profile.use_expand)
283-
immutable, enabled, _disabled = namespace.domain.get_package_use_unconfigured(pkg)
314+
class UnsatisfiableUse(UserException):
315+
"""No USE combination is testable for a package."""
284316

317+
318+
class _PkgUse(NamedTuple):
319+
"""USE flag state of a package, shared by all of its jobs."""
320+
321+
immutable: frozenset[str]
322+
enabled: frozenset[str]
323+
iuse: frozenset[str]
324+
force_on: frozenset[str]
325+
force_off: frozenset[str]
326+
327+
328+
def _resolve_use(namespace, pkg, err):
329+
"""Determine a package's USE flag state, honoring the prefix options."""
330+
immutable, enabled, masked = namespace.domain.get_package_use_unconfigured(pkg)
285331
iuse = frozenset(pkg.iuse_stripped)
286-
force_true = immutable.union(("test",) if is_test else ())
287-
force_false = ("test",) if not is_test else ()
332+
333+
def matching(prefixes):
334+
# "test" is driven by --test alone, never by the prefix options
335+
return frozenset(use for use in iuse if use.startswith(tuple(prefixes))) - {"test"}
336+
337+
force_on, force_off = matching(namespace.enable_prefixes), matching(namespace.disable_prefixes)
338+
if both := force_on.intersection(force_off):
339+
raise UnsatisfiableUse(
340+
f"{pkg.cpvstr}: USE flag{pluralism(both)} matched by both --enable-prefixes "
341+
f"and --disable-prefixes: {', '.join(sorted(both))}"
342+
)
343+
344+
# the profile has the last word, its forced and masked flags can't be flipped
345+
for option, state, flags, profile_flags in (
346+
("--disable-prefixes", "forced", force_off, immutable),
347+
("--enable-prefixes", "masked", force_on, masked),
348+
):
349+
if conflict := flags.intersection(profile_flags):
350+
err.warn(
351+
f"{pkg.cpvstr}: ignoring {option} for profile {state} USE "
352+
f"flag{pluralism(conflict)}: {', '.join(sorted(conflict))}"
353+
)
354+
return _PkgUse(
355+
immutable, enabled, iuse, force_on.difference(masked), force_off.difference(immutable)
356+
)
357+
358+
359+
def _build_job(namespace, pkg, pkg_use: _PkgUse, is_test: bool):
360+
use_expand_prefixes = tuple(s.lower() + "_" for s in namespace.domain.profile.use_expand)
361+
immutable, enabled, iuse = pkg_use.immutable, pkg_use.enabled, pkg_use.iuse
362+
363+
force_true = immutable.union(pkg_use.force_on, ("test",) if is_test else ())
364+
force_false = pkg_use.force_off.union(("test",) if not is_test else ())
288365

289366
if namespace.random_use == "d":
290367
prefer_true = enabled
@@ -348,19 +425,27 @@ def resolve_unconstrained():
348425
produced += 1
349426
if produced >= wanted:
350427
return
428+
if not produced:
429+
forced = sorted(pkg_use.force_on) + [f"-{use}" for use in sorted(pkg_use.force_off)]
430+
raise UnsatisfiableUse(
431+
f"{pkg.cpvstr}: no USE combination satisfies REQUIRED_USE"
432+
+ (f" with {', '.join(forced)}" if forced else "")
433+
)
351434
# replay the combinations if REQUIRED_USE admits fewer than asked for,
352435
# redrawing the unconstrained flags each pass
353436
if exhausted or not randomize:
354437
return
355438

356439

357-
def _build_jobs(namespace, pkgs):
440+
def _build_jobs(namespace, pkgs, err):
358441
for pkg in pkgs:
359-
for flags in islice(_build_job(namespace, pkg, False), namespace.use_combos):
442+
pkg_use = _resolve_use(namespace, pkg, err)
443+
for flags in islice(_build_job(namespace, pkg, pkg_use, False), namespace.use_combos):
360444
yield pkg.versioned_atom, False, flags
361445

362446
if namespace.test:
363-
yield pkg.versioned_atom, True, next(iter(_build_job(namespace, pkg, True)))
447+
flags = next(iter(_build_job(namespace, pkg, pkg_use, True)))
448+
yield pkg.versioned_atom, True, flags
364449

365450

366451
def _create_config_dir(directory: Path):
@@ -430,7 +515,7 @@ def main(options, out, err):
430515
from jinja2 import Template
431516

432517
script = Template(template, trim_blocks=True, lstrip_blocks=True).render(
433-
jobs=list(_build_jobs(options, pkgs)),
518+
jobs=list(_build_jobs(options, pkgs, err)),
434519
report_file=job_name + ".report",
435520
job_name=job_name,
436521
log_dir=options.logs_dir,

0 commit comments

Comments
 (0)