blob: 3569f8ebed154d6e4ddb54ac65393c7e5b6a6de8 [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
maruel@chromium.org39c0b222013-08-17 16:57:01 +00006"""Meta checkout manager supporting both Subversion and GIT."""
7# 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
67# processing the deps_os dict of a DEPS file.
68#
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
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000099import gclient_scm
100import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000101import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000102from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000103import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000104import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000105import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000106
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000107CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
108
109
110def ast_dict_index(dnode, key):
111 """Search an ast.Dict for the argument key, and return its index."""
112 idx = [i for i in range(len(dnode.keys)) if (
113 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
114 if not idx:
115 return -1
116 elif len(idx) > 1:
117 raise gclient_utils.Error('Multiple dict entries with same key in AST')
118 return idx[-1]
119
120def ast2str(node, indent=0):
121 """Return a pretty-printed rendition of an ast.Node."""
122 t = type(node)
123 if t is ast.Module:
124 return '\n'.join([ast2str(x, indent) for x in node.body])
125 elif t is ast.Assign:
126 return ((' ' * indent) +
127 ' = '.join([ast2str(x) for x in node.targets] +
128 [ast2str(node.value, indent)]) + '\n')
129 elif t is ast.Name:
130 return node.id
131 elif t is ast.List:
132 if not node.elts:
133 return '[]'
134 elif len(node.elts) == 1:
135 return '[' + ast2str(node.elts[0], indent) + ']'
136 return ('[\n' + (' ' * (indent + 1)) +
137 (',\n' + (' ' * (indent + 1))).join(
138 [ast2str(x, indent + 1) for x in node.elts]) +
139 '\n' + (' ' * indent) + ']')
140 elif t is ast.Dict:
141 if not node.keys:
142 return '{}'
143 elif len(node.keys) == 1:
144 return '{%s: %s}' % (ast2str(node.keys[0]),
145 ast2str(node.values[0], indent + 1))
146 return ('{\n' + (' ' * (indent + 1)) +
147 (',\n' + (' ' * (indent + 1))).join(
148 ['%s: %s' % (ast2str(node.keys[i]),
149 ast2str(node.values[i], indent + 1))
150 for i in range(len(node.keys))]) +
151 '\n' + (' ' * indent) + '}')
152 elif t is ast.Str:
153 return "'%s'" % node.s
154 else:
155 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
156 % (node.lineno, node.col_offset, t))
157
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000158
maruel@chromium.org116704f2010-06-11 17:34:38 +0000159class GClientKeywords(object):
160 class FromImpl(object):
161 """Used to implement the From() syntax."""
162
163 def __init__(self, module_name, sub_target_name=None):
164 """module_name is the dep module we want to include from. It can also be
165 the name of a subdirectory to include from.
166
167 sub_target_name is an optional parameter if the module name in the other
168 DEPS file is different. E.g., you might want to map src/net to net."""
169 self.module_name = module_name
170 self.sub_target_name = sub_target_name
171
172 def __str__(self):
173 return 'From(%s, %s)' % (repr(self.module_name),
174 repr(self.sub_target_name))
175
maruel@chromium.org116704f2010-06-11 17:34:38 +0000176 class FileImpl(object):
177 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000178 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000179
180 def __init__(self, file_location):
181 self.file_location = file_location
182
183 def __str__(self):
184 return 'File("%s")' % self.file_location
185
186 def GetPath(self):
187 return os.path.split(self.file_location)[0]
188
189 def GetFilename(self):
190 rev_tokens = self.file_location.split('@')
191 return os.path.split(rev_tokens[0])[1]
192
193 def GetRevision(self):
194 rev_tokens = self.file_location.split('@')
195 if len(rev_tokens) > 1:
196 return rev_tokens[1]
197 return None
198
199 class VarImpl(object):
200 def __init__(self, custom_vars, local_scope):
201 self._custom_vars = custom_vars
202 self._local_scope = local_scope
203
204 def Lookup(self, var_name):
205 """Implements the Var syntax."""
206 if var_name in self._custom_vars:
207 return self._custom_vars[var_name]
208 elif var_name in self._local_scope.get("vars", {}):
209 return self._local_scope["vars"][var_name]
210 raise gclient_utils.Error("Var is not defined: %s" % var_name)
211
212
maruel@chromium.org064186c2011-09-27 23:53:33 +0000213class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 """Immutable configuration settings."""
215 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000216 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000217 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000218 GClientKeywords.__init__(self)
219
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220 # These are not mutable:
221 self._parent = parent
222 self._safesync_url = safesync_url
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000223 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000224 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 # 'managed' determines whether or not this dependency is synced/updated by
226 # gclient after gclient checks it out initially. The difference between
227 # 'managed' and 'should_process' is that the user specifies 'managed' via
228 # the --unmanaged command-line flag or a .gclient config, where
229 # 'should_process' is dynamically set by gclient if it goes over its
230 # recursion limit and controls gclient's behavior so it does not misbehave.
231 self._managed = managed
232 self._should_process = should_process
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000233 # This is a mutable value which has the list of 'target_os' OSes listed in
234 # the current deps file.
235 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000236
237 # These are only set in .gclient and not in DEPS files.
238 self._custom_vars = custom_vars or {}
239 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000240 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000241
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000242 # TODO(iannucci): Remove this when all masters are correctly substituting
243 # the new blink url.
244 if (self._custom_vars.get('webkit_trunk', '') ==
245 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000246 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000247 print('Overwriting Var("webkit_trunk") with %s' % new_url)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000248 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000249
maruel@chromium.org064186c2011-09-27 23:53:33 +0000250 # Post process the url to remove trailing slashes.
251 if isinstance(self._url, basestring):
252 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
253 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000254 self._url = self._url.replace('/@', '@')
255 elif not isinstance(self._url,
256 (self.FromImpl, self.FileImpl, None.__class__)):
257 raise gclient_utils.Error(
258 ('dependency url must be either a string, None, '
259 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000260 # Make any deps_file path platform-appropriate.
261 for sep in ['/', '\\']:
262 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000263
264 @property
265 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000266 return self._deps_file
267
268 @property
269 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000270 return self._managed
271
272 @property
273 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000274 return self._parent
275
276 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000277 def root(self):
278 """Returns the root node, a GClient object."""
279 if not self.parent:
280 # This line is to signal pylint that it could be a GClient instance.
281 return self or GClient(None, None)
282 return self.parent.root
283
284 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000285 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000286 return self._safesync_url
287
288 @property
289 def should_process(self):
290 """True if this dependency should be processed, i.e. checked out."""
291 return self._should_process
292
293 @property
294 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000295 return self._custom_vars.copy()
296
297 @property
298 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000299 return self._custom_deps.copy()
300
maruel@chromium.org064186c2011-09-27 23:53:33 +0000301 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000302 def custom_hooks(self):
303 return self._custom_hooks[:]
304
305 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000306 def url(self):
307 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000308
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000309 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000310 def target_os(self):
311 if self.local_target_os is not None:
312 return tuple(set(self.local_target_os).union(self.parent.target_os))
313 else:
314 return self.parent.target_os
315
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000316 def get_custom_deps(self, name, url):
317 """Returns a custom deps if applicable."""
318 if self.parent:
319 url = self.parent.get_custom_deps(name, url)
320 # None is a valid return value to disable a dependency.
321 return self.custom_deps.get(name, url)
322
maruel@chromium.org064186c2011-09-27 23:53:33 +0000323
324class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000325 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000326
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000327 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000328 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000329 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000330 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000331 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000332 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000333
334 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000335 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000336
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000337 self._pre_deps_hooks = []
338
maruel@chromium.org68988972011-09-20 14:11:42 +0000339 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000340 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000341 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000342 # A cache of the files affected by the current operation, necessary for
343 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000344 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000345 # List of host names from which dependencies are allowed.
346 # Default is an empty set, meaning unspecified in DEPS file, and hence all
347 # hosts will be allowed. Non-empty set means whitelist of hosts.
348 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
349 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000350 # If it is not set to True, the dependency wasn't processed for its child
351 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000352 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000353 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000354 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000355 # This dependency had its pre-DEPS hooks run
356 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000357 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000358 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000359 # This is the scm used to checkout self.url. It may be used by dependencies
360 # to get the datetime of the revision we checked out.
361 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000362 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000363 # The actual revision we ended up getting, or None if that information is
364 # unavailable
365 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000366
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000367 # This is a mutable value that overrides the normal recursion limit for this
368 # dependency. It is read from the actual DEPS file so cannot be set on
369 # class instantiation.
370 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000371 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000372 # 'no recursion' setting on a dep-by-dep basis. It will replace
373 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000374 #
375 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
376 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000377 self.recursedeps = None
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000378
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000379 if not self.name and self.parent:
380 raise gclient_utils.Error('Dependency without name')
381
maruel@chromium.org470b5432011-10-11 18:18:19 +0000382 @property
383 def requirements(self):
384 """Calculate the list of requirements."""
385 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000386 # self.parent is implicitly a requirement. This will be recursive by
387 # definition.
388 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000389 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000390
391 # For a tree with at least 2 levels*, the leaf node needs to depend
392 # on the level higher up in an orderly way.
393 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
394 # thus unsorted, while the .gclient format is a list thus sorted.
395 #
396 # * _recursion_limit is hard coded 2 and there is no hope to change this
397 # value.
398 #
399 # Interestingly enough, the following condition only works in the case we
400 # want: self is a 2nd level node. 3nd level node wouldn't need this since
401 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000402 if self.parent and self.parent.parent and not self.parent.parent.parent:
403 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000404
405 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000406 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000407
maruel@chromium.org470b5432011-10-11 18:18:19 +0000408 if self.name:
409 requirements |= set(
410 obj.name for obj in self.root.subtree(False)
411 if (obj is not self
412 and obj.name and
413 self.name.startswith(posixpath.join(obj.name, ''))))
414 requirements = tuple(sorted(requirements))
415 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
416 return requirements
417
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000418 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000419 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000420 """Returns False if recursion_override is ever specified."""
421 if self.recursion_override is not None:
422 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000423 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000424
425 @property
426 def recursion_limit(self):
427 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000428 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000429 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000430 if self.try_recursedeps and self.parent.recursedeps != None:
431 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000432 return 1
433
434 if self.recursion_override is not None:
435 return self.recursion_override
436 return max(self.parent.recursion_limit - 1, 0)
437
maruel@chromium.org470b5432011-10-11 18:18:19 +0000438 def verify_validity(self):
439 """Verifies that this Dependency is fine to add as a child of another one.
440
441 Returns True if this entry should be added, False if it is a duplicate of
442 another entry.
443 """
444 logging.info('Dependency(%s).verify_validity()' % self.name)
445 if self.name in [s.name for s in self.parent.dependencies]:
446 raise gclient_utils.Error(
447 'The same name "%s" appears multiple times in the deps section' %
448 self.name)
449 if not self.should_process:
450 # Return early, no need to set requirements.
451 return True
452
453 # This require a full tree traversal with locks.
454 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
455 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000456 self_url = self.LateOverride(self.url)
457 sibling_url = sibling.LateOverride(sibling.url)
458 # Allow to have only one to be None or ''.
459 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000460 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000461 ('Dependency %s specified more than once:\n'
462 ' %s [%s]\n'
463 'vs\n'
464 ' %s [%s]') % (
465 self.name,
466 sibling.hierarchy(),
467 sibling_url,
468 self.hierarchy(),
469 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000470 # In theory we could keep it as a shadow of the other one. In
471 # practice, simply ignore it.
472 logging.warn('Won\'t process duplicate dependency %s' % sibling)
473 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000474 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000475
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000476 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000477 """Resolves the parsed url from url.
478
479 Manages From() keyword accordingly. Do not touch self.parsed_url nor
480 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000481 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000482 parsed_url = self.get_custom_deps(self.name, url)
483 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000484 logging.info(
485 'Dependency(%s).LateOverride(%s) -> %s' %
486 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000487 return parsed_url
488
489 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000490 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000491 ref = [
492 dep for dep in self.root.subtree(True) if url.module_name == dep.name
493 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000494 if not ref:
495 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
496 url.module_name, ref))
497 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000499 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000500 found_deps = [d for d in ref.dependencies if d.name == sub_target]
501 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000502 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000503 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
504 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000505 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000506
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000507 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000508 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000509 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000510 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000511 'Dependency(%s).LateOverride(%s) -> %s (From)' %
512 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000513 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000514
515 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000517 if (not parsed_url[0] and
518 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 # A relative url. Fetch the real base.
520 path = parsed_url[2]
521 if not path.startswith('/'):
522 raise gclient_utils.Error(
523 'relative DEPS entry \'%s\' must begin with a slash' % url)
524 # Create a scm just to query the full url.
525 parent_url = self.parent.parsed_url
526 if isinstance(parent_url, self.FileImpl):
527 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000528 scm = gclient_scm.CreateSCM(
529 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000530 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000531 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000532 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000533 logging.info(
534 'Dependency(%s).LateOverride(%s) -> %s' %
535 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000536 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000537
538 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000539 logging.info(
540 'Dependency(%s).LateOverride(%s) -> %s (File)' %
541 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000542 return url
543
544 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000545 logging.info(
546 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000547 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000548
549 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000550
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000551 @staticmethod
552 def MergeWithOsDeps(deps, deps_os, target_os_list):
553 """Returns a new "deps" structure that is the deps sent in updated
554 with information from deps_os (the deps_os section of the DEPS
555 file) that matches the list of target os."""
556 os_overrides = {}
557 for the_target_os in target_os_list:
558 the_target_os_deps = deps_os.get(the_target_os, {})
559 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
560 overrides = os_overrides.setdefault(os_dep_key, [])
561 overrides.append((the_target_os, os_dep_value))
562
563 # If any os didn't specify a value (we have fewer value entries
564 # than in the os list), then it wants to use the default value.
565 for os_dep_key, os_dep_value in os_overrides.iteritems():
566 if len(os_dep_value) != len(target_os_list):
567 # Record the default value too so that we don't accidently
568 # set it to None or miss a conflicting DEPS.
569 if os_dep_key in deps:
570 os_dep_value.append(('default', deps[os_dep_key]))
571
572 target_os_deps = {}
573 for os_dep_key, os_dep_value in os_overrides.iteritems():
574 # os_dep_value is a list of (os, value) pairs.
575 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
576 if not possible_values:
577 target_os_deps[os_dep_key] = None
578 else:
579 if len(possible_values) > 1:
580 # It would be possible to abort here but it would be
581 # unfortunate if we end up preventing any kind of checkout.
582 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
583 os_dep_key, os_dep_value, target_os_list)
584 # Sorting to get the same result every time in case of conflicts.
585 target_os_deps[os_dep_key] = sorted(possible_values)[0]
586
587 new_deps = deps.copy()
588 new_deps.update(target_os_deps)
589 return new_deps
590
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000591 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000592 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000593 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000594 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000595
596 deps_content = None
597 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000598
599 # First try to locate the configured deps file. If it's missing, fallback
600 # to DEPS.
601 deps_files = [self.deps_file]
602 if 'DEPS' not in deps_files:
603 deps_files.append('DEPS')
604 for deps_file in deps_files:
605 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
606 if os.path.isfile(filepath):
607 logging.info(
608 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
609 filepath)
610 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000611 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000612 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
613 filepath)
614
615 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000616 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000617 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000618 use_strict = 'use strict' in deps_content.splitlines()[0]
619
620 local_scope = {}
621 if deps_content:
622 # One thing is unintuitive, vars = {} must happen before Var() use.
623 var = self.VarImpl(self.custom_vars, local_scope)
624 if use_strict:
625 logging.info(
626 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
627 global_scope = {
628 '__builtins__': {'None': None},
629 'Var': var.Lookup,
630 'deps_os': {},
631 }
632 else:
633 global_scope = {
634 'File': self.FileImpl,
635 'From': self.FromImpl,
636 'Var': var.Lookup,
637 'deps_os': {},
638 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000639 # Eval the content.
640 try:
641 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000642 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000643 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000644 if use_strict:
645 for key, val in local_scope.iteritems():
646 if not isinstance(val, (dict, list, tuple, str)):
647 raise gclient_utils.Error(
648 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
649 (self.name, key, val))
650
maruel@chromium.org271375b2010-06-23 19:17:38 +0000651 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000652 if 'recursion' in local_scope:
653 self.recursion_override = local_scope.get('recursion')
654 logging.warning(
655 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000656 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000657 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000658 self.recursedeps = {}
659 for ent in local_scope['recursedeps']:
660 if isinstance(ent, basestring):
661 self.recursedeps[ent] = {"deps_file": self.deps_file}
662 else: # (depname, depsfilename)
663 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000664 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000665 # If present, save 'target_os' in the local_target_os property.
666 if 'target_os' in local_scope:
667 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 # load os specific dependencies if defined. these dependencies may
669 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000670 target_os_list = self.target_os
671 if 'deps_os' in local_scope and target_os_list:
672 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673
maruel@chromium.org271375b2010-06-23 19:17:38 +0000674 # If a line is in custom_deps, but not in the solution, we want to append
675 # this line to the solution.
676 for d in self.custom_deps:
677 if d not in deps:
678 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000679
680 # If use_relative_paths is set in the DEPS file, regenerate
681 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000682 # the DEPS file. Also update recursedeps if use_relative_paths is
683 # enabled.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000684 use_relative_paths = local_scope.get('use_relative_paths', False)
685 if use_relative_paths:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000686 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000687 rel_deps = {}
688 for d, url in deps.items():
689 # normpath is required to allow DEPS to use .. in their
690 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000691 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000692 logging.warning('Updating deps by prepending %s.', self.name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000693 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000695 # Update recursedeps if it's set.
696 if self.recursedeps is not None:
697 logging.warning('Updating recursedeps by prepending %s.', self.name)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000698 rel_deps = {}
699 for depname, options in self.recursedeps.iteritems():
700 rel_deps[os.path.normpath(os.path.join(self.name, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000701 self.recursedeps = rel_deps
702
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000703 if 'allowed_hosts' in local_scope:
704 try:
705 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
706 except TypeError: # raised if non-iterable
707 pass
708 if not self._allowed_hosts:
709 logging.warning("allowed_hosts is specified but empty %s",
710 self._allowed_hosts)
711 raise gclient_utils.Error(
712 'ParseDepsFile(%s): allowed_hosts must be absent '
713 'or a non-empty iterable' % self.name)
714
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000715 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000716 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000717 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000718 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000719 deps_file = self.deps_file
720 if self.recursedeps is not None:
721 ent = self.recursedeps.get(name)
722 if ent is not None:
723 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000724 deps_to_add.append(Dependency(
rwalker@codeaurora.orged89ff52016-03-31 19:18:40 +0000725 self, name, url, None, None, None, self.custom_vars, None,
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000726 deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000727 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000728
729 # override named sets of hooks by the custom hooks
730 hooks_to_run = []
731 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
732 for hook in local_scope.get('hooks', []):
733 if hook.get('name', '') not in hook_names_to_suppress:
734 hooks_to_run.append(hook)
735
736 # add the replacements and any additions
737 for hook in self.custom_hooks:
738 if 'action' in hook:
739 hooks_to_run.append(hook)
740
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000741 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
742 local_scope.get('pre_deps_hooks', [])]
743
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000744 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000745 logging.info('ParseDepsFile(%s) done' % self.name)
746
747 def add_dependencies_and_close(self, deps_to_add, hooks):
748 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000749 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000750 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000751 self.add_dependency(dep)
752 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000753
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000754 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000755 """Uses revision/timestamp of parent if no explicit revision was specified.
756
757 If we are performing an update and --transitive is set, use
758 - the parent's revision if 'self.url' is in the same repository
759 - the parent's timestamp otherwise
760 to update 'self.url'. The used revision/timestamp will be set in
761 'options.revision'.
762 If we have an explicit revision do nothing.
763 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000764 if command == 'update' and options.transitive and not options.revision:
765 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
766 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000767 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000768 if (options.revision and
769 not gclient_utils.IsDateRevision(options.revision)):
770 assert self.parent and self.parent.used_scm
771 # If this dependency is in the same repository as parent it's url will
772 # start with a slash. If so we take the parent revision instead of
773 # it's timestamp.
774 # (The timestamps of commits in google code are broken -- which can
775 # result in dependencies to be checked out at the wrong revision)
776 if self.url.startswith('/'):
777 if options.verbose:
778 print('Using parent\'s revision %s since we are in the same '
779 'repository.' % options.revision)
780 else:
781 parent_revision_date = self.parent.used_scm.GetRevisionDate(
782 options.revision)
783 options.revision = gclient_utils.MakeDateRevision(
784 parent_revision_date)
785 if options.verbose:
786 print('Using parent\'s revision date %s since we are in a '
787 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000788
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000789 def findDepsFromNotAllowedHosts(self):
790 """Returns a list of depenecies from not allowed hosts.
791
792 If allowed_hosts is not set, allows all hosts and returns empty list.
793 """
794 if not self._allowed_hosts:
795 return []
796 bad_deps = []
797 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000798 # Don't enforce this for custom_deps.
799 if dep.name in self._custom_deps:
800 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000801 if isinstance(dep.url, basestring):
802 parsed_url = urlparse.urlparse(dep.url)
803 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
804 bad_deps.append(dep)
805 return bad_deps
806
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000807 # Arguments number differs from overridden method
808 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000809 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000810 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000811 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000812 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000813 if not self.should_process:
814 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 # When running runhooks, there's no need to consult the SCM.
816 # All known hooks are expected to run unconditionally regardless of working
817 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000818 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000819 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000820 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000821 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000822 if run_scm and parsed_url:
823 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000824 # Special support for single-file checkout.
825 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000826 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
827 # pylint: disable=E1103
828 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000829 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000830 parsed_url.GetPath(), self.root.root_dir, self.name,
831 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000832 self._used_scm.RunCommand('updatesingle',
833 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000834 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000835 # Create a shallow copy to mutate revision.
836 options = copy.copy(options)
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000837 options.revision = revision_override
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000838 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000839 command, options, parsed_url, self.parent)
840 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000841 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000842 parsed_url, self.root.root_dir, self.name, self.outbuf,
843 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000844 self._got_revision = self._used_scm.RunCommand(command, options, args,
845 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000846 if file_list:
847 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000848
849 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
850 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000851 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000852 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000854 continue
855 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000856 [self.root.root_dir.lower(), file_list[i].lower()])
857 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000858 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000859 while file_list[i].startswith(('\\', '/')):
860 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000861
862 # Always parse the DEPS file.
863 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000864 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000865 if command in ('update', 'revert') and not options.noprehooks:
866 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000867
868 if self.recursion_limit:
869 # Parse the dependencies of this dependency.
870 for s in self.dependencies:
871 work_queue.enqueue(s)
872
873 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000874 if not isinstance(parsed_url, self.FileImpl):
875 # Skip file only checkout.
876 scm = gclient_scm.GetScmName(parsed_url)
877 if not options.scm or scm in options.scm:
878 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000879 # Pass in the SCM type as an env variable. Make sure we don't put
880 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000881 env = os.environ.copy()
882 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000883 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000884 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000885 env['GCLIENT_URL'] = str(parsed_url)
886 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000887 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000888 print_stdout = False
889 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000890 """Git-specific path marshaling. It is optimized for git-grep."""
891
892 def mod_path(git_pathspec):
893 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
894 modified_path = os.path.join(self.name, match.group(2))
895 branch = match.group(1) or ''
896 return '%s%s' % (branch, modified_path)
897
898 match = re.match('^Binary file ([^\0]+) matches$', line)
899 if match:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000900 print('Binary file %s matches\n' % mod_path(match.group(1)))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000901 return
902
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000903 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000904 if len(items) == 2 and items[1]:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000905 print('%s : %s' % (mod_path(items[0]), items[1]))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000906 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000907 # Multiple null bytes or a single trailing null byte indicate
908 # git is likely displaying filenames only (such as with -l)
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000909 print('\n'.join(mod_path(path) for path in items if path))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000910 else:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000911 print(line)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000912 else:
913 print_stdout = True
914 filter_fn = None
915
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000916 if parsed_url is None:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000917 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000918 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000919 try:
920 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000921 args, cwd=cwd, env=env, print_stdout=print_stdout,
922 filter_fn=filter_fn,
923 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000924 except subprocess2.CalledProcessError:
925 if not options.ignore:
926 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000927 else:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000928 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000929
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000931 @gclient_utils.lockedmethod
932 def _run_is_done(self, file_list, parsed_url):
933 # Both these are kept for hooks that are run as a separate tree traversal.
934 self._file_list = file_list
935 self._parsed_url = parsed_url
936 self._processed = True
937
szager@google.comb9a78d32012-03-13 18:46:21 +0000938 @staticmethod
939 def GetHookAction(hook_dict, matching_file_list):
940 """Turns a parsed 'hook' dict into an executable command."""
941 logging.debug(hook_dict)
942 logging.debug(matching_file_list)
943 command = hook_dict['action'][:]
944 if command[0] == 'python':
945 # If the hook specified "python" as the first item, the action is a
946 # Python script. Run it by starting a new copy of the same
947 # interpreter.
948 command[0] = sys.executable
949 if '$matching_files' in command:
950 splice_index = command.index('$matching_files')
951 command[splice_index:splice_index + 1] = matching_file_list
952 return command
953
954 def GetHooks(self, options):
955 """Evaluates all hooks, and return them in a flat list.
956
957 RunOnDeps() must have been called before to load the DEPS.
958 """
959 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000960 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000961 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000962 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000963 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000964 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000965 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000966 # TODO(maruel): If the user is using git or git-svn, then we don't know
967 # what files have changed so we always run all hooks. It'd be nice to fix
968 # that.
969 if (options.force or
970 isinstance(self.parsed_url, self.FileImpl) or
971 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000972 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000974 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000975 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000976 # Run hooks on the basis of whether the files from the gclient operation
977 # match each hook's pattern.
978 for hook_dict in self.deps_hooks:
979 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000980 matching_file_list = [
981 f for f in self.file_list_and_children if pattern.search(f)
982 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000984 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000985 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000986 result.extend(s.GetHooks(options))
987 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988
szager@google.comb9a78d32012-03-13 18:46:21 +0000989 def RunHooksRecursively(self, options):
990 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000991 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000992 for hook in self.GetHooks(options):
993 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000994 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000995 gclient_utils.CheckCallAndFilterAndHeader(
996 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000997 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000998 # Use a discrete exit status code of 2 to indicate that a hook action
999 # failed. Users of this script may wish to treat hook action failures
1000 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001001 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +00001002 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +00001003 finally:
1004 elapsed_time = time.time() - start_time
1005 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001006 print("Hook '%s' took %.2f secs" % (
1007 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001008
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001009 def RunPreDepsHooks(self):
1010 assert self.processed
1011 assert self.deps_parsed
1012 assert not self.pre_deps_hooks_ran
1013 assert not self.hooks_ran
1014 for s in self.dependencies:
1015 assert not s.processed
1016 self._pre_deps_hooks_ran = True
1017 for hook in self.pre_deps_hooks:
1018 try:
1019 start_time = time.time()
1020 gclient_utils.CheckCallAndFilterAndHeader(
1021 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001022 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001023 # Use a discrete exit status code of 2 to indicate that a hook action
1024 # failed. Users of this script may wish to treat hook action failures
1025 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001026 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001027 sys.exit(2)
1028 finally:
1029 elapsed_time = time.time() - start_time
1030 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001031 print("Hook '%s' took %.2f secs" % (
1032 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001033
1034
maruel@chromium.org0d812442010-08-10 12:41:08 +00001035 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001036 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001037 dependencies = self.dependencies
1038 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001039 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001040 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001041 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001042 for i in d.subtree(include_all):
1043 yield i
1044
1045 def depth_first_tree(self):
1046 """Depth-first recursion including the root node."""
1047 yield self
1048 for i in self.dependencies:
1049 for j in i.depth_first_tree():
1050 if j.should_process:
1051 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001052
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001053 @gclient_utils.lockedmethod
1054 def add_dependency(self, new_dep):
1055 self._dependencies.append(new_dep)
1056
1057 @gclient_utils.lockedmethod
1058 def _mark_as_parsed(self, new_hooks):
1059 self._deps_hooks.extend(new_hooks)
1060 self._deps_parsed = True
1061
maruel@chromium.org68988972011-09-20 14:11:42 +00001062 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001063 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001064 def dependencies(self):
1065 return tuple(self._dependencies)
1066
1067 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001069 def deps_hooks(self):
1070 return tuple(self._deps_hooks)
1071
1072 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001073 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001074 def pre_deps_hooks(self):
1075 return tuple(self._pre_deps_hooks)
1076
1077 @property
1078 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001079 def parsed_url(self):
1080 return self._parsed_url
1081
1082 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001083 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001084 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001085 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001086 return self._deps_parsed
1087
1088 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001089 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001090 def processed(self):
1091 return self._processed
1092
1093 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001094 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001095 def pre_deps_hooks_ran(self):
1096 return self._pre_deps_hooks_ran
1097
1098 @property
1099 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001100 def hooks_ran(self):
1101 return self._hooks_ran
1102
1103 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001104 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001105 def allowed_hosts(self):
1106 return self._allowed_hosts
1107
1108 @property
1109 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001110 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001111 return tuple(self._file_list)
1112
1113 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001114 def used_scm(self):
1115 """SCMWrapper instance for this dependency or None if not processed yet."""
1116 return self._used_scm
1117
1118 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001119 @gclient_utils.lockedmethod
1120 def got_revision(self):
1121 return self._got_revision
1122
1123 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001124 def file_list_and_children(self):
1125 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001126 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001127 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001128 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001129
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001130 def __str__(self):
1131 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001132 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001133 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001134 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1135 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001136 # First try the native property if it exists.
1137 if hasattr(self, '_' + i):
1138 value = getattr(self, '_' + i, False)
1139 else:
1140 value = getattr(self, i, False)
1141 if value:
1142 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001143
1144 for d in self.dependencies:
1145 out.extend([' ' + x for x in str(d).splitlines()])
1146 out.append('')
1147 return '\n'.join(out)
1148
1149 def __repr__(self):
1150 return '%s: %s' % (self.name, self.url)
1151
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001152 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001153 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001154 out = '%s(%s)' % (self.name, self.url)
1155 i = self.parent
1156 while i and i.name:
1157 out = '%s(%s) -> %s' % (i.name, i.url, out)
1158 i = i.parent
1159 return out
1160
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161
1162class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001163 """Object that represent a gclient checkout. A tree of Dependency(), one per
1164 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001165
1166 DEPS_OS_CHOICES = {
1167 "win32": "win",
1168 "win": "win",
1169 "cygwin": "win",
1170 "darwin": "mac",
1171 "mac": "mac",
1172 "unix": "unix",
1173 "linux": "unix",
1174 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001175 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001176 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001177 }
1178
1179 DEFAULT_CLIENT_FILE_TEXT = ("""\
1180solutions = [
1181 { "name" : "%(solution_name)s",
1182 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001183 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001184 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001185 "custom_deps" : {
1186 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001187 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001188 },
1189]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001190cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001191""")
1192
1193 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1194 { "name" : "%(solution_name)s",
1195 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001196 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001197 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001198 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001199%(solution_deps)s },
1200 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001201 },
1202""")
1203
1204 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1205# Snapshot generated with gclient revinfo --snapshot
1206solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001207%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001208""")
1209
1210 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001211 # Do not change previous behavior. Only solution level and immediate DEPS
1212 # are processed.
1213 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001214 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001215 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001216 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001217 if options.deps_os:
1218 enforced_os = options.deps_os.split(',')
1219 else:
1220 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1221 if 'all' in enforced_os:
1222 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001223 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001224 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001225 self.config_content = None
1226
borenet@google.com88d10082014-03-21 17:24:48 +00001227 def _CheckConfig(self):
1228 """Verify that the config matches the state of the existing checked-out
1229 solutions."""
1230 for dep in self.dependencies:
1231 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001232 scm = gclient_scm.CreateSCM(
1233 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001234 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001235 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001236 mirror = scm.GetCacheMirror()
1237 if mirror:
1238 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1239 mirror.exists())
1240 else:
1241 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001242 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001243Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001244is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001245
borenet@google.com97882362014-04-07 20:06:02 +00001246The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001247URL: %(expected_url)s (%(expected_scm)s)
1248Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001249
1250The local checkout in %(checkout_path)s reports:
1251%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001252
1253You should ensure that the URL listed in .gclient is correct and either change
1254it or fix the checkout. If you're managing your own git checkout in
1255%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1256want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001257''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1258 'expected_url': dep.url,
1259 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001260 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001261 'actual_url': actual_url,
1262 'actual_scm': gclient_scm.GetScmName(actual_url)})
1263
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001264 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001265 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001266 config_dict = {}
1267 self.config_content = content
1268 try:
1269 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001270 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001271 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001272
peter@chromium.org1efccc82012-04-27 16:34:38 +00001273 # Append any target OS that is not already being enforced to the tuple.
1274 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001275 if config_dict.get('target_os_only', False):
1276 self._enforced_os = tuple(set(target_os))
1277 else:
1278 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1279
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001280 cache_dir = config_dict.get('cache_dir')
1281 if cache_dir:
1282 cache_dir = os.path.join(self.root_dir, cache_dir)
1283 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001284 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001285 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001286 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1287 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001288 gclient_scm.GitWrapper.cache_dir = cache_dir
1289 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001290
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001291 if not target_os and config_dict.get('target_os_only', False):
1292 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1293 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001294
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001295 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001296 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001297 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001298 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001299 self, s['name'], s['url'],
1300 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001301 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001302 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001303 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001304 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001305 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001306 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001307 except KeyError:
1308 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1309 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001310 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1311 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001312
1313 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001314 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001315 self._options.config_filename),
1316 self.config_content)
1317
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001318 def MigrateConfigToGit(self, path, options):
1319 svn_url_re = re.compile('^(https?://src\.chromium\.org/svn|'
1320 'svn://svn\.chromium\.org/chrome)/'
1321 '(trunk|branches/[^/]+)/src')
1322 old_git_re = re.compile('^(https?://git\.chromium\.org|'
1323 'ssh://([a-zA-Z_][a-zA-Z0-9_-]*@)?'
1324 'gerrit\.chromium\.org(:2941[89])?)/'
1325 'chromium/src\.git')
1326 # Scan existing .gclient file for obsolete settings. It would be simpler
1327 # to traverse self.dependencies, but working with the AST allows the code to
1328 # dump an updated .gclient file that preserves the ordering of the original.
1329 a = ast.parse(self.config_content, options.config_filename, 'exec')
1330 modified = False
1331 solutions = [elem for elem in a.body if 'solutions' in
1332 [target.id for target in elem.targets]]
1333 if not solutions:
1334 return self
1335 solutions = solutions[-1]
1336 for solution in solutions.value.elts:
1337 # Check for obsolete URL's
1338 url_idx = ast_dict_index(solution, 'url')
1339 if url_idx == -1:
1340 continue
1341 url_val = solution.values[url_idx]
1342 if type(url_val) is not ast.Str:
1343 continue
1344 if (svn_url_re.match(url_val.s.strip())):
1345 raise gclient_utils.Error(
1346"""
1347The chromium code repository has migrated completely to git.
1348Your SVN-based checkout is now obsolete; you need to create a brand-new
1349git checkout by following these instructions:
1350
1351http://www.chromium.org/developers/how-tos/get-the-code
1352""")
1353 if (old_git_re.match(url_val.s.strip())):
1354 url_val.s = CHROMIUM_SRC_URL
1355 modified = True
1356
szager@chromium.org808bcfb2014-08-24 19:38:43 +00001357 # Ensure deps_file is set to .DEPS.git. We enforce this here to smooth
1358 # over switching between pre-git-migration and post-git-migration
1359 # revisions.
1360 # - For pre-migration revisions, .DEPS.git must be explicitly set.
1361 # - For post-migration revisions, .DEPS.git is not present, so gclient
1362 # will correctly fall back to DEPS.
1363 if url_val.s == CHROMIUM_SRC_URL:
1364 deps_file_idx = ast_dict_index(solution, 'deps_file')
1365 if deps_file_idx != -1:
1366 continue
1367 solution.keys.append(ast.Str('deps_file'))
1368 solution.values.append(ast.Str('.DEPS.git'))
1369 modified = True
1370
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001371 if not modified:
1372 return self
1373
1374 print(
1375"""
1376WARNING: gclient detected an obsolete setting in your %s file. The file has
1377been automagically updated. The previous version is available at %s.old.
1378""" % (options.config_filename, options.config_filename))
1379
1380 # Replace existing .gclient with the updated version.
1381 # Return a new GClient instance based on the new content.
1382 new_content = ast2str(a)
1383 dot_gclient_fn = os.path.join(path, options.config_filename)
iannucci@chromium.orgc7c41682014-08-23 03:44:18 +00001384 try:
1385 os.rename(dot_gclient_fn, dot_gclient_fn + '.old')
1386 except OSError:
1387 pass
1388 with open(dot_gclient_fn, 'w') as fh:
1389 fh.write(new_content)
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001390 client = GClient(path, options)
1391 client.SetConfig(new_content)
1392 return client
1393
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001394 @staticmethod
1395 def LoadCurrentConfig(options):
1396 """Searches for and loads a .gclient file relative to the current working
1397 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001398 if options.spec:
1399 client = GClient('.', options)
1400 client.SetConfig(options.spec)
1401 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001402 if options.verbose:
1403 print('Looking for %s starting from %s\n' % (
1404 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001405 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1406 if not path:
1407 return None
1408 client = GClient(path, options)
1409 client.SetConfig(gclient_utils.FileRead(
1410 os.path.join(path, options.config_filename)))
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001411 client = client.MigrateConfigToGit(path, options)
maruel@chromium.org69392e72011-10-13 22:09:00 +00001412
1413 if (options.revisions and
1414 len(client.dependencies) > 1 and
1415 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001416 print(
1417 ('You must specify the full solution name like --revision %s@%s\n'
1418 'when you have multiple solutions setup in your .gclient file.\n'
1419 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001420 client.dependencies[0].name,
1421 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001422 ', '.join(s.name for s in client.dependencies[1:])),
1423 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001424 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001425
nsylvain@google.comefc80932011-05-31 21:27:56 +00001426 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001427 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001428 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1429 'solution_name': solution_name,
1430 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001431 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001432 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001433 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001434 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001435 })
1436
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001437 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001438 """Creates a .gclient_entries file to record the list of unique checkouts.
1439
1440 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001441 """
1442 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1443 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001444 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001445 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001446 # Skip over File() dependencies as we can't version them.
1447 if not isinstance(entry.parsed_url, self.FileImpl):
1448 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1449 pprint.pformat(entry.parsed_url))
1450 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001451 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001452 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001453 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001454
1455 def _ReadEntries(self):
1456 """Read the .gclient_entries file for the given client.
1457
1458 Returns:
1459 A sequence of solution names, which will be empty if there is the
1460 entries file hasn't been created yet.
1461 """
1462 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001463 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001464 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001465 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001466 try:
1467 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001468 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001469 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001470 return scope['entries']
1471
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001472 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001473 """Checks for revision overrides."""
1474 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001475 if self._options.head:
1476 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001477 # Do not check safesync_url if one or more --revision flag is specified.
1478 if not self._options.revisions:
1479 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001480 if not s.managed:
1481 self._options.revisions.append('%s@unmanaged' % s.name)
1482 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001483 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001484 if not self._options.revisions:
1485 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001486 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001487 index = 0
1488 for revision in self._options.revisions:
1489 if not '@' in revision:
1490 # Support for --revision 123
1491 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001492 name, rev = revision.split('@', 1)
1493 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001494 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001495 return revision_overrides
1496
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001497 def _ApplySafeSyncRev(self, dep):
1498 """Finds a valid revision from the content of the safesync_url and apply it
1499 by appending revisions to the revision list. Throws if revision appears to
1500 be invalid for the given |dep|."""
1501 assert len(dep.safesync_url) > 0
1502 handle = urllib.urlopen(dep.safesync_url)
1503 rev = handle.read().strip()
1504 handle.close()
1505 if not rev:
1506 raise gclient_utils.Error(
1507 'It appears your safesync_url (%s) is not working properly\n'
1508 '(as it returned an empty response). Check your config.' %
1509 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001510 scm = gclient_scm.CreateSCM(
1511 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001512 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001513 if self._options.verbose:
1514 print('Using safesync_url revision: %s.\n' % safe_rev)
1515 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1516
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001517 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001518 """Runs a command on each dependency in a client and its dependencies.
1519
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001520 Args:
1521 command: The command to use (e.g., 'status' or 'diff')
1522 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001523 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001524 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001525 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001526
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001527 revision_overrides = {}
1528 # It's unnecessary to check for revision overrides for 'recurse'.
1529 # Save a few seconds by not calling _EnforceRevisions() in that case.
agable@chromium.org0242eb42015-06-09 00:45:31 +00001530 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001531 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001532 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001533 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001534 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001535 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001536 if command in ('update', 'revert'):
1537 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001538 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001539 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001540 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001541 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1542 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001543 for s in self.dependencies:
1544 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001545 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001546 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001547 print('Please fix your script, having invalid --revision flags will soon '
1548 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001549
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001550 # Once all the dependencies have been processed, it's now safe to run the
1551 # hooks.
1552 if not self._options.nohooks:
1553 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001554
1555 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001556 # Notify the user if there is an orphaned entry in their working copy.
1557 # Only delete the directory if there are no changes in it, and
1558 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001559 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001560 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1561 for e in entries]
1562
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001563 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001564 if not prev_url:
1565 # entry must have been overridden via .gclient custom_deps
1566 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001567 # Fix path separator on Windows.
1568 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001569 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001570 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001571 if (entry not in entries and
1572 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001573 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001574 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001575 scm = gclient_scm.CreateSCM(
1576 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001577
1578 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001579 # The directory might be part of a git OR svn checkout.
1580 scm_root = None
primiano@chromium.org1c127382015-02-17 11:15:40 +00001581 scm_class = None
borenet@google.com359bb642014-05-13 17:28:19 +00001582 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1583 try:
1584 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1585 except subprocess2.CalledProcessError:
1586 pass
1587 if scm_root:
1588 break
1589 else:
1590 logging.warning('Could not find checkout root for %s. Unable to '
1591 'determine whether it is part of a higher-level '
1592 'checkout, so not removing.' % entry)
1593 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001594
1595 # This is to handle the case of third_party/WebKit migrating from
1596 # being a DEPS entry to being part of the main project.
1597 # If the subproject is a Git project, we need to remove its .git
1598 # folder. Otherwise git operations on that folder will have different
1599 # effects depending on the current working directory.
1600 if scm_class == gclient_scm.scm.GIT and (
1601 os.path.abspath(scm_root) == os.path.abspath(e_dir)):
1602 e_par_dir = os.path.join(e_dir, os.pardir)
1603 if scm_class.IsInsideWorkTree(e_par_dir):
1604 par_scm_root = scm_class.GetCheckoutRoot(e_par_dir)
1605 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1606 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1607 if scm_class.IsDirectoryVersioned(par_scm_root, rel_e_dir):
1608 save_dir = scm.GetGitBackupDirPath()
1609 # Remove any eventual stale backup dir for the same project.
1610 if os.path.exists(save_dir):
1611 gclient_utils.rmtree(save_dir)
1612 os.rename(os.path.join(e_dir, '.git'), save_dir)
1613 # When switching between the two states (entry/ is a subproject
1614 # -> entry/ is part of the outer project), it is very likely
1615 # that some files are changed in the checkout, unless we are
1616 # jumping *exactly* across the commit which changed just DEPS.
1617 # In such case we want to cleanup any eventual stale files
1618 # (coming from the old subproject) in order to end up with a
1619 # clean checkout.
1620 scm_class.CleanupDir(par_scm_root, rel_e_dir)
1621 assert not os.path.exists(os.path.join(e_dir, '.git'))
1622 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1623 'level checkout. The git folder containing all the local'
1624 ' branches has been saved to %s.\n'
1625 'If you don\'t care about its state you can safely '
1626 'remove that folder to free up space.') %
1627 (entry, save_dir))
1628 continue
1629
borenet@google.com359bb642014-05-13 17:28:19 +00001630 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001631 logging.info('%s is part of a higher level checkout, not removing',
1632 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001633 continue
1634
1635 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001636 scm.status(self._options, [], file_list)
1637 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001638 if (not self._options.delete_unversioned_trees or
1639 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001640 # There are modified files in this entry. Keep warning until
1641 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001642 print(('\nWARNING: \'%s\' is no longer part of this client. '
1643 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001644 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001645 else:
1646 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001647 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001648 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001649 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001650 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001651 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001652 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001653
1654 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001655 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001656 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001657 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001658 work_queue = gclient_utils.ExecutionQueue(
1659 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001660 for s in self.dependencies:
1661 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001662 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001663
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001664 def GetURLAndRev(dep):
1665 """Returns the revision-qualified SCM url for a Dependency."""
1666 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001667 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001668 if isinstance(dep.parsed_url, self.FileImpl):
1669 original_url = dep.parsed_url.file_location
1670 else:
1671 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001672 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001673 scm = gclient_scm.CreateSCM(
1674 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001675 if not os.path.isdir(scm.checkout_path):
1676 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001677 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001678
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001679 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001680 new_gclient = ''
1681 # First level at .gclient
1682 for d in self.dependencies:
1683 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001684 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001685 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001686 for d in dep.dependencies:
1687 entries[d.name] = GetURLAndRev(d)
1688 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001689 GrabDeps(d)
1690 custom_deps = []
1691 for k in sorted(entries.keys()):
1692 if entries[k]:
1693 # Quotes aren't escaped...
1694 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1695 else:
1696 custom_deps.append(' \"%s\": None,\n' % k)
1697 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1698 'solution_name': d.name,
1699 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001700 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001701 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001702 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001703 'solution_deps': ''.join(custom_deps),
1704 }
1705 # Print the snapshot configuration file
1706 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001707 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001708 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001709 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001710 if self._options.actual:
1711 entries[d.name] = GetURLAndRev(d)
1712 else:
1713 entries[d.name] = d.parsed_url
1714 keys = sorted(entries.keys())
1715 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001716 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001717 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001718
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001719 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001720 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001721 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001722
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001723 def PrintLocationAndContents(self):
1724 # Print out the .gclient file. This is longer than if we just printed the
1725 # client dict, but more legible, and it might contain helpful comments.
1726 print('Loaded .gclient config in %s:\n%s' % (
1727 self.root_dir, self.config_content))
1728
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001729 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001730 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001731 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001732 return self._root_dir
1733
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001734 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001735 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001736 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001737 return self._enforced_os
1738
maruel@chromium.org68988972011-09-20 14:11:42 +00001739 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001740 def recursion_limit(self):
1741 """How recursive can each dependencies in DEPS file can load DEPS file."""
1742 return self._recursion_limit
1743
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001744 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001745 def try_recursedeps(self):
1746 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001747 return True
1748
1749 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001750 def target_os(self):
1751 return self._enforced_os
1752
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001753
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001754#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001755
1756
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001757def CMDcleanup(parser, args):
1758 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001759
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001760 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1761 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001762 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1763 help='override deps for the specified (comma-separated) '
1764 'platform(s); \'all\' will process all deps_os '
1765 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001766 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001767 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001768 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001769 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001770 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001771 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001772 return client.RunOnDeps('cleanup', args)
1773
1774
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001775@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001776def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001777 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001778
1779 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001780 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1781 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001782 """
1783 # Stop parsing at the first non-arg so that these go through to the command
1784 parser.disable_interspersed_args()
1785 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001786 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001787 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001788 help='Ignore non-zero return codes from subcommands.')
1789 parser.add_option('--prepend-dir', action='store_true',
1790 help='Prepend relative dir for use with git <cmd> --null.')
1791 parser.add_option('--no-progress', action='store_true',
1792 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001793 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001794 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001795 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001796 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001797 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1798 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001799 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001800 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001801 'This is because .gclient_entries needs to exist and be up to date.',
1802 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001803 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001804
1805 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001806 scm_set = set()
1807 for scm in options.scm:
1808 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001809 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001810
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001811 options.nohooks = True
1812 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001813 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1814 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001815
1816
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001817@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001818def CMDfetch(parser, args):
1819 """Fetches upstream commits for all modules.
1820
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001821 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1822 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001823 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001824 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001825 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1826
1827
1828def CMDgrep(parser, args):
1829 """Greps through git repos managed by gclient.
1830
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001831 Runs 'git grep [args...]' for each module.
1832 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001833 # We can't use optparse because it will try to parse arguments sent
1834 # to git grep and throw an error. :-(
1835 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001836 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001837 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1838 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1839 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1840 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001841 ' end of your query.',
1842 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001843 return 1
1844
1845 jobs_arg = ['--jobs=1']
1846 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1847 jobs_arg, args = args[:1], args[1:]
1848 elif re.match(r'(-j|--jobs)$', args[0]):
1849 jobs_arg, args = args[:2], args[2:]
1850
1851 return CMDrecurse(
1852 parser,
1853 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1854 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001855
1856
stip@chromium.orga735da22015-04-29 23:18:20 +00001857def CMDroot(parser, args):
1858 """Outputs the solution root (or current dir if there isn't one)."""
1859 (options, args) = parser.parse_args(args)
1860 client = GClient.LoadCurrentConfig(options)
1861 if client:
1862 print(os.path.abspath(client.root_dir))
1863 else:
1864 print(os.path.abspath('.'))
1865
1866
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001867@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001868def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001869 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001870
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001871 This specifies the configuration for further commands. After update/sync,
1872 top-level DEPS files in each module are read to determine dependent
1873 modules to operate on as well. If optional [url] parameter is
1874 provided, then configuration is read from a specified Subversion server
1875 URL.
1876 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001877 # We do a little dance with the --gclientfile option. 'gclient config' is the
1878 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1879 # arguments. So, we temporarily stash any --gclientfile parameter into
1880 # options.output_config_file until after the (gclientfile xor spec) error
1881 # check.
1882 parser.remove_option('--gclientfile')
1883 parser.add_option('--gclientfile', dest='output_config_file',
1884 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001885 parser.add_option('--name',
1886 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001887 parser.add_option('--deps-file', default='DEPS',
1888 help='overrides the default name for the DEPS file for the'
1889 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001890 parser.add_option('--unmanaged', action='store_true', default=False,
1891 help='overrides the default behavior to make it possible '
1892 'to have the main solution untouched by gclient '
1893 '(gclient will check out unmanaged dependencies but '
1894 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001895 parser.add_option('--cache-dir',
1896 help='(git only) Cache all git repos into this dir and do '
1897 'shared clones from the cache, instead of cloning '
1898 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001899 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001900 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001901 if options.output_config_file:
1902 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001903 if ((options.spec and args) or len(args) > 2 or
1904 (not options.spec and not args)):
1905 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1906
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001907 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001908 if options.spec:
1909 client.SetConfig(options.spec)
1910 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001911 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001912 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001913 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001914 if name.endswith('.git'):
1915 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001916 else:
1917 # specify an alternate relpath for the given URL.
1918 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001919 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1920 os.getcwd()):
1921 parser.error('Do not pass a relative path for --name.')
1922 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1923 parser.error('Do not include relative path components in --name.')
1924
nsylvain@google.comefc80932011-05-31 21:27:56 +00001925 deps_file = options.deps_file
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001926 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001927 if len(args) > 1:
1928 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001929 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001930 managed=not options.unmanaged,
1931 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001932 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001933 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001934
1935
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001936@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001937 gclient pack > patch.txt
1938 generate simple patch for configured client and dependences
1939""")
1940def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001941 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001942
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001943 Internally, runs 'svn diff'/'git diff' on each checked out module and
1944 dependencies, and performs minimal postprocessing of the output. The
1945 resulting patch is printed to stdout and can be applied to a freshly
1946 checked out tree via 'patch -p0 < patchfile'.
1947 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001948 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1949 help='override deps for the specified (comma-separated) '
1950 'platform(s); \'all\' will process all deps_os '
1951 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001952 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001953 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001954 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001955 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001956 client = GClient.LoadCurrentConfig(options)
1957 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001958 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001959 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001960 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001961 return client.RunOnDeps('pack', args)
1962
1963
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001964def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001965 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001966 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1967 help='override deps for the specified (comma-separated) '
1968 'platform(s); \'all\' will process all deps_os '
1969 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001970 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001971 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001972 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001973 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001974 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001975 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001976 return client.RunOnDeps('status', args)
1977
1978
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001979@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001980 gclient sync
1981 update files from SCM according to current configuration,
1982 *for modules which have changed since last update or sync*
1983 gclient sync --force
1984 update files from SCM according to current configuration, for
1985 all modules (useful for recovering files deleted from local copy)
1986 gclient sync --revision src@31000
1987 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001988
1989JSON output format:
1990If the --output-json option is specified, the following document structure will
1991be emitted to the provided file. 'null' entries may occur for subprojects which
1992are present in the gclient solution, but were not processed (due to custom_deps,
1993os_deps, etc.)
1994
1995{
1996 "solutions" : {
1997 "<name>": { # <name> is the posix-normalized path to the solution.
1998 "revision": [<svn rev int>|<git id hex string>|null],
1999 "scm": ["svn"|"git"|null],
2000 }
2001 }
2002}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002003""")
2004def CMDsync(parser, args):
2005 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002006 parser.add_option('-f', '--force', action='store_true',
2007 help='force update even for unchanged modules')
2008 parser.add_option('-n', '--nohooks', action='store_true',
2009 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002010 parser.add_option('-p', '--noprehooks', action='store_true',
2011 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002012 parser.add_option('-r', '--revision', action='append',
2013 dest='revisions', metavar='REV', default=[],
2014 help='Enforces revision/hash for the solutions with the '
2015 'format src@rev. The src@ part is optional and can be '
2016 'skipped. -r can be used multiple times when .gclient '
2017 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00002018 'if the src@ part is skipped. Note that specifying '
2019 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002020 parser.add_option('--with_branch_heads', action='store_true',
2021 help='Clone git "branch_heads" refspecs in addition to '
2022 'the default refspecs. This adds about 1/2GB to a '
2023 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002024 parser.add_option('--with_tags', action='store_true',
2025 help='Clone git tags in addition to the default refspecs.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00002026 parser.add_option('-t', '--transitive', action='store_true',
2027 help='When a revision is specified (in the DEPS file or '
2028 'with the command-line flag), transitively update '
2029 'the dependencies to the date of the given revision. '
2030 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002031 parser.add_option('-H', '--head', action='store_true',
2032 help='skips any safesync_urls specified in '
2033 'configured solutions and sync to head instead')
2034 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002035 help='Deletes from the working copy any dependencies that '
2036 'have been removed since the last sync, as long as '
2037 'there are no local modifications. When used with '
2038 '--force, such dependencies are removed even if they '
2039 'have local modifications. When used with --reset, '
2040 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002041 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002042 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002043 parser.add_option('-R', '--reset', action='store_true',
2044 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002045 parser.add_option('-M', '--merge', action='store_true',
2046 help='merge upstream changes instead of trying to '
2047 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002048 parser.add_option('-A', '--auto_rebase', action='store_true',
2049 help='Automatically rebase repositories against local '
2050 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002051 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2052 help='override deps for the specified (comma-separated) '
2053 'platform(s); \'all\' will process all deps_os '
2054 'references')
2055 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
2056 help='Skip svn up whenever possible by requesting '
2057 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002058 parser.add_option('--upstream', action='store_true',
2059 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002060 parser.add_option('--output-json',
2061 help='Output a json document to this path containing '
2062 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002063 parser.add_option('--no-history', action='store_true',
2064 help='GIT ONLY - Reduces the size/time of the checkout at '
2065 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002066 parser.add_option('--shallow', action='store_true',
2067 help='GIT ONLY - Do a shallow clone into the cache dir. '
2068 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002069 parser.add_option('--no_bootstrap', '--no-bootstrap',
2070 action='store_true',
2071 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002072 parser.add_option('--ignore_locks', action='store_true',
2073 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002074 parser.add_option('--break_repo_locks', action='store_true',
2075 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2076 'index.lock). This should only be used if you know for '
2077 'certain that this invocation of gclient is the only '
2078 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002079 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002080 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002081 'cache lock to become available. Default is %default.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002082 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002083 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002084
2085 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002086 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002087
maruel@chromium.org307d1792010-05-31 20:03:13 +00002088 if options.revisions and options.head:
2089 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002090 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002091
2092 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002093 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002094 ret = client.RunOnDeps('update', args)
2095 if options.output_json:
2096 slns = {}
2097 for d in client.subtree(True):
2098 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2099 slns[normed] = {
2100 'revision': d.got_revision,
2101 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002102 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002103 }
2104 with open(options.output_json, 'wb') as f:
2105 json.dump({'solutions': slns}, f)
2106 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002107
2108
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002109CMDupdate = CMDsync
2110
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002111
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002112def CMDdiff(parser, args):
2113 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002114 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2115 help='override deps for the specified (comma-separated) '
2116 'platform(s); \'all\' will process all deps_os '
2117 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002118 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002119 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002120 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002121 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002122 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002123 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002124 return client.RunOnDeps('diff', args)
2125
2126
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002127def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002128 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002129
2130 That's the nuclear option to get back to a 'clean' state. It removes anything
2131 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002132 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2133 help='override deps for the specified (comma-separated) '
2134 'platform(s); \'all\' will process all deps_os '
2135 'references')
2136 parser.add_option('-n', '--nohooks', action='store_true',
2137 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002138 parser.add_option('-p', '--noprehooks', action='store_true',
2139 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002140 parser.add_option('--upstream', action='store_true',
2141 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002142 parser.add_option('--break_repo_locks', action='store_true',
2143 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2144 'index.lock). This should only be used if you know for '
2145 'certain that this invocation of gclient is the only '
2146 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002147 (options, args) = parser.parse_args(args)
2148 # --force is implied.
2149 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002150 options.reset = False
2151 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002152 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002153 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002154 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002155 return client.RunOnDeps('revert', args)
2156
2157
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002158def CMDrunhooks(parser, args):
2159 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002160 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2161 help='override deps for the specified (comma-separated) '
2162 'platform(s); \'all\' will process all deps_os '
2163 'references')
2164 parser.add_option('-f', '--force', action='store_true', default=True,
2165 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002166 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002167 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002168 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002169 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002170 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002171 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002172 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002173 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002174 return client.RunOnDeps('runhooks', args)
2175
2176
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002177def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002178 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002179
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002180 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002181 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002182 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
2183 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002184 commit can change.
2185 """
2186 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2187 help='override deps for the specified (comma-separated) '
2188 'platform(s); \'all\' will process all deps_os '
2189 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002190 parser.add_option('-a', '--actual', action='store_true',
2191 help='gets the actual checked out revisions instead of the '
2192 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002193 parser.add_option('-s', '--snapshot', action='store_true',
2194 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002195 'version of all repositories to reproduce the tree, '
2196 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002197 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002198 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002199 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002200 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002201 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002202 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002203
2204
szager@google.comb9a78d32012-03-13 18:46:21 +00002205def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002206 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002207 (options, args) = parser.parse_args(args)
2208 options.force = True
2209 client = GClient.LoadCurrentConfig(options)
2210 if not client:
2211 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2212 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002213 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002214 return 0
2215
2216
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002217def CMDverify(parser, args):
2218 """Verifies the DEPS file deps are only from allowed_hosts."""
2219 (options, args) = parser.parse_args(args)
2220 client = GClient.LoadCurrentConfig(options)
2221 if not client:
2222 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2223 client.RunOnDeps(None, [])
2224 # Look at each first-level dependency of this gclient only.
2225 for dep in client.dependencies:
2226 bad_deps = dep.findDepsFromNotAllowedHosts()
2227 if not bad_deps:
2228 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002229 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002230 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002231 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2232 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002233 sys.stdout.flush()
2234 raise gclient_utils.Error(
2235 'dependencies from disallowed hosts; check your DEPS file.')
2236 return 0
2237
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002238class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002239 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002240
2241 def __init__(self, **kwargs):
2242 optparse.OptionParser.__init__(
2243 self, version='%prog ' + __version__, **kwargs)
2244
2245 # Some arm boards have issues with parallel sync.
2246 if platform.machine().startswith('arm'):
2247 jobs = 1
2248 else:
2249 jobs = max(8, gclient_utils.NumLocalCpus())
2250 # cmp: 2013/06/19
2251 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00002252 # Bypassed if a bot_update flag is detected.
2253 if (os.environ.get('CHROME_HEADLESS') == '1' and
2254 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00002255 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002256
2257 self.add_option(
2258 '-j', '--jobs', default=jobs, type='int',
2259 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002260 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002261 self.add_option(
2262 '-v', '--verbose', action='count', default=0,
2263 help='Produces additional output for diagnostics. Can be used up to '
2264 'three times for more logging info.')
2265 self.add_option(
2266 '--gclientfile', dest='config_filename',
2267 help='Specify an alternate %s file' % self.gclientfile_default)
2268 self.add_option(
2269 '--spec',
2270 help='create a gclient file containing the provided string. Due to '
2271 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2272 self.add_option(
2273 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002274 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002275
2276 def parse_args(self, args=None, values=None):
2277 """Integrates standard options processing."""
2278 options, args = optparse.OptionParser.parse_args(self, args, values)
2279 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2280 logging.basicConfig(
2281 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002282 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002283 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002284 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002285 if (options.config_filename and
2286 options.config_filename != os.path.basename(options.config_filename)):
2287 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002288 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002289 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002290 options.entries_filename = options.config_filename + '_entries'
2291 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002292 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002293
2294 # These hacks need to die.
2295 if not hasattr(options, 'revisions'):
2296 # GClient.RunOnDeps expects it even if not applicable.
2297 options.revisions = []
2298 if not hasattr(options, 'head'):
2299 options.head = None
2300 if not hasattr(options, 'nohooks'):
2301 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002302 if not hasattr(options, 'noprehooks'):
2303 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002304 if not hasattr(options, 'deps_os'):
2305 options.deps_os = None
2306 if not hasattr(options, 'manually_grab_svn_rev'):
2307 options.manually_grab_svn_rev = None
2308 if not hasattr(options, 'force'):
2309 options.force = None
2310 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002311
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002312
2313def disable_buffering():
2314 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2315 # operations. Python as a strong tendency to buffer sys.stdout.
2316 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2317 # Make stdout annotated with the thread ids.
2318 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002319
2320
sbc@chromium.org013731e2015-02-26 18:28:43 +00002321def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002322 """Doesn't parse the arguments here, just find the right subcommand to
2323 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002324 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002325 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002326 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002327 sys.version.split(' ', 1)[0],
2328 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002329 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002330 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002331 print(
2332 '\nPython cannot find the location of it\'s own executable.\n',
2333 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002334 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002335 fix_encoding.fix_encoding()
2336 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002337 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002338 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002339 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002340 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002341 except KeyboardInterrupt:
2342 gclient_utils.GClientChildren.KillAllRemainingChildren()
2343 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002344 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002345 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002346 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002347 finally:
2348 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002349 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002350
2351
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002352if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002353 try:
2354 sys.exit(main(sys.argv[1:]))
2355 except KeyboardInterrupt:
2356 sys.stderr.write('interrupted\n')
2357 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002358
2359# vim: ts=2:sw=2:tw=80:et: