blob: f1fa9b53e4472a8b6f740dced89633c12a1f8ffb [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
agabled437d762016-10-17 09:35:11 -07006"""Meta checkout dependency manager for Git."""
maruel@chromium.org39c0b222013-08-17 16:57:01 +00007# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
Scott Grahamc4826742017-05-11 16:59:23 -070067# processing the deps_os/hooks_os dict of a DEPS file.
maruel@chromium.org39c0b222013-08-17 16:57:01 +000068#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000079from __future__ import print_function
80
maruel@chromium.org39c0b222013-08-17 16:57:01 +000081__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
szager@chromium.org7b8b6de2014-08-23 00:57:31 +000083import ast
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000084import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000085import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000086import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087import optparse
88import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000089import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000090import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000091import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000094import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000095import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000096import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
maruel@chromium.org35625c72011-03-23 17:34:02 +000098import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020099import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000100import gclient_scm
101import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000102import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000103from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000104import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000105import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000106import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000108CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
109
110
111def ast_dict_index(dnode, key):
112 """Search an ast.Dict for the argument key, and return its index."""
113 idx = [i for i in range(len(dnode.keys)) if (
114 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
115 if not idx:
116 return -1
117 elif len(idx) > 1:
118 raise gclient_utils.Error('Multiple dict entries with same key in AST')
119 return idx[-1]
120
121def ast2str(node, indent=0):
122 """Return a pretty-printed rendition of an ast.Node."""
123 t = type(node)
124 if t is ast.Module:
125 return '\n'.join([ast2str(x, indent) for x in node.body])
126 elif t is ast.Assign:
127 return ((' ' * indent) +
128 ' = '.join([ast2str(x) for x in node.targets] +
129 [ast2str(node.value, indent)]) + '\n')
130 elif t is ast.Name:
131 return node.id
132 elif t is ast.List:
133 if not node.elts:
134 return '[]'
135 elif len(node.elts) == 1:
136 return '[' + ast2str(node.elts[0], indent) + ']'
137 return ('[\n' + (' ' * (indent + 1)) +
138 (',\n' + (' ' * (indent + 1))).join(
139 [ast2str(x, indent + 1) for x in node.elts]) +
140 '\n' + (' ' * indent) + ']')
141 elif t is ast.Dict:
142 if not node.keys:
143 return '{}'
144 elif len(node.keys) == 1:
145 return '{%s: %s}' % (ast2str(node.keys[0]),
146 ast2str(node.values[0], indent + 1))
147 return ('{\n' + (' ' * (indent + 1)) +
148 (',\n' + (' ' * (indent + 1))).join(
149 ['%s: %s' % (ast2str(node.keys[i]),
150 ast2str(node.values[i], indent + 1))
151 for i in range(len(node.keys))]) +
152 '\n' + (' ' * indent) + '}')
153 elif t is ast.Str:
154 return "'%s'" % node.s
155 else:
156 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
157 % (node.lineno, node.col_offset, t))
158
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000159
maruel@chromium.org116704f2010-06-11 17:34:38 +0000160class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000161 class VarImpl(object):
162 def __init__(self, custom_vars, local_scope):
163 self._custom_vars = custom_vars
164 self._local_scope = local_scope
165
166 def Lookup(self, var_name):
167 """Implements the Var syntax."""
168 if var_name in self._custom_vars:
169 return self._custom_vars[var_name]
170 elif var_name in self._local_scope.get("vars", {}):
171 return self._local_scope["vars"][var_name]
172 raise gclient_utils.Error("Var is not defined: %s" % var_name)
173
174
maruel@chromium.org064186c2011-09-27 23:53:33 +0000175class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 """Immutable configuration settings."""
177 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800178 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700179 custom_hooks, deps_file, should_process, relative):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000180 GClientKeywords.__init__(self)
181
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000182 # These are not mutable:
183 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000184 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000185 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 # 'managed' determines whether or not this dependency is synced/updated by
187 # gclient after gclient checks it out initially. The difference between
188 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700189 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 # 'should_process' is dynamically set by gclient if it goes over its
191 # recursion limit and controls gclient's behavior so it does not misbehave.
192 self._managed = managed
193 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700194 # If this is a recursed-upon sub-dependency, and the parent has
195 # use_relative_paths set, then this dependency should check out its own
196 # dependencies relative to that parent's path for this, rather than
197 # relative to the .gclient file.
198 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000199 # This is a mutable value which has the list of 'target_os' OSes listed in
200 # the current deps file.
201 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202
203 # These are only set in .gclient and not in DEPS files.
204 self._custom_vars = custom_vars or {}
205 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000206 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000207
maruel@chromium.org064186c2011-09-27 23:53:33 +0000208 # Post process the url to remove trailing slashes.
209 if isinstance(self._url, basestring):
210 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
211 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000212 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200213 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000214 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200215 ('dependency url must be either string or None, '
216 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000217 # Make any deps_file path platform-appropriate.
218 for sep in ['/', '\\']:
219 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220
221 @property
222 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000223 return self._deps_file
224
225 @property
226 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000227 return self._managed
228
229 @property
230 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000231 return self._parent
232
233 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000234 def root(self):
235 """Returns the root node, a GClient object."""
236 if not self.parent:
237 # This line is to signal pylint that it could be a GClient instance.
238 return self or GClient(None, None)
239 return self.parent.root
240
241 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 def should_process(self):
243 """True if this dependency should be processed, i.e. checked out."""
244 return self._should_process
245
246 @property
247 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000248 return self._custom_vars.copy()
249
250 @property
251 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000252 return self._custom_deps.copy()
253
maruel@chromium.org064186c2011-09-27 23:53:33 +0000254 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000255 def custom_hooks(self):
256 return self._custom_hooks[:]
257
258 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 def url(self):
260 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000261
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000262 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000263 def target_os(self):
264 if self.local_target_os is not None:
265 return tuple(set(self.local_target_os).union(self.parent.target_os))
266 else:
267 return self.parent.target_os
268
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000269 def get_custom_deps(self, name, url):
270 """Returns a custom deps if applicable."""
271 if self.parent:
272 url = self.parent.get_custom_deps(name, url)
273 # None is a valid return value to disable a dependency.
274 return self.custom_deps.get(name, url)
275
maruel@chromium.org064186c2011-09-27 23:53:33 +0000276
277class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000278 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000279
agablea98a6cd2016-11-15 14:30:10 -0800280 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700281 custom_vars, custom_hooks, deps_file, should_process,
282 relative):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000283 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000284 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800285 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700286 custom_hooks, deps_file, should_process, relative)
maruel@chromium.org68988972011-09-20 14:11:42 +0000287
288 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000289 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000290
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000291 self._pre_deps_hooks = []
292
maruel@chromium.org68988972011-09-20 14:11:42 +0000293 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000294 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000295 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000296 # A cache of the files affected by the current operation, necessary for
297 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000298 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000299 # List of host names from which dependencies are allowed.
300 # Default is an empty set, meaning unspecified in DEPS file, and hence all
301 # hosts will be allowed. Non-empty set means whitelist of hosts.
302 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
303 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000304 # If it is not set to True, the dependency wasn't processed for its child
305 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000306 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000307 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000309 # This dependency had its pre-DEPS hooks run
310 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000311 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000312 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000313 # This is the scm used to checkout self.url. It may be used by dependencies
314 # to get the datetime of the revision we checked out.
315 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000316 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000317 # The actual revision we ended up getting, or None if that information is
318 # unavailable
319 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000320
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000321 # This is a mutable value that overrides the normal recursion limit for this
322 # dependency. It is read from the actual DEPS file so cannot be set on
323 # class instantiation.
324 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000325 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000326 # 'no recursion' setting on a dep-by-dep basis. It will replace
327 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000328 #
329 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
330 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000331 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700332 # This is inherited from WorkItem. We want the URL to be a resource.
333 if url and isinstance(url, basestring):
334 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700335 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700336 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000337
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000338 if not self.name and self.parent:
339 raise gclient_utils.Error('Dependency without name')
340
maruel@chromium.org470b5432011-10-11 18:18:19 +0000341 @property
342 def requirements(self):
343 """Calculate the list of requirements."""
344 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000345 # self.parent is implicitly a requirement. This will be recursive by
346 # definition.
347 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000348 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000349
350 # For a tree with at least 2 levels*, the leaf node needs to depend
351 # on the level higher up in an orderly way.
352 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
353 # thus unsorted, while the .gclient format is a list thus sorted.
354 #
355 # * _recursion_limit is hard coded 2 and there is no hope to change this
356 # value.
357 #
358 # Interestingly enough, the following condition only works in the case we
359 # want: self is a 2nd level node. 3nd level node wouldn't need this since
360 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000361 if self.parent and self.parent.parent and not self.parent.parent.parent:
362 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000363
maruel@chromium.org470b5432011-10-11 18:18:19 +0000364 if self.name:
365 requirements |= set(
366 obj.name for obj in self.root.subtree(False)
367 if (obj is not self
368 and obj.name and
369 self.name.startswith(posixpath.join(obj.name, ''))))
370 requirements = tuple(sorted(requirements))
371 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
372 return requirements
373
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000374 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000375 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000376 """Returns False if recursion_override is ever specified."""
377 if self.recursion_override is not None:
378 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000379 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000380
381 @property
382 def recursion_limit(self):
383 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000384 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000385 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000386 if self.try_recursedeps and self.parent.recursedeps != None:
387 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000388 return 1
389
390 if self.recursion_override is not None:
391 return self.recursion_override
392 return max(self.parent.recursion_limit - 1, 0)
393
maruel@chromium.org470b5432011-10-11 18:18:19 +0000394 def verify_validity(self):
395 """Verifies that this Dependency is fine to add as a child of another one.
396
397 Returns True if this entry should be added, False if it is a duplicate of
398 another entry.
399 """
400 logging.info('Dependency(%s).verify_validity()' % self.name)
401 if self.name in [s.name for s in self.parent.dependencies]:
402 raise gclient_utils.Error(
403 'The same name "%s" appears multiple times in the deps section' %
404 self.name)
405 if not self.should_process:
406 # Return early, no need to set requirements.
407 return True
408
409 # This require a full tree traversal with locks.
410 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
411 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000412 self_url = self.LateOverride(self.url)
413 sibling_url = sibling.LateOverride(sibling.url)
414 # Allow to have only one to be None or ''.
415 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000416 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000417 ('Dependency %s specified more than once:\n'
418 ' %s [%s]\n'
419 'vs\n'
420 ' %s [%s]') % (
421 self.name,
422 sibling.hierarchy(),
423 sibling_url,
424 self.hierarchy(),
425 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000426 # In theory we could keep it as a shadow of the other one. In
427 # practice, simply ignore it.
428 logging.warn('Won\'t process duplicate dependency %s' % sibling)
429 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000430 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000431
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000432 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200433 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000434 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000435 parsed_url = self.get_custom_deps(self.name, url)
436 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000437 logging.info(
438 'Dependency(%s).LateOverride(%s) -> %s' %
439 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000440 return parsed_url
441
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000442 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000443 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000444 if (not parsed_url[0] and
445 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000446 # A relative url. Fetch the real base.
447 path = parsed_url[2]
448 if not path.startswith('/'):
449 raise gclient_utils.Error(
450 'relative DEPS entry \'%s\' must begin with a slash' % url)
451 # Create a scm just to query the full url.
452 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000453 scm = gclient_scm.CreateSCM(
454 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000455 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000456 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000457 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000458 logging.info(
459 'Dependency(%s).LateOverride(%s) -> %s' %
460 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000461 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000462
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000463 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000464 logging.info(
465 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000466 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000467
468 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000469
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000470 @staticmethod
471 def MergeWithOsDeps(deps, deps_os, target_os_list):
472 """Returns a new "deps" structure that is the deps sent in updated
473 with information from deps_os (the deps_os section of the DEPS
474 file) that matches the list of target os."""
475 os_overrides = {}
476 for the_target_os in target_os_list:
477 the_target_os_deps = deps_os.get(the_target_os, {})
478 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
479 overrides = os_overrides.setdefault(os_dep_key, [])
480 overrides.append((the_target_os, os_dep_value))
481
482 # If any os didn't specify a value (we have fewer value entries
483 # than in the os list), then it wants to use the default value.
484 for os_dep_key, os_dep_value in os_overrides.iteritems():
485 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700486 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000487 # set it to None or miss a conflicting DEPS.
488 if os_dep_key in deps:
489 os_dep_value.append(('default', deps[os_dep_key]))
490
491 target_os_deps = {}
492 for os_dep_key, os_dep_value in os_overrides.iteritems():
493 # os_dep_value is a list of (os, value) pairs.
494 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
495 if not possible_values:
496 target_os_deps[os_dep_key] = None
497 else:
498 if len(possible_values) > 1:
499 # It would be possible to abort here but it would be
500 # unfortunate if we end up preventing any kind of checkout.
501 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
502 os_dep_key, os_dep_value, target_os_list)
503 # Sorting to get the same result every time in case of conflicts.
504 target_os_deps[os_dep_key] = sorted(possible_values)[0]
505
506 new_deps = deps.copy()
507 new_deps.update(target_os_deps)
508 return new_deps
509
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000510 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000511 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000512 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000513 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000514
515 deps_content = None
516 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000517
518 # First try to locate the configured deps file. If it's missing, fallback
519 # to DEPS.
520 deps_files = [self.deps_file]
521 if 'DEPS' not in deps_files:
522 deps_files.append('DEPS')
523 for deps_file in deps_files:
524 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
525 if os.path.isfile(filepath):
526 logging.info(
527 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
528 filepath)
529 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000530 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000531 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
532 filepath)
533
534 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000535 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000536 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000537 use_strict = 'use strict' in deps_content.splitlines()[0]
538
539 local_scope = {}
540 if deps_content:
541 # One thing is unintuitive, vars = {} must happen before Var() use.
542 var = self.VarImpl(self.custom_vars, local_scope)
543 if use_strict:
544 logging.info(
545 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
546 global_scope = {
547 '__builtins__': {'None': None},
548 'Var': var.Lookup,
549 'deps_os': {},
550 }
551 else:
552 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000553 'Var': var.Lookup,
554 'deps_os': {},
555 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000556 # Eval the content.
557 try:
558 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000559 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000560 gclient_utils.SyntaxErrorToError(filepath, e)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200561 if self._get_option('validate_syntax', False):
562 gclient_eval.Check(deps_content, filepath, global_scope, local_scope)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000563 if use_strict:
564 for key, val in local_scope.iteritems():
565 if not isinstance(val, (dict, list, tuple, str)):
566 raise gclient_utils.Error(
567 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
568 (self.name, key, val))
569
maruel@chromium.org271375b2010-06-23 19:17:38 +0000570 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000571 if 'recursion' in local_scope:
572 self.recursion_override = local_scope.get('recursion')
573 logging.warning(
574 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000575 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000576 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000577 self.recursedeps = {}
578 for ent in local_scope['recursedeps']:
579 if isinstance(ent, basestring):
580 self.recursedeps[ent] = {"deps_file": self.deps_file}
581 else: # (depname, depsfilename)
582 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000583 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000584 # If present, save 'target_os' in the local_target_os property.
585 if 'target_os' in local_scope:
586 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000587 # load os specific dependencies if defined. these dependencies may
588 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000589 target_os_list = self.target_os
590 if 'deps_os' in local_scope and target_os_list:
591 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000592
maruel@chromium.org271375b2010-06-23 19:17:38 +0000593 # If a line is in custom_deps, but not in the solution, we want to append
594 # this line to the solution.
595 for d in self.custom_deps:
596 if d not in deps:
597 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000598
599 # If use_relative_paths is set in the DEPS file, regenerate
600 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000601 # the DEPS file. Also update recursedeps if use_relative_paths is
602 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700603 # If the deps file doesn't set use_relative_paths, but the parent did
604 # (and therefore set self.relative on this Dependency object), then we
605 # want to modify the deps and recursedeps by prepending the parent
606 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000607 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700608 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000609 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700610 rel_prefix = self.name
611 elif self._relative:
612 rel_prefix = os.path.dirname(self.name)
613 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000614 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000615 rel_deps = {}
616 for d, url in deps.items():
617 # normpath is required to allow DEPS to use .. in their
618 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700619 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
620 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000621 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000622
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000623 # Update recursedeps if it's set.
624 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700625 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000626 rel_deps = {}
627 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700628 rel_deps[
629 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000630 self.recursedeps = rel_deps
631
agabledce6ddc2016-09-08 10:02:16 -0700632
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000633 if 'allowed_hosts' in local_scope:
634 try:
635 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
636 except TypeError: # raised if non-iterable
637 pass
638 if not self._allowed_hosts:
639 logging.warning("allowed_hosts is specified but empty %s",
640 self._allowed_hosts)
641 raise gclient_utils.Error(
642 'ParseDepsFile(%s): allowed_hosts must be absent '
643 'or a non-empty iterable' % self.name)
644
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000645 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000646 deps_to_add = []
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200647 for name, dep_value in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000648 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000649 deps_file = self.deps_file
650 if self.recursedeps is not None:
651 ent = self.recursedeps.get(name)
652 if ent is not None:
653 deps_file = ent['deps_file']
Paweł Hajdan, Jr11016452017-05-29 18:02:15 +0200654 if dep_value is None:
655 continue
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200656 if isinstance(dep_value, basestring):
657 url = dep_value
658 else:
659 # This should be guaranteed by schema checking in gclient_eval.
660 assert isinstance(dep_value, dict)
661 url = dep_value['url']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000662 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800663 self, name, url, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700664 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000665 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000666
667 # override named sets of hooks by the custom hooks
668 hooks_to_run = []
669 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
670 for hook in local_scope.get('hooks', []):
671 if hook.get('name', '') not in hook_names_to_suppress:
672 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700673 if 'hooks_os' in local_scope and target_os_list:
674 hooks_os = local_scope['hooks_os']
675 # Specifically append these to ensure that hooks_os run after hooks.
676 for the_target_os in target_os_list:
677 the_target_os_hooks = hooks_os.get(the_target_os, [])
678 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000679
680 # add the replacements and any additions
681 for hook in self.custom_hooks:
682 if 'action' in hook:
683 hooks_to_run.append(hook)
684
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800685 if self.recursion_limit:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200686 self._pre_deps_hooks = [self.GetHookAction(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800687 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000688
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000689 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000690 logging.info('ParseDepsFile(%s) done' % self.name)
691
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200692 def _get_option(self, attr, default):
693 obj = self
694 while not hasattr(obj, '_options'):
695 obj = obj.parent
696 return getattr(obj._options, attr, default)
697
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000698 def add_dependencies_and_close(self, deps_to_add, hooks):
699 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000700 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000701 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000702 self.add_dependency(dep)
703 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000704
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000705 def findDepsFromNotAllowedHosts(self):
706 """Returns a list of depenecies from not allowed hosts.
707
708 If allowed_hosts is not set, allows all hosts and returns empty list.
709 """
710 if not self._allowed_hosts:
711 return []
712 bad_deps = []
713 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000714 # Don't enforce this for custom_deps.
715 if dep.name in self._custom_deps:
716 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000717 if isinstance(dep.url, basestring):
718 parsed_url = urlparse.urlparse(dep.url)
719 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
720 bad_deps.append(dep)
721 return bad_deps
722
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000723 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800724 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000725 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000726 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000727 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000728 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000729 if not self.should_process:
730 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000731 # When running runhooks, there's no need to consult the SCM.
732 # All known hooks are expected to run unconditionally regardless of working
733 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200734 run_scm = command not in (
735 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000736 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000737 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000738 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000739 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700740 # Create a shallow copy to mutate revision.
741 options = copy.copy(options)
742 options.revision = revision_override
743 self._used_revision = options.revision
744 self._used_scm = gclient_scm.CreateSCM(
745 parsed_url, self.root.root_dir, self.name, self.outbuf,
746 out_cb=work_queue.out_cb)
747 self._got_revision = self._used_scm.RunCommand(command, options, args,
748 file_list)
749 if file_list:
750 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000751
752 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
753 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000754 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000755 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000756 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000757 continue
758 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000759 [self.root.root_dir.lower(), file_list[i].lower()])
760 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000761 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000762 while file_list[i].startswith(('\\', '/')):
763 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000764
765 # Always parse the DEPS file.
766 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000767 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000768 if command in ('update', 'revert') and not options.noprehooks:
769 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000770
771 if self.recursion_limit:
772 # Parse the dependencies of this dependency.
773 for s in self.dependencies:
774 work_queue.enqueue(s)
775
776 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700777 # Skip file only checkout.
778 scm = gclient_scm.GetScmName(parsed_url)
779 if not options.scm or scm in options.scm:
780 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
781 # Pass in the SCM type as an env variable. Make sure we don't put
782 # unicode strings in the environment.
783 env = os.environ.copy()
784 if scm:
785 env['GCLIENT_SCM'] = str(scm)
786 if parsed_url:
787 env['GCLIENT_URL'] = str(parsed_url)
788 env['GCLIENT_DEP_PATH'] = str(self.name)
789 if options.prepend_dir and scm == 'git':
790 print_stdout = False
791 def filter_fn(line):
792 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000793
agabled437d762016-10-17 09:35:11 -0700794 def mod_path(git_pathspec):
795 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
796 modified_path = os.path.join(self.name, match.group(2))
797 branch = match.group(1) or ''
798 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000799
agabled437d762016-10-17 09:35:11 -0700800 match = re.match('^Binary file ([^\0]+) matches$', line)
801 if match:
802 print('Binary file %s matches\n' % mod_path(match.group(1)))
803 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000804
agabled437d762016-10-17 09:35:11 -0700805 items = line.split('\0')
806 if len(items) == 2 and items[1]:
807 print('%s : %s' % (mod_path(items[0]), items[1]))
808 elif len(items) >= 2:
809 # Multiple null bytes or a single trailing null byte indicate
810 # git is likely displaying filenames only (such as with -l)
811 print('\n'.join(mod_path(path) for path in items if path))
812 else:
813 print(line)
814 else:
815 print_stdout = True
816 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000817
agabled437d762016-10-17 09:35:11 -0700818 if parsed_url is None:
819 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
820 elif os.path.isdir(cwd):
821 try:
822 gclient_utils.CheckCallAndFilter(
823 args, cwd=cwd, env=env, print_stdout=print_stdout,
824 filter_fn=filter_fn,
825 )
826 except subprocess2.CalledProcessError:
827 if not options.ignore:
828 raise
829 else:
830 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000831
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000832
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000833 @gclient_utils.lockedmethod
834 def _run_is_done(self, file_list, parsed_url):
835 # Both these are kept for hooks that are run as a separate tree traversal.
836 self._file_list = file_list
837 self._parsed_url = parsed_url
838 self._processed = True
839
szager@google.comb9a78d32012-03-13 18:46:21 +0000840 @staticmethod
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200841 def GetHookAction(hook_dict):
szager@google.comb9a78d32012-03-13 18:46:21 +0000842 """Turns a parsed 'hook' dict into an executable command."""
843 logging.debug(hook_dict)
szager@google.comb9a78d32012-03-13 18:46:21 +0000844 command = hook_dict['action'][:]
845 if command[0] == 'python':
846 # If the hook specified "python" as the first item, the action is a
847 # Python script. Run it by starting a new copy of the same
848 # interpreter.
849 command[0] = sys.executable
szager@google.comb9a78d32012-03-13 18:46:21 +0000850 return command
851
852 def GetHooks(self, options):
853 """Evaluates all hooks, and return them in a flat list.
854
855 RunOnDeps() must have been called before to load the DEPS.
856 """
857 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000858 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000859 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000860 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000861 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000862 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000863 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700864 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000865 # what files have changed so we always run all hooks. It'd be nice to fix
866 # that.
867 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000868 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000869 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000870 for hook_dict in self.deps_hooks:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200871 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000872 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000873 # Run hooks on the basis of whether the files from the gclient operation
874 # match each hook's pattern.
875 for hook_dict in self.deps_hooks:
876 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000877 matching_file_list = [
878 f for f in self.file_list_and_children if pattern.search(f)
879 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000880 if matching_file_list:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200881 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000882 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000883 result.extend(s.GetHooks(options))
884 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000885
szager@google.comb9a78d32012-03-13 18:46:21 +0000886 def RunHooksRecursively(self, options):
887 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000888 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000889 for hook in self.GetHooks(options):
890 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000891 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000892 gclient_utils.CheckCallAndFilterAndHeader(
893 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000894 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000895 # Use a discrete exit status code of 2 to indicate that a hook action
896 # failed. Users of this script may wish to treat hook action failures
897 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000898 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000899 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000900 finally:
901 elapsed_time = time.time() - start_time
902 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000903 print("Hook '%s' took %.2f secs" % (
904 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000905
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000906 def RunPreDepsHooks(self):
907 assert self.processed
908 assert self.deps_parsed
909 assert not self.pre_deps_hooks_ran
910 assert not self.hooks_ran
911 for s in self.dependencies:
912 assert not s.processed
913 self._pre_deps_hooks_ran = True
914 for hook in self.pre_deps_hooks:
915 try:
916 start_time = time.time()
917 gclient_utils.CheckCallAndFilterAndHeader(
918 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000919 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000920 # Use a discrete exit status code of 2 to indicate that a hook action
921 # failed. Users of this script may wish to treat hook action failures
922 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000923 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000924 sys.exit(2)
925 finally:
926 elapsed_time = time.time() - start_time
927 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000928 print("Hook '%s' took %.2f secs" % (
929 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000930
931
maruel@chromium.org0d812442010-08-10 12:41:08 +0000932 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000933 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000934 dependencies = self.dependencies
935 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000936 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000937 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000938 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000939 for i in d.subtree(include_all):
940 yield i
941
942 def depth_first_tree(self):
943 """Depth-first recursion including the root node."""
944 yield self
945 for i in self.dependencies:
946 for j in i.depth_first_tree():
947 if j.should_process:
948 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000949
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000950 @gclient_utils.lockedmethod
951 def add_dependency(self, new_dep):
952 self._dependencies.append(new_dep)
953
954 @gclient_utils.lockedmethod
955 def _mark_as_parsed(self, new_hooks):
956 self._deps_hooks.extend(new_hooks)
957 self._deps_parsed = True
958
maruel@chromium.org68988972011-09-20 14:11:42 +0000959 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000960 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000961 def dependencies(self):
962 return tuple(self._dependencies)
963
964 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000965 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000966 def deps_hooks(self):
967 return tuple(self._deps_hooks)
968
969 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000970 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000971 def pre_deps_hooks(self):
972 return tuple(self._pre_deps_hooks)
973
974 @property
975 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000976 def parsed_url(self):
977 return self._parsed_url
978
979 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000980 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000981 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000982 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000983 return self._deps_parsed
984
985 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000986 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000987 def processed(self):
988 return self._processed
989
990 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000991 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000992 def pre_deps_hooks_ran(self):
993 return self._pre_deps_hooks_ran
994
995 @property
996 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000997 def hooks_ran(self):
998 return self._hooks_ran
999
1000 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001001 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001002 def allowed_hosts(self):
1003 return self._allowed_hosts
1004
1005 @property
1006 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001007 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001008 return tuple(self._file_list)
1009
1010 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001011 def used_scm(self):
1012 """SCMWrapper instance for this dependency or None if not processed yet."""
1013 return self._used_scm
1014
1015 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001016 @gclient_utils.lockedmethod
1017 def got_revision(self):
1018 return self._got_revision
1019
1020 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001021 def file_list_and_children(self):
1022 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001023 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001024 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001025 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001026
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001027 def __str__(self):
1028 out = []
agablea98a6cd2016-11-15 14:30:10 -08001029 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001030 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001031 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1032 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001033 # First try the native property if it exists.
1034 if hasattr(self, '_' + i):
1035 value = getattr(self, '_' + i, False)
1036 else:
1037 value = getattr(self, i, False)
1038 if value:
1039 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001040
1041 for d in self.dependencies:
1042 out.extend([' ' + x for x in str(d).splitlines()])
1043 out.append('')
1044 return '\n'.join(out)
1045
1046 def __repr__(self):
1047 return '%s: %s' % (self.name, self.url)
1048
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001049 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001050 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001051 def format_name(d):
1052 if include_url:
1053 return '%s(%s)' % (d.name, d.url)
1054 return d.name
1055 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001056 i = self.parent
1057 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001058 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001059 i = i.parent
1060 return out
1061
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001062
1063class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001064 """Object that represent a gclient checkout. A tree of Dependency(), one per
1065 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001066
1067 DEPS_OS_CHOICES = {
1068 "win32": "win",
1069 "win": "win",
1070 "cygwin": "win",
1071 "darwin": "mac",
1072 "mac": "mac",
1073 "unix": "unix",
1074 "linux": "unix",
1075 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001076 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001077 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001078 }
1079
1080 DEFAULT_CLIENT_FILE_TEXT = ("""\
1081solutions = [
smutae7ea312016-07-18 11:59:41 -07001082 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001083 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001084 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001085 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001086 "custom_deps" : {
1087 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001088 },
1089]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001090cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001091""")
1092
1093 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001094 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001095 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001096 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001097 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001098 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001099%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001100 },
1101""")
1102
1103 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1104# Snapshot generated with gclient revinfo --snapshot
1105solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001106%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001107""")
1108
1109 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001110 # Do not change previous behavior. Only solution level and immediate DEPS
1111 # are processed.
1112 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001113 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001114 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001115 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001116 if options.deps_os:
1117 enforced_os = options.deps_os.split(',')
1118 else:
1119 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1120 if 'all' in enforced_os:
1121 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001122 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001123 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001124 self.config_content = None
1125
borenet@google.com88d10082014-03-21 17:24:48 +00001126 def _CheckConfig(self):
1127 """Verify that the config matches the state of the existing checked-out
1128 solutions."""
1129 for dep in self.dependencies:
1130 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001131 scm = gclient_scm.CreateSCM(
1132 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001133 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001134 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001135 mirror = scm.GetCacheMirror()
1136 if mirror:
1137 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1138 mirror.exists())
1139 else:
1140 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001141 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001142Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001143is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001144
borenet@google.com97882362014-04-07 20:06:02 +00001145The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001146URL: %(expected_url)s (%(expected_scm)s)
1147Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001148
1149The local checkout in %(checkout_path)s reports:
1150%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001151
1152You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001153it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001154''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1155 'expected_url': dep.url,
1156 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001157 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001158 'actual_url': actual_url,
1159 'actual_scm': gclient_scm.GetScmName(actual_url)})
1160
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001162 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001163 config_dict = {}
1164 self.config_content = content
1165 try:
1166 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001167 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001168 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001169
peter@chromium.org1efccc82012-04-27 16:34:38 +00001170 # Append any target OS that is not already being enforced to the tuple.
1171 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001172 if config_dict.get('target_os_only', False):
1173 self._enforced_os = tuple(set(target_os))
1174 else:
1175 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1176
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001177 cache_dir = config_dict.get('cache_dir')
1178 if cache_dir:
1179 cache_dir = os.path.join(self.root_dir, cache_dir)
1180 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001181 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001182 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001183 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1184 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001185 gclient_scm.GitWrapper.cache_dir = cache_dir
1186 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001187
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001188 if not target_os and config_dict.get('target_os_only', False):
1189 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1190 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001191
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001192 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001193 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001194 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001195 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001196 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001197 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001198 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001199 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001200 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001201 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001202 True,
1203 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001204 except KeyError:
1205 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1206 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001207 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1208 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001209
1210 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001211 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001212 self._options.config_filename),
1213 self.config_content)
1214
1215 @staticmethod
1216 def LoadCurrentConfig(options):
1217 """Searches for and loads a .gclient file relative to the current working
1218 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001219 if options.spec:
1220 client = GClient('.', options)
1221 client.SetConfig(options.spec)
1222 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001223 if options.verbose:
1224 print('Looking for %s starting from %s\n' % (
1225 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001226 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1227 if not path:
1228 return None
1229 client = GClient(path, options)
1230 client.SetConfig(gclient_utils.FileRead(
1231 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001232
1233 if (options.revisions and
1234 len(client.dependencies) > 1 and
1235 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001236 print(
1237 ('You must specify the full solution name like --revision %s@%s\n'
1238 'when you have multiple solutions setup in your .gclient file.\n'
1239 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001240 client.dependencies[0].name,
1241 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001242 ', '.join(s.name for s in client.dependencies[1:])),
1243 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001244 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001245
nsylvain@google.comefc80932011-05-31 21:27:56 +00001246 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001247 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001248 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1249 'solution_name': solution_name,
1250 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001251 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001252 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001253 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001254 })
1255
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001256 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001257 """Creates a .gclient_entries file to record the list of unique checkouts.
1258
1259 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001260 """
1261 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1262 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001263 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001264 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001265 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1266 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001267 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001268 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001269 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001270 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001271
1272 def _ReadEntries(self):
1273 """Read the .gclient_entries file for the given client.
1274
1275 Returns:
1276 A sequence of solution names, which will be empty if there is the
1277 entries file hasn't been created yet.
1278 """
1279 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001280 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001281 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001282 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001283 try:
1284 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001285 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001286 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001287 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001288
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001289 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001290 """Checks for revision overrides."""
1291 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001292 if self._options.head:
1293 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001294 if not self._options.revisions:
1295 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001296 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001297 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001298 if not self._options.revisions:
1299 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001300 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001301 index = 0
1302 for revision in self._options.revisions:
1303 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001304 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001305 revision = '%s@%s' % (solutions_names[index], revision)
1306 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001307 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001308 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001309 return revision_overrides
1310
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001311 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001312 """Runs a command on each dependency in a client and its dependencies.
1313
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001314 Args:
1315 command: The command to use (e.g., 'status' or 'diff')
1316 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001317 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001318 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001319 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001320
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001321 revision_overrides = {}
1322 # It's unnecessary to check for revision overrides for 'recurse'.
1323 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001324 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1325 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001326 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001327 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001328 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001329 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001330 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001331 if command in ('update', 'revert'):
1332 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001333 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001334 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001335 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001336 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1337 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001338 for s in self.dependencies:
1339 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001340 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001341 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001342 print('Please fix your script, having invalid --revision flags will soon '
1343 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001344
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001345 # Once all the dependencies have been processed, it's now safe to run the
1346 # hooks.
1347 if not self._options.nohooks:
1348 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001349
1350 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001351 # Notify the user if there is an orphaned entry in their working copy.
1352 # Only delete the directory if there are no changes in it, and
1353 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001354 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001355 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1356 for e in entries]
1357
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001358 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001359 if not prev_url:
1360 # entry must have been overridden via .gclient custom_deps
1361 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001362 # Fix path separator on Windows.
1363 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001364 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001365 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001366 if (entry not in entries and
1367 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001368 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001369 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001370 scm = gclient_scm.CreateSCM(
1371 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001372
1373 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001374 scm_root = None
agabled437d762016-10-17 09:35:11 -07001375 try:
1376 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1377 except subprocess2.CalledProcessError:
1378 pass
1379 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001380 logging.warning('Could not find checkout root for %s. Unable to '
1381 'determine whether it is part of a higher-level '
1382 'checkout, so not removing.' % entry)
1383 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001384
1385 # This is to handle the case of third_party/WebKit migrating from
1386 # being a DEPS entry to being part of the main project.
1387 # If the subproject is a Git project, we need to remove its .git
1388 # folder. Otherwise git operations on that folder will have different
1389 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001390 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001391 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001392 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1393 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001394 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1395 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001396 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1397 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001398 save_dir = scm.GetGitBackupDirPath()
1399 # Remove any eventual stale backup dir for the same project.
1400 if os.path.exists(save_dir):
1401 gclient_utils.rmtree(save_dir)
1402 os.rename(os.path.join(e_dir, '.git'), save_dir)
1403 # When switching between the two states (entry/ is a subproject
1404 # -> entry/ is part of the outer project), it is very likely
1405 # that some files are changed in the checkout, unless we are
1406 # jumping *exactly* across the commit which changed just DEPS.
1407 # In such case we want to cleanup any eventual stale files
1408 # (coming from the old subproject) in order to end up with a
1409 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001410 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001411 assert not os.path.exists(os.path.join(e_dir, '.git'))
1412 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1413 'level checkout. The git folder containing all the local'
1414 ' branches has been saved to %s.\n'
1415 'If you don\'t care about its state you can safely '
1416 'remove that folder to free up space.') %
1417 (entry, save_dir))
1418 continue
1419
borenet@google.com359bb642014-05-13 17:28:19 +00001420 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001421 logging.info('%s is part of a higher level checkout, not removing',
1422 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001423 continue
1424
1425 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001426 scm.status(self._options, [], file_list)
1427 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001428 if (not self._options.delete_unversioned_trees or
1429 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001430 # There are modified files in this entry. Keep warning until
1431 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001432 print(('\nWARNING: \'%s\' is no longer part of this client. '
1433 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001434 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001435 else:
1436 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001437 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001438 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001439 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001440 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001441 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001442 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001443
1444 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001445 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001446 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001447 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001448 work_queue = gclient_utils.ExecutionQueue(
1449 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001450 for s in self.dependencies:
1451 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001452 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001453
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001454 def GetURLAndRev(dep):
1455 """Returns the revision-qualified SCM url for a Dependency."""
1456 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001457 return None
agabled437d762016-10-17 09:35:11 -07001458 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001459 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001460 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001461 if not os.path.isdir(scm.checkout_path):
1462 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001463 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001464
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001465 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001466 new_gclient = ''
1467 # First level at .gclient
1468 for d in self.dependencies:
1469 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001470 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001471 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001472 for d in dep.dependencies:
1473 entries[d.name] = GetURLAndRev(d)
1474 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001475 GrabDeps(d)
1476 custom_deps = []
1477 for k in sorted(entries.keys()):
1478 if entries[k]:
1479 # Quotes aren't escaped...
1480 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1481 else:
1482 custom_deps.append(' \"%s\": None,\n' % k)
1483 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1484 'solution_name': d.name,
1485 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001486 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001487 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001488 'solution_deps': ''.join(custom_deps),
1489 }
1490 # Print the snapshot configuration file
1491 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001492 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001493 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001494 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001495 if self._options.actual:
1496 entries[d.name] = GetURLAndRev(d)
1497 else:
1498 entries[d.name] = d.parsed_url
1499 keys = sorted(entries.keys())
1500 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001501 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001502 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001503
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001504 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001505 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001506 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001507
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001508 def PrintLocationAndContents(self):
1509 # Print out the .gclient file. This is longer than if we just printed the
1510 # client dict, but more legible, and it might contain helpful comments.
1511 print('Loaded .gclient config in %s:\n%s' % (
1512 self.root_dir, self.config_content))
1513
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001514 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001515 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001516 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001517 return self._root_dir
1518
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001519 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001520 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001521 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001522 return self._enforced_os
1523
maruel@chromium.org68988972011-09-20 14:11:42 +00001524 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001525 def recursion_limit(self):
1526 """How recursive can each dependencies in DEPS file can load DEPS file."""
1527 return self._recursion_limit
1528
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001529 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001530 def try_recursedeps(self):
1531 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001532 return True
1533
1534 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001535 def target_os(self):
1536 return self._enforced_os
1537
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001538
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001539#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001540
1541
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001542@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001543def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001544 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001545
1546 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001547 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001548 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001549 """
1550 # Stop parsing at the first non-arg so that these go through to the command
1551 parser.disable_interspersed_args()
1552 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001553 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001554 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001555 help='Ignore non-zero return codes from subcommands.')
1556 parser.add_option('--prepend-dir', action='store_true',
1557 help='Prepend relative dir for use with git <cmd> --null.')
1558 parser.add_option('--no-progress', action='store_true',
1559 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001560 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001561 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001562 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001563 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001564 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1565 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001566 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001567 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001568 'This is because .gclient_entries needs to exist and be up to date.',
1569 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001570 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001571
1572 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001573 scm_set = set()
1574 for scm in options.scm:
1575 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001576 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001577
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001578 options.nohooks = True
1579 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001580 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1581 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001582
1583
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001584@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001585def CMDfetch(parser, args):
1586 """Fetches upstream commits for all modules.
1587
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001588 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1589 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001590 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001591 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001592 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1593
1594
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001595def CMDflatten(parser, args):
1596 """Flattens the solutions into a single DEPS file."""
1597 parser.add_option('--output-deps', help='Path to the output DEPS file')
1598 parser.add_option(
1599 '--require-pinned-revisions', action='store_true',
1600 help='Fail if any of the dependencies uses unpinned revision.')
1601 options, args = parser.parse_args(args)
1602
1603 options.nohooks = True
1604 client = GClient.LoadCurrentConfig(options)
1605
1606 # Only print progress if we're writing to a file. Otherwise, progress updates
1607 # could obscure intended output.
1608 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1609 if code != 0:
1610 return code
1611
1612 deps = {}
1613 hooks = []
1614 pre_deps_hooks = []
1615 unpinned_deps = {}
1616
1617 for solution in client.dependencies:
1618 _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1619
1620 if options.require_pinned_revisions and unpinned_deps:
1621 sys.stderr.write('The following dependencies are not pinned:\n')
1622 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1623 return 1
1624
1625 flattened_deps = '\n'.join(
1626 _DepsToLines(deps) +
1627 _HooksToLines('hooks', hooks) +
1628 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1629 [''] # Ensure newline at end of file.
1630 )
1631
1632 if options.output_deps:
1633 with open(options.output_deps, 'w') as f:
1634 f.write(flattened_deps)
1635 else:
1636 print(flattened_deps)
1637
1638 return 0
1639
1640
1641def _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps):
1642 """Visits a solution in order to flatten it (see CMDflatten).
1643
1644 Arguments:
1645 solution (Dependency): one of top-level solutions in .gclient
1646
1647 Out-parameters:
1648 deps (dict of name -> Dependency): will be filled with all Dependency
1649 objects indexed by their name
1650 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1651 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1652 pre_deps_hooks
1653 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1654 deps
1655 """
1656 logging.debug('_FlattenSolution(%r)', solution)
1657
1658 _FlattenDep(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1659 _FlattenRecurse(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1660
1661
1662def _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1663 """Visits a dependency in order to flatten it (see CMDflatten).
1664
1665 Arguments:
1666 dep (Dependency): dependency to process
1667
1668 Out-parameters:
1669 deps (dict): will be filled with flattened deps
1670 hooks (list): will be filled with flattened hooks
1671 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1672 unpinned_deps (dict): will be filled with unpinned deps
1673 """
1674 logging.debug('_FlattenDep(%r)', dep)
1675
1676 _AddDep(dep, deps, unpinned_deps)
1677
1678 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1679 for recurse_dep_name in (dep.recursedeps or []):
1680 _FlattenRecurse(
1681 deps_by_name[recurse_dep_name], deps, hooks, pre_deps_hooks,
1682 unpinned_deps)
1683
1684 # TODO(phajdan.jr): also handle hooks_os.
1685 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1686 pre_deps_hooks.extend(
1687 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1688
1689
1690def _FlattenRecurse(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1691 """Helper for flatten that recurses into |dep|'s dependencies.
1692
1693 Arguments:
1694 dep (Dependency): dependency to process
1695
1696 Out-parameters:
1697 deps (dict): will be filled with flattened deps
1698 hooks (list): will be filled with flattened hooks
1699 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1700 unpinned_deps (dict): will be filled with unpinned deps
1701 """
1702 logging.debug('_FlattenRecurse(%r)', dep)
1703
1704 # TODO(phajdan.jr): also handle deps_os.
1705 for dep in dep.dependencies:
1706 _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps)
1707
1708
1709def _AddDep(dep, deps, unpinned_deps):
1710 """Helper to add a dependency to flattened lists.
1711
1712 Arguments:
1713 dep (Dependency): dependency to process
1714
1715 Out-parameters:
1716 deps (dict): will be filled with flattened deps
1717 unpinned_deps (dict): will be filled with unpinned deps
1718 """
1719 logging.debug('_AddDep(%r)', dep)
1720
1721 assert dep.name not in deps
1722 deps[dep.name] = dep
1723
1724 # Detect unpinned deps.
1725 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1726 if not revision or not gclient_utils.IsGitSha(revision):
1727 unpinned_deps[dep.name] = dep
1728
1729
1730def _DepsToLines(deps):
1731 """Converts |deps| dict to list of lines for output."""
1732 s = ['deps = {']
1733 for name, dep in sorted(deps.iteritems()):
1734 s.extend([
1735 ' # %s' % dep.hierarchy(include_url=False),
1736 ' "%s": "%s",' % (name, dep.url),
1737 '',
1738 ])
1739 s.extend(['}', ''])
1740 return s
1741
1742
1743def _HooksToLines(name, hooks):
1744 """Converts |hooks| list to list of lines for output."""
1745 s = ['%s = [' % name]
1746 for dep, hook in hooks:
1747 s.extend([
1748 ' # %s' % dep.hierarchy(include_url=False),
1749 ' {',
1750 ])
1751 if 'name' in hook:
1752 s.append(' "name": "%s",' % hook['name'])
1753 if 'pattern' in hook:
1754 s.append(' "pattern": "%s",' % hook['pattern'])
1755 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1756 # i.e. they may be relative to the dependency path, not solution root.
1757 s.extend(
1758 [' "action": ['] +
1759 [' "%s",' % arg for arg in hook['action']] +
1760 [' ]', ' },', '']
1761 )
1762 s.extend([']', ''])
1763 return s
1764
1765
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001766def CMDgrep(parser, args):
1767 """Greps through git repos managed by gclient.
1768
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001769 Runs 'git grep [args...]' for each module.
1770 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001771 # We can't use optparse because it will try to parse arguments sent
1772 # to git grep and throw an error. :-(
1773 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001774 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001775 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1776 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1777 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1778 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001779 ' end of your query.',
1780 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001781 return 1
1782
1783 jobs_arg = ['--jobs=1']
1784 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1785 jobs_arg, args = args[:1], args[1:]
1786 elif re.match(r'(-j|--jobs)$', args[0]):
1787 jobs_arg, args = args[:2], args[2:]
1788
1789 return CMDrecurse(
1790 parser,
1791 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1792 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001793
1794
stip@chromium.orga735da22015-04-29 23:18:20 +00001795def CMDroot(parser, args):
1796 """Outputs the solution root (or current dir if there isn't one)."""
1797 (options, args) = parser.parse_args(args)
1798 client = GClient.LoadCurrentConfig(options)
1799 if client:
1800 print(os.path.abspath(client.root_dir))
1801 else:
1802 print(os.path.abspath('.'))
1803
1804
agablea98a6cd2016-11-15 14:30:10 -08001805@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001806def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001807 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001808
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001809 This specifies the configuration for further commands. After update/sync,
1810 top-level DEPS files in each module are read to determine dependent
1811 modules to operate on as well. If optional [url] parameter is
1812 provided, then configuration is read from a specified Subversion server
1813 URL.
1814 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001815 # We do a little dance with the --gclientfile option. 'gclient config' is the
1816 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1817 # arguments. So, we temporarily stash any --gclientfile parameter into
1818 # options.output_config_file until after the (gclientfile xor spec) error
1819 # check.
1820 parser.remove_option('--gclientfile')
1821 parser.add_option('--gclientfile', dest='output_config_file',
1822 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001823 parser.add_option('--name',
1824 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001825 parser.add_option('--deps-file', default='DEPS',
1826 help='overrides the default name for the DEPS file for the'
1827 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001828 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001829 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001830 'to have the main solution untouched by gclient '
1831 '(gclient will check out unmanaged dependencies but '
1832 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001833 parser.add_option('--cache-dir',
1834 help='(git only) Cache all git repos into this dir and do '
1835 'shared clones from the cache, instead of cloning '
1836 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001837 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001838 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001839 if options.output_config_file:
1840 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001841 if ((options.spec and args) or len(args) > 2 or
1842 (not options.spec and not args)):
1843 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1844
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001845 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001846 if options.spec:
1847 client.SetConfig(options.spec)
1848 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001849 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001850 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001851 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001852 if name.endswith('.git'):
1853 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001854 else:
1855 # specify an alternate relpath for the given URL.
1856 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001857 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1858 os.getcwd()):
1859 parser.error('Do not pass a relative path for --name.')
1860 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1861 parser.error('Do not include relative path components in --name.')
1862
nsylvain@google.comefc80932011-05-31 21:27:56 +00001863 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001864 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001865 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001866 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001867 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001868 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001869
1870
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001871@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001872 gclient pack > patch.txt
1873 generate simple patch for configured client and dependences
1874""")
1875def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001876 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001877
agabled437d762016-10-17 09:35:11 -07001878 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001879 dependencies, and performs minimal postprocessing of the output. The
1880 resulting patch is printed to stdout and can be applied to a freshly
1881 checked out tree via 'patch -p0 < patchfile'.
1882 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001883 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1884 help='override deps for the specified (comma-separated) '
1885 'platform(s); \'all\' will process all deps_os '
1886 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001887 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001888 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001889 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001890 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001891 client = GClient.LoadCurrentConfig(options)
1892 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001893 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001894 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001895 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001896 return client.RunOnDeps('pack', args)
1897
1898
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001899def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001900 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001901 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1902 help='override deps for the specified (comma-separated) '
1903 'platform(s); \'all\' will process all deps_os '
1904 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001905 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001906 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001907 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001908 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001909 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001910 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001911 return client.RunOnDeps('status', args)
1912
1913
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001914@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001915 gclient sync
1916 update files from SCM according to current configuration,
1917 *for modules which have changed since last update or sync*
1918 gclient sync --force
1919 update files from SCM according to current configuration, for
1920 all modules (useful for recovering files deleted from local copy)
1921 gclient sync --revision src@31000
1922 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001923
1924JSON output format:
1925If the --output-json option is specified, the following document structure will
1926be emitted to the provided file. 'null' entries may occur for subprojects which
1927are present in the gclient solution, but were not processed (due to custom_deps,
1928os_deps, etc.)
1929
1930{
1931 "solutions" : {
1932 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001933 "revision": [<git id hex string>|null],
1934 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001935 }
1936 }
1937}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001938""")
1939def CMDsync(parser, args):
1940 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001941 parser.add_option('-f', '--force', action='store_true',
1942 help='force update even for unchanged modules')
1943 parser.add_option('-n', '--nohooks', action='store_true',
1944 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001945 parser.add_option('-p', '--noprehooks', action='store_true',
1946 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001947 parser.add_option('-r', '--revision', action='append',
1948 dest='revisions', metavar='REV', default=[],
1949 help='Enforces revision/hash for the solutions with the '
1950 'format src@rev. The src@ part is optional and can be '
1951 'skipped. -r can be used multiple times when .gclient '
1952 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001953 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001954 parser.add_option('--with_branch_heads', action='store_true',
1955 help='Clone git "branch_heads" refspecs in addition to '
1956 'the default refspecs. This adds about 1/2GB to a '
1957 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001958 parser.add_option('--with_tags', action='store_true',
1959 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001960 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08001961 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001962 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001963 help='Deletes from the working copy any dependencies that '
1964 'have been removed since the last sync, as long as '
1965 'there are no local modifications. When used with '
1966 '--force, such dependencies are removed even if they '
1967 'have local modifications. When used with --reset, '
1968 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001969 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001970 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001971 parser.add_option('-R', '--reset', action='store_true',
1972 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001973 parser.add_option('-M', '--merge', action='store_true',
1974 help='merge upstream changes instead of trying to '
1975 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001976 parser.add_option('-A', '--auto_rebase', action='store_true',
1977 help='Automatically rebase repositories against local '
1978 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001979 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1980 help='override deps for the specified (comma-separated) '
1981 'platform(s); \'all\' will process all deps_os '
1982 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001983 parser.add_option('--upstream', action='store_true',
1984 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001985 parser.add_option('--output-json',
1986 help='Output a json document to this path containing '
1987 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001988 parser.add_option('--no-history', action='store_true',
1989 help='GIT ONLY - Reduces the size/time of the checkout at '
1990 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001991 parser.add_option('--shallow', action='store_true',
1992 help='GIT ONLY - Do a shallow clone into the cache dir. '
1993 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001994 parser.add_option('--no_bootstrap', '--no-bootstrap',
1995 action='store_true',
1996 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001997 parser.add_option('--ignore_locks', action='store_true',
1998 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00001999 parser.add_option('--break_repo_locks', action='store_true',
2000 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2001 'index.lock). This should only be used if you know for '
2002 'certain that this invocation of gclient is the only '
2003 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002004 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002005 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002006 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002007 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2008 parser.add_option('-t', '--transitive', action='store_true',
2009 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002010 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002011 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002012 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002013 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002014 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002015 parser.add_option('--disable-syntax-validation', action='store_false',
2016 dest='validate_syntax',
2017 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002018 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002019 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002020
2021 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002022 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002023
smutae7ea312016-07-18 11:59:41 -07002024 if options.revisions and options.head:
2025 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2026 print('Warning: you cannot use both --head and --revision')
2027
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002028 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002029 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002030 ret = client.RunOnDeps('update', args)
2031 if options.output_json:
2032 slns = {}
2033 for d in client.subtree(True):
2034 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2035 slns[normed] = {
2036 'revision': d.got_revision,
2037 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002038 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002039 }
2040 with open(options.output_json, 'wb') as f:
2041 json.dump({'solutions': slns}, f)
2042 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002043
2044
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002045CMDupdate = CMDsync
2046
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002047
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002048def CMDvalidate(parser, args):
2049 """Validates the .gclient and DEPS syntax."""
2050 options, args = parser.parse_args(args)
2051 options.validate_syntax = True
2052 client = GClient.LoadCurrentConfig(options)
2053 rv = client.RunOnDeps('validate', args)
2054 if rv == 0:
2055 print('validate: SUCCESS')
2056 else:
2057 print('validate: FAILURE')
2058 return rv
2059
2060
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002061def CMDdiff(parser, args):
2062 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002063 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2064 help='override deps for the specified (comma-separated) '
2065 'platform(s); \'all\' will process all deps_os '
2066 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002067 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002068 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002069 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002070 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002071 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002072 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002073 return client.RunOnDeps('diff', args)
2074
2075
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002076def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002077 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002078
2079 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002080 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002081 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2082 help='override deps for the specified (comma-separated) '
2083 'platform(s); \'all\' will process all deps_os '
2084 'references')
2085 parser.add_option('-n', '--nohooks', action='store_true',
2086 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002087 parser.add_option('-p', '--noprehooks', action='store_true',
2088 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002089 parser.add_option('--upstream', action='store_true',
2090 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002091 parser.add_option('--break_repo_locks', action='store_true',
2092 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2093 'index.lock). This should only be used if you know for '
2094 'certain that this invocation of gclient is the only '
2095 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002096 (options, args) = parser.parse_args(args)
2097 # --force is implied.
2098 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002099 options.reset = False
2100 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002101 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002102 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002103 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002104 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002105 return client.RunOnDeps('revert', args)
2106
2107
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002108def CMDrunhooks(parser, args):
2109 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002110 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2111 help='override deps for the specified (comma-separated) '
2112 'platform(s); \'all\' will process all deps_os '
2113 'references')
2114 parser.add_option('-f', '--force', action='store_true', default=True,
2115 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002116 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002117 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002118 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002119 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002120 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002121 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002122 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002123 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002124 return client.RunOnDeps('runhooks', args)
2125
2126
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002127def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002128 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002129
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002130 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002131 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002132 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2133 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002134 """
2135 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2136 help='override deps for the specified (comma-separated) '
2137 'platform(s); \'all\' will process all deps_os '
2138 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002139 parser.add_option('-a', '--actual', action='store_true',
2140 help='gets the actual checked out revisions instead of the '
2141 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002142 parser.add_option('-s', '--snapshot', action='store_true',
2143 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002144 'version of all repositories to reproduce the tree, '
2145 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002146 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002147 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002148 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002149 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002150 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002151 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002152
2153
szager@google.comb9a78d32012-03-13 18:46:21 +00002154def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002155 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002156 (options, args) = parser.parse_args(args)
2157 options.force = True
2158 client = GClient.LoadCurrentConfig(options)
2159 if not client:
2160 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2161 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002162 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002163 return 0
2164
2165
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002166def CMDverify(parser, args):
2167 """Verifies the DEPS file deps are only from allowed_hosts."""
2168 (options, args) = parser.parse_args(args)
2169 client = GClient.LoadCurrentConfig(options)
2170 if not client:
2171 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2172 client.RunOnDeps(None, [])
2173 # Look at each first-level dependency of this gclient only.
2174 for dep in client.dependencies:
2175 bad_deps = dep.findDepsFromNotAllowedHosts()
2176 if not bad_deps:
2177 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002178 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002179 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002180 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2181 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002182 sys.stdout.flush()
2183 raise gclient_utils.Error(
2184 'dependencies from disallowed hosts; check your DEPS file.')
2185 return 0
2186
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002187class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002188 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002189
2190 def __init__(self, **kwargs):
2191 optparse.OptionParser.__init__(
2192 self, version='%prog ' + __version__, **kwargs)
2193
2194 # Some arm boards have issues with parallel sync.
2195 if platform.machine().startswith('arm'):
2196 jobs = 1
2197 else:
2198 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002199
2200 self.add_option(
2201 '-j', '--jobs', default=jobs, type='int',
2202 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002203 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002204 self.add_option(
2205 '-v', '--verbose', action='count', default=0,
2206 help='Produces additional output for diagnostics. Can be used up to '
2207 'three times for more logging info.')
2208 self.add_option(
2209 '--gclientfile', dest='config_filename',
2210 help='Specify an alternate %s file' % self.gclientfile_default)
2211 self.add_option(
2212 '--spec',
2213 help='create a gclient file containing the provided string. Due to '
2214 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2215 self.add_option(
2216 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002217 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002218
2219 def parse_args(self, args=None, values=None):
2220 """Integrates standard options processing."""
2221 options, args = optparse.OptionParser.parse_args(self, args, values)
2222 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2223 logging.basicConfig(
2224 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002225 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002226 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002227 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002228 if (options.config_filename and
2229 options.config_filename != os.path.basename(options.config_filename)):
2230 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002231 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002232 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002233 options.entries_filename = options.config_filename + '_entries'
2234 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002235 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002236
2237 # These hacks need to die.
2238 if not hasattr(options, 'revisions'):
2239 # GClient.RunOnDeps expects it even if not applicable.
2240 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002241 if not hasattr(options, 'head'):
2242 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002243 if not hasattr(options, 'nohooks'):
2244 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002245 if not hasattr(options, 'noprehooks'):
2246 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002247 if not hasattr(options, 'deps_os'):
2248 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002249 if not hasattr(options, 'force'):
2250 options.force = None
2251 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002252
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002253
2254def disable_buffering():
2255 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2256 # operations. Python as a strong tendency to buffer sys.stdout.
2257 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2258 # Make stdout annotated with the thread ids.
2259 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002260
2261
sbc@chromium.org013731e2015-02-26 18:28:43 +00002262def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002263 """Doesn't parse the arguments here, just find the right subcommand to
2264 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002265 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002266 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002267 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002268 sys.version.split(' ', 1)[0],
2269 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002270 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002271 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002272 print(
2273 '\nPython cannot find the location of it\'s own executable.\n',
2274 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002275 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002276 fix_encoding.fix_encoding()
2277 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002278 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002279 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002280 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002281 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002282 except KeyboardInterrupt:
2283 gclient_utils.GClientChildren.KillAllRemainingChildren()
2284 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002285 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002286 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002287 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002288 finally:
2289 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002290 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002291
2292
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002293if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002294 try:
2295 sys.exit(main(sys.argv[1:]))
2296 except KeyboardInterrupt:
2297 sys.stderr.write('interrupted\n')
2298 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002299
2300# vim: ts=2:sw=2:tw=80:et: