blob: 94a981d50248079d0e3fa5d6f7891b766125aa79 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org39c0b222013-08-17 16:57:01 +00006"""Meta checkout manager supporting both Subversion and GIT."""
7# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
67# processing the deps_os dict of a DEPS file.
68#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
maruel@chromium.org39c0b222013-08-17 16:57:01 +000079__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000081import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000082import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000083import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084import optparse
85import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000086import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000087import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000088import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000089import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000090import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000091import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000093import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000094
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000095import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000096
maruel@chromium.org35625c72011-03-23 17:34:02 +000097import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000098import gclient_scm
99import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000100import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000101from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000102import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000103import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +0000104from third_party import colorama
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000105
106
maruel@chromium.org116704f2010-06-11 17:34:38 +0000107class GClientKeywords(object):
108 class FromImpl(object):
109 """Used to implement the From() syntax."""
110
111 def __init__(self, module_name, sub_target_name=None):
112 """module_name is the dep module we want to include from. It can also be
113 the name of a subdirectory to include from.
114
115 sub_target_name is an optional parameter if the module name in the other
116 DEPS file is different. E.g., you might want to map src/net to net."""
117 self.module_name = module_name
118 self.sub_target_name = sub_target_name
119
120 def __str__(self):
121 return 'From(%s, %s)' % (repr(self.module_name),
122 repr(self.sub_target_name))
123
maruel@chromium.org116704f2010-06-11 17:34:38 +0000124 class FileImpl(object):
125 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000126 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000127
128 def __init__(self, file_location):
129 self.file_location = file_location
130
131 def __str__(self):
132 return 'File("%s")' % self.file_location
133
134 def GetPath(self):
135 return os.path.split(self.file_location)[0]
136
137 def GetFilename(self):
138 rev_tokens = self.file_location.split('@')
139 return os.path.split(rev_tokens[0])[1]
140
141 def GetRevision(self):
142 rev_tokens = self.file_location.split('@')
143 if len(rev_tokens) > 1:
144 return rev_tokens[1]
145 return None
146
147 class VarImpl(object):
148 def __init__(self, custom_vars, local_scope):
149 self._custom_vars = custom_vars
150 self._local_scope = local_scope
151
152 def Lookup(self, var_name):
153 """Implements the Var syntax."""
154 if var_name in self._custom_vars:
155 return self._custom_vars[var_name]
156 elif var_name in self._local_scope.get("vars", {}):
157 return self._local_scope["vars"][var_name]
158 raise gclient_utils.Error("Var is not defined: %s" % var_name)
159
160
maruel@chromium.org064186c2011-09-27 23:53:33 +0000161class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000162 """Immutable configuration settings."""
163 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000165 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000166 GClientKeywords.__init__(self)
167
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000168 # These are not mutable:
169 self._parent = parent
170 self._safesync_url = safesync_url
171 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000172 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000173 # 'managed' determines whether or not this dependency is synced/updated by
174 # gclient after gclient checks it out initially. The difference between
175 # 'managed' and 'should_process' is that the user specifies 'managed' via
176 # the --unmanaged command-line flag or a .gclient config, where
177 # 'should_process' is dynamically set by gclient if it goes over its
178 # recursion limit and controls gclient's behavior so it does not misbehave.
179 self._managed = managed
180 self._should_process = should_process
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000181 # This is a mutable value which has the list of 'target_os' OSes listed in
182 # the current deps file.
183 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000184
185 # These are only set in .gclient and not in DEPS files.
186 self._custom_vars = custom_vars or {}
187 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000188 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000189
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000190 # TODO(iannucci): Remove this when all masters are correctly substituting
191 # the new blink url.
192 if (self._custom_vars.get('webkit_trunk', '') ==
193 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000194 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
195 print 'Overwriting Var("webkit_trunk") with %s' % new_url
196 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000197
maruel@chromium.org064186c2011-09-27 23:53:33 +0000198 # Post process the url to remove trailing slashes.
199 if isinstance(self._url, basestring):
200 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
201 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000202 self._url = self._url.replace('/@', '@')
203 elif not isinstance(self._url,
204 (self.FromImpl, self.FileImpl, None.__class__)):
205 raise gclient_utils.Error(
206 ('dependency url must be either a string, None, '
207 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000208 # Make any deps_file path platform-appropriate.
209 for sep in ['/', '\\']:
210 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211
212 @property
213 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 return self._deps_file
215
216 @property
217 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218 return self._managed
219
220 @property
221 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222 return self._parent
223
224 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000225 def root(self):
226 """Returns the root node, a GClient object."""
227 if not self.parent:
228 # This line is to signal pylint that it could be a GClient instance.
229 return self or GClient(None, None)
230 return self.parent.root
231
232 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000233 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000234 return self._safesync_url
235
236 @property
237 def should_process(self):
238 """True if this dependency should be processed, i.e. checked out."""
239 return self._should_process
240
241 @property
242 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 return self._custom_vars.copy()
244
245 @property
246 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 return self._custom_deps.copy()
248
maruel@chromium.org064186c2011-09-27 23:53:33 +0000249 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000250 def custom_hooks(self):
251 return self._custom_hooks[:]
252
253 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000254 def url(self):
255 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000256
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000257 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000258 def target_os(self):
259 if self.local_target_os is not None:
260 return tuple(set(self.local_target_os).union(self.parent.target_os))
261 else:
262 return self.parent.target_os
263
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000264 def get_custom_deps(self, name, url):
265 """Returns a custom deps if applicable."""
266 if self.parent:
267 url = self.parent.get_custom_deps(name, url)
268 # None is a valid return value to disable a dependency.
269 return self.custom_deps.get(name, url)
270
maruel@chromium.org064186c2011-09-27 23:53:33 +0000271
272class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000273 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000274
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000275 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000276 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000277 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000278 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000279 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000280 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000281
282 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000283 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000284
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000285 self._pre_deps_hooks = []
286
maruel@chromium.org68988972011-09-20 14:11:42 +0000287 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000288 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000289 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000290 # A cache of the files affected by the current operation, necessary for
291 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000292 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000293 # If it is not set to True, the dependency wasn't processed for its child
294 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000295 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000296 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000297 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000298 # This dependency had its pre-DEPS hooks run
299 self._pre_deps_hooks_ran = 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
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000305 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000306 # The actual revision we ended up getting, or None if that information is
307 # unavailable
308 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000309
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000310 # This is a mutable value that overrides the normal recursion limit for this
311 # dependency. It is read from the actual DEPS file so cannot be set on
312 # class instantiation.
313 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000314 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000315 # 'no recursion' setting on a dep-by-dep basis. It will replace
316 # recursion_override.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000317 self.recursedeps = None
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000318
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000319 if not self.name and self.parent:
320 raise gclient_utils.Error('Dependency without name')
321
maruel@chromium.org470b5432011-10-11 18:18:19 +0000322 @property
323 def requirements(self):
324 """Calculate the list of requirements."""
325 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000326 # self.parent is implicitly a requirement. This will be recursive by
327 # definition.
328 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000329 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000330
331 # For a tree with at least 2 levels*, the leaf node needs to depend
332 # on the level higher up in an orderly way.
333 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
334 # thus unsorted, while the .gclient format is a list thus sorted.
335 #
336 # * _recursion_limit is hard coded 2 and there is no hope to change this
337 # value.
338 #
339 # Interestingly enough, the following condition only works in the case we
340 # want: self is a 2nd level node. 3nd level node wouldn't need this since
341 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000342 if self.parent and self.parent.parent and not self.parent.parent.parent:
343 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000344
345 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000346 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000347
maruel@chromium.org470b5432011-10-11 18:18:19 +0000348 if self.name:
349 requirements |= set(
350 obj.name for obj in self.root.subtree(False)
351 if (obj is not self
352 and obj.name and
353 self.name.startswith(posixpath.join(obj.name, ''))))
354 requirements = tuple(sorted(requirements))
355 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
356 return requirements
357
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000358 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000359 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000360 """Returns False if recursion_override is ever specified."""
361 if self.recursion_override is not None:
362 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000363 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000364
365 @property
366 def recursion_limit(self):
367 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000368 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000369 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000370 if self.try_recursedeps and self.parent.recursedeps != None:
371 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000372 return 1
373
374 if self.recursion_override is not None:
375 return self.recursion_override
376 return max(self.parent.recursion_limit - 1, 0)
377
maruel@chromium.org470b5432011-10-11 18:18:19 +0000378 def verify_validity(self):
379 """Verifies that this Dependency is fine to add as a child of another one.
380
381 Returns True if this entry should be added, False if it is a duplicate of
382 another entry.
383 """
384 logging.info('Dependency(%s).verify_validity()' % self.name)
385 if self.name in [s.name for s in self.parent.dependencies]:
386 raise gclient_utils.Error(
387 'The same name "%s" appears multiple times in the deps section' %
388 self.name)
389 if not self.should_process:
390 # Return early, no need to set requirements.
391 return True
392
393 # This require a full tree traversal with locks.
394 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
395 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000396 self_url = self.LateOverride(self.url)
397 sibling_url = sibling.LateOverride(sibling.url)
398 # Allow to have only one to be None or ''.
399 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000400 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000401 ('Dependency %s specified more than once:\n'
402 ' %s [%s]\n'
403 'vs\n'
404 ' %s [%s]') % (
405 self.name,
406 sibling.hierarchy(),
407 sibling_url,
408 self.hierarchy(),
409 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000410 # In theory we could keep it as a shadow of the other one. In
411 # practice, simply ignore it.
412 logging.warn('Won\'t process duplicate dependency %s' % sibling)
413 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000414 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000415
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000417 """Resolves the parsed url from url.
418
419 Manages From() keyword accordingly. Do not touch self.parsed_url nor
420 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000421 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000422 parsed_url = self.get_custom_deps(self.name, url)
423 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000424 logging.info(
425 'Dependency(%s).LateOverride(%s) -> %s' %
426 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000427 return parsed_url
428
429 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000430 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000431 ref = [
432 dep for dep in self.root.subtree(True) if url.module_name == dep.name
433 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000434 if not ref:
435 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
436 url.module_name, ref))
437 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000438 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000439 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000440 found_deps = [d for d in ref.dependencies if d.name == sub_target]
441 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000442 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000443 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
444 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000445 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000446
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000447 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000448 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000449 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000450 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000451 'Dependency(%s).LateOverride(%s) -> %s (From)' %
452 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000453 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000454
455 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000456 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000457 if (not parsed_url[0] and
458 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000459 # A relative url. Fetch the real base.
460 path = parsed_url[2]
461 if not path.startswith('/'):
462 raise gclient_utils.Error(
463 'relative DEPS entry \'%s\' must begin with a slash' % url)
464 # Create a scm just to query the full url.
465 parent_url = self.parent.parsed_url
466 if isinstance(parent_url, self.FileImpl):
467 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000468 scm = gclient_scm.CreateSCM(
469 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000470 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000472 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000473 logging.info(
474 'Dependency(%s).LateOverride(%s) -> %s' %
475 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000476 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000477
478 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000479 logging.info(
480 'Dependency(%s).LateOverride(%s) -> %s (File)' %
481 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000482 return url
483
484 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000485 logging.info(
486 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000487 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000488
489 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000490
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000491 @staticmethod
492 def MergeWithOsDeps(deps, deps_os, target_os_list):
493 """Returns a new "deps" structure that is the deps sent in updated
494 with information from deps_os (the deps_os section of the DEPS
495 file) that matches the list of target os."""
496 os_overrides = {}
497 for the_target_os in target_os_list:
498 the_target_os_deps = deps_os.get(the_target_os, {})
499 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
500 overrides = os_overrides.setdefault(os_dep_key, [])
501 overrides.append((the_target_os, os_dep_value))
502
503 # If any os didn't specify a value (we have fewer value entries
504 # than in the os list), then it wants to use the default value.
505 for os_dep_key, os_dep_value in os_overrides.iteritems():
506 if len(os_dep_value) != len(target_os_list):
507 # Record the default value too so that we don't accidently
508 # set it to None or miss a conflicting DEPS.
509 if os_dep_key in deps:
510 os_dep_value.append(('default', deps[os_dep_key]))
511
512 target_os_deps = {}
513 for os_dep_key, os_dep_value in os_overrides.iteritems():
514 # os_dep_value is a list of (os, value) pairs.
515 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
516 if not possible_values:
517 target_os_deps[os_dep_key] = None
518 else:
519 if len(possible_values) > 1:
520 # It would be possible to abort here but it would be
521 # unfortunate if we end up preventing any kind of checkout.
522 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
523 os_dep_key, os_dep_value, target_os_list)
524 # Sorting to get the same result every time in case of conflicts.
525 target_os_deps[os_dep_key] = sorted(possible_values)[0]
526
527 new_deps = deps.copy()
528 new_deps.update(target_os_deps)
529 return new_deps
530
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000531 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000532 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000533 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000534 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000535
536 deps_content = None
537 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000538
539 # First try to locate the configured deps file. If it's missing, fallback
540 # to DEPS.
541 deps_files = [self.deps_file]
542 if 'DEPS' not in deps_files:
543 deps_files.append('DEPS')
544 for deps_file in deps_files:
545 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
546 if os.path.isfile(filepath):
547 logging.info(
548 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
549 filepath)
550 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000551 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000552 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
553 filepath)
554
555 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000556 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000557 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000558 use_strict = 'use strict' in deps_content.splitlines()[0]
559
560 local_scope = {}
561 if deps_content:
562 # One thing is unintuitive, vars = {} must happen before Var() use.
563 var = self.VarImpl(self.custom_vars, local_scope)
564 if use_strict:
565 logging.info(
566 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
567 global_scope = {
568 '__builtins__': {'None': None},
569 'Var': var.Lookup,
570 'deps_os': {},
571 }
572 else:
573 global_scope = {
574 'File': self.FileImpl,
575 'From': self.FromImpl,
576 'Var': var.Lookup,
577 'deps_os': {},
578 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000579 # Eval the content.
580 try:
581 exec(deps_content, global_scope, local_scope)
582 except SyntaxError, e:
583 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000584 if use_strict:
585 for key, val in local_scope.iteritems():
586 if not isinstance(val, (dict, list, tuple, str)):
587 raise gclient_utils.Error(
588 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
589 (self.name, key, val))
590
maruel@chromium.org271375b2010-06-23 19:17:38 +0000591 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000592 if 'recursion' in local_scope:
593 self.recursion_override = local_scope.get('recursion')
594 logging.warning(
595 'Setting %s recursion to %d.', self.name, self.recursion_limit)
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000596 self.recursedeps = local_scope.get('recursedeps', None)
597 if 'recursedeps' in local_scope:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000598 self.recursedeps = set(self.recursedeps)
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000599 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000600 # If present, save 'target_os' in the local_target_os property.
601 if 'target_os' in local_scope:
602 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000603 # load os specific dependencies if defined. these dependencies may
604 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000605 target_os_list = self.target_os
606 if 'deps_os' in local_scope and target_os_list:
607 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000608
maruel@chromium.org271375b2010-06-23 19:17:38 +0000609 # If a line is in custom_deps, but not in the solution, we want to append
610 # this line to the solution.
611 for d in self.custom_deps:
612 if d not in deps:
613 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000614
615 # If use_relative_paths is set in the DEPS file, regenerate
616 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000617 # the DEPS file. Also update recursedeps if use_relative_paths is
618 # enabled.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000619 use_relative_paths = local_scope.get('use_relative_paths', False)
620 if use_relative_paths:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000621 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000622 rel_deps = {}
623 for d, url in deps.items():
624 # normpath is required to allow DEPS to use .. in their
625 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000626 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000627 logging.warning('Updating deps by prepending %s.', self.name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000628 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000629
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000630 # Update recursedeps if it's set.
631 if self.recursedeps is not None:
632 logging.warning('Updating recursedeps by prepending %s.', self.name)
633 rel_deps = set()
634 for d in self.recursedeps:
635 rel_deps.add(os.path.normpath(os.path.join(self.name, d)))
636 self.recursedeps = rel_deps
637
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000638 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000639 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000640 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000641 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000642 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000643 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000644 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000645 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000646
647 # override named sets of hooks by the custom hooks
648 hooks_to_run = []
649 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
650 for hook in local_scope.get('hooks', []):
651 if hook.get('name', '') not in hook_names_to_suppress:
652 hooks_to_run.append(hook)
653
654 # add the replacements and any additions
655 for hook in self.custom_hooks:
656 if 'action' in hook:
657 hooks_to_run.append(hook)
658
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000659 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
660 local_scope.get('pre_deps_hooks', [])]
661
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000662 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000663 logging.info('ParseDepsFile(%s) done' % self.name)
664
665 def add_dependencies_and_close(self, deps_to_add, hooks):
666 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000667 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000668 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000669 self.add_dependency(dep)
670 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000671
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000672 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000673 """Uses revision/timestamp of parent if no explicit revision was specified.
674
675 If we are performing an update and --transitive is set, use
676 - the parent's revision if 'self.url' is in the same repository
677 - the parent's timestamp otherwise
678 to update 'self.url'. The used revision/timestamp will be set in
679 'options.revision'.
680 If we have an explicit revision do nothing.
681 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000682 if command == 'update' and options.transitive and not options.revision:
683 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
684 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000685 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000686 if (options.revision and
687 not gclient_utils.IsDateRevision(options.revision)):
688 assert self.parent and self.parent.used_scm
689 # If this dependency is in the same repository as parent it's url will
690 # start with a slash. If so we take the parent revision instead of
691 # it's timestamp.
692 # (The timestamps of commits in google code are broken -- which can
693 # result in dependencies to be checked out at the wrong revision)
694 if self.url.startswith('/'):
695 if options.verbose:
696 print('Using parent\'s revision %s since we are in the same '
697 'repository.' % options.revision)
698 else:
699 parent_revision_date = self.parent.used_scm.GetRevisionDate(
700 options.revision)
701 options.revision = gclient_utils.MakeDateRevision(
702 parent_revision_date)
703 if options.verbose:
704 print('Using parent\'s revision date %s since we are in a '
705 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000706
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000707 # Arguments number differs from overridden method
708 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000709 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000710 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000711 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000712 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000713 if not self.should_process:
714 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000715 # When running runhooks, there's no need to consult the SCM.
716 # All known hooks are expected to run unconditionally regardless of working
717 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000718 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000719 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000720 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000721 if run_scm and parsed_url:
722 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000723 # Special support for single-file checkout.
724 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000725 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
726 # pylint: disable=E1103
727 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000728 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000729 parsed_url.GetPath(), self.root.root_dir, self.name,
730 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000731 self._used_scm.RunCommand('updatesingle',
732 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000733 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000734 # Create a shallow copy to mutate revision.
735 options = copy.copy(options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000736 options.revision = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000737 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000738 command, options, parsed_url, self.parent)
739 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000740 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000741 parsed_url, self.root.root_dir, self.name, self.outbuf,
742 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000743 self._got_revision = self._used_scm.RunCommand(command, options, args,
744 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000745 if file_list:
746 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000747
748 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
749 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000750 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000751 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000752 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000753 continue
754 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000755 [self.root.root_dir.lower(), file_list[i].lower()])
756 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000757 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000758 while file_list[i].startswith(('\\', '/')):
759 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000760
761 # Always parse the DEPS file.
762 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000763 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000764 if command in ('update', 'revert') and not options.noprehooks:
765 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000766
767 if self.recursion_limit:
768 # Parse the dependencies of this dependency.
769 for s in self.dependencies:
770 work_queue.enqueue(s)
771
772 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000773 if not isinstance(parsed_url, self.FileImpl):
774 # Skip file only checkout.
775 scm = gclient_scm.GetScmName(parsed_url)
776 if not options.scm or scm in options.scm:
777 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000778 # Pass in the SCM type as an env variable. Make sure we don't put
779 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000780 env = os.environ.copy()
781 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000782 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000783 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000784 env['GCLIENT_URL'] = str(parsed_url)
785 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000786 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000787 print_stdout = False
788 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000789 """Git-specific path marshaling. It is optimized for git-grep."""
790
791 def mod_path(git_pathspec):
792 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
793 modified_path = os.path.join(self.name, match.group(2))
794 branch = match.group(1) or ''
795 return '%s%s' % (branch, modified_path)
796
797 match = re.match('^Binary file ([^\0]+) matches$', line)
798 if match:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000799 print 'Binary file %s matches\n' % mod_path(match.group(1))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000800 return
801
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000802 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000803 if len(items) == 2 and items[1]:
804 print '%s : %s' % (mod_path(items[0]), items[1])
805 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000806 # Multiple null bytes or a single trailing null byte indicate
807 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000808 print '\n'.join(mod_path(path) for path in items if path)
809 else:
810 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000811 else:
812 print_stdout = True
813 filter_fn = None
814
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000815 if parsed_url is None:
816 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
817 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000818 try:
819 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000820 args, cwd=cwd, env=env, print_stdout=print_stdout,
821 filter_fn=filter_fn,
822 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000823 except subprocess2.CalledProcessError:
824 if not options.ignore:
825 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000826 else:
827 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000828
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000829
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000830 @gclient_utils.lockedmethod
831 def _run_is_done(self, file_list, parsed_url):
832 # Both these are kept for hooks that are run as a separate tree traversal.
833 self._file_list = file_list
834 self._parsed_url = parsed_url
835 self._processed = True
836
szager@google.comb9a78d32012-03-13 18:46:21 +0000837 @staticmethod
838 def GetHookAction(hook_dict, matching_file_list):
839 """Turns a parsed 'hook' dict into an executable command."""
840 logging.debug(hook_dict)
841 logging.debug(matching_file_list)
842 command = hook_dict['action'][:]
843 if command[0] == 'python':
844 # If the hook specified "python" as the first item, the action is a
845 # Python script. Run it by starting a new copy of the same
846 # interpreter.
847 command[0] = sys.executable
848 if '$matching_files' in command:
849 splice_index = command.index('$matching_files')
850 command[splice_index:splice_index + 1] = matching_file_list
851 return command
852
853 def GetHooks(self, options):
854 """Evaluates all hooks, and return them in a flat list.
855
856 RunOnDeps() must have been called before to load the DEPS.
857 """
858 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000859 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000860 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000861 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000862 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000863 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000864 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000865 # TODO(maruel): If the user is using git or git-svn, then we don't know
866 # what files have changed so we always run all hooks. It'd be nice to fix
867 # that.
868 if (options.force or
869 isinstance(self.parsed_url, self.FileImpl) or
870 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000871 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000872 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000873 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000874 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000875 # Run hooks on the basis of whether the files from the gclient operation
876 # match each hook's pattern.
877 for hook_dict in self.deps_hooks:
878 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000879 matching_file_list = [
880 f for f in self.file_list_and_children if pattern.search(f)
881 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000882 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000883 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000884 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000885 result.extend(s.GetHooks(options))
886 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000887
szager@google.comb9a78d32012-03-13 18:46:21 +0000888 def RunHooksRecursively(self, options):
889 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000890 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000891 for hook in self.GetHooks(options):
892 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000893 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000894 gclient_utils.CheckCallAndFilterAndHeader(
895 hook, cwd=self.root.root_dir, always=True)
896 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
897 # Use a discrete exit status code of 2 to indicate that a hook action
898 # failed. Users of this script may wish to treat hook action failures
899 # differently from VC failures.
900 print >> sys.stderr, 'Error: %s' % str(e)
901 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000902 finally:
903 elapsed_time = time.time() - start_time
904 if elapsed_time > 10:
905 print "Hook '%s' took %.2f secs" % (
906 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000907
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000908 def RunPreDepsHooks(self):
909 assert self.processed
910 assert self.deps_parsed
911 assert not self.pre_deps_hooks_ran
912 assert not self.hooks_ran
913 for s in self.dependencies:
914 assert not s.processed
915 self._pre_deps_hooks_ran = True
916 for hook in self.pre_deps_hooks:
917 try:
918 start_time = time.time()
919 gclient_utils.CheckCallAndFilterAndHeader(
920 hook, cwd=self.root.root_dir, always=True)
921 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
922 # Use a discrete exit status code of 2 to indicate that a hook action
923 # failed. Users of this script may wish to treat hook action failures
924 # differently from VC failures.
925 print >> sys.stderr, 'Error: %s' % str(e)
926 sys.exit(2)
927 finally:
928 elapsed_time = time.time() - start_time
929 if elapsed_time > 10:
930 print "Hook '%s' took %.2f secs" % (
931 gclient_utils.CommandToStr(hook), elapsed_time)
932
933
maruel@chromium.org0d812442010-08-10 12:41:08 +0000934 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000935 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000936 dependencies = self.dependencies
937 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000938 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000939 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000940 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000941 for i in d.subtree(include_all):
942 yield i
943
944 def depth_first_tree(self):
945 """Depth-first recursion including the root node."""
946 yield self
947 for i in self.dependencies:
948 for j in i.depth_first_tree():
949 if j.should_process:
950 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000951
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000952 @gclient_utils.lockedmethod
953 def add_dependency(self, new_dep):
954 self._dependencies.append(new_dep)
955
956 @gclient_utils.lockedmethod
957 def _mark_as_parsed(self, new_hooks):
958 self._deps_hooks.extend(new_hooks)
959 self._deps_parsed = True
960
maruel@chromium.org68988972011-09-20 14:11:42 +0000961 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000962 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000963 def dependencies(self):
964 return tuple(self._dependencies)
965
966 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000967 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000968 def deps_hooks(self):
969 return tuple(self._deps_hooks)
970
971 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000972 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000973 def pre_deps_hooks(self):
974 return tuple(self._pre_deps_hooks)
975
976 @property
977 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000978 def parsed_url(self):
979 return self._parsed_url
980
981 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000982 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000983 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000984 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000985 return self._deps_parsed
986
987 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000988 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000989 def processed(self):
990 return self._processed
991
992 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000993 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000994 def pre_deps_hooks_ran(self):
995 return self._pre_deps_hooks_ran
996
997 @property
998 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000999 def hooks_ran(self):
1000 return self._hooks_ran
1001
1002 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001003 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001004 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001005 return tuple(self._file_list)
1006
1007 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001008 def used_scm(self):
1009 """SCMWrapper instance for this dependency or None if not processed yet."""
1010 return self._used_scm
1011
1012 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001013 @gclient_utils.lockedmethod
1014 def got_revision(self):
1015 return self._got_revision
1016
1017 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001018 def file_list_and_children(self):
1019 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001020 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001021 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001022 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001023
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001024 def __str__(self):
1025 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001026 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001027 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001028 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001029 # First try the native property if it exists.
1030 if hasattr(self, '_' + i):
1031 value = getattr(self, '_' + i, False)
1032 else:
1033 value = getattr(self, i, False)
1034 if value:
1035 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001036
1037 for d in self.dependencies:
1038 out.extend([' ' + x for x in str(d).splitlines()])
1039 out.append('')
1040 return '\n'.join(out)
1041
1042 def __repr__(self):
1043 return '%s: %s' % (self.name, self.url)
1044
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001045 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001046 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001047 out = '%s(%s)' % (self.name, self.url)
1048 i = self.parent
1049 while i and i.name:
1050 out = '%s(%s) -> %s' % (i.name, i.url, out)
1051 i = i.parent
1052 return out
1053
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001054
1055class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001056 """Object that represent a gclient checkout. A tree of Dependency(), one per
1057 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001058
1059 DEPS_OS_CHOICES = {
1060 "win32": "win",
1061 "win": "win",
1062 "cygwin": "win",
1063 "darwin": "mac",
1064 "mac": "mac",
1065 "unix": "unix",
1066 "linux": "unix",
1067 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001068 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001069 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001070 }
1071
1072 DEFAULT_CLIENT_FILE_TEXT = ("""\
1073solutions = [
1074 { "name" : "%(solution_name)s",
1075 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001076 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001077 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001078 "custom_deps" : {
1079 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001080 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001081 },
1082]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001083cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001084""")
1085
1086 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1087 { "name" : "%(solution_name)s",
1088 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001089 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001090 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001091 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001092%(solution_deps)s },
1093 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001094 },
1095""")
1096
1097 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1098# Snapshot generated with gclient revinfo --snapshot
1099solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001100%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001101""")
1102
1103 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001104 # Do not change previous behavior. Only solution level and immediate DEPS
1105 # are processed.
1106 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001107 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001108 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001109 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001110 if options.deps_os:
1111 enforced_os = options.deps_os.split(',')
1112 else:
1113 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1114 if 'all' in enforced_os:
1115 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001116 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001117 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001118 self.config_content = None
1119
borenet@google.com88d10082014-03-21 17:24:48 +00001120 def _CheckConfig(self):
1121 """Verify that the config matches the state of the existing checked-out
1122 solutions."""
1123 for dep in self.dependencies:
1124 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001125 scm = gclient_scm.CreateSCM(
1126 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001127 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001128 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001129 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001130Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001131is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001132
borenet@google.com97882362014-04-07 20:06:02 +00001133The .gclient file contains:
1134%(expected_url)s (%(expected_scm)s)
1135
1136The local checkout in %(checkout_path)s reports:
1137%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001138
1139You should ensure that the URL listed in .gclient is correct and either change
1140it or fix the checkout. If you're managing your own git checkout in
1141%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1142want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001143''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1144 'expected_url': dep.url,
1145 'expected_scm': gclient_scm.GetScmName(dep.url),
1146 'actual_url': actual_url,
1147 'actual_scm': gclient_scm.GetScmName(actual_url)})
1148
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001149 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001150 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001151 config_dict = {}
1152 self.config_content = content
1153 try:
1154 exec(content, config_dict)
1155 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001156 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001157
peter@chromium.org1efccc82012-04-27 16:34:38 +00001158 # Append any target OS that is not already being enforced to the tuple.
1159 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001160 if config_dict.get('target_os_only', False):
1161 self._enforced_os = tuple(set(target_os))
1162 else:
1163 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1164
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001165 cache_dir = config_dict.get('cache_dir')
1166 if cache_dir:
1167 cache_dir = os.path.join(self.root_dir, cache_dir)
1168 cache_dir = os.path.abspath(cache_dir)
1169 gclient_scm.GitWrapper.cache_dir = cache_dir
1170 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001171
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001172 if not target_os and config_dict.get('target_os_only', False):
1173 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1174 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001175
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001176 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001177 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001178 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001179 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001180 self, s['name'], s['url'],
1181 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001182 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001183 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001184 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001185 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001186 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001187 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001188 except KeyError:
1189 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1190 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001191 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1192 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001193
1194 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001195 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001196 self._options.config_filename),
1197 self.config_content)
1198
1199 @staticmethod
1200 def LoadCurrentConfig(options):
1201 """Searches for and loads a .gclient file relative to the current working
1202 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001203 if options.spec:
1204 client = GClient('.', options)
1205 client.SetConfig(options.spec)
1206 else:
1207 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1208 if not path:
1209 return None
1210 client = GClient(path, options)
1211 client.SetConfig(gclient_utils.FileRead(
1212 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001213
1214 if (options.revisions and
1215 len(client.dependencies) > 1 and
1216 any('@' not in r for r in options.revisions)):
1217 print >> sys.stderr, (
1218 'You must specify the full solution name like --revision %s@%s\n'
1219 'when you have multiple solutions setup in your .gclient file.\n'
1220 'Other solutions present are: %s.') % (
1221 client.dependencies[0].name,
1222 options.revisions[0],
1223 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001224 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001225
nsylvain@google.comefc80932011-05-31 21:27:56 +00001226 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001227 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001228 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1229 'solution_name': solution_name,
1230 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001231 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001232 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001233 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001234 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001235 })
1236
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001237 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001238 """Creates a .gclient_entries file to record the list of unique checkouts.
1239
1240 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001241 """
1242 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1243 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001244 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001245 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001246 # Skip over File() dependencies as we can't version them.
1247 if not isinstance(entry.parsed_url, self.FileImpl):
1248 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1249 pprint.pformat(entry.parsed_url))
1250 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001251 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001252 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001253 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001254
1255 def _ReadEntries(self):
1256 """Read the .gclient_entries file for the given client.
1257
1258 Returns:
1259 A sequence of solution names, which will be empty if there is the
1260 entries file hasn't been created yet.
1261 """
1262 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001263 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001264 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001265 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001266 try:
1267 exec(gclient_utils.FileRead(filename), scope)
1268 except SyntaxError, e:
1269 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001270 return scope['entries']
1271
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001272 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001273 """Checks for revision overrides."""
1274 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001275 if self._options.head:
1276 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001277 # Do not check safesync_url if one or more --revision flag is specified.
1278 if not self._options.revisions:
1279 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001280 if not s.managed:
1281 self._options.revisions.append('%s@unmanaged' % s.name)
1282 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001283 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001284 if not self._options.revisions:
1285 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001286 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001287 index = 0
1288 for revision in self._options.revisions:
1289 if not '@' in revision:
1290 # Support for --revision 123
1291 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001292 name, rev = revision.split('@', 1)
1293 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001294 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001295 return revision_overrides
1296
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001297 def _ApplySafeSyncRev(self, dep):
1298 """Finds a valid revision from the content of the safesync_url and apply it
1299 by appending revisions to the revision list. Throws if revision appears to
1300 be invalid for the given |dep|."""
1301 assert len(dep.safesync_url) > 0
1302 handle = urllib.urlopen(dep.safesync_url)
1303 rev = handle.read().strip()
1304 handle.close()
1305 if not rev:
1306 raise gclient_utils.Error(
1307 'It appears your safesync_url (%s) is not working properly\n'
1308 '(as it returned an empty response). Check your config.' %
1309 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001310 scm = gclient_scm.CreateSCM(
1311 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001312 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001313 if self._options.verbose:
1314 print('Using safesync_url revision: %s.\n' % safe_rev)
1315 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1316
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001317 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001318 """Runs a command on each dependency in a client and its dependencies.
1319
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001320 Args:
1321 command: The command to use (e.g., 'status' or 'diff')
1322 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001323 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001324 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001325 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001326
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001327 revision_overrides = {}
1328 # It's unnecessary to check for revision overrides for 'recurse'.
1329 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001330 if command not in ('diff', 'recurse', 'runhooks', 'status'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001331 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001332 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001333 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001334 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001335 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001336 if command in ('update', 'revert'):
1337 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001338 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001339 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001340 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001341 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1342 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001343 for s in self.dependencies:
1344 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001345 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001346 if revision_overrides:
1347 print >> sys.stderr, ('Please fix your script, having invalid '
1348 '--revision flags will soon considered an error.')
piman@chromium.org6f363722010-04-27 00:41:09 +00001349
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001350 # Once all the dependencies have been processed, it's now safe to run the
1351 # hooks.
1352 if not self._options.nohooks:
1353 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001354
1355 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001356 # Notify the user if there is an orphaned entry in their working copy.
1357 # Only delete the directory if there are no changes in it, and
1358 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001359 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001360 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1361 for e in entries]
1362
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001363 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001364 if not prev_url:
1365 # entry must have been overridden via .gclient custom_deps
1366 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001367 # Fix path separator on Windows.
1368 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001369 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001370
1371 def _IsParentOfAny(parent, path_list):
1372 parent_plus_slash = parent + '/'
1373 return any(
1374 path[:len(parent_plus_slash)] == parent_plus_slash
1375 for path in path_list)
1376
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001377 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001378 if (entry not in entries and
1379 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001380 os.path.exists(e_dir)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001381 scm = gclient_scm.CreateSCM(
1382 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001383
1384 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001385 # The directory might be part of a git OR svn checkout.
1386 scm_root = None
1387 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1388 try:
1389 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1390 except subprocess2.CalledProcessError:
1391 pass
1392 if scm_root:
1393 break
1394 else:
1395 logging.warning('Could not find checkout root for %s. Unable to '
1396 'determine whether it is part of a higher-level '
1397 'checkout, so not removing.' % entry)
1398 continue
1399 if scm_root in full_entries:
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001400 logging.info('%s is part of a higher level checkout, not '
1401 'removing.', scm.GetCheckoutRoot())
1402 continue
1403
1404 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001405 scm.status(self._options, [], file_list)
1406 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001407 if (not self._options.delete_unversioned_trees or
1408 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001409 # There are modified files in this entry. Keep warning until
1410 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001411 print(('\nWARNING: \'%s\' is no longer part of this client. '
1412 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001413 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001414 else:
1415 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001416 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001417 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001418 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001419 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001420 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001421 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001422
1423 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001424 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001425 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001426 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001427 work_queue = gclient_utils.ExecutionQueue(
1428 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001429 for s in self.dependencies:
1430 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001431 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001432
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001433 def GetURLAndRev(dep):
1434 """Returns the revision-qualified SCM url for a Dependency."""
1435 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001436 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001437 if isinstance(dep.parsed_url, self.FileImpl):
1438 original_url = dep.parsed_url.file_location
1439 else:
1440 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001441 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001442 scm = gclient_scm.CreateSCM(
1443 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001444 if not os.path.isdir(scm.checkout_path):
1445 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001446 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001447
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001448 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001449 new_gclient = ''
1450 # First level at .gclient
1451 for d in self.dependencies:
1452 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001453 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001454 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001455 for d in dep.dependencies:
1456 entries[d.name] = GetURLAndRev(d)
1457 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001458 GrabDeps(d)
1459 custom_deps = []
1460 for k in sorted(entries.keys()):
1461 if entries[k]:
1462 # Quotes aren't escaped...
1463 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1464 else:
1465 custom_deps.append(' \"%s\": None,\n' % k)
1466 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1467 'solution_name': d.name,
1468 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001469 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001470 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001471 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001472 'solution_deps': ''.join(custom_deps),
1473 }
1474 # Print the snapshot configuration file
1475 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001476 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001477 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001478 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001479 if self._options.actual:
1480 entries[d.name] = GetURLAndRev(d)
1481 else:
1482 entries[d.name] = d.parsed_url
1483 keys = sorted(entries.keys())
1484 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001485 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001486 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001487
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001488 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001489 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001490 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001491
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001492 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001493 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001494 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001495 return self._root_dir
1496
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001497 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001498 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001499 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001500 return self._enforced_os
1501
maruel@chromium.org68988972011-09-20 14:11:42 +00001502 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001503 def recursion_limit(self):
1504 """How recursive can each dependencies in DEPS file can load DEPS file."""
1505 return self._recursion_limit
1506
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001507 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001508 def try_recursedeps(self):
1509 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001510 return True
1511
1512 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001513 def target_os(self):
1514 return self._enforced_os
1515
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001516
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001517#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001518
1519
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001520def CMDcleanup(parser, args):
1521 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001522
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001523 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1524 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001525 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1526 help='override deps for the specified (comma-separated) '
1527 'platform(s); \'all\' will process all deps_os '
1528 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001529 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001530 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001531 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001532 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001533 if options.verbose:
1534 # Print out the .gclient file. This is longer than if we just printed the
1535 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001536 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001537 return client.RunOnDeps('cleanup', args)
1538
1539
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001540@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001541def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001542 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001543
1544 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001545 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1546 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001547 """
1548 # Stop parsing at the first non-arg so that these go through to the command
1549 parser.disable_interspersed_args()
1550 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001551 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001552 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001553 help='Ignore non-zero return codes from subcommands.')
1554 parser.add_option('--prepend-dir', action='store_true',
1555 help='Prepend relative dir for use with git <cmd> --null.')
1556 parser.add_option('--no-progress', action='store_true',
1557 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001558 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001559 if not args:
1560 print >> sys.stderr, 'Need to supply a command!'
1561 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001562 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1563 if not root_and_entries:
1564 print >> sys.stderr, (
1565 'You need to run gclient sync at least once to use \'recurse\'.\n'
1566 'This is because .gclient_entries needs to exist and be up to date.')
1567 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001568
1569 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001570 scm_set = set()
1571 for scm in options.scm:
1572 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001573 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001574
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001575 options.nohooks = True
1576 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001577 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1578 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001579
1580
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001581@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001582def CMDfetch(parser, args):
1583 """Fetches upstream commits for all modules.
1584
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001585 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1586 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001587 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001588 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001589 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1590
1591
1592def CMDgrep(parser, args):
1593 """Greps through git repos managed by gclient.
1594
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001595 Runs 'git grep [args...]' for each module.
1596 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001597 # We can't use optparse because it will try to parse arguments sent
1598 # to git grep and throw an error. :-(
1599 if not args or re.match('(-h|--help)$', args[0]):
1600 print >> sys.stderr, (
1601 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1602 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1603 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1604 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1605 ' end of your query.'
1606 )
1607 return 1
1608
1609 jobs_arg = ['--jobs=1']
1610 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1611 jobs_arg, args = args[:1], args[1:]
1612 elif re.match(r'(-j|--jobs)$', args[0]):
1613 jobs_arg, args = args[:2], args[2:]
1614
1615 return CMDrecurse(
1616 parser,
1617 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1618 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001619
1620
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001621@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001622def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001623 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001624
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001625 This specifies the configuration for further commands. After update/sync,
1626 top-level DEPS files in each module are read to determine dependent
1627 modules to operate on as well. If optional [url] parameter is
1628 provided, then configuration is read from a specified Subversion server
1629 URL.
1630 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001631 # We do a little dance with the --gclientfile option. 'gclient config' is the
1632 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1633 # arguments. So, we temporarily stash any --gclientfile parameter into
1634 # options.output_config_file until after the (gclientfile xor spec) error
1635 # check.
1636 parser.remove_option('--gclientfile')
1637 parser.add_option('--gclientfile', dest='output_config_file',
1638 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001639 parser.add_option('--name',
1640 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001641 parser.add_option('--deps-file', default='DEPS',
1642 help='overrides the default name for the DEPS file for the'
1643 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001644 parser.add_option('--unmanaged', action='store_true', default=False,
1645 help='overrides the default behavior to make it possible '
1646 'to have the main solution untouched by gclient '
1647 '(gclient will check out unmanaged dependencies but '
1648 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001649 parser.add_option('--git-deps', action='store_true',
1650 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001651 parser.add_option('--cache-dir',
1652 help='(git only) Cache all git repos into this dir and do '
1653 'shared clones from the cache, instead of cloning '
1654 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001655 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001656 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001657 if options.output_config_file:
1658 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001659 if ((options.spec and args) or len(args) > 2 or
1660 (not options.spec and not args)):
1661 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1662
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001663 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001664 if options.spec:
1665 client.SetConfig(options.spec)
1666 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001667 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001668 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001669 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001670 if name.endswith('.git'):
1671 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001672 else:
1673 # specify an alternate relpath for the given URL.
1674 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001675 deps_file = options.deps_file
1676 if options.git_deps:
1677 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001678 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001679 if len(args) > 1:
1680 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001681 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001682 managed=not options.unmanaged,
1683 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001684 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001685 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001686
1687
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001688@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001689 gclient pack > patch.txt
1690 generate simple patch for configured client and dependences
1691""")
1692def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001693 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001694
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001695 Internally, runs 'svn diff'/'git diff' on each checked out module and
1696 dependencies, and performs minimal postprocessing of the output. The
1697 resulting patch is printed to stdout and can be applied to a freshly
1698 checked out tree via 'patch -p0 < patchfile'.
1699 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001700 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1701 help='override deps for the specified (comma-separated) '
1702 'platform(s); \'all\' will process all deps_os '
1703 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001704 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001705 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001706 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001707 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001708 client = GClient.LoadCurrentConfig(options)
1709 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001710 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001711 if options.verbose:
1712 # Print out the .gclient file. This is longer than if we just printed the
1713 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001714 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001715 return client.RunOnDeps('pack', args)
1716
1717
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001718def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001719 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001720 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1721 help='override deps for the specified (comma-separated) '
1722 'platform(s); \'all\' will process all deps_os '
1723 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001724 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001725 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001726 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001727 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001728 if options.verbose:
1729 # Print out the .gclient file. This is longer than if we just printed the
1730 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001731 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001732 return client.RunOnDeps('status', args)
1733
1734
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001735@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001736 gclient sync
1737 update files from SCM according to current configuration,
1738 *for modules which have changed since last update or sync*
1739 gclient sync --force
1740 update files from SCM according to current configuration, for
1741 all modules (useful for recovering files deleted from local copy)
1742 gclient sync --revision src@31000
1743 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001744
1745JSON output format:
1746If the --output-json option is specified, the following document structure will
1747be emitted to the provided file. 'null' entries may occur for subprojects which
1748are present in the gclient solution, but were not processed (due to custom_deps,
1749os_deps, etc.)
1750
1751{
1752 "solutions" : {
1753 "<name>": { # <name> is the posix-normalized path to the solution.
1754 "revision": [<svn rev int>|<git id hex string>|null],
1755 "scm": ["svn"|"git"|null],
1756 }
1757 }
1758}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001759""")
1760def CMDsync(parser, args):
1761 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001762 parser.add_option('-f', '--force', action='store_true',
1763 help='force update even for unchanged modules')
1764 parser.add_option('-n', '--nohooks', action='store_true',
1765 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001766 parser.add_option('-p', '--noprehooks', action='store_true',
1767 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001768 parser.add_option('-r', '--revision', action='append',
1769 dest='revisions', metavar='REV', default=[],
1770 help='Enforces revision/hash for the solutions with the '
1771 'format src@rev. The src@ part is optional and can be '
1772 'skipped. -r can be used multiple times when .gclient '
1773 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001774 'if the src@ part is skipped. Note that specifying '
1775 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001776 parser.add_option('--with_branch_heads', action='store_true',
1777 help='Clone git "branch_heads" refspecs in addition to '
1778 'the default refspecs. This adds about 1/2GB to a '
1779 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001780 parser.add_option('--with_tags', action='store_true',
1781 help='Clone git tags in addition to the default refspecs.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001782 parser.add_option('-t', '--transitive', action='store_true',
1783 help='When a revision is specified (in the DEPS file or '
1784 'with the command-line flag), transitively update '
1785 'the dependencies to the date of the given revision. '
1786 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001787 parser.add_option('-H', '--head', action='store_true',
1788 help='skips any safesync_urls specified in '
1789 'configured solutions and sync to head instead')
1790 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001791 help='Deletes from the working copy any dependencies that '
1792 'have been removed since the last sync, as long as '
1793 'there are no local modifications. When used with '
1794 '--force, such dependencies are removed even if they '
1795 'have local modifications. When used with --reset, '
1796 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001797 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001798 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001799 parser.add_option('-R', '--reset', action='store_true',
1800 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001801 parser.add_option('-M', '--merge', action='store_true',
1802 help='merge upstream changes instead of trying to '
1803 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001804 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1805 help='override deps for the specified (comma-separated) '
1806 'platform(s); \'all\' will process all deps_os '
1807 'references')
1808 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1809 help='Skip svn up whenever possible by requesting '
1810 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001811 parser.add_option('--upstream', action='store_true',
1812 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001813 parser.add_option('--output-json',
1814 help='Output a json document to this path containing '
1815 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001816 parser.add_option('--no-history', action='store_true',
1817 help='GIT ONLY - Reduces the size/time of the checkout at '
1818 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001819 parser.add_option('--shallow', action='store_true',
1820 help='GIT ONLY - Do a shallow clone into the cache dir. '
1821 'Requires Git 1.9+')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001822 parser.add_option('--ignore_locks', action='store_true',
1823 help='GIT ONLY - Ignore cache locks.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001824 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001825 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001826
1827 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001828 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001829
maruel@chromium.org307d1792010-05-31 20:03:13 +00001830 if options.revisions and options.head:
1831 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001832 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001833
1834 if options.verbose:
1835 # Print out the .gclient file. This is longer than if we just printed the
1836 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001837 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001838 ret = client.RunOnDeps('update', args)
1839 if options.output_json:
1840 slns = {}
1841 for d in client.subtree(True):
1842 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1843 slns[normed] = {
1844 'revision': d.got_revision,
1845 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001846 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001847 }
1848 with open(options.output_json, 'wb') as f:
1849 json.dump({'solutions': slns}, f)
1850 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001851
1852
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001853CMDupdate = CMDsync
1854
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001855
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001856def CMDdiff(parser, args):
1857 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001858 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1859 help='override deps for the specified (comma-separated) '
1860 'platform(s); \'all\' will process all deps_os '
1861 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001862 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001863 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001864 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001865 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001866 if options.verbose:
1867 # Print out the .gclient file. This is longer than if we just printed the
1868 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001869 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001870 return client.RunOnDeps('diff', args)
1871
1872
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001873def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001874 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001875
1876 That's the nuclear option to get back to a 'clean' state. It removes anything
1877 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001878 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1879 help='override deps for the specified (comma-separated) '
1880 'platform(s); \'all\' will process all deps_os '
1881 'references')
1882 parser.add_option('-n', '--nohooks', action='store_true',
1883 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001884 parser.add_option('-p', '--noprehooks', action='store_true',
1885 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001886 parser.add_option('--upstream', action='store_true',
1887 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001888 (options, args) = parser.parse_args(args)
1889 # --force is implied.
1890 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001891 options.reset = False
1892 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001893 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001894 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001895 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001896 return client.RunOnDeps('revert', args)
1897
1898
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001899def CMDrunhooks(parser, args):
1900 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001901 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1902 help='override deps for the specified (comma-separated) '
1903 'platform(s); \'all\' will process all deps_os '
1904 'references')
1905 parser.add_option('-f', '--force', action='store_true', default=True,
1906 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001907 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001908 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001909 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001910 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001911 if options.verbose:
1912 # Print out the .gclient file. This is longer than if we just printed the
1913 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001914 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001915 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001916 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001917 return client.RunOnDeps('runhooks', args)
1918
1919
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001920def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001921 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001922
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001923 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001924 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001925 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1926 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001927 commit can change.
1928 """
1929 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1930 help='override deps for the specified (comma-separated) '
1931 'platform(s); \'all\' will process all deps_os '
1932 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001933 parser.add_option('-a', '--actual', action='store_true',
1934 help='gets the actual checked out revisions instead of the '
1935 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001936 parser.add_option('-s', '--snapshot', action='store_true',
1937 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001938 'version of all repositories to reproduce the tree, '
1939 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001940 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001941 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001942 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001943 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001944 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001945 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001946
1947
szager@google.comb9a78d32012-03-13 18:46:21 +00001948def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001949 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001950 (options, args) = parser.parse_args(args)
1951 options.force = True
1952 client = GClient.LoadCurrentConfig(options)
1953 if not client:
1954 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1955 client.RunOnDeps(None, [])
1956 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1957 return 0
1958
1959
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001960class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00001961 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001962
1963 def __init__(self, **kwargs):
1964 optparse.OptionParser.__init__(
1965 self, version='%prog ' + __version__, **kwargs)
1966
1967 # Some arm boards have issues with parallel sync.
1968 if platform.machine().startswith('arm'):
1969 jobs = 1
1970 else:
1971 jobs = max(8, gclient_utils.NumLocalCpus())
1972 # cmp: 2013/06/19
1973 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00001974 # Bypassed if a bot_update flag is detected.
1975 if (os.environ.get('CHROME_HEADLESS') == '1' and
1976 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00001977 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001978
1979 self.add_option(
1980 '-j', '--jobs', default=jobs, type='int',
1981 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001982 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001983 self.add_option(
1984 '-v', '--verbose', action='count', default=0,
1985 help='Produces additional output for diagnostics. Can be used up to '
1986 'three times for more logging info.')
1987 self.add_option(
1988 '--gclientfile', dest='config_filename',
1989 help='Specify an alternate %s file' % self.gclientfile_default)
1990 self.add_option(
1991 '--spec',
1992 help='create a gclient file containing the provided string. Due to '
1993 'Cygwin/Python brokenness, it can\'t contain any newlines.')
1994 self.add_option(
1995 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00001996 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001997
1998 def parse_args(self, args=None, values=None):
1999 """Integrates standard options processing."""
2000 options, args = optparse.OptionParser.parse_args(self, args, values)
2001 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2002 logging.basicConfig(
2003 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002004 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002005 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002006 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002007 if (options.config_filename and
2008 options.config_filename != os.path.basename(options.config_filename)):
2009 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002010 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002011 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002012 options.entries_filename = options.config_filename + '_entries'
2013 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002014 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002015
2016 # These hacks need to die.
2017 if not hasattr(options, 'revisions'):
2018 # GClient.RunOnDeps expects it even if not applicable.
2019 options.revisions = []
2020 if not hasattr(options, 'head'):
2021 options.head = None
2022 if not hasattr(options, 'nohooks'):
2023 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002024 if not hasattr(options, 'noprehooks'):
2025 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002026 if not hasattr(options, 'deps_os'):
2027 options.deps_os = None
2028 if not hasattr(options, 'manually_grab_svn_rev'):
2029 options.manually_grab_svn_rev = None
2030 if not hasattr(options, 'force'):
2031 options.force = None
2032 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002033
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002034
2035def disable_buffering():
2036 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2037 # operations. Python as a strong tendency to buffer sys.stdout.
2038 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2039 # Make stdout annotated with the thread ids.
2040 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002041
2042
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002043def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002044 """Doesn't parse the arguments here, just find the right subcommand to
2045 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002046 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00002047 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002048 '\nYour python version %s is unsupported, please upgrade.\n' %
2049 sys.version.split(' ', 1)[0])
2050 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002051 if not sys.executable:
2052 print >> sys.stderr, (
2053 '\nPython cannot find the location of it\'s own executable.\n')
2054 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002055 fix_encoding.fix_encoding()
2056 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00002057 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002058 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002059 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002060 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002061 except KeyboardInterrupt:
2062 gclient_utils.GClientChildren.KillAllRemainingChildren()
2063 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00002064 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002065 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002066 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002067 finally:
2068 gclient_utils.PrintWarnings()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002069
2070
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002071if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002072 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002073
2074# vim: ts=2:sw=2:tw=80:et: