Skip to content

Commit 45c01f7

Browse files
committed
tatt: don't repeat a USE combination already emitted
The replay loop counted runs produced, not distinct combinations. But find_constraint_satisfaction returns the same solution set on every pass, only the flags kept out of the problem are redrawn, so a package with little to redraw simply replayed itself. -u 6 on dev-libs/nspr, whose only free flag is debug, emitted 6 runs covering its 2 real combinations: four byte-identical rebuilds of something already tested. Same for x11-misc/sddm. Track the combinations already emitted, keyed on the enabled IUSE flags rather than the rendered string, whose set iteration order is no identity, and skip repeats. That alone would spin forever once the space is exhausted, as the old exit was reaching --use-combos and nothing is being produced any more, so bound the search by what a replay can actually reach: the solver's own solution count, doubled per redrawn flag. nspr now stops at 2 rather than at an arbitrary cutoff. That bound over-estimates when REQUIRED_USE names a flag outside IUSE, as distinct solutions then collapse into one combination and it is never reached, so keep a cap on consecutive passes that turn up nothing new. It is sampling, so a package can occasionally come up one or two short of its full space, over 200 seeds on media-libs/opusfile (16 combinations) 20 passes reached all 16 in 191 of them, never below 15, but it never repeats. Relates: #164 Relates: #149 (comment) Signed-off-by: Arthur Zamarin <arthurzam@gentoo.org>
1 parent 83f0e44 commit 45c01f7

2 files changed

Lines changed: 43 additions & 10 deletions

File tree

‎NEWS.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ pkgdev 0.2.17 (unreleased)
4646
warning. A package whose ``REQUIRED_USE`` admits no combination once the
4747
options are applied raises an error (Arthur Zamarin, #164)
4848

49+
- tatt: ``--use-combos`` no longer repeats a USE combination it already
50+
emitted. A package with little to vary is now tested fewer times instead of
51+
rebuilding the same thing over and over (Arthur Zamarin, #164)
52+
4953
pkgdev 0.2.16 (2026-07-31)
5054
--------------------------
5155

‎src/pkgdev/scripts/pkgdev_tatt.py‎

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@
214214
""",
215215
)
216216

217+
# consecutive passes turning up no new USE combination before giving up on
218+
# reaching --use-combos, so an unlucky draw doesn't cut the search short
219+
_DRY_PASSES = 20
220+
217221
portage_config = Path("/etc/portage")
218222
portage_accept_keywords = portage_config / "package.accept_keywords"
219223
portage_package_use = portage_config / "package.use"
@@ -388,31 +392,51 @@ def _build_job(namespace, pkg, pkg_use: _PkgUse, is_test: bool):
388392
randomize = namespace.random_use in "rR"
389393
keep_preferred = ignore_prefixes if randomize else ()
390394

395+
def redrawn(flag: str):
396+
"""Whether a flag out of the problem takes a new value on every pass"""
397+
return (
398+
randomize
399+
and flag not in force_true
400+
and flag not in force_false
401+
and not flag.startswith(keep_preferred)
402+
)
403+
391404
def resolve_unconstrained():
392405
"""Values for the flags kept out of the problem"""
393406
for flag in unconstrained:
394407
if flag in force_true:
395408
yield flag, True
396409
elif flag in force_false:
397410
yield flag, False
398-
elif randomize and not flag.startswith(keep_preferred):
411+
elif redrawn(flag):
399412
yield flag, random.choice((True, False))
400413
else:
401414
yield flag, flag in prefer_true
402415

403416
wanted = max(namespace.use_combos, 1)
404-
produced = 0
405-
while produced < wanted:
406-
exhausted = True
417+
# the solver repeats itself every pass, so all a replay can vary is the
418+
# redrawn flags. Refined to the real count once a pass has been solved
419+
limit = wanted
420+
seen: set[frozenset[str]] = set()
421+
dry_passes = 0
422+
while len(seen) < min(wanted, limit):
423+
fresh = solutions = 0
407424
for solution in find_constraint_satisfaction(
408425
pkg.required_use,
409426
constrained.union(immutable),
410427
force_true,
411428
force_false,
412429
prefer_true,
413430
):
414-
exhausted = False
431+
solutions += 1
415432
solution.update(resolve_unconstrained())
433+
# what is actually tested, so a repeat is a wasted rebuild
434+
combo = frozenset(flag for flag, state in solution.items() if state and flag in iuse)
435+
if combo in seen:
436+
continue
437+
seen.add(combo)
438+
fresh += 1
439+
416440
use_flags, use_expand = _groupby_use_expand(
417441
solution, use_expand_prefixes, enabled, iuse
418442
)
@@ -422,18 +446,23 @@ def resolve_unconstrained():
422446
+ " "
423447
+ " ".join(f"{var.upper()}: {' '.join(vals)}" for var, vals in use_expand.items())
424448
)
425-
produced += 1
426-
if produced >= wanted:
449+
if len(seen) >= wanted:
427450
return
428-
if not produced:
451+
if not seen:
429452
forced = sorted(pkg_use.force_on) + [f"-{use}" for use in sorted(pkg_use.force_off)]
430453
raise UnsatisfiableUse(
431454
f"{pkg.cpvstr}: no USE combination satisfies REQUIRED_USE"
432455
+ (f" with {', '.join(forced)}" if forced else "")
433456
)
434457
# replay the combinations if REQUIRED_USE admits fewer than asked for,
435-
# redrawing the unconstrained flags each pass
436-
if exhausted or not randomize:
458+
# redrawing the flags out of the problem each pass
459+
if not randomize:
460+
return
461+
limit = solutions * 2 ** sum(map(redrawn, unconstrained))
462+
# a package can still run out earlier than that, when REQUIRED_USE names
463+
# a flag outside IUSE and distinct solutions collapse into one combination
464+
dry_passes = 0 if fresh else dry_passes + 1
465+
if dry_passes >= _DRY_PASSES:
437466
return
438467

439468

0 commit comments

Comments
 (0)