blob: 59c899dfc3e94757ac91fac7dd66f6f30c5f75d5 [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:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200558 if self._get_option('validate_syntax', False):
559 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
560 else:
561 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000562 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000563 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000564 if use_strict:
565 for key, val in local_scope.iteritems():
566 if not isinstance(val, (dict, list, tuple, str)):
567 raise gclient_utils.Error(
568 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
569 (self.name, key, val))
570
maruel@chromium.org271375b2010-06-23 19:17:38 +0000571 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000572 if 'recursion' in local_scope:
573 self.recursion_override = local_scope.get('recursion')
574 logging.warning(
575 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000576 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000577 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000578 self.recursedeps = {}
579 for ent in local_scope['recursedeps']:
580 if isinstance(ent, basestring):
581 self.recursedeps[ent] = {"deps_file": self.deps_file}
582 else: # (depname, depsfilename)
583 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000584 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000585 # If present, save 'target_os' in the local_target_os property.
586 if 'target_os' in local_scope:
587 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000588 # load os specific dependencies if defined. these dependencies may
589 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000590 target_os_list = self.target_os
591 if 'deps_os' in local_scope and target_os_list:
592 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000593
maruel@chromium.org271375b2010-06-23 19:17:38 +0000594 # If a line is in custom_deps, but not in the solution, we want to append
595 # this line to the solution.
596 for d in self.custom_deps:
597 if d not in deps:
598 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000599
600 # If use_relative_paths is set in the DEPS file, regenerate
601 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000602 # the DEPS file. Also update recursedeps if use_relative_paths is
603 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700604 # If the deps file doesn't set use_relative_paths, but the parent did
605 # (and therefore set self.relative on this Dependency object), then we
606 # want to modify the deps and recursedeps by prepending the parent
607 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000608 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700609 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000610 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700611 rel_prefix = self.name
612 elif self._relative:
613 rel_prefix = os.path.dirname(self.name)
614 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000615 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000616 rel_deps = {}
617 for d, url in deps.items():
618 # normpath is required to allow DEPS to use .. in their
619 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700620 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
621 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000622 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000623
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000624 # Update recursedeps if it's set.
625 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700626 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000627 rel_deps = {}
628 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700629 rel_deps[
630 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000631 self.recursedeps = rel_deps
632
agabledce6ddc2016-09-08 10:02:16 -0700633
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000634 if 'allowed_hosts' in local_scope:
635 try:
636 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
637 except TypeError: # raised if non-iterable
638 pass
639 if not self._allowed_hosts:
640 logging.warning("allowed_hosts is specified but empty %s",
641 self._allowed_hosts)
642 raise gclient_utils.Error(
643 'ParseDepsFile(%s): allowed_hosts must be absent '
644 'or a non-empty iterable' % self.name)
645
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000646 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000647 deps_to_add = []
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200648 for name, dep_value in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000649 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000650 deps_file = self.deps_file
651 if self.recursedeps is not None:
652 ent = self.recursedeps.get(name)
653 if ent is not None:
654 deps_file = ent['deps_file']
Paweł Hajdan, Jr11016452017-05-29 18:02:15 +0200655 if dep_value is None:
656 continue
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200657 if isinstance(dep_value, basestring):
658 url = dep_value
659 else:
660 # This should be guaranteed by schema checking in gclient_eval.
661 assert isinstance(dep_value, dict)
662 url = dep_value['url']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000663 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800664 self, name, url, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700665 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000666 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000667
668 # override named sets of hooks by the custom hooks
669 hooks_to_run = []
670 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
671 for hook in local_scope.get('hooks', []):
672 if hook.get('name', '') not in hook_names_to_suppress:
673 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700674 if 'hooks_os' in local_scope and target_os_list:
675 hooks_os = local_scope['hooks_os']
676 # Specifically append these to ensure that hooks_os run after hooks.
677 for the_target_os in target_os_list:
678 the_target_os_hooks = hooks_os.get(the_target_os, [])
679 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000680
681 # add the replacements and any additions
682 for hook in self.custom_hooks:
683 if 'action' in hook:
684 hooks_to_run.append(hook)
685
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800686 if self.recursion_limit:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200687 self._pre_deps_hooks = [self.GetHookAction(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800688 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000689
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000690 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000691 logging.info('ParseDepsFile(%s) done' % self.name)
692
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200693 def _get_option(self, attr, default):
694 obj = self
695 while not hasattr(obj, '_options'):
696 obj = obj.parent
697 return getattr(obj._options, attr, default)
698
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000699 def add_dependencies_and_close(self, deps_to_add, hooks):
700 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000701 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000702 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000703 self.add_dependency(dep)
704 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000705
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000706 def findDepsFromNotAllowedHosts(self):
707 """Returns a list of depenecies from not allowed hosts.
708
709 If allowed_hosts is not set, allows all hosts and returns empty list.
710 """
711 if not self._allowed_hosts:
712 return []
713 bad_deps = []
714 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000715 # Don't enforce this for custom_deps.
716 if dep.name in self._custom_deps:
717 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000718 if isinstance(dep.url, basestring):
719 parsed_url = urlparse.urlparse(dep.url)
720 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
721 bad_deps.append(dep)
722 return bad_deps
723
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000724 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800725 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000726 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000727 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000728 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000729 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000730 if not self.should_process:
731 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000732 # When running runhooks, there's no need to consult the SCM.
733 # All known hooks are expected to run unconditionally regardless of working
734 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200735 run_scm = command not in (
736 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000737 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000738 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000739 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700740 if not revision_override and parsed_url:
741 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000742 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700743 # Create a shallow copy to mutate revision.
744 options = copy.copy(options)
745 options.revision = revision_override
746 self._used_revision = options.revision
747 self._used_scm = gclient_scm.CreateSCM(
748 parsed_url, self.root.root_dir, self.name, self.outbuf,
749 out_cb=work_queue.out_cb)
750 self._got_revision = self._used_scm.RunCommand(command, options, args,
751 file_list)
752 if file_list:
753 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000754
755 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
756 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000757 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000758 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000759 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000760 continue
761 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000762 [self.root.root_dir.lower(), file_list[i].lower()])
763 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000764 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000765 while file_list[i].startswith(('\\', '/')):
766 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000767
768 # Always parse the DEPS file.
769 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000770 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000771 if command in ('update', 'revert') and not options.noprehooks:
772 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000773
774 if self.recursion_limit:
775 # Parse the dependencies of this dependency.
776 for s in self.dependencies:
777 work_queue.enqueue(s)
778
779 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700780 # Skip file only checkout.
781 scm = gclient_scm.GetScmName(parsed_url)
782 if not options.scm or scm in options.scm:
783 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
784 # Pass in the SCM type as an env variable. Make sure we don't put
785 # unicode strings in the environment.
786 env = os.environ.copy()
787 if scm:
788 env['GCLIENT_SCM'] = str(scm)
789 if parsed_url:
790 env['GCLIENT_URL'] = str(parsed_url)
791 env['GCLIENT_DEP_PATH'] = str(self.name)
792 if options.prepend_dir and scm == 'git':
793 print_stdout = False
794 def filter_fn(line):
795 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000796
agabled437d762016-10-17 09:35:11 -0700797 def mod_path(git_pathspec):
798 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
799 modified_path = os.path.join(self.name, match.group(2))
800 branch = match.group(1) or ''
801 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000802
agabled437d762016-10-17 09:35:11 -0700803 match = re.match('^Binary file ([^\0]+) matches$', line)
804 if match:
805 print('Binary file %s matches\n' % mod_path(match.group(1)))
806 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000807
agabled437d762016-10-17 09:35:11 -0700808 items = line.split('\0')
809 if len(items) == 2 and items[1]:
810 print('%s : %s' % (mod_path(items[0]), items[1]))
811 elif len(items) >= 2:
812 # Multiple null bytes or a single trailing null byte indicate
813 # git is likely displaying filenames only (such as with -l)
814 print('\n'.join(mod_path(path) for path in items if path))
815 else:
816 print(line)
817 else:
818 print_stdout = True
819 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000820
agabled437d762016-10-17 09:35:11 -0700821 if parsed_url is None:
822 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
823 elif os.path.isdir(cwd):
824 try:
825 gclient_utils.CheckCallAndFilter(
826 args, cwd=cwd, env=env, print_stdout=print_stdout,
827 filter_fn=filter_fn,
828 )
829 except subprocess2.CalledProcessError:
830 if not options.ignore:
831 raise
832 else:
833 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000834
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000836 @gclient_utils.lockedmethod
837 def _run_is_done(self, file_list, parsed_url):
838 # Both these are kept for hooks that are run as a separate tree traversal.
839 self._file_list = file_list
840 self._parsed_url = parsed_url
841 self._processed = True
842
szager@google.comb9a78d32012-03-13 18:46:21 +0000843 @staticmethod
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200844 def GetHookAction(hook_dict):
szager@google.comb9a78d32012-03-13 18:46:21 +0000845 """Turns a parsed 'hook' dict into an executable command."""
846 logging.debug(hook_dict)
szager@google.comb9a78d32012-03-13 18:46:21 +0000847 command = hook_dict['action'][:]
848 if command[0] == 'python':
849 # If the hook specified "python" as the first item, the action is a
850 # Python script. Run it by starting a new copy of the same
851 # interpreter.
852 command[0] = sys.executable
szager@google.comb9a78d32012-03-13 18:46:21 +0000853 return command
854
855 def GetHooks(self, options):
856 """Evaluates all hooks, and return them in a flat list.
857
858 RunOnDeps() must have been called before to load the DEPS.
859 """
860 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000861 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000862 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000863 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000864 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000865 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000866 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700867 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000868 # what files have changed so we always run all hooks. It'd be nice to fix
869 # that.
870 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000871 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000872 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000873 for hook_dict in self.deps_hooks:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200874 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000875 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000876 # Run hooks on the basis of whether the files from the gclient operation
877 # match each hook's pattern.
878 for hook_dict in self.deps_hooks:
879 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000880 matching_file_list = [
881 f for f in self.file_list_and_children if pattern.search(f)
882 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000883 if matching_file_list:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200884 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000885 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000886 result.extend(s.GetHooks(options))
887 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000888
szager@google.comb9a78d32012-03-13 18:46:21 +0000889 def RunHooksRecursively(self, options):
890 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000891 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000892 for hook in self.GetHooks(options):
893 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000894 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000895 gclient_utils.CheckCallAndFilterAndHeader(
896 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000897 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000898 # Use a discrete exit status code of 2 to indicate that a hook action
899 # failed. Users of this script may wish to treat hook action failures
900 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000901 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000902 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000903 finally:
904 elapsed_time = time.time() - start_time
905 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000906 print("Hook '%s' took %.2f secs" % (
907 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000908
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000909 def RunPreDepsHooks(self):
910 assert self.processed
911 assert self.deps_parsed
912 assert not self.pre_deps_hooks_ran
913 assert not self.hooks_ran
914 for s in self.dependencies:
915 assert not s.processed
916 self._pre_deps_hooks_ran = True
917 for hook in self.pre_deps_hooks:
918 try:
919 start_time = time.time()
920 gclient_utils.CheckCallAndFilterAndHeader(
921 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000922 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000923 # Use a discrete exit status code of 2 to indicate that a hook action
924 # failed. Users of this script may wish to treat hook action failures
925 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000926 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000927 sys.exit(2)
928 finally:
929 elapsed_time = time.time() - start_time
930 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000931 print("Hook '%s' took %.2f secs" % (
932 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000933
934
maruel@chromium.org0d812442010-08-10 12:41:08 +0000935 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000936 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000937 dependencies = self.dependencies
938 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000939 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000940 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000941 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000942 for i in d.subtree(include_all):
943 yield i
944
945 def depth_first_tree(self):
946 """Depth-first recursion including the root node."""
947 yield self
948 for i in self.dependencies:
949 for j in i.depth_first_tree():
950 if j.should_process:
951 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000952
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000953 @gclient_utils.lockedmethod
954 def add_dependency(self, new_dep):
955 self._dependencies.append(new_dep)
956
957 @gclient_utils.lockedmethod
958 def _mark_as_parsed(self, new_hooks):
959 self._deps_hooks.extend(new_hooks)
960 self._deps_parsed = True
961
maruel@chromium.org68988972011-09-20 14:11:42 +0000962 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000963 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000964 def dependencies(self):
965 return tuple(self._dependencies)
966
967 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000968 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000969 def deps_hooks(self):
970 return tuple(self._deps_hooks)
971
972 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000973 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000974 def pre_deps_hooks(self):
975 return tuple(self._pre_deps_hooks)
976
977 @property
978 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000979 def parsed_url(self):
980 return self._parsed_url
981
982 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000983 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000984 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000985 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000986 return self._deps_parsed
987
988 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000989 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000990 def processed(self):
991 return self._processed
992
993 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000994 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000995 def pre_deps_hooks_ran(self):
996 return self._pre_deps_hooks_ran
997
998 @property
999 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001000 def hooks_ran(self):
1001 return self._hooks_ran
1002
1003 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001004 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001005 def allowed_hosts(self):
1006 return self._allowed_hosts
1007
1008 @property
1009 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001010 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001011 return tuple(self._file_list)
1012
1013 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001014 def used_scm(self):
1015 """SCMWrapper instance for this dependency or None if not processed yet."""
1016 return self._used_scm
1017
1018 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001019 @gclient_utils.lockedmethod
1020 def got_revision(self):
1021 return self._got_revision
1022
1023 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001024 def file_list_and_children(self):
1025 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001026 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001027 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001028 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001029
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001030 def __str__(self):
1031 out = []
agablea98a6cd2016-11-15 14:30:10 -08001032 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001033 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001034 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1035 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001036 # First try the native property if it exists.
1037 if hasattr(self, '_' + i):
1038 value = getattr(self, '_' + i, False)
1039 else:
1040 value = getattr(self, i, False)
1041 if value:
1042 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001043
1044 for d in self.dependencies:
1045 out.extend([' ' + x for x in str(d).splitlines()])
1046 out.append('')
1047 return '\n'.join(out)
1048
1049 def __repr__(self):
1050 return '%s: %s' % (self.name, self.url)
1051
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001052 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001053 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001054 def format_name(d):
1055 if include_url:
1056 return '%s(%s)' % (d.name, d.url)
1057 return d.name
1058 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001059 i = self.parent
1060 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001061 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001062 i = i.parent
1063 return out
1064
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001065
1066class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001067 """Object that represent a gclient checkout. A tree of Dependency(), one per
1068 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001069
1070 DEPS_OS_CHOICES = {
1071 "win32": "win",
1072 "win": "win",
1073 "cygwin": "win",
1074 "darwin": "mac",
1075 "mac": "mac",
1076 "unix": "unix",
1077 "linux": "unix",
1078 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001079 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001080 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001081 }
1082
1083 DEFAULT_CLIENT_FILE_TEXT = ("""\
1084solutions = [
smutae7ea312016-07-18 11:59:41 -07001085 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001086 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001087 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001088 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001089 "custom_deps" : {
1090 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001091 },
1092]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001093cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001094""")
1095
1096 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001097 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001098 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001099 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001100 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001101 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001102%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001103 },
1104""")
1105
1106 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1107# Snapshot generated with gclient revinfo --snapshot
1108solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001109%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001110""")
1111
1112 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001113 # Do not change previous behavior. Only solution level and immediate DEPS
1114 # are processed.
1115 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001116 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001117 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001118 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001119 if options.deps_os:
1120 enforced_os = options.deps_os.split(',')
1121 else:
1122 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1123 if 'all' in enforced_os:
1124 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001125 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001126 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001127 self.config_content = None
1128
borenet@google.com88d10082014-03-21 17:24:48 +00001129 def _CheckConfig(self):
1130 """Verify that the config matches the state of the existing checked-out
1131 solutions."""
1132 for dep in self.dependencies:
1133 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001134 scm = gclient_scm.CreateSCM(
1135 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001136 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001137 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001138 mirror = scm.GetCacheMirror()
1139 if mirror:
1140 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1141 mirror.exists())
1142 else:
1143 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001144 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001145Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001146is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001147
borenet@google.com97882362014-04-07 20:06:02 +00001148The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001149URL: %(expected_url)s (%(expected_scm)s)
1150Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001151
1152The local checkout in %(checkout_path)s reports:
1153%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001154
1155You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001156it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001157''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1158 'expected_url': dep.url,
1159 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001160 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001161 'actual_url': actual_url,
1162 'actual_scm': gclient_scm.GetScmName(actual_url)})
1163
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001164 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001165 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001166 config_dict = {}
1167 self.config_content = content
1168 try:
1169 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001170 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001171 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001172
peter@chromium.org1efccc82012-04-27 16:34:38 +00001173 # Append any target OS that is not already being enforced to the tuple.
1174 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001175 if config_dict.get('target_os_only', False):
1176 self._enforced_os = tuple(set(target_os))
1177 else:
1178 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1179
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001180 cache_dir = config_dict.get('cache_dir')
1181 if cache_dir:
1182 cache_dir = os.path.join(self.root_dir, cache_dir)
1183 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001184 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001185 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001186 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1187 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001188 gclient_scm.GitWrapper.cache_dir = cache_dir
1189 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001190
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001191 if not target_os and config_dict.get('target_os_only', False):
1192 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1193 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001194
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001195 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001196 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001197 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001198 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001199 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001200 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001201 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001202 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001203 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001204 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001205 True,
1206 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001207 except KeyError:
1208 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1209 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001210 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1211 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001212
1213 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001214 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001215 self._options.config_filename),
1216 self.config_content)
1217
1218 @staticmethod
1219 def LoadCurrentConfig(options):
1220 """Searches for and loads a .gclient file relative to the current working
1221 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001222 if options.spec:
1223 client = GClient('.', options)
1224 client.SetConfig(options.spec)
1225 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001226 if options.verbose:
1227 print('Looking for %s starting from %s\n' % (
1228 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001229 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1230 if not path:
1231 return None
1232 client = GClient(path, options)
1233 client.SetConfig(gclient_utils.FileRead(
1234 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001235
1236 if (options.revisions and
1237 len(client.dependencies) > 1 and
1238 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001239 print(
1240 ('You must specify the full solution name like --revision %s@%s\n'
1241 'when you have multiple solutions setup in your .gclient file.\n'
1242 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001243 client.dependencies[0].name,
1244 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001245 ', '.join(s.name for s in client.dependencies[1:])),
1246 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001247 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001248
nsylvain@google.comefc80932011-05-31 21:27:56 +00001249 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001250 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001251 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1252 'solution_name': solution_name,
1253 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001254 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001255 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001256 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001257 })
1258
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001259 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001260 """Creates a .gclient_entries file to record the list of unique checkouts.
1261
1262 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001263 """
1264 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1265 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001266 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001267 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001268 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1269 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001270 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001271 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001272 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001273 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001274
1275 def _ReadEntries(self):
1276 """Read the .gclient_entries file for the given client.
1277
1278 Returns:
1279 A sequence of solution names, which will be empty if there is the
1280 entries file hasn't been created yet.
1281 """
1282 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001283 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001284 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001285 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001286 try:
1287 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001288 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001289 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001290 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001291
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001292 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001293 """Checks for revision overrides."""
1294 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001295 if self._options.head:
1296 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001297 if not self._options.revisions:
1298 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001299 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001300 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001301 if not self._options.revisions:
1302 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001303 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001304 index = 0
1305 for revision in self._options.revisions:
1306 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001307 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001308 revision = '%s@%s' % (solutions_names[index], revision)
1309 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001310 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001311 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001312 return revision_overrides
1313
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001314 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001315 """Runs a command on each dependency in a client and its dependencies.
1316
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001317 Args:
1318 command: The command to use (e.g., 'status' or 'diff')
1319 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001320 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001321 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001322 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001323
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001324 revision_overrides = {}
1325 # It's unnecessary to check for revision overrides for 'recurse'.
1326 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001327 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1328 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001329 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001330 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001331 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001332 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001333 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001334 if command in ('update', 'revert'):
1335 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001336 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001337 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001338 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001339 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1340 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001341 for s in self.dependencies:
1342 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001343 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001344 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001345 print('Please fix your script, having invalid --revision flags will soon '
1346 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001347
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001348 # Once all the dependencies have been processed, it's now safe to run the
1349 # hooks.
1350 if not self._options.nohooks:
1351 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001352
1353 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001354 # Notify the user if there is an orphaned entry in their working copy.
1355 # Only delete the directory if there are no changes in it, and
1356 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001357 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001358 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1359 for e in entries]
1360
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001361 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001362 if not prev_url:
1363 # entry must have been overridden via .gclient custom_deps
1364 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001365 # Fix path separator on Windows.
1366 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001367 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001368 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001369 if (entry not in entries and
1370 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001371 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001372 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001373 scm = gclient_scm.CreateSCM(
1374 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001375
1376 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001377 scm_root = None
agabled437d762016-10-17 09:35:11 -07001378 try:
1379 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1380 except subprocess2.CalledProcessError:
1381 pass
1382 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001383 logging.warning('Could not find checkout root for %s. Unable to '
1384 'determine whether it is part of a higher-level '
1385 'checkout, so not removing.' % entry)
1386 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001387
1388 # This is to handle the case of third_party/WebKit migrating from
1389 # being a DEPS entry to being part of the main project.
1390 # If the subproject is a Git project, we need to remove its .git
1391 # folder. Otherwise git operations on that folder will have different
1392 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001393 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001394 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001395 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1396 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001397 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1398 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001399 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1400 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001401 save_dir = scm.GetGitBackupDirPath()
1402 # Remove any eventual stale backup dir for the same project.
1403 if os.path.exists(save_dir):
1404 gclient_utils.rmtree(save_dir)
1405 os.rename(os.path.join(e_dir, '.git'), save_dir)
1406 # When switching between the two states (entry/ is a subproject
1407 # -> entry/ is part of the outer project), it is very likely
1408 # that some files are changed in the checkout, unless we are
1409 # jumping *exactly* across the commit which changed just DEPS.
1410 # In such case we want to cleanup any eventual stale files
1411 # (coming from the old subproject) in order to end up with a
1412 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001413 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001414 assert not os.path.exists(os.path.join(e_dir, '.git'))
1415 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1416 'level checkout. The git folder containing all the local'
1417 ' branches has been saved to %s.\n'
1418 'If you don\'t care about its state you can safely '
1419 'remove that folder to free up space.') %
1420 (entry, save_dir))
1421 continue
1422
borenet@google.com359bb642014-05-13 17:28:19 +00001423 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001424 logging.info('%s is part of a higher level checkout, not removing',
1425 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001426 continue
1427
1428 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001429 scm.status(self._options, [], file_list)
1430 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001431 if (not self._options.delete_unversioned_trees or
1432 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001433 # There are modified files in this entry. Keep warning until
1434 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001435 print(('\nWARNING: \'%s\' is no longer part of this client. '
1436 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001437 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001438 else:
1439 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001440 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001441 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001442 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001443 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001444 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001445 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001446
1447 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001448 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001449 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001450 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001451 work_queue = gclient_utils.ExecutionQueue(
1452 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001453 for s in self.dependencies:
1454 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001455 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001457 def GetURLAndRev(dep):
1458 """Returns the revision-qualified SCM url for a Dependency."""
1459 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001460 return None
agabled437d762016-10-17 09:35:11 -07001461 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001462 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001463 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001464 if not os.path.isdir(scm.checkout_path):
1465 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001466 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001467
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001468 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001469 new_gclient = ''
1470 # First level at .gclient
1471 for d in self.dependencies:
1472 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001473 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001474 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001475 for d in dep.dependencies:
1476 entries[d.name] = GetURLAndRev(d)
1477 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001478 GrabDeps(d)
1479 custom_deps = []
1480 for k in sorted(entries.keys()):
1481 if entries[k]:
1482 # Quotes aren't escaped...
1483 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1484 else:
1485 custom_deps.append(' \"%s\": None,\n' % k)
1486 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1487 'solution_name': d.name,
1488 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001489 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001490 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001491 'solution_deps': ''.join(custom_deps),
1492 }
1493 # Print the snapshot configuration file
1494 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001495 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001496 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001497 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001498 if self._options.actual:
1499 entries[d.name] = GetURLAndRev(d)
1500 else:
1501 entries[d.name] = d.parsed_url
1502 keys = sorted(entries.keys())
1503 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001504 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001505 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001506
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001507 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001508 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001509 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001510
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001511 def PrintLocationAndContents(self):
1512 # Print out the .gclient file. This is longer than if we just printed the
1513 # client dict, but more legible, and it might contain helpful comments.
1514 print('Loaded .gclient config in %s:\n%s' % (
1515 self.root_dir, self.config_content))
1516
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001517 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001518 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001519 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001520 return self._root_dir
1521
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001522 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001523 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001524 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001525 return self._enforced_os
1526
maruel@chromium.org68988972011-09-20 14:11:42 +00001527 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001528 def recursion_limit(self):
1529 """How recursive can each dependencies in DEPS file can load DEPS file."""
1530 return self._recursion_limit
1531
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001532 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001533 def try_recursedeps(self):
1534 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001535 return True
1536
1537 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001538 def target_os(self):
1539 return self._enforced_os
1540
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001541
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001542#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001543
1544
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001545@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001546def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001547 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001548
1549 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001550 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001551 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001552 """
1553 # Stop parsing at the first non-arg so that these go through to the command
1554 parser.disable_interspersed_args()
1555 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001556 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001557 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001558 help='Ignore non-zero return codes from subcommands.')
1559 parser.add_option('--prepend-dir', action='store_true',
1560 help='Prepend relative dir for use with git <cmd> --null.')
1561 parser.add_option('--no-progress', action='store_true',
1562 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001563 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001564 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001565 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001566 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001567 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1568 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001569 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001570 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001571 'This is because .gclient_entries needs to exist and be up to date.',
1572 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001573 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001574
1575 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001576 scm_set = set()
1577 for scm in options.scm:
1578 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001579 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001580
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001581 options.nohooks = True
1582 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001583 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1584 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001585
1586
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001587@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001588def CMDfetch(parser, args):
1589 """Fetches upstream commits for all modules.
1590
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001591 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1592 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001593 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001594 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001595 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1596
1597
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001598def CMDflatten(parser, args):
1599 """Flattens the solutions into a single DEPS file."""
1600 parser.add_option('--output-deps', help='Path to the output DEPS file')
1601 parser.add_option(
1602 '--require-pinned-revisions', action='store_true',
1603 help='Fail if any of the dependencies uses unpinned revision.')
1604 options, args = parser.parse_args(args)
1605
1606 options.nohooks = True
1607 client = GClient.LoadCurrentConfig(options)
1608
1609 # Only print progress if we're writing to a file. Otherwise, progress updates
1610 # could obscure intended output.
1611 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1612 if code != 0:
1613 return code
1614
1615 deps = {}
1616 hooks = []
1617 pre_deps_hooks = []
1618 unpinned_deps = {}
1619
1620 for solution in client.dependencies:
1621 _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1622
1623 if options.require_pinned_revisions and unpinned_deps:
1624 sys.stderr.write('The following dependencies are not pinned:\n')
1625 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1626 return 1
1627
1628 flattened_deps = '\n'.join(
1629 _DepsToLines(deps) +
1630 _HooksToLines('hooks', hooks) +
1631 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1632 [''] # Ensure newline at end of file.
1633 )
1634
1635 if options.output_deps:
1636 with open(options.output_deps, 'w') as f:
1637 f.write(flattened_deps)
1638 else:
1639 print(flattened_deps)
1640
1641 return 0
1642
1643
1644def _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps):
1645 """Visits a solution in order to flatten it (see CMDflatten).
1646
1647 Arguments:
1648 solution (Dependency): one of top-level solutions in .gclient
1649
1650 Out-parameters:
1651 deps (dict of name -> Dependency): will be filled with all Dependency
1652 objects indexed by their name
1653 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1654 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1655 pre_deps_hooks
1656 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1657 deps
1658 """
1659 logging.debug('_FlattenSolution(%r)', solution)
1660
1661 _FlattenDep(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1662 _FlattenRecurse(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1663
1664
1665def _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1666 """Visits a dependency in order to flatten it (see CMDflatten).
1667
1668 Arguments:
1669 dep (Dependency): dependency to process
1670
1671 Out-parameters:
1672 deps (dict): will be filled with flattened deps
1673 hooks (list): will be filled with flattened hooks
1674 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1675 unpinned_deps (dict): will be filled with unpinned deps
1676 """
1677 logging.debug('_FlattenDep(%r)', dep)
1678
1679 _AddDep(dep, deps, unpinned_deps)
1680
1681 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1682 for recurse_dep_name in (dep.recursedeps or []):
1683 _FlattenRecurse(
1684 deps_by_name[recurse_dep_name], deps, hooks, pre_deps_hooks,
1685 unpinned_deps)
1686
1687 # TODO(phajdan.jr): also handle hooks_os.
1688 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1689 pre_deps_hooks.extend(
1690 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1691
1692
1693def _FlattenRecurse(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1694 """Helper for flatten that recurses into |dep|'s dependencies.
1695
1696 Arguments:
1697 dep (Dependency): dependency to process
1698
1699 Out-parameters:
1700 deps (dict): will be filled with flattened deps
1701 hooks (list): will be filled with flattened hooks
1702 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1703 unpinned_deps (dict): will be filled with unpinned deps
1704 """
1705 logging.debug('_FlattenRecurse(%r)', dep)
1706
1707 # TODO(phajdan.jr): also handle deps_os.
1708 for dep in dep.dependencies:
1709 _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps)
1710
1711
1712def _AddDep(dep, deps, unpinned_deps):
1713 """Helper to add a dependency to flattened lists.
1714
1715 Arguments:
1716 dep (Dependency): dependency to process
1717
1718 Out-parameters:
1719 deps (dict): will be filled with flattened deps
1720 unpinned_deps (dict): will be filled with unpinned deps
1721 """
1722 logging.debug('_AddDep(%r)', dep)
1723
1724 assert dep.name not in deps
1725 deps[dep.name] = dep
1726
1727 # Detect unpinned deps.
1728 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1729 if not revision or not gclient_utils.IsGitSha(revision):
1730 unpinned_deps[dep.name] = dep
1731
1732
1733def _DepsToLines(deps):
1734 """Converts |deps| dict to list of lines for output."""
1735 s = ['deps = {']
1736 for name, dep in sorted(deps.iteritems()):
1737 s.extend([
1738 ' # %s' % dep.hierarchy(include_url=False),
1739 ' "%s": "%s",' % (name, dep.url),
1740 '',
1741 ])
1742 s.extend(['}', ''])
1743 return s
1744
1745
1746def _HooksToLines(name, hooks):
1747 """Converts |hooks| list to list of lines for output."""
1748 s = ['%s = [' % name]
1749 for dep, hook in hooks:
1750 s.extend([
1751 ' # %s' % dep.hierarchy(include_url=False),
1752 ' {',
1753 ])
1754 if 'name' in hook:
1755 s.append(' "name": "%s",' % hook['name'])
1756 if 'pattern' in hook:
1757 s.append(' "pattern": "%s",' % hook['pattern'])
1758 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1759 # i.e. they may be relative to the dependency path, not solution root.
1760 s.extend(
1761 [' "action": ['] +
1762 [' "%s",' % arg for arg in hook['action']] +
1763 [' ]', ' },', '']
1764 )
1765 s.extend([']', ''])
1766 return s
1767
1768
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001769def CMDgrep(parser, args):
1770 """Greps through git repos managed by gclient.
1771
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001772 Runs 'git grep [args...]' for each module.
1773 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001774 # We can't use optparse because it will try to parse arguments sent
1775 # to git grep and throw an error. :-(
1776 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001777 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001778 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1779 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1780 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1781 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001782 ' end of your query.',
1783 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001784 return 1
1785
1786 jobs_arg = ['--jobs=1']
1787 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1788 jobs_arg, args = args[:1], args[1:]
1789 elif re.match(r'(-j|--jobs)$', args[0]):
1790 jobs_arg, args = args[:2], args[2:]
1791
1792 return CMDrecurse(
1793 parser,
1794 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1795 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001796
1797
stip@chromium.orga735da22015-04-29 23:18:20 +00001798def CMDroot(parser, args):
1799 """Outputs the solution root (or current dir if there isn't one)."""
1800 (options, args) = parser.parse_args(args)
1801 client = GClient.LoadCurrentConfig(options)
1802 if client:
1803 print(os.path.abspath(client.root_dir))
1804 else:
1805 print(os.path.abspath('.'))
1806
1807
agablea98a6cd2016-11-15 14:30:10 -08001808@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001809def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001810 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001811
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001812 This specifies the configuration for further commands. After update/sync,
1813 top-level DEPS files in each module are read to determine dependent
1814 modules to operate on as well. If optional [url] parameter is
1815 provided, then configuration is read from a specified Subversion server
1816 URL.
1817 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001818 # We do a little dance with the --gclientfile option. 'gclient config' is the
1819 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1820 # arguments. So, we temporarily stash any --gclientfile parameter into
1821 # options.output_config_file until after the (gclientfile xor spec) error
1822 # check.
1823 parser.remove_option('--gclientfile')
1824 parser.add_option('--gclientfile', dest='output_config_file',
1825 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001826 parser.add_option('--name',
1827 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001828 parser.add_option('--deps-file', default='DEPS',
1829 help='overrides the default name for the DEPS file for the'
1830 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001831 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001832 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001833 'to have the main solution untouched by gclient '
1834 '(gclient will check out unmanaged dependencies but '
1835 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001836 parser.add_option('--cache-dir',
1837 help='(git only) Cache all git repos into this dir and do '
1838 'shared clones from the cache, instead of cloning '
1839 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001840 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001841 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001842 if options.output_config_file:
1843 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001844 if ((options.spec and args) or len(args) > 2 or
1845 (not options.spec and not args)):
1846 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1847
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001848 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001849 if options.spec:
1850 client.SetConfig(options.spec)
1851 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001852 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001853 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001854 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001855 if name.endswith('.git'):
1856 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001857 else:
1858 # specify an alternate relpath for the given URL.
1859 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001860 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1861 os.getcwd()):
1862 parser.error('Do not pass a relative path for --name.')
1863 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1864 parser.error('Do not include relative path components in --name.')
1865
nsylvain@google.comefc80932011-05-31 21:27:56 +00001866 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001867 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001868 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001869 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001870 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001871 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001872
1873
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001874@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001875 gclient pack > patch.txt
1876 generate simple patch for configured client and dependences
1877""")
1878def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001879 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001880
agabled437d762016-10-17 09:35:11 -07001881 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001882 dependencies, and performs minimal postprocessing of the output. The
1883 resulting patch is printed to stdout and can be applied to a freshly
1884 checked out tree via 'patch -p0 < patchfile'.
1885 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001886 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1887 help='override deps for the specified (comma-separated) '
1888 'platform(s); \'all\' will process all deps_os '
1889 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001890 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001891 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001892 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001893 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001894 client = GClient.LoadCurrentConfig(options)
1895 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001896 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001897 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001898 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001899 return client.RunOnDeps('pack', args)
1900
1901
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001902def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001903 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001904 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1905 help='override deps for the specified (comma-separated) '
1906 'platform(s); \'all\' will process all deps_os '
1907 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001908 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001909 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001910 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001911 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001912 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001913 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001914 return client.RunOnDeps('status', args)
1915
1916
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001917@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001918 gclient sync
1919 update files from SCM according to current configuration,
1920 *for modules which have changed since last update or sync*
1921 gclient sync --force
1922 update files from SCM according to current configuration, for
1923 all modules (useful for recovering files deleted from local copy)
1924 gclient sync --revision src@31000
1925 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001926
1927JSON output format:
1928If the --output-json option is specified, the following document structure will
1929be emitted to the provided file. 'null' entries may occur for subprojects which
1930are present in the gclient solution, but were not processed (due to custom_deps,
1931os_deps, etc.)
1932
1933{
1934 "solutions" : {
1935 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001936 "revision": [<git id hex string>|null],
1937 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001938 }
1939 }
1940}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001941""")
1942def CMDsync(parser, args):
1943 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001944 parser.add_option('-f', '--force', action='store_true',
1945 help='force update even for unchanged modules')
1946 parser.add_option('-n', '--nohooks', action='store_true',
1947 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001948 parser.add_option('-p', '--noprehooks', action='store_true',
1949 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001950 parser.add_option('-r', '--revision', action='append',
1951 dest='revisions', metavar='REV', default=[],
1952 help='Enforces revision/hash for the solutions with the '
1953 'format src@rev. The src@ part is optional and can be '
1954 'skipped. -r can be used multiple times when .gclient '
1955 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001956 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001957 parser.add_option('--with_branch_heads', action='store_true',
1958 help='Clone git "branch_heads" refspecs in addition to '
1959 'the default refspecs. This adds about 1/2GB to a '
1960 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001961 parser.add_option('--with_tags', action='store_true',
1962 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001963 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08001964 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001965 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001966 help='Deletes from the working copy any dependencies that '
1967 'have been removed since the last sync, as long as '
1968 'there are no local modifications. When used with '
1969 '--force, such dependencies are removed even if they '
1970 'have local modifications. When used with --reset, '
1971 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001972 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001973 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001974 parser.add_option('-R', '--reset', action='store_true',
1975 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001976 parser.add_option('-M', '--merge', action='store_true',
1977 help='merge upstream changes instead of trying to '
1978 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001979 parser.add_option('-A', '--auto_rebase', action='store_true',
1980 help='Automatically rebase repositories against local '
1981 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001982 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1983 help='override deps for the specified (comma-separated) '
1984 'platform(s); \'all\' will process all deps_os '
1985 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001986 parser.add_option('--upstream', action='store_true',
1987 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001988 parser.add_option('--output-json',
1989 help='Output a json document to this path containing '
1990 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001991 parser.add_option('--no-history', action='store_true',
1992 help='GIT ONLY - Reduces the size/time of the checkout at '
1993 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001994 parser.add_option('--shallow', action='store_true',
1995 help='GIT ONLY - Do a shallow clone into the cache dir. '
1996 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001997 parser.add_option('--no_bootstrap', '--no-bootstrap',
1998 action='store_true',
1999 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002000 parser.add_option('--ignore_locks', action='store_true',
2001 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002002 parser.add_option('--break_repo_locks', action='store_true',
2003 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2004 'index.lock). This should only be used if you know for '
2005 'certain that this invocation of gclient is the only '
2006 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002007 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002008 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002009 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002010 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2011 parser.add_option('-t', '--transitive', action='store_true',
2012 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002013 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002014 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002015 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002016 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002017 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002018 parser.add_option('--disable-syntax-validation', action='store_false',
2019 dest='validate_syntax',
2020 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002021 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002022 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002023
2024 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002025 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002026
smutae7ea312016-07-18 11:59:41 -07002027 if options.revisions and options.head:
2028 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2029 print('Warning: you cannot use both --head and --revision')
2030
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002031 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002032 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002033 ret = client.RunOnDeps('update', args)
2034 if options.output_json:
2035 slns = {}
2036 for d in client.subtree(True):
2037 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2038 slns[normed] = {
2039 'revision': d.got_revision,
2040 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002041 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002042 }
2043 with open(options.output_json, 'wb') as f:
2044 json.dump({'solutions': slns}, f)
2045 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002046
2047
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002048CMDupdate = CMDsync
2049
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002050
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002051def CMDvalidate(parser, args):
2052 """Validates the .gclient and DEPS syntax."""
2053 options, args = parser.parse_args(args)
2054 options.validate_syntax = True
2055 client = GClient.LoadCurrentConfig(options)
2056 rv = client.RunOnDeps('validate', args)
2057 if rv == 0:
2058 print('validate: SUCCESS')
2059 else:
2060 print('validate: FAILURE')
2061 return rv
2062
2063
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002064def CMDdiff(parser, args):
2065 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002066 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2067 help='override deps for the specified (comma-separated) '
2068 'platform(s); \'all\' will process all deps_os '
2069 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002070 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002071 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002072 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002073 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002074 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002075 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002076 return client.RunOnDeps('diff', args)
2077
2078
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002079def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002080 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002081
2082 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002083 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002084 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2085 help='override deps for the specified (comma-separated) '
2086 'platform(s); \'all\' will process all deps_os '
2087 'references')
2088 parser.add_option('-n', '--nohooks', action='store_true',
2089 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002090 parser.add_option('-p', '--noprehooks', action='store_true',
2091 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002092 parser.add_option('--upstream', action='store_true',
2093 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002094 parser.add_option('--break_repo_locks', action='store_true',
2095 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2096 'index.lock). This should only be used if you know for '
2097 'certain that this invocation of gclient is the only '
2098 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002099 (options, args) = parser.parse_args(args)
2100 # --force is implied.
2101 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002102 options.reset = False
2103 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002104 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002105 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002106 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002107 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002108 return client.RunOnDeps('revert', args)
2109
2110
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002111def CMDrunhooks(parser, args):
2112 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002113 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2114 help='override deps for the specified (comma-separated) '
2115 'platform(s); \'all\' will process all deps_os '
2116 'references')
2117 parser.add_option('-f', '--force', action='store_true', default=True,
2118 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002119 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002120 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002121 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002122 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002123 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002124 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002125 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002126 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002127 return client.RunOnDeps('runhooks', args)
2128
2129
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002130def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002131 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002132
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002133 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002134 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002135 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2136 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002137 """
2138 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2139 help='override deps for the specified (comma-separated) '
2140 'platform(s); \'all\' will process all deps_os '
2141 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002142 parser.add_option('-a', '--actual', action='store_true',
2143 help='gets the actual checked out revisions instead of the '
2144 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002145 parser.add_option('-s', '--snapshot', action='store_true',
2146 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002147 'version of all repositories to reproduce the tree, '
2148 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002149 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002150 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002151 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002152 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002153 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002154 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002155
2156
szager@google.comb9a78d32012-03-13 18:46:21 +00002157def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002158 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002159 (options, args) = parser.parse_args(args)
2160 options.force = True
2161 client = GClient.LoadCurrentConfig(options)
2162 if not client:
2163 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2164 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002165 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002166 return 0
2167
2168
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002169def CMDverify(parser, args):
2170 """Verifies the DEPS file deps are only from allowed_hosts."""
2171 (options, args) = parser.parse_args(args)
2172 client = GClient.LoadCurrentConfig(options)
2173 if not client:
2174 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2175 client.RunOnDeps(None, [])
2176 # Look at each first-level dependency of this gclient only.
2177 for dep in client.dependencies:
2178 bad_deps = dep.findDepsFromNotAllowedHosts()
2179 if not bad_deps:
2180 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002181 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002182 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002183 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2184 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002185 sys.stdout.flush()
2186 raise gclient_utils.Error(
2187 'dependencies from disallowed hosts; check your DEPS file.')
2188 return 0
2189
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002190class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002191 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002192
2193 def __init__(self, **kwargs):
2194 optparse.OptionParser.__init__(
2195 self, version='%prog ' + __version__, **kwargs)
2196
2197 # Some arm boards have issues with parallel sync.
2198 if platform.machine().startswith('arm'):
2199 jobs = 1
2200 else:
2201 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002202
2203 self.add_option(
2204 '-j', '--jobs', default=jobs, type='int',
2205 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002206 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002207 self.add_option(
2208 '-v', '--verbose', action='count', default=0,
2209 help='Produces additional output for diagnostics. Can be used up to '
2210 'three times for more logging info.')
2211 self.add_option(
2212 '--gclientfile', dest='config_filename',
2213 help='Specify an alternate %s file' % self.gclientfile_default)
2214 self.add_option(
2215 '--spec',
2216 help='create a gclient file containing the provided string. Due to '
2217 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2218 self.add_option(
2219 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002220 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002221
2222 def parse_args(self, args=None, values=None):
2223 """Integrates standard options processing."""
2224 options, args = optparse.OptionParser.parse_args(self, args, values)
2225 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2226 logging.basicConfig(
2227 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002228 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002229 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002230 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002231 if (options.config_filename and
2232 options.config_filename != os.path.basename(options.config_filename)):
2233 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002234 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002235 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002236 options.entries_filename = options.config_filename + '_entries'
2237 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002238 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002239
2240 # These hacks need to die.
2241 if not hasattr(options, 'revisions'):
2242 # GClient.RunOnDeps expects it even if not applicable.
2243 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002244 if not hasattr(options, 'head'):
2245 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002246 if not hasattr(options, 'nohooks'):
2247 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002248 if not hasattr(options, 'noprehooks'):
2249 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002250 if not hasattr(options, 'deps_os'):
2251 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002252 if not hasattr(options, 'force'):
2253 options.force = None
2254 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002255
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002256
2257def disable_buffering():
2258 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2259 # operations. Python as a strong tendency to buffer sys.stdout.
2260 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2261 # Make stdout annotated with the thread ids.
2262 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002263
2264
sbc@chromium.org013731e2015-02-26 18:28:43 +00002265def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002266 """Doesn't parse the arguments here, just find the right subcommand to
2267 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002268 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002269 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002270 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002271 sys.version.split(' ', 1)[0],
2272 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002273 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002274 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002275 print(
2276 '\nPython cannot find the location of it\'s own executable.\n',
2277 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002278 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002279 fix_encoding.fix_encoding()
2280 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002281 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002282 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002283 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002284 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002285 except KeyboardInterrupt:
2286 gclient_utils.GClientChildren.KillAllRemainingChildren()
2287 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002288 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002289 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002290 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002291 finally:
2292 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002293 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002294
2295
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002296if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002297 try:
2298 sys.exit(main(sys.argv[1:]))
2299 except KeyboardInterrupt:
2300 sys.stderr.write('interrupted\n')
2301 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002302
2303# vim: ts=2:sw=2:tw=80:et: