blob: f1428c52bfca3f06dc85d0c4070f54b18ab72985 [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +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
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000079from __future__ import print_function
80
maruel@chromium.org39c0b222013-08-17 16:57:01 +000081__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
szager@chromium.org7b8b6de2014-08-23 00:57:31 +000083import ast
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000084import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000085import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000086import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087import optparse
88import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000089import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000090import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000091import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000094import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000095import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000096import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
maruel@chromium.org35625c72011-03-23 17:34:02 +000098import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000099import gclient_scm
100import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000101import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000102from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000103import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000104import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000105import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000106
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000107CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
108
109
110def ast_dict_index(dnode, key):
111 """Search an ast.Dict for the argument key, and return its index."""
112 idx = [i for i in range(len(dnode.keys)) if (
113 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
114 if not idx:
115 return -1
116 elif len(idx) > 1:
117 raise gclient_utils.Error('Multiple dict entries with same key in AST')
118 return idx[-1]
119
120def ast2str(node, indent=0):
121 """Return a pretty-printed rendition of an ast.Node."""
122 t = type(node)
123 if t is ast.Module:
124 return '\n'.join([ast2str(x, indent) for x in node.body])
125 elif t is ast.Assign:
126 return ((' ' * indent) +
127 ' = '.join([ast2str(x) for x in node.targets] +
128 [ast2str(node.value, indent)]) + '\n')
129 elif t is ast.Name:
130 return node.id
131 elif t is ast.List:
132 if not node.elts:
133 return '[]'
134 elif len(node.elts) == 1:
135 return '[' + ast2str(node.elts[0], indent) + ']'
136 return ('[\n' + (' ' * (indent + 1)) +
137 (',\n' + (' ' * (indent + 1))).join(
138 [ast2str(x, indent + 1) for x in node.elts]) +
139 '\n' + (' ' * indent) + ']')
140 elif t is ast.Dict:
141 if not node.keys:
142 return '{}'
143 elif len(node.keys) == 1:
144 return '{%s: %s}' % (ast2str(node.keys[0]),
145 ast2str(node.values[0], indent + 1))
146 return ('{\n' + (' ' * (indent + 1)) +
147 (',\n' + (' ' * (indent + 1))).join(
148 ['%s: %s' % (ast2str(node.keys[i]),
149 ast2str(node.values[i], indent + 1))
150 for i in range(len(node.keys))]) +
151 '\n' + (' ' * indent) + '}')
152 elif t is ast.Str:
153 return "'%s'" % node.s
154 else:
155 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
156 % (node.lineno, node.col_offset, t))
157
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000158
maruel@chromium.org116704f2010-06-11 17:34:38 +0000159class GClientKeywords(object):
160 class FromImpl(object):
161 """Used to implement the From() syntax."""
162
163 def __init__(self, module_name, sub_target_name=None):
164 """module_name is the dep module we want to include from. It can also be
165 the name of a subdirectory to include from.
166
167 sub_target_name is an optional parameter if the module name in the other
168 DEPS file is different. E.g., you might want to map src/net to net."""
169 self.module_name = module_name
170 self.sub_target_name = sub_target_name
171
172 def __str__(self):
173 return 'From(%s, %s)' % (repr(self.module_name),
174 repr(self.sub_target_name))
175
maruel@chromium.org116704f2010-06-11 17:34:38 +0000176 class FileImpl(object):
177 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000178 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000179
180 def __init__(self, file_location):
181 self.file_location = file_location
182
183 def __str__(self):
184 return 'File("%s")' % self.file_location
185
186 def GetPath(self):
187 return os.path.split(self.file_location)[0]
188
189 def GetFilename(self):
190 rev_tokens = self.file_location.split('@')
191 return os.path.split(rev_tokens[0])[1]
192
193 def GetRevision(self):
194 rev_tokens = self.file_location.split('@')
195 if len(rev_tokens) > 1:
196 return rev_tokens[1]
197 return None
198
199 class VarImpl(object):
200 def __init__(self, custom_vars, local_scope):
201 self._custom_vars = custom_vars
202 self._local_scope = local_scope
203
204 def Lookup(self, var_name):
205 """Implements the Var syntax."""
206 if var_name in self._custom_vars:
207 return self._custom_vars[var_name]
208 elif var_name in self._local_scope.get("vars", {}):
209 return self._local_scope["vars"][var_name]
210 raise gclient_utils.Error("Var is not defined: %s" % var_name)
211
212
maruel@chromium.org064186c2011-09-27 23:53:33 +0000213class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 """Immutable configuration settings."""
215 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000216 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000217 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000218 GClientKeywords.__init__(self)
219
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220 # These are not mutable:
221 self._parent = parent
222 self._safesync_url = safesync_url
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000223 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000224 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 # 'managed' determines whether or not this dependency is synced/updated by
226 # gclient after gclient checks it out initially. The difference between
227 # 'managed' and 'should_process' is that the user specifies 'managed' via
228 # the --unmanaged command-line flag or a .gclient config, where
229 # 'should_process' is dynamically set by gclient if it goes over its
230 # recursion limit and controls gclient's behavior so it does not misbehave.
231 self._managed = managed
232 self._should_process = should_process
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000233 # This is a mutable value which has the list of 'target_os' OSes listed in
234 # the current deps file.
235 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000236
237 # These are only set in .gclient and not in DEPS files.
238 self._custom_vars = custom_vars or {}
239 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000240 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000241
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000242 # TODO(iannucci): Remove this when all masters are correctly substituting
243 # the new blink url.
244 if (self._custom_vars.get('webkit_trunk', '') ==
245 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000246 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000247 print('Overwriting Var("webkit_trunk") with %s' % new_url)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000248 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000249
maruel@chromium.org064186c2011-09-27 23:53:33 +0000250 # Post process the url to remove trailing slashes.
251 if isinstance(self._url, basestring):
252 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
253 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000254 self._url = self._url.replace('/@', '@')
255 elif not isinstance(self._url,
256 (self.FromImpl, self.FileImpl, None.__class__)):
257 raise gclient_utils.Error(
258 ('dependency url must be either a string, None, '
259 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000260 # Make any deps_file path platform-appropriate.
261 for sep in ['/', '\\']:
262 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000263
264 @property
265 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000266 return self._deps_file
267
268 @property
269 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000270 return self._managed
271
272 @property
273 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000274 return self._parent
275
276 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000277 def root(self):
278 """Returns the root node, a GClient object."""
279 if not self.parent:
280 # This line is to signal pylint that it could be a GClient instance.
281 return self or GClient(None, None)
282 return self.parent.root
283
284 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000285 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000286 return self._safesync_url
287
288 @property
289 def should_process(self):
290 """True if this dependency should be processed, i.e. checked out."""
291 return self._should_process
292
293 @property
294 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000295 return self._custom_vars.copy()
296
297 @property
298 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000299 return self._custom_deps.copy()
300
maruel@chromium.org064186c2011-09-27 23:53:33 +0000301 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000302 def custom_hooks(self):
303 return self._custom_hooks[:]
304
305 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000306 def url(self):
307 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000308
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000309 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000310 def target_os(self):
311 if self.local_target_os is not None:
312 return tuple(set(self.local_target_os).union(self.parent.target_os))
313 else:
314 return self.parent.target_os
315
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000316 def get_custom_deps(self, name, url):
317 """Returns a custom deps if applicable."""
318 if self.parent:
319 url = self.parent.get_custom_deps(name, url)
320 # None is a valid return value to disable a dependency.
321 return self.custom_deps.get(name, url)
322
maruel@chromium.org064186c2011-09-27 23:53:33 +0000323
324class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000325 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000326
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000327 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000328 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000329 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000330 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000331 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000332 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000333
334 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000335 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000336
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000337 self._pre_deps_hooks = []
338
maruel@chromium.org68988972011-09-20 14:11:42 +0000339 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000340 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000341 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000342 # A cache of the files affected by the current operation, necessary for
343 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000344 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000345 # List of host names from which dependencies are allowed.
346 # Default is an empty set, meaning unspecified in DEPS file, and hence all
347 # hosts will be allowed. Non-empty set means whitelist of hosts.
348 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
349 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000350 # If it is not set to True, the dependency wasn't processed for its child
351 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000352 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000353 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000354 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000355 # This dependency had its pre-DEPS hooks run
356 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000357 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000358 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000359 # This is the scm used to checkout self.url. It may be used by dependencies
360 # to get the datetime of the revision we checked out.
361 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000362 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000363 # The actual revision we ended up getting, or None if that information is
364 # unavailable
365 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000366
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000367 # This is a mutable value that overrides the normal recursion limit for this
368 # dependency. It is read from the actual DEPS file so cannot be set on
369 # class instantiation.
370 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000371 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000372 # 'no recursion' setting on a dep-by-dep basis. It will replace
373 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000374 #
375 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
376 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000377 self.recursedeps = None
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000378
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000379 if not self.name and self.parent:
380 raise gclient_utils.Error('Dependency without name')
381
maruel@chromium.org470b5432011-10-11 18:18:19 +0000382 @property
383 def requirements(self):
384 """Calculate the list of requirements."""
385 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000386 # self.parent is implicitly a requirement. This will be recursive by
387 # definition.
388 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000389 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000390
391 # For a tree with at least 2 levels*, the leaf node needs to depend
392 # on the level higher up in an orderly way.
393 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
394 # thus unsorted, while the .gclient format is a list thus sorted.
395 #
396 # * _recursion_limit is hard coded 2 and there is no hope to change this
397 # value.
398 #
399 # Interestingly enough, the following condition only works in the case we
400 # want: self is a 2nd level node. 3nd level node wouldn't need this since
401 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000402 if self.parent and self.parent.parent and not self.parent.parent.parent:
403 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000404
405 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000406 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000407
maruel@chromium.org470b5432011-10-11 18:18:19 +0000408 if self.name:
409 requirements |= set(
410 obj.name for obj in self.root.subtree(False)
411 if (obj is not self
412 and obj.name and
413 self.name.startswith(posixpath.join(obj.name, ''))))
414 requirements = tuple(sorted(requirements))
415 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
416 return requirements
417
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000418 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000419 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000420 """Returns False if recursion_override is ever specified."""
421 if self.recursion_override is not None:
422 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000423 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000424
425 @property
426 def recursion_limit(self):
427 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000428 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000429 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000430 if self.try_recursedeps and self.parent.recursedeps != None:
431 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000432 return 1
433
434 if self.recursion_override is not None:
435 return self.recursion_override
436 return max(self.parent.recursion_limit - 1, 0)
437
maruel@chromium.org470b5432011-10-11 18:18:19 +0000438 def verify_validity(self):
439 """Verifies that this Dependency is fine to add as a child of another one.
440
441 Returns True if this entry should be added, False if it is a duplicate of
442 another entry.
443 """
444 logging.info('Dependency(%s).verify_validity()' % self.name)
445 if self.name in [s.name for s in self.parent.dependencies]:
446 raise gclient_utils.Error(
447 'The same name "%s" appears multiple times in the deps section' %
448 self.name)
449 if not self.should_process:
450 # Return early, no need to set requirements.
451 return True
452
453 # This require a full tree traversal with locks.
454 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
455 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000456 self_url = self.LateOverride(self.url)
457 sibling_url = sibling.LateOverride(sibling.url)
458 # Allow to have only one to be None or ''.
459 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000460 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000461 ('Dependency %s specified more than once:\n'
462 ' %s [%s]\n'
463 'vs\n'
464 ' %s [%s]') % (
465 self.name,
466 sibling.hierarchy(),
467 sibling_url,
468 self.hierarchy(),
469 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000470 # In theory we could keep it as a shadow of the other one. In
471 # practice, simply ignore it.
472 logging.warn('Won\'t process duplicate dependency %s' % sibling)
473 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000474 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000475
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000476 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000477 """Resolves the parsed url from url.
478
479 Manages From() keyword accordingly. Do not touch self.parsed_url nor
480 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000481 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000482 parsed_url = self.get_custom_deps(self.name, url)
483 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000484 logging.info(
485 'Dependency(%s).LateOverride(%s) -> %s' %
486 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000487 return parsed_url
488
489 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000490 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000491 ref = [
492 dep for dep in self.root.subtree(True) if url.module_name == dep.name
493 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000494 if not ref:
495 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
496 url.module_name, ref))
497 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000499 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000500 found_deps = [d for d in ref.dependencies if d.name == sub_target]
501 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000502 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000503 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
504 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000505 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000506
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000507 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000508 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000509 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000510 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000511 'Dependency(%s).LateOverride(%s) -> %s (From)' %
512 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000513 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000514
515 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000517 if (not parsed_url[0] and
518 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 # A relative url. Fetch the real base.
520 path = parsed_url[2]
521 if not path.startswith('/'):
522 raise gclient_utils.Error(
523 'relative DEPS entry \'%s\' must begin with a slash' % url)
524 # Create a scm just to query the full url.
525 parent_url = self.parent.parsed_url
526 if isinstance(parent_url, self.FileImpl):
527 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000528 scm = gclient_scm.CreateSCM(
529 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000530 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000531 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000532 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000533 logging.info(
534 'Dependency(%s).LateOverride(%s) -> %s' %
535 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000536 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000537
538 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000539 logging.info(
540 'Dependency(%s).LateOverride(%s) -> %s (File)' %
541 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000542 return url
543
544 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000545 logging.info(
546 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000547 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000548
549 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000550
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000551 @staticmethod
552 def MergeWithOsDeps(deps, deps_os, target_os_list):
553 """Returns a new "deps" structure that is the deps sent in updated
554 with information from deps_os (the deps_os section of the DEPS
555 file) that matches the list of target os."""
556 os_overrides = {}
557 for the_target_os in target_os_list:
558 the_target_os_deps = deps_os.get(the_target_os, {})
559 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
560 overrides = os_overrides.setdefault(os_dep_key, [])
561 overrides.append((the_target_os, os_dep_value))
562
563 # If any os didn't specify a value (we have fewer value entries
564 # than in the os list), then it wants to use the default value.
565 for os_dep_key, os_dep_value in os_overrides.iteritems():
566 if len(os_dep_value) != len(target_os_list):
567 # Record the default value too so that we don't accidently
568 # set it to None or miss a conflicting DEPS.
569 if os_dep_key in deps:
570 os_dep_value.append(('default', deps[os_dep_key]))
571
572 target_os_deps = {}
573 for os_dep_key, os_dep_value in os_overrides.iteritems():
574 # os_dep_value is a list of (os, value) pairs.
575 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
576 if not possible_values:
577 target_os_deps[os_dep_key] = None
578 else:
579 if len(possible_values) > 1:
580 # It would be possible to abort here but it would be
581 # unfortunate if we end up preventing any kind of checkout.
582 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
583 os_dep_key, os_dep_value, target_os_list)
584 # Sorting to get the same result every time in case of conflicts.
585 target_os_deps[os_dep_key] = sorted(possible_values)[0]
586
587 new_deps = deps.copy()
588 new_deps.update(target_os_deps)
589 return new_deps
590
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000591 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000592 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000593 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000594 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000595
596 deps_content = None
597 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000598
599 # First try to locate the configured deps file. If it's missing, fallback
600 # to DEPS.
601 deps_files = [self.deps_file]
602 if 'DEPS' not in deps_files:
603 deps_files.append('DEPS')
604 for deps_file in deps_files:
605 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
606 if os.path.isfile(filepath):
607 logging.info(
608 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
609 filepath)
610 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000611 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000612 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
613 filepath)
614
615 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000616 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000617 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000618 use_strict = 'use strict' in deps_content.splitlines()[0]
619
620 local_scope = {}
621 if deps_content:
622 # One thing is unintuitive, vars = {} must happen before Var() use.
623 var = self.VarImpl(self.custom_vars, local_scope)
624 if use_strict:
625 logging.info(
626 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
627 global_scope = {
628 '__builtins__': {'None': None},
629 'Var': var.Lookup,
630 'deps_os': {},
631 }
632 else:
633 global_scope = {
634 'File': self.FileImpl,
635 'From': self.FromImpl,
636 'Var': var.Lookup,
637 'deps_os': {},
638 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000639 # Eval the content.
640 try:
641 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000642 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000643 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000644 if use_strict:
645 for key, val in local_scope.iteritems():
646 if not isinstance(val, (dict, list, tuple, str)):
647 raise gclient_utils.Error(
648 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
649 (self.name, key, val))
650
maruel@chromium.org271375b2010-06-23 19:17:38 +0000651 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000652 if 'recursion' in local_scope:
653 self.recursion_override = local_scope.get('recursion')
654 logging.warning(
655 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000656 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000657 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000658 self.recursedeps = {}
659 for ent in local_scope['recursedeps']:
660 if isinstance(ent, basestring):
661 self.recursedeps[ent] = {"deps_file": self.deps_file}
662 else: # (depname, depsfilename)
663 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000664 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000665 # If present, save 'target_os' in the local_target_os property.
666 if 'target_os' in local_scope:
667 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 # load os specific dependencies if defined. these dependencies may
669 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000670 target_os_list = self.target_os
671 if 'deps_os' in local_scope and target_os_list:
672 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673
maruel@chromium.org271375b2010-06-23 19:17:38 +0000674 # If a line is in custom_deps, but not in the solution, we want to append
675 # this line to the solution.
676 for d in self.custom_deps:
677 if d not in deps:
678 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000679
680 # If use_relative_paths is set in the DEPS file, regenerate
681 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000682 # the DEPS file. Also update recursedeps if use_relative_paths is
683 # enabled.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000684 use_relative_paths = local_scope.get('use_relative_paths', False)
685 if use_relative_paths:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000686 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000687 rel_deps = {}
688 for d, url in deps.items():
689 # normpath is required to allow DEPS to use .. in their
690 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000691 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000692 logging.warning('Updating deps by prepending %s.', self.name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000693 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000695 # Update recursedeps if it's set.
696 if self.recursedeps is not None:
697 logging.warning('Updating recursedeps by prepending %s.', self.name)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000698 rel_deps = {}
699 for depname, options in self.recursedeps.iteritems():
700 rel_deps[os.path.normpath(os.path.join(self.name, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000701 self.recursedeps = rel_deps
702
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000703 if 'allowed_hosts' in local_scope:
704 try:
705 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
706 except TypeError: # raised if non-iterable
707 pass
708 if not self._allowed_hosts:
709 logging.warning("allowed_hosts is specified but empty %s",
710 self._allowed_hosts)
711 raise gclient_utils.Error(
712 'ParseDepsFile(%s): allowed_hosts must be absent '
713 'or a non-empty iterable' % self.name)
714
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000715 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000716 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000717 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000718 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000719 deps_file = self.deps_file
720 if self.recursedeps is not None:
721 ent = self.recursedeps.get(name)
722 if ent is not None:
723 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000724 deps_to_add.append(Dependency(
rwalker@codeaurora.orged89ff52016-03-31 19:18:40 +0000725 self, name, url, None, None, None, self.custom_vars, None,
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000726 deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000727 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000728
729 # override named sets of hooks by the custom hooks
730 hooks_to_run = []
731 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
732 for hook in local_scope.get('hooks', []):
733 if hook.get('name', '') not in hook_names_to_suppress:
734 hooks_to_run.append(hook)
735
736 # add the replacements and any additions
737 for hook in self.custom_hooks:
738 if 'action' in hook:
739 hooks_to_run.append(hook)
740
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000741 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
742 local_scope.get('pre_deps_hooks', [])]
743
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000744 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000745 logging.info('ParseDepsFile(%s) done' % self.name)
746
747 def add_dependencies_and_close(self, deps_to_add, hooks):
748 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000749 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000750 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000751 self.add_dependency(dep)
752 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000753
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000754 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000755 """Uses revision/timestamp of parent if no explicit revision was specified.
756
757 If we are performing an update and --transitive is set, use
758 - the parent's revision if 'self.url' is in the same repository
759 - the parent's timestamp otherwise
760 to update 'self.url'. The used revision/timestamp will be set in
761 'options.revision'.
762 If we have an explicit revision do nothing.
763 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000764 if command == 'update' and options.transitive and not options.revision:
765 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
766 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000767 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000768 if (options.revision and
769 not gclient_utils.IsDateRevision(options.revision)):
770 assert self.parent and self.parent.used_scm
771 # If this dependency is in the same repository as parent it's url will
772 # start with a slash. If so we take the parent revision instead of
773 # it's timestamp.
774 # (The timestamps of commits in google code are broken -- which can
775 # result in dependencies to be checked out at the wrong revision)
776 if self.url.startswith('/'):
777 if options.verbose:
778 print('Using parent\'s revision %s since we are in the same '
779 'repository.' % options.revision)
780 else:
781 parent_revision_date = self.parent.used_scm.GetRevisionDate(
782 options.revision)
783 options.revision = gclient_utils.MakeDateRevision(
784 parent_revision_date)
785 if options.verbose:
786 print('Using parent\'s revision date %s since we are in a '
787 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000788
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000789 def findDepsFromNotAllowedHosts(self):
790 """Returns a list of depenecies from not allowed hosts.
791
792 If allowed_hosts is not set, allows all hosts and returns empty list.
793 """
794 if not self._allowed_hosts:
795 return []
796 bad_deps = []
797 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000798 # Don't enforce this for custom_deps.
799 if dep.name in self._custom_deps:
800 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000801 if isinstance(dep.url, basestring):
802 parsed_url = urlparse.urlparse(dep.url)
803 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
804 bad_deps.append(dep)
805 return bad_deps
806
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000807 # Arguments number differs from overridden method
808 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000809 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000810 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000811 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000812 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000813 if not self.should_process:
814 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 # When running runhooks, there's no need to consult the SCM.
816 # All known hooks are expected to run unconditionally regardless of working
817 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000818 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000819 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000820 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000821 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000822 if run_scm and parsed_url:
823 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000824 # Special support for single-file checkout.
825 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000826 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
827 # pylint: disable=E1103
828 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000829 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000830 parsed_url.GetPath(), self.root.root_dir, self.name,
831 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000832 self._used_scm.RunCommand('updatesingle',
833 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000834 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000835 # Create a shallow copy to mutate revision.
836 options = copy.copy(options)
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000837 options.revision = revision_override
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000838 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000839 command, options, parsed_url, self.parent)
840 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000841 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000842 parsed_url, self.root.root_dir, self.name, self.outbuf,
843 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000844 self._got_revision = self._used_scm.RunCommand(command, options, args,
845 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000846 if file_list:
847 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000848
849 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
850 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000851 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000852 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000854 continue
855 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000856 [self.root.root_dir.lower(), file_list[i].lower()])
857 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000858 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000859 while file_list[i].startswith(('\\', '/')):
860 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000861
862 # Always parse the DEPS file.
863 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000864 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000865 if command in ('update', 'revert') and not options.noprehooks:
866 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000867
868 if self.recursion_limit:
869 # Parse the dependencies of this dependency.
870 for s in self.dependencies:
871 work_queue.enqueue(s)
872
873 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000874 if not isinstance(parsed_url, self.FileImpl):
875 # Skip file only checkout.
876 scm = gclient_scm.GetScmName(parsed_url)
877 if not options.scm or scm in options.scm:
878 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000879 # Pass in the SCM type as an env variable. Make sure we don't put
880 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000881 env = os.environ.copy()
882 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000883 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000884 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000885 env['GCLIENT_URL'] = str(parsed_url)
886 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000887 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000888 print_stdout = False
889 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000890 """Git-specific path marshaling. It is optimized for git-grep."""
891
892 def mod_path(git_pathspec):
893 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
894 modified_path = os.path.join(self.name, match.group(2))
895 branch = match.group(1) or ''
896 return '%s%s' % (branch, modified_path)
897
898 match = re.match('^Binary file ([^\0]+) matches$', line)
899 if match:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000900 print('Binary file %s matches\n' % mod_path(match.group(1)))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000901 return
902
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000903 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000904 if len(items) == 2 and items[1]:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000905 print('%s : %s' % (mod_path(items[0]), items[1]))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000906 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000907 # Multiple null bytes or a single trailing null byte indicate
908 # git is likely displaying filenames only (such as with -l)
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000909 print('\n'.join(mod_path(path) for path in items if path))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000910 else:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000911 print(line)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000912 else:
913 print_stdout = True
914 filter_fn = None
915
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000916 if parsed_url is None:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000917 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000918 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000919 try:
920 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000921 args, cwd=cwd, env=env, print_stdout=print_stdout,
922 filter_fn=filter_fn,
923 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000924 except subprocess2.CalledProcessError:
925 if not options.ignore:
926 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000927 else:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000928 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000929
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000931 @gclient_utils.lockedmethod
932 def _run_is_done(self, file_list, parsed_url):
933 # Both these are kept for hooks that are run as a separate tree traversal.
934 self._file_list = file_list
935 self._parsed_url = parsed_url
936 self._processed = True
937
szager@google.comb9a78d32012-03-13 18:46:21 +0000938 @staticmethod
939 def GetHookAction(hook_dict, matching_file_list):
940 """Turns a parsed 'hook' dict into an executable command."""
941 logging.debug(hook_dict)
942 logging.debug(matching_file_list)
943 command = hook_dict['action'][:]
944 if command[0] == 'python':
945 # If the hook specified "python" as the first item, the action is a
946 # Python script. Run it by starting a new copy of the same
947 # interpreter.
948 command[0] = sys.executable
949 if '$matching_files' in command:
950 splice_index = command.index('$matching_files')
951 command[splice_index:splice_index + 1] = matching_file_list
952 return command
953
954 def GetHooks(self, options):
955 """Evaluates all hooks, and return them in a flat list.
956
957 RunOnDeps() must have been called before to load the DEPS.
958 """
959 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000960 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000961 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000962 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000963 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000964 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000965 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000966 # TODO(maruel): If the user is using git or git-svn, then we don't know
967 # what files have changed so we always run all hooks. It'd be nice to fix
968 # that.
969 if (options.force or
970 isinstance(self.parsed_url, self.FileImpl) or
971 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000972 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000974 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000975 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000976 # Run hooks on the basis of whether the files from the gclient operation
977 # match each hook's pattern.
978 for hook_dict in self.deps_hooks:
979 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000980 matching_file_list = [
981 f for f in self.file_list_and_children if pattern.search(f)
982 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000984 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000985 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000986 result.extend(s.GetHooks(options))
987 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988
szager@google.comb9a78d32012-03-13 18:46:21 +0000989 def RunHooksRecursively(self, options):
990 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000991 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000992 for hook in self.GetHooks(options):
993 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000994 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000995 gclient_utils.CheckCallAndFilterAndHeader(
996 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000997 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000998 # Use a discrete exit status code of 2 to indicate that a hook action
999 # failed. Users of this script may wish to treat hook action failures
1000 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001001 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +00001002 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +00001003 finally:
1004 elapsed_time = time.time() - start_time
1005 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001006 print("Hook '%s' took %.2f secs" % (
1007 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001008
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001009 def RunPreDepsHooks(self):
1010 assert self.processed
1011 assert self.deps_parsed
1012 assert not self.pre_deps_hooks_ran
1013 assert not self.hooks_ran
1014 for s in self.dependencies:
1015 assert not s.processed
1016 self._pre_deps_hooks_ran = True
1017 for hook in self.pre_deps_hooks:
1018 try:
1019 start_time = time.time()
1020 gclient_utils.CheckCallAndFilterAndHeader(
1021 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001022 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001023 # Use a discrete exit status code of 2 to indicate that a hook action
1024 # failed. Users of this script may wish to treat hook action failures
1025 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001026 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001027 sys.exit(2)
1028 finally:
1029 elapsed_time = time.time() - start_time
1030 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001031 print("Hook '%s' took %.2f secs" % (
1032 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001033
1034
maruel@chromium.org0d812442010-08-10 12:41:08 +00001035 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001036 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001037 dependencies = self.dependencies
1038 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001039 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001040 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001041 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001042 for i in d.subtree(include_all):
1043 yield i
1044
1045 def depth_first_tree(self):
1046 """Depth-first recursion including the root node."""
1047 yield self
1048 for i in self.dependencies:
1049 for j in i.depth_first_tree():
1050 if j.should_process:
1051 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001052
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001053 @gclient_utils.lockedmethod
1054 def add_dependency(self, new_dep):
1055 self._dependencies.append(new_dep)
1056
1057 @gclient_utils.lockedmethod
1058 def _mark_as_parsed(self, new_hooks):
1059 self._deps_hooks.extend(new_hooks)
1060 self._deps_parsed = True
1061
maruel@chromium.org68988972011-09-20 14:11:42 +00001062 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001063 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001064 def dependencies(self):
1065 return tuple(self._dependencies)
1066
1067 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001069 def deps_hooks(self):
1070 return tuple(self._deps_hooks)
1071
1072 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001073 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001074 def pre_deps_hooks(self):
1075 return tuple(self._pre_deps_hooks)
1076
1077 @property
1078 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001079 def parsed_url(self):
1080 return self._parsed_url
1081
1082 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001083 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001084 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001085 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001086 return self._deps_parsed
1087
1088 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001089 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001090 def processed(self):
1091 return self._processed
1092
1093 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001094 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001095 def pre_deps_hooks_ran(self):
1096 return self._pre_deps_hooks_ran
1097
1098 @property
1099 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001100 def hooks_ran(self):
1101 return self._hooks_ran
1102
1103 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001104 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001105 def allowed_hosts(self):
1106 return self._allowed_hosts
1107
1108 @property
1109 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001110 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001111 return tuple(self._file_list)
1112
1113 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001114 def used_scm(self):
1115 """SCMWrapper instance for this dependency or None if not processed yet."""
1116 return self._used_scm
1117
1118 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001119 @gclient_utils.lockedmethod
1120 def got_revision(self):
1121 return self._got_revision
1122
1123 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001124 def file_list_and_children(self):
1125 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001126 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001127 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001128 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001129
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001130 def __str__(self):
1131 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001132 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001133 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001134 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1135 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001136 # First try the native property if it exists.
1137 if hasattr(self, '_' + i):
1138 value = getattr(self, '_' + i, False)
1139 else:
1140 value = getattr(self, i, False)
1141 if value:
1142 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001143
1144 for d in self.dependencies:
1145 out.extend([' ' + x for x in str(d).splitlines()])
1146 out.append('')
1147 return '\n'.join(out)
1148
1149 def __repr__(self):
1150 return '%s: %s' % (self.name, self.url)
1151
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001152 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001153 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001154 out = '%s(%s)' % (self.name, self.url)
1155 i = self.parent
1156 while i and i.name:
1157 out = '%s(%s) -> %s' % (i.name, i.url, out)
1158 i = i.parent
1159 return out
1160
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161
1162class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001163 """Object that represent a gclient checkout. A tree of Dependency(), one per
1164 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001165
1166 DEPS_OS_CHOICES = {
1167 "win32": "win",
1168 "win": "win",
1169 "cygwin": "win",
1170 "darwin": "mac",
1171 "mac": "mac",
1172 "unix": "unix",
1173 "linux": "unix",
1174 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001175 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001176 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001177 }
1178
1179 DEFAULT_CLIENT_FILE_TEXT = ("""\
1180solutions = [
1181 { "name" : "%(solution_name)s",
1182 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001183 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001184 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001185 "custom_deps" : {
1186 },
maruel@chromium.org73e21142010-07-05 13:32:01 +00001187 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001188 },
1189]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001190cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001191""")
1192
1193 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
1194 { "name" : "%(solution_name)s",
1195 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001196 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001197 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001198 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +00001199%(solution_deps)s },
1200 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001201 },
1202""")
1203
1204 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1205# Snapshot generated with gclient revinfo --snapshot
1206solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001207%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001208""")
1209
1210 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001211 # Do not change previous behavior. Only solution level and immediate DEPS
1212 # are processed.
1213 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001214 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001215 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001216 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001217 if options.deps_os:
1218 enforced_os = options.deps_os.split(',')
1219 else:
1220 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1221 if 'all' in enforced_os:
1222 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001223 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001224 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001225 self.config_content = None
1226
borenet@google.com88d10082014-03-21 17:24:48 +00001227 def _CheckConfig(self):
1228 """Verify that the config matches the state of the existing checked-out
1229 solutions."""
1230 for dep in self.dependencies:
1231 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001232 scm = gclient_scm.CreateSCM(
1233 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001234 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001235 if actual_url and not scm.DoesRemoteURLMatch(self._options):
borenet@google.com0a427372014-04-02 19:12:13 +00001236 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001237Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001238is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001239
borenet@google.com97882362014-04-07 20:06:02 +00001240The .gclient file contains:
1241%(expected_url)s (%(expected_scm)s)
1242
1243The local checkout in %(checkout_path)s reports:
1244%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001245
1246You should ensure that the URL listed in .gclient is correct and either change
1247it or fix the checkout. If you're managing your own git checkout in
1248%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1249want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001250''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1251 'expected_url': dep.url,
1252 'expected_scm': gclient_scm.GetScmName(dep.url),
1253 'actual_url': actual_url,
1254 'actual_scm': gclient_scm.GetScmName(actual_url)})
1255
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001256 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001257 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001258 config_dict = {}
1259 self.config_content = content
1260 try:
1261 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001262 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001263 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001264
peter@chromium.org1efccc82012-04-27 16:34:38 +00001265 # Append any target OS that is not already being enforced to the tuple.
1266 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001267 if config_dict.get('target_os_only', False):
1268 self._enforced_os = tuple(set(target_os))
1269 else:
1270 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1271
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001272 cache_dir = config_dict.get('cache_dir')
1273 if cache_dir:
1274 cache_dir = os.path.join(self.root_dir, cache_dir)
1275 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001276 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001277 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001278 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1279 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001280 gclient_scm.GitWrapper.cache_dir = cache_dir
1281 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001282
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001283 if not target_os and config_dict.get('target_os_only', False):
1284 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1285 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001286
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001287 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001288 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001289 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001290 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001291 self, s['name'], s['url'],
1292 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001293 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001294 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001295 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001296 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001297 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001298 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001299 except KeyError:
1300 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1301 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001302 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1303 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001304
1305 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001306 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001307 self._options.config_filename),
1308 self.config_content)
1309
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001310 def MigrateConfigToGit(self, path, options):
1311 svn_url_re = re.compile('^(https?://src\.chromium\.org/svn|'
1312 'svn://svn\.chromium\.org/chrome)/'
1313 '(trunk|branches/[^/]+)/src')
1314 old_git_re = re.compile('^(https?://git\.chromium\.org|'
1315 'ssh://([a-zA-Z_][a-zA-Z0-9_-]*@)?'
1316 'gerrit\.chromium\.org(:2941[89])?)/'
1317 'chromium/src\.git')
1318 # Scan existing .gclient file for obsolete settings. It would be simpler
1319 # to traverse self.dependencies, but working with the AST allows the code to
1320 # dump an updated .gclient file that preserves the ordering of the original.
1321 a = ast.parse(self.config_content, options.config_filename, 'exec')
1322 modified = False
1323 solutions = [elem for elem in a.body if 'solutions' in
1324 [target.id for target in elem.targets]]
1325 if not solutions:
1326 return self
1327 solutions = solutions[-1]
1328 for solution in solutions.value.elts:
1329 # Check for obsolete URL's
1330 url_idx = ast_dict_index(solution, 'url')
1331 if url_idx == -1:
1332 continue
1333 url_val = solution.values[url_idx]
1334 if type(url_val) is not ast.Str:
1335 continue
1336 if (svn_url_re.match(url_val.s.strip())):
1337 raise gclient_utils.Error(
1338"""
1339The chromium code repository has migrated completely to git.
1340Your SVN-based checkout is now obsolete; you need to create a brand-new
1341git checkout by following these instructions:
1342
1343http://www.chromium.org/developers/how-tos/get-the-code
1344""")
1345 if (old_git_re.match(url_val.s.strip())):
1346 url_val.s = CHROMIUM_SRC_URL
1347 modified = True
1348
szager@chromium.org808bcfb2014-08-24 19:38:43 +00001349 # Ensure deps_file is set to .DEPS.git. We enforce this here to smooth
1350 # over switching between pre-git-migration and post-git-migration
1351 # revisions.
1352 # - For pre-migration revisions, .DEPS.git must be explicitly set.
1353 # - For post-migration revisions, .DEPS.git is not present, so gclient
1354 # will correctly fall back to DEPS.
1355 if url_val.s == CHROMIUM_SRC_URL:
1356 deps_file_idx = ast_dict_index(solution, 'deps_file')
1357 if deps_file_idx != -1:
1358 continue
1359 solution.keys.append(ast.Str('deps_file'))
1360 solution.values.append(ast.Str('.DEPS.git'))
1361 modified = True
1362
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001363 if not modified:
1364 return self
1365
1366 print(
1367"""
1368WARNING: gclient detected an obsolete setting in your %s file. The file has
1369been automagically updated. The previous version is available at %s.old.
1370""" % (options.config_filename, options.config_filename))
1371
1372 # Replace existing .gclient with the updated version.
1373 # Return a new GClient instance based on the new content.
1374 new_content = ast2str(a)
1375 dot_gclient_fn = os.path.join(path, options.config_filename)
iannucci@chromium.orgc7c41682014-08-23 03:44:18 +00001376 try:
1377 os.rename(dot_gclient_fn, dot_gclient_fn + '.old')
1378 except OSError:
1379 pass
1380 with open(dot_gclient_fn, 'w') as fh:
1381 fh.write(new_content)
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001382 client = GClient(path, options)
1383 client.SetConfig(new_content)
1384 return client
1385
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001386 @staticmethod
1387 def LoadCurrentConfig(options):
1388 """Searches for and loads a .gclient file relative to the current working
1389 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001390 if options.spec:
1391 client = GClient('.', options)
1392 client.SetConfig(options.spec)
1393 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001394 if options.verbose:
1395 print('Looking for %s starting from %s\n' % (
1396 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001397 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1398 if not path:
1399 return None
1400 client = GClient(path, options)
1401 client.SetConfig(gclient_utils.FileRead(
1402 os.path.join(path, options.config_filename)))
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001403 client = client.MigrateConfigToGit(path, options)
maruel@chromium.org69392e72011-10-13 22:09:00 +00001404
1405 if (options.revisions and
1406 len(client.dependencies) > 1 and
1407 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001408 print(
1409 ('You must specify the full solution name like --revision %s@%s\n'
1410 'when you have multiple solutions setup in your .gclient file.\n'
1411 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001412 client.dependencies[0].name,
1413 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001414 ', '.join(s.name for s in client.dependencies[1:])),
1415 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001416 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001417
nsylvain@google.comefc80932011-05-31 21:27:56 +00001418 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001419 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001420 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1421 'solution_name': solution_name,
1422 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001423 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001424 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001425 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001426 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001427 })
1428
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001429 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001430 """Creates a .gclient_entries file to record the list of unique checkouts.
1431
1432 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001433 """
1434 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1435 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001436 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001437 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001438 # Skip over File() dependencies as we can't version them.
1439 if not isinstance(entry.parsed_url, self.FileImpl):
1440 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1441 pprint.pformat(entry.parsed_url))
1442 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001443 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001444 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001445 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001446
1447 def _ReadEntries(self):
1448 """Read the .gclient_entries file for the given client.
1449
1450 Returns:
1451 A sequence of solution names, which will be empty if there is the
1452 entries file hasn't been created yet.
1453 """
1454 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001455 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001456 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001457 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001458 try:
1459 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001460 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001461 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001462 return scope['entries']
1463
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001464 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001465 """Checks for revision overrides."""
1466 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001467 if self._options.head:
1468 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001469 # Do not check safesync_url if one or more --revision flag is specified.
1470 if not self._options.revisions:
1471 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001472 if not s.managed:
1473 self._options.revisions.append('%s@unmanaged' % s.name)
1474 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001475 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001476 if not self._options.revisions:
1477 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001478 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001479 index = 0
1480 for revision in self._options.revisions:
1481 if not '@' in revision:
1482 # Support for --revision 123
1483 revision = '%s@%s' % (solutions_names[index], revision)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001484 name, rev = revision.split('@', 1)
1485 revision_overrides[name] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001486 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001487 return revision_overrides
1488
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001489 def _ApplySafeSyncRev(self, dep):
1490 """Finds a valid revision from the content of the safesync_url and apply it
1491 by appending revisions to the revision list. Throws if revision appears to
1492 be invalid for the given |dep|."""
1493 assert len(dep.safesync_url) > 0
1494 handle = urllib.urlopen(dep.safesync_url)
1495 rev = handle.read().strip()
1496 handle.close()
1497 if not rev:
1498 raise gclient_utils.Error(
1499 'It appears your safesync_url (%s) is not working properly\n'
1500 '(as it returned an empty response). Check your config.' %
1501 dep.safesync_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001502 scm = gclient_scm.CreateSCM(
1503 dep.url, dep.root.root_dir, dep.name, self.outbuf)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001504 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001505 if self._options.verbose:
1506 print('Using safesync_url revision: %s.\n' % safe_rev)
1507 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1508
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001509 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001510 """Runs a command on each dependency in a client and its dependencies.
1511
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001512 Args:
1513 command: The command to use (e.g., 'status' or 'diff')
1514 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001515 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001516 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001517 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001518
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001519 revision_overrides = {}
1520 # It's unnecessary to check for revision overrides for 'recurse'.
1521 # Save a few seconds by not calling _EnforceRevisions() in that case.
agable@chromium.org0242eb42015-06-09 00:45:31 +00001522 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001523 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001524 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001525 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001526 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001527 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001528 if command in ('update', 'revert'):
1529 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001530 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001531 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001532 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001533 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1534 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001535 for s in self.dependencies:
1536 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001537 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001538 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001539 print('Please fix your script, having invalid --revision flags will soon '
1540 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001541
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001542 # Once all the dependencies have been processed, it's now safe to run the
1543 # hooks.
1544 if not self._options.nohooks:
1545 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001546
1547 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001548 # Notify the user if there is an orphaned entry in their working copy.
1549 # Only delete the directory if there are no changes in it, and
1550 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001551 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001552 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1553 for e in entries]
1554
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001555 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001556 if not prev_url:
1557 # entry must have been overridden via .gclient custom_deps
1558 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001559 # Fix path separator on Windows.
1560 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001561 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001562 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001563 if (entry not in entries and
1564 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001565 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001566 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001567 scm = gclient_scm.CreateSCM(
1568 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001569
1570 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001571 # The directory might be part of a git OR svn checkout.
1572 scm_root = None
primiano@chromium.org1c127382015-02-17 11:15:40 +00001573 scm_class = None
borenet@google.com359bb642014-05-13 17:28:19 +00001574 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1575 try:
1576 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1577 except subprocess2.CalledProcessError:
1578 pass
1579 if scm_root:
1580 break
1581 else:
1582 logging.warning('Could not find checkout root for %s. Unable to '
1583 'determine whether it is part of a higher-level '
1584 'checkout, so not removing.' % entry)
1585 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001586
1587 # This is to handle the case of third_party/WebKit migrating from
1588 # being a DEPS entry to being part of the main project.
1589 # If the subproject is a Git project, we need to remove its .git
1590 # folder. Otherwise git operations on that folder will have different
1591 # effects depending on the current working directory.
1592 if scm_class == gclient_scm.scm.GIT and (
1593 os.path.abspath(scm_root) == os.path.abspath(e_dir)):
1594 e_par_dir = os.path.join(e_dir, os.pardir)
1595 if scm_class.IsInsideWorkTree(e_par_dir):
1596 par_scm_root = scm_class.GetCheckoutRoot(e_par_dir)
1597 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1598 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1599 if scm_class.IsDirectoryVersioned(par_scm_root, rel_e_dir):
1600 save_dir = scm.GetGitBackupDirPath()
1601 # Remove any eventual stale backup dir for the same project.
1602 if os.path.exists(save_dir):
1603 gclient_utils.rmtree(save_dir)
1604 os.rename(os.path.join(e_dir, '.git'), save_dir)
1605 # When switching between the two states (entry/ is a subproject
1606 # -> entry/ is part of the outer project), it is very likely
1607 # that some files are changed in the checkout, unless we are
1608 # jumping *exactly* across the commit which changed just DEPS.
1609 # In such case we want to cleanup any eventual stale files
1610 # (coming from the old subproject) in order to end up with a
1611 # clean checkout.
1612 scm_class.CleanupDir(par_scm_root, rel_e_dir)
1613 assert not os.path.exists(os.path.join(e_dir, '.git'))
1614 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1615 'level checkout. The git folder containing all the local'
1616 ' branches has been saved to %s.\n'
1617 'If you don\'t care about its state you can safely '
1618 'remove that folder to free up space.') %
1619 (entry, save_dir))
1620 continue
1621
borenet@google.com359bb642014-05-13 17:28:19 +00001622 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001623 logging.info('%s is part of a higher level checkout, not removing',
1624 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001625 continue
1626
1627 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001628 scm.status(self._options, [], file_list)
1629 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001630 if (not self._options.delete_unversioned_trees or
1631 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001632 # There are modified files in this entry. Keep warning until
1633 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001634 print(('\nWARNING: \'%s\' is no longer part of this client. '
1635 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001636 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001637 else:
1638 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001639 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001640 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001641 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001642 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001643 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001644 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001645
1646 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001647 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001648 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001649 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001650 work_queue = gclient_utils.ExecutionQueue(
1651 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001652 for s in self.dependencies:
1653 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001654 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001655
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001656 def GetURLAndRev(dep):
1657 """Returns the revision-qualified SCM url for a Dependency."""
1658 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001659 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001660 if isinstance(dep.parsed_url, self.FileImpl):
1661 original_url = dep.parsed_url.file_location
1662 else:
1663 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001664 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001665 scm = gclient_scm.CreateSCM(
1666 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001667 if not os.path.isdir(scm.checkout_path):
1668 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001669 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001670
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001671 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001672 new_gclient = ''
1673 # First level at .gclient
1674 for d in self.dependencies:
1675 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001676 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001677 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001678 for d in dep.dependencies:
1679 entries[d.name] = GetURLAndRev(d)
1680 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001681 GrabDeps(d)
1682 custom_deps = []
1683 for k in sorted(entries.keys()):
1684 if entries[k]:
1685 # Quotes aren't escaped...
1686 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1687 else:
1688 custom_deps.append(' \"%s\": None,\n' % k)
1689 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1690 'solution_name': d.name,
1691 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001692 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001693 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001694 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001695 'solution_deps': ''.join(custom_deps),
1696 }
1697 # Print the snapshot configuration file
1698 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001699 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001700 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001701 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001702 if self._options.actual:
1703 entries[d.name] = GetURLAndRev(d)
1704 else:
1705 entries[d.name] = d.parsed_url
1706 keys = sorted(entries.keys())
1707 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001708 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001709 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001710
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001711 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001712 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001713 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001714
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001715 def PrintLocationAndContents(self):
1716 # Print out the .gclient file. This is longer than if we just printed the
1717 # client dict, but more legible, and it might contain helpful comments.
1718 print('Loaded .gclient config in %s:\n%s' % (
1719 self.root_dir, self.config_content))
1720
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001721 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001722 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001723 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001724 return self._root_dir
1725
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001726 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001727 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001728 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001729 return self._enforced_os
1730
maruel@chromium.org68988972011-09-20 14:11:42 +00001731 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001732 def recursion_limit(self):
1733 """How recursive can each dependencies in DEPS file can load DEPS file."""
1734 return self._recursion_limit
1735
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001736 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001737 def try_recursedeps(self):
1738 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001739 return True
1740
1741 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001742 def target_os(self):
1743 return self._enforced_os
1744
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001745
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001746#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001747
1748
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001749def CMDcleanup(parser, args):
1750 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001751
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001752 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1753 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001754 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1755 help='override deps for the specified (comma-separated) '
1756 'platform(s); \'all\' will process all deps_os '
1757 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001758 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001759 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001760 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001761 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001762 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001763 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001764 return client.RunOnDeps('cleanup', args)
1765
1766
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001767@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001768def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001769 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001770
1771 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001772 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1773 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001774 """
1775 # Stop parsing at the first non-arg so that these go through to the command
1776 parser.disable_interspersed_args()
1777 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001778 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001779 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001780 help='Ignore non-zero return codes from subcommands.')
1781 parser.add_option('--prepend-dir', action='store_true',
1782 help='Prepend relative dir for use with git <cmd> --null.')
1783 parser.add_option('--no-progress', action='store_true',
1784 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001785 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001786 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001787 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001788 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001789 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1790 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001791 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001792 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001793 'This is because .gclient_entries needs to exist and be up to date.',
1794 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001795 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001796
1797 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001798 scm_set = set()
1799 for scm in options.scm:
1800 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001801 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001802
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001803 options.nohooks = True
1804 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001805 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1806 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001807
1808
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001809@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001810def CMDfetch(parser, args):
1811 """Fetches upstream commits for all modules.
1812
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001813 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1814 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001815 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001816 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001817 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1818
1819
1820def CMDgrep(parser, args):
1821 """Greps through git repos managed by gclient.
1822
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001823 Runs 'git grep [args...]' for each module.
1824 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001825 # We can't use optparse because it will try to parse arguments sent
1826 # to git grep and throw an error. :-(
1827 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001828 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001829 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1830 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1831 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1832 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001833 ' end of your query.',
1834 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001835 return 1
1836
1837 jobs_arg = ['--jobs=1']
1838 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1839 jobs_arg, args = args[:1], args[1:]
1840 elif re.match(r'(-j|--jobs)$', args[0]):
1841 jobs_arg, args = args[:2], args[2:]
1842
1843 return CMDrecurse(
1844 parser,
1845 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1846 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001847
1848
stip@chromium.orga735da22015-04-29 23:18:20 +00001849def CMDroot(parser, args):
1850 """Outputs the solution root (or current dir if there isn't one)."""
1851 (options, args) = parser.parse_args(args)
1852 client = GClient.LoadCurrentConfig(options)
1853 if client:
1854 print(os.path.abspath(client.root_dir))
1855 else:
1856 print(os.path.abspath('.'))
1857
1858
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001859@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001860def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001861 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001862
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001863 This specifies the configuration for further commands. After update/sync,
1864 top-level DEPS files in each module are read to determine dependent
1865 modules to operate on as well. If optional [url] parameter is
1866 provided, then configuration is read from a specified Subversion server
1867 URL.
1868 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001869 # We do a little dance with the --gclientfile option. 'gclient config' is the
1870 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1871 # arguments. So, we temporarily stash any --gclientfile parameter into
1872 # options.output_config_file until after the (gclientfile xor spec) error
1873 # check.
1874 parser.remove_option('--gclientfile')
1875 parser.add_option('--gclientfile', dest='output_config_file',
1876 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001877 parser.add_option('--name',
1878 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001879 parser.add_option('--deps-file', default='DEPS',
1880 help='overrides the default name for the DEPS file for the'
1881 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001882 parser.add_option('--unmanaged', action='store_true', default=False,
1883 help='overrides the default behavior to make it possible '
1884 'to have the main solution untouched by gclient '
1885 '(gclient will check out unmanaged dependencies but '
1886 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001887 parser.add_option('--cache-dir',
1888 help='(git only) Cache all git repos into this dir and do '
1889 'shared clones from the cache, instead of cloning '
1890 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001891 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001892 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001893 if options.output_config_file:
1894 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001895 if ((options.spec and args) or len(args) > 2 or
1896 (not options.spec and not args)):
1897 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1898
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001899 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001900 if options.spec:
1901 client.SetConfig(options.spec)
1902 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001903 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001904 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001905 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001906 if name.endswith('.git'):
1907 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001908 else:
1909 # specify an alternate relpath for the given URL.
1910 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001911 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1912 os.getcwd()):
1913 parser.error('Do not pass a relative path for --name.')
1914 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1915 parser.error('Do not include relative path components in --name.')
1916
nsylvain@google.comefc80932011-05-31 21:27:56 +00001917 deps_file = options.deps_file
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001918 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001919 if len(args) > 1:
1920 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001921 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001922 managed=not options.unmanaged,
1923 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001924 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001925 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001926
1927
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001928@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001929 gclient pack > patch.txt
1930 generate simple patch for configured client and dependences
1931""")
1932def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001933 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001934
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001935 Internally, runs 'svn diff'/'git diff' on each checked out module and
1936 dependencies, and performs minimal postprocessing of the output. The
1937 resulting patch is printed to stdout and can be applied to a freshly
1938 checked out tree via 'patch -p0 < patchfile'.
1939 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001940 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1941 help='override deps for the specified (comma-separated) '
1942 'platform(s); \'all\' will process all deps_os '
1943 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001944 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001945 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001946 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001947 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001948 client = GClient.LoadCurrentConfig(options)
1949 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001950 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001951 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001952 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001953 return client.RunOnDeps('pack', args)
1954
1955
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001956def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001957 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001958 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1959 help='override deps for the specified (comma-separated) '
1960 'platform(s); \'all\' will process all deps_os '
1961 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001962 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001963 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001964 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001965 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001966 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001967 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001968 return client.RunOnDeps('status', args)
1969
1970
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001971@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001972 gclient sync
1973 update files from SCM according to current configuration,
1974 *for modules which have changed since last update or sync*
1975 gclient sync --force
1976 update files from SCM according to current configuration, for
1977 all modules (useful for recovering files deleted from local copy)
1978 gclient sync --revision src@31000
1979 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001980
1981JSON output format:
1982If the --output-json option is specified, the following document structure will
1983be emitted to the provided file. 'null' entries may occur for subprojects which
1984are present in the gclient solution, but were not processed (due to custom_deps,
1985os_deps, etc.)
1986
1987{
1988 "solutions" : {
1989 "<name>": { # <name> is the posix-normalized path to the solution.
1990 "revision": [<svn rev int>|<git id hex string>|null],
1991 "scm": ["svn"|"git"|null],
1992 }
1993 }
1994}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001995""")
1996def CMDsync(parser, args):
1997 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001998 parser.add_option('-f', '--force', action='store_true',
1999 help='force update even for unchanged modules')
2000 parser.add_option('-n', '--nohooks', action='store_true',
2001 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002002 parser.add_option('-p', '--noprehooks', action='store_true',
2003 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002004 parser.add_option('-r', '--revision', action='append',
2005 dest='revisions', metavar='REV', default=[],
2006 help='Enforces revision/hash for the solutions with the '
2007 'format src@rev. The src@ part is optional and can be '
2008 'skipped. -r can be used multiple times when .gclient '
2009 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00002010 'if the src@ part is skipped. Note that specifying '
2011 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002012 parser.add_option('--with_branch_heads', action='store_true',
2013 help='Clone git "branch_heads" refspecs in addition to '
2014 'the default refspecs. This adds about 1/2GB to a '
2015 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002016 parser.add_option('--with_tags', action='store_true',
2017 help='Clone git tags in addition to the default refspecs.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00002018 parser.add_option('-t', '--transitive', action='store_true',
2019 help='When a revision is specified (in the DEPS file or '
2020 'with the command-line flag), transitively update '
2021 'the dependencies to the date of the given revision. '
2022 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002023 parser.add_option('-H', '--head', action='store_true',
2024 help='skips any safesync_urls specified in '
2025 'configured solutions and sync to head instead')
2026 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002027 help='Deletes from the working copy any dependencies that '
2028 'have been removed since the last sync, as long as '
2029 'there are no local modifications. When used with '
2030 '--force, such dependencies are removed even if they '
2031 'have local modifications. When used with --reset, '
2032 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002033 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002034 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002035 parser.add_option('-R', '--reset', action='store_true',
2036 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002037 parser.add_option('-M', '--merge', action='store_true',
2038 help='merge upstream changes instead of trying to '
2039 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002040 parser.add_option('-A', '--auto_rebase', action='store_true',
2041 help='Automatically rebase repositories against local '
2042 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002043 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2044 help='override deps for the specified (comma-separated) '
2045 'platform(s); \'all\' will process all deps_os '
2046 'references')
2047 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
2048 help='Skip svn up whenever possible by requesting '
2049 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002050 parser.add_option('--upstream', action='store_true',
2051 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002052 parser.add_option('--output-json',
2053 help='Output a json document to this path containing '
2054 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002055 parser.add_option('--no-history', action='store_true',
2056 help='GIT ONLY - Reduces the size/time of the checkout at '
2057 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002058 parser.add_option('--shallow', action='store_true',
2059 help='GIT ONLY - Do a shallow clone into the cache dir. '
2060 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002061 parser.add_option('--no_bootstrap', '--no-bootstrap',
2062 action='store_true',
2063 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002064 parser.add_option('--ignore_locks', action='store_true',
2065 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002066 parser.add_option('--break_repo_locks', action='store_true',
2067 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2068 'index.lock). This should only be used if you know for '
2069 'certain that this invocation of gclient is the only '
2070 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002071 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002072 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002073 'cache lock to become available. Default is %default.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002074 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002075 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002076
2077 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002078 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002079
maruel@chromium.org307d1792010-05-31 20:03:13 +00002080 if options.revisions and options.head:
2081 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002082 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002083
2084 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002085 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002086 ret = client.RunOnDeps('update', args)
2087 if options.output_json:
2088 slns = {}
2089 for d in client.subtree(True):
2090 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2091 slns[normed] = {
2092 'revision': d.got_revision,
2093 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002094 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002095 }
2096 with open(options.output_json, 'wb') as f:
2097 json.dump({'solutions': slns}, f)
2098 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002099
2100
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002101CMDupdate = CMDsync
2102
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002103
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002104def CMDdiff(parser, args):
2105 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002106 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2107 help='override deps for the specified (comma-separated) '
2108 'platform(s); \'all\' will process all deps_os '
2109 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002110 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002111 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002112 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002113 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002114 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002115 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002116 return client.RunOnDeps('diff', args)
2117
2118
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002119def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002120 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002121
2122 That's the nuclear option to get back to a 'clean' state. It removes anything
2123 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002124 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2125 help='override deps for the specified (comma-separated) '
2126 'platform(s); \'all\' will process all deps_os '
2127 'references')
2128 parser.add_option('-n', '--nohooks', action='store_true',
2129 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002130 parser.add_option('-p', '--noprehooks', action='store_true',
2131 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002132 parser.add_option('--upstream', action='store_true',
2133 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002134 parser.add_option('--break_repo_locks', action='store_true',
2135 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2136 'index.lock). This should only be used if you know for '
2137 'certain that this invocation of gclient is the only '
2138 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002139 (options, args) = parser.parse_args(args)
2140 # --force is implied.
2141 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002142 options.reset = False
2143 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002144 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002145 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002146 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002147 return client.RunOnDeps('revert', args)
2148
2149
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002150def CMDrunhooks(parser, args):
2151 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002152 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2153 help='override deps for the specified (comma-separated) '
2154 'platform(s); \'all\' will process all deps_os '
2155 'references')
2156 parser.add_option('-f', '--force', action='store_true', default=True,
2157 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002158 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002159 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002160 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002161 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002162 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002163 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002164 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002165 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002166 return client.RunOnDeps('runhooks', args)
2167
2168
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002169def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002170 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002171
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002172 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002173 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002174 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
2175 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002176 commit can change.
2177 """
2178 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2179 help='override deps for the specified (comma-separated) '
2180 'platform(s); \'all\' will process all deps_os '
2181 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002182 parser.add_option('-a', '--actual', action='store_true',
2183 help='gets the actual checked out revisions instead of the '
2184 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002185 parser.add_option('-s', '--snapshot', action='store_true',
2186 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002187 'version of all repositories to reproduce the tree, '
2188 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002189 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002190 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002191 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002192 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002193 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002194 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002195
2196
szager@google.comb9a78d32012-03-13 18:46:21 +00002197def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002198 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002199 (options, args) = parser.parse_args(args)
2200 options.force = True
2201 client = GClient.LoadCurrentConfig(options)
2202 if not client:
2203 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2204 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002205 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002206 return 0
2207
2208
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002209def CMDverify(parser, args):
2210 """Verifies the DEPS file deps are only from allowed_hosts."""
2211 (options, args) = parser.parse_args(args)
2212 client = GClient.LoadCurrentConfig(options)
2213 if not client:
2214 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2215 client.RunOnDeps(None, [])
2216 # Look at each first-level dependency of this gclient only.
2217 for dep in client.dependencies:
2218 bad_deps = dep.findDepsFromNotAllowedHosts()
2219 if not bad_deps:
2220 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002221 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002222 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002223 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2224 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002225 sys.stdout.flush()
2226 raise gclient_utils.Error(
2227 'dependencies from disallowed hosts; check your DEPS file.')
2228 return 0
2229
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002230class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002231 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002232
2233 def __init__(self, **kwargs):
2234 optparse.OptionParser.__init__(
2235 self, version='%prog ' + __version__, **kwargs)
2236
2237 # Some arm boards have issues with parallel sync.
2238 if platform.machine().startswith('arm'):
2239 jobs = 1
2240 else:
2241 jobs = max(8, gclient_utils.NumLocalCpus())
2242 # cmp: 2013/06/19
2243 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00002244 # Bypassed if a bot_update flag is detected.
2245 if (os.environ.get('CHROME_HEADLESS') == '1' and
2246 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00002247 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002248
2249 self.add_option(
2250 '-j', '--jobs', default=jobs, type='int',
2251 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002252 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002253 self.add_option(
2254 '-v', '--verbose', action='count', default=0,
2255 help='Produces additional output for diagnostics. Can be used up to '
2256 'three times for more logging info.')
2257 self.add_option(
2258 '--gclientfile', dest='config_filename',
2259 help='Specify an alternate %s file' % self.gclientfile_default)
2260 self.add_option(
2261 '--spec',
2262 help='create a gclient file containing the provided string. Due to '
2263 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2264 self.add_option(
2265 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002266 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002267
2268 def parse_args(self, args=None, values=None):
2269 """Integrates standard options processing."""
2270 options, args = optparse.OptionParser.parse_args(self, args, values)
2271 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2272 logging.basicConfig(
2273 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002274 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002275 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002276 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002277 if (options.config_filename and
2278 options.config_filename != os.path.basename(options.config_filename)):
2279 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002280 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002281 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002282 options.entries_filename = options.config_filename + '_entries'
2283 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002284 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002285
2286 # These hacks need to die.
2287 if not hasattr(options, 'revisions'):
2288 # GClient.RunOnDeps expects it even if not applicable.
2289 options.revisions = []
2290 if not hasattr(options, 'head'):
2291 options.head = None
2292 if not hasattr(options, 'nohooks'):
2293 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002294 if not hasattr(options, 'noprehooks'):
2295 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002296 if not hasattr(options, 'deps_os'):
2297 options.deps_os = None
2298 if not hasattr(options, 'manually_grab_svn_rev'):
2299 options.manually_grab_svn_rev = None
2300 if not hasattr(options, 'force'):
2301 options.force = None
2302 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002303
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002304
2305def disable_buffering():
2306 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2307 # operations. Python as a strong tendency to buffer sys.stdout.
2308 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2309 # Make stdout annotated with the thread ids.
2310 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002311
2312
sbc@chromium.org013731e2015-02-26 18:28:43 +00002313def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002314 """Doesn't parse the arguments here, just find the right subcommand to
2315 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002316 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002317 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002318 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002319 sys.version.split(' ', 1)[0],
2320 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002321 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002322 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002323 print(
2324 '\nPython cannot find the location of it\'s own executable.\n',
2325 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002326 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002327 fix_encoding.fix_encoding()
2328 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002329 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002330 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002331 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002332 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002333 except KeyboardInterrupt:
2334 gclient_utils.GClientChildren.KillAllRemainingChildren()
2335 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002336 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002337 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002338 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002339 finally:
2340 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002341 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002342
2343
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002344if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002345 try:
2346 sys.exit(main(sys.argv[1:]))
2347 except KeyboardInterrupt:
2348 sys.stderr.write('interrupted\n')
2349 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002350
2351# vim: ts=2:sw=2:tw=80:et: