blob: 53fd398b1c1d12f880ca5b2e22c3545294011a5c [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org39c0b222013-08-17 16:57:01 +00006"""Meta checkout manager supporting both Subversion and GIT."""
7# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
67# processing the deps_os dict of a DEPS file.
68#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000079from __future__ import print_function
80
maruel@chromium.org39c0b222013-08-17 16:57:01 +000081__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
szager@chromium.org7b8b6de2014-08-23 00:57:31 +000083import ast
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000084import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000085import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000086import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087import optparse
88import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000089import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000090import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000091import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000094import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000095import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000096import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
maruel@chromium.org35625c72011-03-23 17:34:02 +000098import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000099import gclient_scm
100import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000101import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000102from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000103import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000104import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000105import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000106
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000107CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
108
109
110def ast_dict_index(dnode, key):
111 """Search an ast.Dict for the argument key, and return its index."""
112 idx = [i for i in range(len(dnode.keys)) if (
113 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
114 if not idx:
115 return -1
116 elif len(idx) > 1:
117 raise gclient_utils.Error('Multiple dict entries with same key in AST')
118 return idx[-1]
119
120def ast2str(node, indent=0):
121 """Return a pretty-printed rendition of an ast.Node."""
122 t = type(node)
123 if t is ast.Module:
124 return '\n'.join([ast2str(x, indent) for x in node.body])
125 elif t is ast.Assign:
126 return ((' ' * indent) +
127 ' = '.join([ast2str(x) for x in node.targets] +
128 [ast2str(node.value, indent)]) + '\n')
129 elif t is ast.Name:
130 return node.id
131 elif t is ast.List:
132 if not node.elts:
133 return '[]'
134 elif len(node.elts) == 1:
135 return '[' + ast2str(node.elts[0], indent) + ']'
136 return ('[\n' + (' ' * (indent + 1)) +
137 (',\n' + (' ' * (indent + 1))).join(
138 [ast2str(x, indent + 1) for x in node.elts]) +
139 '\n' + (' ' * indent) + ']')
140 elif t is ast.Dict:
141 if not node.keys:
142 return '{}'
143 elif len(node.keys) == 1:
144 return '{%s: %s}' % (ast2str(node.keys[0]),
145 ast2str(node.values[0], indent + 1))
146 return ('{\n' + (' ' * (indent + 1)) +
147 (',\n' + (' ' * (indent + 1))).join(
148 ['%s: %s' % (ast2str(node.keys[i]),
149 ast2str(node.values[i], indent + 1))
150 for i in range(len(node.keys))]) +
151 '\n' + (' ' * indent) + '}')
152 elif t is ast.Str:
153 return "'%s'" % node.s
154 else:
155 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
156 % (node.lineno, node.col_offset, t))
157
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000158
maruel@chromium.org116704f2010-06-11 17:34:38 +0000159class GClientKeywords(object):
160 class FromImpl(object):
161 """Used to implement the From() syntax."""
162
163 def __init__(self, module_name, sub_target_name=None):
164 """module_name is the dep module we want to include from. It can also be
165 the name of a subdirectory to include from.
166
167 sub_target_name is an optional parameter if the module name in the other
168 DEPS file is different. E.g., you might want to map src/net to net."""
169 self.module_name = module_name
170 self.sub_target_name = sub_target_name
171
172 def __str__(self):
173 return 'From(%s, %s)' % (repr(self.module_name),
174 repr(self.sub_target_name))
175
maruel@chromium.org116704f2010-06-11 17:34:38 +0000176 class FileImpl(object):
177 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000178 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000179
180 def __init__(self, file_location):
181 self.file_location = file_location
182
183 def __str__(self):
184 return 'File("%s")' % self.file_location
185
186 def GetPath(self):
187 return os.path.split(self.file_location)[0]
188
189 def GetFilename(self):
190 rev_tokens = self.file_location.split('@')
191 return os.path.split(rev_tokens[0])[1]
192
193 def GetRevision(self):
194 rev_tokens = self.file_location.split('@')
195 if len(rev_tokens) > 1:
196 return rev_tokens[1]
197 return None
198
199 class VarImpl(object):
200 def __init__(self, custom_vars, local_scope):
201 self._custom_vars = custom_vars
202 self._local_scope = local_scope
203
204 def Lookup(self, var_name):
205 """Implements the Var syntax."""
206 if var_name in self._custom_vars:
207 return self._custom_vars[var_name]
208 elif var_name in self._local_scope.get("vars", {}):
209 return self._local_scope["vars"][var_name]
210 raise gclient_utils.Error("Var is not defined: %s" % var_name)
211
212
maruel@chromium.org064186c2011-09-27 23:53:33 +0000213class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 """Immutable configuration settings."""
215 def __init__(
agable99a7f802016-06-24 11:19:44 -0700216 self, parent, url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000217 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000218 GClientKeywords.__init__(self)
219
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220 # These are not mutable:
221 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000222 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000223 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000224 # 'managed' determines whether or not this dependency is synced/updated by
225 # gclient after gclient checks it out initially. The difference between
226 # 'managed' and 'should_process' is that the user specifies 'managed' via
agabled4aedc82016-06-28 13:03:59 -0700227 # the --managed command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000228 # 'should_process' is dynamically set by gclient if it goes over its
229 # recursion limit and controls gclient's behavior so it does not misbehave.
230 self._managed = managed
231 self._should_process = should_process
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000232 # This is a mutable value which has the list of 'target_os' OSes listed in
233 # the current deps file.
234 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000235
236 # These are only set in .gclient and not in DEPS files.
237 self._custom_vars = custom_vars or {}
238 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000239 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000241 # TODO(iannucci): Remove this when all masters are correctly substituting
242 # the new blink url.
243 if (self._custom_vars.get('webkit_trunk', '') ==
244 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000245 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000246 print('Overwriting Var("webkit_trunk") with %s' % new_url)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000247 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000248
maruel@chromium.org064186c2011-09-27 23:53:33 +0000249 # Post process the url to remove trailing slashes.
250 if isinstance(self._url, basestring):
251 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
252 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000253 self._url = self._url.replace('/@', '@')
254 elif not isinstance(self._url,
255 (self.FromImpl, self.FileImpl, None.__class__)):
256 raise gclient_utils.Error(
257 ('dependency url must be either a string, None, '
258 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000259 # Make any deps_file path platform-appropriate.
260 for sep in ['/', '\\']:
261 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000262
263 @property
264 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000265 return self._deps_file
266
267 @property
268 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000269 return self._managed
270
271 @property
272 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000273 return self._parent
274
275 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000276 def root(self):
277 """Returns the root node, a GClient object."""
278 if not self.parent:
279 # This line is to signal pylint that it could be a GClient instance.
280 return self or GClient(None, None)
281 return self.parent.root
282
283 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000284 def should_process(self):
285 """True if this dependency should be processed, i.e. checked out."""
286 return self._should_process
287
288 @property
289 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000290 return self._custom_vars.copy()
291
292 @property
293 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000294 return self._custom_deps.copy()
295
maruel@chromium.org064186c2011-09-27 23:53:33 +0000296 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000297 def custom_hooks(self):
298 return self._custom_hooks[:]
299
300 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000301 def url(self):
302 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000303
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000304 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000305 def target_os(self):
306 if self.local_target_os is not None:
307 return tuple(set(self.local_target_os).union(self.parent.target_os))
308 else:
309 return self.parent.target_os
310
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000311 def get_custom_deps(self, name, url):
312 """Returns a custom deps if applicable."""
313 if self.parent:
314 url = self.parent.get_custom_deps(name, url)
315 # None is a valid return value to disable a dependency.
316 return self.custom_deps.get(name, url)
317
maruel@chromium.org064186c2011-09-27 23:53:33 +0000318
319class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000320 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000321
agable99a7f802016-06-24 11:19:44 -0700322 def __init__(self, parent, name, url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000323 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000324 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000325 DependencySettings.__init__(
agable99a7f802016-06-24 11:19:44 -0700326 self, parent, url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000327 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000328
329 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000330 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000331
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000332 self._pre_deps_hooks = []
333
maruel@chromium.org68988972011-09-20 14:11:42 +0000334 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000335 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000336 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000337 # A cache of the files affected by the current operation, necessary for
338 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000339 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000340 # List of host names from which dependencies are allowed.
341 # Default is an empty set, meaning unspecified in DEPS file, and hence all
342 # hosts will be allowed. Non-empty set means whitelist of hosts.
343 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
344 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000345 # If it is not set to True, the dependency wasn't processed for its child
346 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000347 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000348 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000349 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000350 # This dependency had its pre-DEPS hooks run
351 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000352 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000353 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000354 # This is the scm used to checkout self.url. It may be used by dependencies
355 # to get the datetime of the revision we checked out.
356 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000357 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000358 # The actual revision we ended up getting, or None if that information is
359 # unavailable
360 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000361
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000362 # This is a mutable value that overrides the normal recursion limit for this
363 # dependency. It is read from the actual DEPS file so cannot be set on
364 # class instantiation.
365 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000366 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000367 # 'no recursion' setting on a dep-by-dep basis. It will replace
368 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000369 #
370 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
371 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000372 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700373 # This is inherited from WorkItem. We want the URL to be a resource.
374 if url and isinstance(url, basestring):
375 # The url is usually given to gclient either as https://blah@123
376 # or just https://blah. The @123 portion is irrelevent.
377 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000378
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000379 if not self.name and self.parent:
380 raise gclient_utils.Error('Dependency without name')
381
maruel@chromium.org470b5432011-10-11 18:18:19 +0000382 @property
383 def requirements(self):
384 """Calculate the list of requirements."""
385 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000386 # self.parent is implicitly a requirement. This will be recursive by
387 # definition.
388 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000389 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000390
391 # For a tree with at least 2 levels*, the leaf node needs to depend
392 # on the level higher up in an orderly way.
393 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
394 # thus unsorted, while the .gclient format is a list thus sorted.
395 #
396 # * _recursion_limit is hard coded 2 and there is no hope to change this
397 # value.
398 #
399 # Interestingly enough, the following condition only works in the case we
400 # want: self is a 2nd level node. 3nd level node wouldn't need this since
401 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000402 if self.parent and self.parent.parent and not self.parent.parent.parent:
403 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000404
405 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000406 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000407
maruel@chromium.org470b5432011-10-11 18:18:19 +0000408 if self.name:
409 requirements |= set(
410 obj.name for obj in self.root.subtree(False)
411 if (obj is not self
412 and obj.name and
413 self.name.startswith(posixpath.join(obj.name, ''))))
414 requirements = tuple(sorted(requirements))
415 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
416 return requirements
417
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000418 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000419 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000420 """Returns False if recursion_override is ever specified."""
421 if self.recursion_override is not None:
422 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000423 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000424
425 @property
426 def recursion_limit(self):
427 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000428 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000429 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000430 if self.try_recursedeps and self.parent.recursedeps != None:
431 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000432 return 1
433
434 if self.recursion_override is not None:
435 return self.recursion_override
436 return max(self.parent.recursion_limit - 1, 0)
437
maruel@chromium.org470b5432011-10-11 18:18:19 +0000438 def verify_validity(self):
439 """Verifies that this Dependency is fine to add as a child of another one.
440
441 Returns True if this entry should be added, False if it is a duplicate of
442 another entry.
443 """
444 logging.info('Dependency(%s).verify_validity()' % self.name)
445 if self.name in [s.name for s in self.parent.dependencies]:
446 raise gclient_utils.Error(
447 'The same name "%s" appears multiple times in the deps section' %
448 self.name)
449 if not self.should_process:
450 # Return early, no need to set requirements.
451 return True
452
453 # This require a full tree traversal with locks.
454 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
455 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000456 self_url = self.LateOverride(self.url)
457 sibling_url = sibling.LateOverride(sibling.url)
458 # Allow to have only one to be None or ''.
459 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000460 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000461 ('Dependency %s specified more than once:\n'
462 ' %s [%s]\n'
463 'vs\n'
464 ' %s [%s]') % (
465 self.name,
466 sibling.hierarchy(),
467 sibling_url,
468 self.hierarchy(),
469 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000470 # In theory we could keep it as a shadow of the other one. In
471 # practice, simply ignore it.
472 logging.warn('Won\'t process duplicate dependency %s' % sibling)
473 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000474 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000475
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000476 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000477 """Resolves the parsed url from url.
478
479 Manages From() keyword accordingly. Do not touch self.parsed_url nor
480 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000481 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000482 parsed_url = self.get_custom_deps(self.name, url)
483 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000484 logging.info(
485 'Dependency(%s).LateOverride(%s) -> %s' %
486 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000487 return parsed_url
488
489 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000490 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000491 ref = [
492 dep for dep in self.root.subtree(True) if url.module_name == dep.name
493 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000494 if not ref:
495 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
496 url.module_name, ref))
497 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000499 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000500 found_deps = [d for d in ref.dependencies if d.name == sub_target]
501 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000502 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000503 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
504 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000505 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000506
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000507 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000508 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000509 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000510 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000511 'Dependency(%s).LateOverride(%s) -> %s (From)' %
512 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000513 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000514
515 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000517 if (not parsed_url[0] and
518 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 # A relative url. Fetch the real base.
520 path = parsed_url[2]
521 if not path.startswith('/'):
522 raise gclient_utils.Error(
523 'relative DEPS entry \'%s\' must begin with a slash' % url)
524 # Create a scm just to query the full url.
525 parent_url = self.parent.parsed_url
526 if isinstance(parent_url, self.FileImpl):
527 parent_url = parent_url.file_location
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000528 scm = gclient_scm.CreateSCM(
529 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000530 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000531 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000532 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000533 logging.info(
534 'Dependency(%s).LateOverride(%s) -> %s' %
535 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000536 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000537
538 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000539 logging.info(
540 'Dependency(%s).LateOverride(%s) -> %s (File)' %
541 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000542 return url
543
544 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000545 logging.info(
546 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000547 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000548
549 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000550
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000551 @staticmethod
552 def MergeWithOsDeps(deps, deps_os, target_os_list):
553 """Returns a new "deps" structure that is the deps sent in updated
554 with information from deps_os (the deps_os section of the DEPS
555 file) that matches the list of target os."""
556 os_overrides = {}
557 for the_target_os in target_os_list:
558 the_target_os_deps = deps_os.get(the_target_os, {})
559 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
560 overrides = os_overrides.setdefault(os_dep_key, [])
561 overrides.append((the_target_os, os_dep_value))
562
563 # If any os didn't specify a value (we have fewer value entries
564 # than in the os list), then it wants to use the default value.
565 for os_dep_key, os_dep_value in os_overrides.iteritems():
566 if len(os_dep_value) != len(target_os_list):
567 # Record the default value too so that we don't accidently
568 # set it to None or miss a conflicting DEPS.
569 if os_dep_key in deps:
570 os_dep_value.append(('default', deps[os_dep_key]))
571
572 target_os_deps = {}
573 for os_dep_key, os_dep_value in os_overrides.iteritems():
574 # os_dep_value is a list of (os, value) pairs.
575 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
576 if not possible_values:
577 target_os_deps[os_dep_key] = None
578 else:
579 if len(possible_values) > 1:
580 # It would be possible to abort here but it would be
581 # unfortunate if we end up preventing any kind of checkout.
582 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
583 os_dep_key, os_dep_value, target_os_list)
584 # Sorting to get the same result every time in case of conflicts.
585 target_os_deps[os_dep_key] = sorted(possible_values)[0]
586
587 new_deps = deps.copy()
588 new_deps.update(target_os_deps)
589 return new_deps
590
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000591 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000592 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000593 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000594 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000595
596 deps_content = None
597 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000598
599 # First try to locate the configured deps file. If it's missing, fallback
600 # to DEPS.
601 deps_files = [self.deps_file]
602 if 'DEPS' not in deps_files:
603 deps_files.append('DEPS')
604 for deps_file in deps_files:
605 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
606 if os.path.isfile(filepath):
607 logging.info(
608 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
609 filepath)
610 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000611 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000612 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
613 filepath)
614
615 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000616 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000617 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000618 use_strict = 'use strict' in deps_content.splitlines()[0]
619
620 local_scope = {}
621 if deps_content:
622 # One thing is unintuitive, vars = {} must happen before Var() use.
623 var = self.VarImpl(self.custom_vars, local_scope)
624 if use_strict:
625 logging.info(
626 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
627 global_scope = {
628 '__builtins__': {'None': None},
629 'Var': var.Lookup,
630 'deps_os': {},
631 }
632 else:
633 global_scope = {
634 'File': self.FileImpl,
635 'From': self.FromImpl,
636 'Var': var.Lookup,
637 'deps_os': {},
638 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000639 # Eval the content.
640 try:
641 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000642 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000643 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000644 if use_strict:
645 for key, val in local_scope.iteritems():
646 if not isinstance(val, (dict, list, tuple, str)):
647 raise gclient_utils.Error(
648 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
649 (self.name, key, val))
650
maruel@chromium.org271375b2010-06-23 19:17:38 +0000651 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000652 if 'recursion' in local_scope:
653 self.recursion_override = local_scope.get('recursion')
654 logging.warning(
655 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000656 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000657 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000658 self.recursedeps = {}
659 for ent in local_scope['recursedeps']:
660 if isinstance(ent, basestring):
661 self.recursedeps[ent] = {"deps_file": self.deps_file}
662 else: # (depname, depsfilename)
663 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000664 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000665 # If present, save 'target_os' in the local_target_os property.
666 if 'target_os' in local_scope:
667 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 # load os specific dependencies if defined. these dependencies may
669 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000670 target_os_list = self.target_os
671 if 'deps_os' in local_scope and target_os_list:
672 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673
maruel@chromium.org271375b2010-06-23 19:17:38 +0000674 # If a line is in custom_deps, but not in the solution, we want to append
675 # this line to the solution.
676 for d in self.custom_deps:
677 if d not in deps:
678 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000679
680 # If use_relative_paths is set in the DEPS file, regenerate
681 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000682 # the DEPS file. Also update recursedeps if use_relative_paths is
683 # enabled.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000684 use_relative_paths = local_scope.get('use_relative_paths', False)
685 if use_relative_paths:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000686 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000687 rel_deps = {}
688 for d, url in deps.items():
689 # normpath is required to allow DEPS to use .. in their
690 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000691 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000692 logging.warning('Updating deps by prepending %s.', self.name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000693 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000695 # Update recursedeps if it's set.
696 if self.recursedeps is not None:
697 logging.warning('Updating recursedeps by prepending %s.', self.name)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000698 rel_deps = {}
699 for depname, options in self.recursedeps.iteritems():
700 rel_deps[os.path.normpath(os.path.join(self.name, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000701 self.recursedeps = rel_deps
702
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000703 if 'allowed_hosts' in local_scope:
704 try:
705 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
706 except TypeError: # raised if non-iterable
707 pass
708 if not self._allowed_hosts:
709 logging.warning("allowed_hosts is specified but empty %s",
710 self._allowed_hosts)
711 raise gclient_utils.Error(
712 'ParseDepsFile(%s): allowed_hosts must be absent '
713 'or a non-empty iterable' % self.name)
714
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000715 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000716 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000717 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000718 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000719 deps_file = self.deps_file
720 if self.recursedeps is not None:
721 ent = self.recursedeps.get(name)
722 if ent is not None:
723 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000724 deps_to_add.append(Dependency(
agable99a7f802016-06-24 11:19:44 -0700725 self, name, url, None, None, self.custom_vars, None,
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000726 deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000727 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000728
729 # override named sets of hooks by the custom hooks
730 hooks_to_run = []
731 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
732 for hook in local_scope.get('hooks', []):
733 if hook.get('name', '') not in hook_names_to_suppress:
734 hooks_to_run.append(hook)
735
736 # add the replacements and any additions
737 for hook in self.custom_hooks:
738 if 'action' in hook:
739 hooks_to_run.append(hook)
740
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000741 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
742 local_scope.get('pre_deps_hooks', [])]
743
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000744 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000745 logging.info('ParseDepsFile(%s) done' % self.name)
746
747 def add_dependencies_and_close(self, deps_to_add, hooks):
748 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000749 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000750 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000751 self.add_dependency(dep)
752 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000753
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000754 def maybeGetParentRevision(self, command, options, parsed_url, parent):
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000755 """Uses revision/timestamp of parent if no explicit revision was specified.
756
757 If we are performing an update and --transitive is set, use
758 - the parent's revision if 'self.url' is in the same repository
759 - the parent's timestamp otherwise
760 to update 'self.url'. The used revision/timestamp will be set in
761 'options.revision'.
762 If we have an explicit revision do nothing.
763 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000764 if command == 'update' and options.transitive and not options.revision:
765 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
766 if not revision:
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000767 options.revision = getattr(parent, '_used_revision', None)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000768 if (options.revision and
769 not gclient_utils.IsDateRevision(options.revision)):
770 assert self.parent and self.parent.used_scm
771 # If this dependency is in the same repository as parent it's url will
772 # start with a slash. If so we take the parent revision instead of
773 # it's timestamp.
774 # (The timestamps of commits in google code are broken -- which can
775 # result in dependencies to be checked out at the wrong revision)
776 if self.url.startswith('/'):
777 if options.verbose:
778 print('Using parent\'s revision %s since we are in the same '
779 'repository.' % options.revision)
780 else:
781 parent_revision_date = self.parent.used_scm.GetRevisionDate(
782 options.revision)
783 options.revision = gclient_utils.MakeDateRevision(
784 parent_revision_date)
785 if options.verbose:
786 print('Using parent\'s revision date %s since we are in a '
787 'different repository.' % options.revision)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000788
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000789 def findDepsFromNotAllowedHosts(self):
790 """Returns a list of depenecies from not allowed hosts.
791
792 If allowed_hosts is not set, allows all hosts and returns empty list.
793 """
794 if not self._allowed_hosts:
795 return []
796 bad_deps = []
797 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000798 # Don't enforce this for custom_deps.
799 if dep.name in self._custom_deps:
800 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000801 if isinstance(dep.url, basestring):
802 parsed_url = urlparse.urlparse(dep.url)
803 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
804 bad_deps.append(dep)
805 return bad_deps
806
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000807 # Arguments number differs from overridden method
808 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000809 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000810 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000811 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000812 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000813 if not self.should_process:
814 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000815 # When running runhooks, there's no need to consult the SCM.
816 # All known hooks are expected to run unconditionally regardless of working
817 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000818 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000819 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000820 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000821 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000822 if run_scm and parsed_url:
823 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000824 # Special support for single-file checkout.
825 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000826 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
827 # pylint: disable=E1103
828 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000829 self._used_scm = gclient_scm.SVNWrapper(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000830 parsed_url.GetPath(), self.root.root_dir, self.name,
831 out_cb=work_queue.out_cb)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000832 self._used_scm.RunCommand('updatesingle',
833 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000834 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000835 # Create a shallow copy to mutate revision.
836 options = copy.copy(options)
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000837 options.revision = revision_override
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000838 self.maybeGetParentRevision(
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000839 command, options, parsed_url, self.parent)
840 self._used_revision = options.revision
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000841 self._used_scm = gclient_scm.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000842 parsed_url, self.root.root_dir, self.name, self.outbuf,
843 out_cb=work_queue.out_cb)
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000844 self._got_revision = self._used_scm.RunCommand(command, options, args,
845 file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000846 if file_list:
847 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000848
849 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
850 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000851 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000852 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000854 continue
855 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000856 [self.root.root_dir.lower(), file_list[i].lower()])
857 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000858 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000859 while file_list[i].startswith(('\\', '/')):
860 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000861
862 # Always parse the DEPS file.
863 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000864 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000865 if command in ('update', 'revert') and not options.noprehooks:
866 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000867
868 if self.recursion_limit:
869 # Parse the dependencies of this dependency.
870 for s in self.dependencies:
871 work_queue.enqueue(s)
872
873 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000874 if not isinstance(parsed_url, self.FileImpl):
875 # Skip file only checkout.
876 scm = gclient_scm.GetScmName(parsed_url)
877 if not options.scm or scm in options.scm:
878 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000879 # Pass in the SCM type as an env variable. Make sure we don't put
880 # unicode strings in the environment.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000881 env = os.environ.copy()
882 if scm:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000883 env['GCLIENT_SCM'] = str(scm)
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000884 if parsed_url:
rnk@chromium.org2d3c28d2014-03-30 00:56:32 +0000885 env['GCLIENT_URL'] = str(parsed_url)
886 env['GCLIENT_DEP_PATH'] = str(self.name)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000887 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000888 print_stdout = False
889 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000890 """Git-specific path marshaling. It is optimized for git-grep."""
891
892 def mod_path(git_pathspec):
893 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
894 modified_path = os.path.join(self.name, match.group(2))
895 branch = match.group(1) or ''
896 return '%s%s' % (branch, modified_path)
897
898 match = re.match('^Binary file ([^\0]+) matches$', line)
899 if match:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000900 print('Binary file %s matches\n' % mod_path(match.group(1)))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000901 return
902
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000903 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000904 if len(items) == 2 and items[1]:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000905 print('%s : %s' % (mod_path(items[0]), items[1]))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000906 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000907 # Multiple null bytes or a single trailing null byte indicate
908 # git is likely displaying filenames only (such as with -l)
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000909 print('\n'.join(mod_path(path) for path in items if path))
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000910 else:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000911 print(line)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000912 else:
913 print_stdout = True
914 filter_fn = None
915
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000916 if parsed_url is None:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000917 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
iannucci@chromium.orgf3ec5782013-07-18 18:37:50 +0000918 elif os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000919 try:
920 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000921 args, cwd=cwd, env=env, print_stdout=print_stdout,
922 filter_fn=filter_fn,
923 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000924 except subprocess2.CalledProcessError:
925 if not options.ignore:
926 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000927 else:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000928 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000929
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000931 @gclient_utils.lockedmethod
932 def _run_is_done(self, file_list, parsed_url):
933 # Both these are kept for hooks that are run as a separate tree traversal.
934 self._file_list = file_list
935 self._parsed_url = parsed_url
936 self._processed = True
937
szager@google.comb9a78d32012-03-13 18:46:21 +0000938 @staticmethod
939 def GetHookAction(hook_dict, matching_file_list):
940 """Turns a parsed 'hook' dict into an executable command."""
941 logging.debug(hook_dict)
942 logging.debug(matching_file_list)
943 command = hook_dict['action'][:]
944 if command[0] == 'python':
945 # If the hook specified "python" as the first item, the action is a
946 # Python script. Run it by starting a new copy of the same
947 # interpreter.
948 command[0] = sys.executable
949 if '$matching_files' in command:
950 splice_index = command.index('$matching_files')
951 command[splice_index:splice_index + 1] = matching_file_list
952 return command
953
954 def GetHooks(self, options):
955 """Evaluates all hooks, and return them in a flat list.
956
957 RunOnDeps() must have been called before to load the DEPS.
958 """
959 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000960 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000961 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000962 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000963 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000964 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000965 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000966 # TODO(maruel): If the user is using git or git-svn, then we don't know
967 # what files have changed so we always run all hooks. It'd be nice to fix
968 # that.
969 if (options.force or
970 isinstance(self.parsed_url, self.FileImpl) or
971 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000972 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000974 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000975 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000976 # Run hooks on the basis of whether the files from the gclient operation
977 # match each hook's pattern.
978 for hook_dict in self.deps_hooks:
979 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000980 matching_file_list = [
981 f for f in self.file_list_and_children if pattern.search(f)
982 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000984 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000985 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000986 result.extend(s.GetHooks(options))
987 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988
szager@google.comb9a78d32012-03-13 18:46:21 +0000989 def RunHooksRecursively(self, options):
990 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000991 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000992 for hook in self.GetHooks(options):
993 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000994 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000995 gclient_utils.CheckCallAndFilterAndHeader(
996 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000997 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000998 # Use a discrete exit status code of 2 to indicate that a hook action
999 # failed. Users of this script may wish to treat hook action failures
1000 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001001 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +00001002 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +00001003 finally:
1004 elapsed_time = time.time() - start_time
1005 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001006 print("Hook '%s' took %.2f secs" % (
1007 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001008
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001009 def RunPreDepsHooks(self):
1010 assert self.processed
1011 assert self.deps_parsed
1012 assert not self.pre_deps_hooks_ran
1013 assert not self.hooks_ran
1014 for s in self.dependencies:
1015 assert not s.processed
1016 self._pre_deps_hooks_ran = True
1017 for hook in self.pre_deps_hooks:
1018 try:
1019 start_time = time.time()
1020 gclient_utils.CheckCallAndFilterAndHeader(
1021 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001022 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001023 # Use a discrete exit status code of 2 to indicate that a hook action
1024 # failed. Users of this script may wish to treat hook action failures
1025 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001026 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001027 sys.exit(2)
1028 finally:
1029 elapsed_time = time.time() - start_time
1030 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001031 print("Hook '%s' took %.2f secs" % (
1032 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001033
1034
maruel@chromium.org0d812442010-08-10 12:41:08 +00001035 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001036 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001037 dependencies = self.dependencies
1038 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001039 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001040 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001041 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001042 for i in d.subtree(include_all):
1043 yield i
1044
1045 def depth_first_tree(self):
1046 """Depth-first recursion including the root node."""
1047 yield self
1048 for i in self.dependencies:
1049 for j in i.depth_first_tree():
1050 if j.should_process:
1051 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001052
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001053 @gclient_utils.lockedmethod
1054 def add_dependency(self, new_dep):
1055 self._dependencies.append(new_dep)
1056
1057 @gclient_utils.lockedmethod
1058 def _mark_as_parsed(self, new_hooks):
1059 self._deps_hooks.extend(new_hooks)
1060 self._deps_parsed = True
1061
maruel@chromium.org68988972011-09-20 14:11:42 +00001062 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001063 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001064 def dependencies(self):
1065 return tuple(self._dependencies)
1066
1067 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001069 def deps_hooks(self):
1070 return tuple(self._deps_hooks)
1071
1072 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001073 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001074 def pre_deps_hooks(self):
1075 return tuple(self._pre_deps_hooks)
1076
1077 @property
1078 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001079 def parsed_url(self):
1080 return self._parsed_url
1081
1082 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001083 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001084 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001085 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001086 return self._deps_parsed
1087
1088 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001089 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001090 def processed(self):
1091 return self._processed
1092
1093 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001094 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001095 def pre_deps_hooks_ran(self):
1096 return self._pre_deps_hooks_ran
1097
1098 @property
1099 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001100 def hooks_ran(self):
1101 return self._hooks_ran
1102
1103 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001104 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001105 def allowed_hosts(self):
1106 return self._allowed_hosts
1107
1108 @property
1109 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001110 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001111 return tuple(self._file_list)
1112
1113 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001114 def used_scm(self):
1115 """SCMWrapper instance for this dependency or None if not processed yet."""
1116 return self._used_scm
1117
1118 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001119 @gclient_utils.lockedmethod
1120 def got_revision(self):
1121 return self._got_revision
1122
1123 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001124 def file_list_and_children(self):
1125 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001126 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001127 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001128 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001129
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001130 def __str__(self):
1131 out = []
agable99a7f802016-06-24 11:19:44 -07001132 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001133 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001134 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1135 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001136 # First try the native property if it exists.
1137 if hasattr(self, '_' + i):
1138 value = getattr(self, '_' + i, False)
1139 else:
1140 value = getattr(self, i, False)
1141 if value:
1142 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001143
1144 for d in self.dependencies:
1145 out.extend([' ' + x for x in str(d).splitlines()])
1146 out.append('')
1147 return '\n'.join(out)
1148
1149 def __repr__(self):
1150 return '%s: %s' % (self.name, self.url)
1151
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001152 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001153 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001154 out = '%s(%s)' % (self.name, self.url)
1155 i = self.parent
1156 while i and i.name:
1157 out = '%s(%s) -> %s' % (i.name, i.url, out)
1158 i = i.parent
1159 return out
1160
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161
1162class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001163 """Object that represent a gclient checkout. A tree of Dependency(), one per
1164 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001165
1166 DEPS_OS_CHOICES = {
1167 "win32": "win",
1168 "win": "win",
1169 "cygwin": "win",
1170 "darwin": "mac",
1171 "mac": "mac",
1172 "unix": "unix",
1173 "linux": "unix",
1174 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001175 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001176 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001177 }
1178
1179 DEFAULT_CLIENT_FILE_TEXT = ("""\
1180solutions = [
agable99a7f802016-06-24 11:19:44 -07001181 {
1182 "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001184 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001185 "managed" : %(managed)s,
agable99a7f802016-06-24 11:19:44 -07001186 "custom_deps" : {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001187 },
1188]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001189cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001190""")
1191
1192 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
agable99a7f802016-06-24 11:19:44 -07001193 {
1194 "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001195 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001196 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001197 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001198 "custom_deps" : {
agable99a7f802016-06-24 11:19:44 -07001199%(solution_deps)s
1200 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001201 },
1202""")
1203
1204 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1205# Snapshot generated with gclient revinfo --snapshot
1206solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001207%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001208""")
1209
1210 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001211 # Do not change previous behavior. Only solution level and immediate DEPS
1212 # are processed.
1213 self._recursion_limit = 2
agable99a7f802016-06-24 11:19:44 -07001214 Dependency.__init__(self, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001215 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001216 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001217 if options.deps_os:
1218 enforced_os = options.deps_os.split(',')
1219 else:
1220 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1221 if 'all' in enforced_os:
1222 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001223 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001224 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001225 self.config_content = None
1226
borenet@google.com88d10082014-03-21 17:24:48 +00001227 def _CheckConfig(self):
1228 """Verify that the config matches the state of the existing checked-out
1229 solutions."""
1230 for dep in self.dependencies:
1231 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001232 scm = gclient_scm.CreateSCM(
1233 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001234 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001235 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001236 mirror = scm.GetCacheMirror()
1237 if mirror:
1238 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1239 mirror.exists())
1240 else:
1241 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001242 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001243Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001244is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001245
borenet@google.com97882362014-04-07 20:06:02 +00001246The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001247URL: %(expected_url)s (%(expected_scm)s)
1248Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001249
1250The local checkout in %(checkout_path)s reports:
1251%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001252
1253You should ensure that the URL listed in .gclient is correct and either change
1254it or fix the checkout. If you're managing your own git checkout in
agabled4aedc82016-06-28 13:03:59 -07001255%(checkout_path)s but the URL in .gclient is for an svn repository, you should
1256set 'managed': False in .gclient.
borenet@google.com88d10082014-03-21 17:24:48 +00001257''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1258 'expected_url': dep.url,
1259 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001260 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001261 'actual_url': actual_url,
1262 'actual_scm': gclient_scm.GetScmName(actual_url)})
1263
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001264 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001265 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001266 config_dict = {}
1267 self.config_content = content
1268 try:
1269 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001270 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001271 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001272
peter@chromium.org1efccc82012-04-27 16:34:38 +00001273 # Append any target OS that is not already being enforced to the tuple.
1274 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001275 if config_dict.get('target_os_only', False):
1276 self._enforced_os = tuple(set(target_os))
1277 else:
1278 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1279
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001280 cache_dir = config_dict.get('cache_dir')
1281 if cache_dir:
1282 cache_dir = os.path.join(self.root_dir, cache_dir)
1283 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001284 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001285 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001286 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1287 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001288 gclient_scm.GitWrapper.cache_dir = cache_dir
1289 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001290
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001291 if not target_os and config_dict.get('target_os_only', False):
1292 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1293 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001294
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001295 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001296 for s in config_dict.get('solutions', []):
agable99a7f802016-06-24 11:19:44 -07001297 if s.get('safesync_url'):
1298 raise gclient_utils.Error('safesync_url is not supported anymore. '
1299 'Please remove it from your .gclient file.')
maruel@chromium.org81843b82010-06-28 16:49:26 +00001300 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001301 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001302 self, s['name'], s['url'],
agabled4aedc82016-06-28 13:03:59 -07001303 s.get('managed', False),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001304 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001305 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001306 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001307 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001308 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001309 except KeyError:
1310 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1311 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001312 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1313 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001314
1315 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001316 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001317 self._options.config_filename),
1318 self.config_content)
1319
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001320 def MigrateConfigToGit(self, path, options):
1321 svn_url_re = re.compile('^(https?://src\.chromium\.org/svn|'
1322 'svn://svn\.chromium\.org/chrome)/'
1323 '(trunk|branches/[^/]+)/src')
1324 old_git_re = re.compile('^(https?://git\.chromium\.org|'
1325 'ssh://([a-zA-Z_][a-zA-Z0-9_-]*@)?'
1326 'gerrit\.chromium\.org(:2941[89])?)/'
1327 'chromium/src\.git')
1328 # Scan existing .gclient file for obsolete settings. It would be simpler
1329 # to traverse self.dependencies, but working with the AST allows the code to
1330 # dump an updated .gclient file that preserves the ordering of the original.
1331 a = ast.parse(self.config_content, options.config_filename, 'exec')
1332 modified = False
1333 solutions = [elem for elem in a.body if 'solutions' in
1334 [target.id for target in elem.targets]]
1335 if not solutions:
1336 return self
1337 solutions = solutions[-1]
1338 for solution in solutions.value.elts:
1339 # Check for obsolete URL's
1340 url_idx = ast_dict_index(solution, 'url')
1341 if url_idx == -1:
1342 continue
1343 url_val = solution.values[url_idx]
1344 if type(url_val) is not ast.Str:
1345 continue
1346 if (svn_url_re.match(url_val.s.strip())):
1347 raise gclient_utils.Error(
1348"""
1349The chromium code repository has migrated completely to git.
1350Your SVN-based checkout is now obsolete; you need to create a brand-new
1351git checkout by following these instructions:
1352
1353http://www.chromium.org/developers/how-tos/get-the-code
1354""")
1355 if (old_git_re.match(url_val.s.strip())):
1356 url_val.s = CHROMIUM_SRC_URL
1357 modified = True
1358
szager@chromium.org808bcfb2014-08-24 19:38:43 +00001359 # Ensure deps_file is set to .DEPS.git. We enforce this here to smooth
1360 # over switching between pre-git-migration and post-git-migration
1361 # revisions.
1362 # - For pre-migration revisions, .DEPS.git must be explicitly set.
1363 # - For post-migration revisions, .DEPS.git is not present, so gclient
1364 # will correctly fall back to DEPS.
1365 if url_val.s == CHROMIUM_SRC_URL:
1366 deps_file_idx = ast_dict_index(solution, 'deps_file')
1367 if deps_file_idx != -1:
1368 continue
1369 solution.keys.append(ast.Str('deps_file'))
1370 solution.values.append(ast.Str('.DEPS.git'))
1371 modified = True
1372
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001373 if not modified:
1374 return self
1375
1376 print(
1377"""
1378WARNING: gclient detected an obsolete setting in your %s file. The file has
1379been automagically updated. The previous version is available at %s.old.
1380""" % (options.config_filename, options.config_filename))
1381
1382 # Replace existing .gclient with the updated version.
1383 # Return a new GClient instance based on the new content.
1384 new_content = ast2str(a)
1385 dot_gclient_fn = os.path.join(path, options.config_filename)
iannucci@chromium.orgc7c41682014-08-23 03:44:18 +00001386 try:
1387 os.rename(dot_gclient_fn, dot_gclient_fn + '.old')
1388 except OSError:
1389 pass
1390 with open(dot_gclient_fn, 'w') as fh:
1391 fh.write(new_content)
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001392 client = GClient(path, options)
1393 client.SetConfig(new_content)
1394 return client
1395
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001396 @staticmethod
1397 def LoadCurrentConfig(options):
1398 """Searches for and loads a .gclient file relative to the current working
1399 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001400 if options.spec:
1401 client = GClient('.', options)
1402 client.SetConfig(options.spec)
1403 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001404 if options.verbose:
1405 print('Looking for %s starting from %s\n' % (
1406 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001407 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1408 if not path:
1409 return None
1410 client = GClient(path, options)
1411 client.SetConfig(gclient_utils.FileRead(
1412 os.path.join(path, options.config_filename)))
szager@chromium.org7b8b6de2014-08-23 00:57:31 +00001413 client = client.MigrateConfigToGit(path, options)
maruel@chromium.org69392e72011-10-13 22:09:00 +00001414
1415 if (options.revisions and
1416 len(client.dependencies) > 1 and
1417 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001418 print(
1419 ('You must specify the full solution name like --revision %s@%s\n'
1420 'when you have multiple solutions setup in your .gclient file.\n'
1421 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001422 client.dependencies[0].name,
1423 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001424 ', '.join(s.name for s in client.dependencies[1:])),
1425 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001426 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001427
nsylvain@google.comefc80932011-05-31 21:27:56 +00001428 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agabled4aedc82016-06-28 13:03:59 -07001429 managed=False, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001430 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1431 'solution_name': solution_name,
1432 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001433 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001434 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001435 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001436 })
1437
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001438 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001439 """Creates a .gclient_entries file to record the list of unique checkouts.
1440
1441 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001442 """
1443 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1444 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001445 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001446 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001447 # Skip over File() dependencies as we can't version them.
1448 if not isinstance(entry.parsed_url, self.FileImpl):
1449 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1450 pprint.pformat(entry.parsed_url))
1451 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001452 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001453 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001454 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001455
1456 def _ReadEntries(self):
1457 """Read the .gclient_entries file for the given client.
1458
1459 Returns:
1460 A sequence of solution names, which will be empty if there is the
1461 entries file hasn't been created yet.
1462 """
1463 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001464 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001465 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001466 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001467 try:
1468 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001469 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001470 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001471 return scope['entries']
1472
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001473 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001474 """Checks for revision overrides."""
1475 revision_overrides = {}
joi@chromium.org792ea882010-11-10 02:37:27 +00001476 if not self._options.revisions:
1477 for s in self.dependencies:
agabled4aedc82016-06-28 13:03:59 -07001478 if not s.managed and not self._options.__dict__.get('head', False):
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001479 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001480 if not self._options.revisions:
1481 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001482 solutions_names = [s.name for s in self.dependencies]
agable2697cd12016-06-28 10:23:53 -07001483 for i, revision in enumerate(self._options.revisions):
agable8c51b6f2016-06-28 11:21:11 -07001484 if '@' in revision:
1485 name, rev = revision.split('@', 1)
1486 else:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001487 # Support for --revision 123
agable2697cd12016-06-28 10:23:53 -07001488 name, rev = solutions_names[i], revision
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001489 revision_overrides[name] = rev
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001490 return revision_overrides
1491
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001492 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001493 """Runs a command on each dependency in a client and its dependencies.
1494
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001495 Args:
1496 command: The command to use (e.g., 'status' or 'diff')
1497 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001498 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001499 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001500 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001501
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001502 revision_overrides = {}
1503 # It's unnecessary to check for revision overrides for 'recurse'.
1504 # Save a few seconds by not calling _EnforceRevisions() in that case.
agable@chromium.org0242eb42015-06-09 00:45:31 +00001505 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001506 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001507 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001508 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001509 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001510 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001511 if command in ('update', 'revert'):
1512 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001513 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001514 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001515 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001516 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1517 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001518 for s in self.dependencies:
1519 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001520 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001521 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001522 print('Please fix your script, having invalid --revision flags will soon '
1523 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001524
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001525 # Once all the dependencies have been processed, it's now safe to run the
1526 # hooks.
1527 if not self._options.nohooks:
1528 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001529
1530 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001531 # Notify the user if there is an orphaned entry in their working copy.
1532 # Only delete the directory if there are no changes in it, and
1533 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001534 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001535 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1536 for e in entries]
1537
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001538 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001539 if not prev_url:
1540 # entry must have been overridden via .gclient custom_deps
1541 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001542 # Fix path separator on Windows.
1543 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001544 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001545 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001546 if (entry not in entries and
1547 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001548 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001549 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001550 scm = gclient_scm.CreateSCM(
1551 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001552
1553 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001554 # The directory might be part of a git OR svn checkout.
1555 scm_root = None
primiano@chromium.org1c127382015-02-17 11:15:40 +00001556 scm_class = None
borenet@google.com359bb642014-05-13 17:28:19 +00001557 for scm_class in (gclient_scm.scm.GIT, gclient_scm.scm.SVN):
1558 try:
1559 scm_root = scm_class.GetCheckoutRoot(scm.checkout_path)
1560 except subprocess2.CalledProcessError:
1561 pass
1562 if scm_root:
1563 break
1564 else:
1565 logging.warning('Could not find checkout root for %s. Unable to '
1566 'determine whether it is part of a higher-level '
1567 'checkout, so not removing.' % entry)
1568 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001569
1570 # This is to handle the case of third_party/WebKit migrating from
1571 # being a DEPS entry to being part of the main project.
1572 # If the subproject is a Git project, we need to remove its .git
1573 # folder. Otherwise git operations on that folder will have different
1574 # effects depending on the current working directory.
1575 if scm_class == gclient_scm.scm.GIT and (
1576 os.path.abspath(scm_root) == os.path.abspath(e_dir)):
1577 e_par_dir = os.path.join(e_dir, os.pardir)
1578 if scm_class.IsInsideWorkTree(e_par_dir):
1579 par_scm_root = scm_class.GetCheckoutRoot(e_par_dir)
1580 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1581 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1582 if scm_class.IsDirectoryVersioned(par_scm_root, rel_e_dir):
1583 save_dir = scm.GetGitBackupDirPath()
1584 # Remove any eventual stale backup dir for the same project.
1585 if os.path.exists(save_dir):
1586 gclient_utils.rmtree(save_dir)
1587 os.rename(os.path.join(e_dir, '.git'), save_dir)
1588 # When switching between the two states (entry/ is a subproject
1589 # -> entry/ is part of the outer project), it is very likely
1590 # that some files are changed in the checkout, unless we are
1591 # jumping *exactly* across the commit which changed just DEPS.
1592 # In such case we want to cleanup any eventual stale files
1593 # (coming from the old subproject) in order to end up with a
1594 # clean checkout.
1595 scm_class.CleanupDir(par_scm_root, rel_e_dir)
1596 assert not os.path.exists(os.path.join(e_dir, '.git'))
1597 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1598 'level checkout. The git folder containing all the local'
1599 ' branches has been saved to %s.\n'
1600 'If you don\'t care about its state you can safely '
1601 'remove that folder to free up space.') %
1602 (entry, save_dir))
1603 continue
1604
borenet@google.com359bb642014-05-13 17:28:19 +00001605 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001606 logging.info('%s is part of a higher level checkout, not removing',
1607 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001608 continue
1609
1610 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001611 scm.status(self._options, [], file_list)
1612 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001613 if (not self._options.delete_unversioned_trees or
1614 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001615 # There are modified files in this entry. Keep warning until
1616 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001617 print(('\nWARNING: \'%s\' is no longer part of this client. '
1618 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001619 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001620 else:
1621 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001622 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001623 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001624 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001625 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001626 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001627 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001628
1629 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001630 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001631 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001632 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001633 work_queue = gclient_utils.ExecutionQueue(
1634 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001635 for s in self.dependencies:
1636 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001637 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001638
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001639 def GetURLAndRev(dep):
1640 """Returns the revision-qualified SCM url for a Dependency."""
1641 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001642 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001643 if isinstance(dep.parsed_url, self.FileImpl):
1644 original_url = dep.parsed_url.file_location
1645 else:
1646 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001647 url, _ = gclient_utils.SplitUrlRevision(original_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001648 scm = gclient_scm.CreateSCM(
1649 original_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001650 if not os.path.isdir(scm.checkout_path):
1651 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001652 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001653
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001654 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001655 new_gclient = ''
1656 # First level at .gclient
1657 for d in self.dependencies:
1658 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001659 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001660 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001661 for d in dep.dependencies:
1662 entries[d.name] = GetURLAndRev(d)
1663 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001664 GrabDeps(d)
1665 custom_deps = []
1666 for k in sorted(entries.keys()):
1667 if entries[k]:
1668 # Quotes aren't escaped...
1669 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1670 else:
1671 custom_deps.append(' \"%s\": None,\n' % k)
1672 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1673 'solution_name': d.name,
1674 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001675 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001676 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001677 'solution_deps': ''.join(custom_deps),
1678 }
1679 # Print the snapshot configuration file
1680 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001681 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001682 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001683 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001684 if self._options.actual:
1685 entries[d.name] = GetURLAndRev(d)
1686 else:
1687 entries[d.name] = d.parsed_url
1688 keys = sorted(entries.keys())
1689 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001690 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001691 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001692
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001693 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001694 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001695 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001696
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001697 def PrintLocationAndContents(self):
1698 # Print out the .gclient file. This is longer than if we just printed the
1699 # client dict, but more legible, and it might contain helpful comments.
1700 print('Loaded .gclient config in %s:\n%s' % (
1701 self.root_dir, self.config_content))
1702
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001703 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001704 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001705 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001706 return self._root_dir
1707
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001708 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001709 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001710 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001711 return self._enforced_os
1712
maruel@chromium.org68988972011-09-20 14:11:42 +00001713 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001714 def recursion_limit(self):
1715 """How recursive can each dependencies in DEPS file can load DEPS file."""
1716 return self._recursion_limit
1717
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001718 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001719 def try_recursedeps(self):
1720 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001721 return True
1722
1723 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001724 def target_os(self):
1725 return self._enforced_os
1726
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001727
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001728#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001729
1730
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001731def CMDcleanup(parser, args):
1732 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001733
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001734 Mostly svn-specific. Simply runs 'svn cleanup' for each module.
1735 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001736 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1737 help='override deps for the specified (comma-separated) '
1738 'platform(s); \'all\' will process all deps_os '
1739 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001740 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001741 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001742 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001743 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001744 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001745 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001746 return client.RunOnDeps('cleanup', args)
1747
1748
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001749@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001750def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001751 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001752
1753 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001754 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1755 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001756 """
1757 # Stop parsing at the first non-arg so that these go through to the command
1758 parser.disable_interspersed_args()
1759 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001760 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001761 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001762 help='Ignore non-zero return codes from subcommands.')
1763 parser.add_option('--prepend-dir', action='store_true',
1764 help='Prepend relative dir for use with git <cmd> --null.')
1765 parser.add_option('--no-progress', action='store_true',
1766 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001767 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001768 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001769 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001770 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001771 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1772 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001773 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001774 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001775 'This is because .gclient_entries needs to exist and be up to date.',
1776 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001777 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001778
1779 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001780 scm_set = set()
1781 for scm in options.scm:
1782 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001783 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001784
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001785 options.nohooks = True
1786 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001787 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1788 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001789
1790
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001791@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001792def CMDfetch(parser, args):
1793 """Fetches upstream commits for all modules.
1794
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001795 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1796 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001797 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001798 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001799 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1800
1801
1802def CMDgrep(parser, args):
1803 """Greps through git repos managed by gclient.
1804
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001805 Runs 'git grep [args...]' for each module.
1806 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001807 # We can't use optparse because it will try to parse arguments sent
1808 # to git grep and throw an error. :-(
1809 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001810 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001811 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1812 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1813 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1814 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001815 ' end of your query.',
1816 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001817 return 1
1818
1819 jobs_arg = ['--jobs=1']
1820 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1821 jobs_arg, args = args[:1], args[1:]
1822 elif re.match(r'(-j|--jobs)$', args[0]):
1823 jobs_arg, args = args[:2], args[2:]
1824
1825 return CMDrecurse(
1826 parser,
1827 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1828 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001829
1830
stip@chromium.orga735da22015-04-29 23:18:20 +00001831def CMDroot(parser, args):
1832 """Outputs the solution root (or current dir if there isn't one)."""
1833 (options, args) = parser.parse_args(args)
1834 client = GClient.LoadCurrentConfig(options)
1835 if client:
1836 print(os.path.abspath(client.root_dir))
1837 else:
1838 print(os.path.abspath('.'))
1839
1840
agable99a7f802016-06-24 11:19:44 -07001841@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001842def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001843 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001844
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001845 This specifies the configuration for further commands. After update/sync,
1846 top-level DEPS files in each module are read to determine dependent
1847 modules to operate on as well. If optional [url] parameter is
1848 provided, then configuration is read from a specified Subversion server
1849 URL.
1850 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001851 # We do a little dance with the --gclientfile option. 'gclient config' is the
1852 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1853 # arguments. So, we temporarily stash any --gclientfile parameter into
1854 # options.output_config_file until after the (gclientfile xor spec) error
1855 # check.
1856 parser.remove_option('--gclientfile')
1857 parser.add_option('--gclientfile', dest='output_config_file',
1858 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001859 parser.add_option('--name',
1860 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001861 parser.add_option('--deps-file', default='DEPS',
1862 help='overrides the default name for the DEPS file for the'
1863 'main solutions and all sub-dependencies')
agabled4aedc82016-06-28 13:03:59 -07001864 parser.add_option('--managed', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001865 help='overrides the default behavior to make it possible '
agabled4aedc82016-06-28 13:03:59 -07001866 'to have the main solution managed by gclient '
1867 '(gclient will always auto-sync managed solutions '
1868 ' rather than leaving them untouched)')
1869 parser.add_option('--unmanaged', action='store_true', default=True,
1870 help='This flag is a no-op; unmanaged is now the default.')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001871 parser.add_option('--cache-dir',
1872 help='(git only) Cache all git repos into this dir and do '
1873 'shared clones from the cache, instead of cloning '
1874 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001875 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001876 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001877 if options.output_config_file:
1878 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001879 if ((options.spec and args) or len(args) > 2 or
1880 (not options.spec and not args)):
1881 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1882
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001883 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001884 if options.spec:
1885 client.SetConfig(options.spec)
1886 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001887 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001888 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001889 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001890 if name.endswith('.git'):
1891 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001892 else:
1893 # specify an alternate relpath for the given URL.
1894 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001895 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1896 os.getcwd()):
1897 parser.error('Do not pass a relative path for --name.')
1898 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1899 parser.error('Do not include relative path components in --name.')
1900
nsylvain@google.comefc80932011-05-31 21:27:56 +00001901 deps_file = options.deps_file
agable99a7f802016-06-24 11:19:44 -07001902 client.SetDefaultConfig(name, deps_file, base_url,
agabled4aedc82016-06-28 13:03:59 -07001903 managed=options.managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001904 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001905 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001906 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001907
1908
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001909@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001910 gclient pack > patch.txt
1911 generate simple patch for configured client and dependences
1912""")
1913def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001914 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001915
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001916 Internally, runs 'svn diff'/'git diff' on each checked out module and
1917 dependencies, and performs minimal postprocessing of the output. The
1918 resulting patch is printed to stdout and can be applied to a freshly
1919 checked out tree via 'patch -p0 < patchfile'.
1920 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001921 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1922 help='override deps for the specified (comma-separated) '
1923 'platform(s); \'all\' will process all deps_os '
1924 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001925 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001926 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001927 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001928 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001929 client = GClient.LoadCurrentConfig(options)
1930 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001931 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001932 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001933 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001934 return client.RunOnDeps('pack', args)
1935
1936
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001937def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001938 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001939 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1940 help='override deps for the specified (comma-separated) '
1941 'platform(s); \'all\' will process all deps_os '
1942 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001943 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001944 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001945 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001946 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001947 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001948 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001949 return client.RunOnDeps('status', args)
1950
1951
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001952@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001953 gclient sync
1954 update files from SCM according to current configuration,
1955 *for modules which have changed since last update or sync*
1956 gclient sync --force
1957 update files from SCM according to current configuration, for
1958 all modules (useful for recovering files deleted from local copy)
1959 gclient sync --revision src@31000
1960 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001961
1962JSON output format:
1963If the --output-json option is specified, the following document structure will
1964be emitted to the provided file. 'null' entries may occur for subprojects which
1965are present in the gclient solution, but were not processed (due to custom_deps,
1966os_deps, etc.)
1967
1968{
1969 "solutions" : {
1970 "<name>": { # <name> is the posix-normalized path to the solution.
1971 "revision": [<svn rev int>|<git id hex string>|null],
1972 "scm": ["svn"|"git"|null],
1973 }
1974 }
1975}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001976""")
1977def CMDsync(parser, args):
1978 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001979 parser.add_option('-f', '--force', action='store_true',
1980 help='force update even for unchanged modules')
1981 parser.add_option('-n', '--nohooks', action='store_true',
1982 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001983 parser.add_option('-p', '--noprehooks', action='store_true',
1984 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001985 parser.add_option('-r', '--revision', action='append',
1986 dest='revisions', metavar='REV', default=[],
1987 help='Enforces revision/hash for the solutions with the '
1988 'format src@rev. The src@ part is optional and can be '
1989 'skipped. -r can be used multiple times when .gclient '
1990 'has multiple solutions configured and will work even '
agable99a7f802016-06-24 11:19:44 -07001991 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001992 parser.add_option('--with_branch_heads', action='store_true',
1993 help='Clone git "branch_heads" refspecs in addition to '
1994 'the default refspecs. This adds about 1/2GB to a '
1995 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001996 parser.add_option('--with_tags', action='store_true',
1997 help='Clone git tags in addition to the default refspecs.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001998 parser.add_option('-t', '--transitive', action='store_true',
1999 help='When a revision is specified (in the DEPS file or '
2000 'with the command-line flag), transitively update '
2001 'the dependencies to the date of the given revision. '
2002 'Only supported for SVN repositories.')
agable2697cd12016-06-28 10:23:53 -07002003 parser.add_option('-H', '--head', action='store_true',
2004 help='Begin by automatically syncing the root gclient '
2005 'solutions to HEAD of the remote repository. Similar '
2006 'to making the solution temporarily "managed".')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002007 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002008 help='Deletes from the working copy any dependencies that '
2009 'have been removed since the last sync, as long as '
2010 'there are no local modifications. When used with '
2011 '--force, such dependencies are removed even if they '
2012 'have local modifications. When used with --reset, '
2013 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002014 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002015 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002016 parser.add_option('-R', '--reset', action='store_true',
2017 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002018 parser.add_option('-M', '--merge', action='store_true',
2019 help='merge upstream changes instead of trying to '
2020 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002021 parser.add_option('-A', '--auto_rebase', action='store_true',
2022 help='Automatically rebase repositories against local '
2023 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002024 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2025 help='override deps for the specified (comma-separated) '
2026 'platform(s); \'all\' will process all deps_os '
2027 'references')
2028 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
2029 help='Skip svn up whenever possible by requesting '
2030 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002031 parser.add_option('--upstream', action='store_true',
2032 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002033 parser.add_option('--output-json',
2034 help='Output a json document to this path containing '
2035 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002036 parser.add_option('--no-history', action='store_true',
2037 help='GIT ONLY - Reduces the size/time of the checkout at '
2038 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002039 parser.add_option('--shallow', action='store_true',
2040 help='GIT ONLY - Do a shallow clone into the cache dir. '
2041 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002042 parser.add_option('--no_bootstrap', '--no-bootstrap',
2043 action='store_true',
2044 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002045 parser.add_option('--ignore_locks', action='store_true',
2046 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002047 parser.add_option('--break_repo_locks', action='store_true',
2048 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2049 'index.lock). This should only be used if you know for '
2050 'certain that this invocation of gclient is the only '
2051 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002052 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002053 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002054 'cache lock to become available. Default is %default.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002055 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002056 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002057
2058 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002059 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002060
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002061 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002062 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002063 ret = client.RunOnDeps('update', args)
2064 if options.output_json:
2065 slns = {}
2066 for d in client.subtree(True):
2067 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2068 slns[normed] = {
2069 'revision': d.got_revision,
2070 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002071 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002072 }
2073 with open(options.output_json, 'wb') as f:
2074 json.dump({'solutions': slns}, f)
2075 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002076
2077
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002078CMDupdate = CMDsync
2079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002080
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002081def CMDdiff(parser, args):
2082 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002083 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2084 help='override deps for the specified (comma-separated) '
2085 'platform(s); \'all\' will process all deps_os '
2086 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002087 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002088 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002089 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002090 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002091 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002092 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002093 return client.RunOnDeps('diff', args)
2094
2095
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002096def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002097 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002098
2099 That's the nuclear option to get back to a 'clean' state. It removes anything
2100 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002101 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2102 help='override deps for the specified (comma-separated) '
2103 'platform(s); \'all\' will process all deps_os '
2104 'references')
2105 parser.add_option('-n', '--nohooks', action='store_true',
2106 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002107 parser.add_option('-p', '--noprehooks', action='store_true',
2108 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002109 parser.add_option('--upstream', action='store_true',
2110 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002111 parser.add_option('--break_repo_locks', action='store_true',
2112 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2113 'index.lock). This should only be used if you know for '
2114 'certain that this invocation of gclient is the only '
2115 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002116 (options, args) = parser.parse_args(args)
2117 # --force is implied.
2118 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002119 options.reset = False
2120 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002121 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002122 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002123 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002124 return client.RunOnDeps('revert', args)
2125
2126
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002127def CMDrunhooks(parser, args):
2128 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002129 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2130 help='override deps for the specified (comma-separated) '
2131 'platform(s); \'all\' will process all deps_os '
2132 'references')
2133 parser.add_option('-f', '--force', action='store_true', default=True,
2134 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002135 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002136 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002137 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002138 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002139 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002140 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002141 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002142 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002143 return client.RunOnDeps('runhooks', args)
2144
2145
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002146def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002147 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002148
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002149 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002150 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002151 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
2152 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002153 commit can change.
2154 """
2155 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')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002159 parser.add_option('-a', '--actual', action='store_true',
2160 help='gets the actual checked out revisions instead of the '
2161 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002162 parser.add_option('-s', '--snapshot', action='store_true',
2163 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002164 'version of all repositories to reproduce the tree, '
2165 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002166 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002167 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002168 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002169 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002170 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002171 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002172
2173
szager@google.comb9a78d32012-03-13 18:46:21 +00002174def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002175 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002176 (options, args) = parser.parse_args(args)
2177 options.force = True
2178 client = GClient.LoadCurrentConfig(options)
2179 if not client:
2180 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2181 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002182 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002183 return 0
2184
2185
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002186def CMDverify(parser, args):
2187 """Verifies the DEPS file deps are only from allowed_hosts."""
2188 (options, args) = parser.parse_args(args)
2189 client = GClient.LoadCurrentConfig(options)
2190 if not client:
2191 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2192 client.RunOnDeps(None, [])
2193 # Look at each first-level dependency of this gclient only.
2194 for dep in client.dependencies:
2195 bad_deps = dep.findDepsFromNotAllowedHosts()
2196 if not bad_deps:
2197 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002198 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002199 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002200 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2201 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002202 sys.stdout.flush()
2203 raise gclient_utils.Error(
2204 'dependencies from disallowed hosts; check your DEPS file.')
2205 return 0
2206
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002207class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002208 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002209
2210 def __init__(self, **kwargs):
2211 optparse.OptionParser.__init__(
2212 self, version='%prog ' + __version__, **kwargs)
2213
2214 # Some arm boards have issues with parallel sync.
2215 if platform.machine().startswith('arm'):
2216 jobs = 1
2217 else:
2218 jobs = max(8, gclient_utils.NumLocalCpus())
2219 # cmp: 2013/06/19
2220 # Temporary workaround to lower bot-load on SVN server.
hinoka@google.com267f33e2014-02-28 22:02:32 +00002221 # Bypassed if a bot_update flag is detected.
2222 if (os.environ.get('CHROME_HEADLESS') == '1' and
2223 not os.path.exists('update.flag')):
xusydoc@chromium.org05028412013-07-29 13:40:10 +00002224 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002225
2226 self.add_option(
2227 '-j', '--jobs', default=jobs, type='int',
2228 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002229 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002230 self.add_option(
2231 '-v', '--verbose', action='count', default=0,
2232 help='Produces additional output for diagnostics. Can be used up to '
2233 'three times for more logging info.')
2234 self.add_option(
2235 '--gclientfile', dest='config_filename',
2236 help='Specify an alternate %s file' % self.gclientfile_default)
2237 self.add_option(
2238 '--spec',
2239 help='create a gclient file containing the provided string. Due to '
2240 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2241 self.add_option(
2242 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002243 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002244
2245 def parse_args(self, args=None, values=None):
2246 """Integrates standard options processing."""
2247 options, args = optparse.OptionParser.parse_args(self, args, values)
2248 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2249 logging.basicConfig(
2250 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002251 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002252 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002253 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002254 if (options.config_filename and
2255 options.config_filename != os.path.basename(options.config_filename)):
2256 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002257 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002258 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002259 options.entries_filename = options.config_filename + '_entries'
2260 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002261 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002262
2263 # These hacks need to die.
2264 if not hasattr(options, 'revisions'):
2265 # GClient.RunOnDeps expects it even if not applicable.
2266 options.revisions = []
maruel@chromium.org0895b752011-08-26 20:40:33 +00002267 if not hasattr(options, 'nohooks'):
2268 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002269 if not hasattr(options, 'noprehooks'):
2270 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002271 if not hasattr(options, 'deps_os'):
2272 options.deps_os = None
2273 if not hasattr(options, 'manually_grab_svn_rev'):
2274 options.manually_grab_svn_rev = None
2275 if not hasattr(options, 'force'):
2276 options.force = None
2277 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002278
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002279
2280def disable_buffering():
2281 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2282 # operations. Python as a strong tendency to buffer sys.stdout.
2283 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2284 # Make stdout annotated with the thread ids.
2285 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002286
2287
sbc@chromium.org013731e2015-02-26 18:28:43 +00002288def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002289 """Doesn't parse the arguments here, just find the right subcommand to
2290 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002291 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002292 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002293 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002294 sys.version.split(' ', 1)[0],
2295 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002296 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002297 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002298 print(
2299 '\nPython cannot find the location of it\'s own executable.\n',
2300 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002301 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002302 fix_encoding.fix_encoding()
2303 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002304 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002305 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002306 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002307 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002308 except KeyboardInterrupt:
2309 gclient_utils.GClientChildren.KillAllRemainingChildren()
2310 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002311 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002312 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002313 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002314 finally:
2315 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002316 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002317
2318
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002319if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002320 try:
2321 sys.exit(main(sys.argv[1:]))
2322 except KeyboardInterrupt:
2323 sys.stderr.write('interrupted\n')
2324 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002325
2326# vim: ts=2:sw=2:tw=80:et: