1#!/usr/bin/env python
2# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Install Debian sysroots for building chromium.
7"""
8
9# The sysroot is needed to ensure that binaries that get built will run on
10# the oldest stable version of Debian that we currently support.
11# This script can be run manually but is more often run as part of gclient
12# hooks. When run from hooks this script is a no-op on non-linux platforms.
13
14# The sysroot image could be constructed from scratch based on the current state
15# of the Debian archive but for consistency we use a pre-built root image (we
16# don't want upstream changes to Debian to effect the chromium build until we
17# choose to pull them in). The images will normally need to be rebuilt every
18# time chrome's build dependencies are changed but should also be updated
19# periodically to include upstream security fixes from Debian.
20
21from __future__ import print_function
22
23import hashlib
24import json
25import platform
26import optparse
27import os
28import re
29import shutil
30import subprocess
31import sys
32try:
33    # For Python 3.0 and later
34    from urllib.request import urlopen
35except ImportError:
36    # Fall back to Python 2's urllib2
37    from urllib2 import urlopen
38
39SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
40
41URL_PREFIX = 'https://commondatastorage.googleapis.com'
42URL_PATH = 'chrome-linux-sysroot/toolchain'
43
44VALID_ARCHS = ('arm', 'arm64', 'i386', 'amd64', 'mips', 'mips64el')
45
46ARCH_TRANSLATIONS = {
47    'x64': 'amd64',
48    'x86': 'i386',
49    'mipsel': 'mips',
50    'mips64': 'mips64el',
51}
52
53DEFAULT_TARGET_PLATFORM = 'sid'
54
55class Error(Exception):
56  pass
57
58
59def GetSha1(filename):
60  sha1 = hashlib.sha1()
61  with open(filename, 'rb') as f:
62    while True:
63      # Read in 1mb chunks, so it doesn't all have to be loaded into memory.
64      chunk = f.read(1024*1024)
65      if not chunk:
66        break
67      sha1.update(chunk)
68  return sha1.hexdigest()
69
70
71def main(args):
72  parser = optparse.OptionParser('usage: %prog [OPTIONS]', description=__doc__)
73  parser.add_option('--arch',
74                    help='Sysroot architecture: %s' % ', '.join(VALID_ARCHS))
75  parser.add_option('--all', action='store_true',
76                    help='Install all sysroot images (useful when updating the'
77                         ' images)')
78  parser.add_option('--print-hash',
79                    help='Print the hash of the sysroot for the given arch.')
80  options, _ = parser.parse_args(args)
81  if not sys.platform.startswith('linux'):
82    return 0
83
84  if options.print_hash:
85    arch = options.print_hash
86    print(GetSysrootDict(DEFAULT_TARGET_PLATFORM,
87                         ARCH_TRANSLATIONS.get(arch, arch))['Sha1Sum'])
88    return 0
89  if options.arch:
90    InstallSysroot(DEFAULT_TARGET_PLATFORM,
91                   ARCH_TRANSLATIONS.get(options.arch, options.arch))
92  elif options.all:
93    for arch in VALID_ARCHS:
94      InstallSysroot(DEFAULT_TARGET_PLATFORM, arch)
95  else:
96    print('You much specify one of the options.')
97    return 1
98
99  return 0
100
101
102def GetSysrootDict(target_platform, target_arch):
103  if target_arch not in VALID_ARCHS:
104    raise Error('Unknown architecture: %s' % target_arch)
105
106  sysroots_file = os.path.join(SCRIPT_DIR, 'sysroots.json')
107  sysroots = json.load(open(sysroots_file))
108  sysroot_key = '%s_%s' % (target_platform, target_arch)
109  if sysroot_key not in sysroots:
110    raise Error('No sysroot for: %s %s' % (target_platform, target_arch))
111  return sysroots[sysroot_key]
112
113
114def InstallSysroot(target_platform, target_arch):
115  sysroot_dict = GetSysrootDict(target_platform, target_arch)
116  tarball_filename = sysroot_dict['Tarball']
117  tarball_sha1sum = sysroot_dict['Sha1Sum']
118  # TODO(thestig) Consider putting this elsewhere to avoid having to recreate
119  # it on every build.
120  linux_dir = os.path.dirname(SCRIPT_DIR)
121  sysroot = os.path.join(linux_dir, sysroot_dict['SysrootDir'])
122
123  url = '%s/%s/%s/%s' % (URL_PREFIX, URL_PATH, tarball_sha1sum,
124                         tarball_filename)
125
126  stamp = os.path.join(sysroot, '.stamp')
127  if os.path.exists(stamp):
128    with open(stamp) as s:
129      if s.read() == url:
130        return
131
132  print('Installing Debian %s %s root image: %s' % \
133      (target_platform, target_arch, sysroot))
134  if os.path.isdir(sysroot):
135    shutil.rmtree(sysroot)
136  os.mkdir(sysroot)
137  tarball = os.path.join(sysroot, tarball_filename)
138  print('Downloading %s' % url)
139  sys.stdout.flush()
140  sys.stderr.flush()
141  for _ in range(3):
142    try:
143      response = urlopen(url)
144      with open(tarball, "wb") as f:
145        f.write(response.read())
146      break
147    except Exception:  # Ignore exceptions.
148      pass
149  else:
150    raise Error('Failed to download %s' % url)
151  sha1sum = GetSha1(tarball)
152  if sha1sum != tarball_sha1sum:
153    raise Error('Tarball sha1sum is wrong.'
154                'Expected %s, actual: %s' % (tarball_sha1sum, sha1sum))
155  subprocess.check_call(['tar', 'xf', tarball, '-C', sysroot])
156  os.remove(tarball)
157
158  with open(stamp, 'w') as s:
159    s.write(url)
160
161
162if __name__ == '__main__':
163  try:
164    sys.exit(main(sys.argv[1:]))
165  except Error as e:
166    sys.stderr.write(str(e) + '\n')
167    sys.exit(1)
168