summaryrefslogtreecommitdiff
path: root/floss/build/docker-build-image.py
blob: 81b622b87b7b455886247e76589cdd13c16f3ebf (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
#!/usr/bin/env python3

import argparse
import os
import sys
import subprocess

SRC_MOUNT = "/root/src"


class DockerImageBuilder:
    """Builds the docker image for Floss build environment."""

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

        Args:
            workdir: Working directory for this script. Dockerfile should exist here.
            rootdir: Root directory for Bluetooth.
            tag: Label in format |name:version|.
        """
        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.env = os.environ.copy()

        # Mark dpkg builders for docker
        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 _docker_build(self):
        self.run_command('docker build', ['docker', '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('docker stop', ['docker', 'stop', '-t', '1', self.container_name], ignore_rc=True)
        self.run_command('docker rm', ['docker', '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('docker run', [
            'docker', '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
            ['/root/src/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
            ['/root/src/system/build/dpkg/libchrome/gen-src-pkg.sh', '/tmp/libchrome'],

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

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

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

        # Commit changes into the final tag name
        self.run_command('docker commit', ['docker', 'commit', self.container_name, self.final_tag])

        # Stop running the container and remove it
        self.run_command('docker stop', ['docker', 'stop', '-t', '1', self.container_name])
        self.run_command('docker rm', ['docker', 'rm', self.container_name])

    def _check_docker_runnable(self):
        try:
            subprocess.check_output(['docker', '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 docker is ok
        return True

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

        # First build the docker image
        self._docker_build()

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


def main():
    parser = argparse.ArgumentParser(description='Build docker image for Floss build environment.')
    parser.add_argument('--tag', required=True, help='Tag for docker image. i.e. floss:latest')
    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 docker image
    dib = DockerImageBuilder(workdir, rootdir, args.tag)
    dib.build()


if __name__ == '__main__':
    main()