blob: fe2c9aa8fc78c9fc68fa974540ad79fe070b6d3f [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, Jr11016452017-05-29 18:02:15 +0200654 if dep_value is None:
655 continue
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200656 if isinstance(dep_value, basestring):
657 url = dep_value
658 else:
659 # This should be guaranteed by schema checking in gclient_eval.
660 assert isinstance(dep_value, dict)
661 url = dep_value['url']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000662 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800663 self, name, url, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700664 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000665 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000666
667 # override named sets of hooks by the custom hooks
668 hooks_to_run = []
669 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
670 for hook in local_scope.get('hooks', []):
671 if hook.get('name', '') not in hook_names_to_suppress:
672 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700673 if 'hooks_os' in local_scope and target_os_list:
674 hooks_os = local_scope['hooks_os']
675 # Specifically append these to ensure that hooks_os run after hooks.
676 for the_target_os in target_os_list:
677 the_target_os_hooks = hooks_os.get(the_target_os, [])
678 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000679
680 # add the replacements and any additions
681 for hook in self.custom_hooks:
682 if 'action' in hook:
683 hooks_to_run.append(hook)
684
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800685 if self.recursion_limit:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200686 self._pre_deps_hooks = [self.GetHookAction(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800687 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000688
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000689 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000690 logging.info('ParseDepsFile(%s) done' % self.name)
691
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200692 def _get_option(self, attr, default):
693 obj = self
694 while not hasattr(obj, '_options'):
695 obj = obj.parent
696 return getattr(obj._options, attr, default)
697
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000698 def add_dependencies_and_close(self, deps_to_add, hooks):
699 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000700 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000701 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000702 self.add_dependency(dep)
703 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000704
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000705 def findDepsFromNotAllowedHosts(self):
706 """Returns a list of depenecies from not allowed hosts.
707
708 If allowed_hosts is not set, allows all hosts and returns empty list.
709 """
710 if not self._allowed_hosts:
711 return []
712 bad_deps = []
713 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000714 # Don't enforce this for custom_deps.
715 if dep.name in self._custom_deps:
716 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000717 if isinstance(dep.url, basestring):
718 parsed_url = urlparse.urlparse(dep.url)
719 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
720 bad_deps.append(dep)
721 return bad_deps
722
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000723 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800724 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000725 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000726 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000727 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000728 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000729 if not self.should_process:
730 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000731 # When running runhooks, there's no need to consult the SCM.
732 # All known hooks are expected to run unconditionally regardless of working
733 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200734 run_scm = command not in (
735 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000736 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000737 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000738 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700739 if not revision_override and parsed_url:
740 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000741 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700742 # Create a shallow copy to mutate revision.
743 options = copy.copy(options)
744 options.revision = revision_override
745 self._used_revision = options.revision
746 self._used_scm = gclient_scm.CreateSCM(
747 parsed_url, self.root.root_dir, self.name, self.outbuf,
748 out_cb=work_queue.out_cb)
749 self._got_revision = self._used_scm.RunCommand(command, options, args,
750 file_list)
751 if file_list:
752 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000753
754 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
755 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000756 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000757 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000758 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000759 continue
760 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000761 [self.root.root_dir.lower(), file_list[i].lower()])
762 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000763 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000764 while file_list[i].startswith(('\\', '/')):
765 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000766
767 # Always parse the DEPS file.
768 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000769 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000770 if command in ('update', 'revert') and not options.noprehooks:
771 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000772
773 if self.recursion_limit:
774 # Parse the dependencies of this dependency.
775 for s in self.dependencies:
776 work_queue.enqueue(s)
777
778 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700779 # Skip file only checkout.
780 scm = gclient_scm.GetScmName(parsed_url)
781 if not options.scm or scm in options.scm:
782 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
783 # Pass in the SCM type as an env variable. Make sure we don't put
784 # unicode strings in the environment.
785 env = os.environ.copy()
786 if scm:
787 env['GCLIENT_SCM'] = str(scm)
788 if parsed_url:
789 env['GCLIENT_URL'] = str(parsed_url)
790 env['GCLIENT_DEP_PATH'] = str(self.name)
791 if options.prepend_dir and scm == 'git':
792 print_stdout = False
793 def filter_fn(line):
794 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000795
agabled437d762016-10-17 09:35:11 -0700796 def mod_path(git_pathspec):
797 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
798 modified_path = os.path.join(self.name, match.group(2))
799 branch = match.group(1) or ''
800 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000801
agabled437d762016-10-17 09:35:11 -0700802 match = re.match('^Binary file ([^\0]+) matches$', line)
803 if match:
804 print('Binary file %s matches\n' % mod_path(match.group(1)))
805 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000806
agabled437d762016-10-17 09:35:11 -0700807 items = line.split('\0')
808 if len(items) == 2 and items[1]:
809 print('%s : %s' % (mod_path(items[0]), items[1]))
810 elif len(items) >= 2:
811 # Multiple null bytes or a single trailing null byte indicate
812 # git is likely displaying filenames only (such as with -l)
813 print('\n'.join(mod_path(path) for path in items if path))
814 else:
815 print(line)
816 else:
817 print_stdout = True
818 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000819
agabled437d762016-10-17 09:35:11 -0700820 if parsed_url is None:
821 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
822 elif os.path.isdir(cwd):
823 try:
824 gclient_utils.CheckCallAndFilter(
825 args, cwd=cwd, env=env, print_stdout=print_stdout,
826 filter_fn=filter_fn,
827 )
828 except subprocess2.CalledProcessError:
829 if not options.ignore:
830 raise
831 else:
832 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000833
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000834
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000835 @gclient_utils.lockedmethod
836 def _run_is_done(self, file_list, parsed_url):
837 # Both these are kept for hooks that are run as a separate tree traversal.
838 self._file_list = file_list
839 self._parsed_url = parsed_url
840 self._processed = True
841
szager@google.comb9a78d32012-03-13 18:46:21 +0000842 @staticmethod
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200843 def GetHookAction(hook_dict):
szager@google.comb9a78d32012-03-13 18:46:21 +0000844 """Turns a parsed 'hook' dict into an executable command."""
845 logging.debug(hook_dict)
szager@google.comb9a78d32012-03-13 18:46:21 +0000846 command = hook_dict['action'][:]
847 if command[0] == 'python':
848 # If the hook specified "python" as the first item, the action is a
849 # Python script. Run it by starting a new copy of the same
850 # interpreter.
851 command[0] = sys.executable
szager@google.comb9a78d32012-03-13 18:46:21 +0000852 return command
853
854 def GetHooks(self, options):
855 """Evaluates all hooks, and return them in a flat list.
856
857 RunOnDeps() must have been called before to load the DEPS.
858 """
859 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000860 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000861 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000862 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000863 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000864 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000865 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700866 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000867 # what files have changed so we always run all hooks. It'd be nice to fix
868 # that.
869 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000870 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000871 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000872 for hook_dict in self.deps_hooks:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200873 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000874 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000875 # Run hooks on the basis of whether the files from the gclient operation
876 # match each hook's pattern.
877 for hook_dict in self.deps_hooks:
878 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000879 matching_file_list = [
880 f for f in self.file_list_and_children if pattern.search(f)
881 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000882 if matching_file_list:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200883 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000884 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000885 result.extend(s.GetHooks(options))
886 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000887
szager@google.comb9a78d32012-03-13 18:46:21 +0000888 def RunHooksRecursively(self, options):
889 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000890 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000891 for hook in self.GetHooks(options):
892 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000893 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000894 gclient_utils.CheckCallAndFilterAndHeader(
895 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000896 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000897 # Use a discrete exit status code of 2 to indicate that a hook action
898 # failed. Users of this script may wish to treat hook action failures
899 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000900 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000901 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000902 finally:
903 elapsed_time = time.time() - start_time
904 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000905 print("Hook '%s' took %.2f secs" % (
906 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000907
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000908 def RunPreDepsHooks(self):
909 assert self.processed
910 assert self.deps_parsed
911 assert not self.pre_deps_hooks_ran
912 assert not self.hooks_ran
913 for s in self.dependencies:
914 assert not s.processed
915 self._pre_deps_hooks_ran = True
916 for hook in self.pre_deps_hooks:
917 try:
918 start_time = time.time()
919 gclient_utils.CheckCallAndFilterAndHeader(
920 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000921 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000922 # Use a discrete exit status code of 2 to indicate that a hook action
923 # failed. Users of this script may wish to treat hook action failures
924 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000925 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000926 sys.exit(2)
927 finally:
928 elapsed_time = time.time() - start_time
929 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000930 print("Hook '%s' took %.2f secs" % (
931 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000932
933
maruel@chromium.org0d812442010-08-10 12:41:08 +0000934 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000935 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000936 dependencies = self.dependencies
937 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000938 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000939 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000940 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000941 for i in d.subtree(include_all):
942 yield i
943
944 def depth_first_tree(self):
945 """Depth-first recursion including the root node."""
946 yield self
947 for i in self.dependencies:
948 for j in i.depth_first_tree():
949 if j.should_process:
950 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000951
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000952 @gclient_utils.lockedmethod
953 def add_dependency(self, new_dep):
954 self._dependencies.append(new_dep)
955
956 @gclient_utils.lockedmethod
957 def _mark_as_parsed(self, new_hooks):
958 self._deps_hooks.extend(new_hooks)
959 self._deps_parsed = True
960
maruel@chromium.org68988972011-09-20 14:11:42 +0000961 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000962 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000963 def dependencies(self):
964 return tuple(self._dependencies)
965
966 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000967 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000968 def deps_hooks(self):
969 return tuple(self._deps_hooks)
970
971 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000972 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000973 def pre_deps_hooks(self):
974 return tuple(self._pre_deps_hooks)
975
976 @property
977 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000978 def parsed_url(self):
979 return self._parsed_url
980
981 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000982 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000983 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000984 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000985 return self._deps_parsed
986
987 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000988 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000989 def processed(self):
990 return self._processed
991
992 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000993 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000994 def pre_deps_hooks_ran(self):
995 return self._pre_deps_hooks_ran
996
997 @property
998 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000999 def hooks_ran(self):
1000 return self._hooks_ran
1001
1002 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001003 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001004 def allowed_hosts(self):
1005 return self._allowed_hosts
1006
1007 @property
1008 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001009 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001010 return tuple(self._file_list)
1011
1012 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001013 def used_scm(self):
1014 """SCMWrapper instance for this dependency or None if not processed yet."""
1015 return self._used_scm
1016
1017 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001018 @gclient_utils.lockedmethod
1019 def got_revision(self):
1020 return self._got_revision
1021
1022 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001023 def file_list_and_children(self):
1024 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001025 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001026 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001027 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001028
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001029 def __str__(self):
1030 out = []
agablea98a6cd2016-11-15 14:30:10 -08001031 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001032 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001033 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1034 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001035 # First try the native property if it exists.
1036 if hasattr(self, '_' + i):
1037 value = getattr(self, '_' + i, False)
1038 else:
1039 value = getattr(self, i, False)
1040 if value:
1041 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001042
1043 for d in self.dependencies:
1044 out.extend([' ' + x for x in str(d).splitlines()])
1045 out.append('')
1046 return '\n'.join(out)
1047
1048 def __repr__(self):
1049 return '%s: %s' % (self.name, self.url)
1050
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001051 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001052 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001053 def format_name(d):
1054 if include_url:
1055 return '%s(%s)' % (d.name, d.url)
1056 return d.name
1057 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001058 i = self.parent
1059 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001060 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001061 i = i.parent
1062 return out
1063
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001064
1065class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001066 """Object that represent a gclient checkout. A tree of Dependency(), one per
1067 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001068
1069 DEPS_OS_CHOICES = {
1070 "win32": "win",
1071 "win": "win",
1072 "cygwin": "win",
1073 "darwin": "mac",
1074 "mac": "mac",
1075 "unix": "unix",
1076 "linux": "unix",
1077 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001078 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001079 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001080 }
1081
1082 DEFAULT_CLIENT_FILE_TEXT = ("""\
1083solutions = [
smutae7ea312016-07-18 11:59:41 -07001084 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001085 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001086 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001087 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001088 "custom_deps" : {
1089 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001090 },
1091]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001092cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001093""")
1094
1095 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001096 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001097 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001098 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001099 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001100 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001101%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001102 },
1103""")
1104
1105 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1106# Snapshot generated with gclient revinfo --snapshot
1107solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001108%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001109""")
1110
1111 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001112 # Do not change previous behavior. Only solution level and immediate DEPS
1113 # are processed.
1114 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001115 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001116 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001117 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001118 if options.deps_os:
1119 enforced_os = options.deps_os.split(',')
1120 else:
1121 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1122 if 'all' in enforced_os:
1123 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001124 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001125 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001126 self.config_content = None
1127
borenet@google.com88d10082014-03-21 17:24:48 +00001128 def _CheckConfig(self):
1129 """Verify that the config matches the state of the existing checked-out
1130 solutions."""
1131 for dep in self.dependencies:
1132 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001133 scm = gclient_scm.CreateSCM(
1134 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001135 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001136 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001137 mirror = scm.GetCacheMirror()
1138 if mirror:
1139 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1140 mirror.exists())
1141 else:
1142 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001143 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001144Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001145is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001146
borenet@google.com97882362014-04-07 20:06:02 +00001147The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001148URL: %(expected_url)s (%(expected_scm)s)
1149Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001150
1151The local checkout in %(checkout_path)s reports:
1152%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001153
1154You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001155it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001156''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1157 'expected_url': dep.url,
1158 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001159 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001160 'actual_url': actual_url,
1161 'actual_scm': gclient_scm.GetScmName(actual_url)})
1162
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001163 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001164 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001165 config_dict = {}
1166 self.config_content = content
1167 try:
1168 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001169 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001170 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001171
peter@chromium.org1efccc82012-04-27 16:34:38 +00001172 # Append any target OS that is not already being enforced to the tuple.
1173 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001174 if config_dict.get('target_os_only', False):
1175 self._enforced_os = tuple(set(target_os))
1176 else:
1177 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1178
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001179 cache_dir = config_dict.get('cache_dir')
1180 if cache_dir:
1181 cache_dir = os.path.join(self.root_dir, cache_dir)
1182 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001183 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001184 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001185 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1186 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001187 gclient_scm.GitWrapper.cache_dir = cache_dir
1188 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001189
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001190 if not target_os and config_dict.get('target_os_only', False):
1191 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1192 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001193
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001194 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001195 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001196 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001197 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001198 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001199 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001200 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001201 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001202 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001203 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001204 True,
1205 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001206 except KeyError:
1207 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1208 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001209 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1210 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001211
1212 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001213 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001214 self._options.config_filename),
1215 self.config_content)
1216
1217 @staticmethod
1218 def LoadCurrentConfig(options):
1219 """Searches for and loads a .gclient file relative to the current working
1220 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001221 if options.spec:
1222 client = GClient('.', options)
1223 client.SetConfig(options.spec)
1224 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001225 if options.verbose:
1226 print('Looking for %s starting from %s\n' % (
1227 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001228 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1229 if not path:
1230 return None
1231 client = GClient(path, options)
1232 client.SetConfig(gclient_utils.FileRead(
1233 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001234
1235 if (options.revisions and
1236 len(client.dependencies) > 1 and
1237 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001238 print(
1239 ('You must specify the full solution name like --revision %s@%s\n'
1240 'when you have multiple solutions setup in your .gclient file.\n'
1241 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001242 client.dependencies[0].name,
1243 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001244 ', '.join(s.name for s in client.dependencies[1:])),
1245 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001246 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001247
nsylvain@google.comefc80932011-05-31 21:27:56 +00001248 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001249 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001250 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1251 'solution_name': solution_name,
1252 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001253 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001254 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001255 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001256 })
1257
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001258 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001259 """Creates a .gclient_entries file to record the list of unique checkouts.
1260
1261 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001262 """
1263 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1264 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001265 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001266 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001267 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1268 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001269 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001270 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001271 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001272 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001273
1274 def _ReadEntries(self):
1275 """Read the .gclient_entries file for the given client.
1276
1277 Returns:
1278 A sequence of solution names, which will be empty if there is the
1279 entries file hasn't been created yet.
1280 """
1281 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001282 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001283 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001284 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001285 try:
1286 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001287 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001288 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001289 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001290
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001291 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001292 """Checks for revision overrides."""
1293 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001294 if self._options.head:
1295 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001296 if not self._options.revisions:
1297 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001298 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001299 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001300 if not self._options.revisions:
1301 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001302 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001303 index = 0
1304 for revision in self._options.revisions:
1305 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001306 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001307 revision = '%s@%s' % (solutions_names[index], revision)
1308 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001309 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001310 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001311 return revision_overrides
1312
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001313 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001314 """Runs a command on each dependency in a client and its dependencies.
1315
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001316 Args:
1317 command: The command to use (e.g., 'status' or 'diff')
1318 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001319 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001320 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001321 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001322
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001323 revision_overrides = {}
1324 # It's unnecessary to check for revision overrides for 'recurse'.
1325 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001326 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1327 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001328 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001329 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001330 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001331 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001332 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001333 if command in ('update', 'revert'):
1334 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001335 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001336 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001337 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001338 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1339 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001340 for s in self.dependencies:
1341 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001342 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001343 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001344 print('Please fix your script, having invalid --revision flags will soon '
1345 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001346
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001347 # Once all the dependencies have been processed, it's now safe to run the
1348 # hooks.
1349 if not self._options.nohooks:
1350 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001351
1352 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001353 # Notify the user if there is an orphaned entry in their working copy.
1354 # Only delete the directory if there are no changes in it, and
1355 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001356 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001357 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1358 for e in entries]
1359
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001360 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001361 if not prev_url:
1362 # entry must have been overridden via .gclient custom_deps
1363 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001364 # Fix path separator on Windows.
1365 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001366 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001367 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001368 if (entry not in entries and
1369 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001370 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001371 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001372 scm = gclient_scm.CreateSCM(
1373 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001374
1375 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001376 scm_root = None
agabled437d762016-10-17 09:35:11 -07001377 try:
1378 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1379 except subprocess2.CalledProcessError:
1380 pass
1381 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001382 logging.warning('Could not find checkout root for %s. Unable to '
1383 'determine whether it is part of a higher-level '
1384 'checkout, so not removing.' % entry)
1385 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001386
1387 # This is to handle the case of third_party/WebKit migrating from
1388 # being a DEPS entry to being part of the main project.
1389 # If the subproject is a Git project, we need to remove its .git
1390 # folder. Otherwise git operations on that folder will have different
1391 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001392 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001393 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001394 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1395 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001396 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1397 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001398 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1399 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001400 save_dir = scm.GetGitBackupDirPath()
1401 # Remove any eventual stale backup dir for the same project.
1402 if os.path.exists(save_dir):
1403 gclient_utils.rmtree(save_dir)
1404 os.rename(os.path.join(e_dir, '.git'), save_dir)
1405 # When switching between the two states (entry/ is a subproject
1406 # -> entry/ is part of the outer project), it is very likely
1407 # that some files are changed in the checkout, unless we are
1408 # jumping *exactly* across the commit which changed just DEPS.
1409 # In such case we want to cleanup any eventual stale files
1410 # (coming from the old subproject) in order to end up with a
1411 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001412 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001413 assert not os.path.exists(os.path.join(e_dir, '.git'))
1414 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1415 'level checkout. The git folder containing all the local'
1416 ' branches has been saved to %s.\n'
1417 'If you don\'t care about its state you can safely '
1418 'remove that folder to free up space.') %
1419 (entry, save_dir))
1420 continue
1421
borenet@google.com359bb642014-05-13 17:28:19 +00001422 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001423 logging.info('%s is part of a higher level checkout, not removing',
1424 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001425 continue
1426
1427 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001428 scm.status(self._options, [], file_list)
1429 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001430 if (not self._options.delete_unversioned_trees or
1431 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001432 # There are modified files in this entry. Keep warning until
1433 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001434 print(('\nWARNING: \'%s\' is no longer part of this client. '
1435 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001436 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001437 else:
1438 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001439 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001440 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001441 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001442 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001443 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001444 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001445
1446 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001447 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001448 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001449 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001450 work_queue = gclient_utils.ExecutionQueue(
1451 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001452 for s in self.dependencies:
1453 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001454 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001455
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001456 def GetURLAndRev(dep):
1457 """Returns the revision-qualified SCM url for a Dependency."""
1458 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001459 return None
agabled437d762016-10-17 09:35:11 -07001460 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001461 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001462 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001463 if not os.path.isdir(scm.checkout_path):
1464 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001465 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001466
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001467 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001468 new_gclient = ''
1469 # First level at .gclient
1470 for d in self.dependencies:
1471 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001472 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001473 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001474 for d in dep.dependencies:
1475 entries[d.name] = GetURLAndRev(d)
1476 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001477 GrabDeps(d)
1478 custom_deps = []
1479 for k in sorted(entries.keys()):
1480 if entries[k]:
1481 # Quotes aren't escaped...
1482 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1483 else:
1484 custom_deps.append(' \"%s\": None,\n' % k)
1485 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1486 'solution_name': d.name,
1487 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001488 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001489 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001490 'solution_deps': ''.join(custom_deps),
1491 }
1492 # Print the snapshot configuration file
1493 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001494 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001495 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001496 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001497 if self._options.actual:
1498 entries[d.name] = GetURLAndRev(d)
1499 else:
1500 entries[d.name] = d.parsed_url
1501 keys = sorted(entries.keys())
1502 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001503 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001504 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001505
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001506 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001507 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001508 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001509
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001510 def PrintLocationAndContents(self):
1511 # Print out the .gclient file. This is longer than if we just printed the
1512 # client dict, but more legible, and it might contain helpful comments.
1513 print('Loaded .gclient config in %s:\n%s' % (
1514 self.root_dir, self.config_content))
1515
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001516 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001517 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001518 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001519 return self._root_dir
1520
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001521 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001522 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001523 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001524 return self._enforced_os
1525
maruel@chromium.org68988972011-09-20 14:11:42 +00001526 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001527 def recursion_limit(self):
1528 """How recursive can each dependencies in DEPS file can load DEPS file."""
1529 return self._recursion_limit
1530
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001531 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001532 def try_recursedeps(self):
1533 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001534 return True
1535
1536 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001537 def target_os(self):
1538 return self._enforced_os
1539
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001540
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001541#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001542
1543
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001544@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001545def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001546 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001547
1548 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001549 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001550 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001551 """
1552 # Stop parsing at the first non-arg so that these go through to the command
1553 parser.disable_interspersed_args()
1554 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001555 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001556 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001557 help='Ignore non-zero return codes from subcommands.')
1558 parser.add_option('--prepend-dir', action='store_true',
1559 help='Prepend relative dir for use with git <cmd> --null.')
1560 parser.add_option('--no-progress', action='store_true',
1561 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001562 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001563 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001564 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001565 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001566 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1567 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001568 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001569 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001570 'This is because .gclient_entries needs to exist and be up to date.',
1571 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001572 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001573
1574 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001575 scm_set = set()
1576 for scm in options.scm:
1577 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001578 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001579
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001580 options.nohooks = True
1581 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001582 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1583 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001584
1585
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001586@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001587def CMDfetch(parser, args):
1588 """Fetches upstream commits for all modules.
1589
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001590 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1591 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001592 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001593 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001594 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1595
1596
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001597def CMDflatten(parser, args):
1598 """Flattens the solutions into a single DEPS file."""
1599 parser.add_option('--output-deps', help='Path to the output DEPS file')
1600 parser.add_option(
1601 '--require-pinned-revisions', action='store_true',
1602 help='Fail if any of the dependencies uses unpinned revision.')
1603 options, args = parser.parse_args(args)
1604
1605 options.nohooks = True
1606 client = GClient.LoadCurrentConfig(options)
1607
1608 # Only print progress if we're writing to a file. Otherwise, progress updates
1609 # could obscure intended output.
1610 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1611 if code != 0:
1612 return code
1613
1614 deps = {}
1615 hooks = []
1616 pre_deps_hooks = []
1617 unpinned_deps = {}
1618
1619 for solution in client.dependencies:
1620 _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1621
1622 if options.require_pinned_revisions and unpinned_deps:
1623 sys.stderr.write('The following dependencies are not pinned:\n')
1624 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1625 return 1
1626
1627 flattened_deps = '\n'.join(
1628 _DepsToLines(deps) +
1629 _HooksToLines('hooks', hooks) +
1630 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1631 [''] # Ensure newline at end of file.
1632 )
1633
1634 if options.output_deps:
1635 with open(options.output_deps, 'w') as f:
1636 f.write(flattened_deps)
1637 else:
1638 print(flattened_deps)
1639
1640 return 0
1641
1642
1643def _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps):
1644 """Visits a solution in order to flatten it (see CMDflatten).
1645
1646 Arguments:
1647 solution (Dependency): one of top-level solutions in .gclient
1648
1649 Out-parameters:
1650 deps (dict of name -> Dependency): will be filled with all Dependency
1651 objects indexed by their name
1652 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1653 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1654 pre_deps_hooks
1655 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1656 deps
1657 """
1658 logging.debug('_FlattenSolution(%r)', solution)
1659
1660 _FlattenDep(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1661 _FlattenRecurse(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1662
1663
1664def _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1665 """Visits a dependency in order to flatten it (see CMDflatten).
1666
1667 Arguments:
1668 dep (Dependency): dependency to process
1669
1670 Out-parameters:
1671 deps (dict): will be filled with flattened deps
1672 hooks (list): will be filled with flattened hooks
1673 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1674 unpinned_deps (dict): will be filled with unpinned deps
1675 """
1676 logging.debug('_FlattenDep(%r)', dep)
1677
1678 _AddDep(dep, deps, unpinned_deps)
1679
1680 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1681 for recurse_dep_name in (dep.recursedeps or []):
1682 _FlattenRecurse(
1683 deps_by_name[recurse_dep_name], deps, hooks, pre_deps_hooks,
1684 unpinned_deps)
1685
1686 # TODO(phajdan.jr): also handle hooks_os.
1687 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1688 pre_deps_hooks.extend(
1689 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1690
1691
1692def _FlattenRecurse(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1693 """Helper for flatten that recurses into |dep|'s dependencies.
1694
1695 Arguments:
1696 dep (Dependency): dependency to process
1697
1698 Out-parameters:
1699 deps (dict): will be filled with flattened deps
1700 hooks (list): will be filled with flattened hooks
1701 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1702 unpinned_deps (dict): will be filled with unpinned deps
1703 """
1704 logging.debug('_FlattenRecurse(%r)', dep)
1705
1706 # TODO(phajdan.jr): also handle deps_os.
1707 for dep in dep.dependencies:
1708 _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps)
1709
1710
1711def _AddDep(dep, deps, unpinned_deps):
1712 """Helper to add a dependency to flattened lists.
1713
1714 Arguments:
1715 dep (Dependency): dependency to process
1716
1717 Out-parameters:
1718 deps (dict): will be filled with flattened deps
1719 unpinned_deps (dict): will be filled with unpinned deps
1720 """
1721 logging.debug('_AddDep(%r)', dep)
1722
1723 assert dep.name not in deps
1724 deps[dep.name] = dep
1725
1726 # Detect unpinned deps.
1727 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1728 if not revision or not gclient_utils.IsGitSha(revision):
1729 unpinned_deps[dep.name] = dep
1730
1731
1732def _DepsToLines(deps):
1733 """Converts |deps| dict to list of lines for output."""
1734 s = ['deps = {']
1735 for name, dep in sorted(deps.iteritems()):
1736 s.extend([
1737 ' # %s' % dep.hierarchy(include_url=False),
1738 ' "%s": "%s",' % (name, dep.url),
1739 '',
1740 ])
1741 s.extend(['}', ''])
1742 return s
1743
1744
1745def _HooksToLines(name, hooks):
1746 """Converts |hooks| list to list of lines for output."""
1747 s = ['%s = [' % name]
1748 for dep, hook in hooks:
1749 s.extend([
1750 ' # %s' % dep.hierarchy(include_url=False),
1751 ' {',
1752 ])
1753 if 'name' in hook:
1754 s.append(' "name": "%s",' % hook['name'])
1755 if 'pattern' in hook:
1756 s.append(' "pattern": "%s",' % hook['pattern'])
1757 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1758 # i.e. they may be relative to the dependency path, not solution root.
1759 s.extend(
1760 [' "action": ['] +
1761 [' "%s",' % arg for arg in hook['action']] +
1762 [' ]', ' },', '']
1763 )
1764 s.extend([']', ''])
1765 return s
1766
1767
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001768def CMDgrep(parser, args):
1769 """Greps through git repos managed by gclient.
1770
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001771 Runs 'git grep [args...]' for each module.
1772 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001773 # We can't use optparse because it will try to parse arguments sent
1774 # to git grep and throw an error. :-(
1775 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001776 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001777 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1778 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1779 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1780 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001781 ' end of your query.',
1782 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001783 return 1
1784
1785 jobs_arg = ['--jobs=1']
1786 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1787 jobs_arg, args = args[:1], args[1:]
1788 elif re.match(r'(-j|--jobs)$', args[0]):
1789 jobs_arg, args = args[:2], args[2:]
1790
1791 return CMDrecurse(
1792 parser,
1793 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1794 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001795
1796
stip@chromium.orga735da22015-04-29 23:18:20 +00001797def CMDroot(parser, args):
1798 """Outputs the solution root (or current dir if there isn't one)."""
1799 (options, args) = parser.parse_args(args)
1800 client = GClient.LoadCurrentConfig(options)
1801 if client:
1802 print(os.path.abspath(client.root_dir))
1803 else:
1804 print(os.path.abspath('.'))
1805
1806
agablea98a6cd2016-11-15 14:30:10 -08001807@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001808def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001809 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001810
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001811 This specifies the configuration for further commands. After update/sync,
1812 top-level DEPS files in each module are read to determine dependent
1813 modules to operate on as well. If optional [url] parameter is
1814 provided, then configuration is read from a specified Subversion server
1815 URL.
1816 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001817 # We do a little dance with the --gclientfile option. 'gclient config' is the
1818 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1819 # arguments. So, we temporarily stash any --gclientfile parameter into
1820 # options.output_config_file until after the (gclientfile xor spec) error
1821 # check.
1822 parser.remove_option('--gclientfile')
1823 parser.add_option('--gclientfile', dest='output_config_file',
1824 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001825 parser.add_option('--name',
1826 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001827 parser.add_option('--deps-file', default='DEPS',
1828 help='overrides the default name for the DEPS file for the'
1829 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001830 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001831 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001832 'to have the main solution untouched by gclient '
1833 '(gclient will check out unmanaged dependencies but '
1834 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001835 parser.add_option('--cache-dir',
1836 help='(git only) Cache all git repos into this dir and do '
1837 'shared clones from the cache, instead of cloning '
1838 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001839 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001840 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001841 if options.output_config_file:
1842 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001843 if ((options.spec and args) or len(args) > 2 or
1844 (not options.spec and not args)):
1845 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1846
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001847 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001848 if options.spec:
1849 client.SetConfig(options.spec)
1850 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001851 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001852 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001853 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001854 if name.endswith('.git'):
1855 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001856 else:
1857 # specify an alternate relpath for the given URL.
1858 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001859 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1860 os.getcwd()):
1861 parser.error('Do not pass a relative path for --name.')
1862 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1863 parser.error('Do not include relative path components in --name.')
1864
nsylvain@google.comefc80932011-05-31 21:27:56 +00001865 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001866 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001867 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001868 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001869 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001870 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001871
1872
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001873@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001874 gclient pack > patch.txt
1875 generate simple patch for configured client and dependences
1876""")
1877def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001878 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001879
agabled437d762016-10-17 09:35:11 -07001880 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001881 dependencies, and performs minimal postprocessing of the output. The
1882 resulting patch is printed to stdout and can be applied to a freshly
1883 checked out tree via 'patch -p0 < patchfile'.
1884 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001885 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1886 help='override deps for the specified (comma-separated) '
1887 'platform(s); \'all\' will process all deps_os '
1888 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001889 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001890 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001891 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001892 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001893 client = GClient.LoadCurrentConfig(options)
1894 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001895 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001896 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001897 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001898 return client.RunOnDeps('pack', args)
1899
1900
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001901def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001902 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001903 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1904 help='override deps for the specified (comma-separated) '
1905 'platform(s); \'all\' will process all deps_os '
1906 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001907 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001908 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001909 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001910 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001911 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001912 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001913 return client.RunOnDeps('status', args)
1914
1915
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001916@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001917 gclient sync
1918 update files from SCM according to current configuration,
1919 *for modules which have changed since last update or sync*
1920 gclient sync --force
1921 update files from SCM according to current configuration, for
1922 all modules (useful for recovering files deleted from local copy)
1923 gclient sync --revision src@31000
1924 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001925
1926JSON output format:
1927If the --output-json option is specified, the following document structure will
1928be emitted to the provided file. 'null' entries may occur for subprojects which
1929are present in the gclient solution, but were not processed (due to custom_deps,
1930os_deps, etc.)
1931
1932{
1933 "solutions" : {
1934 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001935 "revision": [<git id hex string>|null],
1936 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001937 }
1938 }
1939}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001940""")
1941def CMDsync(parser, args):
1942 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001943 parser.add_option('-f', '--force', action='store_true',
1944 help='force update even for unchanged modules')
1945 parser.add_option('-n', '--nohooks', action='store_true',
1946 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001947 parser.add_option('-p', '--noprehooks', action='store_true',
1948 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001949 parser.add_option('-r', '--revision', action='append',
1950 dest='revisions', metavar='REV', default=[],
1951 help='Enforces revision/hash for the solutions with the '
1952 'format src@rev. The src@ part is optional and can be '
1953 'skipped. -r can be used multiple times when .gclient '
1954 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001955 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001956 parser.add_option('--with_branch_heads', action='store_true',
1957 help='Clone git "branch_heads" refspecs in addition to '
1958 'the default refspecs. This adds about 1/2GB to a '
1959 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001960 parser.add_option('--with_tags', action='store_true',
1961 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001962 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08001963 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001964 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001965 help='Deletes from the working copy any dependencies that '
1966 'have been removed since the last sync, as long as '
1967 'there are no local modifications. When used with '
1968 '--force, such dependencies are removed even if they '
1969 'have local modifications. When used with --reset, '
1970 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001971 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001972 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001973 parser.add_option('-R', '--reset', action='store_true',
1974 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001975 parser.add_option('-M', '--merge', action='store_true',
1976 help='merge upstream changes instead of trying to '
1977 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001978 parser.add_option('-A', '--auto_rebase', action='store_true',
1979 help='Automatically rebase repositories against local '
1980 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001981 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1982 help='override deps for the specified (comma-separated) '
1983 'platform(s); \'all\' will process all deps_os '
1984 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001985 parser.add_option('--upstream', action='store_true',
1986 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001987 parser.add_option('--output-json',
1988 help='Output a json document to this path containing '
1989 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001990 parser.add_option('--no-history', action='store_true',
1991 help='GIT ONLY - Reduces the size/time of the checkout at '
1992 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001993 parser.add_option('--shallow', action='store_true',
1994 help='GIT ONLY - Do a shallow clone into the cache dir. '
1995 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001996 parser.add_option('--no_bootstrap', '--no-bootstrap',
1997 action='store_true',
1998 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001999 parser.add_option('--ignore_locks', action='store_true',
2000 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002001 parser.add_option('--break_repo_locks', action='store_true',
2002 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2003 'index.lock). This should only be used if you know for '
2004 'certain that this invocation of gclient is the only '
2005 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002006 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002007 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002008 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002009 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2010 parser.add_option('-t', '--transitive', action='store_true',
2011 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002012 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002013 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002014 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002015 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002016 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002017 parser.add_option('--disable-syntax-validation', action='store_false',
2018 dest='validate_syntax',
2019 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002020 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002021 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002022
2023 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002024 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002025
smutae7ea312016-07-18 11:59:41 -07002026 if options.revisions and options.head:
2027 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2028 print('Warning: you cannot use both --head and --revision')
2029
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002030 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002031 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002032 ret = client.RunOnDeps('update', args)
2033 if options.output_json:
2034 slns = {}
2035 for d in client.subtree(True):
2036 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2037 slns[normed] = {
2038 'revision': d.got_revision,
2039 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002040 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002041 }
2042 with open(options.output_json, 'wb') as f:
2043 json.dump({'solutions': slns}, f)
2044 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002045
2046
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002047CMDupdate = CMDsync
2048
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002049
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002050def CMDvalidate(parser, args):
2051 """Validates the .gclient and DEPS syntax."""
2052 options, args = parser.parse_args(args)
2053 options.validate_syntax = True
2054 client = GClient.LoadCurrentConfig(options)
2055 rv = client.RunOnDeps('validate', args)
2056 if rv == 0:
2057 print('validate: SUCCESS')
2058 else:
2059 print('validate: FAILURE')
2060 return rv
2061
2062
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002063def CMDdiff(parser, args):
2064 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002065 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2066 help='override deps for the specified (comma-separated) '
2067 'platform(s); \'all\' will process all deps_os '
2068 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002069 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002070 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002071 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002072 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002073 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002074 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002075 return client.RunOnDeps('diff', args)
2076
2077
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002078def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002079 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002080
2081 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002082 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002083 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2084 help='override deps for the specified (comma-separated) '
2085 'platform(s); \'all\' will process all deps_os '
2086 'references')
2087 parser.add_option('-n', '--nohooks', action='store_true',
2088 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002089 parser.add_option('-p', '--noprehooks', action='store_true',
2090 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002091 parser.add_option('--upstream', action='store_true',
2092 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002093 parser.add_option('--break_repo_locks', action='store_true',
2094 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2095 'index.lock). This should only be used if you know for '
2096 'certain that this invocation of gclient is the only '
2097 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002098 (options, args) = parser.parse_args(args)
2099 # --force is implied.
2100 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002101 options.reset = False
2102 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002103 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002104 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002105 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002106 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002107 return client.RunOnDeps('revert', args)
2108
2109
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002110def CMDrunhooks(parser, args):
2111 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002112 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2113 help='override deps for the specified (comma-separated) '
2114 'platform(s); \'all\' will process all deps_os '
2115 'references')
2116 parser.add_option('-f', '--force', action='store_true', default=True,
2117 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002118 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002119 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002120 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002121 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002122 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002123 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002124 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002125 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002126 return client.RunOnDeps('runhooks', args)
2127
2128
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002129def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002130 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002131
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002132 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002133 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002134 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2135 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002136 """
2137 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2138 help='override deps for the specified (comma-separated) '
2139 'platform(s); \'all\' will process all deps_os '
2140 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002141 parser.add_option('-a', '--actual', action='store_true',
2142 help='gets the actual checked out revisions instead of the '
2143 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002144 parser.add_option('-s', '--snapshot', action='store_true',
2145 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002146 'version of all repositories to reproduce the tree, '
2147 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002148 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002149 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002150 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002151 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002152 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002153 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002154
2155
szager@google.comb9a78d32012-03-13 18:46:21 +00002156def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002157 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002158 (options, args) = parser.parse_args(args)
2159 options.force = True
2160 client = GClient.LoadCurrentConfig(options)
2161 if not client:
2162 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2163 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002164 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002165 return 0
2166
2167
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002168def CMDverify(parser, args):
2169 """Verifies the DEPS file deps are only from allowed_hosts."""
2170 (options, args) = parser.parse_args(args)
2171 client = GClient.LoadCurrentConfig(options)
2172 if not client:
2173 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2174 client.RunOnDeps(None, [])
2175 # Look at each first-level dependency of this gclient only.
2176 for dep in client.dependencies:
2177 bad_deps = dep.findDepsFromNotAllowedHosts()
2178 if not bad_deps:
2179 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002180 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002181 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002182 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2183 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002184 sys.stdout.flush()
2185 raise gclient_utils.Error(
2186 'dependencies from disallowed hosts; check your DEPS file.')
2187 return 0
2188
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002189class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002190 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002191
2192 def __init__(self, **kwargs):
2193 optparse.OptionParser.__init__(
2194 self, version='%prog ' + __version__, **kwargs)
2195
2196 # Some arm boards have issues with parallel sync.
2197 if platform.machine().startswith('arm'):
2198 jobs = 1
2199 else:
2200 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002201
2202 self.add_option(
2203 '-j', '--jobs', default=jobs, type='int',
2204 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002205 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002206 self.add_option(
2207 '-v', '--verbose', action='count', default=0,
2208 help='Produces additional output for diagnostics. Can be used up to '
2209 'three times for more logging info.')
2210 self.add_option(
2211 '--gclientfile', dest='config_filename',
2212 help='Specify an alternate %s file' % self.gclientfile_default)
2213 self.add_option(
2214 '--spec',
2215 help='create a gclient file containing the provided string. Due to '
2216 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2217 self.add_option(
2218 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002219 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002220
2221 def parse_args(self, args=None, values=None):
2222 """Integrates standard options processing."""
2223 options, args = optparse.OptionParser.parse_args(self, args, values)
2224 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2225 logging.basicConfig(
2226 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002227 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002228 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002229 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002230 if (options.config_filename and
2231 options.config_filename != os.path.basename(options.config_filename)):
2232 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002233 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002234 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002235 options.entries_filename = options.config_filename + '_entries'
2236 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002237 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002238
2239 # These hacks need to die.
2240 if not hasattr(options, 'revisions'):
2241 # GClient.RunOnDeps expects it even if not applicable.
2242 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002243 if not hasattr(options, 'head'):
2244 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002245 if not hasattr(options, 'nohooks'):
2246 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002247 if not hasattr(options, 'noprehooks'):
2248 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002249 if not hasattr(options, 'deps_os'):
2250 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002251 if not hasattr(options, 'force'):
2252 options.force = None
2253 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002254
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002255
2256def disable_buffering():
2257 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2258 # operations. Python as a strong tendency to buffer sys.stdout.
2259 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2260 # Make stdout annotated with the thread ids.
2261 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002262
2263
sbc@chromium.org013731e2015-02-26 18:28:43 +00002264def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002265 """Doesn't parse the arguments here, just find the right subcommand to
2266 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002267 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002268 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002269 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002270 sys.version.split(' ', 1)[0],
2271 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002272 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002273 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002274 print(
2275 '\nPython cannot find the location of it\'s own executable.\n',
2276 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002277 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002278 fix_encoding.fix_encoding()
2279 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002280 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002281 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002282 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002283 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002284 except KeyboardInterrupt:
2285 gclient_utils.GClientChildren.KillAllRemainingChildren()
2286 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002287 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002288 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002289 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002290 finally:
2291 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002292 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002293
2294
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002295if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002296 try:
2297 sys.exit(main(sys.argv[1:]))
2298 except KeyboardInterrupt:
2299 sys.stderr.write('interrupted\n')
2300 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002301
2302# vim: ts=2:sw=2:tw=80:et: