blob: 3007487a5930d0f09aeac1260defe3be12bd4360 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +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
maruel@chromium.org39c0b222013-08-17 16:57:01 +000079__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
szager@chromium.org7b8b6de2014-08-23 00:57:31 +000081import ast
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000082import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000083import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000084import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085import optparse
86import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000087import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000088import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000089import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000090import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000091import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000092import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000094import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000095
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000096import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +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
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +0000105from third_party import colorama
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'
247 print 'Overwriting Var("webkit_trunk") with %s' % new_url
248 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 = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000345 # If it is not set to True, the dependency wasn't processed for its child
346 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000347 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000348 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000349 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000350 # This dependency had its pre-DEPS hooks run
351 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000352 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000353 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000354 # This is the scm used to checkout self.url. It may be used by dependencies
355 # to get the datetime of the revision we checked out.
356 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000357 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000358 # The actual revision we ended up getting, or None if that information is
359 # unavailable
360 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000361
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000362 # This is a mutable value that overrides the normal recursion limit for this
363 # dependency. It is read from the actual DEPS file so cannot be set on
364 # class instantiation.
365 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000366 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000367 # 'no recursion' setting on a dep-by-dep basis. It will replace
368 # recursion_override.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000369 self.recursedeps = None
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000370
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000371 if not self.name and self.parent:
372 raise gclient_utils.Error('Dependency without name')
373
maruel@chromium.org470b5432011-10-11 18:18:19 +0000374 @property
375 def requirements(self):
376 """Calculate the list of requirements."""
377 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000378 # self.parent is implicitly a requirement. This will be recursive by
379 # definition.
380 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000381 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000382
383 # For a tree with at least 2 levels*, the leaf node needs to depend
384 # on the level higher up in an orderly way.
385 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
386 # thus unsorted, while the .gclient format is a list thus sorted.
387 #
388 # * _recursion_limit is hard coded 2 and there is no hope to change this
389 # value.
390 #
391 # Interestingly enough, the following condition only works in the case we
392 # want: self is a 2nd level node. 3nd level node wouldn't need this since
393 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000394 if self.parent and self.parent.parent and not self.parent.parent.parent:
395 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000396
397 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000398 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000399
maruel@chromium.org470b5432011-10-11 18:18:19 +0000400 if self.name:
401 requirements |= set(
402 obj.name for obj in self.root.subtree(False)
403 if (obj is not self
404 and obj.name and
405 self.name.startswith(posixpath.join(obj.name, ''))))
406 requirements = tuple(sorted(requirements))
407 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
408 return requirements
409
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000410 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000411 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000412 """Returns False if recursion_override is ever specified."""
413 if self.recursion_override is not None:
414 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000415 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000416
417 @property
418 def recursion_limit(self):
419 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000420 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000421 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000422 if self.try_recursedeps and self.parent.recursedeps != None:
423 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000424 return 1
425
426 if self.recursion_override is not None:
427 return self.recursion_override
428 return max(self.parent.recursion_limit - 1, 0)
429
maruel@chromium.org470b5432011-10-11 18:18:19 +0000430 def verify_validity(self):
431 """Verifies that this Dependency is fine to add as a child of another one.
432
433 Returns True if this entry should be added, False if it is a duplicate of
434 another entry.
435 """
436 logging.info('Dependency(%s).verify_validity()' % self.name)
437 if self.name in [s.name for s in self.parent.dependencies]:
438 raise gclient_utils.Error(
439 'The same name "%s" appears multiple times in the deps section' %
440 self.name)
441 if not self.should_process:
442 # Return early, no need to set requirements.
443 return True
444
445 # This require a full tree traversal with locks.
446 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
447 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000448 self_url = self.LateOverride(self.url)
449 sibling_url = sibling.LateOverride(sibling.url)
450 # Allow to have only one to be None or ''.
451 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000452 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000453 ('Dependency %s specified more than once:\n'
454 ' %s [%s]\n'
455 'vs\n'
456 ' %s [%s]') % (
457 self.name,
458 sibling.hierarchy(),
459 sibling_url,
460 self.hierarchy(),
461 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000462 # In theory we could keep it as a shadow of the other one. In
463 # practice, simply ignore it.
464 logging.warn('Won\'t process duplicate dependency %s' % sibling)
465 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000466 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000467
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000468 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000469 """Resolves the parsed url from url.
470
471 Manages From() keyword accordingly. Do not touch self.parsed_url nor
472 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000473 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000474 parsed_url = self.get_custom_deps(self.name, url)
475 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000476 logging.info(
477 'Dependency(%s).LateOverride(%s) -> %s' %
478 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000479 return parsed_url
480
481 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000482 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000483 ref = [
484 dep for dep in self.root.subtree(True) if url.module_name == dep.name
485 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000486 if not ref:
487 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
488 url.module_name, ref))
489 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000490 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000491 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000492 found_deps = [d for d in ref.dependencies if d.name == sub_target]
493 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000494 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000495 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
496 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000497 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000498
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000499 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000500 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000501 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000502 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000503 'Dependency(%s).LateOverride(%s) -> %s (From)' %
504 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000505 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000506
507 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000509 if (not parsed_url[0] and
510 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000511 # A relative url. Fetch the real base.
512 path = parsed_url[2]
513 if not path.startswith('/'):
514 raise gclient_utils.Error(
515 'relative DEPS entry \'%s\' must begin with a slash' % url)
516 # Create a scm just to query the full url.
517 parent_url = self.parent.parsed_url
518 if isinstance(parent_url, self.FileImpl):
519 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000520 scm = gclient_scm.CreateSCM(
521 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000522 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000523 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000524 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000525 logging.info(
526 'Dependency(%s).LateOverride(%s) -> %s' %
527 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000528 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000529
530 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000531 logging.info(
532 'Dependency(%s).LateOverride(%s) -> %s (File)' %
533 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000534 return url
535
536 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000537 logging.info(
538 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000539 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000540
541 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000542
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000543 @staticmethod
544 def MergeWithOsDeps(deps, deps_os, target_os_list):
545 """Returns a new "deps" structure that is the deps sent in updated
546 with information from deps_os (the deps_os section of the DEPS
547 file) that matches the list of target os."""
548 os_overrides = {}
549 for the_target_os in target_os_list:
550 the_target_os_deps = deps_os.get(the_target_os, {})
551 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
552 overrides = os_overrides.setdefault(os_dep_key, [])
553 overrides.append((the_target_os, os_dep_value))
554
555 # If any os didn't specify a value (we have fewer value entries
556 # than in the os list), then it wants to use the default value.
557 for os_dep_key, os_dep_value in os_overrides.iteritems():
558 if len(os_dep_value) != len(target_os_list):
559 # Record the default value too so that we don't accidently
560 # set it to None or miss a conflicting DEPS.
561 if os_dep_key in deps:
562 os_dep_value.append(('default', deps[os_dep_key]))
563
564 target_os_deps = {}
565 for os_dep_key, os_dep_value in os_overrides.iteritems():
566 # os_dep_value is a list of (os, value) pairs.
567 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
568 if not possible_values:
569 target_os_deps[os_dep_key] = None
570 else:
571 if len(possible_values) > 1:
572 # It would be possible to abort here but it would be
573 # unfortunate if we end up preventing any kind of checkout.
574 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
575 os_dep_key, os_dep_value, target_os_list)
576 # Sorting to get the same result every time in case of conflicts.
577 target_os_deps[os_dep_key] = sorted(possible_values)[0]
578
579 new_deps = deps.copy()
580 new_deps.update(target_os_deps)
581 return new_deps
582
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000583 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000584 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000585 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000586 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000587
588 deps_content = None
589 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000590
591 # First try to locate the configured deps file. If it's missing, fallback
592 # to DEPS.
593 deps_files = [self.deps_file]
594 if 'DEPS' not in deps_files:
595 deps_files.append('DEPS')
596 for deps_file in deps_files:
597 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
598 if os.path.isfile(filepath):
599 logging.info(
600 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
601 filepath)
602 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000603 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000604 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
605 filepath)
606
607 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000608 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000609 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000610 use_strict = 'use strict' in deps_content.splitlines()[0]
611
612 local_scope = {}
613 if deps_content:
614 # One thing is unintuitive, vars = {} must happen before Var() use.
615 var = self.VarImpl(self.custom_vars, local_scope)
616 if use_strict:
617 logging.info(
618 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
619 global_scope = {
620 '__builtins__': {'None': None},
621 'Var': var.Lookup,
622 'deps_os': {},
623 }
624 else:
625 global_scope = {
626 'File': self.FileImpl,
627 'From': self.FromImpl,
628 'Var': var.Lookup,
629 'deps_os': {},
630 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000631 # Eval the content.
632 try:
633 exec(deps_content, global_scope, local_scope)
634 except SyntaxError, e:
635 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000636 if use_strict:
637 for key, val in local_scope.iteritems():
638 if not isinstance(val, (dict, list, tuple, str)):
639 raise gclient_utils.Error(
640 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
641 (self.name, key, val))
642
maruel@chromium.org271375b2010-06-23 19:17:38 +0000643 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000644 if 'recursion' in local_scope:
645 self.recursion_override = local_scope.get('recursion')
646 logging.warning(
647 'Setting %s recursion to %d.', self.name, self.recursion_limit)
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000648 self.recursedeps = local_scope.get('recursedeps', None)
649 if 'recursedeps' in local_scope:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000650 self.recursedeps = set(self.recursedeps)
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000651 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000652 # If present, save 'target_os' in the local_target_os property.
653 if 'target_os' in local_scope:
654 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000655 # load os specific dependencies if defined. these dependencies may
656 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000657 target_os_list = self.target_os
658 if 'deps_os' in local_scope and target_os_list:
659 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000660
maruel@chromium.org271375b2010-06-23 19:17:38 +0000661 # If a line is in custom_deps, but not in the solution, we want to append
662 # this line to the solution.
663 for d in self.custom_deps:
664 if d not in deps:
665 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000666
667 # If use_relative_paths is set in the DEPS file, regenerate
668 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000669 # the DEPS file. Also update recursedeps if use_relative_paths is
670 # enabled.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000671 use_relative_paths = local_scope.get('use_relative_paths', False)
672 if use_relative_paths:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000673 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000674 rel_deps = {}
675 for d, url in deps.items():
676 # normpath is required to allow DEPS to use .. in their
677 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000678 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000679 logging.warning('Updating deps by prepending %s.', self.name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000680 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000681
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000682 # Update recursedeps if it's set.
683 if self.recursedeps is not None:
684 logging.warning('Updating recursedeps by prepending %s.', self.name)
685 rel_deps = set()
686 for d in self.recursedeps:
687 rel_deps.add(os.path.normpath(os.path.join(self.name, d)))
688 self.recursedeps = rel_deps
689
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000690 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000691 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000692 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000693 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000694 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000695 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000696 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000697 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000698
699 # override named sets of hooks by the custom hooks
700 hooks_to_run = []
701 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
702 for hook in local_scope.get('hooks', []):
703 if hook.get('name', '') not in hook_names_to_suppress:
704 hooks_to_run.append(hook)
705
706 # add the replacements and any additions
707 for hook in self.custom_hooks:
708 if 'action' in hook:
709 hooks_to_run.append(hook)
710
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000711 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
712 local_scope.get('pre_deps_hooks', [])]
713
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000714 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000715 logging.info('ParseDepsFile(%s) done' % self.name)
716
717 def add_dependencies_and_close(self, deps_to_add, hooks):
718 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000719 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000720 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000721 self.add_dependency(dep)
722 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000723
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000724 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000725 """Uses revision/timestamp of parent if no explicit revision was specified.
726
727 If we are performing an update and --transitive is set, use
728 - the parent's revision if 'self.url' is in the same repository
729 - the parent's timestamp otherwise
730 to update 'self.url'. The used revision/timestamp will be set in
731 'options.revision'.
732 If we have an explicit revision do nothing.
733 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000734 if command == 'update' and options.transitive and not options.revision:
735 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
736 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000737 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000738 if (options.revision and
739 not gclient_utils.IsDateRevision(options.revision)):
740 assert self.parent and self.parent.used_scm
741 # If this dependency is in the same repository as parent it's url will
742 # start with a slash. If so we take the parent revision instead of
743 # it's timestamp.
744 # (The timestamps of commits in google code are broken -- which can
745 # result in dependencies to be checked out at the wrong revision)
746 if self.url.startswith('/'):
747 if options.verbose:
748 print('Using parent\'s revision %s since we are in the same '
749 'repository.' % options.revision)
750 else:
751 parent_revision_date = self.parent.used_scm.GetRevisionDate(
752 options.revision)
753 options.revision = gclient_utils.MakeDateRevision(
754 parent_revision_date)
755 if options.verbose:
756 print('Using parent\'s revision date %s since we are in a '
757 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000758
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000759 # Arguments number differs from overridden method
760 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000761 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000762 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000763 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000764 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000765 if not self.should_process:
766 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000767 # When running runhooks, there's no need to consult the SCM.
768 # All known hooks are expected to run unconditionally regardless of working
769 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000770 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000771 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000772 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000773 if run_scm and parsed_url:
774 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000775 # Special support for single-file checkout.
776 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000777 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
778 # pylint: disable=E1103
779 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000780 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000781 parsed_url.GetPath(), self.root.root_dir, self.name,
782 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000783 self._used_scm.RunCommand('updatesingle',
784 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000785 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000786 # Create a shallow copy to mutate revision.
787 options = copy.copy(options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000788 options.revision = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000789 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000790 command, options, parsed_url, self.parent)
791 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000792 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000793 parsed_url, self.root.root_dir, self.name, self.outbuf,
794 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000795 self._got_revision = self._used_scm.RunCommand(command, options, args,
796 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000797 if file_list:
798 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000799
800 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
801 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000802 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000803 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000804 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000805 continue
806 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000807 [self.root.root_dir.lower(), file_list[i].lower()])
808 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000809 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000810 while file_list[i].startswith(('\\', '/')):
811 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000812
813 # Always parse the DEPS file.
814 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000815 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000816 if command in ('update', 'revert') and not options.noprehooks:
817 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000818
819 if self.recursion_limit:
820 # Parse the dependencies of this dependency.
821 for s in self.dependencies:
822 work_queue.enqueue(s)
823
824 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000825 if not isinstance(parsed_url, self.FileImpl):
826 # Skip file only checkout.
827 scm = gclient_scm.GetScmName(parsed_url)
828 if not options.scm or scm in options.scm:
829 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000830 # Pass in the SCM type as an env variable. Make sure we don't put
831 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000832 env = os.environ.copy()
833 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000834 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000835 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000836 env['GCLIENT_URL'] = str(parsed_url)
837 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000838 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000839 print_stdout = False
840 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000841 """Git-specific path marshaling. It is optimized for git-grep."""
842
843 def mod_path(git_pathspec):
844 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
845 modified_path = os.path.join(self.name, match.group(2))
846 branch = match.group(1) or ''
847 return '%s%s' % (branch, modified_path)
848
849 match = re.match('^Binary file ([^\0]+) matches$', line)
850 if match:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000851 print 'Binary file %s matches\n' % mod_path(match.group(1))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000852 return
853
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000854 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000855 if len(items) == 2 and items[1]:
856 print '%s : %s' % (mod_path(items[0]), items[1])
857 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000858 # Multiple null bytes or a single trailing null byte indicate
859 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000860 print '\n'.join(mod_path(path) for path in items if path)
861 else:
862 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000863 else:
864 print_stdout = True
865 filter_fn = None
866
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000867 if parsed_url is None:
868 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
869 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000870 try:
871 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000872 args, cwd=cwd, env=env, print_stdout=print_stdout,
873 filter_fn=filter_fn,
874 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000875 except subprocess2.CalledProcessError:
876 if not options.ignore:
877 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000878 else:
879 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000880
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000881
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000882 @gclient_utils.lockedmethod
883 def _run_is_done(self, file_list, parsed_url):
884 # Both these are kept for hooks that are run as a separate tree traversal.
885 self._file_list = file_list
886 self._parsed_url = parsed_url
887 self._processed = True
888
szager@google.comb9a78d32012-03-13 18:46:21 +0000889 @staticmethod
890 def GetHookAction(hook_dict, matching_file_list):
891 """Turns a parsed 'hook' dict into an executable command."""
892 logging.debug(hook_dict)
893 logging.debug(matching_file_list)
894 command = hook_dict['action'][:]
895 if command[0] == 'python':
896 # If the hook specified "python" as the first item, the action is a
897 # Python script. Run it by starting a new copy of the same
898 # interpreter.
899 command[0] = sys.executable
900 if '$matching_files' in command:
901 splice_index = command.index('$matching_files')
902 command[splice_index:splice_index + 1] = matching_file_list
903 return command
904
905 def GetHooks(self, options):
906 """Evaluates all hooks, and return them in a flat list.
907
908 RunOnDeps() must have been called before to load the DEPS.
909 """
910 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000911 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000912 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000913 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000914 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000915 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000916 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000917 # TODO(maruel): If the user is using git or git-svn, then we don't know
918 # what files have changed so we always run all hooks. It'd be nice to fix
919 # that.
920 if (options.force or
921 isinstance(self.parsed_url, self.FileImpl) or
922 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000923 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000924 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000925 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000926 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000927 # Run hooks on the basis of whether the files from the gclient operation
928 # match each hook's pattern.
929 for hook_dict in self.deps_hooks:
930 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000931 matching_file_list = [
932 f for f in self.file_list_and_children if pattern.search(f)
933 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000934 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000935 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000936 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000937 result.extend(s.GetHooks(options))
938 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000939
szager@google.comb9a78d32012-03-13 18:46:21 +0000940 def RunHooksRecursively(self, options):
941 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000942 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000943 for hook in self.GetHooks(options):
944 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000945 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000946 gclient_utils.CheckCallAndFilterAndHeader(
947 hook, cwd=self.root.root_dir, always=True)
948 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
949 # Use a discrete exit status code of 2 to indicate that a hook action
950 # failed. Users of this script may wish to treat hook action failures
951 # differently from VC failures.
952 print >> sys.stderr, 'Error: %s' % str(e)
953 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000954 finally:
955 elapsed_time = time.time() - start_time
956 if elapsed_time > 10:
957 print "Hook '%s' took %.2f secs" % (
958 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000959
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000960 def RunPreDepsHooks(self):
961 assert self.processed
962 assert self.deps_parsed
963 assert not self.pre_deps_hooks_ran
964 assert not self.hooks_ran
965 for s in self.dependencies:
966 assert not s.processed
967 self._pre_deps_hooks_ran = True
968 for hook in self.pre_deps_hooks:
969 try:
970 start_time = time.time()
971 gclient_utils.CheckCallAndFilterAndHeader(
972 hook, cwd=self.root.root_dir, always=True)
973 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
974 # Use a discrete exit status code of 2 to indicate that a hook action
975 # failed. Users of this script may wish to treat hook action failures
976 # differently from VC failures.
977 print >> sys.stderr, 'Error: %s' % str(e)
978 sys.exit(2)
979 finally:
980 elapsed_time = time.time() - start_time
981 if elapsed_time > 10:
982 print "Hook '%s' took %.2f secs" % (
983 gclient_utils.CommandToStr(hook), elapsed_time)
984
985
maruel@chromium.org0d812442010-08-10 12:41:08 +0000986 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000987 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000988 dependencies = self.dependencies
989 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000990 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000991 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000992 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000993 for i in d.subtree(include_all):
994 yield i
995
996 def depth_first_tree(self):
997 """Depth-first recursion including the root node."""
998 yield self
999 for i in self.dependencies:
1000 for j in i.depth_first_tree():
1001 if j.should_process:
1002 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001003
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001004 @gclient_utils.lockedmethod
1005 def add_dependency(self, new_dep):
1006 self._dependencies.append(new_dep)
1007
1008 @gclient_utils.lockedmethod
1009 def _mark_as_parsed(self, new_hooks):
1010 self._deps_hooks.extend(new_hooks)
1011 self._deps_parsed = True
1012
maruel@chromium.org68988972011-09-20 14:11:42 +00001013 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001014 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001015 def dependencies(self):
1016 return tuple(self._dependencies)
1017
1018 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001019 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001020 def deps_hooks(self):
1021 return tuple(self._deps_hooks)
1022
1023 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001024 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001025 def pre_deps_hooks(self):
1026 return tuple(self._pre_deps_hooks)
1027
1028 @property
1029 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001030 def parsed_url(self):
1031 return self._parsed_url
1032
1033 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001034 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001035 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001036 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001037 return self._deps_parsed
1038
1039 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001040 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001041 def processed(self):
1042 return self._processed
1043
1044 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001045 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001046 def pre_deps_hooks_ran(self):
1047 return self._pre_deps_hooks_ran
1048
1049 @property
1050 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001051 def hooks_ran(self):
1052 return self._hooks_ran
1053
1054 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001055 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001056 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001057 return tuple(self._file_list)
1058
1059 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001060 def used_scm(self):
1061 """SCMWrapper instance for this dependency or None if not processed yet."""
1062 return self._used_scm
1063
1064 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001065 @gclient_utils.lockedmethod
1066 def got_revision(self):
1067 return self._got_revision
1068
1069 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001070 def file_list_and_children(self):
1071 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001072 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001073 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001074 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001075
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001076 def __str__(self):
1077 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001078 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001079 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001080 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001081 # First try the native property if it exists.
1082 if hasattr(self, '_' + i):
1083 value = getattr(self, '_' + i, False)
1084 else:
1085 value = getattr(self, i, False)
1086 if value:
1087 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001088
1089 for d in self.dependencies:
1090 out.extend([' ' + x for x in str(d).splitlines()])
1091 out.append('')
1092 return '\n'.join(out)
1093
1094 def __repr__(self):
1095 return '%s: %s' % (self.name, self.url)
1096
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001097 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001098 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001099 out = '%s(%s)' % (self.name, self.url)
1100 i = self.parent
1101 while i and i.name:
1102 out = '%s(%s) -> %s' % (i.name, i.url, out)
1103 i = i.parent
1104 return out
1105
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001106
1107class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001108 """Object that represent a gclient checkout. A tree of Dependency(), one per
1109 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001110
1111 DEPS_OS_CHOICES = {
1112 "win32": "win",
1113 "win": "win",
1114 "cygwin": "win",
1115 "darwin": "mac",
1116 "mac": "mac",
1117 "unix": "unix",
1118 "linux": "unix",
1119 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001120 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001121 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001122 }
1123
1124 DEFAULT_CLIENT_FILE_TEXT = ("""\
1125solutions = [
1126 { "name" : "%(solution_name)s",
1127 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001128 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001129 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001130 "custom_deps" : {
1131 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001132 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001133 },
1134]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001135cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001136""")
1137
1138 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1139 { "name" : "%(solution_name)s",
1140 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001141 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001142 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001143 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001144%(solution_deps)s },
1145 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001146 },
1147""")
1148
1149 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1150# Snapshot generated with gclient revinfo --snapshot
1151solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001152%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001153""")
1154
1155 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001156 # Do not change previous behavior. Only solution level and immediate DEPS
1157 # are processed.
1158 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001159 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001160 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001161 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001162 if options.deps_os:
1163 enforced_os = options.deps_os.split(',')
1164 else:
1165 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1166 if 'all' in enforced_os:
1167 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001168 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001169 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001170 self.config_content = None
1171
borenet@google.com88d10082014-03-21 17:24:48 +00001172 def _CheckConfig(self):
1173 """Verify that the config matches the state of the existing checked-out
1174 solutions."""
1175 for dep in self.dependencies:
1176 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001177 scm = gclient_scm.CreateSCM(
1178 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001179 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001180 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001181 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001182Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001183is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001184
borenet@google.com97882362014-04-07 20:06:02 +00001185The .gclient file contains:
1186%(expected_url)s (%(expected_scm)s)
1187
1188The local checkout in %(checkout_path)s reports:
1189%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001190
1191You should ensure that the URL listed in .gclient is correct and either change
1192it or fix the checkout. If you're managing your own git checkout in
1193%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1194want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001195''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1196 'expected_url': dep.url,
1197 'expected_scm': gclient_scm.GetScmName(dep.url),
1198 'actual_url': actual_url,
1199 'actual_scm': gclient_scm.GetScmName(actual_url)})
1200
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001201 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001202 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001203 config_dict = {}
1204 self.config_content = content
1205 try:
1206 exec(content, config_dict)
1207 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001208 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001209
peter@chromium.org1efccc82012-04-27 16:34:38 +00001210 # Append any target OS that is not already being enforced to the tuple.
1211 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001212 if config_dict.get('target_os_only', False):
1213 self._enforced_os = tuple(set(target_os))
1214 else:
1215 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1216
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001217 cache_dir = config_dict.get('cache_dir')
1218 if cache_dir:
1219 cache_dir = os.path.join(self.root_dir, cache_dir)
1220 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001221 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001222 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001223 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1224 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001225 gclient_scm.GitWrapper.cache_dir = cache_dir
1226 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001227
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001228 if not target_os and config_dict.get('target_os_only', False):
1229 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1230 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001231
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001232 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001233 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001234 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001235 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001236 self, s['name'], s['url'],
1237 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001238 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001239 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001240 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001241 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001242 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001243 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001244 except KeyError:
1245 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1246 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001247 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1248 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001249
1250 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001251 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001252 self._options.config_filename),
1253 self.config_content)
1254
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001255 def MigrateConfigToGit(self, path, options):
1256 svn_url_re = re.compile('^(https?://src\.chromium\.org/svn|'
1257 'svn://svn\.chromium\.org/chrome)/'
1258 '(trunk|branches/[^/]+)/src')
1259 old_git_re = re.compile('^(https?://git\.chromium\.org|'
1260 'ssh://([a-zA-Z_][a-zA-Z0-9_-]*@)?'
1261 'gerrit\.chromium\.org(:2941[89])?)/'
1262 'chromium/src\.git')
1263 # Scan existing .gclient file for obsolete settings. It would be simpler
1264 # to traverse self.dependencies, but working with the AST allows the code to
1265 # dump an updated .gclient file that preserves the ordering of the original.
1266 a = ast.parse(self.config_content, options.config_filename, 'exec')
1267 modified = False
1268 solutions = [elem for elem in a.body if 'solutions' in
1269 [target.id for target in elem.targets]]
1270 if not solutions:
1271 return self
1272 solutions = solutions[-1]
1273 for solution in solutions.value.elts:
1274 # Check for obsolete URL's
1275 url_idx = ast_dict_index(solution, 'url')
1276 if url_idx == -1:
1277 continue
1278 url_val = solution.values[url_idx]
1279 if type(url_val) is not ast.Str:
1280 continue
1281 if (svn_url_re.match(url_val.s.strip())):
1282 raise gclient_utils.Error(
1283"""
1284The chromium code repository has migrated completely to git.
1285Your SVN-based checkout is now obsolete; you need to create a brand-new
1286git checkout by following these instructions:
1287
1288http://www.chromium.org/developers/how-tos/get-the-code
1289""")
1290 if (old_git_re.match(url_val.s.strip())):
1291 url_val.s = CHROMIUM_SRC_URL
1292 modified = True
1293
szager@chromium.org808bcfb2014-08-24 19:38:43 +00001294 # Ensure deps_file is set to .DEPS.git. We enforce this here to smooth
1295 # over switching between pre-git-migration and post-git-migration
1296 # revisions.
1297 # - For pre-migration revisions, .DEPS.git must be explicitly set.
1298 # - For post-migration revisions, .DEPS.git is not present, so gclient
1299 # will correctly fall back to DEPS.
1300 if url_val.s == CHROMIUM_SRC_URL:
1301 deps_file_idx = ast_dict_index(solution, 'deps_file')
1302 if deps_file_idx != -1:
1303 continue
1304 solution.keys.append(ast.Str('deps_file'))
1305 solution.values.append(ast.Str('.DEPS.git'))
1306 modified = True
1307
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001308 if not modified:
1309 return self
1310
1311 print(
1312"""
1313WARNING: gclient detected an obsolete setting in your %s file. The file has
1314been automagically updated. The previous version is available at %s.old.
1315""" % (options.config_filename, options.config_filename))
1316
1317 # Replace existing .gclient with the updated version.
1318 # Return a new GClient instance based on the new content.
1319 new_content = ast2str(a)
1320 dot_gclient_fn = os.path.join(path, options.config_filename)
iannucci@chromium.orgc7c41682014-08-23 03:44:18 +00001321 try:
1322 os.rename(dot_gclient_fn, dot_gclient_fn + '.old')
1323 except OSError:
1324 pass
1325 with open(dot_gclient_fn, 'w') as fh:
1326 fh.write(new_content)
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001327 client = GClient(path, options)
1328 client.SetConfig(new_content)
1329 return client
1330
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001331 @staticmethod
1332 def LoadCurrentConfig(options):
1333 """Searches for and loads a .gclient file relative to the current working
1334 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001335 if options.spec:
1336 client = GClient('.', options)
1337 client.SetConfig(options.spec)
1338 else:
1339 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1340 if not path:
1341 return None
1342 client = GClient(path, options)
1343 client.SetConfig(gclient_utils.FileRead(
1344 os.path.join(path, options.config_filename)))
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001345 client = client.MigrateConfigToGit(path, options)
maruel@chromium.org69392e72011-10-13 22:09:00 +00001346
1347 if (options.revisions and
1348 len(client.dependencies) > 1 and
1349 any('@' not in r for r in options.revisions)):
1350 print >> sys.stderr, (
1351 'You must specify the full solution name like --revision %s@%s\n'
1352 'when you have multiple solutions setup in your .gclient file.\n'
1353 'Other solutions present are: %s.') % (
1354 client.dependencies[0].name,
1355 options.revisions[0],
1356 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001357 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001358
nsylvain@google.comefc80932011-05-31 21:27:56 +00001359 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001360 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001361 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1362 'solution_name': solution_name,
1363 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001364 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001365 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001366 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001367 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001368 })
1369
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001370 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001371 """Creates a .gclient_entries file to record the list of unique checkouts.
1372
1373 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001374 """
1375 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1376 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001377 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001378 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001379 # Skip over File() dependencies as we can't version them.
1380 if not isinstance(entry.parsed_url, self.FileImpl):
1381 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1382 pprint.pformat(entry.parsed_url))
1383 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001384 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001385 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001386 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001387
1388 def _ReadEntries(self):
1389 """Read the .gclient_entries file for the given client.
1390
1391 Returns:
1392 A sequence of solution names, which will be empty if there is the
1393 entries file hasn't been created yet.
1394 """
1395 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001396 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001397 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001398 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001399 try:
1400 exec(gclient_utils.FileRead(filename), scope)
1401 except SyntaxError, e:
1402 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001403 return scope['entries']
1404
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001405 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001406 """Checks for revision overrides."""
1407 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001408 if self._options.head:
1409 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001410 # Do not check safesync_url if one or more --revision flag is specified.
1411 if not self._options.revisions:
1412 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001413 if not s.managed:
1414 self._options.revisions.append('%s@unmanaged' % s.name)
1415 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001416 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001417 if not self._options.revisions:
1418 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001419 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001420 index = 0
1421 for revision in self._options.revisions:
1422 if not '@' in revision:
1423 # Support for --revision 123
1424 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001425 name, rev = revision.split('@', 1)
1426 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001427 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001428 return revision_overrides
1429
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001430 def _ApplySafeSyncRev(self, dep):
1431 """Finds a valid revision from the content of the safesync_url and apply it
1432 by appending revisions to the revision list. Throws if revision appears to
1433 be invalid for the given |dep|."""
1434 assert len(dep.safesync_url) > 0
1435 handle = urllib.urlopen(dep.safesync_url)
1436 rev = handle.read().strip()
1437 handle.close()
1438 if not rev:
1439 raise gclient_utils.Error(
1440 'It appears your safesync_url (%s) is not working properly\n'
1441 '(as it returned an empty response). Check your config.' %
1442 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001443 scm = gclient_scm.CreateSCM(
1444 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001445 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001446 if self._options.verbose:
1447 print('Using safesync_url revision: %s.\n' % safe_rev)
1448 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1449
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001450 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001451 """Runs a command on each dependency in a client and its dependencies.
1452
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001453 Args:
1454 command: The command to use (e.g., 'status' or 'diff')
1455 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001457 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001458 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001459
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001460 revision_overrides = {}
1461 # It's unnecessary to check for revision overrides for 'recurse'.
1462 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001463 if command not in ('diff', 'recurse', 'runhooks', 'status'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001464 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001465 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001466 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001467 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001468 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001469 if command in ('update', 'revert'):
1470 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001471 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001472 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001473 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001474 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1475 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001476 for s in self.dependencies:
1477 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001478 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001479 if revision_overrides:
1480 print >> sys.stderr, ('Please fix your script, having invalid '
1481 '--revision flags will soon considered an error.')
piman@chromium.org6f363722010-04-27 00:41:09 +00001482
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001483 # Once all the dependencies have been processed, it's now safe to run the
1484 # hooks.
1485 if not self._options.nohooks:
1486 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001487
1488 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001489 # Notify the user if there is an orphaned entry in their working copy.
1490 # Only delete the directory if there are no changes in it, and
1491 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001492 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001493 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1494 for e in entries]
1495
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001496 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001497 if not prev_url:
1498 # entry must have been overridden via .gclient custom_deps
1499 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001500 # Fix path separator on Windows.
1501 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001502 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001503
1504 def _IsParentOfAny(parent, path_list):
1505 parent_plus_slash = parent + '/'
1506 return any(
1507 path[:len(parent_plus_slash)] == parent_plus_slash
1508 for path in path_list)
1509
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001510 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001511 if (entry not in entries and
1512 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001513 os.path.exists(e_dir)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001514 scm = gclient_scm.CreateSCM(
1515 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001516
1517 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001518 # The directory might be part of a git OR svn checkout.
1519 scm_root = None
1520 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1521 try:
1522 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1523 except subprocess2.CalledProcessError:
1524 pass
1525 if scm_root:
1526 break
1527 else:
1528 logging.warning('Could not find checkout root for %s. Unable to '
1529 'determine whether it is part of a higher-level '
1530 'checkout, so not removing.' % entry)
1531 continue
1532 if scm_root in full_entries:
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001533 logging.info('%s is part of a higher level checkout, not '
1534 'removing.', scm.GetCheckoutRoot())
1535 continue
1536
1537 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001538 scm.status(self._options, [], file_list)
1539 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001540 if (not self._options.delete_unversioned_trees or
1541 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001542 # There are modified files in this entry. Keep warning until
1543 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001544 print(('\nWARNING: \'%s\' is no longer part of this client. '
1545 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001546 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001547 else:
1548 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001549 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001550 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001551 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001552 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001553 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001554 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001555
1556 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001557 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001558 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001559 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001560 work_queue = gclient_utils.ExecutionQueue(
1561 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001562 for s in self.dependencies:
1563 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001564 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001565
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001566 def GetURLAndRev(dep):
1567 """Returns the revision-qualified SCM url for a Dependency."""
1568 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001569 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001570 if isinstance(dep.parsed_url, self.FileImpl):
1571 original_url = dep.parsed_url.file_location
1572 else:
1573 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001574 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001575 scm = gclient_scm.CreateSCM(
1576 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001577 if not os.path.isdir(scm.checkout_path):
1578 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001579 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001580
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001581 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001582 new_gclient = ''
1583 # First level at .gclient
1584 for d in self.dependencies:
1585 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001586 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001587 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001588 for d in dep.dependencies:
1589 entries[d.name] = GetURLAndRev(d)
1590 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001591 GrabDeps(d)
1592 custom_deps = []
1593 for k in sorted(entries.keys()):
1594 if entries[k]:
1595 # Quotes aren't escaped...
1596 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1597 else:
1598 custom_deps.append(' \"%s\": None,\n' % k)
1599 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1600 'solution_name': d.name,
1601 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001602 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001603 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001604 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001605 'solution_deps': ''.join(custom_deps),
1606 }
1607 # Print the snapshot configuration file
1608 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001609 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001610 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001611 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001612 if self._options.actual:
1613 entries[d.name] = GetURLAndRev(d)
1614 else:
1615 entries[d.name] = d.parsed_url
1616 keys = sorted(entries.keys())
1617 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001618 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001619 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001620
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001621 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001622 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001623 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001624
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001625 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001626 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001627 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001628 return self._root_dir
1629
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001630 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001631 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001632 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001633 return self._enforced_os
1634
maruel@chromium.org68988972011-09-20 14:11:42 +00001635 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001636 def recursion_limit(self):
1637 """How recursive can each dependencies in DEPS file can load DEPS file."""
1638 return self._recursion_limit
1639
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001640 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001641 def try_recursedeps(self):
1642 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001643 return True
1644
1645 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001646 def target_os(self):
1647 return self._enforced_os
1648
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001649
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001650#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001651
1652
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001653def CMDcleanup(parser, args):
1654 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001655
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001656 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1657 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001658 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1659 help='override deps for the specified (comma-separated) '
1660 'platform(s); \'all\' will process all deps_os '
1661 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001662 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001663 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001664 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001665 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001666 if options.verbose:
1667 # Print out the .gclient file. This is longer than if we just printed the
1668 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001669 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001670 return client.RunOnDeps('cleanup', args)
1671
1672
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001673@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001674def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001675 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001676
1677 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001678 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1679 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001680 """
1681 # Stop parsing at the first non-arg so that these go through to the command
1682 parser.disable_interspersed_args()
1683 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001684 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001685 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001686 help='Ignore non-zero return codes from subcommands.')
1687 parser.add_option('--prepend-dir', action='store_true',
1688 help='Prepend relative dir for use with git <cmd> --null.')
1689 parser.add_option('--no-progress', action='store_true',
1690 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001691 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001692 if not args:
1693 print >> sys.stderr, 'Need to supply a command!'
1694 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001695 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1696 if not root_and_entries:
1697 print >> sys.stderr, (
1698 'You need to run gclient sync at least once to use \'recurse\'.\n'
1699 'This is because .gclient_entries needs to exist and be up to date.')
1700 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001701
1702 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001703 scm_set = set()
1704 for scm in options.scm:
1705 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001706 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001707
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001708 options.nohooks = True
1709 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001710 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1711 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001712
1713
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001714@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001715def CMDfetch(parser, args):
1716 """Fetches upstream commits for all modules.
1717
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001718 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1719 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001720 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001721 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001722 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1723
1724
1725def CMDgrep(parser, args):
1726 """Greps through git repos managed by gclient.
1727
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001728 Runs 'git grep [args...]' for each module.
1729 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001730 # We can't use optparse because it will try to parse arguments sent
1731 # to git grep and throw an error. :-(
1732 if not args or re.match('(-h|--help)$', args[0]):
1733 print >> sys.stderr, (
1734 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1735 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1736 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1737 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1738 ' end of your query.'
1739 )
1740 return 1
1741
1742 jobs_arg = ['--jobs=1']
1743 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1744 jobs_arg, args = args[:1], args[1:]
1745 elif re.match(r'(-j|--jobs)$', args[0]):
1746 jobs_arg, args = args[:2], args[2:]
1747
1748 return CMDrecurse(
1749 parser,
1750 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1751 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001752
1753
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001754@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001755def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001756 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001757
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001758 This specifies the configuration for further commands. After update/sync,
1759 top-level DEPS files in each module are read to determine dependent
1760 modules to operate on as well. If optional [url] parameter is
1761 provided, then configuration is read from a specified Subversion server
1762 URL.
1763 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001764 # We do a little dance with the --gclientfile option. 'gclient config' is the
1765 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1766 # arguments. So, we temporarily stash any --gclientfile parameter into
1767 # options.output_config_file until after the (gclientfile xor spec) error
1768 # check.
1769 parser.remove_option('--gclientfile')
1770 parser.add_option('--gclientfile', dest='output_config_file',
1771 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001772 parser.add_option('--name',
1773 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001774 parser.add_option('--deps-file', default='DEPS',
1775 help='overrides the default name for the DEPS file for the'
1776 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001777 parser.add_option('--unmanaged', action='store_true', default=False,
1778 help='overrides the default behavior to make it possible '
1779 'to have the main solution untouched by gclient '
1780 '(gclient will check out unmanaged dependencies but '
1781 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001782 parser.add_option('--cache-dir',
1783 help='(git only) Cache all git repos into this dir and do '
1784 'shared clones from the cache, instead of cloning '
1785 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001786 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001787 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001788 if options.output_config_file:
1789 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001790 if ((options.spec and args) or len(args) > 2 or
1791 (not options.spec and not args)):
1792 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1793
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001794 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001795 if options.spec:
1796 client.SetConfig(options.spec)
1797 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001798 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001799 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001800 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001801 if name.endswith('.git'):
1802 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001803 else:
1804 # specify an alternate relpath for the given URL.
1805 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001806 deps_file = options.deps_file
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001807 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001808 if len(args) > 1:
1809 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001810 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001811 managed=not options.unmanaged,
1812 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001813 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001814 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001815
1816
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001817@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001818 gclient pack > patch.txt
1819 generate simple patch for configured client and dependences
1820""")
1821def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001822 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001823
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001824 Internally, runs 'svn diff'/'git diff' on each checked out module and
1825 dependencies, and performs minimal postprocessing of the output. The
1826 resulting patch is printed to stdout and can be applied to a freshly
1827 checked out tree via 'patch -p0 < patchfile'.
1828 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001829 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1830 help='override deps for the specified (comma-separated) '
1831 'platform(s); \'all\' will process all deps_os '
1832 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001833 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001834 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001835 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001836 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001837 client = GClient.LoadCurrentConfig(options)
1838 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001839 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001840 if options.verbose:
1841 # Print out the .gclient file. This is longer than if we just printed the
1842 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001843 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001844 return client.RunOnDeps('pack', args)
1845
1846
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001847def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001848 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001849 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1850 help='override deps for the specified (comma-separated) '
1851 'platform(s); \'all\' will process all deps_os '
1852 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001853 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001854 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001855 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001856 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001857 if options.verbose:
1858 # Print out the .gclient file. This is longer than if we just printed the
1859 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001860 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001861 return client.RunOnDeps('status', args)
1862
1863
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001864@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001865 gclient sync
1866 update files from SCM according to current configuration,
1867 *for modules which have changed since last update or sync*
1868 gclient sync --force
1869 update files from SCM according to current configuration, for
1870 all modules (useful for recovering files deleted from local copy)
1871 gclient sync --revision src@31000
1872 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001873
1874JSON output format:
1875If the --output-json option is specified, the following document structure will
1876be emitted to the provided file. 'null' entries may occur for subprojects which
1877are present in the gclient solution, but were not processed (due to custom_deps,
1878os_deps, etc.)
1879
1880{
1881 "solutions" : {
1882 "<name>": { # <name> is the posix-normalized path to the solution.
1883 "revision": [<svn rev int>|<git id hex string>|null],
1884 "scm": ["svn"|"git"|null],
1885 }
1886 }
1887}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001888""")
1889def CMDsync(parser, args):
1890 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001891 parser.add_option('-f', '--force', action='store_true',
1892 help='force update even for unchanged modules')
1893 parser.add_option('-n', '--nohooks', action='store_true',
1894 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001895 parser.add_option('-p', '--noprehooks', action='store_true',
1896 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001897 parser.add_option('-r', '--revision', action='append',
1898 dest='revisions', metavar='REV', default=[],
1899 help='Enforces revision/hash for the solutions with the '
1900 'format src@rev. The src@ part is optional and can be '
1901 'skipped. -r can be used multiple times when .gclient '
1902 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001903 'if the src@ part is skipped. Note that specifying '
1904 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001905 parser.add_option('--with_branch_heads', action='store_true',
1906 help='Clone git "branch_heads" refspecs in addition to '
1907 'the default refspecs. This adds about 1/2GB to a '
1908 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001909 parser.add_option('--with_tags', action='store_true',
1910 help='Clone git tags in addition to the default refspecs.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001911 parser.add_option('-t', '--transitive', action='store_true',
1912 help='When a revision is specified (in the DEPS file or '
1913 'with the command-line flag), transitively update '
1914 'the dependencies to the date of the given revision. '
1915 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001916 parser.add_option('-H', '--head', action='store_true',
1917 help='skips any safesync_urls specified in '
1918 'configured solutions and sync to head instead')
1919 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001920 help='Deletes from the working copy any dependencies that '
1921 'have been removed since the last sync, as long as '
1922 'there are no local modifications. When used with '
1923 '--force, such dependencies are removed even if they '
1924 'have local modifications. When used with --reset, '
1925 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001926 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001927 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001928 parser.add_option('-R', '--reset', action='store_true',
1929 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001930 parser.add_option('-M', '--merge', action='store_true',
1931 help='merge upstream changes instead of trying to '
1932 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001933 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1934 help='override deps for the specified (comma-separated) '
1935 'platform(s); \'all\' will process all deps_os '
1936 'references')
1937 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1938 help='Skip svn up whenever possible by requesting '
1939 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001940 parser.add_option('--upstream', action='store_true',
1941 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001942 parser.add_option('--output-json',
1943 help='Output a json document to this path containing '
1944 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001945 parser.add_option('--no-history', action='store_true',
1946 help='GIT ONLY - Reduces the size/time of the checkout at '
1947 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001948 parser.add_option('--shallow', action='store_true',
1949 help='GIT ONLY - Do a shallow clone into the cache dir. '
1950 'Requires Git 1.9+')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001951 parser.add_option('--ignore_locks', action='store_true',
1952 help='GIT ONLY - Ignore cache locks.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001953 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001954 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001955
1956 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001957 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001958
maruel@chromium.org307d1792010-05-31 20:03:13 +00001959 if options.revisions and options.head:
1960 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001961 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001962
1963 if options.verbose:
1964 # Print out the .gclient file. This is longer than if we just printed the
1965 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001966 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001967 ret = client.RunOnDeps('update', args)
1968 if options.output_json:
1969 slns = {}
1970 for d in client.subtree(True):
1971 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1972 slns[normed] = {
1973 'revision': d.got_revision,
1974 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001975 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001976 }
1977 with open(options.output_json, 'wb') as f:
1978 json.dump({'solutions': slns}, f)
1979 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001980
1981
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001982CMDupdate = CMDsync
1983
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001984
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001985def CMDdiff(parser, args):
1986 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001987 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1988 help='override deps for the specified (comma-separated) '
1989 'platform(s); \'all\' will process all deps_os '
1990 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001991 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001992 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001993 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001994 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001995 if options.verbose:
1996 # Print out the .gclient file. This is longer than if we just printed the
1997 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001998 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001999 return client.RunOnDeps('diff', args)
2000
2001
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002002def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002003 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002004
2005 That's the nuclear option to get back to a 'clean' state. It removes anything
2006 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002007 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2008 help='override deps for the specified (comma-separated) '
2009 'platform(s); \'all\' will process all deps_os '
2010 'references')
2011 parser.add_option('-n', '--nohooks', action='store_true',
2012 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002013 parser.add_option('-p', '--noprehooks', action='store_true',
2014 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002015 parser.add_option('--upstream', action='store_true',
2016 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002017 (options, args) = parser.parse_args(args)
2018 # --force is implied.
2019 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002020 options.reset = False
2021 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002022 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002023 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002024 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002025 return client.RunOnDeps('revert', args)
2026
2027
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002028def CMDrunhooks(parser, args):
2029 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002030 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2031 help='override deps for the specified (comma-separated) '
2032 'platform(s); \'all\' will process all deps_os '
2033 'references')
2034 parser.add_option('-f', '--force', action='store_true', default=True,
2035 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002036 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002037 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002038 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002039 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002040 if options.verbose:
2041 # Print out the .gclient file. This is longer than if we just printed the
2042 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00002043 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002044 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002045 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002046 return client.RunOnDeps('runhooks', args)
2047
2048
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002049def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002050 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002051
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002052 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002053 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002054 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
2055 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002056 commit can change.
2057 """
2058 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2059 help='override deps for the specified (comma-separated) '
2060 'platform(s); \'all\' will process all deps_os '
2061 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002062 parser.add_option('-a', '--actual', action='store_true',
2063 help='gets the actual checked out revisions instead of the '
2064 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002065 parser.add_option('-s', '--snapshot', action='store_true',
2066 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002067 'version of all repositories to reproduce the tree, '
2068 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002069 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002070 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002071 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002072 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002073 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002074 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002075
2076
szager@google.comb9a78d32012-03-13 18:46:21 +00002077def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002078 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002079 (options, args) = parser.parse_args(args)
2080 options.force = True
2081 client = GClient.LoadCurrentConfig(options)
2082 if not client:
2083 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2084 client.RunOnDeps(None, [])
2085 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
2086 return 0
2087
2088
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002089class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002090 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002091
2092 def __init__(self, **kwargs):
2093 optparse.OptionParser.__init__(
2094 self, version='%prog ' + __version__, **kwargs)
2095
2096 # Some arm boards have issues with parallel sync.
2097 if platform.machine().startswith('arm'):
2098 jobs = 1
2099 else:
2100 jobs = max(8, gclient_utils.NumLocalCpus())
2101 # cmp: 2013/06/19
2102 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00002103 # Bypassed if a bot_update flag is detected.
2104 if (os.environ.get('CHROME_HEADLESS') == '1' and
2105 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00002106 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002107
2108 self.add_option(
2109 '-j', '--jobs', default=jobs, type='int',
2110 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002111 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002112 self.add_option(
2113 '-v', '--verbose', action='count', default=0,
2114 help='Produces additional output for diagnostics. Can be used up to '
2115 'three times for more logging info.')
2116 self.add_option(
2117 '--gclientfile', dest='config_filename',
2118 help='Specify an alternate %s file' % self.gclientfile_default)
2119 self.add_option(
2120 '--spec',
2121 help='create a gclient file containing the provided string. Due to '
2122 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2123 self.add_option(
2124 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002125 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002126
2127 def parse_args(self, args=None, values=None):
2128 """Integrates standard options processing."""
2129 options, args = optparse.OptionParser.parse_args(self, args, values)
2130 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2131 logging.basicConfig(
2132 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002133 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002134 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002135 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002136 if (options.config_filename and
2137 options.config_filename != os.path.basename(options.config_filename)):
2138 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002139 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002140 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002141 options.entries_filename = options.config_filename + '_entries'
2142 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002143 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002144
2145 # These hacks need to die.
2146 if not hasattr(options, 'revisions'):
2147 # GClient.RunOnDeps expects it even if not applicable.
2148 options.revisions = []
2149 if not hasattr(options, 'head'):
2150 options.head = None
2151 if not hasattr(options, 'nohooks'):
2152 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002153 if not hasattr(options, 'noprehooks'):
2154 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002155 if not hasattr(options, 'deps_os'):
2156 options.deps_os = None
2157 if not hasattr(options, 'manually_grab_svn_rev'):
2158 options.manually_grab_svn_rev = None
2159 if not hasattr(options, 'force'):
2160 options.force = None
2161 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002162
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002163
2164def disable_buffering():
2165 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2166 # operations. Python as a strong tendency to buffer sys.stdout.
2167 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2168 # Make stdout annotated with the thread ids.
2169 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002170
2171
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002172def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002173 """Doesn't parse the arguments here, just find the right subcommand to
2174 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002175 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00002176 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002177 '\nYour python version %s is unsupported, please upgrade.\n' %
2178 sys.version.split(' ', 1)[0])
2179 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002180 if not sys.executable:
2181 print >> sys.stderr, (
2182 '\nPython cannot find the location of it\'s own executable.\n')
2183 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002184 fix_encoding.fix_encoding()
2185 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00002186 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002187 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002188 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002189 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002190 except KeyboardInterrupt:
2191 gclient_utils.GClientChildren.KillAllRemainingChildren()
2192 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00002193 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002194 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002195 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002196 finally:
2197 gclient_utils.PrintWarnings()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002198
2199
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002200if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002201 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002202
2203# vim: ts=2:sw=2:tw=80:et: