blob: 407b9ad4e99f1efd3f31270f3695e18f458b1fcb [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#
58# Specifying a target OS
59# An optional key named "target_os" may be added to a gclient file to specify
60# one or more additional operating systems that should be considered when
61# processing the deps_os dict of a DEPS file.
62#
63# Example:
64# target_os = [ "android" ]
65#
66# If the "target_os_only" key is also present and true, then *only* the
67# operating systems listed in "target_os" will be used.
68#
69# Example:
70# target_os = [ "ios" ]
71# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@chromium.org39c0b222013-08-17 16:57:01 +000073__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000074
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000075import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000076import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import optparse
78import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000079import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000080import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000081import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000083import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000084import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000086import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000088import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000089
maruel@chromium.org35625c72011-03-23 17:34:02 +000090import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000091import gclient_scm
92import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000093from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +000094import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000095import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000096from third_party import colorama
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
98
maruel@chromium.org116704f2010-06-11 17:34:38 +000099class GClientKeywords(object):
100 class FromImpl(object):
101 """Used to implement the From() syntax."""
102
103 def __init__(self, module_name, sub_target_name=None):
104 """module_name is the dep module we want to include from. It can also be
105 the name of a subdirectory to include from.
106
107 sub_target_name is an optional parameter if the module name in the other
108 DEPS file is different. E.g., you might want to map src/net to net."""
109 self.module_name = module_name
110 self.sub_target_name = sub_target_name
111
112 def __str__(self):
113 return 'From(%s, %s)' % (repr(self.module_name),
114 repr(self.sub_target_name))
115
maruel@chromium.org116704f2010-06-11 17:34:38 +0000116 class FileImpl(object):
117 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000118 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000119
120 def __init__(self, file_location):
121 self.file_location = file_location
122
123 def __str__(self):
124 return 'File("%s")' % self.file_location
125
126 def GetPath(self):
127 return os.path.split(self.file_location)[0]
128
129 def GetFilename(self):
130 rev_tokens = self.file_location.split('@')
131 return os.path.split(rev_tokens[0])[1]
132
133 def GetRevision(self):
134 rev_tokens = self.file_location.split('@')
135 if len(rev_tokens) > 1:
136 return rev_tokens[1]
137 return None
138
139 class VarImpl(object):
140 def __init__(self, custom_vars, local_scope):
141 self._custom_vars = custom_vars
142 self._local_scope = local_scope
143
144 def Lookup(self, var_name):
145 """Implements the Var syntax."""
146 if var_name in self._custom_vars:
147 return self._custom_vars[var_name]
148 elif var_name in self._local_scope.get("vars", {}):
149 return self._local_scope["vars"][var_name]
150 raise gclient_utils.Error("Var is not defined: %s" % var_name)
151
152
maruel@chromium.org064186c2011-09-27 23:53:33 +0000153class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000154 """Immutable configuration settings."""
155 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000156 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000157 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000158 GClientKeywords.__init__(self)
159
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000160 # These are not mutable:
161 self._parent = parent
162 self._safesync_url = safesync_url
163 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000165 # 'managed' determines whether or not this dependency is synced/updated by
166 # gclient after gclient checks it out initially. The difference between
167 # 'managed' and 'should_process' is that the user specifies 'managed' via
168 # the --unmanaged command-line flag or a .gclient config, where
169 # 'should_process' is dynamically set by gclient if it goes over its
170 # recursion limit and controls gclient's behavior so it does not misbehave.
171 self._managed = managed
172 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000173 # This is a mutable value that overrides the normal recursion limit for this
174 # dependency. It is read from the actual DEPS file so cannot be set on
175 # class instantiation.
176 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000177 # This is a mutable value which has the list of 'target_os' OSes listed in
178 # the current deps file.
179 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000180
181 # These are only set in .gclient and not in DEPS files.
182 self._custom_vars = custom_vars or {}
183 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000184 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000185
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000186 # TODO(iannucci): Remove this when all masters are correctly substituting
187 # the new blink url.
188 if (self._custom_vars.get('webkit_trunk', '') ==
189 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000190 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
191 print 'Overwriting Var("webkit_trunk") with %s' % new_url
192 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000193
maruel@chromium.org064186c2011-09-27 23:53:33 +0000194 # Post process the url to remove trailing slashes.
195 if isinstance(self._url, basestring):
196 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
197 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000198 self._url = self._url.replace('/@', '@')
199 elif not isinstance(self._url,
200 (self.FromImpl, self.FileImpl, None.__class__)):
201 raise gclient_utils.Error(
202 ('dependency url must be either a string, None, '
203 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000204 # Make any deps_file path platform-appropriate.
205 for sep in ['/', '\\']:
206 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000207
208 @property
209 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000210 return self._deps_file
211
212 @property
213 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 return self._managed
215
216 @property
217 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218 return self._parent
219
220 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000221 def root(self):
222 """Returns the root node, a GClient object."""
223 if not self.parent:
224 # This line is to signal pylint that it could be a GClient instance.
225 return self or GClient(None, None)
226 return self.parent.root
227
228 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000229 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000230 return self._safesync_url
231
232 @property
233 def should_process(self):
234 """True if this dependency should be processed, i.e. checked out."""
235 return self._should_process
236
237 @property
238 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 return self._custom_vars.copy()
240
241 @property
242 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 return self._custom_deps.copy()
244
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000246 def custom_hooks(self):
247 return self._custom_hooks[:]
248
249 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000250 def url(self):
251 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000252
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000253 @property
254 def recursion_limit(self):
255 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000256 if self.recursion_override is not None:
257 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000258 return max(self.parent.recursion_limit - 1, 0)
259
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000260 @property
261 def target_os(self):
262 if self.local_target_os is not None:
263 return tuple(set(self.local_target_os).union(self.parent.target_os))
264 else:
265 return self.parent.target_os
266
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000267 def get_custom_deps(self, name, url):
268 """Returns a custom deps if applicable."""
269 if self.parent:
270 url = self.parent.get_custom_deps(name, url)
271 # None is a valid return value to disable a dependency.
272 return self.custom_deps.get(name, url)
273
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274
275class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000276 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000277
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000278 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000279 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000280 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000282 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000283 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000284
285 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000286 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000287
288 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000289 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000290 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000291 # A cache of the files affected by the current operation, necessary for
292 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000293 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000294 # If it is not set to True, the dependency wasn't processed for its child
295 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000296 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000297 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000298 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000299 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000300 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000301 # This is the scm used to checkout self.url. It may be used by dependencies
302 # to get the datetime of the revision we checked out.
303 self._used_scm = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000304
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000305 if not self.name and self.parent:
306 raise gclient_utils.Error('Dependency without name')
307
maruel@chromium.org470b5432011-10-11 18:18:19 +0000308 @property
309 def requirements(self):
310 """Calculate the list of requirements."""
311 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000312 # self.parent is implicitly a requirement. This will be recursive by
313 # definition.
314 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000315 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000316
317 # For a tree with at least 2 levels*, the leaf node needs to depend
318 # on the level higher up in an orderly way.
319 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
320 # thus unsorted, while the .gclient format is a list thus sorted.
321 #
322 # * _recursion_limit is hard coded 2 and there is no hope to change this
323 # value.
324 #
325 # Interestingly enough, the following condition only works in the case we
326 # want: self is a 2nd level node. 3nd level node wouldn't need this since
327 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000328 if self.parent and self.parent.parent and not self.parent.parent.parent:
329 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000330
331 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000332 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000333
maruel@chromium.org470b5432011-10-11 18:18:19 +0000334 if self.name:
335 requirements |= set(
336 obj.name for obj in self.root.subtree(False)
337 if (obj is not self
338 and obj.name and
339 self.name.startswith(posixpath.join(obj.name, ''))))
340 requirements = tuple(sorted(requirements))
341 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
342 return requirements
343
344 def verify_validity(self):
345 """Verifies that this Dependency is fine to add as a child of another one.
346
347 Returns True if this entry should be added, False if it is a duplicate of
348 another entry.
349 """
350 logging.info('Dependency(%s).verify_validity()' % self.name)
351 if self.name in [s.name for s in self.parent.dependencies]:
352 raise gclient_utils.Error(
353 'The same name "%s" appears multiple times in the deps section' %
354 self.name)
355 if not self.should_process:
356 # Return early, no need to set requirements.
357 return True
358
359 # This require a full tree traversal with locks.
360 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
361 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000362 self_url = self.LateOverride(self.url)
363 sibling_url = sibling.LateOverride(sibling.url)
364 # Allow to have only one to be None or ''.
365 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000366 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000367 ('Dependency %s specified more than once:\n'
368 ' %s [%s]\n'
369 'vs\n'
370 ' %s [%s]') % (
371 self.name,
372 sibling.hierarchy(),
373 sibling_url,
374 self.hierarchy(),
375 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000376 # In theory we could keep it as a shadow of the other one. In
377 # practice, simply ignore it.
378 logging.warn('Won\'t process duplicate dependency %s' % sibling)
379 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000380 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000381
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000383 """Resolves the parsed url from url.
384
385 Manages From() keyword accordingly. Do not touch self.parsed_url nor
386 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000387 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000388 parsed_url = self.get_custom_deps(self.name, url)
389 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000390 logging.info(
391 'Dependency(%s).LateOverride(%s) -> %s' %
392 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000393 return parsed_url
394
395 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000396 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000397 ref = [
398 dep for dep in self.root.subtree(True) if url.module_name == dep.name
399 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000400 if not ref:
401 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
402 url.module_name, ref))
403 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000404 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000405 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000406 found_deps = [d for d in ref.dependencies if d.name == sub_target]
407 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000408 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000409 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
410 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000411 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000412
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000413 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000414 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000415 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000416 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000417 'Dependency(%s).LateOverride(%s) -> %s (From)' %
418 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000419 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000420
421 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000422 parsed_url = urlparse.urlparse(url)
423 if not parsed_url[0]:
424 # A relative url. Fetch the real base.
425 path = parsed_url[2]
426 if not path.startswith('/'):
427 raise gclient_utils.Error(
428 'relative DEPS entry \'%s\' must begin with a slash' % url)
429 # Create a scm just to query the full url.
430 parent_url = self.parent.parsed_url
431 if isinstance(parent_url, self.FileImpl):
432 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000433 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000434 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000435 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000436 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000437 logging.info(
438 'Dependency(%s).LateOverride(%s) -> %s' %
439 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000440 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000441
442 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000443 logging.info(
444 'Dependency(%s).LateOverride(%s) -> %s (File)' %
445 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000446 return url
447
448 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 logging.info(
450 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000451 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000452
453 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000454
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000455 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000456 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000457 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000458 assert not self.dependencies
459 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000460 local_scope = {}
461 var = self.VarImpl(self.custom_vars, local_scope)
462 global_scope = {
463 'File': self.FileImpl,
464 'From': self.FromImpl,
465 'Var': var.Lookup,
466 'deps_os': {},
467 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000468 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000469 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000470 logging.info(
471 'ParseDepsFile(%s): No %s file found at %s' % (
472 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000473 else:
474 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000475 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000476 # Eval the content.
477 try:
478 exec(deps_content, global_scope, local_scope)
479 except SyntaxError, e:
480 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000481 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000482 if 'recursion' in local_scope:
483 self.recursion_override = local_scope.get('recursion')
484 logging.warning(
485 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000486 # If present, save 'target_os' in the local_target_os property.
487 if 'target_os' in local_scope:
488 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000489 # load os specific dependencies if defined. these dependencies may
490 # override or extend the values defined by the 'deps' member.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000491 target_os_deps = {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000492 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000493 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000494 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000495 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000496 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000497 # platform, so we collect the broadest set of dependencies
498 # available. We may end up with the wrong revision of something for
499 # our platform, but this is the best we can do.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000500 target_os_deps.update(
501 [x for x in os_deps.items() if not x[0] in target_os_deps])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000502 else:
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000503 target_os_deps.update(os_deps)
504
505 # deps_os overrides paths from deps
506 deps.update(target_os_deps)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000507
maruel@chromium.org271375b2010-06-23 19:17:38 +0000508 # If a line is in custom_deps, but not in the solution, we want to append
509 # this line to the solution.
510 for d in self.custom_deps:
511 if d not in deps:
512 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000513
514 # If use_relative_paths is set in the DEPS file, regenerate
515 # the dictionary using paths relative to the directory containing
516 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000517 use_relative_paths = local_scope.get('use_relative_paths', False)
518 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000519 rel_deps = {}
520 for d, url in deps.items():
521 # normpath is required to allow DEPS to use .. in their
522 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000523 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
524 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000525
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000527 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000528 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000529 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000530 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000531 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000532 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000533 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000534
535 # override named sets of hooks by the custom hooks
536 hooks_to_run = []
537 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
538 for hook in local_scope.get('hooks', []):
539 if hook.get('name', '') not in hook_names_to_suppress:
540 hooks_to_run.append(hook)
541
542 # add the replacements and any additions
543 for hook in self.custom_hooks:
544 if 'action' in hook:
545 hooks_to_run.append(hook)
546
547 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000548 logging.info('ParseDepsFile(%s) done' % self.name)
549
550 def add_dependencies_and_close(self, deps_to_add, hooks):
551 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000552 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000553 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000554 self.add_dependency(dep)
555 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000556
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000557 def maybeGetParentRevision(
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000558 self, command, options, parsed_url, parent_name, revision_overrides):
559 """Uses revision/timestamp of parent if no explicit revision was specified.
560
561 If we are performing an update and --transitive is set, use
562 - the parent's revision if 'self.url' is in the same repository
563 - the parent's timestamp otherwise
564 to update 'self.url'. The used revision/timestamp will be set in
565 'options.revision'.
566 If we have an explicit revision do nothing.
567 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000568 if command == 'update' and options.transitive and not options.revision:
569 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
570 if not revision:
571 options.revision = revision_overrides.get(parent_name)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000572 if (options.revision and
573 not gclient_utils.IsDateRevision(options.revision)):
574 assert self.parent and self.parent.used_scm
575 # If this dependency is in the same repository as parent it's url will
576 # start with a slash. If so we take the parent revision instead of
577 # it's timestamp.
578 # (The timestamps of commits in google code are broken -- which can
579 # result in dependencies to be checked out at the wrong revision)
580 if self.url.startswith('/'):
581 if options.verbose:
582 print('Using parent\'s revision %s since we are in the same '
583 'repository.' % options.revision)
584 else:
585 parent_revision_date = self.parent.used_scm.GetRevisionDate(
586 options.revision)
587 options.revision = gclient_utils.MakeDateRevision(
588 parent_revision_date)
589 if options.verbose:
590 print('Using parent\'s revision date %s since we are in a '
591 'different repository.' % options.revision)
592 revision_overrides[self.name] = options.revision
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000593
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000594 # Arguments number differs from overridden method
595 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000596 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000597 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000598 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000599 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000600 if not self.should_process:
601 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000602 # When running runhooks, there's no need to consult the SCM.
603 # All known hooks are expected to run unconditionally regardless of working
604 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000605 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000606 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000607 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000608 if run_scm and parsed_url:
609 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000610 # Special support for single-file checkout.
611 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000612 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
613 # pylint: disable=E1103
614 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000615 self._used_scm = gclient_scm.SVNWrapper(
616 parsed_url.GetPath(), self.root.root_dir, self.name)
617 self._used_scm.RunCommand('updatesingle',
618 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000619 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000620 # Create a shallow copy to mutate revision.
621 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000622 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000623 self.maybeGetParentRevision(
624 command, options, parsed_url, self.parent.name, revision_overrides)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000625 self._used_scm = gclient_scm.CreateSCM(
626 parsed_url, self.root.root_dir, self.name)
627 self._used_scm.RunCommand(command, options, args, file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000628 if file_list:
629 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000630
631 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
632 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000633 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000634 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000635 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000636 continue
637 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000638 [self.root.root_dir.lower(), file_list[i].lower()])
639 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000640 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000641 while file_list[i].startswith(('\\', '/')):
642 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000643
644 # Always parse the DEPS file.
645 self.ParseDepsFile()
646
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000647 self._run_is_done(file_list or [], parsed_url)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000648
649 if self.recursion_limit:
650 # Parse the dependencies of this dependency.
651 for s in self.dependencies:
652 work_queue.enqueue(s)
653
654 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000655 if not isinstance(parsed_url, self.FileImpl):
656 # Skip file only checkout.
657 scm = gclient_scm.GetScmName(parsed_url)
658 if not options.scm or scm in options.scm:
659 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
660 # Pass in the SCM type as an env variable
661 env = os.environ.copy()
662 if scm:
663 env['GCLIENT_SCM'] = scm
664 if parsed_url:
665 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000666 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000667 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000668 print_stdout = False
669 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000670 """Git-specific path marshaling. It is optimized for git-grep."""
671
672 def mod_path(git_pathspec):
673 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
674 modified_path = os.path.join(self.name, match.group(2))
675 branch = match.group(1) or ''
676 return '%s%s' % (branch, modified_path)
677
678 match = re.match('^Binary file ([^\0]+) matches$', line)
679 if match:
680 print 'Binary file %s matches' % mod_path(match.group(1))
681 return
682
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000683 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000684 if len(items) == 2 and items[1]:
685 print '%s : %s' % (mod_path(items[0]), items[1])
686 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000687 # Multiple null bytes or a single trailing null byte indicate
688 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000689 print '\n'.join(mod_path(path) for path in items if path)
690 else:
691 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000692 else:
693 print_stdout = True
694 filter_fn = None
695
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000696 if parsed_url is None:
697 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
698 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000699 try:
700 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000701 args, cwd=cwd, env=env, print_stdout=print_stdout,
702 filter_fn=filter_fn,
703 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000704 except subprocess2.CalledProcessError:
705 if not options.ignore:
706 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000707 else:
708 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000709
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000710
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000711 @gclient_utils.lockedmethod
712 def _run_is_done(self, file_list, parsed_url):
713 # Both these are kept for hooks that are run as a separate tree traversal.
714 self._file_list = file_list
715 self._parsed_url = parsed_url
716 self._processed = True
717
szager@google.comb9a78d32012-03-13 18:46:21 +0000718 @staticmethod
719 def GetHookAction(hook_dict, matching_file_list):
720 """Turns a parsed 'hook' dict into an executable command."""
721 logging.debug(hook_dict)
722 logging.debug(matching_file_list)
723 command = hook_dict['action'][:]
724 if command[0] == 'python':
725 # If the hook specified "python" as the first item, the action is a
726 # Python script. Run it by starting a new copy of the same
727 # interpreter.
728 command[0] = sys.executable
729 if '$matching_files' in command:
730 splice_index = command.index('$matching_files')
731 command[splice_index:splice_index + 1] = matching_file_list
732 return command
733
734 def GetHooks(self, options):
735 """Evaluates all hooks, and return them in a flat list.
736
737 RunOnDeps() must have been called before to load the DEPS.
738 """
739 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000740 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000741 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000742 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000743 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000744 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000745 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000746 # TODO(maruel): If the user is using git or git-svn, then we don't know
747 # what files have changed so we always run all hooks. It'd be nice to fix
748 # that.
749 if (options.force or
750 isinstance(self.parsed_url, self.FileImpl) or
751 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000752 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000753 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000754 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000755 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000756 # Run hooks on the basis of whether the files from the gclient operation
757 # match each hook's pattern.
758 for hook_dict in self.deps_hooks:
759 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000760 matching_file_list = [
761 f for f in self.file_list_and_children if pattern.search(f)
762 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000763 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000764 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000765 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000766 result.extend(s.GetHooks(options))
767 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000768
szager@google.comb9a78d32012-03-13 18:46:21 +0000769 def RunHooksRecursively(self, options):
770 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000771 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000772 for hook in self.GetHooks(options):
773 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000774 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000775 gclient_utils.CheckCallAndFilterAndHeader(
776 hook, cwd=self.root.root_dir, always=True)
777 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
778 # Use a discrete exit status code of 2 to indicate that a hook action
779 # failed. Users of this script may wish to treat hook action failures
780 # differently from VC failures.
781 print >> sys.stderr, 'Error: %s' % str(e)
782 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000783 finally:
784 elapsed_time = time.time() - start_time
785 if elapsed_time > 10:
786 print "Hook '%s' took %.2f secs" % (
787 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000788
maruel@chromium.org0d812442010-08-10 12:41:08 +0000789 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000790 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000791 dependencies = self.dependencies
792 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000793 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000794 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000795 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000796 for i in d.subtree(include_all):
797 yield i
798
799 def depth_first_tree(self):
800 """Depth-first recursion including the root node."""
801 yield self
802 for i in self.dependencies:
803 for j in i.depth_first_tree():
804 if j.should_process:
805 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000806
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000807 @gclient_utils.lockedmethod
808 def add_dependency(self, new_dep):
809 self._dependencies.append(new_dep)
810
811 @gclient_utils.lockedmethod
812 def _mark_as_parsed(self, new_hooks):
813 self._deps_hooks.extend(new_hooks)
814 self._deps_parsed = True
815
maruel@chromium.org68988972011-09-20 14:11:42 +0000816 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000817 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000818 def dependencies(self):
819 return tuple(self._dependencies)
820
821 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000822 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000823 def deps_hooks(self):
824 return tuple(self._deps_hooks)
825
826 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000827 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000828 def parsed_url(self):
829 return self._parsed_url
830
831 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000832 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000833 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000834 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000835 return self._deps_parsed
836
837 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000838 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000839 def processed(self):
840 return self._processed
841
842 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000843 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000844 def hooks_ran(self):
845 return self._hooks_ran
846
847 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000848 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000849 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000850 return tuple(self._file_list)
851
852 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000853 def used_scm(self):
854 """SCMWrapper instance for this dependency or None if not processed yet."""
855 return self._used_scm
856
857 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000858 def file_list_and_children(self):
859 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000860 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000861 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000862 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000863
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000864 def __str__(self):
865 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000866 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000867 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000868 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000869 # First try the native property if it exists.
870 if hasattr(self, '_' + i):
871 value = getattr(self, '_' + i, False)
872 else:
873 value = getattr(self, i, False)
874 if value:
875 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000876
877 for d in self.dependencies:
878 out.extend([' ' + x for x in str(d).splitlines()])
879 out.append('')
880 return '\n'.join(out)
881
882 def __repr__(self):
883 return '%s: %s' % (self.name, self.url)
884
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000885 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000886 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000887 out = '%s(%s)' % (self.name, self.url)
888 i = self.parent
889 while i and i.name:
890 out = '%s(%s) -> %s' % (i.name, i.url, out)
891 i = i.parent
892 return out
893
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000894
895class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000896 """Object that represent a gclient checkout. A tree of Dependency(), one per
897 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000898
899 DEPS_OS_CHOICES = {
900 "win32": "win",
901 "win": "win",
902 "cygwin": "win",
903 "darwin": "mac",
904 "mac": "mac",
905 "unix": "unix",
906 "linux": "unix",
907 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000908 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000909 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000910 }
911
912 DEFAULT_CLIENT_FILE_TEXT = ("""\
913solutions = [
914 { "name" : "%(solution_name)s",
915 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000916 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000917 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000918 "custom_deps" : {
919 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000920 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000921 },
922]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000923cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000924""")
925
926 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
927 { "name" : "%(solution_name)s",
928 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000929 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000930 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000931 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000932%(solution_deps)s },
933 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000934 },
935""")
936
937 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
938# Snapshot generated with gclient revinfo --snapshot
939solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000940%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000941""")
942
943 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000944 # Do not change previous behavior. Only solution level and immediate DEPS
945 # are processed.
946 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000947 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000948 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000949 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000950 if options.deps_os:
951 enforced_os = options.deps_os.split(',')
952 else:
953 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
954 if 'all' in enforced_os:
955 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000956 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000957 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000958 self.config_content = None
959
960 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000961 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000962 config_dict = {}
963 self.config_content = content
964 try:
965 exec(content, config_dict)
966 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000967 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000968
peter@chromium.org1efccc82012-04-27 16:34:38 +0000969 # Append any target OS that is not already being enforced to the tuple.
970 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000971 if config_dict.get('target_os_only', False):
972 self._enforced_os = tuple(set(target_os))
973 else:
974 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
975
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000976 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
977
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000978 if not target_os and config_dict.get('target_os_only', False):
979 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
980 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000981
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000982 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000983 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000984 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000985 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000986 self, s['name'], s['url'],
987 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000988 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000989 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000990 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000991 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000992 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000993 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000994 except KeyError:
995 raise gclient_utils.Error('Invalid .gclient file. Solution is '
996 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000997 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
998 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000999
1000 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001001 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001002 self._options.config_filename),
1003 self.config_content)
1004
1005 @staticmethod
1006 def LoadCurrentConfig(options):
1007 """Searches for and loads a .gclient file relative to the current working
1008 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001009 if options.spec:
1010 client = GClient('.', options)
1011 client.SetConfig(options.spec)
1012 else:
1013 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1014 if not path:
1015 return None
1016 client = GClient(path, options)
1017 client.SetConfig(gclient_utils.FileRead(
1018 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001019
1020 if (options.revisions and
1021 len(client.dependencies) > 1 and
1022 any('@' not in r for r in options.revisions)):
1023 print >> sys.stderr, (
1024 'You must specify the full solution name like --revision %s@%s\n'
1025 'when you have multiple solutions setup in your .gclient file.\n'
1026 'Other solutions present are: %s.') % (
1027 client.dependencies[0].name,
1028 options.revisions[0],
1029 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001030 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001031
nsylvain@google.comefc80932011-05-31 21:27:56 +00001032 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001033 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001034 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1035 'solution_name': solution_name,
1036 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001037 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001038 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001039 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001040 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001041 })
1042
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001043 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001044 """Creates a .gclient_entries file to record the list of unique checkouts.
1045
1046 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001047 """
1048 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1049 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001050 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001051 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001052 # Skip over File() dependencies as we can't version them.
1053 if not isinstance(entry.parsed_url, self.FileImpl):
1054 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1055 pprint.pformat(entry.parsed_url))
1056 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001057 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001058 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001059 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001060
1061 def _ReadEntries(self):
1062 """Read the .gclient_entries file for the given client.
1063
1064 Returns:
1065 A sequence of solution names, which will be empty if there is the
1066 entries file hasn't been created yet.
1067 """
1068 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001069 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001070 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001071 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001072 try:
1073 exec(gclient_utils.FileRead(filename), scope)
1074 except SyntaxError, e:
1075 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001076 return scope['entries']
1077
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001078 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001079 """Checks for revision overrides."""
1080 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001081 if self._options.head:
1082 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001083 # Do not check safesync_url if one or more --revision flag is specified.
1084 if not self._options.revisions:
1085 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001086 if not s.managed:
1087 self._options.revisions.append('%s@unmanaged' % s.name)
1088 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001089 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001090 if not self._options.revisions:
1091 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001092 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001093 index = 0
1094 for revision in self._options.revisions:
1095 if not '@' in revision:
1096 # Support for --revision 123
1097 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001098 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001099 if not sol in solutions_names:
1100 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1101 print >> sys.stderr, ('Please fix your script, having invalid '
1102 '--revision flags will soon considered an error.')
1103 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001104 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001105 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001106 return revision_overrides
1107
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001108 def _ApplySafeSyncRev(self, dep):
1109 """Finds a valid revision from the content of the safesync_url and apply it
1110 by appending revisions to the revision list. Throws if revision appears to
1111 be invalid for the given |dep|."""
1112 assert len(dep.safesync_url) > 0
1113 handle = urllib.urlopen(dep.safesync_url)
1114 rev = handle.read().strip()
1115 handle.close()
1116 if not rev:
1117 raise gclient_utils.Error(
1118 'It appears your safesync_url (%s) is not working properly\n'
1119 '(as it returned an empty response). Check your config.' %
1120 dep.safesync_url)
1121 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001122 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001123 if self._options.verbose:
1124 print('Using safesync_url revision: %s.\n' % safe_rev)
1125 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1126
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001127 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001128 """Runs a command on each dependency in a client and its dependencies.
1129
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001130 Args:
1131 command: The command to use (e.g., 'status' or 'diff')
1132 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001134 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001135 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001136 revision_overrides = {}
1137 # It's unnecessary to check for revision overrides for 'recurse'.
1138 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001139 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001140 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001141 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001142 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001143 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001144 if command in ('update', 'revert'):
1145 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001146 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001147 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001148 work_queue = gclient_utils.ExecutionQueue(
1149 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001150 for s in self.dependencies:
1151 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001152 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001153
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001154 # Once all the dependencies have been processed, it's now safe to run the
1155 # hooks.
1156 if not self._options.nohooks:
1157 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158
1159 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001160 # Notify the user if there is an orphaned entry in their working copy.
1161 # Only delete the directory if there are no changes in it, and
1162 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001163 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001164 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1165 for e in entries]
1166
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001167 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001168 if not prev_url:
1169 # entry must have been overridden via .gclient custom_deps
1170 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001171 # Fix path separator on Windows.
1172 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001173 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001174
1175 def _IsParentOfAny(parent, path_list):
1176 parent_plus_slash = parent + '/'
1177 return any(
1178 path[:len(parent_plus_slash)] == parent_plus_slash
1179 for path in path_list)
1180
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001181 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001182 if (entry not in entries and
1183 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001184 os.path.exists(e_dir)):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001185 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001186
1187 # Check to see if this directory is now part of a higher-up checkout.
1188 if scm.GetCheckoutRoot() in full_entries:
1189 logging.info('%s is part of a higher level checkout, not '
1190 'removing.', scm.GetCheckoutRoot())
1191 continue
1192
1193 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001194 scm.status(self._options, [], file_list)
1195 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001196 if (not self._options.delete_unversioned_trees or
1197 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001198 # There are modified files in this entry. Keep warning until
1199 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001200 print(('\nWARNING: \'%s\' is no longer part of this client. '
1201 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001202 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001203 else:
1204 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001205 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001206 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001207 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001208 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001209 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001210 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001211
1212 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001213 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001214 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001215 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001216 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001217 for s in self.dependencies:
1218 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001219 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001220
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001221 def GetURLAndRev(dep):
1222 """Returns the revision-qualified SCM url for a Dependency."""
1223 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001224 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001225 if isinstance(dep.parsed_url, self.FileImpl):
1226 original_url = dep.parsed_url.file_location
1227 else:
1228 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001229 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001230 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001231 if not os.path.isdir(scm.checkout_path):
1232 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001233 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001234
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001235 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001236 new_gclient = ''
1237 # First level at .gclient
1238 for d in self.dependencies:
1239 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001240 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001241 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001242 for d in dep.dependencies:
1243 entries[d.name] = GetURLAndRev(d)
1244 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001245 GrabDeps(d)
1246 custom_deps = []
1247 for k in sorted(entries.keys()):
1248 if entries[k]:
1249 # Quotes aren't escaped...
1250 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1251 else:
1252 custom_deps.append(' \"%s\": None,\n' % k)
1253 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1254 'solution_name': d.name,
1255 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001256 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001257 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001258 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001259 'solution_deps': ''.join(custom_deps),
1260 }
1261 # Print the snapshot configuration file
1262 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001263 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001264 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001265 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001266 if self._options.actual:
1267 entries[d.name] = GetURLAndRev(d)
1268 else:
1269 entries[d.name] = d.parsed_url
1270 keys = sorted(entries.keys())
1271 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001272 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001273 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001274
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001275 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001276 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001277 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001278
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001279 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001280 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001281 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001282 return self._root_dir
1283
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001284 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001285 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001286 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001287 return self._enforced_os
1288
maruel@chromium.org68988972011-09-20 14:11:42 +00001289 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001290 def recursion_limit(self):
1291 """How recursive can each dependencies in DEPS file can load DEPS file."""
1292 return self._recursion_limit
1293
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001294 @property
1295 def target_os(self):
1296 return self._enforced_os
1297
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001298
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001299#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001300
1301
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001302def CMDcleanup(parser, args):
1303 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001304
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001305 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1306 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001307 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1308 help='override deps for the specified (comma-separated) '
1309 'platform(s); \'all\' will process all deps_os '
1310 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001311 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001312 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001313 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001314 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001315 if options.verbose:
1316 # Print out the .gclient file. This is longer than if we just printed the
1317 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001318 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001319 return client.RunOnDeps('cleanup', args)
1320
1321
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001322@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001323def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001324 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001325
1326 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001327 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1328 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001329 """
1330 # Stop parsing at the first non-arg so that these go through to the command
1331 parser.disable_interspersed_args()
1332 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001333 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001334 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001335 help='Ignore non-zero return codes from subcommands.')
1336 parser.add_option('--prepend-dir', action='store_true',
1337 help='Prepend relative dir for use with git <cmd> --null.')
1338 parser.add_option('--no-progress', action='store_true',
1339 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001340 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001341 if not args:
1342 print >> sys.stderr, 'Need to supply a command!'
1343 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001344 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1345 if not root_and_entries:
1346 print >> sys.stderr, (
1347 'You need to run gclient sync at least once to use \'recurse\'.\n'
1348 'This is because .gclient_entries needs to exist and be up to date.')
1349 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001350
1351 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001352 scm_set = set()
1353 for scm in options.scm:
1354 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001355 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001356
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001357 options.nohooks = True
1358 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001359 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1360 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001361
1362
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001363@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001364def CMDfetch(parser, args):
1365 """Fetches upstream commits for all modules.
1366
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001367 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1368 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001369 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001370 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001371 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1372
1373
1374def CMDgrep(parser, args):
1375 """Greps through git repos managed by gclient.
1376
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001377 Runs 'git grep [args...]' for each module.
1378 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001379 # We can't use optparse because it will try to parse arguments sent
1380 # to git grep and throw an error. :-(
1381 if not args or re.match('(-h|--help)$', args[0]):
1382 print >> sys.stderr, (
1383 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1384 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1385 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1386 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1387 ' end of your query.'
1388 )
1389 return 1
1390
1391 jobs_arg = ['--jobs=1']
1392 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1393 jobs_arg, args = args[:1], args[1:]
1394 elif re.match(r'(-j|--jobs)$', args[0]):
1395 jobs_arg, args = args[:2], args[2:]
1396
1397 return CMDrecurse(
1398 parser,
1399 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1400 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001401
1402
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001403@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001404def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001405 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001406
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001407 This specifies the configuration for further commands. After update/sync,
1408 top-level DEPS files in each module are read to determine dependent
1409 modules to operate on as well. If optional [url] parameter is
1410 provided, then configuration is read from a specified Subversion server
1411 URL.
1412 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001413 # We do a little dance with the --gclientfile option. 'gclient config' is the
1414 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1415 # arguments. So, we temporarily stash any --gclientfile parameter into
1416 # options.output_config_file until after the (gclientfile xor spec) error
1417 # check.
1418 parser.remove_option('--gclientfile')
1419 parser.add_option('--gclientfile', dest='output_config_file',
1420 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001421 parser.add_option('--name',
1422 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001423 parser.add_option('--deps-file', default='DEPS',
1424 help='overrides the default name for the DEPS file for the'
1425 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001426 parser.add_option('--unmanaged', action='store_true', default=False,
1427 help='overrides the default behavior to make it possible '
1428 'to have the main solution untouched by gclient '
1429 '(gclient will check out unmanaged dependencies but '
1430 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001431 parser.add_option('--git-deps', action='store_true',
1432 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001433 parser.add_option('--cache-dir',
1434 help='(git only) Cache all git repos into this dir and do '
1435 'shared clones from the cache, instead of cloning '
1436 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001437 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001438 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001439 if options.output_config_file:
1440 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001441 if ((options.spec and args) or len(args) > 2 or
1442 (not options.spec and not args)):
1443 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1444
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001445 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001446 if options.spec:
1447 client.SetConfig(options.spec)
1448 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001449 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001450 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001451 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001452 if name.endswith('.git'):
1453 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001454 else:
1455 # specify an alternate relpath for the given URL.
1456 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001457 deps_file = options.deps_file
1458 if options.git_deps:
1459 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001460 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001461 if len(args) > 1:
1462 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001463 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001464 managed=not options.unmanaged,
1465 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001466 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001467 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001468
1469
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001470@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001471 gclient pack > patch.txt
1472 generate simple patch for configured client and dependences
1473""")
1474def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001475 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001476
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001477 Internally, runs 'svn diff'/'git diff' on each checked out module and
1478 dependencies, and performs minimal postprocessing of the output. The
1479 resulting patch is printed to stdout and can be applied to a freshly
1480 checked out tree via 'patch -p0 < patchfile'.
1481 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001482 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1483 help='override deps for the specified (comma-separated) '
1484 'platform(s); \'all\' will process all deps_os '
1485 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001486 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001487 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001488 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001489 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001490 client = GClient.LoadCurrentConfig(options)
1491 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001492 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001493 if options.verbose:
1494 # Print out the .gclient file. This is longer than if we just printed the
1495 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001496 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001497 return client.RunOnDeps('pack', args)
1498
1499
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001500def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001501 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001502 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1503 help='override deps for the specified (comma-separated) '
1504 'platform(s); \'all\' will process all deps_os '
1505 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001506 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001507 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001508 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001509 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001510 if options.verbose:
1511 # Print out the .gclient file. This is longer than if we just printed the
1512 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001513 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001514 return client.RunOnDeps('status', args)
1515
1516
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001517@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001518 gclient sync
1519 update files from SCM according to current configuration,
1520 *for modules which have changed since last update or sync*
1521 gclient sync --force
1522 update files from SCM according to current configuration, for
1523 all modules (useful for recovering files deleted from local copy)
1524 gclient sync --revision src@31000
1525 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001526""")
1527def CMDsync(parser, args):
1528 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001529 parser.add_option('-f', '--force', action='store_true',
1530 help='force update even for unchanged modules')
1531 parser.add_option('-n', '--nohooks', action='store_true',
1532 help='don\'t run hooks after the update is complete')
1533 parser.add_option('-r', '--revision', action='append',
1534 dest='revisions', metavar='REV', default=[],
1535 help='Enforces revision/hash for the solutions with the '
1536 'format src@rev. The src@ part is optional and can be '
1537 'skipped. -r can be used multiple times when .gclient '
1538 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001539 'if the src@ part is skipped. Note that specifying '
1540 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001541 parser.add_option('--with_branch_heads', action='store_true',
1542 help='Clone git "branch_heads" refspecs in addition to '
1543 'the default refspecs. This adds about 1/2GB to a '
1544 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001545 parser.add_option('-t', '--transitive', action='store_true',
1546 help='When a revision is specified (in the DEPS file or '
1547 'with the command-line flag), transitively update '
1548 'the dependencies to the date of the given revision. '
1549 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001550 parser.add_option('-H', '--head', action='store_true',
1551 help='skips any safesync_urls specified in '
1552 'configured solutions and sync to head instead')
1553 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001554 help='Deletes from the working copy any dependencies that '
1555 'have been removed since the last sync, as long as '
1556 'there are no local modifications. When used with '
1557 '--force, such dependencies are removed even if they '
1558 'have local modifications. When used with --reset, '
1559 'all untracked directories are removed from the '
1560 'working copy, exclusing those which are explicitly '
1561 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001562 parser.add_option('-R', '--reset', action='store_true',
1563 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001564 parser.add_option('-M', '--merge', action='store_true',
1565 help='merge upstream changes instead of trying to '
1566 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001567 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1568 help='override deps for the specified (comma-separated) '
1569 'platform(s); \'all\' will process all deps_os '
1570 'references')
1571 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1572 help='Skip svn up whenever possible by requesting '
1573 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001574 parser.add_option('--upstream', action='store_true',
1575 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001576 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001577 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001578
1579 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001580 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001581
maruel@chromium.org307d1792010-05-31 20:03:13 +00001582 if options.revisions and options.head:
1583 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001584 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001585
1586 if options.verbose:
1587 # Print out the .gclient file. This is longer than if we just printed the
1588 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001589 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001590 return client.RunOnDeps('update', args)
1591
1592
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001593CMDupdate = CMDsync
1594
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001595
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001596def CMDdiff(parser, args):
1597 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001598 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1599 help='override deps for the specified (comma-separated) '
1600 'platform(s); \'all\' will process all deps_os '
1601 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001602 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001603 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001604 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001605 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001606 if options.verbose:
1607 # Print out the .gclient file. This is longer than if we just printed the
1608 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001609 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001610 return client.RunOnDeps('diff', args)
1611
1612
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001613def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001614 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001615
1616 That's the nuclear option to get back to a 'clean' state. It removes anything
1617 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001618 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1619 help='override deps for the specified (comma-separated) '
1620 'platform(s); \'all\' will process all deps_os '
1621 'references')
1622 parser.add_option('-n', '--nohooks', action='store_true',
1623 help='don\'t run hooks after the revert is complete')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001624 parser.add_option('--upstream', action='store_true',
1625 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001626 (options, args) = parser.parse_args(args)
1627 # --force is implied.
1628 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001629 options.reset = False
1630 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001631 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001632 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001633 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001634 return client.RunOnDeps('revert', args)
1635
1636
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001637def CMDrunhooks(parser, args):
1638 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001639 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1640 help='override deps for the specified (comma-separated) '
1641 'platform(s); \'all\' will process all deps_os '
1642 'references')
1643 parser.add_option('-f', '--force', action='store_true', default=True,
1644 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001645 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001646 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001647 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001648 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001649 if options.verbose:
1650 # Print out the .gclient file. This is longer than if we just printed the
1651 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001652 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001653 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001654 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001655 return client.RunOnDeps('runhooks', args)
1656
1657
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001658def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001659 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001660
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001661 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001662 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001663 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1664 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001665 commit can change.
1666 """
1667 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1668 help='override deps for the specified (comma-separated) '
1669 'platform(s); \'all\' will process all deps_os '
1670 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001671 parser.add_option('-a', '--actual', action='store_true',
1672 help='gets the actual checked out revisions instead of the '
1673 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001674 parser.add_option('-s', '--snapshot', action='store_true',
1675 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001676 'version of all repositories to reproduce the tree, '
1677 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001678 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001679 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001680 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001681 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001682 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001683 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001684
1685
szager@google.comb9a78d32012-03-13 18:46:21 +00001686def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001687 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001688 (options, args) = parser.parse_args(args)
1689 options.force = True
1690 client = GClient.LoadCurrentConfig(options)
1691 if not client:
1692 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1693 client.RunOnDeps(None, [])
1694 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1695 return 0
1696
1697
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001698class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00001699 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001700
1701 def __init__(self, **kwargs):
1702 optparse.OptionParser.__init__(
1703 self, version='%prog ' + __version__, **kwargs)
1704
1705 # Some arm boards have issues with parallel sync.
1706 if platform.machine().startswith('arm'):
1707 jobs = 1
1708 else:
1709 jobs = max(8, gclient_utils.NumLocalCpus())
1710 # cmp: 2013/06/19
1711 # Temporary workaround to lower bot-load on SVN server.
1712 if os.environ.get('CHROME_HEADLESS') == '1':
xusydoc@chromium.org05028412013-07-29 13:40:10 +00001713 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001714
1715 self.add_option(
1716 '-j', '--jobs', default=jobs, type='int',
1717 help='Specify how many SCM commands can run in parallel; defaults to '
1718 'number of cpu cores (%default)')
1719 self.add_option(
1720 '-v', '--verbose', action='count', default=0,
1721 help='Produces additional output for diagnostics. Can be used up to '
1722 'three times for more logging info.')
1723 self.add_option(
1724 '--gclientfile', dest='config_filename',
1725 help='Specify an alternate %s file' % self.gclientfile_default)
1726 self.add_option(
1727 '--spec',
1728 help='create a gclient file containing the provided string. Due to '
1729 'Cygwin/Python brokenness, it can\'t contain any newlines.')
1730 self.add_option(
1731 '--no-nag-max', default=False, action='store_true',
1732 help='If a subprocess runs for too long without generating terminal '
1733 'output, generate warnings, but do not kill the process.')
1734
1735 def parse_args(self, args=None, values=None):
1736 """Integrates standard options processing."""
1737 options, args = optparse.OptionParser.parse_args(self, args, values)
1738 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
1739 logging.basicConfig(
1740 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00001741 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001742 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001743 self.error('Cannot specifiy both --gclientfile and --spec')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001744 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001745 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001746 options.entries_filename = options.config_filename + '_entries'
1747 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001748 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001749
1750 # These hacks need to die.
1751 if not hasattr(options, 'revisions'):
1752 # GClient.RunOnDeps expects it even if not applicable.
1753 options.revisions = []
1754 if not hasattr(options, 'head'):
1755 options.head = None
1756 if not hasattr(options, 'nohooks'):
1757 options.nohooks = True
1758 if not hasattr(options, 'deps_os'):
1759 options.deps_os = None
1760 if not hasattr(options, 'manually_grab_svn_rev'):
1761 options.manually_grab_svn_rev = None
1762 if not hasattr(options, 'force'):
1763 options.force = None
szager@chromium.org41da24b2013-05-23 19:37:04 +00001764 if options.no_nag_max:
1765 gclient_scm.SCMWrapper.nag_max = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00001766 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001767
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001768
1769def disable_buffering():
1770 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1771 # operations. Python as a strong tendency to buffer sys.stdout.
1772 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
1773 # Make stdout annotated with the thread ids.
1774 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001775
1776
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001777def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001778 """Doesn't parse the arguments here, just find the right subcommand to
1779 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001780 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001781 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001782 '\nYour python version %s is unsupported, please upgrade.\n' %
1783 sys.version.split(' ', 1)[0])
1784 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001785 if not sys.executable:
1786 print >> sys.stderr, (
1787 '\nPython cannot find the location of it\'s own executable.\n')
1788 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001789 fix_encoding.fix_encoding()
1790 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001791 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001792 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001793 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001794 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001795 except KeyboardInterrupt:
1796 gclient_utils.GClientChildren.KillAllRemainingChildren()
1797 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001798 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001799 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001800 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001801
1802
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001803if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001804 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001805
1806# vim: ts=2:sw=2:tw=80:et: