summaryrefslogtreecommitdiff
path: root/build.py
blob: 9bfa52c9ac8093dd7d8e6aa784e22b4a6c02a07f (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
123
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0+

import argparse
import collections
import contextlib
import datetime
import lzma
import os
import os.path
import sqlite3
import subprocess
import time

from common import decompress_stream, yield_lines, yield_chunks

def scan_log_status(filelike):
    it = yield_chunks(filelike)
    it = decompress_stream(it, lzma.LZMADecompressor())
    it = yield_lines(it)
    last_lines = collections.deque(it, 25)
    status = [l.split(b':', 1)[1].strip()
              for l in last_lines if l.startswith(b"Status:")]
    if status:
        return status[0].decode("ascii")
    return "unknown"


def do_build(source, version, architecture, server):
    now = datetime.datetime.utcnow().replace(microsecond=0)
    logtarget = "%s_%s_%s_%s.log.xz" % (source, version, architecture,
                                        now.strftime("%Y%m%d%H%M%S"))
    cmdline = ["ssh", server, "sh", "/dev/stdin", architecture,
               "%s_%s" % (source, version)]
    logname = os.path.join("logs", logtarget)
    with open(logname, "w+b") as output:
        with open("build.sh", "rb") as instructions:
            code = subprocess.call(cmdline, stdin=instructions, stdout=output)
        output.seek(0)
        status = scan_log_status(output)
    if code == 255:
        os.unlink(logname)
        time.sleep(300)
        raise RuntimeError("ssh failed")
    print("status %s code %d" % (status, code))
    return (now, code == 0, logtarget, status == "given-back")

def get_build_arch():
    return subprocess.check_output(["dpkg", "--print-architecture"],
                                   encoding="ascii").strip()

def main():
    argp = argparse.ArgumentParser()
    argp.add_argument("server", help="machine to build on")
    argp.add_argument("--buildarch", help="build architecture of the server")
    args = argp.parse_args()
    buildarch = args.buildarch or get_build_arch()
    db = sqlite3.connect("db", detect_types=sqlite3.PARSE_DECLTYPES)
    with contextlib.closing(db.cursor()) as cur:
        cur.execute("BEGIN IMMEDIATE;")
        cur.execute("""
            SELECT d.source, d.version, d.hostarch, r.id
                FROM depstate AS d
                    JOIN buildrequests AS r
                        ON ? = ifnull(r.buildarch, d.buildarch)
                            AND d.hostarch = ifnull(r.hostarch, d.hostarch)
                            AND d.source = r.source
                    JOIN depcheck
                        ON d.hostarch = depcheck.architecture
                WHERE d.satisfiable = 1 AND depcheck.giveback = 0
                    AND NOT EXISTS (SELECT 1 FROM building
                        WHERE d.source = building.source
                            OR (? = building.buildarch AND
                                d.hostarch = building.hostarch))
                ORDER BY r.priority DESC, r.requesttime ASC, random()
                LIMIT 1;""", (buildarch, buildarch))
        row = cur.fetchone()
        if not row:
            cur.execute("""
                SELECT source, version, depstate.hostarch, NULL
                    FROM depstate JOIN depcheck
                        ON depstate.hostarch = depcheck.architecture
                    WHERE depstate.buildarch = ? AND satisfiable = 1
                        AND giveback = 0
                    ORDER BY random() LIMIT 1;""", (buildarch,))
            row = cur.fetchone()
        if not row:
            cur.execute("ROLLBACK;")
            print("no package satisfiable")
            time.sleep(60)
            return
        source, version, hostarch, requestid = row
        cur.execute("""INSERT INTO building (source, buildarch, hostarch, pid)
                           VALUES (?, ?, ?, ?);""",
                    (source, buildarch, hostarch, os.getpid()))
        cur.execute("COMMIT;")
    try:
        print("building %s_%s for %s%s" %
              (source, version, hostarch,
               "" if requestid is None else " (request %d)" % requestid))
        timestamp, success, filename, giveback = \
            do_build(source, version, hostarch, args.server)
        with contextlib.closing(db.cursor()) as cur:
            cur.execute("""INSERT INTO builds
                            (source, version, buildarch, hostarch, success,
                             starttime, filename)
                            VALUES (?, ?, ?, ?, ?, ?, ?);""",
                        (source, version, buildarch, hostarch, success,
                         timestamp, filename))
            if requestid is not None:
                cur.execute("DELETE FROM buildrequests WHERE id = ?;",
                            (requestid,))
            if giveback:
                cur.execute("""UPDATE depcheck SET giveback = 1
                                WHERE architecture = ?;""",
                            (hostarch,))
    finally:
        with contextlib.closing(db.cursor()) as cur:
            cur.execute("DELETE FROM building WHERE pid = ?;", (os.getpid(),))
        db.commit()

if __name__ == "__main__":
    main()