• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3import argparse
4import os
5import sys
6import subprocess
7import time
8
9SRC_MOUNT = "/root/src"
10
11
12class ContainerImageBuilder:
13    """Builds the container image for Floss build environment."""
14
15    def __init__(self, workdir, rootdir, tag, use_docker):
16        """ Constructor.
17
18        Args:
19            workdir: Working directory for this script. Containerfile should exist here.
20            rootdir: Root directory for Bluetooth.
21            tag: Label in format |name:version|.
22            use_docker: Use docker binary if True (or podman when False).
23        """
24        self.workdir = workdir
25        self.rootdir = rootdir
26        (self.name, self.version) = tag.split(':')
27        self.build_tag = '{}:{}'.format(self.name, 'buildtemp')
28        self.container_name = 'floss-buildtemp'
29        self.final_tag = tag
30        self.container_binary = 'docker' if use_docker else 'podman'
31        self.env = os.environ.copy()
32
33        # Mark dpkg builders for container
34        self.env['LIBCHROME_DOCKER'] = '1'
35        self.env['MODP_DOCKER'] = '1'
36
37    def run_command(self, target, args, cwd=None, env=None, ignore_rc=False):
38        """ Run command and stream the output.
39        """
40        # Set some defaults
41        if not cwd:
42            cwd = self.workdir
43        if not env:
44            env = self.env
45
46        rc = 0
47        process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
48        while True:
49            line = process.stdout.readline()
50            print(line.decode('utf-8'), end="")
51            if not line:
52                rc = process.poll()
53                if rc is not None:
54                    break
55
56                time.sleep(0.1)
57
58        if rc != 0 and not ignore_rc:
59            raise Exception("{} failed. Return code is {}".format(target, rc))
60
61    def _container_build(self):
62        self.run_command(self.container_binary + ' build', [self.container_binary, 'build', '-t', self.build_tag, '.'])
63
64    def _build_dpkg_and_commit(self):
65        # Try to remove any previous instance of the container that may be
66        # running if this script didn't complete cleanly last time.
67        self.run_command(self.container_binary + ' stop', [self.container_binary, 'stop', '-t', '1', self.container_name], ignore_rc=True)
68        self.run_command(self.container_binary + ' rm', [self.container_binary, 'rm', self.container_name], ignore_rc=True)
69
70        # Runs never terminating application on the newly built image in detached mode
71        mount_str = 'type=bind,src={},dst={},readonly'.format(self.rootdir, SRC_MOUNT)
72        self.run_command(self.container_binary + ' run', [
73            self.container_binary, 'run', '--name', self.container_name, '--mount', mount_str, '-d', self.build_tag, 'tail', '-f',
74            '/dev/null'
75        ])
76
77        commands = [
78            # Create the output directories
79            ['mkdir', '-p', '/tmp/libchrome', '/tmp/modpb64'],
80
81            # Run the dpkg builder for modp_b64
82            [f'{SRC_MOUNT}/system/build/dpkg/modp_b64/gen-src-pkg.sh', '/tmp/modpb64'],
83
84            # Install modp_b64 since libchrome depends on it
85            ['find', '/tmp/modpb64', '-name', 'modp*.deb', '-exec', 'dpkg', '-i', '{}', '+'],
86
87            # Run the dpkg builder for libchrome
88            [f'{SRC_MOUNT}/system/build/dpkg/libchrome/gen-src-pkg.sh', '/tmp/libchrome'],
89
90            # Install libchrome.
91            ['find', '/tmp/libchrome', '-name', 'libchrome_*.deb', '-exec', 'dpkg', '-i', '{}', '+'],
92
93            # Delete intermediate files
94            ['rm', '-rf', '/tmp/libchrome', '/tmp/modpb64'],
95        ]
96
97        try:
98            # Run commands in container first to install everything.
99            for i, cmd in enumerate(commands):
100                self.run_command(self.container_binary + ' exec #{}'.format(i), [self.container_binary, 'exec', '-it', self.container_name] + cmd)
101
102            # Commit changes into the final tag name
103            self.run_command(self.container_binary + ' commit', [self.container_binary, 'commit', self.container_name, self.final_tag])
104        finally:
105            # Stop running the container and remove it
106            self.run_command(self.container_binary + ' stop', [self.container_binary, 'stop', '-t', '1', self.container_name])
107            self.run_command(self.container_binary + ' rm', [self.container_binary, 'rm', self.container_name])
108
109    def _check_container_runnable(self):
110        try:
111            subprocess.check_output([self.container_binary, 'ps'], stderr=subprocess.STDOUT)
112        except subprocess.CalledProcessError as err:
113            if 'denied' in err.output.decode('utf-8'):
114                print('Run script as sudo')
115            else:
116                print('Unexpected error: {}'.format(err.output.decode('utf-8')))
117
118            return False
119
120        # No exception means container is ok
121        return True
122
123    def build(self):
124        if not self._check_container_runnable():
125            return
126
127        # First build the container image
128        self._container_build()
129
130        # Then build libchrome and modp-b64 inside the container image and
131        # install them. Commit those changes to the final label.
132        self._build_dpkg_and_commit()
133
134
135def main():
136    parser = argparse.ArgumentParser(description='Build container image for Floss build environment.')
137    parser.add_argument('--tag', required=True, help='Tag for container image. i.e. floss:latest')
138    parser.add_argument('--use-docker', action='store_true', default=False, help='Use flag to use Docker to build Floss. Defaults to using podman.')
139    args = parser.parse_args()
140
141    # cwd should be set to same directory as this script (that's where
142    # Dockerfile is kept).
143    workdir = os.path.dirname(os.path.abspath(sys.argv[0]))
144    rootdir = os.path.abspath(os.path.join(workdir, '../..'))
145
146    # Build the container image
147    pib = ContainerImageBuilder(workdir, rootdir, args.tag, args.use_docker)
148    pib.build()
149
150
151if __name__ == '__main__':
152    main()
153