blob: c3ce41f668b607f77db4fba97bf303de8ad04c6b [file] [log] [blame]
#!/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 logging
import os
import platform
import subprocess
import sys
import tempfile
# The path to the "infra/bootstrap/" directory.
BOOTSTRAP_DIR = os.path.dirname(os.path.abspath(__file__))
# The path to the "infra/" directory.
ROOT = os.path.dirname(BOOTSTRAP_DIR)
# The path where CIPD install lists are stored.
CIPD_LIST_DIR = os.path.join(BOOTSTRAP_DIR, 'cipd')
# Default sysroot install root.
DEFAULT_INSTALL_ROOT = os.path.join(ROOT, 'cipd')
# Default CIPD server url
# function to find the cipd binary. On POSIX this is simple ('cipd'!), but
# for windows this is more complex. depot_tools exposes this as a .bat, but
# buildbot and swarming expose it as an exe. So we have to look for it.
if sys.platform == 'win32':
def cipd_binary():
for p in os.environ.get('PATH', '').split(os.pathsep):
base = os.path.join(p, 'cipd')
for ext in ('.exe', '.bat'):
candidate = base+ext
if os.path.isfile(candidate):
return candidate
# if we didn't find it, guess 'cipd.exe'
return 'cipd.exe'
def cipd_binary():
return 'cipd'
# Map of CIPD configuration based on the current architecture/platform. If a
# platform is not listed here, the bootstrap will be a no-op.
# This is keyed on the platform's (system, machine).
('Linux', 'x86_64'): {
'cipd_install_list': 'cipd_linux_amd64.txt',
('Linux', 'x86'): {
'cipd_install_list': None,
('Darwin', 'x86_64'): {
'cipd_install_list': 'cipd_mac_amd64.txt',
('Windows', 'x86_64'): {
'cipd_install_list': 'cipd_windows_amd64.txt',
('Windows', 'x86'): {
'cipd_install_list': None,
def get_platform_config():
key = get_platform()
return key, ARCH_CONFIG_MAP.get(key)
def get_platform():
machine = platform.machine().lower()
system = platform.system()
machine = ({
'amd64': 'x86_64',
'i686': 'x86',
}).get(machine, machine)
if (machine == 'x86_64' and system == 'Linux' and
sys.maxsize == (2 ** 31) - 1):
# This is 32bit python on 64bit CPU on linux, which probably means the
# entire userland is 32bit and thus we should play along and install 32bit
# packages.
machine = 'x86'
return system, machine
def ensure_directory(path):
# Ensure the parent directory exists.
if os.path.isdir(path):
if os.path.exists(path):
raise ValueError("Target file's directory [%s] exists, but is not a "
"directory." % (path,))
logging.debug('Creating directory: [%s]', path)
def execute(*cmd):
if not logging.getLogger().isEnabledFor(logging.DEBUG):
code =
# Execute the process, passing STDOUT/STDERR through our logger.
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
for line in proc.stdout:
logging.debug('%s: %s', cmd[0], line.rstrip())
code = proc.wait()
if code:
logging.error('Process failed with exit code: %d', code)
return code
class CipdError(Exception):
"""Raised by install_cipd_client on fatal error."""
def cipd_ensure(root, ensure_file, cipd_backend_url=None):
"""Invoke `cipd ensure` with the provided ensure_file in the given root.
ensure_file (str) - file containing the packages to ensure
root (str) - directory to ensure packages in. Will be created if doesn't
cipd_backend_url = cipd_backend_url or DEFAULT_SERVER_URL
assert os.path.isfile(ensure_file)
logging.debug('Installing CIPD packages from [%s] to [%s]', ensure_file, root)
args = [
'-ensure-file', ensure_file,
'-root', root,
'-service-url', cipd_backend_url
if execute(cipd_binary(), *args):
raise CipdError('Failed to execute CIPD client: %s', ' '.join(args))
def cipd_ensure_list(root, ensure_data, cipd_backend_url=None):
"""Invoke `cipd ensure` with the provided ensure_data in the given root.
ensure_data (list(tuple)) - a list of (package_pattern, version). The
package_patterns may contain the ${platform} and ${arch} directives
as defined by the cipd client.
root (str) - directory to ensure packages in
with tempfile.NamedTemporaryFile(prefix="cipd_ensure", delete=False) as tf:
for item in ensure_data:
print >> tf, "%s %s" % item
cipd_ensure(root,, cipd_backend_url)
except OSError:
logging.exception("failed to remove tempfile %r",
def main(argv):
parser = argparse.ArgumentParser('Installs CIPD bootstrap packages.')
parser.add_argument('-v', '--verbose', action='count', default=0,
help='Increase logging verbosity. Can be specified multiple times.')
parser.add_argument('--cipd-backend-url', metavar='URL',
help='Specify the CIPD backend URL (default is %(default)s)')
parser.add_argument('-d', '--cipd-root-dir', metavar='PATH',
help='Specify the root CIPD package installation directory.')
opts = parser.parse_args(argv)
# Setup logging verbosity.
if opts.verbose == 0:
level = logging.WARNING
elif opts.verbose == 1:
level = logging.INFO
level = logging.DEBUG
root = os.path.abspath(opts.cipd_root_dir)
# 2017/02/15 we used to bootstrap the cipd client into <infra.git>/cipd. This
# deletes it, if it's there. This is important because otherwise the stale
# client will end up at the front of $PATH and get used instead of the correct
# one (i.e. the one that depot_tools / swarming / buildbot put there).
EXE_SFX = '.exe' if sys.platform == 'win32' else ''
os.remove(os.path.join(root, 'cipd'+EXE_SFX))
except OSError:
platform_key, config = get_platform_config()
if not config:'No bootstrap configuration for platform [%s].', platform_key)
return 0
# Install the CIPD list for this configuration.
cipd_install_list = config.get('cipd_install_list')
if cipd_install_list:
os.path.join(CIPD_LIST_DIR, cipd_install_list),
return 0
if __name__ == '__main__':