blob: 5fa356540598336da96504eaf4c36e5c995466c4 [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
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000100from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000101import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000102import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +0000103from third_party import colorama
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000104
105
maruel@chromium.org116704f2010-06-11 17:34:38 +0000106class GClientKeywords(object):
107 class FromImpl(object):
108 """Used to implement the From() syntax."""
109
110 def __init__(self, module_name, sub_target_name=None):
111 """module_name is the dep module we want to include from. It can also be
112 the name of a subdirectory to include from.
113
114 sub_target_name is an optional parameter if the module name in the other
115 DEPS file is different. E.g., you might want to map src/net to net."""
116 self.module_name = module_name
117 self.sub_target_name = sub_target_name
118
119 def __str__(self):
120 return 'From(%s, %s)' % (repr(self.module_name),
121 repr(self.sub_target_name))
122
maruel@chromium.org116704f2010-06-11 17:34:38 +0000123 class FileImpl(object):
124 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000125 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000126
127 def __init__(self, file_location):
128 self.file_location = file_location
129
130 def __str__(self):
131 return 'File("%s")' % self.file_location
132
133 def GetPath(self):
134 return os.path.split(self.file_location)[0]
135
136 def GetFilename(self):
137 rev_tokens = self.file_location.split('@')
138 return os.path.split(rev_tokens[0])[1]
139
140 def GetRevision(self):
141 rev_tokens = self.file_location.split('@')
142 if len(rev_tokens) > 1:
143 return rev_tokens[1]
144 return None
145
146 class VarImpl(object):
147 def __init__(self, custom_vars, local_scope):
148 self._custom_vars = custom_vars
149 self._local_scope = local_scope
150
151 def Lookup(self, var_name):
152 """Implements the Var syntax."""
153 if var_name in self._custom_vars:
154 return self._custom_vars[var_name]
155 elif var_name in self._local_scope.get("vars", {}):
156 return self._local_scope["vars"][var_name]
157 raise gclient_utils.Error("Var is not defined: %s" % var_name)
158
159
maruel@chromium.org064186c2011-09-27 23:53:33 +0000160class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000161 """Immutable configuration settings."""
162 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000163 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000164 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000165 GClientKeywords.__init__(self)
166
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000167 # These are not mutable:
168 self._parent = parent
169 self._safesync_url = safesync_url
170 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000171 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000172 # 'managed' determines whether or not this dependency is synced/updated by
173 # gclient after gclient checks it out initially. The difference between
174 # 'managed' and 'should_process' is that the user specifies 'managed' via
175 # the --unmanaged command-line flag or a .gclient config, where
176 # 'should_process' is dynamically set by gclient if it goes over its
177 # recursion limit and controls gclient's behavior so it does not misbehave.
178 self._managed = managed
179 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000180 # This is a mutable value that overrides the normal recursion limit for this
181 # dependency. It is read from the actual DEPS file so cannot be set on
182 # class instantiation.
183 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000184 # This is a mutable value which has the list of 'target_os' OSes listed in
185 # the current deps file.
186 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000187
188 # These are only set in .gclient and not in DEPS files.
189 self._custom_vars = custom_vars or {}
190 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000191 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000192
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000193 # TODO(iannucci): Remove this when all masters are correctly substituting
194 # the new blink url.
195 if (self._custom_vars.get('webkit_trunk', '') ==
196 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000197 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
198 print 'Overwriting Var("webkit_trunk") with %s' % new_url
199 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000200
maruel@chromium.org064186c2011-09-27 23:53:33 +0000201 # Post process the url to remove trailing slashes.
202 if isinstance(self._url, basestring):
203 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
204 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000205 self._url = self._url.replace('/@', '@')
206 elif not isinstance(self._url,
207 (self.FromImpl, self.FileImpl, None.__class__)):
208 raise gclient_utils.Error(
209 ('dependency url must be either a string, None, '
210 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000211 # Make any deps_file path platform-appropriate.
212 for sep in ['/', '\\']:
213 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214
215 @property
216 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000217 return self._deps_file
218
219 @property
220 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000221 return self._managed
222
223 @property
224 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 return self._parent
226
227 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000228 def root(self):
229 """Returns the root node, a GClient object."""
230 if not self.parent:
231 # This line is to signal pylint that it could be a GClient instance.
232 return self or GClient(None, None)
233 return self.parent.root
234
235 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000236 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000237 return self._safesync_url
238
239 @property
240 def should_process(self):
241 """True if this dependency should be processed, i.e. checked out."""
242 return self._should_process
243
244 @property
245 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000246 return self._custom_vars.copy()
247
248 @property
249 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000250 return self._custom_deps.copy()
251
maruel@chromium.org064186c2011-09-27 23:53:33 +0000252 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000253 def custom_hooks(self):
254 return self._custom_hooks[:]
255
256 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 def url(self):
258 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000259
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000260 @property
261 def recursion_limit(self):
262 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000263 if self.recursion_override is not None:
264 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000265 return max(self.parent.recursion_limit - 1, 0)
266
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000267 @property
268 def target_os(self):
269 if self.local_target_os is not None:
270 return tuple(set(self.local_target_os).union(self.parent.target_os))
271 else:
272 return self.parent.target_os
273
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000274 def get_custom_deps(self, name, url):
275 """Returns a custom deps if applicable."""
276 if self.parent:
277 url = self.parent.get_custom_deps(name, url)
278 # None is a valid return value to disable a dependency.
279 return self.custom_deps.get(name, url)
280
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281
282class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000283 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000284
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000285 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000286 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000287 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000288 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000289 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000290 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000291
292 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000293 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000294
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000295 self._pre_deps_hooks = []
296
maruel@chromium.org68988972011-09-20 14:11:42 +0000297 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000298 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000299 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000300 # A cache of the files affected by the current operation, necessary for
301 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000302 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000303 # If it is not set to True, the dependency wasn't processed for its child
304 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000305 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000306 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000307 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000308 # This dependency had its pre-DEPS hooks run
309 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000310 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000311 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000312 # This is the scm used to checkout self.url. It may be used by dependencies
313 # to get the datetime of the revision we checked out.
314 self._used_scm = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000315 # The actual revision we ended up getting, or None if that information is
316 # unavailable
317 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +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
358 def verify_validity(self):
359 """Verifies that this Dependency is fine to add as a child of another one.
360
361 Returns True if this entry should be added, False if it is a duplicate of
362 another entry.
363 """
364 logging.info('Dependency(%s).verify_validity()' % self.name)
365 if self.name in [s.name for s in self.parent.dependencies]:
366 raise gclient_utils.Error(
367 'The same name "%s" appears multiple times in the deps section' %
368 self.name)
369 if not self.should_process:
370 # Return early, no need to set requirements.
371 return True
372
373 # This require a full tree traversal with locks.
374 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
375 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000376 self_url = self.LateOverride(self.url)
377 sibling_url = sibling.LateOverride(sibling.url)
378 # Allow to have only one to be None or ''.
379 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000380 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000381 ('Dependency %s specified more than once:\n'
382 ' %s [%s]\n'
383 'vs\n'
384 ' %s [%s]') % (
385 self.name,
386 sibling.hierarchy(),
387 sibling_url,
388 self.hierarchy(),
389 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000390 # In theory we could keep it as a shadow of the other one. In
391 # practice, simply ignore it.
392 logging.warn('Won\'t process duplicate dependency %s' % sibling)
393 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000394 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000395
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000396 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000397 """Resolves the parsed url from url.
398
399 Manages From() keyword accordingly. Do not touch self.parsed_url nor
400 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000401 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000402 parsed_url = self.get_custom_deps(self.name, url)
403 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000404 logging.info(
405 'Dependency(%s).LateOverride(%s) -> %s' %
406 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000407 return parsed_url
408
409 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000410 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000411 ref = [
412 dep for dep in self.root.subtree(True) if url.module_name == dep.name
413 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000414 if not ref:
415 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
416 url.module_name, ref))
417 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000418 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000419 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000420 found_deps = [d for d in ref.dependencies if d.name == sub_target]
421 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000422 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000423 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
424 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000425 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000426
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000427 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000428 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000429 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000430 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000431 'Dependency(%s).LateOverride(%s) -> %s (From)' %
432 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000433 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000434
435 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000436 parsed_url = urlparse.urlparse(url)
437 if not parsed_url[0]:
438 # A relative url. Fetch the real base.
439 path = parsed_url[2]
440 if not path.startswith('/'):
441 raise gclient_utils.Error(
442 'relative DEPS entry \'%s\' must begin with a slash' % url)
443 # Create a scm just to query the full url.
444 parent_url = self.parent.parsed_url
445 if isinstance(parent_url, self.FileImpl):
446 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000447 scm = gclient_scm.CreateSCM(
448 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000449 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000451 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000452 logging.info(
453 'Dependency(%s).LateOverride(%s) -> %s' %
454 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000455 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000456
457 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000458 logging.info(
459 'Dependency(%s).LateOverride(%s) -> %s (File)' %
460 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000461 return url
462
463 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000464 logging.info(
465 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000466 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000467
468 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000469
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000470 @staticmethod
471 def MergeWithOsDeps(deps, deps_os, target_os_list):
472 """Returns a new "deps" structure that is the deps sent in updated
473 with information from deps_os (the deps_os section of the DEPS
474 file) that matches the list of target os."""
475 os_overrides = {}
476 for the_target_os in target_os_list:
477 the_target_os_deps = deps_os.get(the_target_os, {})
478 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
479 overrides = os_overrides.setdefault(os_dep_key, [])
480 overrides.append((the_target_os, os_dep_value))
481
482 # If any os didn't specify a value (we have fewer value entries
483 # than in the os list), then it wants to use the default value.
484 for os_dep_key, os_dep_value in os_overrides.iteritems():
485 if len(os_dep_value) != len(target_os_list):
486 # Record the default value too so that we don't accidently
487 # set it to None or miss a conflicting DEPS.
488 if os_dep_key in deps:
489 os_dep_value.append(('default', deps[os_dep_key]))
490
491 target_os_deps = {}
492 for os_dep_key, os_dep_value in os_overrides.iteritems():
493 # os_dep_value is a list of (os, value) pairs.
494 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
495 if not possible_values:
496 target_os_deps[os_dep_key] = None
497 else:
498 if len(possible_values) > 1:
499 # It would be possible to abort here but it would be
500 # unfortunate if we end up preventing any kind of checkout.
501 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
502 os_dep_key, os_dep_value, target_os_list)
503 # Sorting to get the same result every time in case of conflicts.
504 target_os_deps[os_dep_key] = sorted(possible_values)[0]
505
506 new_deps = deps.copy()
507 new_deps.update(target_os_deps)
508 return new_deps
509
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000510 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000511 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000512 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000513 assert not self.dependencies
514 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000515 local_scope = {}
516 var = self.VarImpl(self.custom_vars, local_scope)
517 global_scope = {
518 'File': self.FileImpl,
519 'From': self.FromImpl,
520 'Var': var.Lookup,
521 'deps_os': {},
522 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000523 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000524 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000525 logging.info(
526 'ParseDepsFile(%s): No %s file found at %s' % (
527 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000528 else:
529 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000530 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000531 # Eval the content.
532 try:
533 exec(deps_content, global_scope, local_scope)
534 except SyntaxError, e:
535 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000536 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000537 if 'recursion' in local_scope:
538 self.recursion_override = local_scope.get('recursion')
539 logging.warning(
540 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000541 # If present, save 'target_os' in the local_target_os property.
542 if 'target_os' in local_scope:
543 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000544 # load os specific dependencies if defined. these dependencies may
545 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000546 target_os_list = self.target_os
547 if 'deps_os' in local_scope and target_os_list:
548 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000549
maruel@chromium.org271375b2010-06-23 19:17:38 +0000550 # If a line is in custom_deps, but not in the solution, we want to append
551 # this line to the solution.
552 for d in self.custom_deps:
553 if d not in deps:
554 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000555
556 # If use_relative_paths is set in the DEPS file, regenerate
557 # the dictionary using paths relative to the directory containing
558 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000559 use_relative_paths = local_scope.get('use_relative_paths', False)
560 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000561 rel_deps = {}
562 for d, url in deps.items():
563 # normpath is required to allow DEPS to use .. in their
564 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000565 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
566 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000567
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000568 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000569 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000570 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000571 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000572 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000573 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000574 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000575 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000576
577 # override named sets of hooks by the custom hooks
578 hooks_to_run = []
579 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
580 for hook in local_scope.get('hooks', []):
581 if hook.get('name', '') not in hook_names_to_suppress:
582 hooks_to_run.append(hook)
583
584 # add the replacements and any additions
585 for hook in self.custom_hooks:
586 if 'action' in hook:
587 hooks_to_run.append(hook)
588
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000589 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
590 local_scope.get('pre_deps_hooks', [])]
591
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000592 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000593 logging.info('ParseDepsFile(%s) done' % self.name)
594
595 def add_dependencies_and_close(self, deps_to_add, hooks):
596 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000597 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000598 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000599 self.add_dependency(dep)
600 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000601
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000602 def maybeGetParentRevision(
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000603 self, command, options, parsed_url, parent_name, revision_overrides):
604 """Uses revision/timestamp of parent if no explicit revision was specified.
605
606 If we are performing an update and --transitive is set, use
607 - the parent's revision if 'self.url' is in the same repository
608 - the parent's timestamp otherwise
609 to update 'self.url'. The used revision/timestamp will be set in
610 'options.revision'.
611 If we have an explicit revision do nothing.
612 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000613 if command == 'update' and options.transitive and not options.revision:
614 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
615 if not revision:
616 options.revision = revision_overrides.get(parent_name)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000617 if (options.revision and
618 not gclient_utils.IsDateRevision(options.revision)):
619 assert self.parent and self.parent.used_scm
620 # If this dependency is in the same repository as parent it's url will
621 # start with a slash. If so we take the parent revision instead of
622 # it's timestamp.
623 # (The timestamps of commits in google code are broken -- which can
624 # result in dependencies to be checked out at the wrong revision)
625 if self.url.startswith('/'):
626 if options.verbose:
627 print('Using parent\'s revision %s since we are in the same '
628 'repository.' % options.revision)
629 else:
630 parent_revision_date = self.parent.used_scm.GetRevisionDate(
631 options.revision)
632 options.revision = gclient_utils.MakeDateRevision(
633 parent_revision_date)
634 if options.verbose:
635 print('Using parent\'s revision date %s since we are in a '
636 'different repository.' % options.revision)
637 revision_overrides[self.name] = options.revision
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000638
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000639 # Arguments number differs from overridden method
640 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000641 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000642 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000643 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000644 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000645 if not self.should_process:
646 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000647 # When running runhooks, there's no need to consult the SCM.
648 # All known hooks are expected to run unconditionally regardless of working
649 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000650 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000651 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000652 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000653 if run_scm and parsed_url:
654 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000655 # Special support for single-file checkout.
656 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000657 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
658 # pylint: disable=E1103
659 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000660 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000661 parsed_url.GetPath(), self.root.root_dir, self.name,
662 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000663 self._used_scm.RunCommand('updatesingle',
664 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000665 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000666 # Create a shallow copy to mutate revision.
667 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000668 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000669 self.maybeGetParentRevision(
670 command, options, parsed_url, self.parent.name, revision_overrides)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000671 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000672 parsed_url, self.root.root_dir, self.name, self.outbuf,
673 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000674 self._got_revision = self._used_scm.RunCommand(command, options, args,
675 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000676 if file_list:
677 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000678
679 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
680 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000681 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000682 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000683 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000684 continue
685 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000686 [self.root.root_dir.lower(), file_list[i].lower()])
687 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000688 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000689 while file_list[i].startswith(('\\', '/')):
690 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000691
692 # Always parse the DEPS file.
693 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000694 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000695 if command in ('update', 'revert') and not options.noprehooks:
696 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000697
698 if self.recursion_limit:
699 # Parse the dependencies of this dependency.
700 for s in self.dependencies:
701 work_queue.enqueue(s)
702
703 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000704 if not isinstance(parsed_url, self.FileImpl):
705 # Skip file only checkout.
706 scm = gclient_scm.GetScmName(parsed_url)
707 if not options.scm or scm in options.scm:
708 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000709 # Pass in the SCM type as an env variable. Make sure we don't put
710 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000711 env = os.environ.copy()
712 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000713 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000714 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000715 env['GCLIENT_URL'] = str(parsed_url)
716 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000717 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000718 print_stdout = False
719 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000720 """Git-specific path marshaling. It is optimized for git-grep."""
721
722 def mod_path(git_pathspec):
723 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
724 modified_path = os.path.join(self.name, match.group(2))
725 branch = match.group(1) or ''
726 return '%s%s' % (branch, modified_path)
727
728 match = re.match('^Binary file ([^\0]+) matches$', line)
729 if match:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000730 print 'Binary file %s matches\n' % mod_path(match.group(1))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000731 return
732
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000733 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000734 if len(items) == 2 and items[1]:
735 print '%s : %s' % (mod_path(items[0]), items[1])
736 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000737 # Multiple null bytes or a single trailing null byte indicate
738 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000739 print '\n'.join(mod_path(path) for path in items if path)
740 else:
741 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000742 else:
743 print_stdout = True
744 filter_fn = None
745
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000746 if parsed_url is None:
747 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
748 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000749 try:
750 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000751 args, cwd=cwd, env=env, print_stdout=print_stdout,
752 filter_fn=filter_fn,
753 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000754 except subprocess2.CalledProcessError:
755 if not options.ignore:
756 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000757 else:
758 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000759
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000760
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000761 @gclient_utils.lockedmethod
762 def _run_is_done(self, file_list, parsed_url):
763 # Both these are kept for hooks that are run as a separate tree traversal.
764 self._file_list = file_list
765 self._parsed_url = parsed_url
766 self._processed = True
767
szager@google.comb9a78d32012-03-13 18:46:21 +0000768 @staticmethod
769 def GetHookAction(hook_dict, matching_file_list):
770 """Turns a parsed 'hook' dict into an executable command."""
771 logging.debug(hook_dict)
772 logging.debug(matching_file_list)
773 command = hook_dict['action'][:]
774 if command[0] == 'python':
775 # If the hook specified "python" as the first item, the action is a
776 # Python script. Run it by starting a new copy of the same
777 # interpreter.
778 command[0] = sys.executable
779 if '$matching_files' in command:
780 splice_index = command.index('$matching_files')
781 command[splice_index:splice_index + 1] = matching_file_list
782 return command
783
784 def GetHooks(self, options):
785 """Evaluates all hooks, and return them in a flat list.
786
787 RunOnDeps() must have been called before to load the DEPS.
788 """
789 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000790 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000791 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000792 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000793 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000794 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000795 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000796 # TODO(maruel): If the user is using git or git-svn, then we don't know
797 # what files have changed so we always run all hooks. It'd be nice to fix
798 # that.
799 if (options.force or
800 isinstance(self.parsed_url, self.FileImpl) or
801 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000802 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000803 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000804 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000805 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000806 # Run hooks on the basis of whether the files from the gclient operation
807 # match each hook's pattern.
808 for hook_dict in self.deps_hooks:
809 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000810 matching_file_list = [
811 f for f in self.file_list_and_children if pattern.search(f)
812 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000813 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000814 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000815 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000816 result.extend(s.GetHooks(options))
817 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000818
szager@google.comb9a78d32012-03-13 18:46:21 +0000819 def RunHooksRecursively(self, options):
820 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000821 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000822 for hook in self.GetHooks(options):
823 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000824 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000825 gclient_utils.CheckCallAndFilterAndHeader(
826 hook, cwd=self.root.root_dir, always=True)
827 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
828 # Use a discrete exit status code of 2 to indicate that a hook action
829 # failed. Users of this script may wish to treat hook action failures
830 # differently from VC failures.
831 print >> sys.stderr, 'Error: %s' % str(e)
832 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000833 finally:
834 elapsed_time = time.time() - start_time
835 if elapsed_time > 10:
836 print "Hook '%s' took %.2f secs" % (
837 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000838
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000839 def RunPreDepsHooks(self):
840 assert self.processed
841 assert self.deps_parsed
842 assert not self.pre_deps_hooks_ran
843 assert not self.hooks_ran
844 for s in self.dependencies:
845 assert not s.processed
846 self._pre_deps_hooks_ran = True
847 for hook in self.pre_deps_hooks:
848 try:
849 start_time = time.time()
850 gclient_utils.CheckCallAndFilterAndHeader(
851 hook, cwd=self.root.root_dir, always=True)
852 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
853 # Use a discrete exit status code of 2 to indicate that a hook action
854 # failed. Users of this script may wish to treat hook action failures
855 # differently from VC failures.
856 print >> sys.stderr, 'Error: %s' % str(e)
857 sys.exit(2)
858 finally:
859 elapsed_time = time.time() - start_time
860 if elapsed_time > 10:
861 print "Hook '%s' took %.2f secs" % (
862 gclient_utils.CommandToStr(hook), elapsed_time)
863
864
maruel@chromium.org0d812442010-08-10 12:41:08 +0000865 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000866 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000867 dependencies = self.dependencies
868 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000869 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000870 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000871 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000872 for i in d.subtree(include_all):
873 yield i
874
875 def depth_first_tree(self):
876 """Depth-first recursion including the root node."""
877 yield self
878 for i in self.dependencies:
879 for j in i.depth_first_tree():
880 if j.should_process:
881 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000882
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000883 @gclient_utils.lockedmethod
884 def add_dependency(self, new_dep):
885 self._dependencies.append(new_dep)
886
887 @gclient_utils.lockedmethod
888 def _mark_as_parsed(self, new_hooks):
889 self._deps_hooks.extend(new_hooks)
890 self._deps_parsed = True
891
maruel@chromium.org68988972011-09-20 14:11:42 +0000892 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000893 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000894 def dependencies(self):
895 return tuple(self._dependencies)
896
897 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000898 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000899 def deps_hooks(self):
900 return tuple(self._deps_hooks)
901
902 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000903 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000904 def pre_deps_hooks(self):
905 return tuple(self._pre_deps_hooks)
906
907 @property
908 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000909 def parsed_url(self):
910 return self._parsed_url
911
912 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000913 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000914 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000915 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000916 return self._deps_parsed
917
918 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000919 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000920 def processed(self):
921 return self._processed
922
923 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000924 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000925 def pre_deps_hooks_ran(self):
926 return self._pre_deps_hooks_ran
927
928 @property
929 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000930 def hooks_ran(self):
931 return self._hooks_ran
932
933 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000934 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000935 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000936 return tuple(self._file_list)
937
938 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000939 def used_scm(self):
940 """SCMWrapper instance for this dependency or None if not processed yet."""
941 return self._used_scm
942
943 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000944 @gclient_utils.lockedmethod
945 def got_revision(self):
946 return self._got_revision
947
948 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000949 def file_list_and_children(self):
950 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000951 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000952 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000953 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000954
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000955 def __str__(self):
956 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000957 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000958 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000959 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000960 # First try the native property if it exists.
961 if hasattr(self, '_' + i):
962 value = getattr(self, '_' + i, False)
963 else:
964 value = getattr(self, i, False)
965 if value:
966 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000967
968 for d in self.dependencies:
969 out.extend([' ' + x for x in str(d).splitlines()])
970 out.append('')
971 return '\n'.join(out)
972
973 def __repr__(self):
974 return '%s: %s' % (self.name, self.url)
975
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000976 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000977 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000978 out = '%s(%s)' % (self.name, self.url)
979 i = self.parent
980 while i and i.name:
981 out = '%s(%s) -> %s' % (i.name, i.url, out)
982 i = i.parent
983 return out
984
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000985
986class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000987 """Object that represent a gclient checkout. A tree of Dependency(), one per
988 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000989
990 DEPS_OS_CHOICES = {
991 "win32": "win",
992 "win": "win",
993 "cygwin": "win",
994 "darwin": "mac",
995 "mac": "mac",
996 "unix": "unix",
997 "linux": "unix",
998 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000999 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001000 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001001 }
1002
1003 DEFAULT_CLIENT_FILE_TEXT = ("""\
1004solutions = [
1005 { "name" : "%(solution_name)s",
1006 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001007 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001008 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001009 "custom_deps" : {
1010 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001011 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001012 },
1013]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001014cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001015""")
1016
1017 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1018 { "name" : "%(solution_name)s",
1019 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001020 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001021 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001022 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001023%(solution_deps)s },
1024 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001025 },
1026""")
1027
1028 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1029# Snapshot generated with gclient revinfo --snapshot
1030solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001031%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001032""")
1033
1034 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001035 # Do not change previous behavior. Only solution level and immediate DEPS
1036 # are processed.
1037 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001038 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001039 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001040 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001041 if options.deps_os:
1042 enforced_os = options.deps_os.split(',')
1043 else:
1044 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1045 if 'all' in enforced_os:
1046 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001047 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001048 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001049 self.config_content = None
1050
borenet@google.com88d10082014-03-21 17:24:48 +00001051 def _CheckConfig(self):
1052 """Verify that the config matches the state of the existing checked-out
1053 solutions."""
1054 for dep in self.dependencies:
1055 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001056 scm = gclient_scm.CreateSCM(
1057 dep.url, self.root_dir, dep.name, self.outbuf)
borenet@google.com4e9be262014-04-08 19:40:30 +00001058 actual_url = scm.GetActualRemoteURL(self._options)
1059 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001060 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001061Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001062is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001063
borenet@google.com97882362014-04-07 20:06:02 +00001064The .gclient file contains:
1065%(expected_url)s (%(expected_scm)s)
1066
1067The local checkout in %(checkout_path)s reports:
1068%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001069
1070You should ensure that the URL listed in .gclient is correct and either change
1071it or fix the checkout. If you're managing your own git checkout in
1072%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1073want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001074''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1075 'expected_url': dep.url,
1076 'expected_scm': gclient_scm.GetScmName(dep.url),
1077 'actual_url': actual_url,
1078 'actual_scm': gclient_scm.GetScmName(actual_url)})
1079
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001080 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001081 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001082 config_dict = {}
1083 self.config_content = content
1084 try:
1085 exec(content, config_dict)
1086 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001087 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001088
peter@chromium.org1efccc82012-04-27 16:34:38 +00001089 # Append any target OS that is not already being enforced to the tuple.
1090 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001091 if config_dict.get('target_os_only', False):
1092 self._enforced_os = tuple(set(target_os))
1093 else:
1094 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1095
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001096 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
1097
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001098 if not target_os and config_dict.get('target_os_only', False):
1099 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1100 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001101
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001102 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001103 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001104 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001105 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001106 self, s['name'], s['url'],
1107 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001108 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001109 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001110 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001111 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001112 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001113 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001114 except KeyError:
1115 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1116 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001117 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1118 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001119
1120 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001121 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001122 self._options.config_filename),
1123 self.config_content)
1124
1125 @staticmethod
1126 def LoadCurrentConfig(options):
1127 """Searches for and loads a .gclient file relative to the current working
1128 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001129 if options.spec:
1130 client = GClient('.', options)
1131 client.SetConfig(options.spec)
1132 else:
1133 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1134 if not path:
1135 return None
1136 client = GClient(path, options)
1137 client.SetConfig(gclient_utils.FileRead(
1138 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001139
1140 if (options.revisions and
1141 len(client.dependencies) > 1 and
1142 any('@' not in r for r in options.revisions)):
1143 print >> sys.stderr, (
1144 'You must specify the full solution name like --revision %s@%s\n'
1145 'when you have multiple solutions setup in your .gclient file.\n'
1146 'Other solutions present are: %s.') % (
1147 client.dependencies[0].name,
1148 options.revisions[0],
1149 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001150 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001151
nsylvain@google.comefc80932011-05-31 21:27:56 +00001152 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001153 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001154 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1155 'solution_name': solution_name,
1156 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001157 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001158 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001159 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001160 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161 })
1162
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001163 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001164 """Creates a .gclient_entries file to record the list of unique checkouts.
1165
1166 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001167 """
1168 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1169 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001170 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001171 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001172 # Skip over File() dependencies as we can't version them.
1173 if not isinstance(entry.parsed_url, self.FileImpl):
1174 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1175 pprint.pformat(entry.parsed_url))
1176 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001177 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001178 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001179 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001180
1181 def _ReadEntries(self):
1182 """Read the .gclient_entries file for the given client.
1183
1184 Returns:
1185 A sequence of solution names, which will be empty if there is the
1186 entries file hasn't been created yet.
1187 """
1188 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001189 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001190 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001191 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001192 try:
1193 exec(gclient_utils.FileRead(filename), scope)
1194 except SyntaxError, e:
1195 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001196 return scope['entries']
1197
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001198 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001199 """Checks for revision overrides."""
1200 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001201 if self._options.head:
1202 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001203 # Do not check safesync_url if one or more --revision flag is specified.
1204 if not self._options.revisions:
1205 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001206 if not s.managed:
1207 self._options.revisions.append('%s@unmanaged' % s.name)
1208 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001209 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001210 if not self._options.revisions:
1211 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001212 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001213 index = 0
1214 for revision in self._options.revisions:
1215 if not '@' in revision:
1216 # Support for --revision 123
1217 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001218 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001219 if not sol in solutions_names:
1220 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1221 print >> sys.stderr, ('Please fix your script, having invalid '
1222 '--revision flags will soon considered an error.')
1223 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001224 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001225 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001226 return revision_overrides
1227
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001228 def _ApplySafeSyncRev(self, dep):
1229 """Finds a valid revision from the content of the safesync_url and apply it
1230 by appending revisions to the revision list. Throws if revision appears to
1231 be invalid for the given |dep|."""
1232 assert len(dep.safesync_url) > 0
1233 handle = urllib.urlopen(dep.safesync_url)
1234 rev = handle.read().strip()
1235 handle.close()
1236 if not rev:
1237 raise gclient_utils.Error(
1238 'It appears your safesync_url (%s) is not working properly\n'
1239 '(as it returned an empty response). Check your config.' %
1240 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001241 scm = gclient_scm.CreateSCM(
1242 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001243 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001244 if self._options.verbose:
1245 print('Using safesync_url revision: %s.\n' % safe_rev)
1246 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1247
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001248 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001249 """Runs a command on each dependency in a client and its dependencies.
1250
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001251 Args:
1252 command: The command to use (e.g., 'status' or 'diff')
1253 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001254 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001255 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001256 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001257
1258 self._CheckConfig()
1259
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001260 revision_overrides = {}
1261 # It's unnecessary to check for revision overrides for 'recurse'.
1262 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001263 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001264 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001265 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001266 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001267 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001268 if command in ('update', 'revert'):
1269 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001270 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001271 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001272 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001273 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1274 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001275 for s in self.dependencies:
1276 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001277 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001278
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001279 # Once all the dependencies have been processed, it's now safe to run the
1280 # hooks.
1281 if not self._options.nohooks:
1282 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001283
1284 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001285 # Notify the user if there is an orphaned entry in their working copy.
1286 # Only delete the directory if there are no changes in it, and
1287 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001288 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001289 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1290 for e in entries]
1291
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001292 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001293 if not prev_url:
1294 # entry must have been overridden via .gclient custom_deps
1295 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001296 # Fix path separator on Windows.
1297 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001298 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001299
1300 def _IsParentOfAny(parent, path_list):
1301 parent_plus_slash = parent + '/'
1302 return any(
1303 path[:len(parent_plus_slash)] == parent_plus_slash
1304 for path in path_list)
1305
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001306 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001307 if (entry not in entries and
1308 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001309 os.path.exists(e_dir)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001310 scm = gclient_scm.CreateSCM(
1311 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001312
1313 # Check to see if this directory is now part of a higher-up checkout.
1314 if scm.GetCheckoutRoot() in full_entries:
1315 logging.info('%s is part of a higher level checkout, not '
1316 'removing.', scm.GetCheckoutRoot())
1317 continue
1318
1319 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001320 scm.status(self._options, [], file_list)
1321 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001322 if (not self._options.delete_unversioned_trees or
1323 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001324 # There are modified files in this entry. Keep warning until
1325 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001326 print(('\nWARNING: \'%s\' is no longer part of this client. '
1327 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001328 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329 else:
1330 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001331 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001332 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001333 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001334 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001335 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001336 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001337
1338 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001339 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001340 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001341 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001342 work_queue = gclient_utils.ExecutionQueue(
1343 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001344 for s in self.dependencies:
1345 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001346 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001347
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001348 def GetURLAndRev(dep):
1349 """Returns the revision-qualified SCM url for a Dependency."""
1350 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001351 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001352 if isinstance(dep.parsed_url, self.FileImpl):
1353 original_url = dep.parsed_url.file_location
1354 else:
1355 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001356 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001357 scm = gclient_scm.CreateSCM(
1358 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001359 if not os.path.isdir(scm.checkout_path):
1360 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001361 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001362
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001363 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001364 new_gclient = ''
1365 # First level at .gclient
1366 for d in self.dependencies:
1367 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001368 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001369 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001370 for d in dep.dependencies:
1371 entries[d.name] = GetURLAndRev(d)
1372 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001373 GrabDeps(d)
1374 custom_deps = []
1375 for k in sorted(entries.keys()):
1376 if entries[k]:
1377 # Quotes aren't escaped...
1378 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1379 else:
1380 custom_deps.append(' \"%s\": None,\n' % k)
1381 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1382 'solution_name': d.name,
1383 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001384 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001385 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001386 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001387 'solution_deps': ''.join(custom_deps),
1388 }
1389 # Print the snapshot configuration file
1390 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001391 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001392 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001393 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001394 if self._options.actual:
1395 entries[d.name] = GetURLAndRev(d)
1396 else:
1397 entries[d.name] = d.parsed_url
1398 keys = sorted(entries.keys())
1399 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001400 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001401 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001402
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001403 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001404 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001405 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001406
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001407 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001408 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001409 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001410 return self._root_dir
1411
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001412 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001413 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001414 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001415 return self._enforced_os
1416
maruel@chromium.org68988972011-09-20 14:11:42 +00001417 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001418 def recursion_limit(self):
1419 """How recursive can each dependencies in DEPS file can load DEPS file."""
1420 return self._recursion_limit
1421
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001422 @property
1423 def target_os(self):
1424 return self._enforced_os
1425
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001426
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001427#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001428
1429
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001430def CMDcleanup(parser, args):
1431 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001432
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001433 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1434 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001435 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1436 help='override deps for the specified (comma-separated) '
1437 'platform(s); \'all\' will process all deps_os '
1438 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001439 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001440 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001441 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001442 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001443 if options.verbose:
1444 # Print out the .gclient file. This is longer than if we just printed the
1445 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001446 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001447 return client.RunOnDeps('cleanup', args)
1448
1449
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001450@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001451def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001452 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001453
1454 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001455 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1456 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001457 """
1458 # Stop parsing at the first non-arg so that these go through to the command
1459 parser.disable_interspersed_args()
1460 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001461 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001462 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001463 help='Ignore non-zero return codes from subcommands.')
1464 parser.add_option('--prepend-dir', action='store_true',
1465 help='Prepend relative dir for use with git <cmd> --null.')
1466 parser.add_option('--no-progress', action='store_true',
1467 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001468 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001469 if not args:
1470 print >> sys.stderr, 'Need to supply a command!'
1471 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001472 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1473 if not root_and_entries:
1474 print >> sys.stderr, (
1475 'You need to run gclient sync at least once to use \'recurse\'.\n'
1476 'This is because .gclient_entries needs to exist and be up to date.')
1477 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001478
1479 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001480 scm_set = set()
1481 for scm in options.scm:
1482 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001483 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001484
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001485 options.nohooks = True
1486 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001487 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1488 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001489
1490
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001491@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001492def CMDfetch(parser, args):
1493 """Fetches upstream commits for all modules.
1494
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001495 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1496 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001497 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001498 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001499 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1500
1501
1502def CMDgrep(parser, args):
1503 """Greps through git repos managed by gclient.
1504
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001505 Runs 'git grep [args...]' for each module.
1506 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001507 # We can't use optparse because it will try to parse arguments sent
1508 # to git grep and throw an error. :-(
1509 if not args or re.match('(-h|--help)$', args[0]):
1510 print >> sys.stderr, (
1511 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1512 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1513 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1514 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1515 ' end of your query.'
1516 )
1517 return 1
1518
1519 jobs_arg = ['--jobs=1']
1520 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1521 jobs_arg, args = args[:1], args[1:]
1522 elif re.match(r'(-j|--jobs)$', args[0]):
1523 jobs_arg, args = args[:2], args[2:]
1524
1525 return CMDrecurse(
1526 parser,
1527 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1528 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001529
1530
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001531@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001532def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001533 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001534
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001535 This specifies the configuration for further commands. After update/sync,
1536 top-level DEPS files in each module are read to determine dependent
1537 modules to operate on as well. If optional [url] parameter is
1538 provided, then configuration is read from a specified Subversion server
1539 URL.
1540 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001541 # We do a little dance with the --gclientfile option. 'gclient config' is the
1542 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1543 # arguments. So, we temporarily stash any --gclientfile parameter into
1544 # options.output_config_file until after the (gclientfile xor spec) error
1545 # check.
1546 parser.remove_option('--gclientfile')
1547 parser.add_option('--gclientfile', dest='output_config_file',
1548 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001549 parser.add_option('--name',
1550 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001551 parser.add_option('--deps-file', default='DEPS',
1552 help='overrides the default name for the DEPS file for the'
1553 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001554 parser.add_option('--unmanaged', action='store_true', default=False,
1555 help='overrides the default behavior to make it possible '
1556 'to have the main solution untouched by gclient '
1557 '(gclient will check out unmanaged dependencies but '
1558 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001559 parser.add_option('--git-deps', action='store_true',
1560 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001561 parser.add_option('--cache-dir',
1562 help='(git only) Cache all git repos into this dir and do '
1563 'shared clones from the cache, instead of cloning '
1564 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001565 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001566 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001567 if options.output_config_file:
1568 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001569 if ((options.spec and args) or len(args) > 2 or
1570 (not options.spec and not args)):
1571 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1572
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001573 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001574 if options.spec:
1575 client.SetConfig(options.spec)
1576 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001577 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001578 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001579 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001580 if name.endswith('.git'):
1581 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001582 else:
1583 # specify an alternate relpath for the given URL.
1584 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001585 deps_file = options.deps_file
1586 if options.git_deps:
1587 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001588 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001589 if len(args) > 1:
1590 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001591 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001592 managed=not options.unmanaged,
1593 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001594 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001595 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001596
1597
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001598@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001599 gclient pack > patch.txt
1600 generate simple patch for configured client and dependences
1601""")
1602def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001603 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001604
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001605 Internally, runs 'svn diff'/'git diff' on each checked out module and
1606 dependencies, and performs minimal postprocessing of the output. The
1607 resulting patch is printed to stdout and can be applied to a freshly
1608 checked out tree via 'patch -p0 < patchfile'.
1609 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001610 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1611 help='override deps for the specified (comma-separated) '
1612 'platform(s); \'all\' will process all deps_os '
1613 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001614 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001615 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001616 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001617 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001618 client = GClient.LoadCurrentConfig(options)
1619 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001620 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001621 if options.verbose:
1622 # Print out the .gclient file. This is longer than if we just printed the
1623 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001624 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001625 return client.RunOnDeps('pack', args)
1626
1627
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001628def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001629 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001630 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1631 help='override deps for the specified (comma-separated) '
1632 'platform(s); \'all\' will process all deps_os '
1633 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001634 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001635 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001636 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001637 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001638 if options.verbose:
1639 # Print out the .gclient file. This is longer than if we just printed the
1640 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001641 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001642 return client.RunOnDeps('status', args)
1643
1644
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001645@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001646 gclient sync
1647 update files from SCM according to current configuration,
1648 *for modules which have changed since last update or sync*
1649 gclient sync --force
1650 update files from SCM according to current configuration, for
1651 all modules (useful for recovering files deleted from local copy)
1652 gclient sync --revision src@31000
1653 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001654
1655JSON output format:
1656If the --output-json option is specified, the following document structure will
1657be emitted to the provided file. 'null' entries may occur for subprojects which
1658are present in the gclient solution, but were not processed (due to custom_deps,
1659os_deps, etc.)
1660
1661{
1662 "solutions" : {
1663 "<name>": { # <name> is the posix-normalized path to the solution.
1664 "revision": [<svn rev int>|<git id hex string>|null],
1665 "scm": ["svn"|"git"|null],
1666 }
1667 }
1668}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001669""")
1670def CMDsync(parser, args):
1671 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001672 parser.add_option('-f', '--force', action='store_true',
1673 help='force update even for unchanged modules')
1674 parser.add_option('-n', '--nohooks', action='store_true',
1675 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001676 parser.add_option('-p', '--noprehooks', action='store_true',
1677 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001678 parser.add_option('-r', '--revision', action='append',
1679 dest='revisions', metavar='REV', default=[],
1680 help='Enforces revision/hash for the solutions with the '
1681 'format src@rev. The src@ part is optional and can be '
1682 'skipped. -r can be used multiple times when .gclient '
1683 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001684 'if the src@ part is skipped. Note that specifying '
1685 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001686 parser.add_option('--with_branch_heads', action='store_true',
1687 help='Clone git "branch_heads" refspecs in addition to '
1688 'the default refspecs. This adds about 1/2GB to a '
1689 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001690 parser.add_option('-t', '--transitive', action='store_true',
1691 help='When a revision is specified (in the DEPS file or '
1692 'with the command-line flag), transitively update '
1693 'the dependencies to the date of the given revision. '
1694 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001695 parser.add_option('-H', '--head', action='store_true',
1696 help='skips any safesync_urls specified in '
1697 'configured solutions and sync to head instead')
1698 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001699 help='Deletes from the working copy any dependencies that '
1700 'have been removed since the last sync, as long as '
1701 'there are no local modifications. When used with '
1702 '--force, such dependencies are removed even if they '
1703 'have local modifications. When used with --reset, '
1704 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001705 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001706 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001707 parser.add_option('-R', '--reset', action='store_true',
1708 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001709 parser.add_option('-M', '--merge', action='store_true',
1710 help='merge upstream changes instead of trying to '
1711 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001712 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1713 help='override deps for the specified (comma-separated) '
1714 'platform(s); \'all\' will process all deps_os '
1715 'references')
1716 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1717 help='Skip svn up whenever possible by requesting '
1718 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001719 parser.add_option('--upstream', action='store_true',
1720 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001721 parser.add_option('--output-json',
1722 help='Output a json document to this path containing '
1723 'summary information about the sync.')
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
1727 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001728 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001729
maruel@chromium.org307d1792010-05-31 20:03:13 +00001730 if options.revisions and options.head:
1731 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001732 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001733
1734 if options.verbose:
1735 # Print out the .gclient file. This is longer than if we just printed the
1736 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001737 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001738 ret = client.RunOnDeps('update', args)
1739 if options.output_json:
1740 slns = {}
1741 for d in client.subtree(True):
1742 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1743 slns[normed] = {
1744 'revision': d.got_revision,
1745 'scm': d.used_scm.name if d.used_scm else None,
1746 }
1747 with open(options.output_json, 'wb') as f:
1748 json.dump({'solutions': slns}, f)
1749 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001750
1751
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001752CMDupdate = CMDsync
1753
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001754
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001755def CMDdiff(parser, args):
1756 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001757 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1758 help='override deps for the specified (comma-separated) '
1759 'platform(s); \'all\' will process all deps_os '
1760 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001761 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001762 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001763 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001764 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001765 if options.verbose:
1766 # Print out the .gclient file. This is longer than if we just printed the
1767 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001768 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001769 return client.RunOnDeps('diff', args)
1770
1771
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001772def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001773 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001774
1775 That's the nuclear option to get back to a 'clean' state. It removes anything
1776 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001777 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1778 help='override deps for the specified (comma-separated) '
1779 'platform(s); \'all\' will process all deps_os '
1780 'references')
1781 parser.add_option('-n', '--nohooks', action='store_true',
1782 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001783 parser.add_option('-p', '--noprehooks', action='store_true',
1784 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001785 parser.add_option('--upstream', action='store_true',
1786 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001787 (options, args) = parser.parse_args(args)
1788 # --force is implied.
1789 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001790 options.reset = False
1791 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001792 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001793 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001794 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001795 return client.RunOnDeps('revert', args)
1796
1797
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001798def CMDrunhooks(parser, args):
1799 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001800 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1801 help='override deps for the specified (comma-separated) '
1802 'platform(s); \'all\' will process all deps_os '
1803 'references')
1804 parser.add_option('-f', '--force', action='store_true', default=True,
1805 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001806 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001807 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001808 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001809 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001810 if options.verbose:
1811 # Print out the .gclient file. This is longer than if we just printed the
1812 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001813 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001814 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001815 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001816 return client.RunOnDeps('runhooks', args)
1817
1818
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001819def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001820 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001821
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001822 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001823 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001824 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1825 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001826 commit can change.
1827 """
1828 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1829 help='override deps for the specified (comma-separated) '
1830 'platform(s); \'all\' will process all deps_os '
1831 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001832 parser.add_option('-a', '--actual', action='store_true',
1833 help='gets the actual checked out revisions instead of the '
1834 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001835 parser.add_option('-s', '--snapshot', action='store_true',
1836 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001837 'version of all repositories to reproduce the tree, '
1838 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001839 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001840 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001841 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001842 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001843 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001844 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001845
1846
szager@google.comb9a78d32012-03-13 18:46:21 +00001847def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001848 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001849 (options, args) = parser.parse_args(args)
1850 options.force = True
1851 client = GClient.LoadCurrentConfig(options)
1852 if not client:
1853 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1854 client.RunOnDeps(None, [])
1855 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1856 return 0
1857
1858
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001859class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00001860 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001861
1862 def __init__(self, **kwargs):
1863 optparse.OptionParser.__init__(
1864 self, version='%prog ' + __version__, **kwargs)
1865
1866 # Some arm boards have issues with parallel sync.
1867 if platform.machine().startswith('arm'):
1868 jobs = 1
1869 else:
1870 jobs = max(8, gclient_utils.NumLocalCpus())
1871 # cmp: 2013/06/19
1872 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00001873 # Bypassed if a bot_update flag is detected.
1874 if (os.environ.get('CHROME_HEADLESS') == '1' and
1875 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00001876 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001877
1878 self.add_option(
1879 '-j', '--jobs', default=jobs, type='int',
1880 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001881 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001882 self.add_option(
1883 '-v', '--verbose', action='count', default=0,
1884 help='Produces additional output for diagnostics. Can be used up to '
1885 'three times for more logging info.')
1886 self.add_option(
1887 '--gclientfile', dest='config_filename',
1888 help='Specify an alternate %s file' % self.gclientfile_default)
1889 self.add_option(
1890 '--spec',
1891 help='create a gclient file containing the provided string. Due to '
1892 'Cygwin/Python brokenness, it can\'t contain any newlines.')
1893 self.add_option(
1894 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00001895 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001896
1897 def parse_args(self, args=None, values=None):
1898 """Integrates standard options processing."""
1899 options, args = optparse.OptionParser.parse_args(self, args, values)
1900 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
1901 logging.basicConfig(
1902 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00001903 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001904 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001905 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00001906 if (options.config_filename and
1907 options.config_filename != os.path.basename(options.config_filename)):
1908 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001909 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001910 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001911 options.entries_filename = options.config_filename + '_entries'
1912 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001913 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001914
1915 # These hacks need to die.
1916 if not hasattr(options, 'revisions'):
1917 # GClient.RunOnDeps expects it even if not applicable.
1918 options.revisions = []
1919 if not hasattr(options, 'head'):
1920 options.head = None
1921 if not hasattr(options, 'nohooks'):
1922 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001923 if not hasattr(options, 'noprehooks'):
1924 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00001925 if not hasattr(options, 'deps_os'):
1926 options.deps_os = None
1927 if not hasattr(options, 'manually_grab_svn_rev'):
1928 options.manually_grab_svn_rev = None
1929 if not hasattr(options, 'force'):
1930 options.force = None
1931 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001932
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001933
1934def disable_buffering():
1935 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1936 # operations. Python as a strong tendency to buffer sys.stdout.
1937 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
1938 # Make stdout annotated with the thread ids.
1939 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001940
1941
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001942def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001943 """Doesn't parse the arguments here, just find the right subcommand to
1944 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001945 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001946 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001947 '\nYour python version %s is unsupported, please upgrade.\n' %
1948 sys.version.split(' ', 1)[0])
1949 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001950 if not sys.executable:
1951 print >> sys.stderr, (
1952 '\nPython cannot find the location of it\'s own executable.\n')
1953 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001954 fix_encoding.fix_encoding()
1955 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001956 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001957 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001958 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001959 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001960 except KeyboardInterrupt:
1961 gclient_utils.GClientChildren.KillAllRemainingChildren()
1962 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001963 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001964 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001965 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00001966 finally:
1967 gclient_utils.PrintWarnings()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001968
1969
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001970if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001971 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001972
1973# vim: ts=2:sw=2:tw=80:et: