#!/usr/bin/env python # Copyright 2015 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import argparse import operator import os import platform import re import subprocess import sys SUPPORTED_UBUNTU_VERSIONS = ( {'number': '12.04', 'codename': 'precise'}, {'number': '14.04', 'codename': 'trusty'}, {'number': '14.10', 'codename': 'utopic'}, {'number': '15.04', 'codename': 'vivid'}, {'number': '15.10', 'codename': 'wily'}, ) # Packages needed for chromeos only. _packages_chromeos_dev = ( 'libbluetooth-dev', 'libxkbcommon-dev', 'realpath', ) # Packages needed for development. _packages_dev = ( 'bison', 'cdbs', 'curl', 'devscripts', 'dpkg-dev', 'elfutils', 'fakeroot', 'flex', 'fonts-thai-tlwg', 'g++', 'git-core', 'git-svn', 'gperf', 'language-pack-da', 'language-pack-fr', 'language-pack-he', 'language-pack-zh-hant', 'libapache2-mod-php5', 'libasound2-dev', 'libav-tools', 'libbrlapi-dev', 'libbz2-dev', 'libcairo2-dev', 'libcap-dev', 'libcups2-dev', 'libcurl4-gnutls-dev', 'libdrm-dev', 'libelf-dev', 'libgconf2-dev', 'libglib2.0-dev', 'libglu1-mesa-dev', 'libgnome-keyring-dev', 'libgtk2.0-dev', 'libkrb5-dev', 'libnspr4-dev', 'libnss3-dev', 'libpam0g-dev', 'libpci-dev', 'libpulse-dev', 'libsctp-dev', 'libspeechd-dev', 'libsqlite3-dev', 'libssl-dev', 'libudev-dev', 'libwww-perl', 'libxslt1-dev', 'libxss-dev', 'libxt-dev', 'libxtst-dev', 'openbox', 'patch', 'perl', 'php5-cgi', 'pkg-config', 'python', 'python-cherrypy3', 'python-crypto', 'python-dev', 'python-numpy', 'python-opencv', 'python-openssl', 'python-psutil', 'python-yaml', 'rpm', 'ruby', 'subversion', 'ttf-dejavu-core', 'ttf-indic-fonts', 'ttf-kochi-gothic', 'ttf-kochi-mincho', 'wdiff', 'zip', ) # Run-time libraries required by chromeos only. _packages_chromeos_lib = ( 'libbz2-1.0', 'libpulse0', ) # Full list of required run-time libraries. _packages_lib = ( 'libasound2', 'libatk1.0-0', 'libc6', 'libcairo2', 'libcap2', 'libcups2', 'libexpat1', 'libfontconfig1', 'libfreetype6', 'libglib2.0-0', 'libgnome-keyring0', 'libgtk2.0-0', 'libpam0g', 'libpango1.0-0', 'libpci3', 'libpcre3', 'libpixman-1-0', 'libpng12-0', 'libspeechd2', 'libsqlite3-0', 'libstdc++6', 'libx11-6', 'libxau6', 'libxcb1', 'libxcomposite1', 'libxcursor1', 'libxdamage1', 'libxdmcp6', 'libxext6', 'libxfixes3', 'libxi6', 'libxinerama1', 'libxrandr2', 'libxrender1', 'libxtst6', 'zlib1g', ) # Debugging symbols for all of the run-time libraries. _packages_dbg = ( 'libatk1.0-dbg', 'libc6-dbg', 'libcairo2-dbg', 'libfontconfig1-dbg', 'libglib2.0-0-dbg', 'libgtk2.0-0-dbg', 'libpango1.0-0-dbg', 'libpcre3-dbg', 'libpixman-1-0-dbg', 'libsqlite3-0-dbg', 'libx11-6-dbg', 'libxau6-dbg', 'libxcb1-dbg', 'libxcomposite1-dbg', 'libxcursor1-dbg', 'libxdamage1-dbg', 'libxdmcp6-dbg', 'libxext6-dbg', 'libxfixes3-dbg', 'libxi6-dbg', 'libxinerama1-dbg', 'libxrandr2-dbg', 'libxrender1-dbg', 'libxtst6-dbg', 'zlib1g-dbg', ) # 32-bit libraries needed e.g. to compile V8 snapshot for Android or armhf. _packages_lib32 = ( 'linux-libc-dev:i386', ) # arm cross toolchain packages needed to build chrome on armhf. _packages_arm = ( 'g++-arm-linux-gnueabihf', 'libc6-dev-armhf-cross', 'linux-libc-dev-armhf-cross', ) # Packages to build NaCl, its toolchains, and its ports. _packages_naclports = ( 'ant', 'autoconf', 'bison', 'cmake', 'gawk', 'intltool', 'xsltproc', 'xutils-dev', ) _packages_nacl = ( 'g++-mingw-w64-i686', 'lib32ncurses5-dev', 'lib32z1-dev', 'libasound2:i386', 'libcap2:i386', 'libelf-dev:i386', 'libfontconfig1:i386', 'libgconf-2-4:i386', 'libglib2.0-0:i386', 'libgpm2:i386', 'libgtk2.0-0:i386', 'libncurses5:i386', 'libnss3:i386', 'libpango1.0-0:i386', 'libssl1.0.0:i386', 'libtinfo-dev', 'libtinfo-dev:i386', 'libtool', 'libxcomposite1:i386', 'libxcursor1:i386', 'libxdamage1:i386', 'libxi6:i386', 'libxrandr2:i386', 'libxss1:i386', 'libxtst6:i386', 'texinfo', 'xvfb', ) def is_userland_64_bit(): return platform.architecture()[0] == '64bit' def package_exists(pkg): return pkg in subprocess.check_output(['apt-cache', 'pkgnames']).splitlines() def lsb_release_short_codename(): return subprocess.check_output( ['lsb_release', '--codename', '--short']).strip() def write_error(message): sys.stderr.write('ERROR: %s\n' % message) sys.stderr.flush() def nonfatal_get_output(*popenargs, **kwargs): process = subprocess.Popen( stdout=subprocess.PIPE, stderr=subprocess.PIPE, *popenargs, **kwargs) stdout, stderr = process.communicate() retcode = process.poll() return retcode, stdout, stderr def compute_dynamic_package_lists(): global _packages_arm global _packages_dbg global _packages_dev global _packages_lib global _packages_lib32 global _packages_nacl if is_userland_64_bit(): # 64-bit systems need a minimum set of 32-bit compat packages # for the pre-built NaCl binaries. _packages_dev += ( 'lib32gcc1', 'lib32stdc++6', 'libc6-i386', ) # When cross building for arm/Android on 64-bit systems the host binaries # that are part of v8 need to be compiled with -m32 which means # that basic multilib support is needed. # gcc-multilib conflicts with the arm cross compiler (at least in trusty) # but g++-X.Y-multilib gives us the 32-bit support that we need. Find out # the appropriate value of X and Y by seeing what version the current # distribution's g++-multilib package depends on. output = subprocess.check_output(['apt-cache', 'depends', 'g++-multilib']) multilib_package = re.search(r'g\+\+-[0-9.]+-multilib', output).group() _packages_lib32 += (multilib_package,) lsb_codename = lsb_release_short_codename() # Find the proper version of libstdc++6-4.x-dbg. if lsb_codename == 'precise': _packages_dbg += ('libstdc++6-4.6-dbg',) elif lsb_codename == 'trusty': _packages_dbg += ('libstdc++6-4.8-dbg',) else: _packages_dbg += ('libstdc++6-4.9-dbg',) # Work around for dependency issue Ubuntu/Trusty: http://crbug.com/435056 . if lsb_codename == 'trusty': _packages_arm += ( 'g++-4.8-multilib-arm-linux-gnueabihf', 'gcc-4.8-multilib-arm-linux-gnueabihf', ) # Find the proper version of libgbm-dev. We can't just install libgbm-dev as # it depends on mesa, and only one version of mesa can exists on the system. # Hence we must match the same version or this entire script will fail. mesa_variant = '' for variant in ('-lts-trusty', '-lts-utopic'): rc, stdout, stderr = nonfatal_get_output( ['dpkg-query', '-Wf\'{Status}\'', 'libgl1-mesa-glx' + variant]) if 'ok installed' in output: mesa_variant = variant _packages_dev += ( 'libgbm-dev' + mesa_variant, 'libgl1-mesa-dev' + mesa_variant, 'libgles2-mesa-dev' + mesa_variant, 'mesa-common-dev' + mesa_variant, ) if package_exists('ttf-mscorefonts-installer'): _packages_dev += ('ttf-mscorefonts-installer',) else: _packages_dev += ('msttcorefonts',) if package_exists('libnspr4-dbg'): _packages_dbg += ('libnspr4-dbg', 'libnss3-dbg') _packages_lib += ('libnspr4', 'libnss3') else: _packages_dbg += ('libnspr4-0d-dbg', 'libnss3-1d-dbg') _packages_lib += ('libnspr4-0d', 'libnss3-1d') if package_exists('libjpeg-dev'): _packages_dev += ('libjpeg-dev',) else: _packages_dev += ('libjpeg62-dev',) if package_exists('libudev1'): _packages_dev += ('libudev1',) _packages_nacl += ('libudev1:i386',) else: _packages_dev += ('libudev0',) _packages_nacl += ('libudev0:i386',) if package_exists('libbrlapi0.6'): _packages_dev += ('libbrlapi0.6',) else: _packages_dev += ('libbrlapi0.5',) if package_exists('apache2-bin'): _packages_dev += ('apache2-bin',) else: _packages_dev += ('apache2.2-bin',) if package_exists('fonts-stix'): _packages_dev += ('fonts-stix',) else: _packages_dev += ('xfonts-mathml',) # Some packages are only needed if the distribution actually supports # installing them. if package_exists('appmenu-gtk'): _packages_lib += ('appmenu-gtk',) _packages_dev += _packages_chromeos_dev _packages_lib += _packages_chromeos_lib _packages_nacl += _packages_naclports def quick_check(packages): rc, stdout, stderr = nonfatal_get_output([ 'dpkg-query', '-W', '-f', '${PackageSpec}:${Status}\n'] + list(packages)) if rc == 0 and not stderr: return 0 print stderr return 1 def main(argv): parser = argparse.ArgumentParser() parser.add_argument('--quick-check', action='store_true', help='quickly try to determine if dependencies are ' 'installed (this avoids interactive prompts and ' 'sudo commands so might not be 100% accurate)') parser.add_argument('--unsupported', action='store_true', help='attempt installation even on unsupported systems') args = parser.parse_args(argv) lsb_codename = lsb_release_short_codename() if not args.unsupported and not args.quick_check: if lsb_codename not in map( operator.itemgetter('codename'), SUPPORTED_UBUNTU_VERSIONS): supported_ubuntus = ['%(number)s (%(codename)s)' % v for v in SUPPORTED_UBUNTU_VERSIONS] write_error('Only Ubuntu %s are currently supported.' % ', '.join(supported_ubuntus)) return 1 if platform.machine() not in ('i686', 'x86_64'): write_error('Only x86 architectures are currently supported.') return 1 if os.geteuid() != 0 and not args.quick_check: print 'Running as non-root user.' print 'You might have to enter your password one or more times' print 'for \'sudo\'.' print compute_dynamic_package_lists() packages = (_packages_dev + _packages_lib + _packages_dbg + _packages_lib32 + _packages_arm + _packages_nacl) def packages_key(pkg): s = pkg.rsplit(':', 1) if len(s) == 1: return (s, '') return s packages = sorted(set(packages), key=packages_key) if args.quick_check: return quick_check(packages) return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:]))