115 lines
3.5 KiB
Python
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:]))
|