mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
The nocheck markers were written as long single lines; ruff splits the call across three lines and leaves the marker behind the closing paren. Committing the formatter's own output keeps the tree and the history in step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
159 lines
4.6 KiB
Python
Executable File
159 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
from utils.update.collection_source import (
|
|
RATIO,
|
|
SOURCES,
|
|
WINDOW,
|
|
append_sample,
|
|
decide,
|
|
load_history,
|
|
)
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
SETTING = "INFINITO_ANSIBLE_COLLECTIONS_SOURCE"
|
|
REQUIREMENTS = {
|
|
"galaxy": Path("requirements/requirements.galaxy.yml"),
|
|
"git": Path("requirements/requirements.git.yml"),
|
|
}
|
|
|
|
|
|
def read_preferred(env_file: Path) -> str:
|
|
"""The source currently tried first, as declared in default.env.
|
|
|
|
Args:
|
|
env_file: the default.env holding the setting.
|
|
"""
|
|
match = re.search(
|
|
rf"^{SETTING}=(\S+)\s*$",
|
|
env_file.read_text(
|
|
encoding="utf-8"
|
|
), # nocheck: cache-read -- write_preferred rewrites this file in the same run
|
|
re.MULTILINE,
|
|
)
|
|
if match is None:
|
|
raise SystemExit(f"{SETTING} is not declared in {env_file}")
|
|
return match.group(1)
|
|
|
|
|
|
def write_preferred(env_file: Path, source: str) -> None:
|
|
"""Point the setting at SOURCE.
|
|
|
|
Args:
|
|
env_file: the default.env holding the setting.
|
|
source: the source to try first from now on.
|
|
"""
|
|
text = env_file.read_text(
|
|
encoding="utf-8"
|
|
) # nocheck: cache-read -- rewritten on the next line
|
|
env_file.write_text(
|
|
re.sub(rf"^{SETTING}=\S+$", f"{SETTING}={source}", text, flags=re.MULTILINE),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def measure(repo_root: Path, requirements: Path, timeout: int) -> float | None:
|
|
"""Seconds a cold install from REQUIREMENTS takes, or None when it fails.
|
|
|
|
Args:
|
|
repo_root: repository root the requirements path is relative to.
|
|
requirements: the requirements file to install from.
|
|
timeout: seconds after which the install counts as failed.
|
|
"""
|
|
with TemporaryDirectory() as target:
|
|
started = time.monotonic()
|
|
try:
|
|
completed = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"ansible.cli.galaxy",
|
|
"collection",
|
|
"install",
|
|
"-r",
|
|
str(repo_root / requirements),
|
|
"-p",
|
|
target,
|
|
"--force-with-deps",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
check=False,
|
|
)
|
|
except subprocess.TimeoutExpired:
|
|
return None
|
|
elapsed = time.monotonic() - started
|
|
return elapsed if completed.returncode == 0 else None
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
"Time both collection sources, record the sample, and retune the "
|
|
"preferred default when one source is durably slower."
|
|
)
|
|
)
|
|
parser.add_argument("--repo-root", type=Path, default=PROJECT_ROOT)
|
|
parser.add_argument(
|
|
"--history",
|
|
type=Path,
|
|
default=None,
|
|
help="JSON file holding the rolling window of samples",
|
|
)
|
|
parser.add_argument(
|
|
"--timeout",
|
|
type=int,
|
|
default=900,
|
|
help="Seconds after which one install counts as failed",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
repo_root = args.repo_root.resolve()
|
|
env_file = repo_root / "default.env"
|
|
history_file = (
|
|
args.history or repo_root / "build" / "collection-source-history.json"
|
|
)
|
|
|
|
sample: dict[str, float | None] = {}
|
|
for source in SOURCES:
|
|
seconds = measure(repo_root, REQUIREMENTS[source], args.timeout)
|
|
sample[source] = seconds
|
|
print(f"{source}: {'failed' if seconds is None else f'{seconds:.1f}s'}")
|
|
|
|
history = append_sample(load_history(history_file), sample)
|
|
history_file.parent.mkdir(parents=True, exist_ok=True)
|
|
history_file.write_text(json.dumps(history, indent=2), encoding="utf-8")
|
|
|
|
preferred = read_preferred(env_file)
|
|
verdict = decide(history, preferred)
|
|
print(
|
|
f"samples: {verdict.samples}/{WINDOW}, "
|
|
f"medians: {verdict.medians}, preferred: {verdict.preferred}"
|
|
)
|
|
|
|
if not verdict.changed:
|
|
print("Preferred collection source stays unchanged.")
|
|
return 0
|
|
|
|
write_preferred(env_file, verdict.proposed)
|
|
print(
|
|
f"Preferred collection source {verdict.preferred} -> {verdict.proposed}: "
|
|
f"its median is at least {RATIO}x the other's over {WINDOW} samples."
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|