blob: 88edbd7af489b3034a16a2c4b3cae426af2f5908 [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
lpique398a46e2016-10-11 13:19:57 -07006"""Meta checkout manager supporting both Subversion and GIT."""
maruel@chromium.org39c0b222013-08-17 16:57:01 +00007# 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
lpique398a46e2016-10-11 13:19:57 -0700176 class FileImpl(object):
177 """Used to implement the File('') syntax which lets you sync a single file
178 from a SVN repo."""
179
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
maruel@chromium.org116704f2010-06-11 17:34:38 +0000199 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__(
smutae7ea312016-07-18 11:59:41 -0700216 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700217 custom_hooks, deps_file, should_process, relative):
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
smutae7ea312016-07-18 11:59:41 -0700222 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
smutae7ea312016-07-18 11:59:41 -0700228 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000229 # '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
agabledce6ddc2016-09-08 10:02:16 -0700233 # If this is a recursed-upon sub-dependency, and the parent has
234 # use_relative_paths set, then this dependency should check out its own
235 # dependencies relative to that parent's path for this, rather than
236 # relative to the .gclient file.
237 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000238 # This is a mutable value which has the list of 'target_os' OSes listed in
239 # the current deps file.
240 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000241
242 # These are only set in .gclient and not in DEPS files.
243 self._custom_vars = custom_vars or {}
244 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000245 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000246
lpique398a46e2016-10-11 13:19:57 -0700247 # TODO(iannucci): Remove this when all masters are correctly substituting
248 # the new blink url.
249 if (self._custom_vars.get('webkit_trunk', '') ==
250 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
251 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
252 print('Overwriting Var("webkit_trunk") with %s' % new_url)
253 self._custom_vars['webkit_trunk'] = new_url
254
maruel@chromium.org064186c2011-09-27 23:53:33 +0000255 # Post process the url to remove trailing slashes.
256 if isinstance(self._url, basestring):
257 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
258 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 self._url = self._url.replace('/@', '@')
lpique398a46e2016-10-11 13:19:57 -0700260 elif not isinstance(self._url,
261 (self.FromImpl, self.FileImpl, None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000262 raise gclient_utils.Error(
263 ('dependency url must be either a string, None, '
lpique398a46e2016-10-11 13:19:57 -0700264 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000265 # Make any deps_file path platform-appropriate.
266 for sep in ['/', '\\']:
267 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000268
269 @property
270 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000271 return self._deps_file
272
273 @property
274 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000275 return self._managed
276
277 @property
278 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000279 return self._parent
280
281 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000282 def root(self):
283 """Returns the root node, a GClient object."""
284 if not self.parent:
285 # This line is to signal pylint that it could be a GClient instance.
286 return self or GClient(None, None)
287 return self.parent.root
288
289 @property
smutae7ea312016-07-18 11:59:41 -0700290 def safesync_url(self):
291 return self._safesync_url
292
293 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000294 def should_process(self):
295 """True if this dependency should be processed, i.e. checked out."""
296 return self._should_process
297
298 @property
299 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000300 return self._custom_vars.copy()
301
302 @property
303 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000304 return self._custom_deps.copy()
305
maruel@chromium.org064186c2011-09-27 23:53:33 +0000306 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000307 def custom_hooks(self):
308 return self._custom_hooks[:]
309
310 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000311 def url(self):
312 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000313
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000314 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000315 def target_os(self):
316 if self.local_target_os is not None:
317 return tuple(set(self.local_target_os).union(self.parent.target_os))
318 else:
319 return self.parent.target_os
320
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000321 def get_custom_deps(self, name, url):
322 """Returns a custom deps if applicable."""
323 if self.parent:
324 url = self.parent.get_custom_deps(name, url)
325 # None is a valid return value to disable a dependency.
326 return self.custom_deps.get(name, url)
327
maruel@chromium.org064186c2011-09-27 23:53:33 +0000328
329class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000330 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000331
smutae7ea312016-07-18 11:59:41 -0700332 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700333 custom_vars, custom_hooks, deps_file, should_process,
334 relative):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000335 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000336 DependencySettings.__init__(
smutae7ea312016-07-18 11:59:41 -0700337 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700338 custom_hooks, deps_file, should_process, relative)
maruel@chromium.org68988972011-09-20 14:11:42 +0000339
340 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000341 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000342
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000343 self._pre_deps_hooks = []
344
maruel@chromium.org68988972011-09-20 14:11:42 +0000345 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000346 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000347 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000348 # A cache of the files affected by the current operation, necessary for
349 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000350 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000351 # List of host names from which dependencies are allowed.
352 # Default is an empty set, meaning unspecified in DEPS file, and hence all
353 # hosts will be allowed. Non-empty set means whitelist of hosts.
354 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
355 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000356 # If it is not set to True, the dependency wasn't processed for its child
357 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000358 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000359 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000360 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000361 # This dependency had its pre-DEPS hooks run
362 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000363 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000364 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000365 # This is the scm used to checkout self.url. It may be used by dependencies
366 # to get the datetime of the revision we checked out.
367 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000368 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000369 # The actual revision we ended up getting, or None if that information is
370 # unavailable
371 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000372
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000373 # This is a mutable value that overrides the normal recursion limit for this
374 # dependency. It is read from the actual DEPS file so cannot be set on
375 # class instantiation.
376 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000377 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000378 # 'no recursion' setting on a dep-by-dep basis. It will replace
379 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000380 #
381 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
382 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000383 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700384 # This is inherited from WorkItem. We want the URL to be a resource.
385 if url and isinstance(url, basestring):
386 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700387 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700388 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000389
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000390 if not self.name and self.parent:
391 raise gclient_utils.Error('Dependency without name')
392
maruel@chromium.org470b5432011-10-11 18:18:19 +0000393 @property
394 def requirements(self):
395 """Calculate the list of requirements."""
396 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000397 # self.parent is implicitly a requirement. This will be recursive by
398 # definition.
399 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000400 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000401
402 # For a tree with at least 2 levels*, the leaf node needs to depend
403 # on the level higher up in an orderly way.
404 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
405 # thus unsorted, while the .gclient format is a list thus sorted.
406 #
407 # * _recursion_limit is hard coded 2 and there is no hope to change this
408 # value.
409 #
410 # Interestingly enough, the following condition only works in the case we
411 # want: self is a 2nd level node. 3nd level node wouldn't need this since
412 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000413 if self.parent and self.parent.parent and not self.parent.parent.parent:
414 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000415
416 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000417 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000418
maruel@chromium.org470b5432011-10-11 18:18:19 +0000419 if self.name:
420 requirements |= set(
421 obj.name for obj in self.root.subtree(False)
422 if (obj is not self
423 and obj.name and
424 self.name.startswith(posixpath.join(obj.name, ''))))
425 requirements = tuple(sorted(requirements))
426 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
427 return requirements
428
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000429 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000430 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000431 """Returns False if recursion_override is ever specified."""
432 if self.recursion_override is not None:
433 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000434 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000435
436 @property
437 def recursion_limit(self):
438 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000439 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000440 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000441 if self.try_recursedeps and self.parent.recursedeps != None:
442 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000443 return 1
444
445 if self.recursion_override is not None:
446 return self.recursion_override
447 return max(self.parent.recursion_limit - 1, 0)
448
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 def verify_validity(self):
450 """Verifies that this Dependency is fine to add as a child of another one.
451
452 Returns True if this entry should be added, False if it is a duplicate of
453 another entry.
454 """
455 logging.info('Dependency(%s).verify_validity()' % self.name)
456 if self.name in [s.name for s in self.parent.dependencies]:
457 raise gclient_utils.Error(
458 'The same name "%s" appears multiple times in the deps section' %
459 self.name)
460 if not self.should_process:
461 # Return early, no need to set requirements.
462 return True
463
464 # This require a full tree traversal with locks.
465 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
466 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000467 self_url = self.LateOverride(self.url)
468 sibling_url = sibling.LateOverride(sibling.url)
469 # Allow to have only one to be None or ''.
470 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000471 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000472 ('Dependency %s specified more than once:\n'
473 ' %s [%s]\n'
474 'vs\n'
475 ' %s [%s]') % (
476 self.name,
477 sibling.hierarchy(),
478 sibling_url,
479 self.hierarchy(),
480 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000481 # In theory we could keep it as a shadow of the other one. In
482 # practice, simply ignore it.
483 logging.warn('Won\'t process duplicate dependency %s' % sibling)
484 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000485 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000486
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000487 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000488 """Resolves the parsed url from url.
489
490 Manages From() keyword accordingly. Do not touch self.parsed_url nor
491 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000492 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000493 parsed_url = self.get_custom_deps(self.name, url)
494 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000495 logging.info(
496 'Dependency(%s).LateOverride(%s) -> %s' %
497 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000498 return parsed_url
499
500 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000501 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000502 ref = [
503 dep for dep in self.root.subtree(True) if url.module_name == dep.name
504 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000505 if not ref:
506 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
507 url.module_name, ref))
508 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000510 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000511 found_deps = [d for d in ref.dependencies if d.name == sub_target]
512 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000513 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000514 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
515 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000516 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000517
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000518 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000519 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000520 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000521 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000522 'Dependency(%s).LateOverride(%s) -> %s (From)' %
523 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000524 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000525
526 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000527 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000528 if (not parsed_url[0] and
529 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000530 # A relative url. Fetch the real base.
531 path = parsed_url[2]
532 if not path.startswith('/'):
533 raise gclient_utils.Error(
534 'relative DEPS entry \'%s\' must begin with a slash' % url)
535 # Create a scm just to query the full url.
536 parent_url = self.parent.parsed_url
lpique398a46e2016-10-11 13:19:57 -0700537 if isinstance(parent_url, self.FileImpl):
538 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000539 scm = gclient_scm.CreateSCM(
540 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000541 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000542 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000543 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000544 logging.info(
545 'Dependency(%s).LateOverride(%s) -> %s' %
546 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000547 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000548
lpique398a46e2016-10-11 13:19:57 -0700549 if isinstance(url, self.FileImpl):
550 logging.info(
551 'Dependency(%s).LateOverride(%s) -> %s (File)' %
552 (self.name, url, url))
553 return url
554
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000555 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000556 logging.info(
557 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000558 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000559
560 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000561
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000562 @staticmethod
563 def MergeWithOsDeps(deps, deps_os, target_os_list):
564 """Returns a new "deps" structure that is the deps sent in updated
565 with information from deps_os (the deps_os section of the DEPS
566 file) that matches the list of target os."""
567 os_overrides = {}
568 for the_target_os in target_os_list:
569 the_target_os_deps = deps_os.get(the_target_os, {})
570 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
571 overrides = os_overrides.setdefault(os_dep_key, [])
572 overrides.append((the_target_os, os_dep_value))
573
574 # If any os didn't specify a value (we have fewer value entries
575 # than in the os list), then it wants to use the default value.
576 for os_dep_key, os_dep_value in os_overrides.iteritems():
577 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700578 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000579 # set it to None or miss a conflicting DEPS.
580 if os_dep_key in deps:
581 os_dep_value.append(('default', deps[os_dep_key]))
582
583 target_os_deps = {}
584 for os_dep_key, os_dep_value in os_overrides.iteritems():
585 # os_dep_value is a list of (os, value) pairs.
586 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
587 if not possible_values:
588 target_os_deps[os_dep_key] = None
589 else:
590 if len(possible_values) > 1:
591 # It would be possible to abort here but it would be
592 # unfortunate if we end up preventing any kind of checkout.
593 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
594 os_dep_key, os_dep_value, target_os_list)
595 # Sorting to get the same result every time in case of conflicts.
596 target_os_deps[os_dep_key] = sorted(possible_values)[0]
597
598 new_deps = deps.copy()
599 new_deps.update(target_os_deps)
600 return new_deps
601
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000602 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000603 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000604 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000605 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000606
607 deps_content = None
608 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000609
610 # First try to locate the configured deps file. If it's missing, fallback
611 # to DEPS.
612 deps_files = [self.deps_file]
613 if 'DEPS' not in deps_files:
614 deps_files.append('DEPS')
615 for deps_file in deps_files:
616 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
617 if os.path.isfile(filepath):
618 logging.info(
619 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
620 filepath)
621 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000622 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000623 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
624 filepath)
625
626 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000627 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000628 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000629 use_strict = 'use strict' in deps_content.splitlines()[0]
630
631 local_scope = {}
632 if deps_content:
633 # One thing is unintuitive, vars = {} must happen before Var() use.
634 var = self.VarImpl(self.custom_vars, local_scope)
635 if use_strict:
636 logging.info(
637 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
638 global_scope = {
639 '__builtins__': {'None': None},
640 'Var': var.Lookup,
641 'deps_os': {},
642 }
643 else:
644 global_scope = {
lpique398a46e2016-10-11 13:19:57 -0700645 'File': self.FileImpl,
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000646 'From': self.FromImpl,
647 'Var': var.Lookup,
648 'deps_os': {},
649 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000650 # Eval the content.
651 try:
652 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000653 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000654 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000655 if use_strict:
656 for key, val in local_scope.iteritems():
657 if not isinstance(val, (dict, list, tuple, str)):
658 raise gclient_utils.Error(
659 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
660 (self.name, key, val))
661
maruel@chromium.org271375b2010-06-23 19:17:38 +0000662 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000663 if 'recursion' in local_scope:
664 self.recursion_override = local_scope.get('recursion')
665 logging.warning(
666 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000667 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000668 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000669 self.recursedeps = {}
670 for ent in local_scope['recursedeps']:
671 if isinstance(ent, basestring):
672 self.recursedeps[ent] = {"deps_file": self.deps_file}
673 else: # (depname, depsfilename)
674 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000675 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000676 # If present, save 'target_os' in the local_target_os property.
677 if 'target_os' in local_scope:
678 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000679 # load os specific dependencies if defined. these dependencies may
680 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000681 target_os_list = self.target_os
682 if 'deps_os' in local_scope and target_os_list:
683 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000684
maruel@chromium.org271375b2010-06-23 19:17:38 +0000685 # If a line is in custom_deps, but not in the solution, we want to append
686 # this line to the solution.
687 for d in self.custom_deps:
688 if d not in deps:
689 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000690
691 # If use_relative_paths is set in the DEPS file, regenerate
692 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000693 # the DEPS file. Also update recursedeps if use_relative_paths is
694 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700695 # If the deps file doesn't set use_relative_paths, but the parent did
696 # (and therefore set self.relative on this Dependency object), then we
697 # want to modify the deps and recursedeps by prepending the parent
698 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000699 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700700 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000701 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700702 rel_prefix = self.name
703 elif self._relative:
704 rel_prefix = os.path.dirname(self.name)
705 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000706 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000707 rel_deps = {}
708 for d, url in deps.items():
709 # normpath is required to allow DEPS to use .. in their
710 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700711 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
712 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000713 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000714
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000715 # Update recursedeps if it's set.
716 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700717 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000718 rel_deps = {}
719 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700720 rel_deps[
721 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000722 self.recursedeps = rel_deps
723
agabledce6ddc2016-09-08 10:02:16 -0700724
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000725 if 'allowed_hosts' in local_scope:
726 try:
727 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
728 except TypeError: # raised if non-iterable
729 pass
730 if not self._allowed_hosts:
731 logging.warning("allowed_hosts is specified but empty %s",
732 self._allowed_hosts)
733 raise gclient_utils.Error(
734 'ParseDepsFile(%s): allowed_hosts must be absent '
735 'or a non-empty iterable' % self.name)
736
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000737 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000738 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000739 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000740 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000741 deps_file = self.deps_file
742 if self.recursedeps is not None:
743 ent = self.recursedeps.get(name)
744 if ent is not None:
745 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000746 deps_to_add.append(Dependency(
smutae7ea312016-07-18 11:59:41 -0700747 self, name, url, None, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700748 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000749 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000750
751 # override named sets of hooks by the custom hooks
752 hooks_to_run = []
753 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
754 for hook in local_scope.get('hooks', []):
755 if hook.get('name', '') not in hook_names_to_suppress:
756 hooks_to_run.append(hook)
757
758 # add the replacements and any additions
759 for hook in self.custom_hooks:
760 if 'action' in hook:
761 hooks_to_run.append(hook)
762
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000763 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
764 local_scope.get('pre_deps_hooks', [])]
765
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000766 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000767 logging.info('ParseDepsFile(%s) done' % self.name)
768
769 def add_dependencies_and_close(self, deps_to_add, hooks):
770 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000771 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000772 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000773 self.add_dependency(dep)
774 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000775
lpique398a46e2016-10-11 13:19:57 -0700776 def maybeGetParentRevision(self, command, options, parsed_url, parent):
777 """Uses revision/timestamp of parent if no explicit revision was specified.
778
779 If we are performing an update and --transitive is set, use
780 - the parent's revision if 'self.url' is in the same repository
781 - the parent's timestamp otherwise
782 to update 'self.url'. The used revision/timestamp will be set in
783 'options.revision'.
784 If we have an explicit revision do nothing.
785 """
786 if command == 'update' and options.transitive and not options.revision:
787 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
788 if not revision:
789 options.revision = getattr(parent, '_used_revision', None)
790 if (options.revision and
791 not gclient_utils.IsDateRevision(options.revision)):
792 assert self.parent and self.parent.used_scm
793 # If this dependency is in the same repository as parent it's url will
794 # start with a slash. If so we take the parent revision instead of
795 # it's timestamp.
796 # (The timestamps of commits in google code are broken -- which can
797 # result in dependencies to be checked out at the wrong revision)
798 if self.url.startswith('/'):
799 if options.verbose:
800 print('Using parent\'s revision %s since we are in the same '
801 'repository.' % options.revision)
802 else:
803 parent_revision_date = self.parent.used_scm.GetRevisionDate(
804 options.revision)
805 options.revision = gclient_utils.MakeDateRevision(
806 parent_revision_date)
807 if options.verbose:
808 print('Using parent\'s revision date %s since we are in a '
809 'different repository.' % options.revision)
810
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000811 def findDepsFromNotAllowedHosts(self):
812 """Returns a list of depenecies from not allowed hosts.
813
814 If allowed_hosts is not set, allows all hosts and returns empty list.
815 """
816 if not self._allowed_hosts:
817 return []
818 bad_deps = []
819 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000820 # Don't enforce this for custom_deps.
821 if dep.name in self._custom_deps:
822 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000823 if isinstance(dep.url, basestring):
824 parsed_url = urlparse.urlparse(dep.url)
825 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
826 bad_deps.append(dep)
827 return bad_deps
828
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000829 # Arguments number differs from overridden method
830 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000831 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000832 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000833 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000834 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000835 if not self.should_process:
836 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000837 # When running runhooks, there's no need to consult the SCM.
838 # All known hooks are expected to run unconditionally regardless of working
839 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000840 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000841 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000842 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000843 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000844 if run_scm and parsed_url:
lpique398a46e2016-10-11 13:19:57 -0700845 if isinstance(parsed_url, self.FileImpl):
846 # Special support for single-file checkout.
847 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
848 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
849 # pylint: disable=E1103
850 options.revision = parsed_url.GetRevision()
851 self._used_scm = gclient_scm.SVNWrapper(
852 parsed_url.GetPath(), self.root.root_dir, self.name,
853 out_cb=work_queue.out_cb)
854 self._used_scm.RunCommand('updatesingle',
855 options, args + [parsed_url.GetFilename()], file_list)
856 else:
857 # Create a shallow copy to mutate revision.
858 options = copy.copy(options)
859 options.revision = revision_override
860 self.maybeGetParentRevision(
861 command, options, parsed_url, self.parent)
862 self._used_revision = options.revision
863 self._used_scm = gclient_scm.CreateSCM(
864 parsed_url, self.root.root_dir, self.name, self.outbuf,
865 out_cb=work_queue.out_cb)
866 self._got_revision = self._used_scm.RunCommand(command, options, args,
867 file_list)
868 if file_list:
869 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000870
871 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
872 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000873 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000874 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000875 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000876 continue
877 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000878 [self.root.root_dir.lower(), file_list[i].lower()])
879 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000880 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000881 while file_list[i].startswith(('\\', '/')):
882 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000883
884 # Always parse the DEPS file.
885 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000886 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000887 if command in ('update', 'revert') and not options.noprehooks:
888 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000889
890 if self.recursion_limit:
891 # Parse the dependencies of this dependency.
892 for s in self.dependencies:
893 work_queue.enqueue(s)
894
895 if command == 'recurse':
lpique398a46e2016-10-11 13:19:57 -0700896 if not isinstance(parsed_url, self.FileImpl):
897 # Skip file only checkout.
898 scm = gclient_scm.GetScmName(parsed_url)
899 if not options.scm or scm in options.scm:
900 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
901 # Pass in the SCM type as an env variable. Make sure we don't put
902 # unicode strings in the environment.
903 env = os.environ.copy()
904 if scm:
905 env['GCLIENT_SCM'] = str(scm)
906 if parsed_url:
907 env['GCLIENT_URL'] = str(parsed_url)
908 env['GCLIENT_DEP_PATH'] = str(self.name)
909 if options.prepend_dir and scm == 'git':
910 print_stdout = False
911 def filter_fn(line):
912 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000913
lpique398a46e2016-10-11 13:19:57 -0700914 def mod_path(git_pathspec):
915 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
916 modified_path = os.path.join(self.name, match.group(2))
917 branch = match.group(1) or ''
918 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000919
lpique398a46e2016-10-11 13:19:57 -0700920 match = re.match('^Binary file ([^\0]+) matches$', line)
921 if match:
922 print('Binary file %s matches\n' % mod_path(match.group(1)))
923 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000924
lpique398a46e2016-10-11 13:19:57 -0700925 items = line.split('\0')
926 if len(items) == 2 and items[1]:
927 print('%s : %s' % (mod_path(items[0]), items[1]))
928 elif len(items) >= 2:
929 # Multiple null bytes or a single trailing null byte indicate
930 # git is likely displaying filenames only (such as with -l)
931 print('\n'.join(mod_path(path) for path in items if path))
932 else:
933 print(line)
934 else:
935 print_stdout = True
936 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000937
lpique398a46e2016-10-11 13:19:57 -0700938 if parsed_url is None:
939 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
940 elif os.path.isdir(cwd):
941 try:
942 gclient_utils.CheckCallAndFilter(
943 args, cwd=cwd, env=env, print_stdout=print_stdout,
944 filter_fn=filter_fn,
945 )
946 except subprocess2.CalledProcessError:
947 if not options.ignore:
948 raise
949 else:
950 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000951
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000953 @gclient_utils.lockedmethod
954 def _run_is_done(self, file_list, parsed_url):
955 # Both these are kept for hooks that are run as a separate tree traversal.
956 self._file_list = file_list
957 self._parsed_url = parsed_url
958 self._processed = True
959
szager@google.comb9a78d32012-03-13 18:46:21 +0000960 @staticmethod
961 def GetHookAction(hook_dict, matching_file_list):
962 """Turns a parsed 'hook' dict into an executable command."""
963 logging.debug(hook_dict)
964 logging.debug(matching_file_list)
965 command = hook_dict['action'][:]
966 if command[0] == 'python':
967 # If the hook specified "python" as the first item, the action is a
968 # Python script. Run it by starting a new copy of the same
969 # interpreter.
970 command[0] = sys.executable
971 if '$matching_files' in command:
972 splice_index = command.index('$matching_files')
973 command[splice_index:splice_index + 1] = matching_file_list
974 return command
975
976 def GetHooks(self, options):
977 """Evaluates all hooks, and return them in a flat list.
978
979 RunOnDeps() must have been called before to load the DEPS.
980 """
981 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000982 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000983 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000984 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000985 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000986 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000987 if self.deps_hooks:
lpique398a46e2016-10-11 13:19:57 -0700988 # TODO(maruel): If the user is using git or git-svn, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000989 # what files have changed so we always run all hooks. It'd be nice to fix
990 # that.
991 if (options.force or
lpique398a46e2016-10-11 13:19:57 -0700992 isinstance(self.parsed_url, self.FileImpl) or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000993 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000994 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000995 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000996 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000997 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000998 # Run hooks on the basis of whether the files from the gclient operation
999 # match each hook's pattern.
1000 for hook_dict in self.deps_hooks:
1001 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001002 matching_file_list = [
1003 f for f in self.file_list_and_children if pattern.search(f)
1004 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001005 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +00001006 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001007 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001008 result.extend(s.GetHooks(options))
1009 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001010
szager@google.comb9a78d32012-03-13 18:46:21 +00001011 def RunHooksRecursively(self, options):
1012 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001013 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +00001014 for hook in self.GetHooks(options):
1015 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +00001016 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +00001017 gclient_utils.CheckCallAndFilterAndHeader(
1018 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001019 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +00001020 # Use a discrete exit status code of 2 to indicate that a hook action
1021 # failed. Users of this script may wish to treat hook action failures
1022 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001023 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +00001024 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +00001025 finally:
1026 elapsed_time = time.time() - start_time
1027 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001028 print("Hook '%s' took %.2f secs" % (
1029 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001030
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001031 def RunPreDepsHooks(self):
1032 assert self.processed
1033 assert self.deps_parsed
1034 assert not self.pre_deps_hooks_ran
1035 assert not self.hooks_ran
1036 for s in self.dependencies:
1037 assert not s.processed
1038 self._pre_deps_hooks_ran = True
1039 for hook in self.pre_deps_hooks:
1040 try:
1041 start_time = time.time()
1042 gclient_utils.CheckCallAndFilterAndHeader(
1043 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001044 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001045 # Use a discrete exit status code of 2 to indicate that a hook action
1046 # failed. Users of this script may wish to treat hook action failures
1047 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001048 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001049 sys.exit(2)
1050 finally:
1051 elapsed_time = time.time() - start_time
1052 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001053 print("Hook '%s' took %.2f secs" % (
1054 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001055
1056
maruel@chromium.org0d812442010-08-10 12:41:08 +00001057 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001058 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001059 dependencies = self.dependencies
1060 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001061 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001062 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001063 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001064 for i in d.subtree(include_all):
1065 yield i
1066
1067 def depth_first_tree(self):
1068 """Depth-first recursion including the root node."""
1069 yield self
1070 for i in self.dependencies:
1071 for j in i.depth_first_tree():
1072 if j.should_process:
1073 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001074
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001075 @gclient_utils.lockedmethod
1076 def add_dependency(self, new_dep):
1077 self._dependencies.append(new_dep)
1078
1079 @gclient_utils.lockedmethod
1080 def _mark_as_parsed(self, new_hooks):
1081 self._deps_hooks.extend(new_hooks)
1082 self._deps_parsed = True
1083
maruel@chromium.org68988972011-09-20 14:11:42 +00001084 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001085 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001086 def dependencies(self):
1087 return tuple(self._dependencies)
1088
1089 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001090 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001091 def deps_hooks(self):
1092 return tuple(self._deps_hooks)
1093
1094 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001095 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001096 def pre_deps_hooks(self):
1097 return tuple(self._pre_deps_hooks)
1098
1099 @property
1100 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001101 def parsed_url(self):
1102 return self._parsed_url
1103
1104 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001105 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001106 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001107 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001108 return self._deps_parsed
1109
1110 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001111 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001112 def processed(self):
1113 return self._processed
1114
1115 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001116 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001117 def pre_deps_hooks_ran(self):
1118 return self._pre_deps_hooks_ran
1119
1120 @property
1121 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001122 def hooks_ran(self):
1123 return self._hooks_ran
1124
1125 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001126 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001127 def allowed_hosts(self):
1128 return self._allowed_hosts
1129
1130 @property
1131 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001132 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001133 return tuple(self._file_list)
1134
1135 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001136 def used_scm(self):
1137 """SCMWrapper instance for this dependency or None if not processed yet."""
1138 return self._used_scm
1139
1140 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001141 @gclient_utils.lockedmethod
1142 def got_revision(self):
1143 return self._got_revision
1144
1145 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001146 def file_list_and_children(self):
1147 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001148 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001149 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001150 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001151
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001152 def __str__(self):
1153 out = []
smutae7ea312016-07-18 11:59:41 -07001154 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001155 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001156 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1157 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001158 # First try the native property if it exists.
1159 if hasattr(self, '_' + i):
1160 value = getattr(self, '_' + i, False)
1161 else:
1162 value = getattr(self, i, False)
1163 if value:
1164 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001165
1166 for d in self.dependencies:
1167 out.extend([' ' + x for x in str(d).splitlines()])
1168 out.append('')
1169 return '\n'.join(out)
1170
1171 def __repr__(self):
1172 return '%s: %s' % (self.name, self.url)
1173
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001174 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001175 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001176 out = '%s(%s)' % (self.name, self.url)
1177 i = self.parent
1178 while i and i.name:
1179 out = '%s(%s) -> %s' % (i.name, i.url, out)
1180 i = i.parent
1181 return out
1182
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183
1184class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001185 """Object that represent a gclient checkout. A tree of Dependency(), one per
1186 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001187
1188 DEPS_OS_CHOICES = {
1189 "win32": "win",
1190 "win": "win",
1191 "cygwin": "win",
1192 "darwin": "mac",
1193 "mac": "mac",
1194 "unix": "unix",
1195 "linux": "unix",
1196 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001197 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001198 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001199 }
1200
1201 DEFAULT_CLIENT_FILE_TEXT = ("""\
1202solutions = [
smutae7ea312016-07-18 11:59:41 -07001203 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001204 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001205 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001206 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001207 "custom_deps" : {
1208 },
1209 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001210 },
1211]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001212cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001213""")
1214
1215 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001216 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001217 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001218 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001219 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001220 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001221%(solution_deps)s },
1222 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001223 },
1224""")
1225
1226 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1227# Snapshot generated with gclient revinfo --snapshot
1228solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001229%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001230""")
1231
1232 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001233 # Do not change previous behavior. Only solution level and immediate DEPS
1234 # are processed.
1235 self._recursion_limit = 2
smutae7ea312016-07-18 11:59:41 -07001236 Dependency.__init__(self, None, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001237 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001238 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001239 if options.deps_os:
1240 enforced_os = options.deps_os.split(',')
1241 else:
1242 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1243 if 'all' in enforced_os:
1244 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001245 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001246 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001247 self.config_content = None
1248
borenet@google.com88d10082014-03-21 17:24:48 +00001249 def _CheckConfig(self):
1250 """Verify that the config matches the state of the existing checked-out
1251 solutions."""
1252 for dep in self.dependencies:
1253 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001254 scm = gclient_scm.CreateSCM(
1255 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001256 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001257 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001258 mirror = scm.GetCacheMirror()
1259 if mirror:
1260 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1261 mirror.exists())
1262 else:
1263 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001264 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001265Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001266is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001267
borenet@google.com97882362014-04-07 20:06:02 +00001268The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001269URL: %(expected_url)s (%(expected_scm)s)
1270Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001271
1272The local checkout in %(checkout_path)s reports:
1273%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001274
1275You should ensure that the URL listed in .gclient is correct and either change
lpique398a46e2016-10-11 13:19:57 -07001276it or fix the checkout. If you're managing your own git checkout in
1277%(checkout_path)s but the URL in .gclient is for an svn repository, you probably
1278want to set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001279''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1280 'expected_url': dep.url,
1281 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001282 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001283 'actual_url': actual_url,
1284 'actual_scm': gclient_scm.GetScmName(actual_url)})
1285
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001286 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001287 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001288 config_dict = {}
1289 self.config_content = content
1290 try:
1291 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001292 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001293 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001294
peter@chromium.org1efccc82012-04-27 16:34:38 +00001295 # Append any target OS that is not already being enforced to the tuple.
1296 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001297 if config_dict.get('target_os_only', False):
1298 self._enforced_os = tuple(set(target_os))
1299 else:
1300 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1301
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001302 cache_dir = config_dict.get('cache_dir')
1303 if cache_dir:
1304 cache_dir = os.path.join(self.root_dir, cache_dir)
1305 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001306 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001307 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001308 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1309 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001310 gclient_scm.GitWrapper.cache_dir = cache_dir
1311 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001312
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001313 if not target_os and config_dict.get('target_os_only', False):
1314 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1315 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001316
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001317 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001318 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001319 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001320 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001321 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001322 s.get('safesync_url', None),
1323 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001324 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001325 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001326 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001327 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001328 True,
1329 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001330 except KeyError:
1331 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1332 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001333 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1334 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001335
1336 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001337 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001338 self._options.config_filename),
1339 self.config_content)
1340
lpique398a46e2016-10-11 13:19:57 -07001341 def MigrateConfigToGit(self, path, options):
1342 svn_url_re = re.compile('^(https?://src\.chromium\.org/svn|'
1343 'svn://svn\.chromium\.org/chrome)/'
1344 '(trunk|branches/[^/]+)/src')
1345 old_git_re = re.compile('^(https?://git\.chromium\.org|'
1346 'ssh://([a-zA-Z_][a-zA-Z0-9_-]*@)?'
1347 'gerrit\.chromium\.org(:2941[89])?)/'
1348 'chromium/src\.git')
1349 # Scan existing .gclient file for obsolete settings. It would be simpler
1350 # to traverse self.dependencies, but working with the AST allows the code to
1351 # dump an updated .gclient file that preserves the ordering of the original.
1352 a = ast.parse(self.config_content, options.config_filename, 'exec')
1353 modified = False
1354 solutions = [elem for elem in a.body if 'solutions' in
1355 [target.id for target in elem.targets]]
1356 if not solutions:
1357 return self
1358 solutions = solutions[-1]
1359 for solution in solutions.value.elts:
1360 # Check for obsolete URL's
1361 url_idx = ast_dict_index(solution, 'url')
1362 if url_idx == -1:
1363 continue
1364 url_val = solution.values[url_idx]
1365 if type(url_val) is not ast.Str:
1366 continue
1367 if (svn_url_re.match(url_val.s.strip())):
1368 raise gclient_utils.Error(
1369"""
1370The chromium code repository has migrated completely to git.
1371Your SVN-based checkout is now obsolete; you need to create a brand-new
1372git checkout by following these instructions:
1373
1374http://www.chromium.org/developers/how-tos/get-the-code
1375""")
1376 if (old_git_re.match(url_val.s.strip())):
1377 url_val.s = CHROMIUM_SRC_URL
1378 modified = True
1379
1380 # Ensure deps_file is set to .DEPS.git. We enforce this here to smooth
1381 # over switching between pre-git-migration and post-git-migration
1382 # revisions.
1383 # - For pre-migration revisions, .DEPS.git must be explicitly set.
1384 # - For post-migration revisions, .DEPS.git is not present, so gclient
1385 # will correctly fall back to DEPS.
1386 if url_val.s == CHROMIUM_SRC_URL:
1387 deps_file_idx = ast_dict_index(solution, 'deps_file')
1388 if deps_file_idx != -1:
1389 continue
1390 solution.keys.append(ast.Str('deps_file'))
1391 solution.values.append(ast.Str('.DEPS.git'))
1392 modified = True
1393
1394 if not modified:
1395 return self
1396
1397 print(
1398"""
1399WARNING: gclient detected an obsolete setting in your %s file. The file has
1400been automagically updated. The previous version is available at %s.old.
1401""" % (options.config_filename, options.config_filename))
1402
1403 # Replace existing .gclient with the updated version.
1404 # Return a new GClient instance based on the new content.
1405 new_content = ast2str(a)
1406 dot_gclient_fn = os.path.join(path, options.config_filename)
1407 try:
1408 os.rename(dot_gclient_fn, dot_gclient_fn + '.old')
1409 except OSError:
1410 pass
1411 with open(dot_gclient_fn, 'w') as fh:
1412 fh.write(new_content)
1413 client = GClient(path, options)
1414 client.SetConfig(new_content)
1415 return client
1416
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001417 @staticmethod
1418 def LoadCurrentConfig(options):
1419 """Searches for and loads a .gclient file relative to the current working
1420 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001421 if options.spec:
1422 client = GClient('.', options)
1423 client.SetConfig(options.spec)
1424 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001425 if options.verbose:
1426 print('Looking for %s starting from %s\n' % (
1427 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001428 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1429 if not path:
1430 return None
1431 client = GClient(path, options)
1432 client.SetConfig(gclient_utils.FileRead(
1433 os.path.join(path, options.config_filename)))
lpique398a46e2016-10-11 13:19:57 -07001434 client = client.MigrateConfigToGit(path, options)
maruel@chromium.org69392e72011-10-13 22:09:00 +00001435
1436 if (options.revisions and
1437 len(client.dependencies) > 1 and
1438 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001439 print(
1440 ('You must specify the full solution name like --revision %s@%s\n'
1441 'when you have multiple solutions setup in your .gclient file.\n'
1442 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001443 client.dependencies[0].name,
1444 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001445 ', '.join(s.name for s in client.dependencies[1:])),
1446 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001447 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001448
nsylvain@google.comefc80932011-05-31 21:27:56 +00001449 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
smutae7ea312016-07-18 11:59:41 -07001450 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001451 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1452 'solution_name': solution_name,
1453 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001454 'deps_file': deps_file,
smutae7ea312016-07-18 11:59:41 -07001455 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001456 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001457 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001458 })
1459
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001460 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001461 """Creates a .gclient_entries file to record the list of unique checkouts.
1462
1463 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001464 """
1465 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1466 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001467 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001468 for entry in self.root.subtree(False):
lpique398a46e2016-10-11 13:19:57 -07001469 # Skip over File() dependencies as we can't version them.
1470 if not isinstance(entry.parsed_url, self.FileImpl):
1471 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1472 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001473 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001474 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001475 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001476 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001477
1478 def _ReadEntries(self):
1479 """Read the .gclient_entries file for the given client.
1480
1481 Returns:
1482 A sequence of solution names, which will be empty if there is the
1483 entries file hasn't been created yet.
1484 """
1485 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001486 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001487 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001488 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001489 try:
1490 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001491 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001492 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001493 return scope['entries']
1494
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001495 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001496 """Checks for revision overrides."""
1497 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001498 if self._options.head:
1499 return revision_overrides
1500 # Do not check safesync_url if one or more --revision flag is specified.
joi@chromium.org792ea882010-11-10 02:37:27 +00001501 if not self._options.revisions:
1502 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001503 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001504 self._options.revisions.append('%s@unmanaged' % s.name)
smutae7ea312016-07-18 11:59:41 -07001505 elif s.safesync_url:
1506 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001507 if not self._options.revisions:
1508 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001509 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001510 index = 0
1511 for revision in self._options.revisions:
1512 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001513 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001514 revision = '%s@%s' % (solutions_names[index], revision)
1515 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001516 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001517 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001518 return revision_overrides
1519
smutae7ea312016-07-18 11:59:41 -07001520 def _ApplySafeSyncRev(self, dep):
1521 """Finds a valid revision from the content of the safesync_url and apply it
1522 by appending revisions to the revision list. Throws if revision appears to
1523 be invalid for the given |dep|."""
1524 assert len(dep.safesync_url) > 0
1525 handle = urllib.urlopen(dep.safesync_url)
1526 rev = handle.read().strip()
1527 handle.close()
1528 if not rev:
1529 raise gclient_utils.Error(
1530 'It appears your safesync_url (%s) is not working properly\n'
1531 '(as it returned an empty response). Check your config.' %
1532 dep.safesync_url)
1533 scm = gclient_scm.CreateSCM(
1534 dep.url, dep.root.root_dir, dep.name, self.outbuf)
1535 safe_rev = scm.GetUsableRev(rev, self._options)
1536 if self._options.verbose:
1537 print('Using safesync_url revision: %s.\n' % safe_rev)
1538 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1539
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001540 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001541 """Runs a command on each dependency in a client and its dependencies.
1542
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001543 Args:
1544 command: The command to use (e.g., 'status' or 'diff')
1545 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001546 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001547 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001548 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001549
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001550 revision_overrides = {}
1551 # It's unnecessary to check for revision overrides for 'recurse'.
1552 # Save a few seconds by not calling _EnforceRevisions() in that case.
agable@chromium.org0242eb42015-06-09 00:45:31 +00001553 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001554 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001555 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001556 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001557 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001558 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001559 if command in ('update', 'revert'):
1560 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001561 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001562 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001563 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001564 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1565 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001566 for s in self.dependencies:
1567 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001568 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001569 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001570 print('Please fix your script, having invalid --revision flags will soon '
1571 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001572
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001573 # Once all the dependencies have been processed, it's now safe to run the
1574 # hooks.
1575 if not self._options.nohooks:
1576 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001577
1578 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001579 # Notify the user if there is an orphaned entry in their working copy.
1580 # Only delete the directory if there are no changes in it, and
1581 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001582 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001583 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1584 for e in entries]
1585
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001586 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001587 if not prev_url:
1588 # entry must have been overridden via .gclient custom_deps
1589 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001590 # Fix path separator on Windows.
1591 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001592 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001593 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001594 if (entry not in entries and
1595 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001596 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001597 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001598 scm = gclient_scm.CreateSCM(
1599 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001600
1601 # Check to see if this directory is now part of a higher-up checkout.
lpique398a46e2016-10-11 13:19:57 -07001602 # The directory might be part of a git OR svn checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001603 scm_root = None
lpique398a46e2016-10-11 13:19:57 -07001604 scm_class = None
1605 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1606 try:
1607 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1608 except subprocess2.CalledProcessError:
1609 pass
1610 if scm_root:
1611 break
1612 else:
borenet@google.com359bb642014-05-13 17:28:19 +00001613 logging.warning('Could not find checkout root for %s. Unable to '
1614 'determine whether it is part of a higher-level '
1615 'checkout, so not removing.' % entry)
1616 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001617
1618 # This is to handle the case of third_party/WebKit migrating from
1619 # being a DEPS entry to being part of the main project.
1620 # If the subproject is a Git project, we need to remove its .git
1621 # folder. Otherwise git operations on that folder will have different
1622 # effects depending on the current working directory.
lpique398a46e2016-10-11 13:19:57 -07001623 if scm_class == gclient_scm.scm.GIT and (
1624 os.path.abspath(scm_root) == os.path.abspath(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001625 e_par_dir = os.path.join(e_dir, os.pardir)
lpique398a46e2016-10-11 13:19:57 -07001626 if scm_class.IsInsideWorkTree(e_par_dir):
1627 par_scm_root = scm_class.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001628 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1629 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
lpique398a46e2016-10-11 13:19:57 -07001630 if scm_class.IsDirectoryVersioned(par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001631 save_dir = scm.GetGitBackupDirPath()
1632 # Remove any eventual stale backup dir for the same project.
1633 if os.path.exists(save_dir):
1634 gclient_utils.rmtree(save_dir)
1635 os.rename(os.path.join(e_dir, '.git'), save_dir)
1636 # When switching between the two states (entry/ is a subproject
1637 # -> entry/ is part of the outer project), it is very likely
1638 # that some files are changed in the checkout, unless we are
1639 # jumping *exactly* across the commit which changed just DEPS.
1640 # In such case we want to cleanup any eventual stale files
1641 # (coming from the old subproject) in order to end up with a
1642 # clean checkout.
lpique398a46e2016-10-11 13:19:57 -07001643 scm_class.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001644 assert not os.path.exists(os.path.join(e_dir, '.git'))
1645 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1646 'level checkout. The git folder containing all the local'
1647 ' branches has been saved to %s.\n'
1648 'If you don\'t care about its state you can safely '
1649 'remove that folder to free up space.') %
1650 (entry, save_dir))
1651 continue
1652
borenet@google.com359bb642014-05-13 17:28:19 +00001653 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001654 logging.info('%s is part of a higher level checkout, not removing',
1655 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001656 continue
1657
1658 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001659 scm.status(self._options, [], file_list)
1660 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001661 if (not self._options.delete_unversioned_trees or
1662 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001663 # There are modified files in this entry. Keep warning until
1664 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001665 print(('\nWARNING: \'%s\' is no longer part of this client. '
1666 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001667 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001668 else:
1669 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001670 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001671 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001672 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001673 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001674 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001675 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001676
1677 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001678 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001679 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001680 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001681 work_queue = gclient_utils.ExecutionQueue(
1682 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001683 for s in self.dependencies:
1684 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001685 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001686
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001687 def GetURLAndRev(dep):
1688 """Returns the revision-qualified SCM url for a Dependency."""
1689 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001690 return None
lpique398a46e2016-10-11 13:19:57 -07001691 if isinstance(dep.parsed_url, self.FileImpl):
1692 original_url = dep.parsed_url.file_location
1693 else:
1694 original_url = dep.parsed_url
1695 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001696 scm = gclient_scm.CreateSCM(
lpique398a46e2016-10-11 13:19:57 -07001697 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001698 if not os.path.isdir(scm.checkout_path):
1699 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001700 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001701
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001702 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001703 new_gclient = ''
1704 # First level at .gclient
1705 for d in self.dependencies:
1706 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001707 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001708 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001709 for d in dep.dependencies:
1710 entries[d.name] = GetURLAndRev(d)
1711 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001712 GrabDeps(d)
1713 custom_deps = []
1714 for k in sorted(entries.keys()):
1715 if entries[k]:
1716 # Quotes aren't escaped...
1717 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1718 else:
1719 custom_deps.append(' \"%s\": None,\n' % k)
1720 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1721 'solution_name': d.name,
1722 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001723 'deps_file': d.deps_file,
smutae7ea312016-07-18 11:59:41 -07001724 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001725 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001726 'solution_deps': ''.join(custom_deps),
1727 }
1728 # Print the snapshot configuration file
1729 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001730 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001731 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001732 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001733 if self._options.actual:
1734 entries[d.name] = GetURLAndRev(d)
1735 else:
1736 entries[d.name] = d.parsed_url
1737 keys = sorted(entries.keys())
1738 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001739 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001740 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001741
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001742 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001743 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001744 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001745
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001746 def PrintLocationAndContents(self):
1747 # Print out the .gclient file. This is longer than if we just printed the
1748 # client dict, but more legible, and it might contain helpful comments.
1749 print('Loaded .gclient config in %s:\n%s' % (
1750 self.root_dir, self.config_content))
1751
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001752 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001753 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001754 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001755 return self._root_dir
1756
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001757 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001758 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001759 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001760 return self._enforced_os
1761
maruel@chromium.org68988972011-09-20 14:11:42 +00001762 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001763 def recursion_limit(self):
1764 """How recursive can each dependencies in DEPS file can load DEPS file."""
1765 return self._recursion_limit
1766
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001767 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001768 def try_recursedeps(self):
1769 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001770 return True
1771
1772 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001773 def target_os(self):
1774 return self._enforced_os
1775
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001776
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001777#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001778
1779
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001780def CMDcleanup(parser, args):
lpique398a46e2016-10-11 13:19:57 -07001781 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001782
lpique398a46e2016-10-11 13:19:57 -07001783 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001784 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001785 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1786 help='override deps for the specified (comma-separated) '
1787 'platform(s); \'all\' will process all deps_os '
1788 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001789 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001790 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001791 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001792 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001793 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001794 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001795 return client.RunOnDeps('cleanup', args)
1796
1797
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001798@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001799def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001800 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001801
1802 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001803 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001804 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001805 """
1806 # Stop parsing at the first non-arg so that these go through to the command
1807 parser.disable_interspersed_args()
1808 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001809 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001810 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001811 help='Ignore non-zero return codes from subcommands.')
1812 parser.add_option('--prepend-dir', action='store_true',
1813 help='Prepend relative dir for use with git <cmd> --null.')
1814 parser.add_option('--no-progress', action='store_true',
1815 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001816 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001817 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001818 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001819 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001820 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1821 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001822 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001823 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001824 'This is because .gclient_entries needs to exist and be up to date.',
1825 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001826 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001827
1828 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001829 scm_set = set()
1830 for scm in options.scm:
1831 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001832 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001833
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001834 options.nohooks = True
1835 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001836 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1837 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001838
1839
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001840@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001841def CMDfetch(parser, args):
1842 """Fetches upstream commits for all modules.
1843
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001844 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1845 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001846 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001847 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001848 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1849
1850
1851def CMDgrep(parser, args):
1852 """Greps through git repos managed by gclient.
1853
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001854 Runs 'git grep [args...]' for each module.
1855 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001856 # We can't use optparse because it will try to parse arguments sent
1857 # to git grep and throw an error. :-(
1858 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001859 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001860 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1861 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1862 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1863 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001864 ' end of your query.',
1865 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001866 return 1
1867
1868 jobs_arg = ['--jobs=1']
1869 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1870 jobs_arg, args = args[:1], args[1:]
1871 elif re.match(r'(-j|--jobs)$', args[0]):
1872 jobs_arg, args = args[:2], args[2:]
1873
1874 return CMDrecurse(
1875 parser,
1876 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1877 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001878
1879
stip@chromium.orga735da22015-04-29 23:18:20 +00001880def CMDroot(parser, args):
1881 """Outputs the solution root (or current dir if there isn't one)."""
1882 (options, args) = parser.parse_args(args)
1883 client = GClient.LoadCurrentConfig(options)
1884 if client:
1885 print(os.path.abspath(client.root_dir))
1886 else:
1887 print(os.path.abspath('.'))
1888
1889
smutae7ea312016-07-18 11:59:41 -07001890@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001891def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001892 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001893
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001894 This specifies the configuration for further commands. After update/sync,
1895 top-level DEPS files in each module are read to determine dependent
1896 modules to operate on as well. If optional [url] parameter is
1897 provided, then configuration is read from a specified Subversion server
1898 URL.
1899 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001900 # We do a little dance with the --gclientfile option. 'gclient config' is the
1901 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1902 # arguments. So, we temporarily stash any --gclientfile parameter into
1903 # options.output_config_file until after the (gclientfile xor spec) error
1904 # check.
1905 parser.remove_option('--gclientfile')
1906 parser.add_option('--gclientfile', dest='output_config_file',
1907 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001908 parser.add_option('--name',
1909 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001910 parser.add_option('--deps-file', default='DEPS',
1911 help='overrides the default name for the DEPS file for the'
1912 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001913 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001914 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001915 'to have the main solution untouched by gclient '
1916 '(gclient will check out unmanaged dependencies but '
1917 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001918 parser.add_option('--cache-dir',
1919 help='(git only) Cache all git repos into this dir and do '
1920 'shared clones from the cache, instead of cloning '
1921 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001922 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001923 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001924 if options.output_config_file:
1925 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001926 if ((options.spec and args) or len(args) > 2 or
1927 (not options.spec and not args)):
1928 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1929
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001930 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001931 if options.spec:
1932 client.SetConfig(options.spec)
1933 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001934 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001935 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001936 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001937 if name.endswith('.git'):
1938 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001939 else:
1940 # specify an alternate relpath for the given URL.
1941 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001942 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1943 os.getcwd()):
1944 parser.error('Do not pass a relative path for --name.')
1945 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1946 parser.error('Do not include relative path components in --name.')
1947
nsylvain@google.comefc80932011-05-31 21:27:56 +00001948 deps_file = options.deps_file
smutae7ea312016-07-18 11:59:41 -07001949 safesync_url = ''
1950 if len(args) > 1:
1951 safesync_url = args[1]
1952 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1953 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001954 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001955 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001956 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001957
1958
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001959@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001960 gclient pack > patch.txt
1961 generate simple patch for configured client and dependences
1962""")
1963def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001964 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001965
lpique398a46e2016-10-11 13:19:57 -07001966 Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001967 dependencies, and performs minimal postprocessing of the output. The
1968 resulting patch is printed to stdout and can be applied to a freshly
1969 checked out tree via 'patch -p0 < patchfile'.
1970 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001971 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1972 help='override deps for the specified (comma-separated) '
1973 'platform(s); \'all\' will process all deps_os '
1974 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001975 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001976 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001977 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001978 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001979 client = GClient.LoadCurrentConfig(options)
1980 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001981 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001982 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001983 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001984 return client.RunOnDeps('pack', args)
1985
1986
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001987def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001988 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001989 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1990 help='override deps for the specified (comma-separated) '
1991 'platform(s); \'all\' will process all deps_os '
1992 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001993 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001994 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001995 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001996 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001997 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001998 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001999 return client.RunOnDeps('status', args)
2000
2001
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002002@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002003 gclient sync
2004 update files from SCM according to current configuration,
2005 *for modules which have changed since last update or sync*
2006 gclient sync --force
2007 update files from SCM according to current configuration, for
2008 all modules (useful for recovering files deleted from local copy)
2009 gclient sync --revision src@31000
2010 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002011
2012JSON output format:
2013If the --output-json option is specified, the following document structure will
2014be emitted to the provided file. 'null' entries may occur for subprojects which
2015are present in the gclient solution, but were not processed (due to custom_deps,
2016os_deps, etc.)
2017
2018{
2019 "solutions" : {
2020 "<name>": { # <name> is the posix-normalized path to the solution.
lpique398a46e2016-10-11 13:19:57 -07002021 "revision": [<svn rev int>|<git id hex string>|null],
2022 "scm": ["svn"|"git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002023 }
2024 }
2025}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002026""")
2027def CMDsync(parser, args):
2028 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002029 parser.add_option('-f', '--force', action='store_true',
2030 help='force update even for unchanged modules')
2031 parser.add_option('-n', '--nohooks', action='store_true',
2032 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002033 parser.add_option('-p', '--noprehooks', action='store_true',
2034 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002035 parser.add_option('-r', '--revision', action='append',
2036 dest='revisions', metavar='REV', default=[],
2037 help='Enforces revision/hash for the solutions with the '
2038 'format src@rev. The src@ part is optional and can be '
2039 'skipped. -r can be used multiple times when .gclient '
2040 'has multiple solutions configured and will work even '
smutae7ea312016-07-18 11:59:41 -07002041 'if the src@ part is skipped. Note that specifying '
2042 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002043 parser.add_option('--with_branch_heads', action='store_true',
2044 help='Clone git "branch_heads" refspecs in addition to '
2045 'the default refspecs. This adds about 1/2GB to a '
2046 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002047 parser.add_option('--with_tags', action='store_true',
2048 help='Clone git tags in addition to the default refspecs.')
lpique398a46e2016-10-11 13:19:57 -07002049 parser.add_option('-t', '--transitive', action='store_true',
2050 help='When a revision is specified (in the DEPS file or '
2051 'with the command-line flag), transitively update '
2052 'the dependencies to the date of the given revision. '
2053 'Only supported for SVN repositories.')
agable2697cd12016-06-28 10:23:53 -07002054 parser.add_option('-H', '--head', action='store_true',
smutae7ea312016-07-18 11:59:41 -07002055 help='skips any safesync_urls specified in '
2056 'configured solutions and sync to head instead')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002057 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002058 help='Deletes from the working copy any dependencies that '
2059 'have been removed since the last sync, as long as '
2060 'there are no local modifications. When used with '
2061 '--force, such dependencies are removed even if they '
2062 'have local modifications. When used with --reset, '
2063 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002064 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002065 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002066 parser.add_option('-R', '--reset', action='store_true',
2067 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002068 parser.add_option('-M', '--merge', action='store_true',
2069 help='merge upstream changes instead of trying to '
2070 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002071 parser.add_option('-A', '--auto_rebase', action='store_true',
2072 help='Automatically rebase repositories against local '
2073 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002074 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2075 help='override deps for the specified (comma-separated) '
2076 'platform(s); \'all\' will process all deps_os '
2077 'references')
lpique398a46e2016-10-11 13:19:57 -07002078 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
2079 help='Skip svn up whenever possible by requesting '
2080 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002081 parser.add_option('--upstream', action='store_true',
2082 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002083 parser.add_option('--output-json',
2084 help='Output a json document to this path containing '
2085 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002086 parser.add_option('--no-history', action='store_true',
2087 help='GIT ONLY - Reduces the size/time of the checkout at '
2088 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002089 parser.add_option('--shallow', action='store_true',
2090 help='GIT ONLY - Do a shallow clone into the cache dir. '
2091 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002092 parser.add_option('--no_bootstrap', '--no-bootstrap',
2093 action='store_true',
2094 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002095 parser.add_option('--ignore_locks', action='store_true',
2096 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002097 parser.add_option('--break_repo_locks', action='store_true',
2098 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2099 'index.lock). This should only be used if you know for '
2100 'certain that this invocation of gclient is the only '
2101 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002102 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002103 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002104 'cache lock to become available. Default is %default.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002105 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002106 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002107
2108 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002109 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002110
smutae7ea312016-07-18 11:59:41 -07002111 if options.revisions and options.head:
2112 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2113 print('Warning: you cannot use both --head and --revision')
2114
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002115 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002116 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002117 ret = client.RunOnDeps('update', args)
2118 if options.output_json:
2119 slns = {}
2120 for d in client.subtree(True):
2121 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2122 slns[normed] = {
2123 'revision': d.got_revision,
2124 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002125 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002126 }
2127 with open(options.output_json, 'wb') as f:
2128 json.dump({'solutions': slns}, f)
2129 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002130
2131
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002132CMDupdate = CMDsync
2133
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002134
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002135def CMDdiff(parser, args):
2136 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002137 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2138 help='override deps for the specified (comma-separated) '
2139 'platform(s); \'all\' will process all deps_os '
2140 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002141 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002142 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002143 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002144 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002145 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002146 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002147 return client.RunOnDeps('diff', args)
2148
2149
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002150def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002151 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002152
2153 That's the nuclear option to get back to a 'clean' state. It removes anything
lpique398a46e2016-10-11 13:19:57 -07002154 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002155 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2156 help='override deps for the specified (comma-separated) '
2157 'platform(s); \'all\' will process all deps_os '
2158 'references')
2159 parser.add_option('-n', '--nohooks', action='store_true',
2160 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002161 parser.add_option('-p', '--noprehooks', action='store_true',
2162 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002163 parser.add_option('--upstream', action='store_true',
2164 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002165 parser.add_option('--break_repo_locks', action='store_true',
2166 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2167 'index.lock). This should only be used if you know for '
2168 'certain that this invocation of gclient is the only '
2169 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002170 (options, args) = parser.parse_args(args)
2171 # --force is implied.
2172 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002173 options.reset = False
2174 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002175 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002176 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002177 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002178 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002179 return client.RunOnDeps('revert', args)
2180
2181
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002182def CMDrunhooks(parser, args):
2183 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002184 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2185 help='override deps for the specified (comma-separated) '
2186 'platform(s); \'all\' will process all deps_os '
2187 'references')
2188 parser.add_option('-f', '--force', action='store_true', default=True,
2189 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002190 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002191 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002192 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002193 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002194 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002195 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002196 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002197 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002198 return client.RunOnDeps('runhooks', args)
2199
2200
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002201def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002202 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002203
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002204 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002205 can be used to reproduce the same tree in the future. It is only useful for
lpique398a46e2016-10-11 13:19:57 -07002206 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
2207 number or a git hash. A git branch name isn't 'pinned' since the actual
2208 commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002209 """
2210 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2211 help='override deps for the specified (comma-separated) '
2212 'platform(s); \'all\' will process all deps_os '
2213 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002214 parser.add_option('-a', '--actual', action='store_true',
2215 help='gets the actual checked out revisions instead of the '
2216 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002217 parser.add_option('-s', '--snapshot', action='store_true',
2218 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002219 'version of all repositories to reproduce the tree, '
2220 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002221 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002222 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002223 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002224 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002225 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002226 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002227
2228
szager@google.comb9a78d32012-03-13 18:46:21 +00002229def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002230 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002231 (options, args) = parser.parse_args(args)
2232 options.force = True
2233 client = GClient.LoadCurrentConfig(options)
2234 if not client:
2235 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2236 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002237 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002238 return 0
2239
2240
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002241def CMDverify(parser, args):
2242 """Verifies the DEPS file deps are only from allowed_hosts."""
2243 (options, args) = parser.parse_args(args)
2244 client = GClient.LoadCurrentConfig(options)
2245 if not client:
2246 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2247 client.RunOnDeps(None, [])
2248 # Look at each first-level dependency of this gclient only.
2249 for dep in client.dependencies:
2250 bad_deps = dep.findDepsFromNotAllowedHosts()
2251 if not bad_deps:
2252 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002253 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002254 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002255 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2256 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002257 sys.stdout.flush()
2258 raise gclient_utils.Error(
2259 'dependencies from disallowed hosts; check your DEPS file.')
2260 return 0
2261
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002262class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002263 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002264
2265 def __init__(self, **kwargs):
2266 optparse.OptionParser.__init__(
2267 self, version='%prog ' + __version__, **kwargs)
2268
2269 # Some arm boards have issues with parallel sync.
2270 if platform.machine().startswith('arm'):
2271 jobs = 1
2272 else:
2273 jobs = max(8, gclient_utils.NumLocalCpus())
lpique398a46e2016-10-11 13:19:57 -07002274 # cmp: 2013/06/19
2275 # Temporary workaround to lower bot-load on SVN server.
2276 # Bypassed if a bot_update flag is detected.
2277 if (os.environ.get('CHROME_HEADLESS') == '1' and
2278 not os.path.exists('update.flag')):
2279 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002280
2281 self.add_option(
2282 '-j', '--jobs', default=jobs, type='int',
2283 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002284 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002285 self.add_option(
2286 '-v', '--verbose', action='count', default=0,
2287 help='Produces additional output for diagnostics. Can be used up to '
2288 'three times for more logging info.')
2289 self.add_option(
2290 '--gclientfile', dest='config_filename',
2291 help='Specify an alternate %s file' % self.gclientfile_default)
2292 self.add_option(
2293 '--spec',
2294 help='create a gclient file containing the provided string. Due to '
2295 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2296 self.add_option(
2297 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002298 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002299
2300 def parse_args(self, args=None, values=None):
2301 """Integrates standard options processing."""
2302 options, args = optparse.OptionParser.parse_args(self, args, values)
2303 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2304 logging.basicConfig(
2305 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002306 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002307 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002308 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002309 if (options.config_filename and
2310 options.config_filename != os.path.basename(options.config_filename)):
2311 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002312 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002313 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002314 options.entries_filename = options.config_filename + '_entries'
2315 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002316 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002317
2318 # These hacks need to die.
2319 if not hasattr(options, 'revisions'):
2320 # GClient.RunOnDeps expects it even if not applicable.
2321 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002322 if not hasattr(options, 'head'):
2323 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002324 if not hasattr(options, 'nohooks'):
2325 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002326 if not hasattr(options, 'noprehooks'):
2327 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002328 if not hasattr(options, 'deps_os'):
2329 options.deps_os = None
lpique398a46e2016-10-11 13:19:57 -07002330 if not hasattr(options, 'manually_grab_svn_rev'):
2331 options.manually_grab_svn_rev = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002332 if not hasattr(options, 'force'):
2333 options.force = None
2334 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002335
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002336
2337def disable_buffering():
2338 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2339 # operations. Python as a strong tendency to buffer sys.stdout.
2340 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2341 # Make stdout annotated with the thread ids.
2342 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002343
2344
sbc@chromium.org013731e2015-02-26 18:28:43 +00002345def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002346 """Doesn't parse the arguments here, just find the right subcommand to
2347 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002348 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002349 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002350 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002351 sys.version.split(' ', 1)[0],
2352 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002353 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002354 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002355 print(
2356 '\nPython cannot find the location of it\'s own executable.\n',
2357 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002358 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002359 fix_encoding.fix_encoding()
2360 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002361 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002362 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002363 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002364 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002365 except KeyboardInterrupt:
2366 gclient_utils.GClientChildren.KillAllRemainingChildren()
2367 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002368 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002369 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002370 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002371 finally:
2372 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002373 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002374
2375
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002376if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002377 try:
2378 sys.exit(main(sys.argv[1:]))
2379 except KeyboardInterrupt:
2380 sys.stderr.write('interrupted\n')
2381 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002382
2383# vim: ts=2:sw=2:tw=80:et: