123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- #!/usr/bin/env vpython3
- # Copyright (c) 2013 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.
- """
- Tool to perform checkouts in one easy command line!
- Usage:
- fetch <config> [--property=value [--property2=value2 ...]]
- This script is a wrapper around various version control and repository
- checkout commands. It requires a |config| name, fetches data from that
- config in depot_tools/fetch_configs, and then performs all necessary inits,
- checkouts, pulls, fetches, etc.
- Optional arguments may be passed on the command line in key-value pairs.
- These parameters will be passed through to the config's main method.
- """
- import json
- import argparse
- import os
- import shlex
- import subprocess
- import sys
- import git_common
- from distutils import spawn
- SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
- #################################################
- # Checkout class definitions.
- #################################################
- class Checkout(object):
- """Base class for implementing different types of checkouts.
- Attributes:
- |base|: the absolute path of the directory in which this script is run.
- |spec|: the spec for this checkout as returned by the config. Different
- subclasses will expect different keys in this dictionary.
- |root|: the directory into which the checkout will be performed, as
- returnedby the config. This is a relative path from |base|.
- """
- def __init__(self, options, spec, root):
- self.base = os.getcwd()
- self.options = options
- self.spec = spec
- self.root = root
- def exists(self):
- """Check does this checkout already exist on desired location."""
- def init(self):
- pass
- def run(self, cmd, return_stdout=False, **kwargs):
- print('Running: %s' % (' '.join(shlex.quote(x) for x in cmd)))
- if self.options.dry_run:
- return ''
- if return_stdout:
- return subprocess.check_output(cmd, **kwargs).decode()
- try:
- subprocess.check_call(cmd, **kwargs)
- except subprocess.CalledProcessError as e:
- # If the subprocess failed, it likely emitted its own distress
- # message already - don't scroll that message off the screen with a
- # stack trace from this program as well. Emit a terse message and
- # bail out here; otherwise a later step will try doing more work and
- # may hide the subprocess message.
- print('Subprocess failed with return code %d.' % e.returncode)
- sys.exit(e.returncode)
- return ''
- class GclientCheckout(Checkout):
- def run_gclient(self, *cmd, **kwargs):
- if not spawn.find_executable('gclient'):
- cmd_prefix = (sys.executable, os.path.join(SCRIPT_PATH,
- 'gclient.py'))
- else:
- cmd_prefix = ('gclient', )
- return self.run(cmd_prefix + cmd, **kwargs)
- def exists(self):
- try:
- gclient_root = self.run_gclient('root', return_stdout=True).strip()
- return (os.path.exists(os.path.join(gclient_root, '.gclient'))
- or os.path.exists(
- os.path.join(os.getcwd(), self.root, '.git')))
- except subprocess.CalledProcessError:
- pass
- return os.path.exists(os.path.join(os.getcwd(), self.root))
- class GitCheckout(Checkout):
- def run_git(self, *cmd, **kwargs):
- print('Running: git %s' % (' '.join(shlex.quote(x) for x in cmd)))
- if self.options.dry_run:
- return ''
- return git_common.run(*cmd, **kwargs)
- class GclientGitCheckout(GclientCheckout, GitCheckout):
- def __init__(self, options, spec, root):
- super(GclientGitCheckout, self).__init__(options, spec, root)
- assert 'solutions' in self.spec
- def _format_spec(self):
- def _format_literal(lit):
- if isinstance(lit, str):
- return '"%s"' % lit
- if isinstance(lit, list):
- return '[%s]' % ', '.join(_format_literal(i) for i in lit)
- return '%r' % lit
- soln_strings = []
- for soln in self.spec['solutions']:
- soln_string = '\n'.join(' "%s": %s,' %
- (key, _format_literal(value))
- for key, value in soln.items())
- soln_strings.append(' {\n%s\n },' % soln_string)
- gclient_spec = 'solutions = [\n%s\n]\n' % '\n'.join(soln_strings)
- extra_keys = ['target_os', 'target_os_only', 'cache_dir']
- gclient_spec += ''.join('%s = %s\n' %
- (key, _format_literal(self.spec[key]))
- for key in extra_keys if key in self.spec)
- return gclient_spec
- def init(self):
- # Configure and do the gclient checkout.
- self.run_gclient('config', '--spec', self._format_spec())
- sync_cmd = ['sync']
- if self.options.nohooks:
- sync_cmd.append('--nohooks')
- if self.options.nohistory:
- sync_cmd.append('--no-history')
- if self.spec.get('with_branch_heads', False):
- sync_cmd.append('--with_branch_heads')
- self.run_gclient(*sync_cmd)
- # Configure git.
- wd = os.path.join(self.base, self.root)
- if self.options.dry_run:
- print('cd %s' % wd)
- if not self.options.nohistory:
- self.run_git('config',
- '--add',
- 'remote.origin.fetch',
- '+refs/tags/*:refs/tags/*',
- cwd=wd)
- self.run_git('config', 'diff.ignoreSubmodules', 'dirty', cwd=wd)
- CHECKOUT_TYPE_MAP = {
- 'gclient': GclientCheckout,
- 'gclient_git': GclientGitCheckout,
- 'git': GitCheckout,
- }
- def CheckoutFactory(type_name, options, spec, root):
- """Factory to build Checkout class instances."""
- class_ = CHECKOUT_TYPE_MAP.get(type_name)
- if not class_:
- raise KeyError('unrecognized checkout type: %s' % type_name)
- return class_(options, spec, root)
- def handle_args(argv):
- """Gets the config name from the command line arguments."""
- configs_dir = os.path.join(SCRIPT_PATH, 'fetch_configs')
- configs = [f[:-3] for f in os.listdir(configs_dir) if f.endswith('.py')]
- configs.sort()
- parser = argparse.ArgumentParser(
- formatter_class=argparse.RawDescriptionHelpFormatter,
- description='''
- This script can be used to download the Chromium sources. See
- http://www.chromium.org/developers/how-tos/get-the-code
- for full usage instructions.''',
- epilog='Valid fetch configs:\n' + \
- '\n'.join(map(lambda s: ' ' + s, configs))
- )
- parser.add_argument('-n',
- '--dry-run',
- action='store_true',
- default=False,
- help='Don\'t run commands, only print them.')
- parser.add_argument('--nohooks',
- '--no-hooks',
- action='store_true',
- default=False,
- help='Don\'t run hooks after checkout.')
- parser.add_argument(
- '--nohistory',
- '--no-history',
- action='store_true',
- default=False,
- help='Perform shallow clones, don\'t fetch the full git history.')
- parser.add_argument(
- '--force',
- action='store_true',
- default=False,
- help='(dangerous) Don\'t look for existing .gclient file.')
- parser.add_argument(
- '-p',
- '--protocol-override',
- type=str,
- default=None,
- help='Protocol to use to fetch dependencies, defaults to https.')
- parser.add_argument('config',
- type=str,
- help="Project to fetch, e.g. chromium.")
- parser.add_argument('props',
- metavar='props',
- type=str,
- nargs=argparse.REMAINDER,
- default=[])
- args = parser.parse_args(argv[1:])
- # props passed to config must be of the format --<name>=<value>
- looks_like_arg = lambda arg: arg.startswith('--') and arg.count('=') == 1
- bad_param = [x for x in args.props if not looks_like_arg(x)]
- if bad_param:
- print('Error: Got bad arguments %s' % bad_param)
- parser.print_help()
- sys.exit(1)
- return args
- def run_config_fetch(config, props, aliased=False):
- """Invoke a config's fetch method with the passed-through args
- and return its json output as a python object."""
- config_path = os.path.abspath(
- os.path.join(SCRIPT_PATH, 'fetch_configs', config))
- if not os.path.exists(config_path + '.py'):
- print("Could not find a config for %s" % config)
- sys.exit(1)
- cmd = [sys.executable, config_path + '.py', 'fetch'] + props
- result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
- spec = json.loads(result.decode("utf-8"))
- if 'alias' in spec:
- assert not aliased
- return run_config_fetch(spec['alias']['config'],
- spec['alias']['props'] + props,
- aliased=True)
- cmd = [sys.executable, config_path + '.py', 'root']
- result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
- root = json.loads(result.decode("utf-8"))
- return spec, root
- def run(options, spec, root):
- """Perform a checkout with the given type and configuration.
- Args:
- options: Options instance.
- spec: Checkout configuration returned by the the config's fetch_spec
- method (checkout type, repository url, etc.).
- root: The directory into which the repo expects to be checkout out.
- """
- assert 'type' in spec
- checkout_type = spec['type']
- checkout_spec = spec['%s_spec' % checkout_type]
- # Update solutions with protocol_override field
- if options.protocol_override is not None:
- for solution in checkout_spec['solutions']:
- solution['protocol_override'] = options.protocol_override
- try:
- checkout = CheckoutFactory(checkout_type, options, checkout_spec, root)
- except KeyError:
- return 1
- if not options.force and checkout.exists():
- print(
- 'Your current directory appears to already contain, or be part of, '
- )
- print('a checkout. "fetch" is used only to get new checkouts. Use ')
- print('"gclient sync" to update existing checkouts.')
- print()
- print(
- 'Fetch also does not yet deal with partial checkouts, so if fetch')
- print('failed, delete the checkout and start over (crbug.com/230691).')
- return 1
- return checkout.init()
- def main():
- args = handle_args(sys.argv)
- spec, root = run_config_fetch(args.config, args.props)
- return run(args, spec, root)
- if __name__ == '__main__':
- try:
- sys.exit(main())
- except KeyboardInterrupt:
- sys.stderr.write('interrupted\n')
- sys.exit(1)
|