#!/usr/bin/python3 # SPDX-License-Identifier: MIT """mdbp backend using pbuilder""" import argparse import contextlib import os import pathlib import shlex import subprocess import sys import tempfile import typing from .common import AddSpaceSeparatedValues, buildjson, clean_dir, \ compute_env, get_dsc, make_option, profile_option, \ temporary_static_file def find_basetgz(distribution: str, basedir: str = "/var/cache/pbuiler") -> typing.Optional[str]: """Locate a pbuilder base.tgz for the given distribution.""" if distribution in ("sid", "unstable"): return None for pat in ("/%s-base.tgz", "/%s.tgz"): basetgz = basedir + pat % distribution if pathlib.Path(basetgz).is_file(): return basetgz raise ValueError("unsupported distribution %s" % distribution) @contextlib.contextmanager def hookdir() -> typing.Iterator[ typing.Tuple[pathlib.Path, typing.Callable[[str, str], None]] ]: """A context manager that returns a pair of a hook directory suitable for pbuilder and a function for adding hooks to the directory. """ with tempfile.TemporaryDirectory() as tdir: directory = pathlib.Path(tdir) counter = 0 def addhook(key: str, content: str, prio: str = "50") -> None: nonlocal counter hookpath = directory / ("%s%s%05X" % (key, prio, counter)) hookpath.write_text(content) hookpath.chmod(0o755) yield (directory, addhook) def main() -> None: """Entry point for mdbp-pbuilder backend""" parser = argparse.ArgumentParser() parser.add_argument( "--pbuilderopt", dest="pbuilderopts", action="append", metavar="OPT", default=[], help="a custom option to be passed down to pbuilder, can be specified " "multiple times and mixed with --pbuilderopts", ) parser.add_argument( "--pbuilderopts", action=AddSpaceSeparatedValues, metavar="OPTS", default=[], help="space-separated options to be passed down to pbuilder, can be " "specified multiple times and mixed with --pbuilderopt", ) parser.add_argument("buildjson", type=buildjson) args = parser.parse_args() build = args.buildjson if "sourcename" in build["input"]: raise ValueError("building a source package by name is not supported") enablelog = build["output"].get("log", True) if "bd-uninstallable-explainer" in build: raise ValueError("bd-uninstallable-explainer %r not supported" % build["bd-uinstallable-explainer"]) cmd = [ *([] if os.getuid() == 0 else ["sudo", "-E", "--"]), "/usr/sbin/pbuilder", "build", "--no-source-only-changes", *make_option("--basetgz", find_basetgz(build["distribution"])), *make_option("--architecture", build.get("build_architecture")), *make_option("--host-arch", build.get("host_architecture")), *make_option("--othermirror", "|".join(build.get("extrarepositories", ()))), *make_option("--use-network", {"enable": "yes", "try-enable": "yes", "disable": "no", "try-disable": "no"}.get(build.get("network"))), *dict(any=["--binary-arch"], all=["--binary-indep"], binary=["--debbuildopts", "-b"])[ build.get("type", "binary")], *profile_option(build, "--profiles"), "--buildresult", build["output"]["directory"], *([] if enablelog else ["--loglevel", "E"]), ] apt_get = ["apt-get", "-oAPT::Keep-Downloaded-Path=false", "--yes"] with contextlib.ExitStack() as stack: if build.get("build_path"): pbuilderrc = "BUILDDIR=%s\n" % shlex.quote(build["build_path"]) cmd.extend([ "--configfile", str(stack.enter_context(temporary_static_file(pbuilderrc))), ]) hookdirname, addhook = stack.enter_context(hookdir()) addhook( "D", """#/bin/sh set -e apt-get --error-on=any update %s dist-upgrade """ % shlex.join(apt_get), ) if build.get("lintian", {}).get("run", False): addhook( "B", """#!/bin/sh set -e %s install lintian runuser -u pbuilder -- lintian %s "${BUILDDIR:-/tmp/buildd}"/*.changes """ % ( shlex.join(apt_get), shlex.join(build["lintian"].get("options", [])), ), ) cmd.extend(["--hookdir", str(hookdirname), *args.pbuilderopts, str(stack.enter_context(get_dsc(build)))]) ret = subprocess.call(cmd, env=compute_env(build), stdout=None if enablelog else subprocess.DEVNULL, stderr=subprocess.STDOUT if enablelog else subprocess.DEVNULL) clean_dir(pathlib.Path(build["output"]["directory"]), build["output"].get("artifacts", ["*"])) sys.exit(ret) if __name__ == "__main__": main()