blob: 506439630438004a437d34037d3427a8b12d3486 [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
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000076import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000077import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import optparse
79import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000080import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000081import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000082import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000083import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000085import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000086import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000087import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000088
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000089import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000090
maruel@chromium.org35625c72011-03-23 17:34:02 +000091import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000092import gclient_scm
93import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000094from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +000095import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000096import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000097from third_party import colorama
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000098
99
maruel@chromium.org116704f2010-06-11 17:34:38 +0000100class GClientKeywords(object):
101 class FromImpl(object):
102 """Used to implement the From() syntax."""
103
104 def __init__(self, module_name, sub_target_name=None):
105 """module_name is the dep module we want to include from. It can also be
106 the name of a subdirectory to include from.
107
108 sub_target_name is an optional parameter if the module name in the other
109 DEPS file is different. E.g., you might want to map src/net to net."""
110 self.module_name = module_name
111 self.sub_target_name = sub_target_name
112
113 def __str__(self):
114 return 'From(%s, %s)' % (repr(self.module_name),
115 repr(self.sub_target_name))
116
maruel@chromium.org116704f2010-06-11 17:34:38 +0000117 class FileImpl(object):
118 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000119 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000120
121 def __init__(self, file_location):
122 self.file_location = file_location
123
124 def __str__(self):
125 return 'File("%s")' % self.file_location
126
127 def GetPath(self):
128 return os.path.split(self.file_location)[0]
129
130 def GetFilename(self):
131 rev_tokens = self.file_location.split('@')
132 return os.path.split(rev_tokens[0])[1]
133
134 def GetRevision(self):
135 rev_tokens = self.file_location.split('@')
136 if len(rev_tokens) > 1:
137 return rev_tokens[1]
138 return None
139
140 class VarImpl(object):
141 def __init__(self, custom_vars, local_scope):
142 self._custom_vars = custom_vars
143 self._local_scope = local_scope
144
145 def Lookup(self, var_name):
146 """Implements the Var syntax."""
147 if var_name in self._custom_vars:
148 return self._custom_vars[var_name]
149 elif var_name in self._local_scope.get("vars", {}):
150 return self._local_scope["vars"][var_name]
151 raise gclient_utils.Error("Var is not defined: %s" % var_name)
152
153
maruel@chromium.org064186c2011-09-27 23:53:33 +0000154class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000155 """Immutable configuration settings."""
156 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000157 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000158 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000159 GClientKeywords.__init__(self)
160
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000161 # These are not mutable:
162 self._parent = parent
163 self._safesync_url = safesync_url
164 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000165 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000166 # 'managed' determines whether or not this dependency is synced/updated by
167 # gclient after gclient checks it out initially. The difference between
168 # 'managed' and 'should_process' is that the user specifies 'managed' via
169 # the --unmanaged command-line flag or a .gclient config, where
170 # 'should_process' is dynamically set by gclient if it goes over its
171 # recursion limit and controls gclient's behavior so it does not misbehave.
172 self._managed = managed
173 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000174 # This is a mutable value that overrides the normal recursion limit for this
175 # dependency. It is read from the actual DEPS file so cannot be set on
176 # class instantiation.
177 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000178 # This is a mutable value which has the list of 'target_os' OSes listed in
179 # the current deps file.
180 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000181
182 # These are only set in .gclient and not in DEPS files.
183 self._custom_vars = custom_vars or {}
184 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000185 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000187 # TODO(iannucci): Remove this when all masters are correctly substituting
188 # the new blink url.
189 if (self._custom_vars.get('webkit_trunk', '') ==
190 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000191 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
192 print 'Overwriting Var("webkit_trunk") with %s' % new_url
193 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000194
maruel@chromium.org064186c2011-09-27 23:53:33 +0000195 # Post process the url to remove trailing slashes.
196 if isinstance(self._url, basestring):
197 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
198 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000199 self._url = self._url.replace('/@', '@')
200 elif not isinstance(self._url,
201 (self.FromImpl, self.FileImpl, None.__class__)):
202 raise gclient_utils.Error(
203 ('dependency url must be either a string, None, '
204 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000205 # Make any deps_file path platform-appropriate.
206 for sep in ['/', '\\']:
207 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000208
209 @property
210 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 return self._deps_file
212
213 @property
214 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215 return self._managed
216
217 @property
218 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000219 return self._parent
220
221 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000222 def root(self):
223 """Returns the root node, a GClient object."""
224 if not self.parent:
225 # This line is to signal pylint that it could be a GClient instance.
226 return self or GClient(None, None)
227 return self.parent.root
228
229 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000230 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000231 return self._safesync_url
232
233 @property
234 def should_process(self):
235 """True if this dependency should be processed, i.e. checked out."""
236 return self._should_process
237
238 @property
239 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 return self._custom_vars.copy()
241
242 @property
243 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244 return self._custom_deps.copy()
245
maruel@chromium.org064186c2011-09-27 23:53:33 +0000246 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000247 def custom_hooks(self):
248 return self._custom_hooks[:]
249
250 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000251 def url(self):
252 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000253
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000254 @property
255 def recursion_limit(self):
256 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000257 if self.recursion_override is not None:
258 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000259 return max(self.parent.recursion_limit - 1, 0)
260
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000261 @property
262 def target_os(self):
263 if self.local_target_os is not None:
264 return tuple(set(self.local_target_os).union(self.parent.target_os))
265 else:
266 return self.parent.target_os
267
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000268 def get_custom_deps(self, name, url):
269 """Returns a custom deps if applicable."""
270 if self.parent:
271 url = self.parent.get_custom_deps(name, url)
272 # None is a valid return value to disable a dependency.
273 return self.custom_deps.get(name, url)
274
maruel@chromium.org064186c2011-09-27 23:53:33 +0000275
276class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000277 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000278
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000279 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000280 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000281 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000282 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000283 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000284 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000285
286 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000287 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000288
289 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000290 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000291 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000292 # A cache of the files affected by the current operation, necessary for
293 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000294 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000295 # If it is not set to True, the dependency wasn't processed for its child
296 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000297 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000298 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000299 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000300 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000301 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000302 # This is the scm used to checkout self.url. It may be used by dependencies
303 # to get the datetime of the revision we checked out.
304 self._used_scm = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000305 # The actual revision we ended up getting, or None if that information is
306 # unavailable
307 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000308
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000309 if not self.name and self.parent:
310 raise gclient_utils.Error('Dependency without name')
311
maruel@chromium.org470b5432011-10-11 18:18:19 +0000312 @property
313 def requirements(self):
314 """Calculate the list of requirements."""
315 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000316 # self.parent is implicitly a requirement. This will be recursive by
317 # definition.
318 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000319 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000320
321 # For a tree with at least 2 levels*, the leaf node needs to depend
322 # on the level higher up in an orderly way.
323 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
324 # thus unsorted, while the .gclient format is a list thus sorted.
325 #
326 # * _recursion_limit is hard coded 2 and there is no hope to change this
327 # value.
328 #
329 # Interestingly enough, the following condition only works in the case we
330 # want: self is a 2nd level node. 3nd level node wouldn't need this since
331 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000332 if self.parent and self.parent.parent and not self.parent.parent.parent:
333 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000334
335 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000336 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000337
maruel@chromium.org470b5432011-10-11 18:18:19 +0000338 if self.name:
339 requirements |= set(
340 obj.name for obj in self.root.subtree(False)
341 if (obj is not self
342 and obj.name and
343 self.name.startswith(posixpath.join(obj.name, ''))))
344 requirements = tuple(sorted(requirements))
345 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
346 return requirements
347
348 def verify_validity(self):
349 """Verifies that this Dependency is fine to add as a child of another one.
350
351 Returns True if this entry should be added, False if it is a duplicate of
352 another entry.
353 """
354 logging.info('Dependency(%s).verify_validity()' % self.name)
355 if self.name in [s.name for s in self.parent.dependencies]:
356 raise gclient_utils.Error(
357 'The same name "%s" appears multiple times in the deps section' %
358 self.name)
359 if not self.should_process:
360 # Return early, no need to set requirements.
361 return True
362
363 # This require a full tree traversal with locks.
364 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
365 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000366 self_url = self.LateOverride(self.url)
367 sibling_url = sibling.LateOverride(sibling.url)
368 # Allow to have only one to be None or ''.
369 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000370 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000371 ('Dependency %s specified more than once:\n'
372 ' %s [%s]\n'
373 'vs\n'
374 ' %s [%s]') % (
375 self.name,
376 sibling.hierarchy(),
377 sibling_url,
378 self.hierarchy(),
379 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000380 # In theory we could keep it as a shadow of the other one. In
381 # practice, simply ignore it.
382 logging.warn('Won\'t process duplicate dependency %s' % sibling)
383 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000384 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000385
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000386 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000387 """Resolves the parsed url from url.
388
389 Manages From() keyword accordingly. Do not touch self.parsed_url nor
390 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000391 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000392 parsed_url = self.get_custom_deps(self.name, url)
393 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000394 logging.info(
395 'Dependency(%s).LateOverride(%s) -> %s' %
396 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000397 return parsed_url
398
399 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000400 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000401 ref = [
402 dep for dep in self.root.subtree(True) if url.module_name == dep.name
403 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000404 if not ref:
405 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
406 url.module_name, ref))
407 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000408 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000409 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000410 found_deps = [d for d in ref.dependencies if d.name == sub_target]
411 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000412 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000413 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
414 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000415 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000416
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000417 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000418 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000419 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000420 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000421 'Dependency(%s).LateOverride(%s) -> %s (From)' %
422 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000423 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000424
425 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000426 parsed_url = urlparse.urlparse(url)
427 if not parsed_url[0]:
428 # A relative url. Fetch the real base.
429 path = parsed_url[2]
430 if not path.startswith('/'):
431 raise gclient_utils.Error(
432 'relative DEPS entry \'%s\' must begin with a slash' % url)
433 # Create a scm just to query the full url.
434 parent_url = self.parent.parsed_url
435 if isinstance(parent_url, self.FileImpl):
436 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000437 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000438 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000439 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000440 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000441 logging.info(
442 'Dependency(%s).LateOverride(%s) -> %s' %
443 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000444 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000445
446 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000447 logging.info(
448 'Dependency(%s).LateOverride(%s) -> %s (File)' %
449 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000450 return url
451
452 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000453 logging.info(
454 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000455 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000456
457 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000458
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000459 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000460 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000461 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000462 assert not self.dependencies
463 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000464 local_scope = {}
465 var = self.VarImpl(self.custom_vars, local_scope)
466 global_scope = {
467 'File': self.FileImpl,
468 'From': self.FromImpl,
469 'Var': var.Lookup,
470 'deps_os': {},
471 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000472 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000473 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000474 logging.info(
475 'ParseDepsFile(%s): No %s file found at %s' % (
476 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000477 else:
478 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000479 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000480 # Eval the content.
481 try:
482 exec(deps_content, global_scope, local_scope)
483 except SyntaxError, e:
484 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000485 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000486 if 'recursion' in local_scope:
487 self.recursion_override = local_scope.get('recursion')
488 logging.warning(
489 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000490 # If present, save 'target_os' in the local_target_os property.
491 if 'target_os' in local_scope:
492 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000493 # load os specific dependencies if defined. these dependencies may
494 # override or extend the values defined by the 'deps' member.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000495 target_os_deps = {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000496 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000497 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000498 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000499 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000500 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000501 # platform, so we collect the broadest set of dependencies
502 # available. We may end up with the wrong revision of something for
503 # our platform, but this is the best we can do.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000504 target_os_deps.update(
505 [x for x in os_deps.items() if not x[0] in target_os_deps])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000506 else:
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000507 target_os_deps.update(os_deps)
508
509 # deps_os overrides paths from deps
510 deps.update(target_os_deps)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000511
maruel@chromium.org271375b2010-06-23 19:17:38 +0000512 # If a line is in custom_deps, but not in the solution, we want to append
513 # this line to the solution.
514 for d in self.custom_deps:
515 if d not in deps:
516 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000517
518 # If use_relative_paths is set in the DEPS file, regenerate
519 # the dictionary using paths relative to the directory containing
520 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000521 use_relative_paths = local_scope.get('use_relative_paths', False)
522 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000523 rel_deps = {}
524 for d, url in deps.items():
525 # normpath is required to allow DEPS to use .. in their
526 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000527 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
528 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000529
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000530 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000531 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000532 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000533 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000534 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000535 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000536 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000537 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000538
539 # override named sets of hooks by the custom hooks
540 hooks_to_run = []
541 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
542 for hook in local_scope.get('hooks', []):
543 if hook.get('name', '') not in hook_names_to_suppress:
544 hooks_to_run.append(hook)
545
546 # add the replacements and any additions
547 for hook in self.custom_hooks:
548 if 'action' in hook:
549 hooks_to_run.append(hook)
550
551 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000552 logging.info('ParseDepsFile(%s) done' % self.name)
553
554 def add_dependencies_and_close(self, deps_to_add, hooks):
555 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000556 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000557 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000558 self.add_dependency(dep)
559 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000560
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000561 def maybeGetParentRevision(
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000562 self, command, options, parsed_url, parent_name, revision_overrides):
563 """Uses revision/timestamp of parent if no explicit revision was specified.
564
565 If we are performing an update and --transitive is set, use
566 - the parent's revision if 'self.url' is in the same repository
567 - the parent's timestamp otherwise
568 to update 'self.url'. The used revision/timestamp will be set in
569 'options.revision'.
570 If we have an explicit revision do nothing.
571 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000572 if command == 'update' and options.transitive and not options.revision:
573 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
574 if not revision:
575 options.revision = revision_overrides.get(parent_name)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000576 if (options.revision and
577 not gclient_utils.IsDateRevision(options.revision)):
578 assert self.parent and self.parent.used_scm
579 # If this dependency is in the same repository as parent it's url will
580 # start with a slash. If so we take the parent revision instead of
581 # it's timestamp.
582 # (The timestamps of commits in google code are broken -- which can
583 # result in dependencies to be checked out at the wrong revision)
584 if self.url.startswith('/'):
585 if options.verbose:
586 print('Using parent\'s revision %s since we are in the same '
587 'repository.' % options.revision)
588 else:
589 parent_revision_date = self.parent.used_scm.GetRevisionDate(
590 options.revision)
591 options.revision = gclient_utils.MakeDateRevision(
592 parent_revision_date)
593 if options.verbose:
594 print('Using parent\'s revision date %s since we are in a '
595 'different repository.' % options.revision)
596 revision_overrides[self.name] = options.revision
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000597
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000598 # Arguments number differs from overridden method
599 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000600 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000601 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000602 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000603 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000604 if not self.should_process:
605 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000606 # When running runhooks, there's no need to consult the SCM.
607 # All known hooks are expected to run unconditionally regardless of working
608 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000609 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000610 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000611 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000612 if run_scm and parsed_url:
613 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000614 # Special support for single-file checkout.
615 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000616 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
617 # pylint: disable=E1103
618 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000619 self._used_scm = gclient_scm.SVNWrapper(
620 parsed_url.GetPath(), self.root.root_dir, self.name)
621 self._used_scm.RunCommand('updatesingle',
622 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000623 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000624 # Create a shallow copy to mutate revision.
625 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000626 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000627 self.maybeGetParentRevision(
628 command, options, parsed_url, self.parent.name, revision_overrides)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000629 self._used_scm = gclient_scm.CreateSCM(
630 parsed_url, self.root.root_dir, self.name)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000631 self._got_revision = self._used_scm.RunCommand(command, options, args,
632 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000633 if file_list:
634 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000635
636 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
637 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000638 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000639 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000640 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000641 continue
642 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000643 [self.root.root_dir.lower(), file_list[i].lower()])
644 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000645 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000646 while file_list[i].startswith(('\\', '/')):
647 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000648
649 # Always parse the DEPS file.
650 self.ParseDepsFile()
651
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000652 self._run_is_done(file_list or [], parsed_url)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000653
654 if self.recursion_limit:
655 # Parse the dependencies of this dependency.
656 for s in self.dependencies:
657 work_queue.enqueue(s)
658
659 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000660 if not isinstance(parsed_url, self.FileImpl):
661 # Skip file only checkout.
662 scm = gclient_scm.GetScmName(parsed_url)
663 if not options.scm or scm in options.scm:
664 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
665 # Pass in the SCM type as an env variable
666 env = os.environ.copy()
667 if scm:
668 env['GCLIENT_SCM'] = scm
669 if parsed_url:
670 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000671 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000672 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000673 print_stdout = False
674 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000675 """Git-specific path marshaling. It is optimized for git-grep."""
676
677 def mod_path(git_pathspec):
678 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
679 modified_path = os.path.join(self.name, match.group(2))
680 branch = match.group(1) or ''
681 return '%s%s' % (branch, modified_path)
682
683 match = re.match('^Binary file ([^\0]+) matches$', line)
684 if match:
685 print 'Binary file %s matches' % mod_path(match.group(1))
686 return
687
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000688 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000689 if len(items) == 2 and items[1]:
690 print '%s : %s' % (mod_path(items[0]), items[1])
691 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000692 # Multiple null bytes or a single trailing null byte indicate
693 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000694 print '\n'.join(mod_path(path) for path in items if path)
695 else:
696 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000697 else:
698 print_stdout = True
699 filter_fn = None
700
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000701 if parsed_url is None:
702 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
703 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000704 try:
705 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000706 args, cwd=cwd, env=env, print_stdout=print_stdout,
707 filter_fn=filter_fn,
708 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000709 except subprocess2.CalledProcessError:
710 if not options.ignore:
711 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000712 else:
713 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000714
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000715
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000716 @gclient_utils.lockedmethod
717 def _run_is_done(self, file_list, parsed_url):
718 # Both these are kept for hooks that are run as a separate tree traversal.
719 self._file_list = file_list
720 self._parsed_url = parsed_url
721 self._processed = True
722
szager@google.comb9a78d32012-03-13 18:46:21 +0000723 @staticmethod
724 def GetHookAction(hook_dict, matching_file_list):
725 """Turns a parsed 'hook' dict into an executable command."""
726 logging.debug(hook_dict)
727 logging.debug(matching_file_list)
728 command = hook_dict['action'][:]
729 if command[0] == 'python':
730 # If the hook specified "python" as the first item, the action is a
731 # Python script. Run it by starting a new copy of the same
732 # interpreter.
733 command[0] = sys.executable
734 if '$matching_files' in command:
735 splice_index = command.index('$matching_files')
736 command[splice_index:splice_index + 1] = matching_file_list
737 return command
738
739 def GetHooks(self, options):
740 """Evaluates all hooks, and return them in a flat list.
741
742 RunOnDeps() must have been called before to load the DEPS.
743 """
744 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000745 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000746 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000747 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000748 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000749 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000750 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000751 # TODO(maruel): If the user is using git or git-svn, then we don't know
752 # what files have changed so we always run all hooks. It'd be nice to fix
753 # that.
754 if (options.force or
755 isinstance(self.parsed_url, self.FileImpl) or
756 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000757 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000758 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000759 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000760 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000761 # Run hooks on the basis of whether the files from the gclient operation
762 # match each hook's pattern.
763 for hook_dict in self.deps_hooks:
764 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000765 matching_file_list = [
766 f for f in self.file_list_and_children if pattern.search(f)
767 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000768 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000769 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000770 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000771 result.extend(s.GetHooks(options))
772 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000773
szager@google.comb9a78d32012-03-13 18:46:21 +0000774 def RunHooksRecursively(self, options):
775 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000776 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000777 for hook in self.GetHooks(options):
778 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000779 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000780 gclient_utils.CheckCallAndFilterAndHeader(
781 hook, cwd=self.root.root_dir, always=True)
782 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
783 # Use a discrete exit status code of 2 to indicate that a hook action
784 # failed. Users of this script may wish to treat hook action failures
785 # differently from VC failures.
786 print >> sys.stderr, 'Error: %s' % str(e)
787 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000788 finally:
789 elapsed_time = time.time() - start_time
790 if elapsed_time > 10:
791 print "Hook '%s' took %.2f secs" % (
792 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000793
maruel@chromium.org0d812442010-08-10 12:41:08 +0000794 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000795 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000796 dependencies = self.dependencies
797 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000798 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000799 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000800 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000801 for i in d.subtree(include_all):
802 yield i
803
804 def depth_first_tree(self):
805 """Depth-first recursion including the root node."""
806 yield self
807 for i in self.dependencies:
808 for j in i.depth_first_tree():
809 if j.should_process:
810 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000811
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000812 @gclient_utils.lockedmethod
813 def add_dependency(self, new_dep):
814 self._dependencies.append(new_dep)
815
816 @gclient_utils.lockedmethod
817 def _mark_as_parsed(self, new_hooks):
818 self._deps_hooks.extend(new_hooks)
819 self._deps_parsed = True
820
maruel@chromium.org68988972011-09-20 14:11:42 +0000821 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000822 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000823 def dependencies(self):
824 return tuple(self._dependencies)
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 deps_hooks(self):
829 return tuple(self._deps_hooks)
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 parsed_url(self):
834 return self._parsed_url
835
836 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000837 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000838 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000839 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000840 return self._deps_parsed
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 processed(self):
845 return self._processed
846
847 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000848 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000849 def hooks_ran(self):
850 return self._hooks_ran
851
852 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000854 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000855 return tuple(self._file_list)
856
857 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000858 def used_scm(self):
859 """SCMWrapper instance for this dependency or None if not processed yet."""
860 return self._used_scm
861
862 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000863 @gclient_utils.lockedmethod
864 def got_revision(self):
865 return self._got_revision
866
867 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000868 def file_list_and_children(self):
869 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000870 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000871 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000872 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000873
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000874 def __str__(self):
875 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000876 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000877 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000878 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000879 # First try the native property if it exists.
880 if hasattr(self, '_' + i):
881 value = getattr(self, '_' + i, False)
882 else:
883 value = getattr(self, i, False)
884 if value:
885 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000886
887 for d in self.dependencies:
888 out.extend([' ' + x for x in str(d).splitlines()])
889 out.append('')
890 return '\n'.join(out)
891
892 def __repr__(self):
893 return '%s: %s' % (self.name, self.url)
894
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000895 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000896 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000897 out = '%s(%s)' % (self.name, self.url)
898 i = self.parent
899 while i and i.name:
900 out = '%s(%s) -> %s' % (i.name, i.url, out)
901 i = i.parent
902 return out
903
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000904
905class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000906 """Object that represent a gclient checkout. A tree of Dependency(), one per
907 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000908
909 DEPS_OS_CHOICES = {
910 "win32": "win",
911 "win": "win",
912 "cygwin": "win",
913 "darwin": "mac",
914 "mac": "mac",
915 "unix": "unix",
916 "linux": "unix",
917 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000918 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000919 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000920 }
921
922 DEFAULT_CLIENT_FILE_TEXT = ("""\
923solutions = [
924 { "name" : "%(solution_name)s",
925 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000926 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000927 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000928 "custom_deps" : {
929 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000930 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000931 },
932]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000933cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000934""")
935
936 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
937 { "name" : "%(solution_name)s",
938 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000939 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000940 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000941 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000942%(solution_deps)s },
943 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000944 },
945""")
946
947 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
948# Snapshot generated with gclient revinfo --snapshot
949solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000950%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000951""")
952
953 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000954 # Do not change previous behavior. Only solution level and immediate DEPS
955 # are processed.
956 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000957 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000958 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000959 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000960 if options.deps_os:
961 enforced_os = options.deps_os.split(',')
962 else:
963 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
964 if 'all' in enforced_os:
965 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000966 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000967 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000968 self.config_content = None
969
970 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000971 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000972 config_dict = {}
973 self.config_content = content
974 try:
975 exec(content, config_dict)
976 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000977 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000978
peter@chromium.org1efccc82012-04-27 16:34:38 +0000979 # Append any target OS that is not already being enforced to the tuple.
980 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000981 if config_dict.get('target_os_only', False):
982 self._enforced_os = tuple(set(target_os))
983 else:
984 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
985
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000986 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
987
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000988 if not target_os and config_dict.get('target_os_only', False):
989 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
990 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000991
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000992 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000993 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000994 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000995 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000996 self, s['name'], s['url'],
997 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000998 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000999 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001000 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001001 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001002 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001003 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001004 except KeyError:
1005 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1006 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001007 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1008 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001009
1010 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001011 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001012 self._options.config_filename),
1013 self.config_content)
1014
1015 @staticmethod
1016 def LoadCurrentConfig(options):
1017 """Searches for and loads a .gclient file relative to the current working
1018 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001019 if options.spec:
1020 client = GClient('.', options)
1021 client.SetConfig(options.spec)
1022 else:
1023 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1024 if not path:
1025 return None
1026 client = GClient(path, options)
1027 client.SetConfig(gclient_utils.FileRead(
1028 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001029
1030 if (options.revisions and
1031 len(client.dependencies) > 1 and
1032 any('@' not in r for r in options.revisions)):
1033 print >> sys.stderr, (
1034 'You must specify the full solution name like --revision %s@%s\n'
1035 'when you have multiple solutions setup in your .gclient file.\n'
1036 'Other solutions present are: %s.') % (
1037 client.dependencies[0].name,
1038 options.revisions[0],
1039 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001040 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001041
nsylvain@google.comefc80932011-05-31 21:27:56 +00001042 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001043 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001044 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1045 'solution_name': solution_name,
1046 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001047 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001048 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001049 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001050 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001051 })
1052
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001053 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001054 """Creates a .gclient_entries file to record the list of unique checkouts.
1055
1056 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001057 """
1058 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1059 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001060 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001061 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001062 # Skip over File() dependencies as we can't version them.
1063 if not isinstance(entry.parsed_url, self.FileImpl):
1064 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1065 pprint.pformat(entry.parsed_url))
1066 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001067 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001068 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001069 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001070
1071 def _ReadEntries(self):
1072 """Read the .gclient_entries file for the given client.
1073
1074 Returns:
1075 A sequence of solution names, which will be empty if there is the
1076 entries file hasn't been created yet.
1077 """
1078 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001079 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001080 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001081 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001082 try:
1083 exec(gclient_utils.FileRead(filename), scope)
1084 except SyntaxError, e:
1085 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001086 return scope['entries']
1087
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001088 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001089 """Checks for revision overrides."""
1090 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001091 if self._options.head:
1092 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001093 # Do not check safesync_url if one or more --revision flag is specified.
1094 if not self._options.revisions:
1095 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001096 if not s.managed:
1097 self._options.revisions.append('%s@unmanaged' % s.name)
1098 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001099 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001100 if not self._options.revisions:
1101 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001102 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001103 index = 0
1104 for revision in self._options.revisions:
1105 if not '@' in revision:
1106 # Support for --revision 123
1107 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001108 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001109 if not sol in solutions_names:
1110 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1111 print >> sys.stderr, ('Please fix your script, having invalid '
1112 '--revision flags will soon considered an error.')
1113 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001114 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001115 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001116 return revision_overrides
1117
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001118 def _ApplySafeSyncRev(self, dep):
1119 """Finds a valid revision from the content of the safesync_url and apply it
1120 by appending revisions to the revision list. Throws if revision appears to
1121 be invalid for the given |dep|."""
1122 assert len(dep.safesync_url) > 0
1123 handle = urllib.urlopen(dep.safesync_url)
1124 rev = handle.read().strip()
1125 handle.close()
1126 if not rev:
1127 raise gclient_utils.Error(
1128 'It appears your safesync_url (%s) is not working properly\n'
1129 '(as it returned an empty response). Check your config.' %
1130 dep.safesync_url)
1131 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001132 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001133 if self._options.verbose:
1134 print('Using safesync_url revision: %s.\n' % safe_rev)
1135 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1136
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001137 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001138 """Runs a command on each dependency in a client and its dependencies.
1139
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001140 Args:
1141 command: The command to use (e.g., 'status' or 'diff')
1142 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001143 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001144 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001145 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001146 revision_overrides = {}
1147 # It's unnecessary to check for revision overrides for 'recurse'.
1148 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001149 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001150 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001151 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001152 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001153 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001154 if command in ('update', 'revert'):
1155 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001156 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001157 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001158 work_queue = gclient_utils.ExecutionQueue(
1159 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001160 for s in self.dependencies:
1161 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001162 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001163
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001164 # Once all the dependencies have been processed, it's now safe to run the
1165 # hooks.
1166 if not self._options.nohooks:
1167 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001168
1169 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001170 # Notify the user if there is an orphaned entry in their working copy.
1171 # Only delete the directory if there are no changes in it, and
1172 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001173 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001174 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1175 for e in entries]
1176
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001177 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001178 if not prev_url:
1179 # entry must have been overridden via .gclient custom_deps
1180 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001181 # Fix path separator on Windows.
1182 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001183 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001184
1185 def _IsParentOfAny(parent, path_list):
1186 parent_plus_slash = parent + '/'
1187 return any(
1188 path[:len(parent_plus_slash)] == parent_plus_slash
1189 for path in path_list)
1190
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001191 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001192 if (entry not in entries and
1193 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001194 os.path.exists(e_dir)):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001195 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001196
1197 # Check to see if this directory is now part of a higher-up checkout.
1198 if scm.GetCheckoutRoot() in full_entries:
1199 logging.info('%s is part of a higher level checkout, not '
1200 'removing.', scm.GetCheckoutRoot())
1201 continue
1202
1203 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001204 scm.status(self._options, [], file_list)
1205 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001206 if (not self._options.delete_unversioned_trees or
1207 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001208 # There are modified files in this entry. Keep warning until
1209 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001210 print(('\nWARNING: \'%s\' is no longer part of this client. '
1211 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001212 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001213 else:
1214 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001215 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001216 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001217 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001218 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001219 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001220 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001221
1222 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001223 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001224 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001225 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001226 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001227 for s in self.dependencies:
1228 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001229 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001230
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001231 def GetURLAndRev(dep):
1232 """Returns the revision-qualified SCM url for a Dependency."""
1233 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001234 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001235 if isinstance(dep.parsed_url, self.FileImpl):
1236 original_url = dep.parsed_url.file_location
1237 else:
1238 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001239 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001240 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001241 if not os.path.isdir(scm.checkout_path):
1242 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001243 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001244
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001245 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001246 new_gclient = ''
1247 # First level at .gclient
1248 for d in self.dependencies:
1249 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001250 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001251 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001252 for d in dep.dependencies:
1253 entries[d.name] = GetURLAndRev(d)
1254 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001255 GrabDeps(d)
1256 custom_deps = []
1257 for k in sorted(entries.keys()):
1258 if entries[k]:
1259 # Quotes aren't escaped...
1260 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1261 else:
1262 custom_deps.append(' \"%s\": None,\n' % k)
1263 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1264 'solution_name': d.name,
1265 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001266 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001267 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001268 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001269 'solution_deps': ''.join(custom_deps),
1270 }
1271 # Print the snapshot configuration file
1272 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001273 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001274 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001275 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001276 if self._options.actual:
1277 entries[d.name] = GetURLAndRev(d)
1278 else:
1279 entries[d.name] = d.parsed_url
1280 keys = sorted(entries.keys())
1281 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001282 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001283 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001285 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001286 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001287 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001288
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001289 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001290 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001291 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001292 return self._root_dir
1293
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001294 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001295 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001296 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001297 return self._enforced_os
1298
maruel@chromium.org68988972011-09-20 14:11:42 +00001299 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001300 def recursion_limit(self):
1301 """How recursive can each dependencies in DEPS file can load DEPS file."""
1302 return self._recursion_limit
1303
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001304 @property
1305 def target_os(self):
1306 return self._enforced_os
1307
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001308
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001309#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001310
1311
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001312def CMDcleanup(parser, args):
1313 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001314
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001315 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1316 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001317 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1318 help='override deps for the specified (comma-separated) '
1319 'platform(s); \'all\' will process all deps_os '
1320 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001321 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001322 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001323 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001324 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001325 if options.verbose:
1326 # Print out the .gclient file. This is longer than if we just printed the
1327 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001328 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329 return client.RunOnDeps('cleanup', args)
1330
1331
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001332@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001333def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001334 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001335
1336 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001337 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1338 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001339 """
1340 # Stop parsing at the first non-arg so that these go through to the command
1341 parser.disable_interspersed_args()
1342 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001343 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001344 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001345 help='Ignore non-zero return codes from subcommands.')
1346 parser.add_option('--prepend-dir', action='store_true',
1347 help='Prepend relative dir for use with git <cmd> --null.')
1348 parser.add_option('--no-progress', action='store_true',
1349 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001350 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001351 if not args:
1352 print >> sys.stderr, 'Need to supply a command!'
1353 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001354 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1355 if not root_and_entries:
1356 print >> sys.stderr, (
1357 'You need to run gclient sync at least once to use \'recurse\'.\n'
1358 'This is because .gclient_entries needs to exist and be up to date.')
1359 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001360
1361 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001362 scm_set = set()
1363 for scm in options.scm:
1364 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001365 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001366
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001367 options.nohooks = True
1368 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001369 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1370 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001371
1372
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001373@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001374def CMDfetch(parser, args):
1375 """Fetches upstream commits for all modules.
1376
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001377 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1378 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001379 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001380 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001381 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1382
1383
1384def CMDgrep(parser, args):
1385 """Greps through git repos managed by gclient.
1386
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001387 Runs 'git grep [args...]' for each module.
1388 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001389 # We can't use optparse because it will try to parse arguments sent
1390 # to git grep and throw an error. :-(
1391 if not args or re.match('(-h|--help)$', args[0]):
1392 print >> sys.stderr, (
1393 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1394 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1395 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1396 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1397 ' end of your query.'
1398 )
1399 return 1
1400
1401 jobs_arg = ['--jobs=1']
1402 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1403 jobs_arg, args = args[:1], args[1:]
1404 elif re.match(r'(-j|--jobs)$', args[0]):
1405 jobs_arg, args = args[:2], args[2:]
1406
1407 return CMDrecurse(
1408 parser,
1409 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1410 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001411
1412
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001413@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001414def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001415 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001416
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001417 This specifies the configuration for further commands. After update/sync,
1418 top-level DEPS files in each module are read to determine dependent
1419 modules to operate on as well. If optional [url] parameter is
1420 provided, then configuration is read from a specified Subversion server
1421 URL.
1422 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001423 # We do a little dance with the --gclientfile option. 'gclient config' is the
1424 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1425 # arguments. So, we temporarily stash any --gclientfile parameter into
1426 # options.output_config_file until after the (gclientfile xor spec) error
1427 # check.
1428 parser.remove_option('--gclientfile')
1429 parser.add_option('--gclientfile', dest='output_config_file',
1430 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001431 parser.add_option('--name',
1432 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001433 parser.add_option('--deps-file', default='DEPS',
1434 help='overrides the default name for the DEPS file for the'
1435 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001436 parser.add_option('--unmanaged', action='store_true', default=False,
1437 help='overrides the default behavior to make it possible '
1438 'to have the main solution untouched by gclient '
1439 '(gclient will check out unmanaged dependencies but '
1440 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001441 parser.add_option('--git-deps', action='store_true',
1442 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001443 parser.add_option('--cache-dir',
1444 help='(git only) Cache all git repos into this dir and do '
1445 'shared clones from the cache, instead of cloning '
1446 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001447 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001448 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001449 if options.output_config_file:
1450 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001451 if ((options.spec and args) or len(args) > 2 or
1452 (not options.spec and not args)):
1453 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1454
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001455 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 if options.spec:
1457 client.SetConfig(options.spec)
1458 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001459 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001460 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001461 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001462 if name.endswith('.git'):
1463 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001464 else:
1465 # specify an alternate relpath for the given URL.
1466 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001467 deps_file = options.deps_file
1468 if options.git_deps:
1469 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001470 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001471 if len(args) > 1:
1472 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001473 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001474 managed=not options.unmanaged,
1475 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001476 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001477 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001478
1479
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001480@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001481 gclient pack > patch.txt
1482 generate simple patch for configured client and dependences
1483""")
1484def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001485 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001486
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001487 Internally, runs 'svn diff'/'git diff' on each checked out module and
1488 dependencies, and performs minimal postprocessing of the output. The
1489 resulting patch is printed to stdout and can be applied to a freshly
1490 checked out tree via 'patch -p0 < patchfile'.
1491 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001492 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1493 help='override deps for the specified (comma-separated) '
1494 'platform(s); \'all\' will process all deps_os '
1495 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001496 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001497 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001498 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001499 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001500 client = GClient.LoadCurrentConfig(options)
1501 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001502 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001503 if options.verbose:
1504 # Print out the .gclient file. This is longer than if we just printed the
1505 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001506 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001507 return client.RunOnDeps('pack', args)
1508
1509
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001510def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001511 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001512 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1513 help='override deps for the specified (comma-separated) '
1514 'platform(s); \'all\' will process all deps_os '
1515 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001516 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001517 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001518 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001519 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001520 if options.verbose:
1521 # Print out the .gclient file. This is longer than if we just printed the
1522 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001523 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001524 return client.RunOnDeps('status', args)
1525
1526
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001527@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001528 gclient sync
1529 update files from SCM according to current configuration,
1530 *for modules which have changed since last update or sync*
1531 gclient sync --force
1532 update files from SCM according to current configuration, for
1533 all modules (useful for recovering files deleted from local copy)
1534 gclient sync --revision src@31000
1535 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001536
1537JSON output format:
1538If the --output-json option is specified, the following document structure will
1539be emitted to the provided file. 'null' entries may occur for subprojects which
1540are present in the gclient solution, but were not processed (due to custom_deps,
1541os_deps, etc.)
1542
1543{
1544 "solutions" : {
1545 "<name>": { # <name> is the posix-normalized path to the solution.
1546 "revision": [<svn rev int>|<git id hex string>|null],
1547 "scm": ["svn"|"git"|null],
1548 }
1549 }
1550}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001551""")
1552def CMDsync(parser, args):
1553 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001554 parser.add_option('-f', '--force', action='store_true',
1555 help='force update even for unchanged modules')
1556 parser.add_option('-n', '--nohooks', action='store_true',
1557 help='don\'t run hooks after the update is complete')
1558 parser.add_option('-r', '--revision', action='append',
1559 dest='revisions', metavar='REV', default=[],
1560 help='Enforces revision/hash for the solutions with the '
1561 'format src@rev. The src@ part is optional and can be '
1562 'skipped. -r can be used multiple times when .gclient '
1563 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001564 'if the src@ part is skipped. Note that specifying '
1565 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001566 parser.add_option('--with_branch_heads', action='store_true',
1567 help='Clone git "branch_heads" refspecs in addition to '
1568 'the default refspecs. This adds about 1/2GB to a '
1569 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001570 parser.add_option('-t', '--transitive', action='store_true',
1571 help='When a revision is specified (in the DEPS file or '
1572 'with the command-line flag), transitively update '
1573 'the dependencies to the date of the given revision. '
1574 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001575 parser.add_option('-H', '--head', action='store_true',
1576 help='skips any safesync_urls specified in '
1577 'configured solutions and sync to head instead')
1578 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001579 help='Deletes from the working copy any dependencies that '
1580 'have been removed since the last sync, as long as '
1581 'there are no local modifications. When used with '
1582 '--force, such dependencies are removed even if they '
1583 'have local modifications. When used with --reset, '
1584 'all untracked directories are removed from the '
1585 'working copy, exclusing those which are explicitly '
1586 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001587 parser.add_option('-R', '--reset', action='store_true',
1588 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001589 parser.add_option('-M', '--merge', action='store_true',
1590 help='merge upstream changes instead of trying to '
1591 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001592 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1593 help='override deps for the specified (comma-separated) '
1594 'platform(s); \'all\' will process all deps_os '
1595 'references')
1596 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1597 help='Skip svn up whenever possible by requesting '
1598 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001599 parser.add_option('--upstream', action='store_true',
1600 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001601 parser.add_option('--output-json',
1602 help='Output a json document to this path containing '
1603 'summary information about the sync.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001604 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001605 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001606
1607 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001608 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001609
maruel@chromium.org307d1792010-05-31 20:03:13 +00001610 if options.revisions and options.head:
1611 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001612 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001613
1614 if options.verbose:
1615 # Print out the .gclient file. This is longer than if we just printed the
1616 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001617 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001618 ret = client.RunOnDeps('update', args)
1619 if options.output_json:
1620 slns = {}
1621 for d in client.subtree(True):
1622 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1623 slns[normed] = {
1624 'revision': d.got_revision,
1625 'scm': d.used_scm.name if d.used_scm else None,
1626 }
1627 with open(options.output_json, 'wb') as f:
1628 json.dump({'solutions': slns}, f)
1629 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001630
1631
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001632CMDupdate = CMDsync
1633
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001634
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001635def CMDdiff(parser, args):
1636 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001637 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1638 help='override deps for the specified (comma-separated) '
1639 'platform(s); \'all\' will process all deps_os '
1640 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001641 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001642 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001643 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001644 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001645 if options.verbose:
1646 # Print out the .gclient file. This is longer than if we just printed the
1647 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001648 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001649 return client.RunOnDeps('diff', args)
1650
1651
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001652def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001653 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001654
1655 That's the nuclear option to get back to a 'clean' state. It removes anything
1656 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001657 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1658 help='override deps for the specified (comma-separated) '
1659 'platform(s); \'all\' will process all deps_os '
1660 'references')
1661 parser.add_option('-n', '--nohooks', action='store_true',
1662 help='don\'t run hooks after the revert is complete')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001663 parser.add_option('--upstream', action='store_true',
1664 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001665 (options, args) = parser.parse_args(args)
1666 # --force is implied.
1667 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001668 options.reset = False
1669 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001670 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001671 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001672 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001673 return client.RunOnDeps('revert', args)
1674
1675
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001676def CMDrunhooks(parser, args):
1677 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001678 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1679 help='override deps for the specified (comma-separated) '
1680 'platform(s); \'all\' will process all deps_os '
1681 'references')
1682 parser.add_option('-f', '--force', action='store_true', default=True,
1683 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001684 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001685 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001686 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001687 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001688 if options.verbose:
1689 # Print out the .gclient file. This is longer than if we just printed the
1690 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001691 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001692 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001693 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001694 return client.RunOnDeps('runhooks', args)
1695
1696
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001697def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001698 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001699
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001700 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001701 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001702 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1703 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001704 commit can change.
1705 """
1706 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1707 help='override deps for the specified (comma-separated) '
1708 'platform(s); \'all\' will process all deps_os '
1709 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001710 parser.add_option('-a', '--actual', action='store_true',
1711 help='gets the actual checked out revisions instead of the '
1712 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001713 parser.add_option('-s', '--snapshot', action='store_true',
1714 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001715 'version of all repositories to reproduce the tree, '
1716 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001717 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001718 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001719 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001720 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001721 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001722 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001723
1724
szager@google.comb9a78d32012-03-13 18:46:21 +00001725def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001726 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001727 (options, args) = parser.parse_args(args)
1728 options.force = True
1729 client = GClient.LoadCurrentConfig(options)
1730 if not client:
1731 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1732 client.RunOnDeps(None, [])
1733 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1734 return 0
1735
1736
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001737class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00001738 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001739
1740 def __init__(self, **kwargs):
1741 optparse.OptionParser.__init__(
1742 self, version='%prog ' + __version__, **kwargs)
1743
1744 # Some arm boards have issues with parallel sync.
1745 if platform.machine().startswith('arm'):
1746 jobs = 1
1747 else:
1748 jobs = max(8, gclient_utils.NumLocalCpus())
1749 # cmp: 2013/06/19
1750 # Temporary workaround to lower bot-load on SVN server.
1751 if os.environ.get('CHROME_HEADLESS') == '1':
xusydoc@chromium.org05028412013-07-29 13:40:10 +00001752 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001753
1754 self.add_option(
1755 '-j', '--jobs', default=jobs, type='int',
1756 help='Specify how many SCM commands can run in parallel; defaults to '
1757 'number of cpu cores (%default)')
1758 self.add_option(
1759 '-v', '--verbose', action='count', default=0,
1760 help='Produces additional output for diagnostics. Can be used up to '
1761 'three times for more logging info.')
1762 self.add_option(
1763 '--gclientfile', dest='config_filename',
1764 help='Specify an alternate %s file' % self.gclientfile_default)
1765 self.add_option(
1766 '--spec',
1767 help='create a gclient file containing the provided string. Due to '
1768 'Cygwin/Python brokenness, it can\'t contain any newlines.')
1769 self.add_option(
1770 '--no-nag-max', default=False, action='store_true',
1771 help='If a subprocess runs for too long without generating terminal '
1772 'output, generate warnings, but do not kill the process.')
1773
1774 def parse_args(self, args=None, values=None):
1775 """Integrates standard options processing."""
1776 options, args = optparse.OptionParser.parse_args(self, args, values)
1777 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
1778 logging.basicConfig(
1779 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00001780 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001781 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001782 self.error('Cannot specifiy both --gclientfile and --spec')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001783 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001784 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001785 options.entries_filename = options.config_filename + '_entries'
1786 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001787 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001788
1789 # These hacks need to die.
1790 if not hasattr(options, 'revisions'):
1791 # GClient.RunOnDeps expects it even if not applicable.
1792 options.revisions = []
1793 if not hasattr(options, 'head'):
1794 options.head = None
1795 if not hasattr(options, 'nohooks'):
1796 options.nohooks = True
1797 if not hasattr(options, 'deps_os'):
1798 options.deps_os = None
1799 if not hasattr(options, 'manually_grab_svn_rev'):
1800 options.manually_grab_svn_rev = None
1801 if not hasattr(options, 'force'):
1802 options.force = None
szager@chromium.org41da24b2013-05-23 19:37:04 +00001803 if options.no_nag_max:
1804 gclient_scm.SCMWrapper.nag_max = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00001805 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001806
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001807
1808def disable_buffering():
1809 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1810 # operations. Python as a strong tendency to buffer sys.stdout.
1811 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
1812 # Make stdout annotated with the thread ids.
1813 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001814
1815
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001816def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001817 """Doesn't parse the arguments here, just find the right subcommand to
1818 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001819 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001820 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001821 '\nYour python version %s is unsupported, please upgrade.\n' %
1822 sys.version.split(' ', 1)[0])
1823 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001824 if not sys.executable:
1825 print >> sys.stderr, (
1826 '\nPython cannot find the location of it\'s own executable.\n')
1827 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001828 fix_encoding.fix_encoding()
1829 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001830 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001831 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001832 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001833 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001834 except KeyboardInterrupt:
1835 gclient_utils.GClientChildren.KillAllRemainingChildren()
1836 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001837 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001838 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001839 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001840
1841
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001842if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001843 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001844
1845# vim: ts=2:sw=2:tw=80:et: