1#!/usr/bin/env python2
2
3# Copyright 2019 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""
8Install Debian sysroots for cross compiling Open Screen.
9"""
10
11# The sysroot is needed to ensure that binaries that get built will run on
12# the oldest stable version of Debian that we currently support.
13# This script can be run manually but is more often run as part of gclient
14# hooks. When run from hooks this script is a no-op on non-linux platforms.
15
16# The sysroot image could be constructed from scratch based on the current state
17# of the Debian archive but for consistency we use a pre-built root image (we
18# don't want upstream changes to Debian to affect the build until we
19# choose to pull them in). The sysroot images are stored in Chrome's common
20# data storage, and the sysroots.json file should be kept in sync with Chrome's
21# copy of it.
22
23from __future__ import print_function
24
25import hashlib
26import json
27import platform
28import argparse
29import os
30import re
31import shutil
32import subprocess
33import sys
34try:
35    # For Python 3.0 and later
36    from urllib.request import urlopen
37except ImportError:
38    # Fall back to Python 2's urllib2
39    from urllib2 import urlopen
40
41SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
42PARENT_DIR = os.path.dirname(SCRIPT_DIR)
43URL_PREFIX = 'https://storage.googleapis.com'
44URL_PATH = 'openscreen-sysroots'
45
46VALID_ARCHS = ('arm', 'arm64')
47VALID_PLATFORMS = ('stretch', 'sid')
48
49
50class Error(Exception):
51    pass
52
53
54def GetSha1(filename):
55    """Generates a SHA1 hash for validating download. Done in chunks to avoid
56    excess memory usage."""
57    BLOCKSIZE = 1024 * 1024
58    sha1 = hashlib.sha1()
59    with open(filename, 'rb') as f:
60        chunk = f.read(BLOCKSIZE)
61        while chunk:
62            sha1.update(chunk)
63            chunk = f.read(BLOCKSIZE)
64    return sha1.hexdigest()
65
66
67def GetSysrootDict(target_platform, target_arch):
68    """Gets the sysroot information for a given platform and arch from the sysroots.json
69    file."""
70    if target_arch not in VALID_ARCHS:
71        raise Error('Unknown architecture: %s' % target_arch)
72
73    sysroots_file = os.path.join(SCRIPT_DIR, 'sysroots.json')
74    sysroots = json.load(open(sysroots_file))
75    sysroot_key = '%s_%s' % (target_platform, target_arch)
76    if sysroot_key not in sysroots:
77        raise Error('No sysroot for: %s' % (sysroot_key))
78    return sysroots[sysroot_key]
79
80
81def DownloadFile(url, local_path):
82    """Uses urllib to download a remote file into local_path."""
83    for _ in range(3):
84        try:
85            response = urlopen(url)
86            with open(local_path, "wb") as f:
87                f.write(response.read())
88            break
89        except Exception:
90            pass
91    else:
92        raise Error('Failed to download %s' % url)
93
94def ValidateFile(local_path, expected_sum):
95    """Generates the SHA1 hash of a local file to compare with an expected hashsum."""
96    sha1sum = GetSha1(local_path)
97    if sha1sum != expected_sum:
98        raise Error('Tarball sha1sum is wrong.'
99                    'Expected %s, actual: %s' % (expected_sum, sha1sum))
100
101def InstallSysroot(target_platform, target_arch):
102    """Downloads, validates, unpacks, and installs a sysroot image."""
103    sysroot_dict = GetSysrootDict(target_platform, target_arch)
104    tarball_filename = sysroot_dict['Tarball']
105    tarball_sha1sum = sysroot_dict['Sha1Sum']
106
107    sysroot = os.path.join(PARENT_DIR, sysroot_dict['SysrootDir'])
108
109    url = '%s/%s/%s/%s' % (URL_PREFIX, URL_PATH, tarball_sha1sum,
110                           tarball_filename)
111
112    stamp = os.path.join(sysroot, '.stamp')
113    if os.path.exists(stamp):
114        with open(stamp) as s:
115            if s.read() == url:
116                return
117
118    if os.path.isdir(sysroot):
119        shutil.rmtree(sysroot)
120    os.mkdir(sysroot)
121
122    tarball_path = os.path.join(sysroot, tarball_filename)
123    DownloadFile(url, tarball_path)
124    ValidateFile(tarball_path, tarball_sha1sum)
125    subprocess.check_call(['tar', 'xf', tarball_path, '-C', sysroot])
126    os.remove(tarball_path)
127
128    with open(stamp, 'w') as s:
129        s.write(url)
130
131
132def parse_args(args):
133    """Parses the passed in arguments into an object."""
134    p = argparse.ArgumentParser()
135    p.add_argument(
136        'arch',
137        help='Sysroot architecture: %s' % ', '.join(VALID_ARCHS))
138    p.add_argument(
139        'platform',
140        help='Sysroot platform: %s' % ', '.join(VALID_PLATFORMS))
141    p.add_argument(
142        '--print-hash', action="store_true",
143        help='Print the hash of the sysroot for the specified arch.')
144
145    return p.parse_args(args)
146
147
148def main(args):
149    if not (sys.platform.startswith('linux') or sys.platform == 'darwin'):
150        print('Unsupported platform. Only Linux and Mac OS X are supported.')
151        return 1
152
153    parsed_args = parse_args(args)
154    if parsed_args.print_hash:
155        print(GetSysrootDict(parsed_args.platform, parsed_args.arch)['Sha1Sum'])
156
157    InstallSysroot(parsed_args.platform, parsed_args.arch)
158    return 0
159
160
161if __name__ == '__main__':
162    try:
163        sys.exit(main(sys.argv[1:]))
164    except Error as e:
165        sys.stderr.write('Installing sysroot error: {}\n'.format(e))
166        sys.exit(1)
167