summaryrefslogtreecommitdiff
path: root/floss/build/container-build-image.py
blob: b48d8e50adcfa46407c1df619308ced0668c8c81 (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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3

import argparse
import os
import sys
import subprocess
import time

SRC_MOUNT = "/root/src"


class ContainerImageBuilder:
    """Builds the container image for Floss build environment."""

    def __init__(self, workdir, rootdir, tag, use_docker):
        """ Constructor.

        Args:
            workdir: Working directory for this script. Containerfile should exist here.
            rootdir: Root directory for Bluetooth.
            tag: Label in format |name:version|.
            use_docker: Use docker binary if True (or podman when False).
        """
        self.workdir = workdir
        self.rootdir = rootdir
        (self.name, self.version) = tag.split(':')
        self.build_tag = '{}:{}'.format(self.name, 'buildtemp')
        self.container_name = 'floss-buildtemp'
        self.final_tag = tag
        self.container_binary = 'docker' if use_docker else 'podman'
        self.env = os.environ.copy()

        # Mark dpkg builders for container
        self.env['LIBCHROME_DOCKER'] = '1'
        self.env['MODP_DOCKER'] = '1'

    def run_command(self, target, args, cwd=None, env=None, ignore_rc=False):
        """ Run command and stream the output.
        """
        # Set some defaults
        if not cwd:
            cwd = self.workdir
        if not env:
            env = self.env

        rc = 0
        process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
        while True:
            line = process.stdout.readline()
            print(line.decode('utf-8'), end="")
            if not line:
                rc = process.poll()
                if rc is not None:
                    break

                time.sleep(0.1)

        if rc != 0 and not ignore_rc:
            raise Exception("{} failed. Return code is {}".format(target, rc))

    def _container_build(self):
        self.run_command(self.container_binary + ' build', [self.container_binary, 'build', '-t', self.build_tag, '.'])

    def _build_dpkg_and_commit(self):
        # Try to remove any previous instance of the container that may be
        # running if this script didn't complete cleanly last time.
        self.run_command(self.container_binary + ' stop', [self.container_binary, 'stop', '-t', '1', self.container_name], ignore_rc=True)
        self.run_command(self.container_binary + ' rm', [self.container_binary, 'rm', self.container_name], ignore_rc=True)

        # Runs never terminating application on the newly built image in detached mode
        mount_str = 'type=bind,src={},dst={},readonly'.format(self.rootdir, SRC_MOUNT)
        self.run_command(self.container_binary + ' run', [
            self.container_binary, 'run', '--name', self.container_name, '--mount', mount_str, '-d', self.build_tag, 'tail', '-f',
            '/dev/null'
        ])

        commands = [
            # Create the output directories
            ['mkdir', '-p', '/tmp/libchrome', '/tmp/modpb64'],

            # Run the dpkg builder for modp_b64
            [f'{SRC_MOUNT}/system/build/dpkg/modp_b64/gen-src-pkg.sh', '/tmp/modpb64'],

            # Install modp_b64 since libchrome depends on it
            ['find', '/tmp/modpb64', '-name', 'modp*.deb', '-exec', 'dpkg', '-i', '{}', '+'],

            # Run the dpkg builder for libchrome
            [f'{SRC_MOUNT}/system/build/dpkg/libchrome/gen-src-pkg.sh', '/tmp/libchrome'],

            # Install libchrome.
            ['find', '/tmp/libchrome', '-name', 'libchrome_*.deb', '-exec', 'dpkg', '-i', '{}', '+'],

            # Run the dpkg builder for sysprop
            [f'{SRC_MOUNT}/system/build/dpkg/sysprop/gen-src-pkg.sh', '/tmp/sysprop'],

            # Install sysprop.
            ['find', '/tmp/sysprop', '-name', 'sysprop_*.deb', '-exec', 'dpkg', '-i', '{}', '+'],

            # Delete intermediate files
            ['rm', '-rf', '/tmp/libchrome', '/tmp/modpb64', '/tmp/sysprop'],
        ]

        try:
            # Run commands in container first to install everything.
            for i, cmd in enumerate(commands):
                self.run_command(self.container_binary + ' exec #{}'.format(i), [self.container_binary, 'exec', '-it', self.container_name] + cmd)

            # Commit changes into the final tag name
            self.run_command(self.container_binary + ' commit', [self.container_binary, 'commit', self.container_name, self.final_tag])
        finally:
            # Stop running the container and remove it
            self.run_command(self.container_binary + ' stop', [self.container_binary, 'stop', '-t', '1', self.container_name])
            self.run_command(self.container_binary + ' rm', [self.container_binary, 'rm', self.container_name])

    def _check_container_runnable(self):
        try:
            subprocess.check_output([self.container_binary, 'ps'], stderr=subprocess.STDOUT)
        except subprocess.CalledProcessError as err:
            if 'denied' in err.output.decode('utf-8'):
                print('Run script as sudo')
            else:
                print('Unexpected error: {}'.format(err.output.decode('utf-8')))

            return False

        # No exception means container is ok
        return True

    def build(self):
        if not self._check_container_runnable():
            return

        # First build the container image
        self._container_build()

        # Then build libchrome and modp-b64 inside the container image and
        # install them. Commit those changes to the final label.
        self._build_dpkg_and_commit()


def main():
    parser = argparse.ArgumentParser(description='Build container image for Floss build environment.')
    parser.add_argument('--tag', required=True, help='Tag for container image. i.e. floss:latest')
    parser.add_argument('--use-docker', action='store_true', default=False, help='Use flag to use Docker to build Floss. Defaults to using podman.')
    args = parser.parse_args()

    # cwd should be set to same directory as this script (that's where
    # Dockerfile is kept).
    workdir = os.path.dirname(os.path.abspath(sys.argv[0]))
    rootdir = os.path.abspath(os.path.join(workdir, '../..'))

    # Build the container image
    pib = ContainerImageBuilder(workdir, rootdir, args.tag, args.use_docker)
    pib.build()


if __name__ == '__main__':
    main()