blob: 76de96f6ece8ecab6c3c385c789a48c12dd0a5ef [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
517 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000518 local_scope = {}
519 var = self.VarImpl(self.custom_vars, local_scope)
520 global_scope = {
521 'File': self.FileImpl,
522 'From': self.FromImpl,
523 'Var': var.Lookup,
524 'deps_os': {},
525 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000526 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000527 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000528 logging.info(
529 'ParseDepsFile(%s): No %s file found at %s' % (
530 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000531 else:
532 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000533 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000534 # Eval the content.
535 try:
536 exec(deps_content, global_scope, local_scope)
537 except SyntaxError, e:
538 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000539 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000540 if 'recursion' in local_scope:
541 self.recursion_override = local_scope.get('recursion')
542 logging.warning(
543 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000544 # If present, save 'target_os' in the local_target_os property.
545 if 'target_os' in local_scope:
546 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000547 # load os specific dependencies if defined. these dependencies may
548 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000549 target_os_list = self.target_os
550 if 'deps_os' in local_scope and target_os_list:
551 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000552
maruel@chromium.org271375b2010-06-23 19:17:38 +0000553 # If a line is in custom_deps, but not in the solution, we want to append
554 # this line to the solution.
555 for d in self.custom_deps:
556 if d not in deps:
557 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000558
559 # If use_relative_paths is set in the DEPS file, regenerate
560 # the dictionary using paths relative to the directory containing
561 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000562 use_relative_paths = local_scope.get('use_relative_paths', False)
563 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000564 rel_deps = {}
565 for d, url in deps.items():
566 # normpath is required to allow DEPS to use .. in their
567 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000568 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
569 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000570
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000571 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000572 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000573 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000574 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000575 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000576 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000577 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000578 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000579
580 # override named sets of hooks by the custom hooks
581 hooks_to_run = []
582 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
583 for hook in local_scope.get('hooks', []):
584 if hook.get('name', '') not in hook_names_to_suppress:
585 hooks_to_run.append(hook)
586
587 # add the replacements and any additions
588 for hook in self.custom_hooks:
589 if 'action' in hook:
590 hooks_to_run.append(hook)
591
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000592 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
593 local_scope.get('pre_deps_hooks', [])]
594
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000595 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000596 logging.info('ParseDepsFile(%s) done' % self.name)
597
598 def add_dependencies_and_close(self, deps_to_add, hooks):
599 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000600 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000601 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000602 self.add_dependency(dep)
603 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000604
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000605 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000606 """Uses revision/timestamp of parent if no explicit revision was specified.
607
608 If we are performing an update and --transitive is set, use
609 - the parent's revision if 'self.url' is in the same repository
610 - the parent's timestamp otherwise
611 to update 'self.url'. The used revision/timestamp will be set in
612 'options.revision'.
613 If we have an explicit revision do nothing.
614 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000615 if command == 'update' and options.transitive and not options.revision:
616 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
617 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000618 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000619 if (options.revision and
620 not gclient_utils.IsDateRevision(options.revision)):
621 assert self.parent and self.parent.used_scm
622 # If this dependency is in the same repository as parent it's url will
623 # start with a slash. If so we take the parent revision instead of
624 # it's timestamp.
625 # (The timestamps of commits in google code are broken -- which can
626 # result in dependencies to be checked out at the wrong revision)
627 if self.url.startswith('/'):
628 if options.verbose:
629 print('Using parent\'s revision %s since we are in the same '
630 'repository.' % options.revision)
631 else:
632 parent_revision_date = self.parent.used_scm.GetRevisionDate(
633 options.revision)
634 options.revision = gclient_utils.MakeDateRevision(
635 parent_revision_date)
636 if options.verbose:
637 print('Using parent\'s revision date %s since we are in a '
638 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000639
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000640 # Arguments number differs from overridden method
641 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000642 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000643 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000644 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000645 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000646 if not self.should_process:
647 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000648 # When running runhooks, there's no need to consult the SCM.
649 # All known hooks are expected to run unconditionally regardless of working
650 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000651 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000652 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000653 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000654 if run_scm and parsed_url:
655 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000656 # Special support for single-file checkout.
657 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000658 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
659 # pylint: disable=E1103
660 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000661 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000662 parsed_url.GetPath(), self.root.root_dir, self.name,
663 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000664 self._used_scm.RunCommand('updatesingle',
665 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000666 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000667 # Create a shallow copy to mutate revision.
668 options = copy.copy(options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000669 options.revision = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000670 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000671 command, options, parsed_url, self.parent)
672 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000673 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000674 parsed_url, self.root.root_dir, self.name, self.outbuf,
675 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000676 self._got_revision = self._used_scm.RunCommand(command, options, args,
677 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000678 if file_list:
679 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000680
681 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
682 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000683 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000684 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000685 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000686 continue
687 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000688 [self.root.root_dir.lower(), file_list[i].lower()])
689 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000690 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000691 while file_list[i].startswith(('\\', '/')):
692 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000693
694 # Always parse the DEPS file.
695 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000696 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000697 if command in ('update', 'revert') and not options.noprehooks:
698 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000699
700 if self.recursion_limit:
701 # Parse the dependencies of this dependency.
702 for s in self.dependencies:
703 work_queue.enqueue(s)
704
705 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000706 if not isinstance(parsed_url, self.FileImpl):
707 # Skip file only checkout.
708 scm = gclient_scm.GetScmName(parsed_url)
709 if not options.scm or scm in options.scm:
710 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000711 # Pass in the SCM type as an env variable. Make sure we don't put
712 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000713 env = os.environ.copy()
714 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000715 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000716 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000717 env['GCLIENT_URL'] = str(parsed_url)
718 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000719 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000720 print_stdout = False
721 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000722 """Git-specific path marshaling. It is optimized for git-grep."""
723
724 def mod_path(git_pathspec):
725 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
726 modified_path = os.path.join(self.name, match.group(2))
727 branch = match.group(1) or ''
728 return '%s%s' % (branch, modified_path)
729
730 match = re.match('^Binary file ([^\0]+) matches$', line)
731 if match:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000732 print 'Binary file %s matches\n' % mod_path(match.group(1))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000733 return
734
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000735 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000736 if len(items) == 2 and items[1]:
737 print '%s : %s' % (mod_path(items[0]), items[1])
738 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000739 # Multiple null bytes or a single trailing null byte indicate
740 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000741 print '\n'.join(mod_path(path) for path in items if path)
742 else:
743 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000744 else:
745 print_stdout = True
746 filter_fn = None
747
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000748 if parsed_url is None:
749 print >> sys.stderr, 'Skipped omitted dependency %s' % cwd
750 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000751 try:
752 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000753 args, cwd=cwd, env=env, print_stdout=print_stdout,
754 filter_fn=filter_fn,
755 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000756 except subprocess2.CalledProcessError:
757 if not options.ignore:
758 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000759 else:
760 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000761
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000762
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000763 @gclient_utils.lockedmethod
764 def _run_is_done(self, file_list, parsed_url):
765 # Both these are kept for hooks that are run as a separate tree traversal.
766 self._file_list = file_list
767 self._parsed_url = parsed_url
768 self._processed = True
769
szager@google.comb9a78d32012-03-13 18:46:21 +0000770 @staticmethod
771 def GetHookAction(hook_dict, matching_file_list):
772 """Turns a parsed 'hook' dict into an executable command."""
773 logging.debug(hook_dict)
774 logging.debug(matching_file_list)
775 command = hook_dict['action'][:]
776 if command[0] == 'python':
777 # If the hook specified "python" as the first item, the action is a
778 # Python script. Run it by starting a new copy of the same
779 # interpreter.
780 command[0] = sys.executable
781 if '$matching_files' in command:
782 splice_index = command.index('$matching_files')
783 command[splice_index:splice_index + 1] = matching_file_list
784 return command
785
786 def GetHooks(self, options):
787 """Evaluates all hooks, and return them in a flat list.
788
789 RunOnDeps() must have been called before to load the DEPS.
790 """
791 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000792 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000793 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000794 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000795 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000796 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000797 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000798 # TODO(maruel): If the user is using git or git-svn, then we don't know
799 # what files have changed so we always run all hooks. It'd be nice to fix
800 # that.
801 if (options.force or
802 isinstance(self.parsed_url, self.FileImpl) or
803 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000804 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000805 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000806 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000807 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000808 # Run hooks on the basis of whether the files from the gclient operation
809 # match each hook's pattern.
810 for hook_dict in self.deps_hooks:
811 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000812 matching_file_list = [
813 f for f in self.file_list_and_children if pattern.search(f)
814 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000816 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000817 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000818 result.extend(s.GetHooks(options))
819 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000820
szager@google.comb9a78d32012-03-13 18:46:21 +0000821 def RunHooksRecursively(self, options):
822 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000823 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000824 for hook in self.GetHooks(options):
825 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000826 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000827 gclient_utils.CheckCallAndFilterAndHeader(
828 hook, cwd=self.root.root_dir, always=True)
829 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
830 # Use a discrete exit status code of 2 to indicate that a hook action
831 # failed. Users of this script may wish to treat hook action failures
832 # differently from VC failures.
833 print >> sys.stderr, 'Error: %s' % str(e)
834 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000835 finally:
836 elapsed_time = time.time() - start_time
837 if elapsed_time > 10:
838 print "Hook '%s' took %.2f secs" % (
839 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000840
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000841 def RunPreDepsHooks(self):
842 assert self.processed
843 assert self.deps_parsed
844 assert not self.pre_deps_hooks_ran
845 assert not self.hooks_ran
846 for s in self.dependencies:
847 assert not s.processed
848 self._pre_deps_hooks_ran = True
849 for hook in self.pre_deps_hooks:
850 try:
851 start_time = time.time()
852 gclient_utils.CheckCallAndFilterAndHeader(
853 hook, cwd=self.root.root_dir, always=True)
854 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
855 # Use a discrete exit status code of 2 to indicate that a hook action
856 # failed. Users of this script may wish to treat hook action failures
857 # differently from VC failures.
858 print >> sys.stderr, 'Error: %s' % str(e)
859 sys.exit(2)
860 finally:
861 elapsed_time = time.time() - start_time
862 if elapsed_time > 10:
863 print "Hook '%s' took %.2f secs" % (
864 gclient_utils.CommandToStr(hook), elapsed_time)
865
866
maruel@chromium.org0d812442010-08-10 12:41:08 +0000867 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000868 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000869 dependencies = self.dependencies
870 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000871 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000872 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000873 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000874 for i in d.subtree(include_all):
875 yield i
876
877 def depth_first_tree(self):
878 """Depth-first recursion including the root node."""
879 yield self
880 for i in self.dependencies:
881 for j in i.depth_first_tree():
882 if j.should_process:
883 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000884
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000885 @gclient_utils.lockedmethod
886 def add_dependency(self, new_dep):
887 self._dependencies.append(new_dep)
888
889 @gclient_utils.lockedmethod
890 def _mark_as_parsed(self, new_hooks):
891 self._deps_hooks.extend(new_hooks)
892 self._deps_parsed = True
893
maruel@chromium.org68988972011-09-20 14:11:42 +0000894 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000895 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000896 def dependencies(self):
897 return tuple(self._dependencies)
898
899 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000900 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000901 def deps_hooks(self):
902 return tuple(self._deps_hooks)
903
904 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000905 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000906 def pre_deps_hooks(self):
907 return tuple(self._pre_deps_hooks)
908
909 @property
910 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000911 def parsed_url(self):
912 return self._parsed_url
913
914 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000915 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000916 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000917 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000918 return self._deps_parsed
919
920 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000921 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000922 def processed(self):
923 return self._processed
924
925 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000926 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000927 def pre_deps_hooks_ran(self):
928 return self._pre_deps_hooks_ran
929
930 @property
931 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000932 def hooks_ran(self):
933 return self._hooks_ran
934
935 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000936 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000937 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000938 return tuple(self._file_list)
939
940 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000941 def used_scm(self):
942 """SCMWrapper instance for this dependency or None if not processed yet."""
943 return self._used_scm
944
945 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000946 @gclient_utils.lockedmethod
947 def got_revision(self):
948 return self._got_revision
949
950 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000951 def file_list_and_children(self):
952 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000953 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000954 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000955 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000956
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000957 def __str__(self):
958 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000959 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000960 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000961 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000962 # First try the native property if it exists.
963 if hasattr(self, '_' + i):
964 value = getattr(self, '_' + i, False)
965 else:
966 value = getattr(self, i, False)
967 if value:
968 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000969
970 for d in self.dependencies:
971 out.extend([' ' + x for x in str(d).splitlines()])
972 out.append('')
973 return '\n'.join(out)
974
975 def __repr__(self):
976 return '%s: %s' % (self.name, self.url)
977
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000978 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000979 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000980 out = '%s(%s)' % (self.name, self.url)
981 i = self.parent
982 while i and i.name:
983 out = '%s(%s) -> %s' % (i.name, i.url, out)
984 i = i.parent
985 return out
986
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000987
988class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000989 """Object that represent a gclient checkout. A tree of Dependency(), one per
990 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000991
992 DEPS_OS_CHOICES = {
993 "win32": "win",
994 "win": "win",
995 "cygwin": "win",
996 "darwin": "mac",
997 "mac": "mac",
998 "unix": "unix",
999 "linux": "unix",
1000 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001001 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001002 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001003 }
1004
1005 DEFAULT_CLIENT_FILE_TEXT = ("""\
1006solutions = [
1007 { "name" : "%(solution_name)s",
1008 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001009 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001010 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001011 "custom_deps" : {
1012 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001013 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001014 },
1015]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001016cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001017""")
1018
1019 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1020 { "name" : "%(solution_name)s",
1021 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001022 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001023 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001024 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001025%(solution_deps)s },
1026 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001027 },
1028""")
1029
1030 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1031# Snapshot generated with gclient revinfo --snapshot
1032solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001033%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001034""")
1035
1036 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001037 # Do not change previous behavior. Only solution level and immediate DEPS
1038 # are processed.
1039 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001040 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001041 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001042 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001043 if options.deps_os:
1044 enforced_os = options.deps_os.split(',')
1045 else:
1046 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1047 if 'all' in enforced_os:
1048 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001049 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001050 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001051 self.config_content = None
1052
borenet@google.com88d10082014-03-21 17:24:48 +00001053 def _CheckConfig(self):
1054 """Verify that the config matches the state of the existing checked-out
1055 solutions."""
1056 for dep in self.dependencies:
1057 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001058 scm = gclient_scm.CreateSCM(
1059 dep.url, self.root_dir, dep.name, self.outbuf)
borenet@google.com4e9be262014-04-08 19:40:30 +00001060 actual_url = scm.GetActualRemoteURL(self._options)
1061 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001062 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001063Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001064is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001065
borenet@google.com97882362014-04-07 20:06:02 +00001066The .gclient file contains:
1067%(expected_url)s (%(expected_scm)s)
1068
1069The local checkout in %(checkout_path)s reports:
1070%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001071
1072You should ensure that the URL listed in .gclient is correct and either change
1073it or fix the checkout. If you're managing your own git checkout in
1074%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1075want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001076''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1077 'expected_url': dep.url,
1078 'expected_scm': gclient_scm.GetScmName(dep.url),
1079 'actual_url': actual_url,
1080 'actual_scm': gclient_scm.GetScmName(actual_url)})
1081
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001082 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001083 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001084 config_dict = {}
1085 self.config_content = content
1086 try:
1087 exec(content, config_dict)
1088 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001089 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001090
peter@chromium.org1efccc82012-04-27 16:34:38 +00001091 # Append any target OS that is not already being enforced to the tuple.
1092 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001093 if config_dict.get('target_os_only', False):
1094 self._enforced_os = tuple(set(target_os))
1095 else:
1096 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1097
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001098 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
szager@chromium.org848fd492014-04-09 19:06:44 +00001099 git_cache.Mirror.SetCachePath(config_dict.get('cache_dir'))
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001100
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001101 if not target_os and config_dict.get('target_os_only', False):
1102 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1103 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001104
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001105 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001106 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001107 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001108 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001109 self, s['name'], s['url'],
1110 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001111 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001112 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001113 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001114 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001115 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001116 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001117 except KeyError:
1118 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1119 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001120 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1121 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001122
1123 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001124 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001125 self._options.config_filename),
1126 self.config_content)
1127
1128 @staticmethod
1129 def LoadCurrentConfig(options):
1130 """Searches for and loads a .gclient file relative to the current working
1131 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001132 if options.spec:
1133 client = GClient('.', options)
1134 client.SetConfig(options.spec)
1135 else:
1136 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1137 if not path:
1138 return None
1139 client = GClient(path, options)
1140 client.SetConfig(gclient_utils.FileRead(
1141 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001142
1143 if (options.revisions and
1144 len(client.dependencies) > 1 and
1145 any('@' not in r for r in options.revisions)):
1146 print >> sys.stderr, (
1147 'You must specify the full solution name like --revision %s@%s\n'
1148 'when you have multiple solutions setup in your .gclient file.\n'
1149 'Other solutions present are: %s.') % (
1150 client.dependencies[0].name,
1151 options.revisions[0],
1152 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001153 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001154
nsylvain@google.comefc80932011-05-31 21:27:56 +00001155 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001156 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001157 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1158 'solution_name': solution_name,
1159 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001160 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001162 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001163 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001164 })
1165
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001166 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001167 """Creates a .gclient_entries file to record the list of unique checkouts.
1168
1169 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001170 """
1171 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1172 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001173 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001174 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001175 # Skip over File() dependencies as we can't version them.
1176 if not isinstance(entry.parsed_url, self.FileImpl):
1177 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1178 pprint.pformat(entry.parsed_url))
1179 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001180 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001181 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001182 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183
1184 def _ReadEntries(self):
1185 """Read the .gclient_entries file for the given client.
1186
1187 Returns:
1188 A sequence of solution names, which will be empty if there is the
1189 entries file hasn't been created yet.
1190 """
1191 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001192 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001193 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001194 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001195 try:
1196 exec(gclient_utils.FileRead(filename), scope)
1197 except SyntaxError, e:
1198 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001199 return scope['entries']
1200
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001201 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001202 """Checks for revision overrides."""
1203 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001204 if self._options.head:
1205 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001206 # Do not check safesync_url if one or more --revision flag is specified.
1207 if not self._options.revisions:
1208 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001209 if not s.managed:
1210 self._options.revisions.append('%s@unmanaged' % s.name)
1211 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001212 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001213 if not self._options.revisions:
1214 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001215 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001216 index = 0
1217 for revision in self._options.revisions:
1218 if not '@' in revision:
1219 # Support for --revision 123
1220 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001221 name, rev = revision.split('@', 1)
1222 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001223 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001224 return revision_overrides
1225
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001226 def _ApplySafeSyncRev(self, dep):
1227 """Finds a valid revision from the content of the safesync_url and apply it
1228 by appending revisions to the revision list. Throws if revision appears to
1229 be invalid for the given |dep|."""
1230 assert len(dep.safesync_url) > 0
1231 handle = urllib.urlopen(dep.safesync_url)
1232 rev = handle.read().strip()
1233 handle.close()
1234 if not rev:
1235 raise gclient_utils.Error(
1236 'It appears your safesync_url (%s) is not working properly\n'
1237 '(as it returned an empty response). Check your config.' %
1238 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001239 scm = gclient_scm.CreateSCM(
1240 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001241 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001242 if self._options.verbose:
1243 print('Using safesync_url revision: %s.\n' % safe_rev)
1244 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1245
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001246 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001247 """Runs a command on each dependency in a client and its dependencies.
1248
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001249 Args:
1250 command: The command to use (e.g., 'status' or 'diff')
1251 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001253 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001254 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001255
1256 self._CheckConfig()
1257
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001258 revision_overrides = {}
1259 # It's unnecessary to check for revision overrides for 'recurse'.
1260 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001261 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001262 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001263 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001264 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001265 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001266 if command in ('update', 'revert'):
1267 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001268 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001269 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001270 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001271 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1272 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001273 for s in self.dependencies:
1274 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001275 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001276 if revision_overrides:
1277 print >> sys.stderr, ('Please fix your script, having invalid '
1278 '--revision flags will soon considered an error.')
piman@chromium.org6f363722010-04-27 00:41:09 +00001279
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001280 # Once all the dependencies have been processed, it's now safe to run the
1281 # hooks.
1282 if not self._options.nohooks:
1283 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284
1285 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001286 # Notify the user if there is an orphaned entry in their working copy.
1287 # Only delete the directory if there are no changes in it, and
1288 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001289 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001290 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1291 for e in entries]
1292
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001293 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001294 if not prev_url:
1295 # entry must have been overridden via .gclient custom_deps
1296 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001297 # Fix path separator on Windows.
1298 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001299 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001300
1301 def _IsParentOfAny(parent, path_list):
1302 parent_plus_slash = parent + '/'
1303 return any(
1304 path[:len(parent_plus_slash)] == parent_plus_slash
1305 for path in path_list)
1306
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001307 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001308 if (entry not in entries and
1309 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001310 os.path.exists(e_dir)):
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001311 scm = gclient_scm.CreateSCM(
1312 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001313
1314 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001315 # The directory might be part of a git OR svn checkout.
1316 scm_root = None
1317 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1318 try:
1319 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1320 except subprocess2.CalledProcessError:
1321 pass
1322 if scm_root:
1323 break
1324 else:
1325 logging.warning('Could not find checkout root for %s. Unable to '
1326 'determine whether it is part of a higher-level '
1327 'checkout, so not removing.' % entry)
1328 continue
1329 if scm_root in full_entries:
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001330 logging.info('%s is part of a higher level checkout, not '
1331 'removing.', scm.GetCheckoutRoot())
1332 continue
1333
1334 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001335 scm.status(self._options, [], file_list)
1336 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001337 if (not self._options.delete_unversioned_trees or
1338 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001339 # There are modified files in this entry. Keep warning until
1340 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001341 print(('\nWARNING: \'%s\' is no longer part of this client. '
1342 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001343 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001344 else:
1345 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001346 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001347 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001348 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001349 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001350 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001351 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001352
1353 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001354 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001355 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001356 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001357 work_queue = gclient_utils.ExecutionQueue(
1358 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001359 for s in self.dependencies:
1360 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001361 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001362
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001363 def GetURLAndRev(dep):
1364 """Returns the revision-qualified SCM url for a Dependency."""
1365 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001366 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001367 if isinstance(dep.parsed_url, self.FileImpl):
1368 original_url = dep.parsed_url.file_location
1369 else:
1370 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001371 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001372 scm = gclient_scm.CreateSCM(
1373 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001374 if not os.path.isdir(scm.checkout_path):
1375 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001376 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001377
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001378 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001379 new_gclient = ''
1380 # First level at .gclient
1381 for d in self.dependencies:
1382 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001383 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001384 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001385 for d in dep.dependencies:
1386 entries[d.name] = GetURLAndRev(d)
1387 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001388 GrabDeps(d)
1389 custom_deps = []
1390 for k in sorted(entries.keys()):
1391 if entries[k]:
1392 # Quotes aren't escaped...
1393 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1394 else:
1395 custom_deps.append(' \"%s\": None,\n' % k)
1396 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1397 'solution_name': d.name,
1398 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001399 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001400 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001401 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001402 'solution_deps': ''.join(custom_deps),
1403 }
1404 # Print the snapshot configuration file
1405 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001406 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001407 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001408 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001409 if self._options.actual:
1410 entries[d.name] = GetURLAndRev(d)
1411 else:
1412 entries[d.name] = d.parsed_url
1413 keys = sorted(entries.keys())
1414 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001415 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001416 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001417
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001418 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001419 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001420 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001421
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001422 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001423 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001424 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001425 return self._root_dir
1426
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001427 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001428 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001429 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001430 return self._enforced_os
1431
maruel@chromium.org68988972011-09-20 14:11:42 +00001432 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001433 def recursion_limit(self):
1434 """How recursive can each dependencies in DEPS file can load DEPS file."""
1435 return self._recursion_limit
1436
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001437 @property
1438 def target_os(self):
1439 return self._enforced_os
1440
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001441
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001442#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001443
1444
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001445def CMDcleanup(parser, args):
1446 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001447
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001448 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1449 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001450 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1451 help='override deps for the specified (comma-separated) '
1452 'platform(s); \'all\' will process all deps_os '
1453 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001454 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001455 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001457 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001458 if options.verbose:
1459 # Print out the .gclient file. This is longer than if we just printed the
1460 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001461 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001462 return client.RunOnDeps('cleanup', args)
1463
1464
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001465@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001466def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001467 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001468
1469 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001470 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1471 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001472 """
1473 # Stop parsing at the first non-arg so that these go through to the command
1474 parser.disable_interspersed_args()
1475 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001476 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001477 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001478 help='Ignore non-zero return codes from subcommands.')
1479 parser.add_option('--prepend-dir', action='store_true',
1480 help='Prepend relative dir for use with git <cmd> --null.')
1481 parser.add_option('--no-progress', action='store_true',
1482 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001483 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001484 if not args:
1485 print >> sys.stderr, 'Need to supply a command!'
1486 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001487 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1488 if not root_and_entries:
1489 print >> sys.stderr, (
1490 'You need to run gclient sync at least once to use \'recurse\'.\n'
1491 'This is because .gclient_entries needs to exist and be up to date.')
1492 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001493
1494 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001495 scm_set = set()
1496 for scm in options.scm:
1497 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001498 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001499
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001500 options.nohooks = True
1501 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001502 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1503 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001504
1505
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001506@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001507def CMDfetch(parser, args):
1508 """Fetches upstream commits for all modules.
1509
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001510 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1511 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001512 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001513 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001514 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1515
1516
1517def CMDgrep(parser, args):
1518 """Greps through git repos managed by gclient.
1519
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001520 Runs 'git grep [args...]' for each module.
1521 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001522 # We can't use optparse because it will try to parse arguments sent
1523 # to git grep and throw an error. :-(
1524 if not args or re.match('(-h|--help)$', args[0]):
1525 print >> sys.stderr, (
1526 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1527 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1528 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1529 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1530 ' end of your query.'
1531 )
1532 return 1
1533
1534 jobs_arg = ['--jobs=1']
1535 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1536 jobs_arg, args = args[:1], args[1:]
1537 elif re.match(r'(-j|--jobs)$', args[0]):
1538 jobs_arg, args = args[:2], args[2:]
1539
1540 return CMDrecurse(
1541 parser,
1542 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1543 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001544
1545
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001546@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001547def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001548 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001549
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001550 This specifies the configuration for further commands. After update/sync,
1551 top-level DEPS files in each module are read to determine dependent
1552 modules to operate on as well. If optional [url] parameter is
1553 provided, then configuration is read from a specified Subversion server
1554 URL.
1555 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001556 # We do a little dance with the --gclientfile option. 'gclient config' is the
1557 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1558 # arguments. So, we temporarily stash any --gclientfile parameter into
1559 # options.output_config_file until after the (gclientfile xor spec) error
1560 # check.
1561 parser.remove_option('--gclientfile')
1562 parser.add_option('--gclientfile', dest='output_config_file',
1563 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001564 parser.add_option('--name',
1565 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001566 parser.add_option('--deps-file', default='DEPS',
1567 help='overrides the default name for the DEPS file for the'
1568 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001569 parser.add_option('--unmanaged', action='store_true', default=False,
1570 help='overrides the default behavior to make it possible '
1571 'to have the main solution untouched by gclient '
1572 '(gclient will check out unmanaged dependencies but '
1573 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001574 parser.add_option('--git-deps', action='store_true',
1575 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001576 parser.add_option('--cache-dir',
1577 help='(git only) Cache all git repos into this dir and do '
1578 'shared clones from the cache, instead of cloning '
1579 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001580 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001581 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001582 if options.output_config_file:
1583 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001584 if ((options.spec and args) or len(args) > 2 or
1585 (not options.spec and not args)):
1586 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1587
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001588 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001589 if options.spec:
1590 client.SetConfig(options.spec)
1591 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001592 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001593 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001594 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001595 if name.endswith('.git'):
1596 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001597 else:
1598 # specify an alternate relpath for the given URL.
1599 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001600 deps_file = options.deps_file
1601 if options.git_deps:
1602 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001603 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001604 if len(args) > 1:
1605 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001606 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001607 managed=not options.unmanaged,
1608 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001609 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001610 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001611
1612
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001613@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001614 gclient pack > patch.txt
1615 generate simple patch for configured client and dependences
1616""")
1617def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001618 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001619
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001620 Internally, runs 'svn diff'/'git diff' on each checked out module and
1621 dependencies, and performs minimal postprocessing of the output. The
1622 resulting patch is printed to stdout and can be applied to a freshly
1623 checked out tree via 'patch -p0 < patchfile'.
1624 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001625 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1626 help='override deps for the specified (comma-separated) '
1627 'platform(s); \'all\' will process all deps_os '
1628 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001629 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001630 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001631 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001632 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001633 client = GClient.LoadCurrentConfig(options)
1634 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001635 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001636 if options.verbose:
1637 # Print out the .gclient file. This is longer than if we just printed the
1638 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001639 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001640 return client.RunOnDeps('pack', args)
1641
1642
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001643def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001644 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001645 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1646 help='override deps for the specified (comma-separated) '
1647 'platform(s); \'all\' will process all deps_os '
1648 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001649 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001650 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001651 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001652 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001653 if options.verbose:
1654 # Print out the .gclient file. This is longer than if we just printed the
1655 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001656 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001657 return client.RunOnDeps('status', args)
1658
1659
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001660@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001661 gclient sync
1662 update files from SCM according to current configuration,
1663 *for modules which have changed since last update or sync*
1664 gclient sync --force
1665 update files from SCM according to current configuration, for
1666 all modules (useful for recovering files deleted from local copy)
1667 gclient sync --revision src@31000
1668 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001669
1670JSON output format:
1671If the --output-json option is specified, the following document structure will
1672be emitted to the provided file. 'null' entries may occur for subprojects which
1673are present in the gclient solution, but were not processed (due to custom_deps,
1674os_deps, etc.)
1675
1676{
1677 "solutions" : {
1678 "<name>": { # <name> is the posix-normalized path to the solution.
1679 "revision": [<svn rev int>|<git id hex string>|null],
1680 "scm": ["svn"|"git"|null],
1681 }
1682 }
1683}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001684""")
1685def CMDsync(parser, args):
1686 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001687 parser.add_option('-f', '--force', action='store_true',
1688 help='force update even for unchanged modules')
1689 parser.add_option('-n', '--nohooks', action='store_true',
1690 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001691 parser.add_option('-p', '--noprehooks', action='store_true',
1692 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001693 parser.add_option('-r', '--revision', action='append',
1694 dest='revisions', metavar='REV', default=[],
1695 help='Enforces revision/hash for the solutions with the '
1696 'format src@rev. The src@ part is optional and can be '
1697 'skipped. -r can be used multiple times when .gclient '
1698 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001699 'if the src@ part is skipped. Note that specifying '
1700 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001701 parser.add_option('--with_branch_heads', action='store_true',
1702 help='Clone git "branch_heads" refspecs in addition to '
1703 'the default refspecs. This adds about 1/2GB to a '
1704 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001705 parser.add_option('-t', '--transitive', action='store_true',
1706 help='When a revision is specified (in the DEPS file or '
1707 'with the command-line flag), transitively update '
1708 'the dependencies to the date of the given revision. '
1709 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001710 parser.add_option('-H', '--head', action='store_true',
1711 help='skips any safesync_urls specified in '
1712 'configured solutions and sync to head instead')
1713 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001714 help='Deletes from the working copy any dependencies that '
1715 'have been removed since the last sync, as long as '
1716 'there are no local modifications. When used with '
1717 '--force, such dependencies are removed even if they '
1718 'have local modifications. When used with --reset, '
1719 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001720 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001721 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001722 parser.add_option('-R', '--reset', action='store_true',
1723 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001724 parser.add_option('-M', '--merge', action='store_true',
1725 help='merge upstream changes instead of trying to '
1726 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001727 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1728 help='override deps for the specified (comma-separated) '
1729 'platform(s); \'all\' will process all deps_os '
1730 'references')
1731 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1732 help='Skip svn up whenever possible by requesting '
1733 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001734 parser.add_option('--upstream', action='store_true',
1735 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001736 parser.add_option('--output-json',
1737 help='Output a json document to this path containing '
1738 'summary information about the sync.')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001739 parser.add_option('--shallow', action='store_true',
1740 help='GIT ONLY - Do a shallow clone into the cache dir. '
1741 'Requires Git 1.9+')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001742 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001743 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001744
1745 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001746 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001747
maruel@chromium.org307d1792010-05-31 20:03:13 +00001748 if options.revisions and options.head:
1749 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001750 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001751
1752 if options.verbose:
1753 # Print out the .gclient file. This is longer than if we just printed the
1754 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001755 print(client.config_content)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001756 ret = client.RunOnDeps('update', args)
1757 if options.output_json:
1758 slns = {}
1759 for d in client.subtree(True):
1760 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1761 slns[normed] = {
1762 'revision': d.got_revision,
1763 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001764 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001765 }
1766 with open(options.output_json, 'wb') as f:
1767 json.dump({'solutions': slns}, f)
1768 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001769
1770
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001771CMDupdate = CMDsync
1772
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001773
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001774def CMDdiff(parser, args):
1775 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001776 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1777 help='override deps for the specified (comma-separated) '
1778 'platform(s); \'all\' will process all deps_os '
1779 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001780 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001781 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001782 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001783 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001784 if options.verbose:
1785 # Print out the .gclient file. This is longer than if we just printed the
1786 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001787 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001788 return client.RunOnDeps('diff', args)
1789
1790
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001791def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001792 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001793
1794 That's the nuclear option to get back to a 'clean' state. It removes anything
1795 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001796 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1797 help='override deps for the specified (comma-separated) '
1798 'platform(s); \'all\' will process all deps_os '
1799 'references')
1800 parser.add_option('-n', '--nohooks', action='store_true',
1801 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001802 parser.add_option('-p', '--noprehooks', action='store_true',
1803 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001804 parser.add_option('--upstream', action='store_true',
1805 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001806 (options, args) = parser.parse_args(args)
1807 # --force is implied.
1808 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001809 options.reset = False
1810 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001811 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001812 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001813 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001814 return client.RunOnDeps('revert', args)
1815
1816
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001817def CMDrunhooks(parser, args):
1818 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001819 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1820 help='override deps for the specified (comma-separated) '
1821 'platform(s); \'all\' will process all deps_os '
1822 'references')
1823 parser.add_option('-f', '--force', action='store_true', default=True,
1824 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001825 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001826 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001827 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001828 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001829 if options.verbose:
1830 # Print out the .gclient file. This is longer than if we just printed the
1831 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001832 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001833 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001834 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001835 return client.RunOnDeps('runhooks', args)
1836
1837
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001838def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001839 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001840
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001841 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001842 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001843 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1844 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001845 commit can change.
1846 """
1847 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1848 help='override deps for the specified (comma-separated) '
1849 'platform(s); \'all\' will process all deps_os '
1850 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001851 parser.add_option('-a', '--actual', action='store_true',
1852 help='gets the actual checked out revisions instead of the '
1853 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001854 parser.add_option('-s', '--snapshot', action='store_true',
1855 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001856 'version of all repositories to reproduce the tree, '
1857 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001858 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001859 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001860 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001861 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001862 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001863 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001864
1865
szager@google.comb9a78d32012-03-13 18:46:21 +00001866def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001867 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001868 (options, args) = parser.parse_args(args)
1869 options.force = True
1870 client = GClient.LoadCurrentConfig(options)
1871 if not client:
1872 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1873 client.RunOnDeps(None, [])
1874 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1875 return 0
1876
1877
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001878class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00001879 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001880
1881 def __init__(self, **kwargs):
1882 optparse.OptionParser.__init__(
1883 self, version='%prog ' + __version__, **kwargs)
1884
1885 # Some arm boards have issues with parallel sync.
1886 if platform.machine().startswith('arm'):
1887 jobs = 1
1888 else:
1889 jobs = max(8, gclient_utils.NumLocalCpus())
1890 # cmp: 2013/06/19
1891 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00001892 # Bypassed if a bot_update flag is detected.
1893 if (os.environ.get('CHROME_HEADLESS') == '1' and
1894 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00001895 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001896
1897 self.add_option(
1898 '-j', '--jobs', default=jobs, type='int',
1899 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001900 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001901 self.add_option(
1902 '-v', '--verbose', action='count', default=0,
1903 help='Produces additional output for diagnostics. Can be used up to '
1904 'three times for more logging info.')
1905 self.add_option(
1906 '--gclientfile', dest='config_filename',
1907 help='Specify an alternate %s file' % self.gclientfile_default)
1908 self.add_option(
1909 '--spec',
1910 help='create a gclient file containing the provided string. Due to '
1911 'Cygwin/Python brokenness, it can\'t contain any newlines.')
1912 self.add_option(
1913 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00001914 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001915
1916 def parse_args(self, args=None, values=None):
1917 """Integrates standard options processing."""
1918 options, args = optparse.OptionParser.parse_args(self, args, values)
1919 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
1920 logging.basicConfig(
1921 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00001922 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001923 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001924 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00001925 if (options.config_filename and
1926 options.config_filename != os.path.basename(options.config_filename)):
1927 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001928 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001929 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001930 options.entries_filename = options.config_filename + '_entries'
1931 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001932 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001933
1934 # These hacks need to die.
1935 if not hasattr(options, 'revisions'):
1936 # GClient.RunOnDeps expects it even if not applicable.
1937 options.revisions = []
1938 if not hasattr(options, 'head'):
1939 options.head = None
1940 if not hasattr(options, 'nohooks'):
1941 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001942 if not hasattr(options, 'noprehooks'):
1943 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00001944 if not hasattr(options, 'deps_os'):
1945 options.deps_os = None
1946 if not hasattr(options, 'manually_grab_svn_rev'):
1947 options.manually_grab_svn_rev = None
1948 if not hasattr(options, 'force'):
1949 options.force = None
1950 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001951
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001952
1953def disable_buffering():
1954 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1955 # operations. Python as a strong tendency to buffer sys.stdout.
1956 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
1957 # Make stdout annotated with the thread ids.
1958 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001959
1960
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001961def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001962 """Doesn't parse the arguments here, just find the right subcommand to
1963 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001964 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001965 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001966 '\nYour python version %s is unsupported, please upgrade.\n' %
1967 sys.version.split(' ', 1)[0])
1968 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001969 if not sys.executable:
1970 print >> sys.stderr, (
1971 '\nPython cannot find the location of it\'s own executable.\n')
1972 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001973 fix_encoding.fix_encoding()
1974 disable_buffering()
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001975 colorama.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001976 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001977 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001978 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001979 except KeyboardInterrupt:
1980 gclient_utils.GClientChildren.KillAllRemainingChildren()
1981 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001982 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001983 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001984 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00001985 finally:
1986 gclient_utils.PrintWarnings()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001987
1988
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001989if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001990 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001991
1992# vim: ts=2:sw=2:tw=80:et: