blob: f861ed930a81a257cfedfbb4d345a380b842684e [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
agabled437d762016-10-17 09:35:11 -07006"""Meta checkout dependency manager for Git."""
maruel@chromium.org39c0b222013-08-17 16:57:01 +00007# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
Scott Grahamc4826742017-05-11 16:59:23 -070067# processing the deps_os/hooks_os dict of a DEPS file.
maruel@chromium.org39c0b222013-08-17 16:57:01 +000068#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000079from __future__ import print_function
80
maruel@chromium.org39c0b222013-08-17 16:57:01 +000081__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
szager@chromium.org7b8b6de2014-08-23 00:57:31 +000083import ast
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000084import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000085import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000086import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087import optparse
88import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000089import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000090import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000091import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000094import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000095import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000096import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
maruel@chromium.org35625c72011-03-23 17:34:02 +000098import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020099import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000100import gclient_scm
101import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000102import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000103from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000104import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000105import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000106import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000108CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
109
110
111def ast_dict_index(dnode, key):
112 """Search an ast.Dict for the argument key, and return its index."""
113 idx = [i for i in range(len(dnode.keys)) if (
114 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
115 if not idx:
116 return -1
117 elif len(idx) > 1:
118 raise gclient_utils.Error('Multiple dict entries with same key in AST')
119 return idx[-1]
120
121def ast2str(node, indent=0):
122 """Return a pretty-printed rendition of an ast.Node."""
123 t = type(node)
124 if t is ast.Module:
125 return '\n'.join([ast2str(x, indent) for x in node.body])
126 elif t is ast.Assign:
127 return ((' ' * indent) +
128 ' = '.join([ast2str(x) for x in node.targets] +
129 [ast2str(node.value, indent)]) + '\n')
130 elif t is ast.Name:
131 return node.id
132 elif t is ast.List:
133 if not node.elts:
134 return '[]'
135 elif len(node.elts) == 1:
136 return '[' + ast2str(node.elts[0], indent) + ']'
137 return ('[\n' + (' ' * (indent + 1)) +
138 (',\n' + (' ' * (indent + 1))).join(
139 [ast2str(x, indent + 1) for x in node.elts]) +
140 '\n' + (' ' * indent) + ']')
141 elif t is ast.Dict:
142 if not node.keys:
143 return '{}'
144 elif len(node.keys) == 1:
145 return '{%s: %s}' % (ast2str(node.keys[0]),
146 ast2str(node.values[0], indent + 1))
147 return ('{\n' + (' ' * (indent + 1)) +
148 (',\n' + (' ' * (indent + 1))).join(
149 ['%s: %s' % (ast2str(node.keys[i]),
150 ast2str(node.values[i], indent + 1))
151 for i in range(len(node.keys))]) +
152 '\n' + (' ' * indent) + '}')
153 elif t is ast.Str:
154 return "'%s'" % node.s
155 else:
156 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
157 % (node.lineno, node.col_offset, t))
158
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000159
maruel@chromium.org116704f2010-06-11 17:34:38 +0000160class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000161 class VarImpl(object):
162 def __init__(self, custom_vars, local_scope):
163 self._custom_vars = custom_vars
164 self._local_scope = local_scope
165
166 def Lookup(self, var_name):
167 """Implements the Var syntax."""
168 if var_name in self._custom_vars:
169 return self._custom_vars[var_name]
170 elif var_name in self._local_scope.get("vars", {}):
171 return self._local_scope["vars"][var_name]
172 raise gclient_utils.Error("Var is not defined: %s" % var_name)
173
174
maruel@chromium.org064186c2011-09-27 23:53:33 +0000175class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 """Immutable configuration settings."""
177 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800178 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700179 custom_hooks, deps_file, should_process, relative):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000180 GClientKeywords.__init__(self)
181
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000182 # These are not mutable:
183 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000184 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000185 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 # 'managed' determines whether or not this dependency is synced/updated by
187 # gclient after gclient checks it out initially. The difference between
188 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700189 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 # 'should_process' is dynamically set by gclient if it goes over its
191 # recursion limit and controls gclient's behavior so it does not misbehave.
192 self._managed = managed
193 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700194 # If this is a recursed-upon sub-dependency, and the parent has
195 # use_relative_paths set, then this dependency should check out its own
196 # dependencies relative to that parent's path for this, rather than
197 # relative to the .gclient file.
198 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000199 # This is a mutable value which has the list of 'target_os' OSes listed in
200 # the current deps file.
201 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202
203 # These are only set in .gclient and not in DEPS files.
204 self._custom_vars = custom_vars or {}
205 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000206 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000207
maruel@chromium.org064186c2011-09-27 23:53:33 +0000208 # Post process the url to remove trailing slashes.
209 if isinstance(self._url, basestring):
210 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
211 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000212 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200213 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000214 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200215 ('dependency url must be either string or None, '
216 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000217 # Make any deps_file path platform-appropriate.
218 for sep in ['/', '\\']:
219 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220
221 @property
222 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000223 return self._deps_file
224
225 @property
226 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000227 return self._managed
228
229 @property
230 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000231 return self._parent
232
233 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000234 def root(self):
235 """Returns the root node, a GClient object."""
236 if not self.parent:
237 # This line is to signal pylint that it could be a GClient instance.
238 return self or GClient(None, None)
239 return self.parent.root
240
241 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 def should_process(self):
243 """True if this dependency should be processed, i.e. checked out."""
244 return self._should_process
245
246 @property
247 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000248 return self._custom_vars.copy()
249
250 @property
251 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000252 return self._custom_deps.copy()
253
maruel@chromium.org064186c2011-09-27 23:53:33 +0000254 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000255 def custom_hooks(self):
256 return self._custom_hooks[:]
257
258 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 def url(self):
260 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000261
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000262 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000263 def target_os(self):
264 if self.local_target_os is not None:
265 return tuple(set(self.local_target_os).union(self.parent.target_os))
266 else:
267 return self.parent.target_os
268
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000269 def get_custom_deps(self, name, url):
270 """Returns a custom deps if applicable."""
271 if self.parent:
272 url = self.parent.get_custom_deps(name, url)
273 # None is a valid return value to disable a dependency.
274 return self.custom_deps.get(name, url)
275
maruel@chromium.org064186c2011-09-27 23:53:33 +0000276
277class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000278 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000279
agablea98a6cd2016-11-15 14:30:10 -0800280 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700281 custom_vars, custom_hooks, deps_file, should_process,
282 relative):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000283 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000284 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800285 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700286 custom_hooks, deps_file, should_process, relative)
maruel@chromium.org68988972011-09-20 14:11:42 +0000287
288 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000289 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000290
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000291 self._pre_deps_hooks = []
292
maruel@chromium.org68988972011-09-20 14:11:42 +0000293 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000294 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000295 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000296 # A cache of the files affected by the current operation, necessary for
297 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000298 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000299 # List of host names from which dependencies are allowed.
300 # Default is an empty set, meaning unspecified in DEPS file, and hence all
301 # hosts will be allowed. Non-empty set means whitelist of hosts.
302 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
303 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000304 # If it is not set to True, the dependency wasn't processed for its child
305 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000306 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000307 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000309 # This dependency had its pre-DEPS hooks run
310 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000311 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000312 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000313 # This is the scm used to checkout self.url. It may be used by dependencies
314 # to get the datetime of the revision we checked out.
315 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000316 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000317 # The actual revision we ended up getting, or None if that information is
318 # unavailable
319 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000320
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000321 # This is a mutable value that overrides the normal recursion limit for this
322 # dependency. It is read from the actual DEPS file so cannot be set on
323 # class instantiation.
324 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000325 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000326 # 'no recursion' setting on a dep-by-dep basis. It will replace
327 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000328 #
329 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
330 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000331 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700332 # This is inherited from WorkItem. We want the URL to be a resource.
333 if url and isinstance(url, basestring):
334 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700335 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700336 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000337
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000338 if not self.name and self.parent:
339 raise gclient_utils.Error('Dependency without name')
340
maruel@chromium.org470b5432011-10-11 18:18:19 +0000341 @property
342 def requirements(self):
343 """Calculate the list of requirements."""
344 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000345 # self.parent is implicitly a requirement. This will be recursive by
346 # definition.
347 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000348 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000349
350 # For a tree with at least 2 levels*, the leaf node needs to depend
351 # on the level higher up in an orderly way.
352 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
353 # thus unsorted, while the .gclient format is a list thus sorted.
354 #
355 # * _recursion_limit is hard coded 2 and there is no hope to change this
356 # value.
357 #
358 # Interestingly enough, the following condition only works in the case we
359 # want: self is a 2nd level node. 3nd level node wouldn't need this since
360 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000361 if self.parent and self.parent.parent and not self.parent.parent.parent:
362 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000363
maruel@chromium.org470b5432011-10-11 18:18:19 +0000364 if self.name:
365 requirements |= set(
366 obj.name for obj in self.root.subtree(False)
367 if (obj is not self
368 and obj.name and
369 self.name.startswith(posixpath.join(obj.name, ''))))
370 requirements = tuple(sorted(requirements))
371 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
372 return requirements
373
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000374 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000375 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000376 """Returns False if recursion_override is ever specified."""
377 if self.recursion_override is not None:
378 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000379 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000380
381 @property
382 def recursion_limit(self):
383 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000384 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000385 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000386 if self.try_recursedeps and self.parent.recursedeps != None:
387 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000388 return 1
389
390 if self.recursion_override is not None:
391 return self.recursion_override
392 return max(self.parent.recursion_limit - 1, 0)
393
maruel@chromium.org470b5432011-10-11 18:18:19 +0000394 def verify_validity(self):
395 """Verifies that this Dependency is fine to add as a child of another one.
396
397 Returns True if this entry should be added, False if it is a duplicate of
398 another entry.
399 """
400 logging.info('Dependency(%s).verify_validity()' % self.name)
401 if self.name in [s.name for s in self.parent.dependencies]:
402 raise gclient_utils.Error(
403 'The same name "%s" appears multiple times in the deps section' %
404 self.name)
405 if not self.should_process:
406 # Return early, no need to set requirements.
407 return True
408
409 # This require a full tree traversal with locks.
410 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
411 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000412 self_url = self.LateOverride(self.url)
413 sibling_url = sibling.LateOverride(sibling.url)
414 # Allow to have only one to be None or ''.
415 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000416 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000417 ('Dependency %s specified more than once:\n'
418 ' %s [%s]\n'
419 'vs\n'
420 ' %s [%s]') % (
421 self.name,
422 sibling.hierarchy(),
423 sibling_url,
424 self.hierarchy(),
425 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000426 # In theory we could keep it as a shadow of the other one. In
427 # practice, simply ignore it.
428 logging.warn('Won\'t process duplicate dependency %s' % sibling)
429 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000430 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000431
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000432 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200433 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000434 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000435 parsed_url = self.get_custom_deps(self.name, url)
436 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000437 logging.info(
438 'Dependency(%s).LateOverride(%s) -> %s' %
439 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000440 return parsed_url
441
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000442 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000443 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000444 if (not parsed_url[0] and
445 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000446 # A relative url. Fetch the real base.
447 path = parsed_url[2]
448 if not path.startswith('/'):
449 raise gclient_utils.Error(
450 'relative DEPS entry \'%s\' must begin with a slash' % url)
451 # Create a scm just to query the full url.
452 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000453 scm = gclient_scm.CreateSCM(
454 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000455 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000456 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000457 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000458 logging.info(
459 'Dependency(%s).LateOverride(%s) -> %s' %
460 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000461 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000462
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000463 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000464 logging.info(
465 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000466 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000467
468 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000469
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000470 @staticmethod
471 def MergeWithOsDeps(deps, deps_os, target_os_list):
472 """Returns a new "deps" structure that is the deps sent in updated
473 with information from deps_os (the deps_os section of the DEPS
474 file) that matches the list of target os."""
475 os_overrides = {}
476 for the_target_os in target_os_list:
477 the_target_os_deps = deps_os.get(the_target_os, {})
478 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
479 overrides = os_overrides.setdefault(os_dep_key, [])
480 overrides.append((the_target_os, os_dep_value))
481
482 # If any os didn't specify a value (we have fewer value entries
483 # than in the os list), then it wants to use the default value.
484 for os_dep_key, os_dep_value in os_overrides.iteritems():
485 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700486 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000487 # set it to None or miss a conflicting DEPS.
488 if os_dep_key in deps:
489 os_dep_value.append(('default', deps[os_dep_key]))
490
491 target_os_deps = {}
492 for os_dep_key, os_dep_value in os_overrides.iteritems():
493 # os_dep_value is a list of (os, value) pairs.
494 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
495 if not possible_values:
496 target_os_deps[os_dep_key] = None
497 else:
498 if len(possible_values) > 1:
499 # It would be possible to abort here but it would be
500 # unfortunate if we end up preventing any kind of checkout.
501 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
502 os_dep_key, os_dep_value, target_os_list)
503 # Sorting to get the same result every time in case of conflicts.
504 target_os_deps[os_dep_key] = sorted(possible_values)[0]
505
506 new_deps = deps.copy()
507 new_deps.update(target_os_deps)
508 return new_deps
509
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000510 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000511 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000512 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000513 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000514
515 deps_content = None
516 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000517
518 # First try to locate the configured deps file. If it's missing, fallback
519 # to DEPS.
520 deps_files = [self.deps_file]
521 if 'DEPS' not in deps_files:
522 deps_files.append('DEPS')
523 for deps_file in deps_files:
524 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
525 if os.path.isfile(filepath):
526 logging.info(
527 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
528 filepath)
529 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000530 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000531 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
532 filepath)
533
534 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000535 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000536 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000537 use_strict = 'use strict' in deps_content.splitlines()[0]
538
539 local_scope = {}
540 if deps_content:
541 # One thing is unintuitive, vars = {} must happen before Var() use.
542 var = self.VarImpl(self.custom_vars, local_scope)
543 if use_strict:
544 logging.info(
545 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
546 global_scope = {
547 '__builtins__': {'None': None},
548 'Var': var.Lookup,
549 'deps_os': {},
550 }
551 else:
552 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000553 'Var': var.Lookup,
554 'deps_os': {},
555 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000556 # Eval the content.
557 try:
558 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000559 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000560 gclient_utils.SyntaxErrorToError(filepath, e)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200561 if self._get_option('validate_syntax', False):
562 gclient_eval.Check(deps_content, filepath, global_scope, local_scope)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000563 if use_strict:
564 for key, val in local_scope.iteritems():
565 if not isinstance(val, (dict, list, tuple, str)):
566 raise gclient_utils.Error(
567 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
568 (self.name, key, val))
569
maruel@chromium.org271375b2010-06-23 19:17:38 +0000570 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000571 if 'recursion' in local_scope:
572 self.recursion_override = local_scope.get('recursion')
573 logging.warning(
574 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000575 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000576 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000577 self.recursedeps = {}
578 for ent in local_scope['recursedeps']:
579 if isinstance(ent, basestring):
580 self.recursedeps[ent] = {"deps_file": self.deps_file}
581 else: # (depname, depsfilename)
582 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000583 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000584 # If present, save 'target_os' in the local_target_os property.
585 if 'target_os' in local_scope:
586 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000587 # load os specific dependencies if defined. these dependencies may
588 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000589 target_os_list = self.target_os
590 if 'deps_os' in local_scope and target_os_list:
591 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000592
maruel@chromium.org271375b2010-06-23 19:17:38 +0000593 # If a line is in custom_deps, but not in the solution, we want to append
594 # this line to the solution.
595 for d in self.custom_deps:
596 if d not in deps:
597 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000598
599 # If use_relative_paths is set in the DEPS file, regenerate
600 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000601 # the DEPS file. Also update recursedeps if use_relative_paths is
602 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700603 # If the deps file doesn't set use_relative_paths, but the parent did
604 # (and therefore set self.relative on this Dependency object), then we
605 # want to modify the deps and recursedeps by prepending the parent
606 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000607 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700608 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000609 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700610 rel_prefix = self.name
611 elif self._relative:
612 rel_prefix = os.path.dirname(self.name)
613 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000614 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000615 rel_deps = {}
616 for d, url in deps.items():
617 # normpath is required to allow DEPS to use .. in their
618 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700619 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
620 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000621 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000622
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000623 # Update recursedeps if it's set.
624 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700625 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000626 rel_deps = {}
627 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700628 rel_deps[
629 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000630 self.recursedeps = rel_deps
631
agabledce6ddc2016-09-08 10:02:16 -0700632
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000633 if 'allowed_hosts' in local_scope:
634 try:
635 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
636 except TypeError: # raised if non-iterable
637 pass
638 if not self._allowed_hosts:
639 logging.warning("allowed_hosts is specified but empty %s",
640 self._allowed_hosts)
641 raise gclient_utils.Error(
642 'ParseDepsFile(%s): allowed_hosts must be absent '
643 'or a non-empty iterable' % self.name)
644
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000645 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000646 deps_to_add = []
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200647 for name, dep_value in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000648 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000649 deps_file = self.deps_file
650 if self.recursedeps is not None:
651 ent = self.recursedeps.get(name)
652 if ent is not None:
653 deps_file = ent['deps_file']
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200654 if isinstance(dep_value, basestring):
655 url = dep_value
656 else:
657 # This should be guaranteed by schema checking in gclient_eval.
658 assert isinstance(dep_value, dict)
659 url = dep_value['url']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000660 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800661 self, name, url, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700662 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000663 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000664
665 # override named sets of hooks by the custom hooks
666 hooks_to_run = []
667 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
668 for hook in local_scope.get('hooks', []):
669 if hook.get('name', '') not in hook_names_to_suppress:
670 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700671 if 'hooks_os' in local_scope and target_os_list:
672 hooks_os = local_scope['hooks_os']
673 # Specifically append these to ensure that hooks_os run after hooks.
674 for the_target_os in target_os_list:
675 the_target_os_hooks = hooks_os.get(the_target_os, [])
676 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000677
678 # add the replacements and any additions
679 for hook in self.custom_hooks:
680 if 'action' in hook:
681 hooks_to_run.append(hook)
682
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800683 if self.recursion_limit:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200684 self._pre_deps_hooks = [self.GetHookAction(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800685 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000686
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000687 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000688 logging.info('ParseDepsFile(%s) done' % self.name)
689
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200690 def _get_option(self, attr, default):
691 obj = self
692 while not hasattr(obj, '_options'):
693 obj = obj.parent
694 return getattr(obj._options, attr, default)
695
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000696 def add_dependencies_and_close(self, deps_to_add, hooks):
697 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000698 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000699 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000700 self.add_dependency(dep)
701 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000702
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000703 def findDepsFromNotAllowedHosts(self):
704 """Returns a list of depenecies from not allowed hosts.
705
706 If allowed_hosts is not set, allows all hosts and returns empty list.
707 """
708 if not self._allowed_hosts:
709 return []
710 bad_deps = []
711 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000712 # Don't enforce this for custom_deps.
713 if dep.name in self._custom_deps:
714 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000715 if isinstance(dep.url, basestring):
716 parsed_url = urlparse.urlparse(dep.url)
717 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
718 bad_deps.append(dep)
719 return bad_deps
720
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000721 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800722 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000723 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000724 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000725 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000726 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000727 if not self.should_process:
728 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000729 # When running runhooks, there's no need to consult the SCM.
730 # All known hooks are expected to run unconditionally regardless of working
731 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200732 run_scm = command not in (
733 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000734 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000735 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000736 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000737 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700738 # Create a shallow copy to mutate revision.
739 options = copy.copy(options)
740 options.revision = revision_override
741 self._used_revision = options.revision
742 self._used_scm = gclient_scm.CreateSCM(
743 parsed_url, self.root.root_dir, self.name, self.outbuf,
744 out_cb=work_queue.out_cb)
745 self._got_revision = self._used_scm.RunCommand(command, options, args,
746 file_list)
747 if file_list:
748 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000749
750 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
751 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000752 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000753 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000754 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000755 continue
756 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000757 [self.root.root_dir.lower(), file_list[i].lower()])
758 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000759 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000760 while file_list[i].startswith(('\\', '/')):
761 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000762
763 # Always parse the DEPS file.
764 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000765 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000766 if command in ('update', 'revert') and not options.noprehooks:
767 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000768
769 if self.recursion_limit:
770 # Parse the dependencies of this dependency.
771 for s in self.dependencies:
772 work_queue.enqueue(s)
773
774 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700775 # Skip file only checkout.
776 scm = gclient_scm.GetScmName(parsed_url)
777 if not options.scm or scm in options.scm:
778 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
779 # Pass in the SCM type as an env variable. Make sure we don't put
780 # unicode strings in the environment.
781 env = os.environ.copy()
782 if scm:
783 env['GCLIENT_SCM'] = str(scm)
784 if parsed_url:
785 env['GCLIENT_URL'] = str(parsed_url)
786 env['GCLIENT_DEP_PATH'] = str(self.name)
787 if options.prepend_dir and scm == 'git':
788 print_stdout = False
789 def filter_fn(line):
790 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000791
agabled437d762016-10-17 09:35:11 -0700792 def mod_path(git_pathspec):
793 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
794 modified_path = os.path.join(self.name, match.group(2))
795 branch = match.group(1) or ''
796 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000797
agabled437d762016-10-17 09:35:11 -0700798 match = re.match('^Binary file ([^\0]+) matches$', line)
799 if match:
800 print('Binary file %s matches\n' % mod_path(match.group(1)))
801 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000802
agabled437d762016-10-17 09:35:11 -0700803 items = line.split('\0')
804 if len(items) == 2 and items[1]:
805 print('%s : %s' % (mod_path(items[0]), items[1]))
806 elif len(items) >= 2:
807 # Multiple null bytes or a single trailing null byte indicate
808 # git is likely displaying filenames only (such as with -l)
809 print('\n'.join(mod_path(path) for path in items if path))
810 else:
811 print(line)
812 else:
813 print_stdout = True
814 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000815
agabled437d762016-10-17 09:35:11 -0700816 if parsed_url is None:
817 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
818 elif os.path.isdir(cwd):
819 try:
820 gclient_utils.CheckCallAndFilter(
821 args, cwd=cwd, env=env, print_stdout=print_stdout,
822 filter_fn=filter_fn,
823 )
824 except subprocess2.CalledProcessError:
825 if not options.ignore:
826 raise
827 else:
828 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000829
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000831 @gclient_utils.lockedmethod
832 def _run_is_done(self, file_list, parsed_url):
833 # Both these are kept for hooks that are run as a separate tree traversal.
834 self._file_list = file_list
835 self._parsed_url = parsed_url
836 self._processed = True
837
szager@google.comb9a78d32012-03-13 18:46:21 +0000838 @staticmethod
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200839 def GetHookAction(hook_dict):
szager@google.comb9a78d32012-03-13 18:46:21 +0000840 """Turns a parsed 'hook' dict into an executable command."""
841 logging.debug(hook_dict)
szager@google.comb9a78d32012-03-13 18:46:21 +0000842 command = hook_dict['action'][:]
843 if command[0] == 'python':
844 # If the hook specified "python" as the first item, the action is a
845 # Python script. Run it by starting a new copy of the same
846 # interpreter.
847 command[0] = sys.executable
szager@google.comb9a78d32012-03-13 18:46:21 +0000848 return command
849
850 def GetHooks(self, options):
851 """Evaluates all hooks, and return them in a flat list.
852
853 RunOnDeps() must have been called before to load the DEPS.
854 """
855 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000856 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000857 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000858 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000859 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000860 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000861 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700862 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000863 # what files have changed so we always run all hooks. It'd be nice to fix
864 # that.
865 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000866 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000867 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000868 for hook_dict in self.deps_hooks:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200869 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000870 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000871 # Run hooks on the basis of whether the files from the gclient operation
872 # match each hook's pattern.
873 for hook_dict in self.deps_hooks:
874 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000875 matching_file_list = [
876 f for f in self.file_list_and_children if pattern.search(f)
877 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000878 if matching_file_list:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200879 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000880 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000881 result.extend(s.GetHooks(options))
882 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000883
szager@google.comb9a78d32012-03-13 18:46:21 +0000884 def RunHooksRecursively(self, options):
885 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000886 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000887 for hook in self.GetHooks(options):
888 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000889 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000890 gclient_utils.CheckCallAndFilterAndHeader(
891 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000892 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000893 # Use a discrete exit status code of 2 to indicate that a hook action
894 # failed. Users of this script may wish to treat hook action failures
895 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000896 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000897 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000898 finally:
899 elapsed_time = time.time() - start_time
900 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000901 print("Hook '%s' took %.2f secs" % (
902 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000903
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000904 def RunPreDepsHooks(self):
905 assert self.processed
906 assert self.deps_parsed
907 assert not self.pre_deps_hooks_ran
908 assert not self.hooks_ran
909 for s in self.dependencies:
910 assert not s.processed
911 self._pre_deps_hooks_ran = True
912 for hook in self.pre_deps_hooks:
913 try:
914 start_time = time.time()
915 gclient_utils.CheckCallAndFilterAndHeader(
916 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000917 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000918 # Use a discrete exit status code of 2 to indicate that a hook action
919 # failed. Users of this script may wish to treat hook action failures
920 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000921 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000922 sys.exit(2)
923 finally:
924 elapsed_time = time.time() - start_time
925 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000926 print("Hook '%s' took %.2f secs" % (
927 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000928
929
maruel@chromium.org0d812442010-08-10 12:41:08 +0000930 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000931 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000932 dependencies = self.dependencies
933 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000934 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000935 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000936 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000937 for i in d.subtree(include_all):
938 yield i
939
940 def depth_first_tree(self):
941 """Depth-first recursion including the root node."""
942 yield self
943 for i in self.dependencies:
944 for j in i.depth_first_tree():
945 if j.should_process:
946 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000947
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000948 @gclient_utils.lockedmethod
949 def add_dependency(self, new_dep):
950 self._dependencies.append(new_dep)
951
952 @gclient_utils.lockedmethod
953 def _mark_as_parsed(self, new_hooks):
954 self._deps_hooks.extend(new_hooks)
955 self._deps_parsed = True
956
maruel@chromium.org68988972011-09-20 14:11:42 +0000957 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000958 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000959 def dependencies(self):
960 return tuple(self._dependencies)
961
962 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000963 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000964 def deps_hooks(self):
965 return tuple(self._deps_hooks)
966
967 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000968 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000969 def pre_deps_hooks(self):
970 return tuple(self._pre_deps_hooks)
971
972 @property
973 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000974 def parsed_url(self):
975 return self._parsed_url
976
977 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000978 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000979 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000980 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000981 return self._deps_parsed
982
983 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000984 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000985 def processed(self):
986 return self._processed
987
988 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000989 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000990 def pre_deps_hooks_ran(self):
991 return self._pre_deps_hooks_ran
992
993 @property
994 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000995 def hooks_ran(self):
996 return self._hooks_ran
997
998 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000999 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001000 def allowed_hosts(self):
1001 return self._allowed_hosts
1002
1003 @property
1004 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001005 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001006 return tuple(self._file_list)
1007
1008 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001009 def used_scm(self):
1010 """SCMWrapper instance for this dependency or None if not processed yet."""
1011 return self._used_scm
1012
1013 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001014 @gclient_utils.lockedmethod
1015 def got_revision(self):
1016 return self._got_revision
1017
1018 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001019 def file_list_and_children(self):
1020 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001021 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001022 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001023 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001024
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001025 def __str__(self):
1026 out = []
agablea98a6cd2016-11-15 14:30:10 -08001027 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001028 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001029 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1030 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001031 # First try the native property if it exists.
1032 if hasattr(self, '_' + i):
1033 value = getattr(self, '_' + i, False)
1034 else:
1035 value = getattr(self, i, False)
1036 if value:
1037 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001038
1039 for d in self.dependencies:
1040 out.extend([' ' + x for x in str(d).splitlines()])
1041 out.append('')
1042 return '\n'.join(out)
1043
1044 def __repr__(self):
1045 return '%s: %s' % (self.name, self.url)
1046
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001047 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001048 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001049 def format_name(d):
1050 if include_url:
1051 return '%s(%s)' % (d.name, d.url)
1052 return d.name
1053 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001054 i = self.parent
1055 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001056 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001057 i = i.parent
1058 return out
1059
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001060
1061class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001062 """Object that represent a gclient checkout. A tree of Dependency(), one per
1063 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001064
1065 DEPS_OS_CHOICES = {
1066 "win32": "win",
1067 "win": "win",
1068 "cygwin": "win",
1069 "darwin": "mac",
1070 "mac": "mac",
1071 "unix": "unix",
1072 "linux": "unix",
1073 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001074 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001075 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001076 }
1077
1078 DEFAULT_CLIENT_FILE_TEXT = ("""\
1079solutions = [
smutae7ea312016-07-18 11:59:41 -07001080 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001081 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001082 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001083 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001084 "custom_deps" : {
1085 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001086 },
1087]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001088cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001089""")
1090
1091 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001092 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001093 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001094 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001095 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001096 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001097%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001098 },
1099""")
1100
1101 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1102# Snapshot generated with gclient revinfo --snapshot
1103solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001104%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001105""")
1106
1107 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001108 # Do not change previous behavior. Only solution level and immediate DEPS
1109 # are processed.
1110 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001111 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001112 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001113 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001114 if options.deps_os:
1115 enforced_os = options.deps_os.split(',')
1116 else:
1117 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1118 if 'all' in enforced_os:
1119 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001120 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001121 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001122 self.config_content = None
1123
borenet@google.com88d10082014-03-21 17:24:48 +00001124 def _CheckConfig(self):
1125 """Verify that the config matches the state of the existing checked-out
1126 solutions."""
1127 for dep in self.dependencies:
1128 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001129 scm = gclient_scm.CreateSCM(
1130 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001131 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001132 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001133 mirror = scm.GetCacheMirror()
1134 if mirror:
1135 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1136 mirror.exists())
1137 else:
1138 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001139 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001140Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001141is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001142
borenet@google.com97882362014-04-07 20:06:02 +00001143The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001144URL: %(expected_url)s (%(expected_scm)s)
1145Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001146
1147The local checkout in %(checkout_path)s reports:
1148%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001149
1150You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001151it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001152''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1153 'expected_url': dep.url,
1154 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001155 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001156 'actual_url': actual_url,
1157 'actual_scm': gclient_scm.GetScmName(actual_url)})
1158
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001159 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001160 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161 config_dict = {}
1162 self.config_content = content
1163 try:
1164 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001165 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001166 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001167
peter@chromium.org1efccc82012-04-27 16:34:38 +00001168 # Append any target OS that is not already being enforced to the tuple.
1169 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001170 if config_dict.get('target_os_only', False):
1171 self._enforced_os = tuple(set(target_os))
1172 else:
1173 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1174
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001175 cache_dir = config_dict.get('cache_dir')
1176 if cache_dir:
1177 cache_dir = os.path.join(self.root_dir, cache_dir)
1178 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001179 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001180 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001181 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1182 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001183 gclient_scm.GitWrapper.cache_dir = cache_dir
1184 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001185
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001186 if not target_os and config_dict.get('target_os_only', False):
1187 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1188 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001189
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001190 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001191 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001192 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001193 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001194 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001195 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001196 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001197 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001198 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001199 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001200 True,
1201 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001202 except KeyError:
1203 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1204 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001205 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1206 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001207
1208 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001209 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001210 self._options.config_filename),
1211 self.config_content)
1212
1213 @staticmethod
1214 def LoadCurrentConfig(options):
1215 """Searches for and loads a .gclient file relative to the current working
1216 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001217 if options.spec:
1218 client = GClient('.', options)
1219 client.SetConfig(options.spec)
1220 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001221 if options.verbose:
1222 print('Looking for %s starting from %s\n' % (
1223 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001224 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1225 if not path:
1226 return None
1227 client = GClient(path, options)
1228 client.SetConfig(gclient_utils.FileRead(
1229 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001230
1231 if (options.revisions and
1232 len(client.dependencies) > 1 and
1233 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001234 print(
1235 ('You must specify the full solution name like --revision %s@%s\n'
1236 'when you have multiple solutions setup in your .gclient file.\n'
1237 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001238 client.dependencies[0].name,
1239 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001240 ', '.join(s.name for s in client.dependencies[1:])),
1241 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001242 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001243
nsylvain@google.comefc80932011-05-31 21:27:56 +00001244 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001245 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001246 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1247 'solution_name': solution_name,
1248 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001249 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001250 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001251 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001252 })
1253
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001254 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001255 """Creates a .gclient_entries file to record the list of unique checkouts.
1256
1257 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001258 """
1259 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1260 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001261 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001262 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001263 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1264 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001265 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001266 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001267 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001268 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001269
1270 def _ReadEntries(self):
1271 """Read the .gclient_entries file for the given client.
1272
1273 Returns:
1274 A sequence of solution names, which will be empty if there is the
1275 entries file hasn't been created yet.
1276 """
1277 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001278 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001279 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001280 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001281 try:
1282 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001283 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001284 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001285 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001286
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001287 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001288 """Checks for revision overrides."""
1289 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001290 if self._options.head:
1291 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001292 if not self._options.revisions:
1293 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001294 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001295 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001296 if not self._options.revisions:
1297 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001298 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001299 index = 0
1300 for revision in self._options.revisions:
1301 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001302 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001303 revision = '%s@%s' % (solutions_names[index], revision)
1304 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001305 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001306 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001307 return revision_overrides
1308
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001309 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001310 """Runs a command on each dependency in a client and its dependencies.
1311
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001312 Args:
1313 command: The command to use (e.g., 'status' or 'diff')
1314 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001315 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001316 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001317 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001318
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001319 revision_overrides = {}
1320 # It's unnecessary to check for revision overrides for 'recurse'.
1321 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001322 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1323 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001324 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001325 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001326 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001327 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001328 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001329 if command in ('update', 'revert'):
1330 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001331 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001332 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001333 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001334 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1335 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001336 for s in self.dependencies:
1337 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001338 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001339 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001340 print('Please fix your script, having invalid --revision flags will soon '
1341 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001342
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001343 # Once all the dependencies have been processed, it's now safe to run the
1344 # hooks.
1345 if not self._options.nohooks:
1346 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001347
1348 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001349 # Notify the user if there is an orphaned entry in their working copy.
1350 # Only delete the directory if there are no changes in it, and
1351 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001352 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001353 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1354 for e in entries]
1355
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001356 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001357 if not prev_url:
1358 # entry must have been overridden via .gclient custom_deps
1359 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001360 # Fix path separator on Windows.
1361 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001362 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001363 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001364 if (entry not in entries and
1365 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001366 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001367 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001368 scm = gclient_scm.CreateSCM(
1369 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001370
1371 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001372 scm_root = None
agabled437d762016-10-17 09:35:11 -07001373 try:
1374 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1375 except subprocess2.CalledProcessError:
1376 pass
1377 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001378 logging.warning('Could not find checkout root for %s. Unable to '
1379 'determine whether it is part of a higher-level '
1380 'checkout, so not removing.' % entry)
1381 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001382
1383 # This is to handle the case of third_party/WebKit migrating from
1384 # being a DEPS entry to being part of the main project.
1385 # If the subproject is a Git project, we need to remove its .git
1386 # folder. Otherwise git operations on that folder will have different
1387 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001388 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001389 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001390 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1391 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001392 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1393 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001394 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1395 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001396 save_dir = scm.GetGitBackupDirPath()
1397 # Remove any eventual stale backup dir for the same project.
1398 if os.path.exists(save_dir):
1399 gclient_utils.rmtree(save_dir)
1400 os.rename(os.path.join(e_dir, '.git'), save_dir)
1401 # When switching between the two states (entry/ is a subproject
1402 # -> entry/ is part of the outer project), it is very likely
1403 # that some files are changed in the checkout, unless we are
1404 # jumping *exactly* across the commit which changed just DEPS.
1405 # In such case we want to cleanup any eventual stale files
1406 # (coming from the old subproject) in order to end up with a
1407 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001408 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001409 assert not os.path.exists(os.path.join(e_dir, '.git'))
1410 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1411 'level checkout. The git folder containing all the local'
1412 ' branches has been saved to %s.\n'
1413 'If you don\'t care about its state you can safely '
1414 'remove that folder to free up space.') %
1415 (entry, save_dir))
1416 continue
1417
borenet@google.com359bb642014-05-13 17:28:19 +00001418 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001419 logging.info('%s is part of a higher level checkout, not removing',
1420 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001421 continue
1422
1423 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001424 scm.status(self._options, [], file_list)
1425 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001426 if (not self._options.delete_unversioned_trees or
1427 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001428 # There are modified files in this entry. Keep warning until
1429 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001430 print(('\nWARNING: \'%s\' is no longer part of this client. '
1431 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001432 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001433 else:
1434 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001435 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001436 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001437 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001438 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001439 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001440 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001441
1442 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001443 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001444 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001445 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001446 work_queue = gclient_utils.ExecutionQueue(
1447 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001448 for s in self.dependencies:
1449 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001450 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001451
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001452 def GetURLAndRev(dep):
1453 """Returns the revision-qualified SCM url for a Dependency."""
1454 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001455 return None
agabled437d762016-10-17 09:35:11 -07001456 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001457 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001458 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001459 if not os.path.isdir(scm.checkout_path):
1460 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001461 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001462
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001463 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001464 new_gclient = ''
1465 # First level at .gclient
1466 for d in self.dependencies:
1467 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001468 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001469 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001470 for d in dep.dependencies:
1471 entries[d.name] = GetURLAndRev(d)
1472 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001473 GrabDeps(d)
1474 custom_deps = []
1475 for k in sorted(entries.keys()):
1476 if entries[k]:
1477 # Quotes aren't escaped...
1478 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1479 else:
1480 custom_deps.append(' \"%s\": None,\n' % k)
1481 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1482 'solution_name': d.name,
1483 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001484 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001485 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001486 'solution_deps': ''.join(custom_deps),
1487 }
1488 # Print the snapshot configuration file
1489 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001490 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001491 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001492 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001493 if self._options.actual:
1494 entries[d.name] = GetURLAndRev(d)
1495 else:
1496 entries[d.name] = d.parsed_url
1497 keys = sorted(entries.keys())
1498 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001499 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001500 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001501
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001502 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001503 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001504 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001505
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001506 def PrintLocationAndContents(self):
1507 # Print out the .gclient file. This is longer than if we just printed the
1508 # client dict, but more legible, and it might contain helpful comments.
1509 print('Loaded .gclient config in %s:\n%s' % (
1510 self.root_dir, self.config_content))
1511
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001512 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001513 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001514 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001515 return self._root_dir
1516
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001517 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001518 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001519 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001520 return self._enforced_os
1521
maruel@chromium.org68988972011-09-20 14:11:42 +00001522 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001523 def recursion_limit(self):
1524 """How recursive can each dependencies in DEPS file can load DEPS file."""
1525 return self._recursion_limit
1526
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001527 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001528 def try_recursedeps(self):
1529 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001530 return True
1531
1532 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001533 def target_os(self):
1534 return self._enforced_os
1535
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001536
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001537#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001538
1539
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001540@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001541def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001542 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001543
1544 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001545 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001546 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001547 """
1548 # Stop parsing at the first non-arg so that these go through to the command
1549 parser.disable_interspersed_args()
1550 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001551 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001552 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001553 help='Ignore non-zero return codes from subcommands.')
1554 parser.add_option('--prepend-dir', action='store_true',
1555 help='Prepend relative dir for use with git <cmd> --null.')
1556 parser.add_option('--no-progress', action='store_true',
1557 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001558 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001559 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001560 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001561 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001562 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1563 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001564 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001565 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001566 'This is because .gclient_entries needs to exist and be up to date.',
1567 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001568 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001569
1570 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001571 scm_set = set()
1572 for scm in options.scm:
1573 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001574 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001575
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001576 options.nohooks = True
1577 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001578 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1579 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001580
1581
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001582@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001583def CMDfetch(parser, args):
1584 """Fetches upstream commits for all modules.
1585
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001586 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1587 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001588 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001589 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001590 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1591
1592
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001593def CMDflatten(parser, args):
1594 """Flattens the solutions into a single DEPS file."""
1595 parser.add_option('--output-deps', help='Path to the output DEPS file')
1596 parser.add_option(
1597 '--require-pinned-revisions', action='store_true',
1598 help='Fail if any of the dependencies uses unpinned revision.')
1599 options, args = parser.parse_args(args)
1600
1601 options.nohooks = True
1602 client = GClient.LoadCurrentConfig(options)
1603
1604 # Only print progress if we're writing to a file. Otherwise, progress updates
1605 # could obscure intended output.
1606 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1607 if code != 0:
1608 return code
1609
1610 deps = {}
1611 hooks = []
1612 pre_deps_hooks = []
1613 unpinned_deps = {}
1614
1615 for solution in client.dependencies:
1616 _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1617
1618 if options.require_pinned_revisions and unpinned_deps:
1619 sys.stderr.write('The following dependencies are not pinned:\n')
1620 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1621 return 1
1622
1623 flattened_deps = '\n'.join(
1624 _DepsToLines(deps) +
1625 _HooksToLines('hooks', hooks) +
1626 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1627 [''] # Ensure newline at end of file.
1628 )
1629
1630 if options.output_deps:
1631 with open(options.output_deps, 'w') as f:
1632 f.write(flattened_deps)
1633 else:
1634 print(flattened_deps)
1635
1636 return 0
1637
1638
1639def _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps):
1640 """Visits a solution in order to flatten it (see CMDflatten).
1641
1642 Arguments:
1643 solution (Dependency): one of top-level solutions in .gclient
1644
1645 Out-parameters:
1646 deps (dict of name -> Dependency): will be filled with all Dependency
1647 objects indexed by their name
1648 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1649 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1650 pre_deps_hooks
1651 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1652 deps
1653 """
1654 logging.debug('_FlattenSolution(%r)', solution)
1655
1656 _FlattenDep(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1657 _FlattenRecurse(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1658
1659
1660def _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1661 """Visits a dependency in order to flatten it (see CMDflatten).
1662
1663 Arguments:
1664 dep (Dependency): dependency to process
1665
1666 Out-parameters:
1667 deps (dict): will be filled with flattened deps
1668 hooks (list): will be filled with flattened hooks
1669 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1670 unpinned_deps (dict): will be filled with unpinned deps
1671 """
1672 logging.debug('_FlattenDep(%r)', dep)
1673
1674 _AddDep(dep, deps, unpinned_deps)
1675
1676 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1677 for recurse_dep_name in (dep.recursedeps or []):
1678 _FlattenRecurse(
1679 deps_by_name[recurse_dep_name], deps, hooks, pre_deps_hooks,
1680 unpinned_deps)
1681
1682 # TODO(phajdan.jr): also handle hooks_os.
1683 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1684 pre_deps_hooks.extend(
1685 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1686
1687
1688def _FlattenRecurse(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1689 """Helper for flatten that recurses into |dep|'s dependencies.
1690
1691 Arguments:
1692 dep (Dependency): dependency to process
1693
1694 Out-parameters:
1695 deps (dict): will be filled with flattened deps
1696 hooks (list): will be filled with flattened hooks
1697 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1698 unpinned_deps (dict): will be filled with unpinned deps
1699 """
1700 logging.debug('_FlattenRecurse(%r)', dep)
1701
1702 # TODO(phajdan.jr): also handle deps_os.
1703 for dep in dep.dependencies:
1704 _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps)
1705
1706
1707def _AddDep(dep, deps, unpinned_deps):
1708 """Helper to add a dependency to flattened lists.
1709
1710 Arguments:
1711 dep (Dependency): dependency to process
1712
1713 Out-parameters:
1714 deps (dict): will be filled with flattened deps
1715 unpinned_deps (dict): will be filled with unpinned deps
1716 """
1717 logging.debug('_AddDep(%r)', dep)
1718
1719 assert dep.name not in deps
1720 deps[dep.name] = dep
1721
1722 # Detect unpinned deps.
1723 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1724 if not revision or not gclient_utils.IsGitSha(revision):
1725 unpinned_deps[dep.name] = dep
1726
1727
1728def _DepsToLines(deps):
1729 """Converts |deps| dict to list of lines for output."""
1730 s = ['deps = {']
1731 for name, dep in sorted(deps.iteritems()):
1732 s.extend([
1733 ' # %s' % dep.hierarchy(include_url=False),
1734 ' "%s": "%s",' % (name, dep.url),
1735 '',
1736 ])
1737 s.extend(['}', ''])
1738 return s
1739
1740
1741def _HooksToLines(name, hooks):
1742 """Converts |hooks| list to list of lines for output."""
1743 s = ['%s = [' % name]
1744 for dep, hook in hooks:
1745 s.extend([
1746 ' # %s' % dep.hierarchy(include_url=False),
1747 ' {',
1748 ])
1749 if 'name' in hook:
1750 s.append(' "name": "%s",' % hook['name'])
1751 if 'pattern' in hook:
1752 s.append(' "pattern": "%s",' % hook['pattern'])
1753 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1754 # i.e. they may be relative to the dependency path, not solution root.
1755 s.extend(
1756 [' "action": ['] +
1757 [' "%s",' % arg for arg in hook['action']] +
1758 [' ]', ' },', '']
1759 )
1760 s.extend([']', ''])
1761 return s
1762
1763
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001764def CMDgrep(parser, args):
1765 """Greps through git repos managed by gclient.
1766
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001767 Runs 'git grep [args...]' for each module.
1768 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001769 # We can't use optparse because it will try to parse arguments sent
1770 # to git grep and throw an error. :-(
1771 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001772 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001773 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1774 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1775 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1776 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001777 ' end of your query.',
1778 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001779 return 1
1780
1781 jobs_arg = ['--jobs=1']
1782 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1783 jobs_arg, args = args[:1], args[1:]
1784 elif re.match(r'(-j|--jobs)$', args[0]):
1785 jobs_arg, args = args[:2], args[2:]
1786
1787 return CMDrecurse(
1788 parser,
1789 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1790 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001791
1792
stip@chromium.orga735da22015-04-29 23:18:20 +00001793def CMDroot(parser, args):
1794 """Outputs the solution root (or current dir if there isn't one)."""
1795 (options, args) = parser.parse_args(args)
1796 client = GClient.LoadCurrentConfig(options)
1797 if client:
1798 print(os.path.abspath(client.root_dir))
1799 else:
1800 print(os.path.abspath('.'))
1801
1802
agablea98a6cd2016-11-15 14:30:10 -08001803@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001804def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001805 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001806
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001807 This specifies the configuration for further commands. After update/sync,
1808 top-level DEPS files in each module are read to determine dependent
1809 modules to operate on as well. If optional [url] parameter is
1810 provided, then configuration is read from a specified Subversion server
1811 URL.
1812 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001813 # We do a little dance with the --gclientfile option. 'gclient config' is the
1814 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1815 # arguments. So, we temporarily stash any --gclientfile parameter into
1816 # options.output_config_file until after the (gclientfile xor spec) error
1817 # check.
1818 parser.remove_option('--gclientfile')
1819 parser.add_option('--gclientfile', dest='output_config_file',
1820 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001821 parser.add_option('--name',
1822 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001823 parser.add_option('--deps-file', default='DEPS',
1824 help='overrides the default name for the DEPS file for the'
1825 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001826 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001827 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001828 'to have the main solution untouched by gclient '
1829 '(gclient will check out unmanaged dependencies but '
1830 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001831 parser.add_option('--cache-dir',
1832 help='(git only) Cache all git repos into this dir and do '
1833 'shared clones from the cache, instead of cloning '
1834 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001835 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001836 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001837 if options.output_config_file:
1838 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001839 if ((options.spec and args) or len(args) > 2 or
1840 (not options.spec and not args)):
1841 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1842
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001843 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001844 if options.spec:
1845 client.SetConfig(options.spec)
1846 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001847 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001848 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001849 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001850 if name.endswith('.git'):
1851 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001852 else:
1853 # specify an alternate relpath for the given URL.
1854 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001855 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1856 os.getcwd()):
1857 parser.error('Do not pass a relative path for --name.')
1858 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1859 parser.error('Do not include relative path components in --name.')
1860
nsylvain@google.comefc80932011-05-31 21:27:56 +00001861 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001862 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001863 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001864 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001865 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001866 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001867
1868
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001869@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001870 gclient pack > patch.txt
1871 generate simple patch for configured client and dependences
1872""")
1873def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001874 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001875
agabled437d762016-10-17 09:35:11 -07001876 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001877 dependencies, and performs minimal postprocessing of the output. The
1878 resulting patch is printed to stdout and can be applied to a freshly
1879 checked out tree via 'patch -p0 < patchfile'.
1880 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001881 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1882 help='override deps for the specified (comma-separated) '
1883 'platform(s); \'all\' will process all deps_os '
1884 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001885 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001886 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001887 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001888 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001889 client = GClient.LoadCurrentConfig(options)
1890 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001891 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001892 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001893 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001894 return client.RunOnDeps('pack', args)
1895
1896
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001897def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001898 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001899 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1900 help='override deps for the specified (comma-separated) '
1901 'platform(s); \'all\' will process all deps_os '
1902 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001903 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001904 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001905 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001906 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001907 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001908 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001909 return client.RunOnDeps('status', args)
1910
1911
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001912@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001913 gclient sync
1914 update files from SCM according to current configuration,
1915 *for modules which have changed since last update or sync*
1916 gclient sync --force
1917 update files from SCM according to current configuration, for
1918 all modules (useful for recovering files deleted from local copy)
1919 gclient sync --revision src@31000
1920 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001921
1922JSON output format:
1923If the --output-json option is specified, the following document structure will
1924be emitted to the provided file. 'null' entries may occur for subprojects which
1925are present in the gclient solution, but were not processed (due to custom_deps,
1926os_deps, etc.)
1927
1928{
1929 "solutions" : {
1930 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001931 "revision": [<git id hex string>|null],
1932 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001933 }
1934 }
1935}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001936""")
1937def CMDsync(parser, args):
1938 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001939 parser.add_option('-f', '--force', action='store_true',
1940 help='force update even for unchanged modules')
1941 parser.add_option('-n', '--nohooks', action='store_true',
1942 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001943 parser.add_option('-p', '--noprehooks', action='store_true',
1944 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001945 parser.add_option('-r', '--revision', action='append',
1946 dest='revisions', metavar='REV', default=[],
1947 help='Enforces revision/hash for the solutions with the '
1948 'format src@rev. The src@ part is optional and can be '
1949 'skipped. -r can be used multiple times when .gclient '
1950 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001951 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001952 parser.add_option('--with_branch_heads', action='store_true',
1953 help='Clone git "branch_heads" refspecs in addition to '
1954 'the default refspecs. This adds about 1/2GB to a '
1955 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001956 parser.add_option('--with_tags', action='store_true',
1957 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001958 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08001959 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001960 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001961 help='Deletes from the working copy any dependencies that '
1962 'have been removed since the last sync, as long as '
1963 'there are no local modifications. When used with '
1964 '--force, such dependencies are removed even if they '
1965 'have local modifications. When used with --reset, '
1966 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001967 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001968 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001969 parser.add_option('-R', '--reset', action='store_true',
1970 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001971 parser.add_option('-M', '--merge', action='store_true',
1972 help='merge upstream changes instead of trying to '
1973 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001974 parser.add_option('-A', '--auto_rebase', action='store_true',
1975 help='Automatically rebase repositories against local '
1976 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001977 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1978 help='override deps for the specified (comma-separated) '
1979 'platform(s); \'all\' will process all deps_os '
1980 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001981 parser.add_option('--upstream', action='store_true',
1982 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001983 parser.add_option('--output-json',
1984 help='Output a json document to this path containing '
1985 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001986 parser.add_option('--no-history', action='store_true',
1987 help='GIT ONLY - Reduces the size/time of the checkout at '
1988 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001989 parser.add_option('--shallow', action='store_true',
1990 help='GIT ONLY - Do a shallow clone into the cache dir. '
1991 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001992 parser.add_option('--no_bootstrap', '--no-bootstrap',
1993 action='store_true',
1994 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001995 parser.add_option('--ignore_locks', action='store_true',
1996 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00001997 parser.add_option('--break_repo_locks', action='store_true',
1998 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1999 'index.lock). This should only be used if you know for '
2000 'certain that this invocation of gclient is the only '
2001 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002002 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002003 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002004 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002005 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2006 parser.add_option('-t', '--transitive', action='store_true',
2007 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002008 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002009 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002010 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002011 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002012 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002013 parser.add_option('--disable-syntax-validation', action='store_false',
2014 dest='validate_syntax',
2015 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002016 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002017 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002018
2019 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002020 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002021
smutae7ea312016-07-18 11:59:41 -07002022 if options.revisions and options.head:
2023 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2024 print('Warning: you cannot use both --head and --revision')
2025
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002026 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002027 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002028 ret = client.RunOnDeps('update', args)
2029 if options.output_json:
2030 slns = {}
2031 for d in client.subtree(True):
2032 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2033 slns[normed] = {
2034 'revision': d.got_revision,
2035 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002036 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002037 }
2038 with open(options.output_json, 'wb') as f:
2039 json.dump({'solutions': slns}, f)
2040 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002041
2042
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002043CMDupdate = CMDsync
2044
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002045
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002046def CMDvalidate(parser, args):
2047 """Validates the .gclient and DEPS syntax."""
2048 options, args = parser.parse_args(args)
2049 options.validate_syntax = True
2050 client = GClient.LoadCurrentConfig(options)
2051 rv = client.RunOnDeps('validate', args)
2052 if rv == 0:
2053 print('validate: SUCCESS')
2054 else:
2055 print('validate: FAILURE')
2056 return rv
2057
2058
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002059def CMDdiff(parser, args):
2060 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002061 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2062 help='override deps for the specified (comma-separated) '
2063 'platform(s); \'all\' will process all deps_os '
2064 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002065 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002066 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002067 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002068 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002069 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002070 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002071 return client.RunOnDeps('diff', args)
2072
2073
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002074def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002075 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002076
2077 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002078 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002079 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2080 help='override deps for the specified (comma-separated) '
2081 'platform(s); \'all\' will process all deps_os '
2082 'references')
2083 parser.add_option('-n', '--nohooks', action='store_true',
2084 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002085 parser.add_option('-p', '--noprehooks', action='store_true',
2086 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002087 parser.add_option('--upstream', action='store_true',
2088 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002089 parser.add_option('--break_repo_locks', action='store_true',
2090 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2091 'index.lock). This should only be used if you know for '
2092 'certain that this invocation of gclient is the only '
2093 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002094 (options, args) = parser.parse_args(args)
2095 # --force is implied.
2096 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002097 options.reset = False
2098 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002099 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002100 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002101 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002102 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002103 return client.RunOnDeps('revert', args)
2104
2105
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002106def CMDrunhooks(parser, args):
2107 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002108 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2109 help='override deps for the specified (comma-separated) '
2110 'platform(s); \'all\' will process all deps_os '
2111 'references')
2112 parser.add_option('-f', '--force', action='store_true', default=True,
2113 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002114 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002115 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002116 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002117 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002118 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002119 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002120 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002121 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002122 return client.RunOnDeps('runhooks', args)
2123
2124
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002125def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002126 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002127
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002128 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002129 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002130 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2131 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002132 """
2133 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2134 help='override deps for the specified (comma-separated) '
2135 'platform(s); \'all\' will process all deps_os '
2136 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002137 parser.add_option('-a', '--actual', action='store_true',
2138 help='gets the actual checked out revisions instead of the '
2139 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002140 parser.add_option('-s', '--snapshot', action='store_true',
2141 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002142 'version of all repositories to reproduce the tree, '
2143 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002144 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002145 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002146 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002147 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002148 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002149 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002150
2151
szager@google.comb9a78d32012-03-13 18:46:21 +00002152def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002153 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002154 (options, args) = parser.parse_args(args)
2155 options.force = True
2156 client = GClient.LoadCurrentConfig(options)
2157 if not client:
2158 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2159 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002160 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002161 return 0
2162
2163
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002164def CMDverify(parser, args):
2165 """Verifies the DEPS file deps are only from allowed_hosts."""
2166 (options, args) = parser.parse_args(args)
2167 client = GClient.LoadCurrentConfig(options)
2168 if not client:
2169 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2170 client.RunOnDeps(None, [])
2171 # Look at each first-level dependency of this gclient only.
2172 for dep in client.dependencies:
2173 bad_deps = dep.findDepsFromNotAllowedHosts()
2174 if not bad_deps:
2175 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002176 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002177 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002178 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2179 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002180 sys.stdout.flush()
2181 raise gclient_utils.Error(
2182 'dependencies from disallowed hosts; check your DEPS file.')
2183 return 0
2184
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002185class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002186 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002187
2188 def __init__(self, **kwargs):
2189 optparse.OptionParser.__init__(
2190 self, version='%prog ' + __version__, **kwargs)
2191
2192 # Some arm boards have issues with parallel sync.
2193 if platform.machine().startswith('arm'):
2194 jobs = 1
2195 else:
2196 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002197
2198 self.add_option(
2199 '-j', '--jobs', default=jobs, type='int',
2200 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002201 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002202 self.add_option(
2203 '-v', '--verbose', action='count', default=0,
2204 help='Produces additional output for diagnostics. Can be used up to '
2205 'three times for more logging info.')
2206 self.add_option(
2207 '--gclientfile', dest='config_filename',
2208 help='Specify an alternate %s file' % self.gclientfile_default)
2209 self.add_option(
2210 '--spec',
2211 help='create a gclient file containing the provided string. Due to '
2212 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2213 self.add_option(
2214 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002215 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002216
2217 def parse_args(self, args=None, values=None):
2218 """Integrates standard options processing."""
2219 options, args = optparse.OptionParser.parse_args(self, args, values)
2220 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2221 logging.basicConfig(
2222 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002223 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002224 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002225 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002226 if (options.config_filename and
2227 options.config_filename != os.path.basename(options.config_filename)):
2228 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002229 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002230 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002231 options.entries_filename = options.config_filename + '_entries'
2232 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002233 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002234
2235 # These hacks need to die.
2236 if not hasattr(options, 'revisions'):
2237 # GClient.RunOnDeps expects it even if not applicable.
2238 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002239 if not hasattr(options, 'head'):
2240 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002241 if not hasattr(options, 'nohooks'):
2242 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002243 if not hasattr(options, 'noprehooks'):
2244 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002245 if not hasattr(options, 'deps_os'):
2246 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002247 if not hasattr(options, 'force'):
2248 options.force = None
2249 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002250
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002251
2252def disable_buffering():
2253 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2254 # operations. Python as a strong tendency to buffer sys.stdout.
2255 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2256 # Make stdout annotated with the thread ids.
2257 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002258
2259
sbc@chromium.org013731e2015-02-26 18:28:43 +00002260def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002261 """Doesn't parse the arguments here, just find the right subcommand to
2262 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002263 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002264 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002265 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002266 sys.version.split(' ', 1)[0],
2267 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002268 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002269 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002270 print(
2271 '\nPython cannot find the location of it\'s own executable.\n',
2272 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002273 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002274 fix_encoding.fix_encoding()
2275 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002276 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002277 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002278 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002279 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002280 except KeyboardInterrupt:
2281 gclient_utils.GClientChildren.KillAllRemainingChildren()
2282 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002283 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002284 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002285 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002286 finally:
2287 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002288 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002289
2290
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002291if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002292 try:
2293 sys.exit(main(sys.argv[1:]))
2294 except KeyboardInterrupt:
2295 sys.stderr.write('interrupted\n')
2296 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002297
2298# vim: ts=2:sw=2:tw=80:et: