blob: 8d67b5a9eb1a3c4cc5d88ce297e9b32bd7390065 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org39c0b222013-08-17 16:57:01 +00006"""Meta checkout manager supporting both Subversion and GIT."""
7# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
67# processing the deps_os dict of a DEPS file.
68#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
maruel@chromium.org39c0b222013-08-17 16:57:01 +000079__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000081import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000082import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000083import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084import optparse
85import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000086import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000087import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000088import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000089import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000090import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000091import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000093import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000094
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000095import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000096
maruel@chromium.org35625c72011-03-23 17:34:02 +000097import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000098import gclient_scm
99import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000100import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000101from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000102import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000103import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +0000104from third_party import colorama
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000105
106
maruel@chromium.org116704f2010-06-11 17:34:38 +0000107class GClientKeywords(object):
108 class FromImpl(object):
109 """Used to implement the From() syntax."""
110
111 def __init__(self, module_name, sub_target_name=None):
112 """module_name is the dep module we want to include from. It can also be
113 the name of a subdirectory to include from.
114
115 sub_target_name is an optional parameter if the module name in the other
116 DEPS file is different. E.g., you might want to map src/net to net."""
117 self.module_name = module_name
118 self.sub_target_name = sub_target_name
119
120 def __str__(self):
121 return 'From(%s, %s)' % (repr(self.module_name),
122 repr(self.sub_target_name))
123
maruel@chromium.org116704f2010-06-11 17:34:38 +0000124 class FileImpl(object):
125 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000126 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000127
128 def __init__(self, file_location):
129 self.file_location = file_location
130
131 def __str__(self):
132 return 'File("%s")' % self.file_location
133
134 def GetPath(self):
135 return os.path.split(self.file_location)[0]
136
137 def GetFilename(self):
138 rev_tokens = self.file_location.split('@')
139 return os.path.split(rev_tokens[0])[1]
140
141 def GetRevision(self):
142 rev_tokens = self.file_location.split('@')
143 if len(rev_tokens) > 1:
144 return rev_tokens[1]
145 return None
146
147 class VarImpl(object):
148 def __init__(self, custom_vars, local_scope):
149 self._custom_vars = custom_vars
150 self._local_scope = local_scope
151
152 def Lookup(self, var_name):
153 """Implements the Var syntax."""
154 if var_name in self._custom_vars:
155 return self._custom_vars[var_name]
156 elif var_name in self._local_scope.get("vars", {}):
157 return self._local_scope["vars"][var_name]
158 raise gclient_utils.Error("Var is not defined: %s" % var_name)
159
160
maruel@chromium.org064186c2011-09-27 23:53:33 +0000161class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000162 """Immutable configuration settings."""
163 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000165 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000166 GClientKeywords.__init__(self)
167
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000168 # These are not mutable:
169 self._parent = parent
170 self._safesync_url = safesync_url
171 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000172 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000173 # 'managed' determines whether or not this dependency is synced/updated by
174 # gclient after gclient checks it out initially. The difference between
175 # 'managed' and 'should_process' is that the user specifies 'managed' via
176 # the --unmanaged command-line flag or a .gclient config, where
177 # 'should_process' is dynamically set by gclient if it goes over its
178 # recursion limit and controls gclient's behavior so it does not misbehave.
179 self._managed = managed
180 self._should_process = should_process
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000181 # This is a mutable value which has the list of 'target_os' OSes listed in
182 # the current deps file.
183 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000184
185 # These are only set in .gclient and not in DEPS files.
186 self._custom_vars = custom_vars or {}
187 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000188 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000189
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000190 # TODO(iannucci): Remove this when all masters are correctly substituting
191 # the new blink url.
192 if (self._custom_vars.get('webkit_trunk', '') ==
193 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000194 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
195 print 'Overwriting Var("webkit_trunk") with %s' % new_url
196 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000197
maruel@chromium.org064186c2011-09-27 23:53:33 +0000198 # Post process the url to remove trailing slashes.
199 if isinstance(self._url, basestring):
200 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
201 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000202 self._url = self._url.replace('/@', '@')
203 elif not isinstance(self._url,
204 (self.FromImpl, self.FileImpl, None.__class__)):
205 raise gclient_utils.Error(
206 ('dependency url must be either a string, None, '
207 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000208 # Make any deps_file path platform-appropriate.
209 for sep in ['/', '\\']:
210 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211
212 @property
213 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 return self._deps_file
215
216 @property
217 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218 return self._managed
219
220 @property
221 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222 return self._parent
223
224 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000225 def root(self):
226 """Returns the root node, a GClient object."""
227 if not self.parent:
228 # This line is to signal pylint that it could be a GClient instance.
229 return self or GClient(None, None)
230 return self.parent.root
231
232 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000233 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000234 return self._safesync_url
235
236 @property
237 def should_process(self):
238 """True if this dependency should be processed, i.e. checked out."""
239 return self._should_process
240
241 @property
242 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 return self._custom_vars.copy()
244
245 @property
246 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 return self._custom_deps.copy()
248
maruel@chromium.org064186c2011-09-27 23:53:33 +0000249 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000250 def custom_hooks(self):
251 return self._custom_hooks[:]
252
253 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000254 def url(self):
255 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000256
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000257 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000258 def target_os(self):
259 if self.local_target_os is not None:
260 return tuple(set(self.local_target_os).union(self.parent.target_os))
261 else:
262 return self.parent.target_os
263
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000264 def get_custom_deps(self, name, url):
265 """Returns a custom deps if applicable."""
266 if self.parent:
267 url = self.parent.get_custom_deps(name, url)
268 # None is a valid return value to disable a dependency.
269 return self.custom_deps.get(name, url)
270
maruel@chromium.org064186c2011-09-27 23:53:33 +0000271
272class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000273 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000274
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000275 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000276 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000277 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000278 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000279 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000280 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000281
282 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000283 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000284
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000285 self._pre_deps_hooks = []
286
maruel@chromium.org68988972011-09-20 14:11:42 +0000287 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000288 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000289 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000290 # A cache of the files affected by the current operation, necessary for
291 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000292 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000293 # If it is not set to True, the dependency wasn't processed for its child
294 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000295 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000296 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000297 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000298 # This dependency had its pre-DEPS hooks run
299 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000300 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000301 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000302 # This is the scm used to checkout self.url. It may be used by dependencies
303 # to get the datetime of the revision we checked out.
304 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000305 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000306 # The actual revision we ended up getting, or None if that information is
307 # unavailable
308 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000309
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000310 # This is a mutable value that overrides the normal recursion limit for this
311 # dependency. It is read from the actual DEPS file so cannot be set on
312 # class instantiation.
313 self.recursion_override = None
314 # recurselist is a mutable value that selectively overrides the default
315 # 'no recursion' setting on a dep-by-dep basis. It will replace
316 # recursion_override.
317 self.recurselist = None
318
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000319 if not self.name and self.parent:
320 raise gclient_utils.Error('Dependency without name')
321
maruel@chromium.org470b5432011-10-11 18:18:19 +0000322 @property
323 def requirements(self):
324 """Calculate the list of requirements."""
325 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000326 # self.parent is implicitly a requirement. This will be recursive by
327 # definition.
328 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000329 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000330
331 # For a tree with at least 2 levels*, the leaf node needs to depend
332 # on the level higher up in an orderly way.
333 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
334 # thus unsorted, while the .gclient format is a list thus sorted.
335 #
336 # * _recursion_limit is hard coded 2 and there is no hope to change this
337 # value.
338 #
339 # Interestingly enough, the following condition only works in the case we
340 # want: self is a 2nd level node. 3nd level node wouldn't need this since
341 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000342 if self.parent and self.parent.parent and not self.parent.parent.parent:
343 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000344
345 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000346 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000347
maruel@chromium.org470b5432011-10-11 18:18:19 +0000348 if self.name:
349 requirements |= set(
350 obj.name for obj in self.root.subtree(False)
351 if (obj is not self
352 and obj.name and
353 self.name.startswith(posixpath.join(obj.name, ''))))
354 requirements = tuple(sorted(requirements))
355 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
356 return requirements
357
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000358 @property
359 def try_recurselist(self):
360 """Returns False if recursion_override is ever specified."""
361 if self.recursion_override is not None:
362 return False
363 return self.parent.try_recurselist
364
365 @property
366 def recursion_limit(self):
367 """Returns > 0 if this dependency is not too recursed to be processed."""
368 # We continue to support the absence of recurselist until tools and DEPS
369 # using recursion_override are updated.
370 if self.try_recurselist and self.parent.recurselist != None:
371 if self.name in self.parent.recurselist:
372 return 1
373
374 if self.recursion_override is not None:
375 return self.recursion_override
376 return max(self.parent.recursion_limit - 1, 0)
377
maruel@chromium.org470b5432011-10-11 18:18:19 +0000378 def verify_validity(self):
379 """Verifies that this Dependency is fine to add as a child of another one.
380
381 Returns True if this entry should be added, False if it is a duplicate of
382 another entry.
383 """
384 logging.info('Dependency(%s).verify_validity()' % self.name)
385 if self.name in [s.name for s in self.parent.dependencies]:
386 raise gclient_utils.Error(
387 'The same name "%s" appears multiple times in the deps section' %
388 self.name)
389 if not self.should_process:
390 # Return early, no need to set requirements.
391 return True
392
393 # This require a full tree traversal with locks.
394 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
395 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000396 self_url = self.LateOverride(self.url)
397 sibling_url = sibling.LateOverride(sibling.url)
398 # Allow to have only one to be None or ''.
399 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000400 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000401 ('Dependency %s specified more than once:\n'
402 ' %s [%s]\n'
403 'vs\n'
404 ' %s [%s]') % (
405 self.name,
406 sibling.hierarchy(),
407 sibling_url,
408 self.hierarchy(),
409 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000410 # In theory we could keep it as a shadow of the other one. In
411 # practice, simply ignore it.
412 logging.warn('Won\'t process duplicate dependency %s' % sibling)
413 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000414 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000415
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000417 """Resolves the parsed url from url.
418
419 Manages From() keyword accordingly. Do not touch self.parsed_url nor
420 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000421 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000422 parsed_url = self.get_custom_deps(self.name, url)
423 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000424 logging.info(
425 'Dependency(%s).LateOverride(%s) -> %s' %
426 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000427 return parsed_url
428
429 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000430 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000431 ref = [
432 dep for dep in self.root.subtree(True) if url.module_name == dep.name
433 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000434 if not ref:
435 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
436 url.module_name, ref))
437 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000438 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000439 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000440 found_deps = [d for d in ref.dependencies if d.name == sub_target]
441 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000442 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000443 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
444 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000445 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000446
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000447 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000448 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000449 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000450 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000451 'Dependency(%s).LateOverride(%s) -> %s (From)' %
452 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000453 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000454
455 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000456 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000457 if (not parsed_url[0] and
458 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000459 # A relative url. Fetch the real base.
460 path = parsed_url[2]
461 if not path.startswith('/'):
462 raise gclient_utils.Error(
463 'relative DEPS entry \'%s\' must begin with a slash' % url)
464 # Create a scm just to query the full url.
465 parent_url = self.parent.parsed_url
466 if isinstance(parent_url, self.FileImpl):
467 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000468 scm = gclient_scm.CreateSCM(
469 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000470 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000472 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000473 logging.info(
474 'Dependency(%s).LateOverride(%s) -> %s' %
475 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000476 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000477
478 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000479 logging.info(
480 'Dependency(%s).LateOverride(%s) -> %s (File)' %
481 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000482 return url
483
484 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000485 logging.info(
486 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000487 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000488
489 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000490
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000491 @staticmethod
492 def MergeWithOsDeps(deps, deps_os, target_os_list):
493 """Returns a new "deps" structure that is the deps sent in updated
494 with information from deps_os (the deps_os section of the DEPS
495 file) that matches the list of target os."""
496 os_overrides = {}
497 for the_target_os in target_os_list:
498 the_target_os_deps = deps_os.get(the_target_os, {})
499 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
500 overrides = os_overrides.setdefault(os_dep_key, [])
501 overrides.append((the_target_os, os_dep_value))
502
503 # If any os didn't specify a value (we have fewer value entries
504 # than in the os list), then it wants to use the default value.
505 for os_dep_key, os_dep_value in os_overrides.iteritems():
506 if len(os_dep_value) != len(target_os_list):
507 # Record the default value too so that we don't accidently
508 # set it to None or miss a conflicting DEPS.
509 if os_dep_key in deps:
510 os_dep_value.append(('default', deps[os_dep_key]))
511
512 target_os_deps = {}
513 for os_dep_key, os_dep_value in os_overrides.iteritems():
514 # os_dep_value is a list of (os, value) pairs.
515 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
516 if not possible_values:
517 target_os_deps[os_dep_key] = None
518 else:
519 if len(possible_values) > 1:
520 # It would be possible to abort here but it would be
521 # unfortunate if we end up preventing any kind of checkout.
522 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
523 os_dep_key, os_dep_value, target_os_list)
524 # Sorting to get the same result every time in case of conflicts.
525 target_os_deps[os_dep_key] = sorted(possible_values)[0]
526
527 new_deps = deps.copy()
528 new_deps.update(target_os_deps)
529 return new_deps
530
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000531 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000532 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000533 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000534 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000535
536 deps_content = None
537 use_strict = False
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000538 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000539 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000540 logging.info(
541 'ParseDepsFile(%s): No %s file found at %s' % (
542 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000543 else:
544 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000545 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000546 use_strict = 'use strict' in deps_content.splitlines()[0]
547
548 local_scope = {}
549 if deps_content:
550 # One thing is unintuitive, vars = {} must happen before Var() use.
551 var = self.VarImpl(self.custom_vars, local_scope)
552 if use_strict:
553 logging.info(
554 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
555 global_scope = {
556 '__builtins__': {'None': None},
557 'Var': var.Lookup,
558 'deps_os': {},
559 }
560 else:
561 global_scope = {
562 'File': self.FileImpl,
563 'From': self.FromImpl,
564 'Var': var.Lookup,
565 'deps_os': {},
566 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000567 # Eval the content.
568 try:
569 exec(deps_content, global_scope, local_scope)
570 except SyntaxError, e:
571 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000572 if use_strict:
573 for key, val in local_scope.iteritems():
574 if not isinstance(val, (dict, list, tuple, str)):
575 raise gclient_utils.Error(
576 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
577 (self.name, key, val))
578
maruel@chromium.org271375b2010-06-23 19:17:38 +0000579 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000580 if 'recursion' in local_scope:
581 self.recursion_override = local_scope.get('recursion')
582 logging.warning(
583 'Setting %s recursion to %d.', self.name, self.recursion_limit)
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000584 self.recurselist = local_scope.get('recurselist', None)
585 if 'recurselist' in local_scope:
586 logging.warning('Found recurselist %r.', repr(self.recurselist))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000587 # If present, save 'target_os' in the local_target_os property.
588 if 'target_os' in local_scope:
589 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000590 # load os specific dependencies if defined. these dependencies may
591 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000592 target_os_list = self.target_os
593 if 'deps_os' in local_scope and target_os_list:
594 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000595
maruel@chromium.org271375b2010-06-23 19:17:38 +0000596 # If a line is in custom_deps, but not in the solution, we want to append
597 # this line to the solution.
598 for d in self.custom_deps:
599 if d not in deps:
600 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000601
602 # If use_relative_paths is set in the DEPS file, regenerate
603 # the dictionary using paths relative to the directory containing
604 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000605 use_relative_paths = local_scope.get('use_relative_paths', False)
606 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000607 rel_deps = {}
608 for d, url in deps.items():
609 # normpath is required to allow DEPS to use .. in their
610 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000611 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
612 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000613
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000614 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000615 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000616 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000617 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000618 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000619 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000620 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000621 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000622
623 # override named sets of hooks by the custom hooks
624 hooks_to_run = []
625 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
626 for hook in local_scope.get('hooks', []):
627 if hook.get('name', '') not in hook_names_to_suppress:
628 hooks_to_run.append(hook)
629
630 # add the replacements and any additions
631 for hook in self.custom_hooks:
632 if 'action' in hook:
633 hooks_to_run.append(hook)
634
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000635 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
636 local_scope.get('pre_deps_hooks', [])]
637
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000638 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000639 logging.info('ParseDepsFile(%s) done' % self.name)
640
641 def add_dependencies_and_close(self, deps_to_add, hooks):
642 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000643 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000644 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000645 self.add_dependency(dep)
646 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000647
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000648 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000649 """Uses revision/timestamp of parent if no explicit revision was specified.
650
651 If we are performing an update and --transitive is set, use
652 - the parent's revision if 'self.url' is in the same repository
653 - the parent's timestamp otherwise
654 to update 'self.url'. The used revision/timestamp will be set in
655 'options.revision'.
656 If we have an explicit revision do nothing.
657 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000658 if command == 'update' and options.transitive and not options.revision:
659 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
660 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000661 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000662 if (options.revision and
663 not gclient_utils.IsDateRevision(options.revision)):
664 assert self.parent and self.parent.used_scm
665 # If this dependency is in the same repository as parent it's url will
666 # start with a slash. If so we take the parent revision instead of
667 # it's timestamp.
668 # (The timestamps of commits in google code are broken -- which can
669 # result in dependencies to be checked out at the wrong revision)
670 if self.url.startswith('/'):
671 if options.verbose:
672 print('Using parent\'s revision %s since we are in the same '
673 'repository.' % options.revision)
674 else:
675 parent_revision_date = self.parent.used_scm.GetRevisionDate(
676 options.revision)
677 options.revision = gclient_utils.MakeDateRevision(
678 parent_revision_date)
679 if options.verbose:
680 print('Using parent\'s revision date %s since we are in a '
681 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000682
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000683 # Arguments number differs from overridden method
684 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000685 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000686 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000687 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000688 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000689 if not self.should_process:
690 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000691 # When running runhooks, there's no need to consult the SCM.
692 # All known hooks are expected to run unconditionally regardless of working
693 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000694 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000695 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000696 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000697 if run_scm and parsed_url:
698 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000699 # Special support for single-file checkout.
700 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000701 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
702 # pylint: disable=E1103
703 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000704 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000705 parsed_url.GetPath(), self.root.root_dir, self.name,
706 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000707 self._used_scm.RunCommand('updatesingle',
708 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000709 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000710 # Create a shallow copy to mutate revision.
711 options = copy.copy(options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000712 options.revision = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000713 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000714 command, options, parsed_url, self.parent)
715 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000716 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000717 parsed_url, self.root.root_dir, self.name, self.outbuf,
718 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000719 self._got_revision = self._used_scm.RunCommand(command, options, args,
720 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000721 if file_list:
722 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000723
724 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
725 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000726 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000727 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000728 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000729 continue
730 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000731 [self.root.root_dir.lower(), file_list[i].lower()])
732 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000733 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000734 while file_list[i].startswith(('\\', '/')):
735 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000736
737 # Always parse the DEPS file.
738 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000739 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000740 if command in ('update', 'revert') and not options.noprehooks:
741 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000742
743 if self.recursion_limit:
744 # Parse the dependencies of this dependency.
745 for s in self.dependencies:
746 work_queue.enqueue(s)
747
748 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000749 if not isinstance(parsed_url, self.FileImpl):
750 # Skip file only checkout.
751 scm = gclient_scm.GetScmName(parsed_url)
752 if not options.scm or scm in options.scm:
753 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000754 # Pass in the SCM type as an env variable. Make sure we don't put
755 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000756 env = os.environ.copy()
757 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000758 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000759 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000760 env['GCLIENT_URL'] = str(parsed_url)
761 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000762 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000763 print_stdout = False
764 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000765 """Git-specific path marshaling. It is optimized for git-grep."""
766
767 def mod_path(git_pathspec):
768 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
769 modified_path = os.path.join(self.name, match.group(2))
770 branch = match.group(1) or ''
771 return '%s%s' % (branch, modified_path)
772
773 match = re.match('^Binary file ([^\0]+) matches$', line)
774 if match:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000775 print 'Binary file %s matches\n' % mod_path(match.group(1))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000776 return
777
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000778 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000779 if len(items) == 2 and items[1]:
780 print '%s : %s' % (mod_path(items[0]), items[1])
781 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000782 # Multiple null bytes or a single trailing null byte indicate
783 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000784 print '\n'.join(mod_path(path) for path in items if path)
785 else:
786 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000787 else:
788 print_stdout = True
789 filter_fn = None
790
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000791 if parsed_url is None:
792 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
793 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000794 try:
795 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000796 args, cwd=cwd, env=env, print_stdout=print_stdout,
797 filter_fn=filter_fn,
798 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000799 except subprocess2.CalledProcessError:
800 if not options.ignore:
801 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000802 else:
803 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000804
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000806 @gclient_utils.lockedmethod
807 def _run_is_done(self, file_list, parsed_url):
808 # Both these are kept for hooks that are run as a separate tree traversal.
809 self._file_list = file_list
810 self._parsed_url = parsed_url
811 self._processed = True
812
szager@google.comb9a78d32012-03-13 18:46:21 +0000813 @staticmethod
814 def GetHookAction(hook_dict, matching_file_list):
815 """Turns a parsed 'hook' dict into an executable command."""
816 logging.debug(hook_dict)
817 logging.debug(matching_file_list)
818 command = hook_dict['action'][:]
819 if command[0] == 'python':
820 # If the hook specified "python" as the first item, the action is a
821 # Python script. Run it by starting a new copy of the same
822 # interpreter.
823 command[0] = sys.executable
824 if '$matching_files' in command:
825 splice_index = command.index('$matching_files')
826 command[splice_index:splice_index + 1] = matching_file_list
827 return command
828
829 def GetHooks(self, options):
830 """Evaluates all hooks, and return them in a flat list.
831
832 RunOnDeps() must have been called before to load the DEPS.
833 """
834 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000835 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000836 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000837 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000838 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000839 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000840 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000841 # TODO(maruel): If the user is using git or git-svn, then we don't know
842 # what files have changed so we always run all hooks. It'd be nice to fix
843 # that.
844 if (options.force or
845 isinstance(self.parsed_url, self.FileImpl) or
846 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000847 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000848 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000849 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000850 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000851 # Run hooks on the basis of whether the files from the gclient operation
852 # match each hook's pattern.
853 for hook_dict in self.deps_hooks:
854 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000855 matching_file_list = [
856 f for f in self.file_list_and_children if pattern.search(f)
857 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000858 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000859 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000860 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000861 result.extend(s.GetHooks(options))
862 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863
szager@google.comb9a78d32012-03-13 18:46:21 +0000864 def RunHooksRecursively(self, options):
865 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000866 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000867 for hook in self.GetHooks(options):
868 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000869 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000870 gclient_utils.CheckCallAndFilterAndHeader(
871 hook, cwd=self.root.root_dir, always=True)
872 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
873 # Use a discrete exit status code of 2 to indicate that a hook action
874 # failed. Users of this script may wish to treat hook action failures
875 # differently from VC failures.
876 print >> sys.stderr, 'Error: %s' % str(e)
877 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000878 finally:
879 elapsed_time = time.time() - start_time
880 if elapsed_time > 10:
881 print "Hook '%s' took %.2f secs" % (
882 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000883
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000884 def RunPreDepsHooks(self):
885 assert self.processed
886 assert self.deps_parsed
887 assert not self.pre_deps_hooks_ran
888 assert not self.hooks_ran
889 for s in self.dependencies:
890 assert not s.processed
891 self._pre_deps_hooks_ran = True
892 for hook in self.pre_deps_hooks:
893 try:
894 start_time = time.time()
895 gclient_utils.CheckCallAndFilterAndHeader(
896 hook, cwd=self.root.root_dir, always=True)
897 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
898 # Use a discrete exit status code of 2 to indicate that a hook action
899 # failed. Users of this script may wish to treat hook action failures
900 # differently from VC failures.
901 print >> sys.stderr, 'Error: %s' % str(e)
902 sys.exit(2)
903 finally:
904 elapsed_time = time.time() - start_time
905 if elapsed_time > 10:
906 print "Hook '%s' took %.2f secs" % (
907 gclient_utils.CommandToStr(hook), elapsed_time)
908
909
maruel@chromium.org0d812442010-08-10 12:41:08 +0000910 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000911 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000912 dependencies = self.dependencies
913 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000914 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000915 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000916 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000917 for i in d.subtree(include_all):
918 yield i
919
920 def depth_first_tree(self):
921 """Depth-first recursion including the root node."""
922 yield self
923 for i in self.dependencies:
924 for j in i.depth_first_tree():
925 if j.should_process:
926 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000927
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000928 @gclient_utils.lockedmethod
929 def add_dependency(self, new_dep):
930 self._dependencies.append(new_dep)
931
932 @gclient_utils.lockedmethod
933 def _mark_as_parsed(self, new_hooks):
934 self._deps_hooks.extend(new_hooks)
935 self._deps_parsed = True
936
maruel@chromium.org68988972011-09-20 14:11:42 +0000937 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000938 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000939 def dependencies(self):
940 return tuple(self._dependencies)
941
942 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000943 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000944 def deps_hooks(self):
945 return tuple(self._deps_hooks)
946
947 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000948 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000949 def pre_deps_hooks(self):
950 return tuple(self._pre_deps_hooks)
951
952 @property
953 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000954 def parsed_url(self):
955 return self._parsed_url
956
957 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000958 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000959 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000960 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000961 return self._deps_parsed
962
963 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000964 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000965 def processed(self):
966 return self._processed
967
968 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000969 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000970 def pre_deps_hooks_ran(self):
971 return self._pre_deps_hooks_ran
972
973 @property
974 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000975 def hooks_ran(self):
976 return self._hooks_ran
977
978 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000979 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000980 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000981 return tuple(self._file_list)
982
983 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000984 def used_scm(self):
985 """SCMWrapper instance for this dependency or None if not processed yet."""
986 return self._used_scm
987
988 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000989 @gclient_utils.lockedmethod
990 def got_revision(self):
991 return self._got_revision
992
993 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000994 def file_list_and_children(self):
995 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000996 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000997 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000998 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000999
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001000 def __str__(self):
1001 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001002 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001003 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001004 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001005 # First try the native property if it exists.
1006 if hasattr(self, '_' + i):
1007 value = getattr(self, '_' + i, False)
1008 else:
1009 value = getattr(self, i, False)
1010 if value:
1011 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001012
1013 for d in self.dependencies:
1014 out.extend([' ' + x for x in str(d).splitlines()])
1015 out.append('')
1016 return '\n'.join(out)
1017
1018 def __repr__(self):
1019 return '%s: %s' % (self.name, self.url)
1020
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001021 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001022 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001023 out = '%s(%s)' % (self.name, self.url)
1024 i = self.parent
1025 while i and i.name:
1026 out = '%s(%s) -> %s' % (i.name, i.url, out)
1027 i = i.parent
1028 return out
1029
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001030
1031class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001032 """Object that represent a gclient checkout. A tree of Dependency(), one per
1033 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001034
1035 DEPS_OS_CHOICES = {
1036 "win32": "win",
1037 "win": "win",
1038 "cygwin": "win",
1039 "darwin": "mac",
1040 "mac": "mac",
1041 "unix": "unix",
1042 "linux": "unix",
1043 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001044 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001045 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001046 }
1047
1048 DEFAULT_CLIENT_FILE_TEXT = ("""\
1049solutions = [
1050 { "name" : "%(solution_name)s",
1051 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001052 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001053 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001054 "custom_deps" : {
1055 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001056 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001057 },
1058]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001059cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001060""")
1061
1062 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1063 { "name" : "%(solution_name)s",
1064 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001065 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001066 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001067 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001068%(solution_deps)s },
1069 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001070 },
1071""")
1072
1073 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1074# Snapshot generated with gclient revinfo --snapshot
1075solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001076%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001077""")
1078
1079 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001080 # Do not change previous behavior. Only solution level and immediate DEPS
1081 # are processed.
1082 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001083 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001084 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001085 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001086 if options.deps_os:
1087 enforced_os = options.deps_os.split(',')
1088 else:
1089 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1090 if 'all' in enforced_os:
1091 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001092 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001093 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001094 self.config_content = None
1095
borenet@google.com88d10082014-03-21 17:24:48 +00001096 def _CheckConfig(self):
1097 """Verify that the config matches the state of the existing checked-out
1098 solutions."""
1099 for dep in self.dependencies:
1100 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001101 scm = gclient_scm.CreateSCM(
1102 dep.url, self.root_dir, dep.name, self.outbuf)
borenet@google.com4e9be262014-04-08 19:40:30 +00001103 actual_url = scm.GetActualRemoteURL(self._options)
1104 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001105 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001106Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001107is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001108
borenet@google.com97882362014-04-07 20:06:02 +00001109The .gclient file contains:
1110%(expected_url)s (%(expected_scm)s)
1111
1112The local checkout in %(checkout_path)s reports:
1113%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001114
1115You should ensure that the URL listed in .gclient is correct and either change
1116it or fix the checkout. If you're managing your own git checkout in
1117%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1118want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001119''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1120 'expected_url': dep.url,
1121 'expected_scm': gclient_scm.GetScmName(dep.url),
1122 'actual_url': actual_url,
1123 'actual_scm': gclient_scm.GetScmName(actual_url)})
1124
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001125 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001126 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001127 config_dict = {}
1128 self.config_content = content
1129 try:
1130 exec(content, config_dict)
1131 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001132 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001133
peter@chromium.org1efccc82012-04-27 16:34:38 +00001134 # Append any target OS that is not already being enforced to the tuple.
1135 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001136 if config_dict.get('target_os_only', False):
1137 self._enforced_os = tuple(set(target_os))
1138 else:
1139 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1140
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001141 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
szager@chromium.org848fd492014-04-09 19:06:44 +00001142 git_cache.Mirror.SetCachePath(config_dict.get('cache_dir'))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001143
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001144 if not target_os and config_dict.get('target_os_only', False):
1145 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1146 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001147
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001148 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001149 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001150 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001151 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001152 self, s['name'], s['url'],
1153 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001154 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001155 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001156 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001157 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001158 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001159 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001160 except KeyError:
1161 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1162 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001163 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1164 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001165
1166 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001167 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001168 self._options.config_filename),
1169 self.config_content)
1170
1171 @staticmethod
1172 def LoadCurrentConfig(options):
1173 """Searches for and loads a .gclient file relative to the current working
1174 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001175 if options.spec:
1176 client = GClient('.', options)
1177 client.SetConfig(options.spec)
1178 else:
1179 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1180 if not path:
1181 return None
1182 client = GClient(path, options)
1183 client.SetConfig(gclient_utils.FileRead(
1184 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001185
1186 if (options.revisions and
1187 len(client.dependencies) > 1 and
1188 any('@' not in r for r in options.revisions)):
1189 print >> sys.stderr, (
1190 'You must specify the full solution name like --revision %s@%s\n'
1191 'when you have multiple solutions setup in your .gclient file.\n'
1192 'Other solutions present are: %s.') % (
1193 client.dependencies[0].name,
1194 options.revisions[0],
1195 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001196 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001197
nsylvain@google.comefc80932011-05-31 21:27:56 +00001198 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001199 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001200 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1201 'solution_name': solution_name,
1202 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001203 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001204 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001205 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001206 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001207 })
1208
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001209 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001210 """Creates a .gclient_entries file to record the list of unique checkouts.
1211
1212 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001213 """
1214 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1215 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001216 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001217 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001218 # Skip over File() dependencies as we can't version them.
1219 if not isinstance(entry.parsed_url, self.FileImpl):
1220 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1221 pprint.pformat(entry.parsed_url))
1222 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001223 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001224 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001225 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001226
1227 def _ReadEntries(self):
1228 """Read the .gclient_entries file for the given client.
1229
1230 Returns:
1231 A sequence of solution names, which will be empty if there is the
1232 entries file hasn't been created yet.
1233 """
1234 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001235 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001236 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001237 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001238 try:
1239 exec(gclient_utils.FileRead(filename), scope)
1240 except SyntaxError, e:
1241 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001242 return scope['entries']
1243
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001244 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001245 """Checks for revision overrides."""
1246 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001247 if self._options.head:
1248 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001249 # Do not check safesync_url if one or more --revision flag is specified.
1250 if not self._options.revisions:
1251 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001252 if not s.managed:
1253 self._options.revisions.append('%s@unmanaged' % s.name)
1254 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001255 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001256 if not self._options.revisions:
1257 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001258 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001259 index = 0
1260 for revision in self._options.revisions:
1261 if not '@' in revision:
1262 # Support for --revision 123
1263 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001264 name, rev = revision.split('@', 1)
1265 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001266 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001267 return revision_overrides
1268
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001269 def _ApplySafeSyncRev(self, dep):
1270 """Finds a valid revision from the content of the safesync_url and apply it
1271 by appending revisions to the revision list. Throws if revision appears to
1272 be invalid for the given |dep|."""
1273 assert len(dep.safesync_url) > 0
1274 handle = urllib.urlopen(dep.safesync_url)
1275 rev = handle.read().strip()
1276 handle.close()
1277 if not rev:
1278 raise gclient_utils.Error(
1279 'It appears your safesync_url (%s) is not working properly\n'
1280 '(as it returned an empty response). Check your config.' %
1281 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001282 scm = gclient_scm.CreateSCM(
1283 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001284 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001285 if self._options.verbose:
1286 print('Using safesync_url revision: %s.\n' % safe_rev)
1287 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1288
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001289 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001290 """Runs a command on each dependency in a client and its dependencies.
1291
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001292 Args:
1293 command: The command to use (e.g., 'status' or 'diff')
1294 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001295 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001296 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001297 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001298
1299 self._CheckConfig()
1300
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001301 revision_overrides = {}
1302 # It's unnecessary to check for revision overrides for 'recurse'.
1303 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001304 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001305 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001306 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001307 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001308 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001309 if command in ('update', 'revert'):
1310 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001311 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001312 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001313 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001314 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1315 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001316 for s in self.dependencies:
1317 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001318 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001319 if revision_overrides:
1320 print >> sys.stderr, ('Please fix your script, having invalid '
1321 '--revision flags will soon considered an error.')
piman@chromium.org6f363722010-04-27 00:41:09 +00001322
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001323 # Once all the dependencies have been processed, it's now safe to run the
1324 # hooks.
1325 if not self._options.nohooks:
1326 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001327
1328 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001329 # Notify the user if there is an orphaned entry in their working copy.
1330 # Only delete the directory if there are no changes in it, and
1331 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001332 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001333 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1334 for e in entries]
1335
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001336 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001337 if not prev_url:
1338 # entry must have been overridden via .gclient custom_deps
1339 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001340 # Fix path separator on Windows.
1341 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001342 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001343
1344 def _IsParentOfAny(parent, path_list):
1345 parent_plus_slash = parent + '/'
1346 return any(
1347 path[:len(parent_plus_slash)] == parent_plus_slash
1348 for path in path_list)
1349
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001350 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001351 if (entry not in entries and
1352 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001353 os.path.exists(e_dir)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001354 scm = gclient_scm.CreateSCM(
1355 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001356
1357 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001358 # The directory might be part of a git OR svn checkout.
1359 scm_root = None
1360 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1361 try:
1362 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1363 except subprocess2.CalledProcessError:
1364 pass
1365 if scm_root:
1366 break
1367 else:
1368 logging.warning('Could not find checkout root for %s. Unable to '
1369 'determine whether it is part of a higher-level '
1370 'checkout, so not removing.' % entry)
1371 continue
1372 if scm_root in full_entries:
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001373 logging.info('%s is part of a higher level checkout, not '
1374 'removing.', scm.GetCheckoutRoot())
1375 continue
1376
1377 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001378 scm.status(self._options, [], file_list)
1379 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001380 if (not self._options.delete_unversioned_trees or
1381 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001382 # There are modified files in this entry. Keep warning until
1383 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001384 print(('\nWARNING: \'%s\' is no longer part of this client. '
1385 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001386 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001387 else:
1388 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001389 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001390 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001391 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001392 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001393 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001394 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001395
1396 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001397 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001398 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001399 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001400 work_queue = gclient_utils.ExecutionQueue(
1401 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001402 for s in self.dependencies:
1403 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001404 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001405
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001406 def GetURLAndRev(dep):
1407 """Returns the revision-qualified SCM url for a Dependency."""
1408 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001409 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001410 if isinstance(dep.parsed_url, self.FileImpl):
1411 original_url = dep.parsed_url.file_location
1412 else:
1413 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001414 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001415 scm = gclient_scm.CreateSCM(
1416 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001417 if not os.path.isdir(scm.checkout_path):
1418 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001419 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001420
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001421 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001422 new_gclient = ''
1423 # First level at .gclient
1424 for d in self.dependencies:
1425 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001426 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001427 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001428 for d in dep.dependencies:
1429 entries[d.name] = GetURLAndRev(d)
1430 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001431 GrabDeps(d)
1432 custom_deps = []
1433 for k in sorted(entries.keys()):
1434 if entries[k]:
1435 # Quotes aren't escaped...
1436 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1437 else:
1438 custom_deps.append(' \"%s\": None,\n' % k)
1439 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1440 'solution_name': d.name,
1441 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001442 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001443 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001444 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001445 'solution_deps': ''.join(custom_deps),
1446 }
1447 # Print the snapshot configuration file
1448 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001449 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001450 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001451 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001452 if self._options.actual:
1453 entries[d.name] = GetURLAndRev(d)
1454 else:
1455 entries[d.name] = d.parsed_url
1456 keys = sorted(entries.keys())
1457 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001458 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001459 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001460
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001461 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001462 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001463 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001464
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001465 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001466 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001467 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001468 return self._root_dir
1469
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001470 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001471 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001472 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001473 return self._enforced_os
1474
maruel@chromium.org68988972011-09-20 14:11:42 +00001475 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001476 def recursion_limit(self):
1477 """How recursive can each dependencies in DEPS file can load DEPS file."""
1478 return self._recursion_limit
1479
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001480 @property
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001481 def try_recurselist(self):
1482 """Whether to attempt using recurselist-style recursion processing."""
1483 return True
1484
1485 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001486 def target_os(self):
1487 return self._enforced_os
1488
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001489
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001490#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001491
1492
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001493def CMDcleanup(parser, args):
1494 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001495
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001496 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1497 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001498 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1499 help='override deps for the specified (comma-separated) '
1500 'platform(s); \'all\' will process all deps_os '
1501 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001502 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001503 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001504 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001505 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001506 if options.verbose:
1507 # Print out the .gclient file. This is longer than if we just printed the
1508 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001509 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001510 return client.RunOnDeps('cleanup', args)
1511
1512
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001513@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001514def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001515 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001516
1517 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001518 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1519 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001520 """
1521 # Stop parsing at the first non-arg so that these go through to the command
1522 parser.disable_interspersed_args()
1523 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001524 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001525 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001526 help='Ignore non-zero return codes from subcommands.')
1527 parser.add_option('--prepend-dir', action='store_true',
1528 help='Prepend relative dir for use with git <cmd> --null.')
1529 parser.add_option('--no-progress', action='store_true',
1530 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001531 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001532 if not args:
1533 print >> sys.stderr, 'Need to supply a command!'
1534 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001535 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1536 if not root_and_entries:
1537 print >> sys.stderr, (
1538 'You need to run gclient sync at least once to use \'recurse\'.\n'
1539 'This is because .gclient_entries needs to exist and be up to date.')
1540 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001541
1542 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001543 scm_set = set()
1544 for scm in options.scm:
1545 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001546 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001547
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001548 options.nohooks = True
1549 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001550 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1551 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001552
1553
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001554@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001555def CMDfetch(parser, args):
1556 """Fetches upstream commits for all modules.
1557
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001558 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1559 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001560 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001561 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001562 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1563
1564
1565def CMDgrep(parser, args):
1566 """Greps through git repos managed by gclient.
1567
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001568 Runs 'git grep [args...]' for each module.
1569 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001570 # We can't use optparse because it will try to parse arguments sent
1571 # to git grep and throw an error. :-(
1572 if not args or re.match('(-h|--help)$', args[0]):
1573 print >> sys.stderr, (
1574 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1575 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1576 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1577 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1578 ' end of your query.'
1579 )
1580 return 1
1581
1582 jobs_arg = ['--jobs=1']
1583 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1584 jobs_arg, args = args[:1], args[1:]
1585 elif re.match(r'(-j|--jobs)$', args[0]):
1586 jobs_arg, args = args[:2], args[2:]
1587
1588 return CMDrecurse(
1589 parser,
1590 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1591 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001592
1593
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001594@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001595def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001596 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001597
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001598 This specifies the configuration for further commands. After update/sync,
1599 top-level DEPS files in each module are read to determine dependent
1600 modules to operate on as well. If optional [url] parameter is
1601 provided, then configuration is read from a specified Subversion server
1602 URL.
1603 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001604 # We do a little dance with the --gclientfile option. 'gclient config' is the
1605 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1606 # arguments. So, we temporarily stash any --gclientfile parameter into
1607 # options.output_config_file until after the (gclientfile xor spec) error
1608 # check.
1609 parser.remove_option('--gclientfile')
1610 parser.add_option('--gclientfile', dest='output_config_file',
1611 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001612 parser.add_option('--name',
1613 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001614 parser.add_option('--deps-file', default='DEPS',
1615 help='overrides the default name for the DEPS file for the'
1616 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001617 parser.add_option('--unmanaged', action='store_true', default=False,
1618 help='overrides the default behavior to make it possible '
1619 'to have the main solution untouched by gclient '
1620 '(gclient will check out unmanaged dependencies but '
1621 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001622 parser.add_option('--git-deps', action='store_true',
1623 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001624 parser.add_option('--cache-dir',
1625 help='(git only) Cache all git repos into this dir and do '
1626 'shared clones from the cache, instead of cloning '
1627 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001628 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001629 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001630 if options.output_config_file:
1631 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001632 if ((options.spec and args) or len(args) > 2 or
1633 (not options.spec and not args)):
1634 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1635
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001636 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001637 if options.spec:
1638 client.SetConfig(options.spec)
1639 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001640 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001641 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001642 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001643 if name.endswith('.git'):
1644 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001645 else:
1646 # specify an alternate relpath for the given URL.
1647 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001648 deps_file = options.deps_file
1649 if options.git_deps:
1650 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001651 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001652 if len(args) > 1:
1653 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001654 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001655 managed=not options.unmanaged,
1656 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001657 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001658 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001659
1660
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001661@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001662 gclient pack > patch.txt
1663 generate simple patch for configured client and dependences
1664""")
1665def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001666 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001667
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001668 Internally, runs 'svn diff'/'git diff' on each checked out module and
1669 dependencies, and performs minimal postprocessing of the output. The
1670 resulting patch is printed to stdout and can be applied to a freshly
1671 checked out tree via 'patch -p0 < patchfile'.
1672 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001673 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1674 help='override deps for the specified (comma-separated) '
1675 'platform(s); \'all\' will process all deps_os '
1676 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001677 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001678 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001679 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001680 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001681 client = GClient.LoadCurrentConfig(options)
1682 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001683 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001684 if options.verbose:
1685 # Print out the .gclient file. This is longer than if we just printed the
1686 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001687 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001688 return client.RunOnDeps('pack', args)
1689
1690
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001691def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001692 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001693 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1694 help='override deps for the specified (comma-separated) '
1695 'platform(s); \'all\' will process all deps_os '
1696 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001697 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001698 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001699 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001700 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001701 if options.verbose:
1702 # Print out the .gclient file. This is longer than if we just printed the
1703 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001704 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001705 return client.RunOnDeps('status', args)
1706
1707
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001708@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001709 gclient sync
1710 update files from SCM according to current configuration,
1711 *for modules which have changed since last update or sync*
1712 gclient sync --force
1713 update files from SCM according to current configuration, for
1714 all modules (useful for recovering files deleted from local copy)
1715 gclient sync --revision src@31000
1716 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001717
1718JSON output format:
1719If the --output-json option is specified, the following document structure will
1720be emitted to the provided file. 'null' entries may occur for subprojects which
1721are present in the gclient solution, but were not processed (due to custom_deps,
1722os_deps, etc.)
1723
1724{
1725 "solutions" : {
1726 "<name>": { # <name> is the posix-normalized path to the solution.
1727 "revision": [<svn rev int>|<git id hex string>|null],
1728 "scm": ["svn"|"git"|null],
1729 }
1730 }
1731}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001732""")
1733def CMDsync(parser, args):
1734 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001735 parser.add_option('-f', '--force', action='store_true',
1736 help='force update even for unchanged modules')
1737 parser.add_option('-n', '--nohooks', action='store_true',
1738 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001739 parser.add_option('-p', '--noprehooks', action='store_true',
1740 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001741 parser.add_option('-r', '--revision', action='append',
1742 dest='revisions', metavar='REV', default=[],
1743 help='Enforces revision/hash for the solutions with the '
1744 'format src@rev. The src@ part is optional and can be '
1745 'skipped. -r can be used multiple times when .gclient '
1746 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001747 'if the src@ part is skipped. Note that specifying '
1748 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001749 parser.add_option('--with_branch_heads', action='store_true',
1750 help='Clone git "branch_heads" refspecs in addition to '
1751 'the default refspecs. This adds about 1/2GB to a '
1752 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001753 parser.add_option('-t', '--transitive', action='store_true',
1754 help='When a revision is specified (in the DEPS file or '
1755 'with the command-line flag), transitively update '
1756 'the dependencies to the date of the given revision. '
1757 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001758 parser.add_option('-H', '--head', action='store_true',
1759 help='skips any safesync_urls specified in '
1760 'configured solutions and sync to head instead')
1761 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001762 help='Deletes from the working copy any dependencies that '
1763 'have been removed since the last sync, as long as '
1764 'there are no local modifications. When used with '
1765 '--force, such dependencies are removed even if they '
1766 'have local modifications. When used with --reset, '
1767 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001768 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001769 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001770 parser.add_option('-R', '--reset', action='store_true',
1771 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001772 parser.add_option('-M', '--merge', action='store_true',
1773 help='merge upstream changes instead of trying to '
1774 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001775 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1776 help='override deps for the specified (comma-separated) '
1777 'platform(s); \'all\' will process all deps_os '
1778 'references')
1779 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1780 help='Skip svn up whenever possible by requesting '
1781 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001782 parser.add_option('--upstream', action='store_true',
1783 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001784 parser.add_option('--output-json',
1785 help='Output a json document to this path containing '
1786 'summary information about the sync.')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001787 parser.add_option('--shallow', action='store_true',
1788 help='GIT ONLY - Do a shallow clone into the cache dir. '
1789 'Requires Git 1.9+')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001790 parser.add_option('--ignore_locks', action='store_true',
1791 help='GIT ONLY - Ignore cache locks.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001792 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001793 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001794
1795 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001796 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001797
maruel@chromium.org307d1792010-05-31 20:03:13 +00001798 if options.revisions and options.head:
1799 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001800 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001801
1802 if options.verbose:
1803 # Print out the .gclient file. This is longer than if we just printed the
1804 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001805 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001806 ret = client.RunOnDeps('update', args)
1807 if options.output_json:
1808 slns = {}
1809 for d in client.subtree(True):
1810 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1811 slns[normed] = {
1812 'revision': d.got_revision,
1813 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001814 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001815 }
1816 with open(options.output_json, 'wb') as f:
1817 json.dump({'solutions': slns}, f)
1818 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001819
1820
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001821CMDupdate = CMDsync
1822
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001823
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001824def CMDdiff(parser, args):
1825 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001826 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1827 help='override deps for the specified (comma-separated) '
1828 'platform(s); \'all\' will process all deps_os '
1829 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001830 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001831 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001832 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001833 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001834 if options.verbose:
1835 # Print out the .gclient file. This is longer than if we just printed the
1836 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001837 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001838 return client.RunOnDeps('diff', args)
1839
1840
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001841def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001842 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001843
1844 That's the nuclear option to get back to a 'clean' state. It removes anything
1845 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001846 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1847 help='override deps for the specified (comma-separated) '
1848 'platform(s); \'all\' will process all deps_os '
1849 'references')
1850 parser.add_option('-n', '--nohooks', action='store_true',
1851 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001852 parser.add_option('-p', '--noprehooks', action='store_true',
1853 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001854 parser.add_option('--upstream', action='store_true',
1855 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001856 (options, args) = parser.parse_args(args)
1857 # --force is implied.
1858 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001859 options.reset = False
1860 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001861 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001862 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001863 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001864 return client.RunOnDeps('revert', args)
1865
1866
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001867def CMDrunhooks(parser, args):
1868 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001869 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1870 help='override deps for the specified (comma-separated) '
1871 'platform(s); \'all\' will process all deps_os '
1872 'references')
1873 parser.add_option('-f', '--force', action='store_true', default=True,
1874 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001875 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001876 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001877 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001878 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001879 if options.verbose:
1880 # Print out the .gclient file. This is longer than if we just printed the
1881 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001882 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001883 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001884 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001885 return client.RunOnDeps('runhooks', args)
1886
1887
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001888def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001889 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001890
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001891 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001892 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001893 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1894 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001895 commit can change.
1896 """
1897 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1898 help='override deps for the specified (comma-separated) '
1899 'platform(s); \'all\' will process all deps_os '
1900 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001901 parser.add_option('-a', '--actual', action='store_true',
1902 help='gets the actual checked out revisions instead of the '
1903 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001904 parser.add_option('-s', '--snapshot', action='store_true',
1905 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001906 'version of all repositories to reproduce the tree, '
1907 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001908 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001909 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001910 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001911 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001912 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001913 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001914
1915
szager@google.comb9a78d32012-03-13 18:46:21 +00001916def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001917 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001918 (options, args) = parser.parse_args(args)
1919 options.force = True
1920 client = GClient.LoadCurrentConfig(options)
1921 if not client:
1922 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1923 client.RunOnDeps(None, [])
1924 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1925 return 0
1926
1927
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001928class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00001929 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001930
1931 def __init__(self, **kwargs):
1932 optparse.OptionParser.__init__(
1933 self, version='%prog ' + __version__, **kwargs)
1934
1935 # Some arm boards have issues with parallel sync.
1936 if platform.machine().startswith('arm'):
1937 jobs = 1
1938 else:
1939 jobs = max(8, gclient_utils.NumLocalCpus())
1940 # cmp: 2013/06/19
1941 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00001942 # Bypassed if a bot_update flag is detected.
1943 if (os.environ.get('CHROME_HEADLESS') == '1' and
1944 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00001945 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001946
1947 self.add_option(
1948 '-j', '--jobs', default=jobs, type='int',
1949 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001950 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001951 self.add_option(
1952 '-v', '--verbose', action='count', default=0,
1953 help='Produces additional output for diagnostics. Can be used up to '
1954 'three times for more logging info.')
1955 self.add_option(
1956 '--gclientfile', dest='config_filename',
1957 help='Specify an alternate %s file' % self.gclientfile_default)
1958 self.add_option(
1959 '--spec',
1960 help='create a gclient file containing the provided string. Due to '
1961 'Cygwin/Python brokenness, it can\'t contain any newlines.')
1962 self.add_option(
1963 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00001964 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001965
1966 def parse_args(self, args=None, values=None):
1967 """Integrates standard options processing."""
1968 options, args = optparse.OptionParser.parse_args(self, args, values)
1969 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
1970 logging.basicConfig(
1971 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00001972 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001973 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001974 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00001975 if (options.config_filename and
1976 options.config_filename != os.path.basename(options.config_filename)):
1977 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001978 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001979 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001980 options.entries_filename = options.config_filename + '_entries'
1981 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001982 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001983
1984 # These hacks need to die.
1985 if not hasattr(options, 'revisions'):
1986 # GClient.RunOnDeps expects it even if not applicable.
1987 options.revisions = []
1988 if not hasattr(options, 'head'):
1989 options.head = None
1990 if not hasattr(options, 'nohooks'):
1991 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001992 if not hasattr(options, 'noprehooks'):
1993 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00001994 if not hasattr(options, 'deps_os'):
1995 options.deps_os = None
1996 if not hasattr(options, 'manually_grab_svn_rev'):
1997 options.manually_grab_svn_rev = None
1998 if not hasattr(options, 'force'):
1999 options.force = None
2000 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002001
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002002
2003def disable_buffering():
2004 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2005 # operations. Python as a strong tendency to buffer sys.stdout.
2006 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2007 # Make stdout annotated with the thread ids.
2008 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002009
2010
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002011def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002012 """Doesn't parse the arguments here, just find the right subcommand to
2013 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002014 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00002015 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002016 '\nYour python version %s is unsupported, please upgrade.\n' %
2017 sys.version.split(' ', 1)[0])
2018 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002019 if not sys.executable:
2020 print >> sys.stderr, (
2021 '\nPython cannot find the location of it\'s own executable.\n')
2022 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002023 fix_encoding.fix_encoding()
2024 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00002025 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002026 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002027 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002028 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002029 except KeyboardInterrupt:
2030 gclient_utils.GClientChildren.KillAllRemainingChildren()
2031 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00002032 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002033 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002034 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002035 finally:
2036 gclient_utils.PrintWarnings()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002037
2038
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002039if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002040 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002041
2042# vim: ts=2:sw=2:tw=80:et: