blob: 6a7b0e477d939508e25a00884e39889f16b596a8 [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
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000181 # This is a mutable value that overrides the normal recursion limit for this
182 # dependency. It is read from the actual DEPS file so cannot be set on
183 # class instantiation.
184 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000185 # This is a mutable value which has the list of 'target_os' OSes listed in
186 # the current deps file.
187 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000188
189 # These are only set in .gclient and not in DEPS files.
190 self._custom_vars = custom_vars or {}
191 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000192 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000193
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000194 # TODO(iannucci): Remove this when all masters are correctly substituting
195 # the new blink url.
196 if (self._custom_vars.get('webkit_trunk', '') ==
197 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000198 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
199 print 'Overwriting Var("webkit_trunk") with %s' % new_url
200 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000201
maruel@chromium.org064186c2011-09-27 23:53:33 +0000202 # Post process the url to remove trailing slashes.
203 if isinstance(self._url, basestring):
204 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
205 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000206 self._url = self._url.replace('/@', '@')
207 elif not isinstance(self._url,
208 (self.FromImpl, self.FileImpl, None.__class__)):
209 raise gclient_utils.Error(
210 ('dependency url must be either a string, None, '
211 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000212 # Make any deps_file path platform-appropriate.
213 for sep in ['/', '\\']:
214 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215
216 @property
217 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218 return self._deps_file
219
220 @property
221 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222 return self._managed
223
224 @property
225 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000226 return self._parent
227
228 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000229 def root(self):
230 """Returns the root node, a GClient object."""
231 if not self.parent:
232 # This line is to signal pylint that it could be a GClient instance.
233 return self or GClient(None, None)
234 return self.parent.root
235
236 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000237 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000238 return self._safesync_url
239
240 @property
241 def should_process(self):
242 """True if this dependency should be processed, i.e. checked out."""
243 return self._should_process
244
245 @property
246 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 return self._custom_vars.copy()
248
249 @property
250 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000251 return self._custom_deps.copy()
252
maruel@chromium.org064186c2011-09-27 23:53:33 +0000253 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000254 def custom_hooks(self):
255 return self._custom_hooks[:]
256
257 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000258 def url(self):
259 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000260
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000261 @property
262 def recursion_limit(self):
263 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000264 if self.recursion_override is not None:
265 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000266 return max(self.parent.recursion_limit - 1, 0)
267
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000268 @property
269 def target_os(self):
270 if self.local_target_os is not None:
271 return tuple(set(self.local_target_os).union(self.parent.target_os))
272 else:
273 return self.parent.target_os
274
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000275 def get_custom_deps(self, name, url):
276 """Returns a custom deps if applicable."""
277 if self.parent:
278 url = self.parent.get_custom_deps(name, url)
279 # None is a valid return value to disable a dependency.
280 return self.custom_deps.get(name, url)
281
maruel@chromium.org064186c2011-09-27 23:53:33 +0000282
283class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000284 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000285
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000286 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000287 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000288 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000289 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000290 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000291 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000292
293 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000294 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000295
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000296 self._pre_deps_hooks = []
297
maruel@chromium.org68988972011-09-20 14:11:42 +0000298 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000299 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000300 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000301 # A cache of the files affected by the current operation, necessary for
302 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000303 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000304 # If it is not set to True, the dependency wasn't processed for its child
305 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000306 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000307 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000309 # This dependency had its pre-DEPS hooks run
310 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000311 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000312 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000313 # This is the scm used to checkout self.url. It may be used by dependencies
314 # to get the datetime of the revision we checked out.
315 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000316 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000317 # The actual revision we ended up getting, or None if that information is
318 # unavailable
319 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000320
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000321 if not self.name and self.parent:
322 raise gclient_utils.Error('Dependency without name')
323
maruel@chromium.org470b5432011-10-11 18:18:19 +0000324 @property
325 def requirements(self):
326 """Calculate the list of requirements."""
327 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000328 # self.parent is implicitly a requirement. This will be recursive by
329 # definition.
330 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000331 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000332
333 # For a tree with at least 2 levels*, the leaf node needs to depend
334 # on the level higher up in an orderly way.
335 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
336 # thus unsorted, while the .gclient format is a list thus sorted.
337 #
338 # * _recursion_limit is hard coded 2 and there is no hope to change this
339 # value.
340 #
341 # Interestingly enough, the following condition only works in the case we
342 # want: self is a 2nd level node. 3nd level node wouldn't need this since
343 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000344 if self.parent and self.parent.parent and not self.parent.parent.parent:
345 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000346
347 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000348 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000349
maruel@chromium.org470b5432011-10-11 18:18:19 +0000350 if self.name:
351 requirements |= set(
352 obj.name for obj in self.root.subtree(False)
353 if (obj is not self
354 and obj.name and
355 self.name.startswith(posixpath.join(obj.name, ''))))
356 requirements = tuple(sorted(requirements))
357 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
358 return requirements
359
360 def verify_validity(self):
361 """Verifies that this Dependency is fine to add as a child of another one.
362
363 Returns True if this entry should be added, False if it is a duplicate of
364 another entry.
365 """
366 logging.info('Dependency(%s).verify_validity()' % self.name)
367 if self.name in [s.name for s in self.parent.dependencies]:
368 raise gclient_utils.Error(
369 'The same name "%s" appears multiple times in the deps section' %
370 self.name)
371 if not self.should_process:
372 # Return early, no need to set requirements.
373 return True
374
375 # This require a full tree traversal with locks.
376 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
377 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000378 self_url = self.LateOverride(self.url)
379 sibling_url = sibling.LateOverride(sibling.url)
380 # Allow to have only one to be None or ''.
381 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000382 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000383 ('Dependency %s specified more than once:\n'
384 ' %s [%s]\n'
385 'vs\n'
386 ' %s [%s]') % (
387 self.name,
388 sibling.hierarchy(),
389 sibling_url,
390 self.hierarchy(),
391 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000392 # In theory we could keep it as a shadow of the other one. In
393 # practice, simply ignore it.
394 logging.warn('Won\'t process duplicate dependency %s' % sibling)
395 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000396 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000397
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000398 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000399 """Resolves the parsed url from url.
400
401 Manages From() keyword accordingly. Do not touch self.parsed_url nor
402 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000403 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000404 parsed_url = self.get_custom_deps(self.name, url)
405 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000406 logging.info(
407 'Dependency(%s).LateOverride(%s) -> %s' %
408 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000409 return parsed_url
410
411 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000412 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000413 ref = [
414 dep for dep in self.root.subtree(True) if url.module_name == dep.name
415 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000416 if not ref:
417 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
418 url.module_name, ref))
419 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000420 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000421 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000422 found_deps = [d for d in ref.dependencies if d.name == sub_target]
423 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000424 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000425 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
426 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000427 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000428
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000429 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000430 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000431 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000432 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000433 'Dependency(%s).LateOverride(%s) -> %s (From)' %
434 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000435 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000436
437 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000438 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000439 if (not parsed_url[0] and
440 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000441 # A relative url. Fetch the real base.
442 path = parsed_url[2]
443 if not path.startswith('/'):
444 raise gclient_utils.Error(
445 'relative DEPS entry \'%s\' must begin with a slash' % url)
446 # Create a scm just to query the full url.
447 parent_url = self.parent.parsed_url
448 if isinstance(parent_url, self.FileImpl):
449 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000450 scm = gclient_scm.CreateSCM(
451 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000452 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000453 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000454 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000455 logging.info(
456 'Dependency(%s).LateOverride(%s) -> %s' %
457 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000458 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000459
460 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000461 logging.info(
462 'Dependency(%s).LateOverride(%s) -> %s (File)' %
463 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000464 return url
465
466 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000467 logging.info(
468 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000469 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000470
471 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000472
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000473 @staticmethod
474 def MergeWithOsDeps(deps, deps_os, target_os_list):
475 """Returns a new "deps" structure that is the deps sent in updated
476 with information from deps_os (the deps_os section of the DEPS
477 file) that matches the list of target os."""
478 os_overrides = {}
479 for the_target_os in target_os_list:
480 the_target_os_deps = deps_os.get(the_target_os, {})
481 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
482 overrides = os_overrides.setdefault(os_dep_key, [])
483 overrides.append((the_target_os, os_dep_value))
484
485 # If any os didn't specify a value (we have fewer value entries
486 # than in the os list), then it wants to use the default value.
487 for os_dep_key, os_dep_value in os_overrides.iteritems():
488 if len(os_dep_value) != len(target_os_list):
489 # Record the default value too so that we don't accidently
490 # set it to None or miss a conflicting DEPS.
491 if os_dep_key in deps:
492 os_dep_value.append(('default', deps[os_dep_key]))
493
494 target_os_deps = {}
495 for os_dep_key, os_dep_value in os_overrides.iteritems():
496 # os_dep_value is a list of (os, value) pairs.
497 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
498 if not possible_values:
499 target_os_deps[os_dep_key] = None
500 else:
501 if len(possible_values) > 1:
502 # It would be possible to abort here but it would be
503 # unfortunate if we end up preventing any kind of checkout.
504 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
505 os_dep_key, os_dep_value, target_os_list)
506 # Sorting to get the same result every time in case of conflicts.
507 target_os_deps[os_dep_key] = sorted(possible_values)[0]
508
509 new_deps = deps.copy()
510 new_deps.update(target_os_deps)
511 return new_deps
512
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000513 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000514 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000515 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000516 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000517
518 deps_content = None
519 use_strict = False
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000520 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000521 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000522 logging.info(
523 'ParseDepsFile(%s): No %s file found at %s' % (
524 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000525 else:
526 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000527 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000528 use_strict = 'use strict' in deps_content.splitlines()[0]
529
530 local_scope = {}
531 if deps_content:
532 # One thing is unintuitive, vars = {} must happen before Var() use.
533 var = self.VarImpl(self.custom_vars, local_scope)
534 if use_strict:
535 logging.info(
536 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
537 global_scope = {
538 '__builtins__': {'None': None},
539 'Var': var.Lookup,
540 'deps_os': {},
541 }
542 else:
543 global_scope = {
544 'File': self.FileImpl,
545 'From': self.FromImpl,
546 'Var': var.Lookup,
547 'deps_os': {},
548 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000549 # Eval the content.
550 try:
551 exec(deps_content, global_scope, local_scope)
552 except SyntaxError, e:
553 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000554 if use_strict:
555 for key, val in local_scope.iteritems():
556 if not isinstance(val, (dict, list, tuple, str)):
557 raise gclient_utils.Error(
558 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
559 (self.name, key, val))
560
maruel@chromium.org271375b2010-06-23 19:17:38 +0000561 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000562 if 'recursion' in local_scope:
563 self.recursion_override = local_scope.get('recursion')
564 logging.warning(
565 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000566 # If present, save 'target_os' in the local_target_os property.
567 if 'target_os' in local_scope:
568 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000569 # load os specific dependencies if defined. these dependencies may
570 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000571 target_os_list = self.target_os
572 if 'deps_os' in local_scope and target_os_list:
573 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000574
maruel@chromium.org271375b2010-06-23 19:17:38 +0000575 # If a line is in custom_deps, but not in the solution, we want to append
576 # this line to the solution.
577 for d in self.custom_deps:
578 if d not in deps:
579 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000580
581 # If use_relative_paths is set in the DEPS file, regenerate
582 # the dictionary using paths relative to the directory containing
583 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000584 use_relative_paths = local_scope.get('use_relative_paths', False)
585 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000586 rel_deps = {}
587 for d, url in deps.items():
588 # normpath is required to allow DEPS to use .. in their
589 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000590 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
591 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000592
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000593 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000594 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000595 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000596 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000597 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000598 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000599 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000600 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000601
602 # override named sets of hooks by the custom hooks
603 hooks_to_run = []
604 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
605 for hook in local_scope.get('hooks', []):
606 if hook.get('name', '') not in hook_names_to_suppress:
607 hooks_to_run.append(hook)
608
609 # add the replacements and any additions
610 for hook in self.custom_hooks:
611 if 'action' in hook:
612 hooks_to_run.append(hook)
613
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000614 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
615 local_scope.get('pre_deps_hooks', [])]
616
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000617 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000618 logging.info('ParseDepsFile(%s) done' % self.name)
619
620 def add_dependencies_and_close(self, deps_to_add, hooks):
621 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000622 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000623 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000624 self.add_dependency(dep)
625 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000626
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000627 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000628 """Uses revision/timestamp of parent if no explicit revision was specified.
629
630 If we are performing an update and --transitive is set, use
631 - the parent's revision if 'self.url' is in the same repository
632 - the parent's timestamp otherwise
633 to update 'self.url'. The used revision/timestamp will be set in
634 'options.revision'.
635 If we have an explicit revision do nothing.
636 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000637 if command == 'update' and options.transitive and not options.revision:
638 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
639 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000640 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000641 if (options.revision and
642 not gclient_utils.IsDateRevision(options.revision)):
643 assert self.parent and self.parent.used_scm
644 # If this dependency is in the same repository as parent it's url will
645 # start with a slash. If so we take the parent revision instead of
646 # it's timestamp.
647 # (The timestamps of commits in google code are broken -- which can
648 # result in dependencies to be checked out at the wrong revision)
649 if self.url.startswith('/'):
650 if options.verbose:
651 print('Using parent\'s revision %s since we are in the same '
652 'repository.' % options.revision)
653 else:
654 parent_revision_date = self.parent.used_scm.GetRevisionDate(
655 options.revision)
656 options.revision = gclient_utils.MakeDateRevision(
657 parent_revision_date)
658 if options.verbose:
659 print('Using parent\'s revision date %s since we are in a '
660 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000661
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000662 # Arguments number differs from overridden method
663 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000664 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000665 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000666 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000667 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000668 if not self.should_process:
669 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000670 # When running runhooks, there's no need to consult the SCM.
671 # All known hooks are expected to run unconditionally regardless of working
672 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000673 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000674 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000675 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000676 if run_scm and parsed_url:
677 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000678 # Special support for single-file checkout.
679 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000680 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
681 # pylint: disable=E1103
682 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000683 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000684 parsed_url.GetPath(), self.root.root_dir, self.name,
685 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000686 self._used_scm.RunCommand('updatesingle',
687 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000688 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000689 # Create a shallow copy to mutate revision.
690 options = copy.copy(options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000691 options.revision = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000692 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000693 command, options, parsed_url, self.parent)
694 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000695 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000696 parsed_url, self.root.root_dir, self.name, self.outbuf,
697 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000698 self._got_revision = self._used_scm.RunCommand(command, options, args,
699 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000700 if file_list:
701 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000702
703 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
704 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000705 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000706 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000707 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000708 continue
709 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000710 [self.root.root_dir.lower(), file_list[i].lower()])
711 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000712 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000713 while file_list[i].startswith(('\\', '/')):
714 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000715
716 # Always parse the DEPS file.
717 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000718 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000719 if command in ('update', 'revert') and not options.noprehooks:
720 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000721
722 if self.recursion_limit:
723 # Parse the dependencies of this dependency.
724 for s in self.dependencies:
725 work_queue.enqueue(s)
726
727 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000728 if not isinstance(parsed_url, self.FileImpl):
729 # Skip file only checkout.
730 scm = gclient_scm.GetScmName(parsed_url)
731 if not options.scm or scm in options.scm:
732 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000733 # Pass in the SCM type as an env variable. Make sure we don't put
734 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000735 env = os.environ.copy()
736 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000737 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000738 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000739 env['GCLIENT_URL'] = str(parsed_url)
740 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000741 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000742 print_stdout = False
743 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000744 """Git-specific path marshaling. It is optimized for git-grep."""
745
746 def mod_path(git_pathspec):
747 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
748 modified_path = os.path.join(self.name, match.group(2))
749 branch = match.group(1) or ''
750 return '%s%s' % (branch, modified_path)
751
752 match = re.match('^Binary file ([^\0]+) matches$', line)
753 if match:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000754 print 'Binary file %s matches\n' % mod_path(match.group(1))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000755 return
756
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000757 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000758 if len(items) == 2 and items[1]:
759 print '%s : %s' % (mod_path(items[0]), items[1])
760 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000761 # Multiple null bytes or a single trailing null byte indicate
762 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000763 print '\n'.join(mod_path(path) for path in items if path)
764 else:
765 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000766 else:
767 print_stdout = True
768 filter_fn = None
769
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000770 if parsed_url is None:
771 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
772 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000773 try:
774 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000775 args, cwd=cwd, env=env, print_stdout=print_stdout,
776 filter_fn=filter_fn,
777 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000778 except subprocess2.CalledProcessError:
779 if not options.ignore:
780 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000781 else:
782 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000783
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000784
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000785 @gclient_utils.lockedmethod
786 def _run_is_done(self, file_list, parsed_url):
787 # Both these are kept for hooks that are run as a separate tree traversal.
788 self._file_list = file_list
789 self._parsed_url = parsed_url
790 self._processed = True
791
szager@google.comb9a78d32012-03-13 18:46:21 +0000792 @staticmethod
793 def GetHookAction(hook_dict, matching_file_list):
794 """Turns a parsed 'hook' dict into an executable command."""
795 logging.debug(hook_dict)
796 logging.debug(matching_file_list)
797 command = hook_dict['action'][:]
798 if command[0] == 'python':
799 # If the hook specified "python" as the first item, the action is a
800 # Python script. Run it by starting a new copy of the same
801 # interpreter.
802 command[0] = sys.executable
803 if '$matching_files' in command:
804 splice_index = command.index('$matching_files')
805 command[splice_index:splice_index + 1] = matching_file_list
806 return command
807
808 def GetHooks(self, options):
809 """Evaluates all hooks, and return them in a flat list.
810
811 RunOnDeps() must have been called before to load the DEPS.
812 """
813 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000814 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000815 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000816 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000817 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000818 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000819 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000820 # TODO(maruel): If the user is using git or git-svn, then we don't know
821 # what files have changed so we always run all hooks. It'd be nice to fix
822 # that.
823 if (options.force or
824 isinstance(self.parsed_url, self.FileImpl) or
825 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000826 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000827 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000828 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000829 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000830 # Run hooks on the basis of whether the files from the gclient operation
831 # match each hook's pattern.
832 for hook_dict in self.deps_hooks:
833 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000834 matching_file_list = [
835 f for f in self.file_list_and_children if pattern.search(f)
836 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000837 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000838 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000839 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000840 result.extend(s.GetHooks(options))
841 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000842
szager@google.comb9a78d32012-03-13 18:46:21 +0000843 def RunHooksRecursively(self, options):
844 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000845 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000846 for hook in self.GetHooks(options):
847 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000848 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000849 gclient_utils.CheckCallAndFilterAndHeader(
850 hook, cwd=self.root.root_dir, always=True)
851 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
852 # Use a discrete exit status code of 2 to indicate that a hook action
853 # failed. Users of this script may wish to treat hook action failures
854 # differently from VC failures.
855 print >> sys.stderr, 'Error: %s' % str(e)
856 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000857 finally:
858 elapsed_time = time.time() - start_time
859 if elapsed_time > 10:
860 print "Hook '%s' took %.2f secs" % (
861 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000862
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000863 def RunPreDepsHooks(self):
864 assert self.processed
865 assert self.deps_parsed
866 assert not self.pre_deps_hooks_ran
867 assert not self.hooks_ran
868 for s in self.dependencies:
869 assert not s.processed
870 self._pre_deps_hooks_ran = True
871 for hook in self.pre_deps_hooks:
872 try:
873 start_time = time.time()
874 gclient_utils.CheckCallAndFilterAndHeader(
875 hook, cwd=self.root.root_dir, always=True)
876 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
877 # Use a discrete exit status code of 2 to indicate that a hook action
878 # failed. Users of this script may wish to treat hook action failures
879 # differently from VC failures.
880 print >> sys.stderr, 'Error: %s' % str(e)
881 sys.exit(2)
882 finally:
883 elapsed_time = time.time() - start_time
884 if elapsed_time > 10:
885 print "Hook '%s' took %.2f secs" % (
886 gclient_utils.CommandToStr(hook), elapsed_time)
887
888
maruel@chromium.org0d812442010-08-10 12:41:08 +0000889 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000890 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000891 dependencies = self.dependencies
892 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000893 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000894 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000895 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000896 for i in d.subtree(include_all):
897 yield i
898
899 def depth_first_tree(self):
900 """Depth-first recursion including the root node."""
901 yield self
902 for i in self.dependencies:
903 for j in i.depth_first_tree():
904 if j.should_process:
905 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000906
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000907 @gclient_utils.lockedmethod
908 def add_dependency(self, new_dep):
909 self._dependencies.append(new_dep)
910
911 @gclient_utils.lockedmethod
912 def _mark_as_parsed(self, new_hooks):
913 self._deps_hooks.extend(new_hooks)
914 self._deps_parsed = True
915
maruel@chromium.org68988972011-09-20 14:11:42 +0000916 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000917 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000918 def dependencies(self):
919 return tuple(self._dependencies)
920
921 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000922 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000923 def deps_hooks(self):
924 return tuple(self._deps_hooks)
925
926 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000927 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000928 def pre_deps_hooks(self):
929 return tuple(self._pre_deps_hooks)
930
931 @property
932 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000933 def parsed_url(self):
934 return self._parsed_url
935
936 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000937 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000938 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000939 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000940 return self._deps_parsed
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 processed(self):
945 return self._processed
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_ran(self):
950 return self._pre_deps_hooks_ran
951
952 @property
953 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000954 def hooks_ran(self):
955 return self._hooks_ran
956
957 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000958 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000959 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000960 return tuple(self._file_list)
961
962 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000963 def used_scm(self):
964 """SCMWrapper instance for this dependency or None if not processed yet."""
965 return self._used_scm
966
967 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000968 @gclient_utils.lockedmethod
969 def got_revision(self):
970 return self._got_revision
971
972 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000973 def file_list_and_children(self):
974 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000975 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000976 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000977 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000978
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000979 def __str__(self):
980 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000981 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000982 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000983 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000984 # First try the native property if it exists.
985 if hasattr(self, '_' + i):
986 value = getattr(self, '_' + i, False)
987 else:
988 value = getattr(self, i, False)
989 if value:
990 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000991
992 for d in self.dependencies:
993 out.extend([' ' + x for x in str(d).splitlines()])
994 out.append('')
995 return '\n'.join(out)
996
997 def __repr__(self):
998 return '%s: %s' % (self.name, self.url)
999
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001000 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001001 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001002 out = '%s(%s)' % (self.name, self.url)
1003 i = self.parent
1004 while i and i.name:
1005 out = '%s(%s) -> %s' % (i.name, i.url, out)
1006 i = i.parent
1007 return out
1008
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001009
1010class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001011 """Object that represent a gclient checkout. A tree of Dependency(), one per
1012 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001013
1014 DEPS_OS_CHOICES = {
1015 "win32": "win",
1016 "win": "win",
1017 "cygwin": "win",
1018 "darwin": "mac",
1019 "mac": "mac",
1020 "unix": "unix",
1021 "linux": "unix",
1022 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001023 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001024 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001025 }
1026
1027 DEFAULT_CLIENT_FILE_TEXT = ("""\
1028solutions = [
1029 { "name" : "%(solution_name)s",
1030 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001031 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001032 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001033 "custom_deps" : {
1034 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001035 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001036 },
1037]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001038cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001039""")
1040
1041 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1042 { "name" : "%(solution_name)s",
1043 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001044 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001045 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001046 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001047%(solution_deps)s },
1048 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001049 },
1050""")
1051
1052 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1053# Snapshot generated with gclient revinfo --snapshot
1054solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001055%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001056""")
1057
1058 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001059 # Do not change previous behavior. Only solution level and immediate DEPS
1060 # are processed.
1061 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001062 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001063 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001064 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001065 if options.deps_os:
1066 enforced_os = options.deps_os.split(',')
1067 else:
1068 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1069 if 'all' in enforced_os:
1070 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001071 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001072 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001073 self.config_content = None
1074
borenet@google.com88d10082014-03-21 17:24:48 +00001075 def _CheckConfig(self):
1076 """Verify that the config matches the state of the existing checked-out
1077 solutions."""
1078 for dep in self.dependencies:
1079 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001080 scm = gclient_scm.CreateSCM(
1081 dep.url, self.root_dir, dep.name, self.outbuf)
borenet@google.com4e9be262014-04-08 19:40:30 +00001082 actual_url = scm.GetActualRemoteURL(self._options)
1083 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001084 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001085Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001086is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001087
borenet@google.com97882362014-04-07 20:06:02 +00001088The .gclient file contains:
1089%(expected_url)s (%(expected_scm)s)
1090
1091The local checkout in %(checkout_path)s reports:
1092%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001093
1094You should ensure that the URL listed in .gclient is correct and either change
1095it or fix the checkout. If you're managing your own git checkout in
1096%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1097want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001098''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1099 'expected_url': dep.url,
1100 'expected_scm': gclient_scm.GetScmName(dep.url),
1101 'actual_url': actual_url,
1102 'actual_scm': gclient_scm.GetScmName(actual_url)})
1103
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001104 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001105 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001106 config_dict = {}
1107 self.config_content = content
1108 try:
1109 exec(content, config_dict)
1110 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001111 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001112
peter@chromium.org1efccc82012-04-27 16:34:38 +00001113 # Append any target OS that is not already being enforced to the tuple.
1114 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001115 if config_dict.get('target_os_only', False):
1116 self._enforced_os = tuple(set(target_os))
1117 else:
1118 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1119
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001120 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
szager@chromium.org848fd492014-04-09 19:06:44 +00001121 git_cache.Mirror.SetCachePath(config_dict.get('cache_dir'))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001122
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001123 if not target_os and config_dict.get('target_os_only', False):
1124 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1125 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001126
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001127 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001128 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001129 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001130 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001131 self, s['name'], s['url'],
1132 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001133 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001134 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001135 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001136 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001137 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001138 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001139 except KeyError:
1140 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1141 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001142 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1143 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001144
1145 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001146 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001147 self._options.config_filename),
1148 self.config_content)
1149
1150 @staticmethod
1151 def LoadCurrentConfig(options):
1152 """Searches for and loads a .gclient file relative to the current working
1153 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001154 if options.spec:
1155 client = GClient('.', options)
1156 client.SetConfig(options.spec)
1157 else:
1158 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1159 if not path:
1160 return None
1161 client = GClient(path, options)
1162 client.SetConfig(gclient_utils.FileRead(
1163 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001164
1165 if (options.revisions and
1166 len(client.dependencies) > 1 and
1167 any('@' not in r for r in options.revisions)):
1168 print >> sys.stderr, (
1169 'You must specify the full solution name like --revision %s@%s\n'
1170 'when you have multiple solutions setup in your .gclient file.\n'
1171 'Other solutions present are: %s.') % (
1172 client.dependencies[0].name,
1173 options.revisions[0],
1174 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001175 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001176
nsylvain@google.comefc80932011-05-31 21:27:56 +00001177 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001178 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001179 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1180 'solution_name': solution_name,
1181 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001182 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001184 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001185 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001186 })
1187
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001188 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001189 """Creates a .gclient_entries file to record the list of unique checkouts.
1190
1191 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001192 """
1193 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1194 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001195 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001196 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001197 # Skip over File() dependencies as we can't version them.
1198 if not isinstance(entry.parsed_url, self.FileImpl):
1199 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1200 pprint.pformat(entry.parsed_url))
1201 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001202 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001203 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001204 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001205
1206 def _ReadEntries(self):
1207 """Read the .gclient_entries file for the given client.
1208
1209 Returns:
1210 A sequence of solution names, which will be empty if there is the
1211 entries file hasn't been created yet.
1212 """
1213 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001214 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001215 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001216 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001217 try:
1218 exec(gclient_utils.FileRead(filename), scope)
1219 except SyntaxError, e:
1220 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001221 return scope['entries']
1222
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001223 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001224 """Checks for revision overrides."""
1225 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001226 if self._options.head:
1227 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001228 # Do not check safesync_url if one or more --revision flag is specified.
1229 if not self._options.revisions:
1230 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001231 if not s.managed:
1232 self._options.revisions.append('%s@unmanaged' % s.name)
1233 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001234 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001235 if not self._options.revisions:
1236 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001237 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001238 index = 0
1239 for revision in self._options.revisions:
1240 if not '@' in revision:
1241 # Support for --revision 123
1242 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001243 name, rev = revision.split('@', 1)
1244 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001245 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001246 return revision_overrides
1247
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001248 def _ApplySafeSyncRev(self, dep):
1249 """Finds a valid revision from the content of the safesync_url and apply it
1250 by appending revisions to the revision list. Throws if revision appears to
1251 be invalid for the given |dep|."""
1252 assert len(dep.safesync_url) > 0
1253 handle = urllib.urlopen(dep.safesync_url)
1254 rev = handle.read().strip()
1255 handle.close()
1256 if not rev:
1257 raise gclient_utils.Error(
1258 'It appears your safesync_url (%s) is not working properly\n'
1259 '(as it returned an empty response). Check your config.' %
1260 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001261 scm = gclient_scm.CreateSCM(
1262 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001263 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001264 if self._options.verbose:
1265 print('Using safesync_url revision: %s.\n' % safe_rev)
1266 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1267
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001268 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001269 """Runs a command on each dependency in a client and its dependencies.
1270
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001271 Args:
1272 command: The command to use (e.g., 'status' or 'diff')
1273 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001274 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001275 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001276 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001277
1278 self._CheckConfig()
1279
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001280 revision_overrides = {}
1281 # It's unnecessary to check for revision overrides for 'recurse'.
1282 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001283 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001284 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001285 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001286 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001287 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001288 if command in ('update', 'revert'):
1289 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001290 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001291 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001292 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001293 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1294 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001295 for s in self.dependencies:
1296 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001297 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001298 if revision_overrides:
1299 print >> sys.stderr, ('Please fix your script, having invalid '
1300 '--revision flags will soon considered an error.')
piman@chromium.org6f363722010-04-27 00:41:09 +00001301
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001302 # Once all the dependencies have been processed, it's now safe to run the
1303 # hooks.
1304 if not self._options.nohooks:
1305 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001306
1307 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001308 # Notify the user if there is an orphaned entry in their working copy.
1309 # Only delete the directory if there are no changes in it, and
1310 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001311 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001312 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1313 for e in entries]
1314
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001315 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001316 if not prev_url:
1317 # entry must have been overridden via .gclient custom_deps
1318 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001319 # Fix path separator on Windows.
1320 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001321 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001322
1323 def _IsParentOfAny(parent, path_list):
1324 parent_plus_slash = parent + '/'
1325 return any(
1326 path[:len(parent_plus_slash)] == parent_plus_slash
1327 for path in path_list)
1328
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001329 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001330 if (entry not in entries and
1331 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001332 os.path.exists(e_dir)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001333 scm = gclient_scm.CreateSCM(
1334 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001335
1336 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001337 # The directory might be part of a git OR svn checkout.
1338 scm_root = None
1339 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1340 try:
1341 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1342 except subprocess2.CalledProcessError:
1343 pass
1344 if scm_root:
1345 break
1346 else:
1347 logging.warning('Could not find checkout root for %s. Unable to '
1348 'determine whether it is part of a higher-level '
1349 'checkout, so not removing.' % entry)
1350 continue
1351 if scm_root in full_entries:
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001352 logging.info('%s is part of a higher level checkout, not '
1353 'removing.', scm.GetCheckoutRoot())
1354 continue
1355
1356 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001357 scm.status(self._options, [], file_list)
1358 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001359 if (not self._options.delete_unversioned_trees or
1360 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001361 # There are modified files in this entry. Keep warning until
1362 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001363 print(('\nWARNING: \'%s\' is no longer part of this client. '
1364 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001365 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001366 else:
1367 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001368 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001369 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001370 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001371 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001372 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001373 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001374
1375 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001376 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001377 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001378 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001379 work_queue = gclient_utils.ExecutionQueue(
1380 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001381 for s in self.dependencies:
1382 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001383 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001384
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001385 def GetURLAndRev(dep):
1386 """Returns the revision-qualified SCM url for a Dependency."""
1387 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001388 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001389 if isinstance(dep.parsed_url, self.FileImpl):
1390 original_url = dep.parsed_url.file_location
1391 else:
1392 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001393 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001394 scm = gclient_scm.CreateSCM(
1395 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001396 if not os.path.isdir(scm.checkout_path):
1397 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001398 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001399
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001400 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001401 new_gclient = ''
1402 # First level at .gclient
1403 for d in self.dependencies:
1404 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001405 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001406 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001407 for d in dep.dependencies:
1408 entries[d.name] = GetURLAndRev(d)
1409 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001410 GrabDeps(d)
1411 custom_deps = []
1412 for k in sorted(entries.keys()):
1413 if entries[k]:
1414 # Quotes aren't escaped...
1415 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1416 else:
1417 custom_deps.append(' \"%s\": None,\n' % k)
1418 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1419 'solution_name': d.name,
1420 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001421 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001422 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001423 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001424 'solution_deps': ''.join(custom_deps),
1425 }
1426 # Print the snapshot configuration file
1427 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001428 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001429 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001430 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001431 if self._options.actual:
1432 entries[d.name] = GetURLAndRev(d)
1433 else:
1434 entries[d.name] = d.parsed_url
1435 keys = sorted(entries.keys())
1436 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001437 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001438 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001439
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001440 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001441 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001442 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001443
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001444 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001445 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001446 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001447 return self._root_dir
1448
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001449 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001450 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001451 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001452 return self._enforced_os
1453
maruel@chromium.org68988972011-09-20 14:11:42 +00001454 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001455 def recursion_limit(self):
1456 """How recursive can each dependencies in DEPS file can load DEPS file."""
1457 return self._recursion_limit
1458
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001459 @property
1460 def target_os(self):
1461 return self._enforced_os
1462
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001463
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001464#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001465
1466
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001467def CMDcleanup(parser, args):
1468 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001469
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001470 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1471 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001472 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1473 help='override deps for the specified (comma-separated) '
1474 'platform(s); \'all\' will process all deps_os '
1475 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001476 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001477 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001478 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001479 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001480 if options.verbose:
1481 # Print out the .gclient file. This is longer than if we just printed the
1482 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001483 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001484 return client.RunOnDeps('cleanup', args)
1485
1486
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001487@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001488def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001489 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001490
1491 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001492 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1493 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001494 """
1495 # Stop parsing at the first non-arg so that these go through to the command
1496 parser.disable_interspersed_args()
1497 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001498 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001499 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001500 help='Ignore non-zero return codes from subcommands.')
1501 parser.add_option('--prepend-dir', action='store_true',
1502 help='Prepend relative dir for use with git <cmd> --null.')
1503 parser.add_option('--no-progress', action='store_true',
1504 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001505 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001506 if not args:
1507 print >> sys.stderr, 'Need to supply a command!'
1508 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001509 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1510 if not root_and_entries:
1511 print >> sys.stderr, (
1512 'You need to run gclient sync at least once to use \'recurse\'.\n'
1513 'This is because .gclient_entries needs to exist and be up to date.')
1514 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001515
1516 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001517 scm_set = set()
1518 for scm in options.scm:
1519 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001520 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001521
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001522 options.nohooks = True
1523 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001524 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1525 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001526
1527
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001528@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001529def CMDfetch(parser, args):
1530 """Fetches upstream commits for all modules.
1531
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001532 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1533 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001534 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001535 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001536 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1537
1538
1539def CMDgrep(parser, args):
1540 """Greps through git repos managed by gclient.
1541
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001542 Runs 'git grep [args...]' for each module.
1543 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001544 # We can't use optparse because it will try to parse arguments sent
1545 # to git grep and throw an error. :-(
1546 if not args or re.match('(-h|--help)$', args[0]):
1547 print >> sys.stderr, (
1548 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1549 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1550 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1551 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1552 ' end of your query.'
1553 )
1554 return 1
1555
1556 jobs_arg = ['--jobs=1']
1557 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1558 jobs_arg, args = args[:1], args[1:]
1559 elif re.match(r'(-j|--jobs)$', args[0]):
1560 jobs_arg, args = args[:2], args[2:]
1561
1562 return CMDrecurse(
1563 parser,
1564 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1565 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001566
1567
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001568@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001569def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001570 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001571
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001572 This specifies the configuration for further commands. After update/sync,
1573 top-level DEPS files in each module are read to determine dependent
1574 modules to operate on as well. If optional [url] parameter is
1575 provided, then configuration is read from a specified Subversion server
1576 URL.
1577 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001578 # We do a little dance with the --gclientfile option. 'gclient config' is the
1579 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1580 # arguments. So, we temporarily stash any --gclientfile parameter into
1581 # options.output_config_file until after the (gclientfile xor spec) error
1582 # check.
1583 parser.remove_option('--gclientfile')
1584 parser.add_option('--gclientfile', dest='output_config_file',
1585 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001586 parser.add_option('--name',
1587 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001588 parser.add_option('--deps-file', default='DEPS',
1589 help='overrides the default name for the DEPS file for the'
1590 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001591 parser.add_option('--unmanaged', action='store_true', default=False,
1592 help='overrides the default behavior to make it possible '
1593 'to have the main solution untouched by gclient '
1594 '(gclient will check out unmanaged dependencies but '
1595 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001596 parser.add_option('--git-deps', action='store_true',
1597 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001598 parser.add_option('--cache-dir',
1599 help='(git only) Cache all git repos into this dir and do '
1600 'shared clones from the cache, instead of cloning '
1601 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001602 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001603 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001604 if options.output_config_file:
1605 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001606 if ((options.spec and args) or len(args) > 2 or
1607 (not options.spec and not args)):
1608 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1609
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001610 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001611 if options.spec:
1612 client.SetConfig(options.spec)
1613 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001614 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001615 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001616 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001617 if name.endswith('.git'):
1618 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001619 else:
1620 # specify an alternate relpath for the given URL.
1621 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001622 deps_file = options.deps_file
1623 if options.git_deps:
1624 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001625 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001626 if len(args) > 1:
1627 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001628 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001629 managed=not options.unmanaged,
1630 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001631 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001632 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001633
1634
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001635@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001636 gclient pack > patch.txt
1637 generate simple patch for configured client and dependences
1638""")
1639def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001640 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001641
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001642 Internally, runs 'svn diff'/'git diff' on each checked out module and
1643 dependencies, and performs minimal postprocessing of the output. The
1644 resulting patch is printed to stdout and can be applied to a freshly
1645 checked out tree via 'patch -p0 < patchfile'.
1646 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001647 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1648 help='override deps for the specified (comma-separated) '
1649 'platform(s); \'all\' will process all deps_os '
1650 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001651 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001652 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001653 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001654 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001655 client = GClient.LoadCurrentConfig(options)
1656 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001657 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001658 if options.verbose:
1659 # Print out the .gclient file. This is longer than if we just printed the
1660 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001661 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001662 return client.RunOnDeps('pack', args)
1663
1664
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001665def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001666 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001667 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1668 help='override deps for the specified (comma-separated) '
1669 'platform(s); \'all\' will process all deps_os '
1670 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001671 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001672 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001673 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001674 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001675 if options.verbose:
1676 # Print out the .gclient file. This is longer than if we just printed the
1677 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001678 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001679 return client.RunOnDeps('status', args)
1680
1681
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001682@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001683 gclient sync
1684 update files from SCM according to current configuration,
1685 *for modules which have changed since last update or sync*
1686 gclient sync --force
1687 update files from SCM according to current configuration, for
1688 all modules (useful for recovering files deleted from local copy)
1689 gclient sync --revision src@31000
1690 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001691
1692JSON output format:
1693If the --output-json option is specified, the following document structure will
1694be emitted to the provided file. 'null' entries may occur for subprojects which
1695are present in the gclient solution, but were not processed (due to custom_deps,
1696os_deps, etc.)
1697
1698{
1699 "solutions" : {
1700 "<name>": { # <name> is the posix-normalized path to the solution.
1701 "revision": [<svn rev int>|<git id hex string>|null],
1702 "scm": ["svn"|"git"|null],
1703 }
1704 }
1705}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001706""")
1707def CMDsync(parser, args):
1708 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001709 parser.add_option('-f', '--force', action='store_true',
1710 help='force update even for unchanged modules')
1711 parser.add_option('-n', '--nohooks', action='store_true',
1712 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001713 parser.add_option('-p', '--noprehooks', action='store_true',
1714 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001715 parser.add_option('-r', '--revision', action='append',
1716 dest='revisions', metavar='REV', default=[],
1717 help='Enforces revision/hash for the solutions with the '
1718 'format src@rev. The src@ part is optional and can be '
1719 'skipped. -r can be used multiple times when .gclient '
1720 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001721 'if the src@ part is skipped. Note that specifying '
1722 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001723 parser.add_option('--with_branch_heads', action='store_true',
1724 help='Clone git "branch_heads" refspecs in addition to '
1725 'the default refspecs. This adds about 1/2GB to a '
1726 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001727 parser.add_option('-t', '--transitive', action='store_true',
1728 help='When a revision is specified (in the DEPS file or '
1729 'with the command-line flag), transitively update '
1730 'the dependencies to the date of the given revision. '
1731 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001732 parser.add_option('-H', '--head', action='store_true',
1733 help='skips any safesync_urls specified in '
1734 'configured solutions and sync to head instead')
1735 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001736 help='Deletes from the working copy any dependencies that '
1737 'have been removed since the last sync, as long as '
1738 'there are no local modifications. When used with '
1739 '--force, such dependencies are removed even if they '
1740 'have local modifications. When used with --reset, '
1741 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001742 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001743 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001744 parser.add_option('-R', '--reset', action='store_true',
1745 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001746 parser.add_option('-M', '--merge', action='store_true',
1747 help='merge upstream changes instead of trying to '
1748 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001749 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1750 help='override deps for the specified (comma-separated) '
1751 'platform(s); \'all\' will process all deps_os '
1752 'references')
1753 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1754 help='Skip svn up whenever possible by requesting '
1755 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001756 parser.add_option('--upstream', action='store_true',
1757 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001758 parser.add_option('--output-json',
1759 help='Output a json document to this path containing '
1760 'summary information about the sync.')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001761 parser.add_option('--shallow', action='store_true',
1762 help='GIT ONLY - Do a shallow clone into the cache dir. '
1763 'Requires Git 1.9+')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001764 parser.add_option('--ignore_locks', action='store_true',
1765 help='GIT ONLY - Ignore cache locks.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001766 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001767 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001768
1769 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001770 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001771
maruel@chromium.org307d1792010-05-31 20:03:13 +00001772 if options.revisions and options.head:
1773 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001774 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001775
1776 if options.verbose:
1777 # Print out the .gclient file. This is longer than if we just printed the
1778 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001779 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001780 ret = client.RunOnDeps('update', args)
1781 if options.output_json:
1782 slns = {}
1783 for d in client.subtree(True):
1784 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1785 slns[normed] = {
1786 'revision': d.got_revision,
1787 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001788 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001789 }
1790 with open(options.output_json, 'wb') as f:
1791 json.dump({'solutions': slns}, f)
1792 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001793
1794
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001795CMDupdate = CMDsync
1796
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001797
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001798def CMDdiff(parser, args):
1799 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001800 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1801 help='override deps for the specified (comma-separated) '
1802 'platform(s); \'all\' will process all deps_os '
1803 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001804 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001805 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001806 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001807 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001808 if options.verbose:
1809 # Print out the .gclient file. This is longer than if we just printed the
1810 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001811 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001812 return client.RunOnDeps('diff', args)
1813
1814
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001815def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001816 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001817
1818 That's the nuclear option to get back to a 'clean' state. It removes anything
1819 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001820 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1821 help='override deps for the specified (comma-separated) '
1822 'platform(s); \'all\' will process all deps_os '
1823 'references')
1824 parser.add_option('-n', '--nohooks', action='store_true',
1825 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001826 parser.add_option('-p', '--noprehooks', action='store_true',
1827 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001828 parser.add_option('--upstream', action='store_true',
1829 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001830 (options, args) = parser.parse_args(args)
1831 # --force is implied.
1832 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001833 options.reset = False
1834 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001835 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001836 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001837 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001838 return client.RunOnDeps('revert', args)
1839
1840
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001841def CMDrunhooks(parser, args):
1842 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001843 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1844 help='override deps for the specified (comma-separated) '
1845 'platform(s); \'all\' will process all deps_os '
1846 'references')
1847 parser.add_option('-f', '--force', action='store_true', default=True,
1848 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001849 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001850 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001851 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001852 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001853 if options.verbose:
1854 # Print out the .gclient file. This is longer than if we just printed the
1855 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001856 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001857 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001858 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001859 return client.RunOnDeps('runhooks', args)
1860
1861
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001862def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001863 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001864
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001865 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001866 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001867 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1868 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001869 commit can change.
1870 """
1871 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1872 help='override deps for the specified (comma-separated) '
1873 'platform(s); \'all\' will process all deps_os '
1874 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001875 parser.add_option('-a', '--actual', action='store_true',
1876 help='gets the actual checked out revisions instead of the '
1877 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001878 parser.add_option('-s', '--snapshot', action='store_true',
1879 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001880 'version of all repositories to reproduce the tree, '
1881 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001882 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001883 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001884 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001885 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001886 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001887 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001888
1889
szager@google.comb9a78d32012-03-13 18:46:21 +00001890def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001891 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001892 (options, args) = parser.parse_args(args)
1893 options.force = True
1894 client = GClient.LoadCurrentConfig(options)
1895 if not client:
1896 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1897 client.RunOnDeps(None, [])
1898 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1899 return 0
1900
1901
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001902class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00001903 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001904
1905 def __init__(self, **kwargs):
1906 optparse.OptionParser.__init__(
1907 self, version='%prog ' + __version__, **kwargs)
1908
1909 # Some arm boards have issues with parallel sync.
1910 if platform.machine().startswith('arm'):
1911 jobs = 1
1912 else:
1913 jobs = max(8, gclient_utils.NumLocalCpus())
1914 # cmp: 2013/06/19
1915 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00001916 # Bypassed if a bot_update flag is detected.
1917 if (os.environ.get('CHROME_HEADLESS') == '1' and
1918 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00001919 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001920
1921 self.add_option(
1922 '-j', '--jobs', default=jobs, type='int',
1923 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001924 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001925 self.add_option(
1926 '-v', '--verbose', action='count', default=0,
1927 help='Produces additional output for diagnostics. Can be used up to '
1928 'three times for more logging info.')
1929 self.add_option(
1930 '--gclientfile', dest='config_filename',
1931 help='Specify an alternate %s file' % self.gclientfile_default)
1932 self.add_option(
1933 '--spec',
1934 help='create a gclient file containing the provided string. Due to '
1935 'Cygwin/Python brokenness, it can\'t contain any newlines.')
1936 self.add_option(
1937 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00001938 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001939
1940 def parse_args(self, args=None, values=None):
1941 """Integrates standard options processing."""
1942 options, args = optparse.OptionParser.parse_args(self, args, values)
1943 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
1944 logging.basicConfig(
1945 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00001946 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001947 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001948 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00001949 if (options.config_filename and
1950 options.config_filename != os.path.basename(options.config_filename)):
1951 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001952 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001953 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001954 options.entries_filename = options.config_filename + '_entries'
1955 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001956 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001957
1958 # These hacks need to die.
1959 if not hasattr(options, 'revisions'):
1960 # GClient.RunOnDeps expects it even if not applicable.
1961 options.revisions = []
1962 if not hasattr(options, 'head'):
1963 options.head = None
1964 if not hasattr(options, 'nohooks'):
1965 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001966 if not hasattr(options, 'noprehooks'):
1967 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00001968 if not hasattr(options, 'deps_os'):
1969 options.deps_os = None
1970 if not hasattr(options, 'manually_grab_svn_rev'):
1971 options.manually_grab_svn_rev = None
1972 if not hasattr(options, 'force'):
1973 options.force = None
1974 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001975
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001976
1977def disable_buffering():
1978 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1979 # operations. Python as a strong tendency to buffer sys.stdout.
1980 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
1981 # Make stdout annotated with the thread ids.
1982 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001983
1984
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001985def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001986 """Doesn't parse the arguments here, just find the right subcommand to
1987 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001988 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001989 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001990 '\nYour python version %s is unsupported, please upgrade.\n' %
1991 sys.version.split(' ', 1)[0])
1992 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001993 if not sys.executable:
1994 print >> sys.stderr, (
1995 '\nPython cannot find the location of it\'s own executable.\n')
1996 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001997 fix_encoding.fix_encoding()
1998 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001999 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002000 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002001 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002002 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002003 except KeyboardInterrupt:
2004 gclient_utils.GClientChildren.KillAllRemainingChildren()
2005 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00002006 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002007 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002008 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002009 finally:
2010 gclient_utils.PrintWarnings()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002011
2012
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002013if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002014 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002015
2016# vim: ts=2:sw=2:tw=80:et: