summaryrefslogtreecommitdiff
path: root/mdbp/sbuild.py
blob: da0ac4b5588f2ffd74edd658af9931e84bf8f31e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/python3
# SPDX-License-Identifier: MIT
"""mdbp backend using sbuild"""

import argparse
import contextlib
import pathlib
import subprocess
import sys
import typing

from .common import AddSpaceSeparatedValues, buildjson, clean_dir, \
        compute_env, get_dsc, temporary_static_file

PerlValue = typing.Union[None, str, typing.List[typing.Any],
                         typing.Dict[str, typing.Any]]

perl_escape_map = {
    ord("\\"): r"\\", ord('"'): r'\"', ord("@"): r"\@", ord("$"): r"\$",
    ord("\n"): r"\n", ord("\r"): r"\r",
}
def perl_repr(value: PerlValue) -> str:
    """Encode a Python value as a string parseable to Perl."""
    if value is None:
        return "undef"
    if isinstance(value, bool):
        return str(int(value))
    if isinstance(value, str):
        return '"%s"' % value.translate(perl_escape_map)
    if isinstance(value, list):
        return "[%s]" % ",".join(map(perl_repr, value))
    if isinstance(value, dict):
        return "{%s}" % ",".join("%s => %s" % (perl_repr(key),
                                               perl_repr(value))
                                  for key, value in value.items())
    raise TypeError("unexpected value type %r" % type(value))

def perl_conf(conf: typing.Dict[str, PerlValue]) -> str:
    """Turn a mapping into a Perl source assigning variables."""
    return "".join("$%s=%s;\n" % (key, perl_repr(value))
                   for key, value in conf.items()) + "1;\n"

def main() -> None:
    """Entry point for mdbp-sbuild backend"""
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--sbuildopt",
        dest="sbuildopts",
        action="append",
        metavar="OPT",
        default=[],
        help="a custom option to be passed down to sbuild, can be specified "
             "multiple times and mixed with --sbuildopts",
    )
    parser.add_argument(
        "--sbuildopts",
        action=AddSpaceSeparatedValues,
        metavar="OPTS",
        default=[],
        help="space-separated options to be passed down to sbuild, can be "
             "specified multiple times and mixed with --sbuildopt",
    )
    parser.add_argument("buildjson", type=buildjson)
    args = parser.parse_args()
    build = args.buildjson

    if build.get("network") == "disable":
        raise ValueError("disabling network not supported with sbuild")

    sbc = dict(
        nolog=True,
        apt_update=True,
        apt_distupgrade=True,
        distribution=build["distribution"],
        build_arch_all=build.get("type") != "any",
        build_arch_any=build.get("type") != "all",
        bd_uninstallable_explainer=build.get("bd-uninstallable-explainer", ""),
        run_lintian=bool(build.get("lintian", {}).get("run")),
        extra_repositories=build.get("extra_repositories", []),
        build_environment=compute_env(build),
        external_commands={})
    with contextlib.suppress(KeyError):
        sbc["lintian_opts"] = build["lintian"]["options"]
    with contextlib.suppress(KeyError):
        sbc["build_arch"] = build["build_architecture"]
    with contextlib.suppress(KeyError):
        sbc["host_arch"] = build["host_architecture"]
    with contextlib.suppress(KeyError):
        sbc["build_profiles"] = " ".join(build["build_profiles"])
    with contextlib.suppress(KeyError):
        sbc["build_path"] = build["build_path"]
    if build.get("network") == "try-disable":
        sbc["external_commands"]["starting-build-commands"] = \
                ["mv /etc/resolv.conf /etc/resolv.conf.disabled"]
        sbc["external_commands"]["finished-build-commands"] = \
                ["mv /etc/resolv.conf.disabled /etc/resolv.conf"]

    with contextlib.ExitStack() as stack:
        sbuildconf = stack.enter_context(temporary_static_file(perl_conf(sbc)))

        try:
            thing = build["input"]["sourcename"]
            with contextlib.suppress(KeyError):
                thing += "_" + build["input"]["version"]
        except KeyError:
            thing = str(stack.enter_context(get_dsc(build)).absolute())

        ret = subprocess.call(["sbuild", *args.sbuildopts, thing],
                              env=dict(SBUILD_CONFIG=str(sbuildconf),
                                       PATH="/usr/bin:/bin"),
                              cwd=build["output"]["directory"],
                              stdout=None if build["output"].get("log", True)
                                     else subprocess.DEVNULL,
                              stderr=subprocess.STDOUT
                                     if build["output"].get("log", True)
                                     else subprocess.DEVNULL)
        clean_dir(pathlib.Path(build["output"]["directory"]),
                  build["output"].get("artifacts", ["*"]))
        sys.exit(ret)

if __name__ == "__main__":
    main()