blob: 4663a1e0b33ea9157b804c19e431b39ee30addd4 [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
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000223 if url == CHROMIUM_SRC_URL:
224 self._deps_file = 'DEPS'
225 else:
226 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000227 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000228 # 'managed' determines whether or not this dependency is synced/updated by
229 # gclient after gclient checks it out initially. The difference between
230 # 'managed' and 'should_process' is that the user specifies 'managed' via
231 # the --unmanaged command-line flag or a .gclient config, where
232 # 'should_process' is dynamically set by gclient if it goes over its
233 # recursion limit and controls gclient's behavior so it does not misbehave.
234 self._managed = managed
235 self._should_process = should_process
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000236 # This is a mutable value which has the list of 'target_os' OSes listed in
237 # the current deps file.
238 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239
240 # These are only set in .gclient and not in DEPS files.
241 self._custom_vars = custom_vars or {}
242 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000243 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000245 # TODO(iannucci): Remove this when all masters are correctly substituting
246 # the new blink url.
247 if (self._custom_vars.get('webkit_trunk', '') ==
248 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000249 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
250 print 'Overwriting Var("webkit_trunk") with %s' % new_url
251 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000252
maruel@chromium.org064186c2011-09-27 23:53:33 +0000253 # Post process the url to remove trailing slashes.
254 if isinstance(self._url, basestring):
255 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
256 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self._url = self._url.replace('/@', '@')
258 elif not isinstance(self._url,
259 (self.FromImpl, self.FileImpl, None.__class__)):
260 raise gclient_utils.Error(
261 ('dependency url must be either a string, None, '
262 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000263 # Make any deps_file path platform-appropriate.
264 for sep in ['/', '\\']:
265 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000266
267 @property
268 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000269 return self._deps_file
270
271 @property
272 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000273 return self._managed
274
275 @property
276 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277 return self._parent
278
279 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000280 def root(self):
281 """Returns the root node, a GClient object."""
282 if not self.parent:
283 # This line is to signal pylint that it could be a GClient instance.
284 return self or GClient(None, None)
285 return self.parent.root
286
287 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000288 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000289 return self._safesync_url
290
291 @property
292 def should_process(self):
293 """True if this dependency should be processed, i.e. checked out."""
294 return self._should_process
295
296 @property
297 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000298 return self._custom_vars.copy()
299
300 @property
301 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000302 return self._custom_deps.copy()
303
maruel@chromium.org064186c2011-09-27 23:53:33 +0000304 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000305 def custom_hooks(self):
306 return self._custom_hooks[:]
307
308 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000309 def url(self):
310 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000311
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000312 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000313 def target_os(self):
314 if self.local_target_os is not None:
315 return tuple(set(self.local_target_os).union(self.parent.target_os))
316 else:
317 return self.parent.target_os
318
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000319 def get_custom_deps(self, name, url):
320 """Returns a custom deps if applicable."""
321 if self.parent:
322 url = self.parent.get_custom_deps(name, url)
323 # None is a valid return value to disable a dependency.
324 return self.custom_deps.get(name, url)
325
maruel@chromium.org064186c2011-09-27 23:53:33 +0000326
327class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000328 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000329
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000330 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000331 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000332 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000333 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000334 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000335 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000336
337 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000338 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000339
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000340 self._pre_deps_hooks = []
341
maruel@chromium.org68988972011-09-20 14:11:42 +0000342 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000343 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000344 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000345 # A cache of the files affected by the current operation, necessary for
346 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000347 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000348 # If it is not set to True, the dependency wasn't processed for its child
349 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000350 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000351 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000352 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000353 # This dependency had its pre-DEPS hooks run
354 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000355 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000356 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000357 # This is the scm used to checkout self.url. It may be used by dependencies
358 # to get the datetime of the revision we checked out.
359 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000360 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000361 # The actual revision we ended up getting, or None if that information is
362 # unavailable
363 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000364
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000365 # This is a mutable value that overrides the normal recursion limit for this
366 # dependency. It is read from the actual DEPS file so cannot be set on
367 # class instantiation.
368 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000369 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000370 # 'no recursion' setting on a dep-by-dep basis. It will replace
371 # recursion_override.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000372 self.recursedeps = None
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000373
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000374 if not self.name and self.parent:
375 raise gclient_utils.Error('Dependency without name')
376
maruel@chromium.org470b5432011-10-11 18:18:19 +0000377 @property
378 def requirements(self):
379 """Calculate the list of requirements."""
380 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000381 # self.parent is implicitly a requirement. This will be recursive by
382 # definition.
383 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000384 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000385
386 # For a tree with at least 2 levels*, the leaf node needs to depend
387 # on the level higher up in an orderly way.
388 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
389 # thus unsorted, while the .gclient format is a list thus sorted.
390 #
391 # * _recursion_limit is hard coded 2 and there is no hope to change this
392 # value.
393 #
394 # Interestingly enough, the following condition only works in the case we
395 # want: self is a 2nd level node. 3nd level node wouldn't need this since
396 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000397 if self.parent and self.parent.parent and not self.parent.parent.parent:
398 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000399
400 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000401 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000402
maruel@chromium.org470b5432011-10-11 18:18:19 +0000403 if self.name:
404 requirements |= set(
405 obj.name for obj in self.root.subtree(False)
406 if (obj is not self
407 and obj.name and
408 self.name.startswith(posixpath.join(obj.name, ''))))
409 requirements = tuple(sorted(requirements))
410 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
411 return requirements
412
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000413 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000414 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000415 """Returns False if recursion_override is ever specified."""
416 if self.recursion_override is not None:
417 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000418 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000419
420 @property
421 def recursion_limit(self):
422 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000423 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000424 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000425 if self.try_recursedeps and self.parent.recursedeps != None:
426 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000427 return 1
428
429 if self.recursion_override is not None:
430 return self.recursion_override
431 return max(self.parent.recursion_limit - 1, 0)
432
maruel@chromium.org470b5432011-10-11 18:18:19 +0000433 def verify_validity(self):
434 """Verifies that this Dependency is fine to add as a child of another one.
435
436 Returns True if this entry should be added, False if it is a duplicate of
437 another entry.
438 """
439 logging.info('Dependency(%s).verify_validity()' % self.name)
440 if self.name in [s.name for s in self.parent.dependencies]:
441 raise gclient_utils.Error(
442 'The same name "%s" appears multiple times in the deps section' %
443 self.name)
444 if not self.should_process:
445 # Return early, no need to set requirements.
446 return True
447
448 # This require a full tree traversal with locks.
449 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
450 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000451 self_url = self.LateOverride(self.url)
452 sibling_url = sibling.LateOverride(sibling.url)
453 # Allow to have only one to be None or ''.
454 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000455 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000456 ('Dependency %s specified more than once:\n'
457 ' %s [%s]\n'
458 'vs\n'
459 ' %s [%s]') % (
460 self.name,
461 sibling.hierarchy(),
462 sibling_url,
463 self.hierarchy(),
464 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000465 # In theory we could keep it as a shadow of the other one. In
466 # practice, simply ignore it.
467 logging.warn('Won\'t process duplicate dependency %s' % sibling)
468 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000469 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000470
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000472 """Resolves the parsed url from url.
473
474 Manages From() keyword accordingly. Do not touch self.parsed_url nor
475 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000476 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000477 parsed_url = self.get_custom_deps(self.name, url)
478 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000479 logging.info(
480 'Dependency(%s).LateOverride(%s) -> %s' %
481 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000482 return parsed_url
483
484 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000485 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000486 ref = [
487 dep for dep in self.root.subtree(True) if url.module_name == dep.name
488 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000489 if not ref:
490 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
491 url.module_name, ref))
492 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000493 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000494 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000495 found_deps = [d for d in ref.dependencies if d.name == sub_target]
496 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000497 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000498 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
499 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000500 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000501
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000502 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000503 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000504 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000505 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000506 'Dependency(%s).LateOverride(%s) -> %s (From)' %
507 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000508 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000509
510 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000511 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000512 if (not parsed_url[0] and
513 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514 # A relative url. Fetch the real base.
515 path = parsed_url[2]
516 if not path.startswith('/'):
517 raise gclient_utils.Error(
518 'relative DEPS entry \'%s\' must begin with a slash' % url)
519 # Create a scm just to query the full url.
520 parent_url = self.parent.parsed_url
521 if isinstance(parent_url, self.FileImpl):
522 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000523 scm = gclient_scm.CreateSCM(
524 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000525 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000527 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000528 logging.info(
529 'Dependency(%s).LateOverride(%s) -> %s' %
530 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000531 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000532
533 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000534 logging.info(
535 'Dependency(%s).LateOverride(%s) -> %s (File)' %
536 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000537 return url
538
539 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000540 logging.info(
541 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000542 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000543
544 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000545
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000546 @staticmethod
547 def MergeWithOsDeps(deps, deps_os, target_os_list):
548 """Returns a new "deps" structure that is the deps sent in updated
549 with information from deps_os (the deps_os section of the DEPS
550 file) that matches the list of target os."""
551 os_overrides = {}
552 for the_target_os in target_os_list:
553 the_target_os_deps = deps_os.get(the_target_os, {})
554 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
555 overrides = os_overrides.setdefault(os_dep_key, [])
556 overrides.append((the_target_os, os_dep_value))
557
558 # If any os didn't specify a value (we have fewer value entries
559 # than in the os list), then it wants to use the default value.
560 for os_dep_key, os_dep_value in os_overrides.iteritems():
561 if len(os_dep_value) != len(target_os_list):
562 # Record the default value too so that we don't accidently
563 # set it to None or miss a conflicting DEPS.
564 if os_dep_key in deps:
565 os_dep_value.append(('default', deps[os_dep_key]))
566
567 target_os_deps = {}
568 for os_dep_key, os_dep_value in os_overrides.iteritems():
569 # os_dep_value is a list of (os, value) pairs.
570 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
571 if not possible_values:
572 target_os_deps[os_dep_key] = None
573 else:
574 if len(possible_values) > 1:
575 # It would be possible to abort here but it would be
576 # unfortunate if we end up preventing any kind of checkout.
577 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
578 os_dep_key, os_dep_value, target_os_list)
579 # Sorting to get the same result every time in case of conflicts.
580 target_os_deps[os_dep_key] = sorted(possible_values)[0]
581
582 new_deps = deps.copy()
583 new_deps.update(target_os_deps)
584 return new_deps
585
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000586 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000587 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000588 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000589 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000590
591 deps_content = None
592 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000593
594 # First try to locate the configured deps file. If it's missing, fallback
595 # to DEPS.
596 deps_files = [self.deps_file]
597 if 'DEPS' not in deps_files:
598 deps_files.append('DEPS')
599 for deps_file in deps_files:
600 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
601 if os.path.isfile(filepath):
602 logging.info(
603 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
604 filepath)
605 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000606 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000607 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
608 filepath)
609
610 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000611 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000612 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000613 use_strict = 'use strict' in deps_content.splitlines()[0]
614
615 local_scope = {}
616 if deps_content:
617 # One thing is unintuitive, vars = {} must happen before Var() use.
618 var = self.VarImpl(self.custom_vars, local_scope)
619 if use_strict:
620 logging.info(
621 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
622 global_scope = {
623 '__builtins__': {'None': None},
624 'Var': var.Lookup,
625 'deps_os': {},
626 }
627 else:
628 global_scope = {
629 'File': self.FileImpl,
630 'From': self.FromImpl,
631 'Var': var.Lookup,
632 'deps_os': {},
633 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000634 # Eval the content.
635 try:
636 exec(deps_content, global_scope, local_scope)
637 except SyntaxError, e:
638 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000639 if use_strict:
640 for key, val in local_scope.iteritems():
641 if not isinstance(val, (dict, list, tuple, str)):
642 raise gclient_utils.Error(
643 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
644 (self.name, key, val))
645
maruel@chromium.org271375b2010-06-23 19:17:38 +0000646 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000647 if 'recursion' in local_scope:
648 self.recursion_override = local_scope.get('recursion')
649 logging.warning(
650 'Setting %s recursion to %d.', self.name, self.recursion_limit)
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000651 self.recursedeps = local_scope.get('recursedeps', None)
652 if 'recursedeps' in local_scope:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000653 self.recursedeps = set(self.recursedeps)
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000654 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000655 # If present, save 'target_os' in the local_target_os property.
656 if 'target_os' in local_scope:
657 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000658 # load os specific dependencies if defined. these dependencies may
659 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000660 target_os_list = self.target_os
661 if 'deps_os' in local_scope and target_os_list:
662 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000663
maruel@chromium.org271375b2010-06-23 19:17:38 +0000664 # If a line is in custom_deps, but not in the solution, we want to append
665 # this line to the solution.
666 for d in self.custom_deps:
667 if d not in deps:
668 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000669
670 # If use_relative_paths is set in the DEPS file, regenerate
671 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000672 # the DEPS file. Also update recursedeps if use_relative_paths is
673 # enabled.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000674 use_relative_paths = local_scope.get('use_relative_paths', False)
675 if use_relative_paths:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000676 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000677 rel_deps = {}
678 for d, url in deps.items():
679 # normpath is required to allow DEPS to use .. in their
680 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000681 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000682 logging.warning('Updating deps by prepending %s.', self.name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000683 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000684
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000685 # Update recursedeps if it's set.
686 if self.recursedeps is not None:
687 logging.warning('Updating recursedeps by prepending %s.', self.name)
688 rel_deps = set()
689 for d in self.recursedeps:
690 rel_deps.add(os.path.normpath(os.path.join(self.name, d)))
691 self.recursedeps = rel_deps
692
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000693 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000694 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000695 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000696 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000697 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000698 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000699 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000700 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000701
702 # override named sets of hooks by the custom hooks
703 hooks_to_run = []
704 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
705 for hook in local_scope.get('hooks', []):
706 if hook.get('name', '') not in hook_names_to_suppress:
707 hooks_to_run.append(hook)
708
709 # add the replacements and any additions
710 for hook in self.custom_hooks:
711 if 'action' in hook:
712 hooks_to_run.append(hook)
713
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000714 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
715 local_scope.get('pre_deps_hooks', [])]
716
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000717 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000718 logging.info('ParseDepsFile(%s) done' % self.name)
719
720 def add_dependencies_and_close(self, deps_to_add, hooks):
721 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000722 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000723 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000724 self.add_dependency(dep)
725 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000726
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000727 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000728 """Uses revision/timestamp of parent if no explicit revision was specified.
729
730 If we are performing an update and --transitive is set, use
731 - the parent's revision if 'self.url' is in the same repository
732 - the parent's timestamp otherwise
733 to update 'self.url'. The used revision/timestamp will be set in
734 'options.revision'.
735 If we have an explicit revision do nothing.
736 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000737 if command == 'update' and options.transitive and not options.revision:
738 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
739 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000740 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000741 if (options.revision and
742 not gclient_utils.IsDateRevision(options.revision)):
743 assert self.parent and self.parent.used_scm
744 # If this dependency is in the same repository as parent it's url will
745 # start with a slash. If so we take the parent revision instead of
746 # it's timestamp.
747 # (The timestamps of commits in google code are broken -- which can
748 # result in dependencies to be checked out at the wrong revision)
749 if self.url.startswith('/'):
750 if options.verbose:
751 print('Using parent\'s revision %s since we are in the same '
752 'repository.' % options.revision)
753 else:
754 parent_revision_date = self.parent.used_scm.GetRevisionDate(
755 options.revision)
756 options.revision = gclient_utils.MakeDateRevision(
757 parent_revision_date)
758 if options.verbose:
759 print('Using parent\'s revision date %s since we are in a '
760 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000761
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000762 # Arguments number differs from overridden method
763 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000764 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000765 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000766 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000767 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000768 if not self.should_process:
769 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000770 # When running runhooks, there's no need to consult the SCM.
771 # All known hooks are expected to run unconditionally regardless of working
772 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000773 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000774 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000775 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000776 if run_scm and parsed_url:
777 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000778 # Special support for single-file checkout.
779 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000780 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
781 # pylint: disable=E1103
782 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000783 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000784 parsed_url.GetPath(), self.root.root_dir, self.name,
785 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000786 self._used_scm.RunCommand('updatesingle',
787 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000788 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000789 # Create a shallow copy to mutate revision.
790 options = copy.copy(options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000791 options.revision = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000792 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000793 command, options, parsed_url, self.parent)
794 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000795 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000796 parsed_url, self.root.root_dir, self.name, self.outbuf,
797 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000798 self._got_revision = self._used_scm.RunCommand(command, options, args,
799 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000800 if file_list:
801 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000802
803 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
804 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000805 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000806 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000807 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000808 continue
809 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000810 [self.root.root_dir.lower(), file_list[i].lower()])
811 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000812 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000813 while file_list[i].startswith(('\\', '/')):
814 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000815
816 # Always parse the DEPS file.
817 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000818 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000819 if command in ('update', 'revert') and not options.noprehooks:
820 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000821
822 if self.recursion_limit:
823 # Parse the dependencies of this dependency.
824 for s in self.dependencies:
825 work_queue.enqueue(s)
826
827 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000828 if not isinstance(parsed_url, self.FileImpl):
829 # Skip file only checkout.
830 scm = gclient_scm.GetScmName(parsed_url)
831 if not options.scm or scm in options.scm:
832 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000833 # Pass in the SCM type as an env variable. Make sure we don't put
834 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000835 env = os.environ.copy()
836 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000837 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000838 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000839 env['GCLIENT_URL'] = str(parsed_url)
840 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000841 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000842 print_stdout = False
843 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000844 """Git-specific path marshaling. It is optimized for git-grep."""
845
846 def mod_path(git_pathspec):
847 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
848 modified_path = os.path.join(self.name, match.group(2))
849 branch = match.group(1) or ''
850 return '%s%s' % (branch, modified_path)
851
852 match = re.match('^Binary file ([^\0]+) matches$', line)
853 if match:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000854 print 'Binary file %s matches\n' % mod_path(match.group(1))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000855 return
856
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000857 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000858 if len(items) == 2 and items[1]:
859 print '%s : %s' % (mod_path(items[0]), items[1])
860 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000861 # Multiple null bytes or a single trailing null byte indicate
862 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000863 print '\n'.join(mod_path(path) for path in items if path)
864 else:
865 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000866 else:
867 print_stdout = True
868 filter_fn = None
869
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000870 if parsed_url is None:
871 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
872 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000873 try:
874 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000875 args, cwd=cwd, env=env, print_stdout=print_stdout,
876 filter_fn=filter_fn,
877 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000878 except subprocess2.CalledProcessError:
879 if not options.ignore:
880 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000881 else:
882 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000883
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000884
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000885 @gclient_utils.lockedmethod
886 def _run_is_done(self, file_list, parsed_url):
887 # Both these are kept for hooks that are run as a separate tree traversal.
888 self._file_list = file_list
889 self._parsed_url = parsed_url
890 self._processed = True
891
szager@google.comb9a78d32012-03-13 18:46:21 +0000892 @staticmethod
893 def GetHookAction(hook_dict, matching_file_list):
894 """Turns a parsed 'hook' dict into an executable command."""
895 logging.debug(hook_dict)
896 logging.debug(matching_file_list)
897 command = hook_dict['action'][:]
898 if command[0] == 'python':
899 # If the hook specified "python" as the first item, the action is a
900 # Python script. Run it by starting a new copy of the same
901 # interpreter.
902 command[0] = sys.executable
903 if '$matching_files' in command:
904 splice_index = command.index('$matching_files')
905 command[splice_index:splice_index + 1] = matching_file_list
906 return command
907
908 def GetHooks(self, options):
909 """Evaluates all hooks, and return them in a flat list.
910
911 RunOnDeps() must have been called before to load the DEPS.
912 """
913 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000914 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000915 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000916 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000917 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000918 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000919 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000920 # TODO(maruel): If the user is using git or git-svn, then we don't know
921 # what files have changed so we always run all hooks. It'd be nice to fix
922 # that.
923 if (options.force or
924 isinstance(self.parsed_url, self.FileImpl) or
925 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000926 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000927 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000928 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000929 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000930 # Run hooks on the basis of whether the files from the gclient operation
931 # match each hook's pattern.
932 for hook_dict in self.deps_hooks:
933 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000934 matching_file_list = [
935 f for f in self.file_list_and_children if pattern.search(f)
936 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000937 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000938 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000939 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000940 result.extend(s.GetHooks(options))
941 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942
szager@google.comb9a78d32012-03-13 18:46:21 +0000943 def RunHooksRecursively(self, options):
944 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000945 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000946 for hook in self.GetHooks(options):
947 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000948 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000949 gclient_utils.CheckCallAndFilterAndHeader(
950 hook, cwd=self.root.root_dir, always=True)
951 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
952 # Use a discrete exit status code of 2 to indicate that a hook action
953 # failed. Users of this script may wish to treat hook action failures
954 # differently from VC failures.
955 print >> sys.stderr, 'Error: %s' % str(e)
956 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000957 finally:
958 elapsed_time = time.time() - start_time
959 if elapsed_time > 10:
960 print "Hook '%s' took %.2f secs" % (
961 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000962
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000963 def RunPreDepsHooks(self):
964 assert self.processed
965 assert self.deps_parsed
966 assert not self.pre_deps_hooks_ran
967 assert not self.hooks_ran
968 for s in self.dependencies:
969 assert not s.processed
970 self._pre_deps_hooks_ran = True
971 for hook in self.pre_deps_hooks:
972 try:
973 start_time = time.time()
974 gclient_utils.CheckCallAndFilterAndHeader(
975 hook, cwd=self.root.root_dir, always=True)
976 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
977 # Use a discrete exit status code of 2 to indicate that a hook action
978 # failed. Users of this script may wish to treat hook action failures
979 # differently from VC failures.
980 print >> sys.stderr, 'Error: %s' % str(e)
981 sys.exit(2)
982 finally:
983 elapsed_time = time.time() - start_time
984 if elapsed_time > 10:
985 print "Hook '%s' took %.2f secs" % (
986 gclient_utils.CommandToStr(hook), elapsed_time)
987
988
maruel@chromium.org0d812442010-08-10 12:41:08 +0000989 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000990 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000991 dependencies = self.dependencies
992 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000993 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000994 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000995 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000996 for i in d.subtree(include_all):
997 yield i
998
999 def depth_first_tree(self):
1000 """Depth-first recursion including the root node."""
1001 yield self
1002 for i in self.dependencies:
1003 for j in i.depth_first_tree():
1004 if j.should_process:
1005 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001006
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001007 @gclient_utils.lockedmethod
1008 def add_dependency(self, new_dep):
1009 self._dependencies.append(new_dep)
1010
1011 @gclient_utils.lockedmethod
1012 def _mark_as_parsed(self, new_hooks):
1013 self._deps_hooks.extend(new_hooks)
1014 self._deps_parsed = True
1015
maruel@chromium.org68988972011-09-20 14:11:42 +00001016 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001017 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001018 def dependencies(self):
1019 return tuple(self._dependencies)
1020
1021 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001022 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001023 def deps_hooks(self):
1024 return tuple(self._deps_hooks)
1025
1026 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001027 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001028 def pre_deps_hooks(self):
1029 return tuple(self._pre_deps_hooks)
1030
1031 @property
1032 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001033 def parsed_url(self):
1034 return self._parsed_url
1035
1036 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001037 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001038 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001039 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001040 return self._deps_parsed
1041
1042 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001043 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001044 def processed(self):
1045 return self._processed
1046
1047 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001048 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001049 def pre_deps_hooks_ran(self):
1050 return self._pre_deps_hooks_ran
1051
1052 @property
1053 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001054 def hooks_ran(self):
1055 return self._hooks_ran
1056
1057 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001058 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001059 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001060 return tuple(self._file_list)
1061
1062 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001063 def used_scm(self):
1064 """SCMWrapper instance for this dependency or None if not processed yet."""
1065 return self._used_scm
1066
1067 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001068 @gclient_utils.lockedmethod
1069 def got_revision(self):
1070 return self._got_revision
1071
1072 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001073 def file_list_and_children(self):
1074 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001075 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001076 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001077 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001078
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001079 def __str__(self):
1080 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001081 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001082 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001083 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001084 # First try the native property if it exists.
1085 if hasattr(self, '_' + i):
1086 value = getattr(self, '_' + i, False)
1087 else:
1088 value = getattr(self, i, False)
1089 if value:
1090 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001091
1092 for d in self.dependencies:
1093 out.extend([' ' + x for x in str(d).splitlines()])
1094 out.append('')
1095 return '\n'.join(out)
1096
1097 def __repr__(self):
1098 return '%s: %s' % (self.name, self.url)
1099
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001100 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001101 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001102 out = '%s(%s)' % (self.name, self.url)
1103 i = self.parent
1104 while i and i.name:
1105 out = '%s(%s) -> %s' % (i.name, i.url, out)
1106 i = i.parent
1107 return out
1108
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001109
1110class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001111 """Object that represent a gclient checkout. A tree of Dependency(), one per
1112 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001113
1114 DEPS_OS_CHOICES = {
1115 "win32": "win",
1116 "win": "win",
1117 "cygwin": "win",
1118 "darwin": "mac",
1119 "mac": "mac",
1120 "unix": "unix",
1121 "linux": "unix",
1122 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001123 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001124 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001125 }
1126
1127 DEFAULT_CLIENT_FILE_TEXT = ("""\
1128solutions = [
1129 { "name" : "%(solution_name)s",
1130 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001131 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001132 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001133 "custom_deps" : {
1134 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001135 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001136 },
1137]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001138cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001139""")
1140
1141 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1142 { "name" : "%(solution_name)s",
1143 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001144 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001145 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001146 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001147%(solution_deps)s },
1148 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001149 },
1150""")
1151
1152 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1153# Snapshot generated with gclient revinfo --snapshot
1154solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001155%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001156""")
1157
1158 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001159 # Do not change previous behavior. Only solution level and immediate DEPS
1160 # are processed.
1161 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001162 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001163 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001164 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001165 if options.deps_os:
1166 enforced_os = options.deps_os.split(',')
1167 else:
1168 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1169 if 'all' in enforced_os:
1170 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001171 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001172 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001173 self.config_content = None
1174
borenet@google.com88d10082014-03-21 17:24:48 +00001175 def _CheckConfig(self):
1176 """Verify that the config matches the state of the existing checked-out
1177 solutions."""
1178 for dep in self.dependencies:
1179 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001180 scm = gclient_scm.CreateSCM(
1181 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001182 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001183 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001184 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001185Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001186is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001187
borenet@google.com97882362014-04-07 20:06:02 +00001188The .gclient file contains:
1189%(expected_url)s (%(expected_scm)s)
1190
1191The local checkout in %(checkout_path)s reports:
1192%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001193
1194You should ensure that the URL listed in .gclient is correct and either change
1195it or fix the checkout. If you're managing your own git checkout in
1196%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1197want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001198''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1199 'expected_url': dep.url,
1200 'expected_scm': gclient_scm.GetScmName(dep.url),
1201 'actual_url': actual_url,
1202 'actual_scm': gclient_scm.GetScmName(actual_url)})
1203
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001204 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001205 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001206 config_dict = {}
1207 self.config_content = content
1208 try:
1209 exec(content, config_dict)
1210 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001211 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001212
peter@chromium.org1efccc82012-04-27 16:34:38 +00001213 # Append any target OS that is not already being enforced to the tuple.
1214 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001215 if config_dict.get('target_os_only', False):
1216 self._enforced_os = tuple(set(target_os))
1217 else:
1218 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1219
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001220 cache_dir = config_dict.get('cache_dir')
1221 if cache_dir:
1222 cache_dir = os.path.join(self.root_dir, cache_dir)
1223 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001224 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001225 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001226 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1227 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001228 gclient_scm.GitWrapper.cache_dir = cache_dir
1229 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001230
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001231 if not target_os and config_dict.get('target_os_only', False):
1232 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1233 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001234
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001235 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001236 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001237 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001238 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001239 self, s['name'], s['url'],
1240 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001241 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001242 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001243 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001244 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001245 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001246 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001247 except KeyError:
1248 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1249 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001250 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1251 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001252
1253 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001254 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001255 self._options.config_filename),
1256 self.config_content)
1257
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001258 def MigrateConfigToGit(self, path, options):
1259 svn_url_re = re.compile('^(https?://src\.chromium\.org/svn|'
1260 'svn://svn\.chromium\.org/chrome)/'
1261 '(trunk|branches/[^/]+)/src')
1262 old_git_re = re.compile('^(https?://git\.chromium\.org|'
1263 'ssh://([a-zA-Z_][a-zA-Z0-9_-]*@)?'
1264 'gerrit\.chromium\.org(:2941[89])?)/'
1265 'chromium/src\.git')
1266 # Scan existing .gclient file for obsolete settings. It would be simpler
1267 # to traverse self.dependencies, but working with the AST allows the code to
1268 # dump an updated .gclient file that preserves the ordering of the original.
1269 a = ast.parse(self.config_content, options.config_filename, 'exec')
1270 modified = False
1271 solutions = [elem for elem in a.body if 'solutions' in
1272 [target.id for target in elem.targets]]
1273 if not solutions:
1274 return self
1275 solutions = solutions[-1]
1276 for solution in solutions.value.elts:
1277 # Check for obsolete URL's
1278 url_idx = ast_dict_index(solution, 'url')
1279 if url_idx == -1:
1280 continue
1281 url_val = solution.values[url_idx]
1282 if type(url_val) is not ast.Str:
1283 continue
1284 if (svn_url_re.match(url_val.s.strip())):
1285 raise gclient_utils.Error(
1286"""
1287The chromium code repository has migrated completely to git.
1288Your SVN-based checkout is now obsolete; you need to create a brand-new
1289git checkout by following these instructions:
1290
1291http://www.chromium.org/developers/how-tos/get-the-code
1292""")
1293 if (old_git_re.match(url_val.s.strip())):
1294 url_val.s = CHROMIUM_SRC_URL
1295 modified = True
1296
szager@chromium.org808bcfb2014-08-24 19:38:43 +00001297 # Ensure deps_file is set to .DEPS.git. We enforce this here to smooth
1298 # over switching between pre-git-migration and post-git-migration
1299 # revisions.
1300 # - For pre-migration revisions, .DEPS.git must be explicitly set.
1301 # - For post-migration revisions, .DEPS.git is not present, so gclient
1302 # will correctly fall back to DEPS.
1303 if url_val.s == CHROMIUM_SRC_URL:
1304 deps_file_idx = ast_dict_index(solution, 'deps_file')
1305 if deps_file_idx != -1:
1306 continue
1307 solution.keys.append(ast.Str('deps_file'))
1308 solution.values.append(ast.Str('.DEPS.git'))
1309 modified = True
1310
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001311 if not modified:
1312 return self
1313
1314 print(
1315"""
1316WARNING: gclient detected an obsolete setting in your %s file. The file has
1317been automagically updated. The previous version is available at %s.old.
1318""" % (options.config_filename, options.config_filename))
1319
1320 # Replace existing .gclient with the updated version.
1321 # Return a new GClient instance based on the new content.
1322 new_content = ast2str(a)
1323 dot_gclient_fn = os.path.join(path, options.config_filename)
iannucci@chromium.orgc7c41682014-08-23 03:44:18 +00001324 try:
1325 os.rename(dot_gclient_fn, dot_gclient_fn + '.old')
1326 except OSError:
1327 pass
1328 with open(dot_gclient_fn, 'w') as fh:
1329 fh.write(new_content)
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001330 client = GClient(path, options)
1331 client.SetConfig(new_content)
1332 return client
1333
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001334 @staticmethod
1335 def LoadCurrentConfig(options):
1336 """Searches for and loads a .gclient file relative to the current working
1337 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001338 if options.spec:
1339 client = GClient('.', options)
1340 client.SetConfig(options.spec)
1341 else:
1342 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1343 if not path:
1344 return None
1345 client = GClient(path, options)
1346 client.SetConfig(gclient_utils.FileRead(
1347 os.path.join(path, options.config_filename)))
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001348 client = client.MigrateConfigToGit(path, options)
maruel@chromium.org69392e72011-10-13 22:09:00 +00001349
1350 if (options.revisions and
1351 len(client.dependencies) > 1 and
1352 any('@' not in r for r in options.revisions)):
1353 print >> sys.stderr, (
1354 'You must specify the full solution name like --revision %s@%s\n'
1355 'when you have multiple solutions setup in your .gclient file.\n'
1356 'Other solutions present are: %s.') % (
1357 client.dependencies[0].name,
1358 options.revisions[0],
1359 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001360 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001361
nsylvain@google.comefc80932011-05-31 21:27:56 +00001362 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001363 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001364 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1365 'solution_name': solution_name,
1366 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001367 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001368 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001369 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001370 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001371 })
1372
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001373 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001374 """Creates a .gclient_entries file to record the list of unique checkouts.
1375
1376 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001377 """
1378 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1379 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001380 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001381 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001382 # Skip over File() dependencies as we can't version them.
1383 if not isinstance(entry.parsed_url, self.FileImpl):
1384 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1385 pprint.pformat(entry.parsed_url))
1386 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001387 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001388 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001389 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001390
1391 def _ReadEntries(self):
1392 """Read the .gclient_entries file for the given client.
1393
1394 Returns:
1395 A sequence of solution names, which will be empty if there is the
1396 entries file hasn't been created yet.
1397 """
1398 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001399 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001400 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001401 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001402 try:
1403 exec(gclient_utils.FileRead(filename), scope)
1404 except SyntaxError, e:
1405 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001406 return scope['entries']
1407
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001408 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001409 """Checks for revision overrides."""
1410 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001411 if self._options.head:
1412 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001413 # Do not check safesync_url if one or more --revision flag is specified.
1414 if not self._options.revisions:
1415 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001416 if not s.managed:
1417 self._options.revisions.append('%s@unmanaged' % s.name)
1418 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001419 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001420 if not self._options.revisions:
1421 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001422 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001423 index = 0
1424 for revision in self._options.revisions:
1425 if not '@' in revision:
1426 # Support for --revision 123
1427 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001428 name, rev = revision.split('@', 1)
1429 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001430 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001431 return revision_overrides
1432
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001433 def _ApplySafeSyncRev(self, dep):
1434 """Finds a valid revision from the content of the safesync_url and apply it
1435 by appending revisions to the revision list. Throws if revision appears to
1436 be invalid for the given |dep|."""
1437 assert len(dep.safesync_url) > 0
1438 handle = urllib.urlopen(dep.safesync_url)
1439 rev = handle.read().strip()
1440 handle.close()
1441 if not rev:
1442 raise gclient_utils.Error(
1443 'It appears your safesync_url (%s) is not working properly\n'
1444 '(as it returned an empty response). Check your config.' %
1445 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001446 scm = gclient_scm.CreateSCM(
1447 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001448 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001449 if self._options.verbose:
1450 print('Using safesync_url revision: %s.\n' % safe_rev)
1451 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1452
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001453 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001454 """Runs a command on each dependency in a client and its dependencies.
1455
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 Args:
1457 command: The command to use (e.g., 'status' or 'diff')
1458 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001459 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001460 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001461 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001462
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001463 revision_overrides = {}
1464 # It's unnecessary to check for revision overrides for 'recurse'.
1465 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001466 if command not in ('diff', 'recurse', 'runhooks', 'status'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001467 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001468 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001469 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001470 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001471 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001472 if command in ('update', 'revert'):
1473 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001474 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001475 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001476 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001477 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1478 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001479 for s in self.dependencies:
1480 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001481 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001482 if revision_overrides:
1483 print >> sys.stderr, ('Please fix your script, having invalid '
1484 '--revision flags will soon considered an error.')
piman@chromium.org6f363722010-04-27 00:41:09 +00001485
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001486 # Once all the dependencies have been processed, it's now safe to run the
1487 # hooks.
1488 if not self._options.nohooks:
1489 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001490
1491 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001492 # Notify the user if there is an orphaned entry in their working copy.
1493 # Only delete the directory if there are no changes in it, and
1494 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001495 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001496 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1497 for e in entries]
1498
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001499 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001500 if not prev_url:
1501 # entry must have been overridden via .gclient custom_deps
1502 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001503 # Fix path separator on Windows.
1504 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001505 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001506
1507 def _IsParentOfAny(parent, path_list):
1508 parent_plus_slash = parent + '/'
1509 return any(
1510 path[:len(parent_plus_slash)] == parent_plus_slash
1511 for path in path_list)
1512
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001513 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001514 if (entry not in entries and
1515 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001516 os.path.exists(e_dir)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001517 scm = gclient_scm.CreateSCM(
1518 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001519
1520 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001521 # The directory might be part of a git OR svn checkout.
1522 scm_root = None
1523 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1524 try:
1525 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1526 except subprocess2.CalledProcessError:
1527 pass
1528 if scm_root:
1529 break
1530 else:
1531 logging.warning('Could not find checkout root for %s. Unable to '
1532 'determine whether it is part of a higher-level '
1533 'checkout, so not removing.' % entry)
1534 continue
1535 if scm_root in full_entries:
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001536 logging.info('%s is part of a higher level checkout, not '
1537 'removing.', scm.GetCheckoutRoot())
1538 continue
1539
1540 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001541 scm.status(self._options, [], file_list)
1542 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001543 if (not self._options.delete_unversioned_trees or
1544 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001545 # There are modified files in this entry. Keep warning until
1546 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001547 print(('\nWARNING: \'%s\' is no longer part of this client. '
1548 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001549 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001550 else:
1551 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001552 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001553 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001554 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001555 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001556 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001557 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001558
1559 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001560 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001561 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001562 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001563 work_queue = gclient_utils.ExecutionQueue(
1564 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001565 for s in self.dependencies:
1566 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001567 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001568
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001569 def GetURLAndRev(dep):
1570 """Returns the revision-qualified SCM url for a Dependency."""
1571 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001572 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001573 if isinstance(dep.parsed_url, self.FileImpl):
1574 original_url = dep.parsed_url.file_location
1575 else:
1576 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001577 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001578 scm = gclient_scm.CreateSCM(
1579 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001580 if not os.path.isdir(scm.checkout_path):
1581 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001582 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001583
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001584 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001585 new_gclient = ''
1586 # First level at .gclient
1587 for d in self.dependencies:
1588 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001589 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001590 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001591 for d in dep.dependencies:
1592 entries[d.name] = GetURLAndRev(d)
1593 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001594 GrabDeps(d)
1595 custom_deps = []
1596 for k in sorted(entries.keys()):
1597 if entries[k]:
1598 # Quotes aren't escaped...
1599 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1600 else:
1601 custom_deps.append(' \"%s\": None,\n' % k)
1602 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1603 'solution_name': d.name,
1604 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001605 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001606 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001607 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001608 'solution_deps': ''.join(custom_deps),
1609 }
1610 # Print the snapshot configuration file
1611 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001612 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001613 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001614 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001615 if self._options.actual:
1616 entries[d.name] = GetURLAndRev(d)
1617 else:
1618 entries[d.name] = d.parsed_url
1619 keys = sorted(entries.keys())
1620 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001621 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001622 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001623
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001624 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001625 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001626 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001627
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001628 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001629 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001630 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001631 return self._root_dir
1632
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001633 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001634 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001635 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001636 return self._enforced_os
1637
maruel@chromium.org68988972011-09-20 14:11:42 +00001638 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001639 def recursion_limit(self):
1640 """How recursive can each dependencies in DEPS file can load DEPS file."""
1641 return self._recursion_limit
1642
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001643 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001644 def try_recursedeps(self):
1645 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001646 return True
1647
1648 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001649 def target_os(self):
1650 return self._enforced_os
1651
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001652
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001653#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001654
1655
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001656def CMDcleanup(parser, args):
1657 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001658
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001659 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1660 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001661 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1662 help='override deps for the specified (comma-separated) '
1663 'platform(s); \'all\' will process all deps_os '
1664 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001665 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001666 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001667 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001668 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001669 if options.verbose:
1670 # Print out the .gclient file. This is longer than if we just printed the
1671 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001672 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001673 return client.RunOnDeps('cleanup', args)
1674
1675
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001676@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001677def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001678 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001679
1680 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001681 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1682 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001683 """
1684 # Stop parsing at the first non-arg so that these go through to the command
1685 parser.disable_interspersed_args()
1686 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001687 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001688 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001689 help='Ignore non-zero return codes from subcommands.')
1690 parser.add_option('--prepend-dir', action='store_true',
1691 help='Prepend relative dir for use with git <cmd> --null.')
1692 parser.add_option('--no-progress', action='store_true',
1693 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001694 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001695 if not args:
1696 print >> sys.stderr, 'Need to supply a command!'
1697 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001698 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1699 if not root_and_entries:
1700 print >> sys.stderr, (
1701 'You need to run gclient sync at least once to use \'recurse\'.\n'
1702 'This is because .gclient_entries needs to exist and be up to date.')
1703 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001704
1705 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001706 scm_set = set()
1707 for scm in options.scm:
1708 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001709 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001710
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001711 options.nohooks = True
1712 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001713 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1714 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001715
1716
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001717@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001718def CMDfetch(parser, args):
1719 """Fetches upstream commits for all modules.
1720
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001721 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1722 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001723 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001724 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001725 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1726
1727
1728def CMDgrep(parser, args):
1729 """Greps through git repos managed by gclient.
1730
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001731 Runs 'git grep [args...]' for each module.
1732 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001733 # We can't use optparse because it will try to parse arguments sent
1734 # to git grep and throw an error. :-(
1735 if not args or re.match('(-h|--help)$', args[0]):
1736 print >> sys.stderr, (
1737 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1738 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1739 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1740 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1741 ' end of your query.'
1742 )
1743 return 1
1744
1745 jobs_arg = ['--jobs=1']
1746 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1747 jobs_arg, args = args[:1], args[1:]
1748 elif re.match(r'(-j|--jobs)$', args[0]):
1749 jobs_arg, args = args[:2], args[2:]
1750
1751 return CMDrecurse(
1752 parser,
1753 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1754 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001755
1756
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001757@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001758def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001759 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001760
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001761 This specifies the configuration for further commands. After update/sync,
1762 top-level DEPS files in each module are read to determine dependent
1763 modules to operate on as well. If optional [url] parameter is
1764 provided, then configuration is read from a specified Subversion server
1765 URL.
1766 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001767 # We do a little dance with the --gclientfile option. 'gclient config' is the
1768 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1769 # arguments. So, we temporarily stash any --gclientfile parameter into
1770 # options.output_config_file until after the (gclientfile xor spec) error
1771 # check.
1772 parser.remove_option('--gclientfile')
1773 parser.add_option('--gclientfile', dest='output_config_file',
1774 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001775 parser.add_option('--name',
1776 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001777 parser.add_option('--deps-file', default='DEPS',
1778 help='overrides the default name for the DEPS file for the'
1779 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001780 parser.add_option('--unmanaged', action='store_true', default=False,
1781 help='overrides the default behavior to make it possible '
1782 'to have the main solution untouched by gclient '
1783 '(gclient will check out unmanaged dependencies but '
1784 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001785 parser.add_option('--cache-dir',
1786 help='(git only) Cache all git repos into this dir and do '
1787 'shared clones from the cache, instead of cloning '
1788 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001789 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001790 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001791 if options.output_config_file:
1792 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001793 if ((options.spec and args) or len(args) > 2 or
1794 (not options.spec and not args)):
1795 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1796
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001797 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001798 if options.spec:
1799 client.SetConfig(options.spec)
1800 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001801 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001802 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001803 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001804 if name.endswith('.git'):
1805 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001806 else:
1807 # specify an alternate relpath for the given URL.
1808 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001809 deps_file = options.deps_file
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001810 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001811 if len(args) > 1:
1812 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001813 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001814 managed=not options.unmanaged,
1815 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001816 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001817 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001818
1819
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001820@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001821 gclient pack > patch.txt
1822 generate simple patch for configured client and dependences
1823""")
1824def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001825 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001826
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001827 Internally, runs 'svn diff'/'git diff' on each checked out module and
1828 dependencies, and performs minimal postprocessing of the output. The
1829 resulting patch is printed to stdout and can be applied to a freshly
1830 checked out tree via 'patch -p0 < patchfile'.
1831 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001832 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1833 help='override deps for the specified (comma-separated) '
1834 'platform(s); \'all\' will process all deps_os '
1835 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001836 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001837 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001838 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001839 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001840 client = GClient.LoadCurrentConfig(options)
1841 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001842 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001843 if options.verbose:
1844 # Print out the .gclient file. This is longer than if we just printed the
1845 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001846 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001847 return client.RunOnDeps('pack', args)
1848
1849
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001850def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001851 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001852 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1853 help='override deps for the specified (comma-separated) '
1854 'platform(s); \'all\' will process all deps_os '
1855 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001856 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001857 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001858 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001859 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001860 if options.verbose:
1861 # Print out the .gclient file. This is longer than if we just printed the
1862 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001863 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001864 return client.RunOnDeps('status', args)
1865
1866
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001867@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001868 gclient sync
1869 update files from SCM according to current configuration,
1870 *for modules which have changed since last update or sync*
1871 gclient sync --force
1872 update files from SCM according to current configuration, for
1873 all modules (useful for recovering files deleted from local copy)
1874 gclient sync --revision src@31000
1875 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001876
1877JSON output format:
1878If the --output-json option is specified, the following document structure will
1879be emitted to the provided file. 'null' entries may occur for subprojects which
1880are present in the gclient solution, but were not processed (due to custom_deps,
1881os_deps, etc.)
1882
1883{
1884 "solutions" : {
1885 "<name>": { # <name> is the posix-normalized path to the solution.
1886 "revision": [<svn rev int>|<git id hex string>|null],
1887 "scm": ["svn"|"git"|null],
1888 }
1889 }
1890}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001891""")
1892def CMDsync(parser, args):
1893 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001894 parser.add_option('-f', '--force', action='store_true',
1895 help='force update even for unchanged modules')
1896 parser.add_option('-n', '--nohooks', action='store_true',
1897 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001898 parser.add_option('-p', '--noprehooks', action='store_true',
1899 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001900 parser.add_option('-r', '--revision', action='append',
1901 dest='revisions', metavar='REV', default=[],
1902 help='Enforces revision/hash for the solutions with the '
1903 'format src@rev. The src@ part is optional and can be '
1904 'skipped. -r can be used multiple times when .gclient '
1905 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001906 'if the src@ part is skipped. Note that specifying '
1907 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001908 parser.add_option('--with_branch_heads', action='store_true',
1909 help='Clone git "branch_heads" refspecs in addition to '
1910 'the default refspecs. This adds about 1/2GB to a '
1911 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001912 parser.add_option('--with_tags', action='store_true',
1913 help='Clone git tags in addition to the default refspecs.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001914 parser.add_option('-t', '--transitive', action='store_true',
1915 help='When a revision is specified (in the DEPS file or '
1916 'with the command-line flag), transitively update '
1917 'the dependencies to the date of the given revision. '
1918 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001919 parser.add_option('-H', '--head', action='store_true',
1920 help='skips any safesync_urls specified in '
1921 'configured solutions and sync to head instead')
1922 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001923 help='Deletes from the working copy any dependencies that '
1924 'have been removed since the last sync, as long as '
1925 'there are no local modifications. When used with '
1926 '--force, such dependencies are removed even if they '
1927 'have local modifications. When used with --reset, '
1928 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001929 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001930 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001931 parser.add_option('-R', '--reset', action='store_true',
1932 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001933 parser.add_option('-M', '--merge', action='store_true',
1934 help='merge upstream changes instead of trying to '
1935 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001936 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1937 help='override deps for the specified (comma-separated) '
1938 'platform(s); \'all\' will process all deps_os '
1939 'references')
1940 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1941 help='Skip svn up whenever possible by requesting '
1942 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001943 parser.add_option('--upstream', action='store_true',
1944 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001945 parser.add_option('--output-json',
1946 help='Output a json document to this path containing '
1947 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001948 parser.add_option('--no-history', action='store_true',
1949 help='GIT ONLY - Reduces the size/time of the checkout at '
1950 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001951 parser.add_option('--shallow', action='store_true',
1952 help='GIT ONLY - Do a shallow clone into the cache dir. '
1953 'Requires Git 1.9+')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001954 parser.add_option('--ignore_locks', action='store_true',
1955 help='GIT ONLY - Ignore cache locks.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001956 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001957 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001958
1959 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001960 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001961
maruel@chromium.org307d1792010-05-31 20:03:13 +00001962 if options.revisions and options.head:
1963 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001964 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001965
1966 if options.verbose:
1967 # Print out the .gclient file. This is longer than if we just printed the
1968 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001969 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001970 ret = client.RunOnDeps('update', args)
1971 if options.output_json:
1972 slns = {}
1973 for d in client.subtree(True):
1974 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1975 slns[normed] = {
1976 'revision': d.got_revision,
1977 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001978 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001979 }
1980 with open(options.output_json, 'wb') as f:
1981 json.dump({'solutions': slns}, f)
1982 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001983
1984
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001985CMDupdate = CMDsync
1986
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001987
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001988def CMDdiff(parser, args):
1989 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001990 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1991 help='override deps for the specified (comma-separated) '
1992 'platform(s); \'all\' will process all deps_os '
1993 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001994 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001995 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001996 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001997 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001998 if options.verbose:
1999 # Print out the .gclient file. This is longer than if we just printed the
2000 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00002001 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002002 return client.RunOnDeps('diff', args)
2003
2004
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002005def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002006 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002007
2008 That's the nuclear option to get back to a 'clean' state. It removes anything
2009 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002010 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2011 help='override deps for the specified (comma-separated) '
2012 'platform(s); \'all\' will process all deps_os '
2013 'references')
2014 parser.add_option('-n', '--nohooks', action='store_true',
2015 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002016 parser.add_option('-p', '--noprehooks', action='store_true',
2017 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002018 parser.add_option('--upstream', action='store_true',
2019 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002020 (options, args) = parser.parse_args(args)
2021 # --force is implied.
2022 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002023 options.reset = False
2024 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002025 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002026 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002027 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002028 return client.RunOnDeps('revert', args)
2029
2030
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002031def CMDrunhooks(parser, args):
2032 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002033 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2034 help='override deps for the specified (comma-separated) '
2035 'platform(s); \'all\' will process all deps_os '
2036 'references')
2037 parser.add_option('-f', '--force', action='store_true', default=True,
2038 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002039 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002040 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002041 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002042 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002043 if options.verbose:
2044 # Print out the .gclient file. This is longer than if we just printed the
2045 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00002046 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002047 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002048 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002049 return client.RunOnDeps('runhooks', args)
2050
2051
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002052def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002053 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002054
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002055 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002056 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002057 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
2058 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002059 commit can change.
2060 """
2061 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2062 help='override deps for the specified (comma-separated) '
2063 'platform(s); \'all\' will process all deps_os '
2064 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002065 parser.add_option('-a', '--actual', action='store_true',
2066 help='gets the actual checked out revisions instead of the '
2067 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002068 parser.add_option('-s', '--snapshot', action='store_true',
2069 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002070 'version of all repositories to reproduce the tree, '
2071 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002072 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002073 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002074 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002075 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002076 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002077 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002078
2079
szager@google.comb9a78d32012-03-13 18:46:21 +00002080def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002081 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002082 (options, args) = parser.parse_args(args)
2083 options.force = True
2084 client = GClient.LoadCurrentConfig(options)
2085 if not client:
2086 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2087 client.RunOnDeps(None, [])
2088 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
2089 return 0
2090
2091
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002092class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002093 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002094
2095 def __init__(self, **kwargs):
2096 optparse.OptionParser.__init__(
2097 self, version='%prog ' + __version__, **kwargs)
2098
2099 # Some arm boards have issues with parallel sync.
2100 if platform.machine().startswith('arm'):
2101 jobs = 1
2102 else:
2103 jobs = max(8, gclient_utils.NumLocalCpus())
2104 # cmp: 2013/06/19
2105 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00002106 # Bypassed if a bot_update flag is detected.
2107 if (os.environ.get('CHROME_HEADLESS') == '1' and
2108 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00002109 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002110
2111 self.add_option(
2112 '-j', '--jobs', default=jobs, type='int',
2113 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002114 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002115 self.add_option(
2116 '-v', '--verbose', action='count', default=0,
2117 help='Produces additional output for diagnostics. Can be used up to '
2118 'three times for more logging info.')
2119 self.add_option(
2120 '--gclientfile', dest='config_filename',
2121 help='Specify an alternate %s file' % self.gclientfile_default)
2122 self.add_option(
2123 '--spec',
2124 help='create a gclient file containing the provided string. Due to '
2125 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2126 self.add_option(
2127 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002128 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002129
2130 def parse_args(self, args=None, values=None):
2131 """Integrates standard options processing."""
2132 options, args = optparse.OptionParser.parse_args(self, args, values)
2133 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2134 logging.basicConfig(
2135 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002136 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002137 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002138 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002139 if (options.config_filename and
2140 options.config_filename != os.path.basename(options.config_filename)):
2141 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002142 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002143 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002144 options.entries_filename = options.config_filename + '_entries'
2145 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002146 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002147
2148 # These hacks need to die.
2149 if not hasattr(options, 'revisions'):
2150 # GClient.RunOnDeps expects it even if not applicable.
2151 options.revisions = []
2152 if not hasattr(options, 'head'):
2153 options.head = None
2154 if not hasattr(options, 'nohooks'):
2155 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002156 if not hasattr(options, 'noprehooks'):
2157 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002158 if not hasattr(options, 'deps_os'):
2159 options.deps_os = None
2160 if not hasattr(options, 'manually_grab_svn_rev'):
2161 options.manually_grab_svn_rev = None
2162 if not hasattr(options, 'force'):
2163 options.force = None
2164 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002165
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002166
2167def disable_buffering():
2168 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2169 # operations. Python as a strong tendency to buffer sys.stdout.
2170 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2171 # Make stdout annotated with the thread ids.
2172 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002173
2174
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002175def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002176 """Doesn't parse the arguments here, just find the right subcommand to
2177 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002178 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00002179 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002180 '\nYour python version %s is unsupported, please upgrade.\n' %
2181 sys.version.split(' ', 1)[0])
2182 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002183 if not sys.executable:
2184 print >> sys.stderr, (
2185 '\nPython cannot find the location of it\'s own executable.\n')
2186 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002187 fix_encoding.fix_encoding()
2188 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00002189 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002190 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002191 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002192 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002193 except KeyboardInterrupt:
2194 gclient_utils.GClientChildren.KillAllRemainingChildren()
2195 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00002196 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002197 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002198 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002199 finally:
2200 gclient_utils.PrintWarnings()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002201
2202
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002203if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002204 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002205
2206# vim: ts=2:sw=2:tw=80:et: