Files
2026-10-31 18:07:56 -03:00

115 lines
3.5 KiB
Python

# SPDX-License-Identifier: GPL-3.0-or-later
"""Run a build with a temporary apt-cacher-ng process and persistent cache."""
import errno
import os
import random
import signal
import socket
import subprocess
import sys
import tempfile
import time
from pathlib import Path
WORK = Path("/work")
BINARY = Path("/usr/sbin/apt-cacher-ng")
CA_FILE = Path("/etc/ssl/certs/ca-certificates.crt")
def stop(process):
if process is None or process.poll() is not None:
return
process.terminate()
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
def wait_ready(process, port, log):
deadline = time.monotonic() + 10
while time.monotonic() < deadline:
if process.poll() is not None:
raise RuntimeError(
"apt-cacher-ng exited during startup. Inspect " + str(log)
)
try:
with socket.create_connection(("127.0.0.1", port), timeout=0.2):
return
except OSError:
time.sleep(0.05)
raise RuntimeError("apt-cacher-ng did not start. Inspect " + str(log))
def cache_port():
# apt-cacher-ng 3.7.5 requires listener ports below 32768.
for port in random.SystemRandom().sample(range(16000, 32000), 64):
with socket.socket() as reservation:
try:
reservation.bind(("127.0.0.1", port))
except OSError as error:
if error.errno == errno.EADDRINUSE:
continue
raise
return port
raise RuntimeError("No free loopback port for apt-cacher-ng.")
def run_cached(command):
if not BINARY.is_file():
raise FileNotFoundError("Run tools/plasma-next setup to install apt-cacher-ng.")
cache = WORK / "apt-cache"
logs = WORK / "records/apt-cache"
for path in (cache, logs):
if path.is_symlink():
raise ValueError("Cache directories must not be symlinks: " + str(path))
path.mkdir(parents=True, exist_ok=True)
port = cache_port()
proxy = None
child = None
log = logs / "daemon.log"
with tempfile.TemporaryDirectory(prefix="apt-cache-", dir=WORK / "tmp") as temp:
config = Path(temp) / "acng.conf"
config.write_text(
f"CacheDir: {cache}\n"
f"LogDir: {logs}\n"
f"Port: {port}\n"
"BindAddress: 127.0.0.1\n"
"ForeGround: 1\n"
"AllowUserPorts: 80 443\n"
f"SocketPath: {Path(temp) / 'cache.sock'}\n"
f"CAfile: {CA_FILE}\n"
)
with log.open("ab") as output:
try:
proxy = subprocess.Popen(
[str(BINARY), "-c", temp],
stdout=output,
stderr=subprocess.STDOUT,
)
wait_ready(proxy, port, log)
env = {
**os.environ,
"PLASMA_NEXT_APT_CACHE_URL": f"http://127.0.0.1:{port}",
}
child = subprocess.Popen(command, env=env)
status = child.wait()
return status if status >= 0 else 128 - status
finally:
stop(child)
stop(proxy)
def interrupted(signum, frame):
raise SystemExit(128 + signum)
if __name__ == "__main__":
if len(sys.argv) < 2:
raise SystemExit("Supply the build command to run.")
signal.signal(signal.SIGTERM, interrupted)
signal.signal(signal.SIGINT, interrupted)
raise SystemExit(run_cached(sys.argv[1:]))