blob: 779ce51ac16bf28aa302ba7a3405bf13b79947d1 [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
agabled437d762016-10-17 09:35:11 -07006"""Meta checkout dependency manager for Git."""
maruel@chromium.org39c0b222013-08-17 16:57:01 +00007# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
Scott Grahamc4826742017-05-11 16:59:23 -070067# processing the deps_os/hooks_os dict of a DEPS file.
maruel@chromium.org39c0b222013-08-17 16:57:01 +000068#
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
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020099import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000100import gclient_scm
101import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000102import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000103from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000104import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000105import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000106import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000108CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
109
110
111def ast_dict_index(dnode, key):
112 """Search an ast.Dict for the argument key, and return its index."""
113 idx = [i for i in range(len(dnode.keys)) if (
114 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
115 if not idx:
116 return -1
117 elif len(idx) > 1:
118 raise gclient_utils.Error('Multiple dict entries with same key in AST')
119 return idx[-1]
120
121def ast2str(node, indent=0):
122 """Return a pretty-printed rendition of an ast.Node."""
123 t = type(node)
124 if t is ast.Module:
125 return '\n'.join([ast2str(x, indent) for x in node.body])
126 elif t is ast.Assign:
127 return ((' ' * indent) +
128 ' = '.join([ast2str(x) for x in node.targets] +
129 [ast2str(node.value, indent)]) + '\n')
130 elif t is ast.Name:
131 return node.id
132 elif t is ast.List:
133 if not node.elts:
134 return '[]'
135 elif len(node.elts) == 1:
136 return '[' + ast2str(node.elts[0], indent) + ']'
137 return ('[\n' + (' ' * (indent + 1)) +
138 (',\n' + (' ' * (indent + 1))).join(
139 [ast2str(x, indent + 1) for x in node.elts]) +
140 '\n' + (' ' * indent) + ']')
141 elif t is ast.Dict:
142 if not node.keys:
143 return '{}'
144 elif len(node.keys) == 1:
145 return '{%s: %s}' % (ast2str(node.keys[0]),
146 ast2str(node.values[0], indent + 1))
147 return ('{\n' + (' ' * (indent + 1)) +
148 (',\n' + (' ' * (indent + 1))).join(
149 ['%s: %s' % (ast2str(node.keys[i]),
150 ast2str(node.values[i], indent + 1))
151 for i in range(len(node.keys))]) +
152 '\n' + (' ' * indent) + '}')
153 elif t is ast.Str:
154 return "'%s'" % node.s
155 else:
156 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
157 % (node.lineno, node.col_offset, t))
158
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000159
maruel@chromium.org116704f2010-06-11 17:34:38 +0000160class GClientKeywords(object):
Andrii Shyshkalovb4f9d902017-05-23 08:49:41 +0000161 class FromImpl(object):
162 """Used to implement the From() syntax."""
163
164 def __init__(self, module_name, sub_target_name=None):
165 """module_name is the dep module we want to include from. It can also be
166 the name of a subdirectory to include from.
167
168 sub_target_name is an optional parameter if the module name in the other
169 DEPS file is different. E.g., you might want to map src/net to net."""
170 self.module_name = module_name
171 self.sub_target_name = sub_target_name
172
173 def __str__(self):
174 return 'From(%s, %s)' % (repr(self.module_name),
175 repr(self.sub_target_name))
176
maruel@chromium.org116704f2010-06-11 17:34:38 +0000177 class VarImpl(object):
178 def __init__(self, custom_vars, local_scope):
179 self._custom_vars = custom_vars
180 self._local_scope = local_scope
181
182 def Lookup(self, var_name):
183 """Implements the Var syntax."""
184 if var_name in self._custom_vars:
185 return self._custom_vars[var_name]
186 elif var_name in self._local_scope.get("vars", {}):
187 return self._local_scope["vars"][var_name]
188 raise gclient_utils.Error("Var is not defined: %s" % var_name)
189
190
maruel@chromium.org064186c2011-09-27 23:53:33 +0000191class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000192 """Immutable configuration settings."""
193 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800194 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700195 custom_hooks, deps_file, should_process, relative):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000196 GClientKeywords.__init__(self)
197
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000198 # These are not mutable:
199 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000200 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000201 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 # 'managed' determines whether or not this dependency is synced/updated by
203 # gclient after gclient checks it out initially. The difference between
204 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700205 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000206 # 'should_process' is dynamically set by gclient if it goes over its
207 # recursion limit and controls gclient's behavior so it does not misbehave.
208 self._managed = managed
209 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700210 # If this is a recursed-upon sub-dependency, and the parent has
211 # use_relative_paths set, then this dependency should check out its own
212 # dependencies relative to that parent's path for this, rather than
213 # relative to the .gclient file.
214 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000215 # This is a mutable value which has the list of 'target_os' OSes listed in
216 # the current deps file.
217 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218
219 # These are only set in .gclient and not in DEPS files.
220 self._custom_vars = custom_vars or {}
221 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000222 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000223
maruel@chromium.org064186c2011-09-27 23:53:33 +0000224 # Post process the url to remove trailing slashes.
225 if isinstance(self._url, basestring):
226 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
227 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000228 self._url = self._url.replace('/@', '@')
Andrii Shyshkalovb4f9d902017-05-23 08:49:41 +0000229 elif not isinstance(self._url, (self.FromImpl, None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000230 raise gclient_utils.Error(
Andrii Shyshkalovb4f9d902017-05-23 08:49:41 +0000231 ('dependency url must be either a string, None, '
232 'or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000233 # Make any deps_file path platform-appropriate.
234 for sep in ['/', '\\']:
235 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000236
237 @property
238 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 return self._deps_file
240
241 @property
242 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 return self._managed
244
245 @property
246 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 return self._parent
248
249 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000250 def root(self):
251 """Returns the root node, a GClient object."""
252 if not self.parent:
253 # This line is to signal pylint that it could be a GClient instance.
254 return self or GClient(None, None)
255 return self.parent.root
256
257 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000258 def should_process(self):
259 """True if this dependency should be processed, i.e. checked out."""
260 return self._should_process
261
262 @property
263 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000264 return self._custom_vars.copy()
265
266 @property
267 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000268 return self._custom_deps.copy()
269
maruel@chromium.org064186c2011-09-27 23:53:33 +0000270 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000271 def custom_hooks(self):
272 return self._custom_hooks[:]
273
274 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000275 def url(self):
276 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000278 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000279 def target_os(self):
280 if self.local_target_os is not None:
281 return tuple(set(self.local_target_os).union(self.parent.target_os))
282 else:
283 return self.parent.target_os
284
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000285 def get_custom_deps(self, name, url):
286 """Returns a custom deps if applicable."""
287 if self.parent:
288 url = self.parent.get_custom_deps(name, url)
289 # None is a valid return value to disable a dependency.
290 return self.custom_deps.get(name, url)
291
maruel@chromium.org064186c2011-09-27 23:53:33 +0000292
293class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000294 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000295
agablea98a6cd2016-11-15 14:30:10 -0800296 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700297 custom_vars, custom_hooks, deps_file, should_process,
298 relative):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000299 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000300 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800301 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700302 custom_hooks, deps_file, should_process, relative)
maruel@chromium.org68988972011-09-20 14:11:42 +0000303
304 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000305 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000306
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000307 self._pre_deps_hooks = []
308
maruel@chromium.org68988972011-09-20 14:11:42 +0000309 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000310 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000311 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 # A cache of the files affected by the current operation, necessary for
313 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000314 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000315 # List of host names from which dependencies are allowed.
316 # Default is an empty set, meaning unspecified in DEPS file, and hence all
317 # hosts will be allowed. Non-empty set means whitelist of hosts.
318 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
319 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000320 # If it is not set to True, the dependency wasn't processed for its child
321 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000322 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000323 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000324 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000325 # This dependency had its pre-DEPS hooks run
326 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000327 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000328 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000329 # This is the scm used to checkout self.url. It may be used by dependencies
330 # to get the datetime of the revision we checked out.
331 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000332 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000333 # The actual revision we ended up getting, or None if that information is
334 # unavailable
335 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000336
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000337 # This is a mutable value that overrides the normal recursion limit for this
338 # dependency. It is read from the actual DEPS file so cannot be set on
339 # class instantiation.
340 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000341 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000342 # 'no recursion' setting on a dep-by-dep basis. It will replace
343 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000344 #
345 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
346 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000347 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700348 # This is inherited from WorkItem. We want the URL to be a resource.
349 if url and isinstance(url, basestring):
350 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700351 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700352 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000353
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000354 if not self.name and self.parent:
355 raise gclient_utils.Error('Dependency without name')
356
maruel@chromium.org470b5432011-10-11 18:18:19 +0000357 @property
358 def requirements(self):
359 """Calculate the list of requirements."""
360 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000361 # self.parent is implicitly a requirement. This will be recursive by
362 # definition.
363 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000364 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000365
366 # For a tree with at least 2 levels*, the leaf node needs to depend
367 # on the level higher up in an orderly way.
368 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
369 # thus unsorted, while the .gclient format is a list thus sorted.
370 #
371 # * _recursion_limit is hard coded 2 and there is no hope to change this
372 # value.
373 #
374 # Interestingly enough, the following condition only works in the case we
375 # want: self is a 2nd level node. 3nd level node wouldn't need this since
376 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000377 if self.parent and self.parent.parent and not self.parent.parent.parent:
378 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000379
Andrii Shyshkalovb4f9d902017-05-23 08:49:41 +0000380 if isinstance(self.url, self.FromImpl):
381 requirements.add(self.url.module_name)
382
maruel@chromium.org470b5432011-10-11 18:18:19 +0000383 if self.name:
384 requirements |= set(
385 obj.name for obj in self.root.subtree(False)
386 if (obj is not self
387 and obj.name and
388 self.name.startswith(posixpath.join(obj.name, ''))))
389 requirements = tuple(sorted(requirements))
390 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
391 return requirements
392
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000393 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000394 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000395 """Returns False if recursion_override is ever specified."""
396 if self.recursion_override is not None:
397 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000398 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000399
400 @property
401 def recursion_limit(self):
402 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000403 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000404 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000405 if self.try_recursedeps and self.parent.recursedeps != None:
406 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000407 return 1
408
409 if self.recursion_override is not None:
410 return self.recursion_override
411 return max(self.parent.recursion_limit - 1, 0)
412
maruel@chromium.org470b5432011-10-11 18:18:19 +0000413 def verify_validity(self):
414 """Verifies that this Dependency is fine to add as a child of another one.
415
416 Returns True if this entry should be added, False if it is a duplicate of
417 another entry.
418 """
419 logging.info('Dependency(%s).verify_validity()' % self.name)
420 if self.name in [s.name for s in self.parent.dependencies]:
421 raise gclient_utils.Error(
422 'The same name "%s" appears multiple times in the deps section' %
423 self.name)
424 if not self.should_process:
425 # Return early, no need to set requirements.
426 return True
427
428 # This require a full tree traversal with locks.
429 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
430 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000431 self_url = self.LateOverride(self.url)
432 sibling_url = sibling.LateOverride(sibling.url)
433 # Allow to have only one to be None or ''.
434 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000435 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000436 ('Dependency %s specified more than once:\n'
437 ' %s [%s]\n'
438 'vs\n'
439 ' %s [%s]') % (
440 self.name,
441 sibling.hierarchy(),
442 sibling_url,
443 self.hierarchy(),
444 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000445 # In theory we could keep it as a shadow of the other one. In
446 # practice, simply ignore it.
447 logging.warn('Won\'t process duplicate dependency %s' % sibling)
448 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000449 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000450
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000451 def LateOverride(self, url):
Andrii Shyshkalovb4f9d902017-05-23 08:49:41 +0000452 """Resolves the parsed url from url.
453
454 Manages From() keyword accordingly. Do not touch self.parsed_url nor
455 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000456 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000457 parsed_url = self.get_custom_deps(self.name, url)
458 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000459 logging.info(
460 'Dependency(%s).LateOverride(%s) -> %s' %
461 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000462 return parsed_url
463
Andrii Shyshkalovb4f9d902017-05-23 08:49:41 +0000464 if isinstance(url, self.FromImpl):
465 # Requires tree traversal.
466 ref = [
467 dep for dep in self.root.subtree(True) if url.module_name == dep.name
468 ]
469 if not ref:
470 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
471 url.module_name, ref))
472 # It may happen that len(ref) > 1 but it's no big deal.
473 ref = ref[0]
474 sub_target = url.sub_target_name or self.name
475 found_deps = [d for d in ref.dependencies if d.name == sub_target]
476 if len(found_deps) != 1:
477 raise gclient_utils.Error(
478 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
479 sub_target, ref.name, self.name, self.parent.name,
480 str(self.root)))
481
482 # Call LateOverride() again.
483 found_dep = found_deps[0]
484 parsed_url = found_dep.LateOverride(found_dep.url)
485 logging.info(
486 'Dependency(%s).LateOverride(%s) -> %s (From)' %
487 (self.name, url, parsed_url))
488 return parsed_url
489
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000490 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000491 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000492 if (not parsed_url[0] and
493 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000494 # A relative url. Fetch the real base.
495 path = parsed_url[2]
496 if not path.startswith('/'):
497 raise gclient_utils.Error(
498 'relative DEPS entry \'%s\' must begin with a slash' % url)
499 # Create a scm just to query the full url.
500 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000501 scm = gclient_scm.CreateSCM(
502 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000503 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000504 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000505 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000506 logging.info(
507 'Dependency(%s).LateOverride(%s) -> %s' %
508 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000509 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000510
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000511 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000512 logging.info(
513 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000514 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000515
516 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000518 @staticmethod
519 def MergeWithOsDeps(deps, deps_os, target_os_list):
520 """Returns a new "deps" structure that is the deps sent in updated
521 with information from deps_os (the deps_os section of the DEPS
522 file) that matches the list of target os."""
523 os_overrides = {}
524 for the_target_os in target_os_list:
525 the_target_os_deps = deps_os.get(the_target_os, {})
526 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
527 overrides = os_overrides.setdefault(os_dep_key, [])
528 overrides.append((the_target_os, os_dep_value))
529
530 # If any os didn't specify a value (we have fewer value entries
531 # than in the os list), then it wants to use the default value.
532 for os_dep_key, os_dep_value in os_overrides.iteritems():
533 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700534 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000535 # set it to None or miss a conflicting DEPS.
536 if os_dep_key in deps:
537 os_dep_value.append(('default', deps[os_dep_key]))
538
539 target_os_deps = {}
540 for os_dep_key, os_dep_value in os_overrides.iteritems():
541 # os_dep_value is a list of (os, value) pairs.
542 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
543 if not possible_values:
544 target_os_deps[os_dep_key] = None
545 else:
546 if len(possible_values) > 1:
547 # It would be possible to abort here but it would be
548 # unfortunate if we end up preventing any kind of checkout.
549 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
550 os_dep_key, os_dep_value, target_os_list)
551 # Sorting to get the same result every time in case of conflicts.
552 target_os_deps[os_dep_key] = sorted(possible_values)[0]
553
554 new_deps = deps.copy()
555 new_deps.update(target_os_deps)
556 return new_deps
557
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000558 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000559 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000560 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000561 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000562
563 deps_content = None
564 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000565
566 # First try to locate the configured deps file. If it's missing, fallback
567 # to DEPS.
568 deps_files = [self.deps_file]
569 if 'DEPS' not in deps_files:
570 deps_files.append('DEPS')
571 for deps_file in deps_files:
572 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
573 if os.path.isfile(filepath):
574 logging.info(
575 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
576 filepath)
577 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000578 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000579 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
580 filepath)
581
582 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000583 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000584 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000585 use_strict = 'use strict' in deps_content.splitlines()[0]
586
587 local_scope = {}
588 if deps_content:
589 # One thing is unintuitive, vars = {} must happen before Var() use.
590 var = self.VarImpl(self.custom_vars, local_scope)
591 if use_strict:
592 logging.info(
593 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
594 global_scope = {
595 '__builtins__': {'None': None},
596 'Var': var.Lookup,
597 'deps_os': {},
598 }
599 else:
600 global_scope = {
Andrii Shyshkalovb4f9d902017-05-23 08:49:41 +0000601 'From': self.FromImpl,
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000602 'Var': var.Lookup,
603 'deps_os': {},
604 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000605 # Eval the content.
606 try:
607 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000608 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000609 gclient_utils.SyntaxErrorToError(filepath, e)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200610 if self._get_option('validate_syntax', False):
611 gclient_eval.Check(deps_content, filepath, global_scope, local_scope)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000612 if use_strict:
613 for key, val in local_scope.iteritems():
614 if not isinstance(val, (dict, list, tuple, str)):
615 raise gclient_utils.Error(
616 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
617 (self.name, key, val))
618
maruel@chromium.org271375b2010-06-23 19:17:38 +0000619 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000620 if 'recursion' in local_scope:
621 self.recursion_override = local_scope.get('recursion')
622 logging.warning(
623 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000624 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000625 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000626 self.recursedeps = {}
627 for ent in local_scope['recursedeps']:
628 if isinstance(ent, basestring):
629 self.recursedeps[ent] = {"deps_file": self.deps_file}
630 else: # (depname, depsfilename)
631 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000632 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000633 # If present, save 'target_os' in the local_target_os property.
634 if 'target_os' in local_scope:
635 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000636 # load os specific dependencies if defined. these dependencies may
637 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000638 target_os_list = self.target_os
639 if 'deps_os' in local_scope and target_os_list:
640 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000641
maruel@chromium.org271375b2010-06-23 19:17:38 +0000642 # If a line is in custom_deps, but not in the solution, we want to append
643 # this line to the solution.
644 for d in self.custom_deps:
645 if d not in deps:
646 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000647
648 # If use_relative_paths is set in the DEPS file, regenerate
649 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000650 # the DEPS file. Also update recursedeps if use_relative_paths is
651 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700652 # If the deps file doesn't set use_relative_paths, but the parent did
653 # (and therefore set self.relative on this Dependency object), then we
654 # want to modify the deps and recursedeps by prepending the parent
655 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000656 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700657 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000658 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700659 rel_prefix = self.name
660 elif self._relative:
661 rel_prefix = os.path.dirname(self.name)
662 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000663 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000664 rel_deps = {}
665 for d, url in deps.items():
666 # normpath is required to allow DEPS to use .. in their
667 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700668 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
669 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000670 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000671
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000672 # Update recursedeps if it's set.
673 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700674 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000675 rel_deps = {}
676 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700677 rel_deps[
678 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000679 self.recursedeps = rel_deps
680
agabledce6ddc2016-09-08 10:02:16 -0700681
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000682 if 'allowed_hosts' in local_scope:
683 try:
684 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
685 except TypeError: # raised if non-iterable
686 pass
687 if not self._allowed_hosts:
688 logging.warning("allowed_hosts is specified but empty %s",
689 self._allowed_hosts)
690 raise gclient_utils.Error(
691 'ParseDepsFile(%s): allowed_hosts must be absent '
692 'or a non-empty iterable' % self.name)
693
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000694 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000695 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000696 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000697 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000698 deps_file = self.deps_file
699 if self.recursedeps is not None:
700 ent = self.recursedeps.get(name)
701 if ent is not None:
702 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000703 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800704 self, name, url, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700705 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000706 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000707
708 # override named sets of hooks by the custom hooks
709 hooks_to_run = []
710 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
711 for hook in local_scope.get('hooks', []):
712 if hook.get('name', '') not in hook_names_to_suppress:
713 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700714 if 'hooks_os' in local_scope and target_os_list:
715 hooks_os = local_scope['hooks_os']
716 # Specifically append these to ensure that hooks_os run after hooks.
717 for the_target_os in target_os_list:
718 the_target_os_hooks = hooks_os.get(the_target_os, [])
719 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000720
721 # add the replacements and any additions
722 for hook in self.custom_hooks:
723 if 'action' in hook:
724 hooks_to_run.append(hook)
725
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800726 if self.recursion_limit:
Andrii Shyshkalov00492332017-05-23 08:50:22 +0000727 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800728 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000729
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000730 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000731 logging.info('ParseDepsFile(%s) done' % self.name)
732
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200733 def _get_option(self, attr, default):
734 obj = self
735 while not hasattr(obj, '_options'):
736 obj = obj.parent
737 return getattr(obj._options, attr, default)
738
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000739 def add_dependencies_and_close(self, deps_to_add, hooks):
740 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000741 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000742 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000743 self.add_dependency(dep)
744 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000745
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000746 def findDepsFromNotAllowedHosts(self):
747 """Returns a list of depenecies from not allowed hosts.
748
749 If allowed_hosts is not set, allows all hosts and returns empty list.
750 """
751 if not self._allowed_hosts:
752 return []
753 bad_deps = []
754 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000755 # Don't enforce this for custom_deps.
756 if dep.name in self._custom_deps:
757 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000758 if isinstance(dep.url, basestring):
759 parsed_url = urlparse.urlparse(dep.url)
760 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
761 bad_deps.append(dep)
762 return bad_deps
763
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000764 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800765 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000766 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000767 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000768 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000769 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000770 if not self.should_process:
771 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000772 # When running runhooks, there's no need to consult the SCM.
773 # All known hooks are expected to run unconditionally regardless of working
774 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200775 run_scm = command not in (
776 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000777 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000778 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000779 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000780 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700781 # Create a shallow copy to mutate revision.
782 options = copy.copy(options)
783 options.revision = revision_override
784 self._used_revision = options.revision
785 self._used_scm = gclient_scm.CreateSCM(
786 parsed_url, self.root.root_dir, self.name, self.outbuf,
787 out_cb=work_queue.out_cb)
788 self._got_revision = self._used_scm.RunCommand(command, options, args,
789 file_list)
790 if file_list:
791 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000792
793 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
794 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000795 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000796 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000797 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000798 continue
799 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000800 [self.root.root_dir.lower(), file_list[i].lower()])
801 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000802 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000803 while file_list[i].startswith(('\\', '/')):
804 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000805
806 # Always parse the DEPS file.
807 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000808 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000809 if command in ('update', 'revert') and not options.noprehooks:
810 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000811
812 if self.recursion_limit:
813 # Parse the dependencies of this dependency.
814 for s in self.dependencies:
815 work_queue.enqueue(s)
816
817 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700818 # Skip file only checkout.
819 scm = gclient_scm.GetScmName(parsed_url)
820 if not options.scm or scm in options.scm:
821 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
822 # Pass in the SCM type as an env variable. Make sure we don't put
823 # unicode strings in the environment.
824 env = os.environ.copy()
825 if scm:
826 env['GCLIENT_SCM'] = str(scm)
827 if parsed_url:
828 env['GCLIENT_URL'] = str(parsed_url)
829 env['GCLIENT_DEP_PATH'] = str(self.name)
830 if options.prepend_dir and scm == 'git':
831 print_stdout = False
832 def filter_fn(line):
833 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000834
agabled437d762016-10-17 09:35:11 -0700835 def mod_path(git_pathspec):
836 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
837 modified_path = os.path.join(self.name, match.group(2))
838 branch = match.group(1) or ''
839 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000840
agabled437d762016-10-17 09:35:11 -0700841 match = re.match('^Binary file ([^\0]+) matches$', line)
842 if match:
843 print('Binary file %s matches\n' % mod_path(match.group(1)))
844 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000845
agabled437d762016-10-17 09:35:11 -0700846 items = line.split('\0')
847 if len(items) == 2 and items[1]:
848 print('%s : %s' % (mod_path(items[0]), items[1]))
849 elif len(items) >= 2:
850 # Multiple null bytes or a single trailing null byte indicate
851 # git is likely displaying filenames only (such as with -l)
852 print('\n'.join(mod_path(path) for path in items if path))
853 else:
854 print(line)
855 else:
856 print_stdout = True
857 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000858
agabled437d762016-10-17 09:35:11 -0700859 if parsed_url is None:
860 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
861 elif os.path.isdir(cwd):
862 try:
863 gclient_utils.CheckCallAndFilter(
864 args, cwd=cwd, env=env, print_stdout=print_stdout,
865 filter_fn=filter_fn,
866 )
867 except subprocess2.CalledProcessError:
868 if not options.ignore:
869 raise
870 else:
871 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000872
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000873
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000874 @gclient_utils.lockedmethod
875 def _run_is_done(self, file_list, parsed_url):
876 # Both these are kept for hooks that are run as a separate tree traversal.
877 self._file_list = file_list
878 self._parsed_url = parsed_url
879 self._processed = True
880
szager@google.comb9a78d32012-03-13 18:46:21 +0000881 @staticmethod
Andrii Shyshkalov00492332017-05-23 08:50:22 +0000882 def GetHookAction(hook_dict, matching_file_list):
szager@google.comb9a78d32012-03-13 18:46:21 +0000883 """Turns a parsed 'hook' dict into an executable command."""
884 logging.debug(hook_dict)
Andrii Shyshkalov00492332017-05-23 08:50:22 +0000885 logging.debug(matching_file_list)
szager@google.comb9a78d32012-03-13 18:46:21 +0000886 command = hook_dict['action'][:]
887 if command[0] == 'python':
888 # If the hook specified "python" as the first item, the action is a
889 # Python script. Run it by starting a new copy of the same
890 # interpreter.
891 command[0] = sys.executable
Andrii Shyshkalov00492332017-05-23 08:50:22 +0000892 if '$matching_files' in command:
893 splice_index = command.index('$matching_files')
894 command[splice_index:splice_index + 1] = matching_file_list
szager@google.comb9a78d32012-03-13 18:46:21 +0000895 return command
896
897 def GetHooks(self, options):
898 """Evaluates all hooks, and return them in a flat list.
899
900 RunOnDeps() must have been called before to load the DEPS.
901 """
902 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000903 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000904 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000905 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000906 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000907 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000908 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700909 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000910 # what files have changed so we always run all hooks. It'd be nice to fix
911 # that.
912 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000913 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000914 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000915 for hook_dict in self.deps_hooks:
Andrii Shyshkalov00492332017-05-23 08:50:22 +0000916 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000917 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000918 # Run hooks on the basis of whether the files from the gclient operation
919 # match each hook's pattern.
920 for hook_dict in self.deps_hooks:
921 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000922 matching_file_list = [
923 f for f in self.file_list_and_children if pattern.search(f)
924 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000925 if matching_file_list:
Andrii Shyshkalov00492332017-05-23 08:50:22 +0000926 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000927 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000928 result.extend(s.GetHooks(options))
929 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930
szager@google.comb9a78d32012-03-13 18:46:21 +0000931 def RunHooksRecursively(self, options):
932 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000933 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000934 for hook in self.GetHooks(options):
935 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000936 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000937 gclient_utils.CheckCallAndFilterAndHeader(
938 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000939 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000940 # Use a discrete exit status code of 2 to indicate that a hook action
941 # failed. Users of this script may wish to treat hook action failures
942 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000943 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000944 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000945 finally:
946 elapsed_time = time.time() - start_time
947 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000948 print("Hook '%s' took %.2f secs" % (
949 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000950
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000951 def RunPreDepsHooks(self):
952 assert self.processed
953 assert self.deps_parsed
954 assert not self.pre_deps_hooks_ran
955 assert not self.hooks_ran
956 for s in self.dependencies:
957 assert not s.processed
958 self._pre_deps_hooks_ran = True
959 for hook in self.pre_deps_hooks:
960 try:
961 start_time = time.time()
962 gclient_utils.CheckCallAndFilterAndHeader(
963 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000964 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000965 # Use a discrete exit status code of 2 to indicate that a hook action
966 # failed. Users of this script may wish to treat hook action failures
967 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000968 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000969 sys.exit(2)
970 finally:
971 elapsed_time = time.time() - start_time
972 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000973 print("Hook '%s' took %.2f secs" % (
974 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000975
976
maruel@chromium.org0d812442010-08-10 12:41:08 +0000977 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000978 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000979 dependencies = self.dependencies
980 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000981 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000982 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000983 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000984 for i in d.subtree(include_all):
985 yield i
986
987 def depth_first_tree(self):
988 """Depth-first recursion including the root node."""
989 yield self
990 for i in self.dependencies:
991 for j in i.depth_first_tree():
992 if j.should_process:
993 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000994
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000995 @gclient_utils.lockedmethod
996 def add_dependency(self, new_dep):
997 self._dependencies.append(new_dep)
998
999 @gclient_utils.lockedmethod
1000 def _mark_as_parsed(self, new_hooks):
1001 self._deps_hooks.extend(new_hooks)
1002 self._deps_parsed = True
1003
maruel@chromium.org68988972011-09-20 14:11:42 +00001004 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001005 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001006 def dependencies(self):
1007 return tuple(self._dependencies)
1008
1009 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001010 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001011 def deps_hooks(self):
1012 return tuple(self._deps_hooks)
1013
1014 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001015 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001016 def pre_deps_hooks(self):
1017 return tuple(self._pre_deps_hooks)
1018
1019 @property
1020 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001021 def parsed_url(self):
1022 return self._parsed_url
1023
1024 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001025 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001026 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001027 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001028 return self._deps_parsed
1029
1030 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001031 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001032 def processed(self):
1033 return self._processed
1034
1035 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001036 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001037 def pre_deps_hooks_ran(self):
1038 return self._pre_deps_hooks_ran
1039
1040 @property
1041 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001042 def hooks_ran(self):
1043 return self._hooks_ran
1044
1045 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001046 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001047 def allowed_hosts(self):
1048 return self._allowed_hosts
1049
1050 @property
1051 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001052 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001053 return tuple(self._file_list)
1054
1055 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001056 def used_scm(self):
1057 """SCMWrapper instance for this dependency or None if not processed yet."""
1058 return self._used_scm
1059
1060 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001061 @gclient_utils.lockedmethod
1062 def got_revision(self):
1063 return self._got_revision
1064
1065 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001066 def file_list_and_children(self):
1067 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001068 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001069 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001070 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001071
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001072 def __str__(self):
1073 out = []
agablea98a6cd2016-11-15 14:30:10 -08001074 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001075 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001076 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1077 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001078 # First try the native property if it exists.
1079 if hasattr(self, '_' + i):
1080 value = getattr(self, '_' + i, False)
1081 else:
1082 value = getattr(self, i, False)
1083 if value:
1084 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001085
1086 for d in self.dependencies:
1087 out.extend([' ' + x for x in str(d).splitlines()])
1088 out.append('')
1089 return '\n'.join(out)
1090
1091 def __repr__(self):
1092 return '%s: %s' % (self.name, self.url)
1093
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001094 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001095 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001096 def format_name(d):
1097 if include_url:
1098 return '%s(%s)' % (d.name, d.url)
1099 return d.name
1100 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001101 i = self.parent
1102 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001103 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001104 i = i.parent
1105 return out
1106
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001107
1108class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001109 """Object that represent a gclient checkout. A tree of Dependency(), one per
1110 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001111
1112 DEPS_OS_CHOICES = {
1113 "win32": "win",
1114 "win": "win",
1115 "cygwin": "win",
1116 "darwin": "mac",
1117 "mac": "mac",
1118 "unix": "unix",
1119 "linux": "unix",
1120 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001121 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001122 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001123 }
1124
1125 DEFAULT_CLIENT_FILE_TEXT = ("""\
1126solutions = [
smutae7ea312016-07-18 11:59:41 -07001127 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001128 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001129 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001130 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001131 "custom_deps" : {
1132 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001133 },
1134]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001135cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001136""")
1137
1138 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001139 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001140 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001141 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001142 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001143 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001144%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001145 },
1146""")
1147
1148 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1149# Snapshot generated with gclient revinfo --snapshot
1150solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001151%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001152""")
1153
1154 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001155 # Do not change previous behavior. Only solution level and immediate DEPS
1156 # are processed.
1157 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001158 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001159 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001160 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001161 if options.deps_os:
1162 enforced_os = options.deps_os.split(',')
1163 else:
1164 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1165 if 'all' in enforced_os:
1166 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001167 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001168 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001169 self.config_content = None
1170
borenet@google.com88d10082014-03-21 17:24:48 +00001171 def _CheckConfig(self):
1172 """Verify that the config matches the state of the existing checked-out
1173 solutions."""
1174 for dep in self.dependencies:
1175 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001176 scm = gclient_scm.CreateSCM(
1177 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001178 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001179 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001180 mirror = scm.GetCacheMirror()
1181 if mirror:
1182 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1183 mirror.exists())
1184 else:
1185 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001186 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001187Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001188is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001189
borenet@google.com97882362014-04-07 20:06:02 +00001190The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001191URL: %(expected_url)s (%(expected_scm)s)
1192Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001193
1194The local checkout in %(checkout_path)s reports:
1195%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001196
1197You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001198it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001199''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1200 'expected_url': dep.url,
1201 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001202 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001203 'actual_url': actual_url,
1204 'actual_scm': gclient_scm.GetScmName(actual_url)})
1205
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001206 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001207 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001208 config_dict = {}
1209 self.config_content = content
1210 try:
1211 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001212 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001213 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001214
peter@chromium.org1efccc82012-04-27 16:34:38 +00001215 # Append any target OS that is not already being enforced to the tuple.
1216 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001217 if config_dict.get('target_os_only', False):
1218 self._enforced_os = tuple(set(target_os))
1219 else:
1220 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1221
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001222 cache_dir = config_dict.get('cache_dir')
1223 if cache_dir:
1224 cache_dir = os.path.join(self.root_dir, cache_dir)
1225 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001226 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001227 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001228 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1229 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001230 gclient_scm.GitWrapper.cache_dir = cache_dir
1231 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001232
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001233 if not target_os and config_dict.get('target_os_only', False):
1234 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1235 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001236
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001237 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001238 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001239 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001240 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001241 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001242 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001243 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001244 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001245 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001246 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001247 True,
1248 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001249 except KeyError:
1250 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1251 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001252 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1253 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001254
1255 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001256 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001257 self._options.config_filename),
1258 self.config_content)
1259
1260 @staticmethod
1261 def LoadCurrentConfig(options):
1262 """Searches for and loads a .gclient file relative to the current working
1263 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001264 if options.spec:
1265 client = GClient('.', options)
1266 client.SetConfig(options.spec)
1267 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001268 if options.verbose:
1269 print('Looking for %s starting from %s\n' % (
1270 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001271 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1272 if not path:
1273 return None
1274 client = GClient(path, options)
1275 client.SetConfig(gclient_utils.FileRead(
1276 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001277
1278 if (options.revisions and
1279 len(client.dependencies) > 1 and
1280 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001281 print(
1282 ('You must specify the full solution name like --revision %s@%s\n'
1283 'when you have multiple solutions setup in your .gclient file.\n'
1284 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001285 client.dependencies[0].name,
1286 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001287 ', '.join(s.name for s in client.dependencies[1:])),
1288 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001289 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001290
nsylvain@google.comefc80932011-05-31 21:27:56 +00001291 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001292 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001293 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1294 'solution_name': solution_name,
1295 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001296 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001297 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001298 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001299 })
1300
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001301 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001302 """Creates a .gclient_entries file to record the list of unique checkouts.
1303
1304 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001305 """
1306 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1307 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001308 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001309 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001310 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1311 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001312 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001313 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001314 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001315 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001316
1317 def _ReadEntries(self):
1318 """Read the .gclient_entries file for the given client.
1319
1320 Returns:
1321 A sequence of solution names, which will be empty if there is the
1322 entries file hasn't been created yet.
1323 """
1324 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001325 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001326 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001327 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001328 try:
1329 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001330 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001331 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001332 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001333
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001334 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001335 """Checks for revision overrides."""
1336 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001337 if self._options.head:
1338 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001339 if not self._options.revisions:
1340 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001341 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001342 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001343 if not self._options.revisions:
1344 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001345 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001346 index = 0
1347 for revision in self._options.revisions:
1348 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001349 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001350 revision = '%s@%s' % (solutions_names[index], revision)
1351 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001352 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001353 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001354 return revision_overrides
1355
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001356 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001357 """Runs a command on each dependency in a client and its dependencies.
1358
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001359 Args:
1360 command: The command to use (e.g., 'status' or 'diff')
1361 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001362 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001363 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001364 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001365
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001366 revision_overrides = {}
1367 # It's unnecessary to check for revision overrides for 'recurse'.
1368 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001369 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1370 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001371 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001372 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001373 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001374 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001375 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001376 if command in ('update', 'revert'):
1377 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001378 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001379 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001380 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001381 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1382 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001383 for s in self.dependencies:
1384 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001385 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001386 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001387 print('Please fix your script, having invalid --revision flags will soon '
1388 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001389
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001390 # Once all the dependencies have been processed, it's now safe to run the
1391 # hooks.
1392 if not self._options.nohooks:
1393 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001394
1395 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001396 # Notify the user if there is an orphaned entry in their working copy.
1397 # Only delete the directory if there are no changes in it, and
1398 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001399 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001400 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1401 for e in entries]
1402
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001403 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001404 if not prev_url:
1405 # entry must have been overridden via .gclient custom_deps
1406 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001407 # Fix path separator on Windows.
1408 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001409 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001410 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001411 if (entry not in entries and
1412 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001413 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001414 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001415 scm = gclient_scm.CreateSCM(
1416 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001417
1418 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001419 scm_root = None
agabled437d762016-10-17 09:35:11 -07001420 try:
1421 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1422 except subprocess2.CalledProcessError:
1423 pass
1424 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001425 logging.warning('Could not find checkout root for %s. Unable to '
1426 'determine whether it is part of a higher-level '
1427 'checkout, so not removing.' % entry)
1428 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001429
1430 # This is to handle the case of third_party/WebKit migrating from
1431 # being a DEPS entry to being part of the main project.
1432 # If the subproject is a Git project, we need to remove its .git
1433 # folder. Otherwise git operations on that folder will have different
1434 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001435 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001436 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001437 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1438 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001439 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1440 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001441 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1442 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001443 save_dir = scm.GetGitBackupDirPath()
1444 # Remove any eventual stale backup dir for the same project.
1445 if os.path.exists(save_dir):
1446 gclient_utils.rmtree(save_dir)
1447 os.rename(os.path.join(e_dir, '.git'), save_dir)
1448 # When switching between the two states (entry/ is a subproject
1449 # -> entry/ is part of the outer project), it is very likely
1450 # that some files are changed in the checkout, unless we are
1451 # jumping *exactly* across the commit which changed just DEPS.
1452 # In such case we want to cleanup any eventual stale files
1453 # (coming from the old subproject) in order to end up with a
1454 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001455 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001456 assert not os.path.exists(os.path.join(e_dir, '.git'))
1457 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1458 'level checkout. The git folder containing all the local'
1459 ' branches has been saved to %s.\n'
1460 'If you don\'t care about its state you can safely '
1461 'remove that folder to free up space.') %
1462 (entry, save_dir))
1463 continue
1464
borenet@google.com359bb642014-05-13 17:28:19 +00001465 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001466 logging.info('%s is part of a higher level checkout, not removing',
1467 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001468 continue
1469
1470 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001471 scm.status(self._options, [], file_list)
1472 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001473 if (not self._options.delete_unversioned_trees or
1474 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001475 # There are modified files in this entry. Keep warning until
1476 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001477 print(('\nWARNING: \'%s\' is no longer part of this client. '
1478 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001479 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001480 else:
1481 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001482 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001483 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001484 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001485 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001486 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001487 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001488
1489 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001490 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001491 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001492 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001493 work_queue = gclient_utils.ExecutionQueue(
1494 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001495 for s in self.dependencies:
1496 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001497 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001498
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001499 def GetURLAndRev(dep):
1500 """Returns the revision-qualified SCM url for a Dependency."""
1501 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001502 return None
agabled437d762016-10-17 09:35:11 -07001503 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001504 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001505 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001506 if not os.path.isdir(scm.checkout_path):
1507 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001508 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001509
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001510 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001511 new_gclient = ''
1512 # First level at .gclient
1513 for d in self.dependencies:
1514 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001515 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001516 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001517 for d in dep.dependencies:
1518 entries[d.name] = GetURLAndRev(d)
1519 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001520 GrabDeps(d)
1521 custom_deps = []
1522 for k in sorted(entries.keys()):
1523 if entries[k]:
1524 # Quotes aren't escaped...
1525 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1526 else:
1527 custom_deps.append(' \"%s\": None,\n' % k)
1528 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1529 'solution_name': d.name,
1530 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001531 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001532 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001533 'solution_deps': ''.join(custom_deps),
1534 }
1535 # Print the snapshot configuration file
1536 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001537 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001538 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001539 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001540 if self._options.actual:
1541 entries[d.name] = GetURLAndRev(d)
1542 else:
1543 entries[d.name] = d.parsed_url
1544 keys = sorted(entries.keys())
1545 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001546 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001547 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001548
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001549 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001550 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001551 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001552
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001553 def PrintLocationAndContents(self):
1554 # Print out the .gclient file. This is longer than if we just printed the
1555 # client dict, but more legible, and it might contain helpful comments.
1556 print('Loaded .gclient config in %s:\n%s' % (
1557 self.root_dir, self.config_content))
1558
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001559 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001560 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001561 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001562 return self._root_dir
1563
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001564 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001565 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001566 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001567 return self._enforced_os
1568
maruel@chromium.org68988972011-09-20 14:11:42 +00001569 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001570 def recursion_limit(self):
1571 """How recursive can each dependencies in DEPS file can load DEPS file."""
1572 return self._recursion_limit
1573
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001574 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001575 def try_recursedeps(self):
1576 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001577 return True
1578
1579 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001580 def target_os(self):
1581 return self._enforced_os
1582
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001583
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001584#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001585
1586
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001587@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001588def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001589 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001590
1591 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001592 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001593 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001594 """
1595 # Stop parsing at the first non-arg so that these go through to the command
1596 parser.disable_interspersed_args()
1597 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001598 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001599 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001600 help='Ignore non-zero return codes from subcommands.')
1601 parser.add_option('--prepend-dir', action='store_true',
1602 help='Prepend relative dir for use with git <cmd> --null.')
1603 parser.add_option('--no-progress', action='store_true',
1604 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001605 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001606 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001607 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001608 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001609 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1610 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001611 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001612 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001613 'This is because .gclient_entries needs to exist and be up to date.',
1614 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001615 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001616
1617 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001618 scm_set = set()
1619 for scm in options.scm:
1620 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001621 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001622
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001623 options.nohooks = True
1624 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001625 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1626 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001627
1628
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001629@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001630def CMDfetch(parser, args):
1631 """Fetches upstream commits for all modules.
1632
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001633 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1634 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001635 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001636 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001637 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1638
1639
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001640def CMDflatten(parser, args):
1641 """Flattens the solutions into a single DEPS file."""
1642 parser.add_option('--output-deps', help='Path to the output DEPS file')
1643 parser.add_option(
1644 '--require-pinned-revisions', action='store_true',
1645 help='Fail if any of the dependencies uses unpinned revision.')
1646 options, args = parser.parse_args(args)
1647
1648 options.nohooks = True
1649 client = GClient.LoadCurrentConfig(options)
1650
1651 # Only print progress if we're writing to a file. Otherwise, progress updates
1652 # could obscure intended output.
1653 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1654 if code != 0:
1655 return code
1656
1657 deps = {}
1658 hooks = []
1659 pre_deps_hooks = []
1660 unpinned_deps = {}
1661
1662 for solution in client.dependencies:
1663 _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1664
1665 if options.require_pinned_revisions and unpinned_deps:
1666 sys.stderr.write('The following dependencies are not pinned:\n')
1667 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1668 return 1
1669
1670 flattened_deps = '\n'.join(
1671 _DepsToLines(deps) +
1672 _HooksToLines('hooks', hooks) +
1673 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1674 [''] # Ensure newline at end of file.
1675 )
1676
1677 if options.output_deps:
1678 with open(options.output_deps, 'w') as f:
1679 f.write(flattened_deps)
1680 else:
1681 print(flattened_deps)
1682
1683 return 0
1684
1685
1686def _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps):
1687 """Visits a solution in order to flatten it (see CMDflatten).
1688
1689 Arguments:
1690 solution (Dependency): one of top-level solutions in .gclient
1691
1692 Out-parameters:
1693 deps (dict of name -> Dependency): will be filled with all Dependency
1694 objects indexed by their name
1695 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1696 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1697 pre_deps_hooks
1698 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1699 deps
1700 """
1701 logging.debug('_FlattenSolution(%r)', solution)
1702
1703 _FlattenDep(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1704 _FlattenRecurse(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1705
1706
1707def _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1708 """Visits a dependency in order to flatten it (see CMDflatten).
1709
1710 Arguments:
1711 dep (Dependency): dependency to process
1712
1713 Out-parameters:
1714 deps (dict): will be filled with flattened deps
1715 hooks (list): will be filled with flattened hooks
1716 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1717 unpinned_deps (dict): will be filled with unpinned deps
1718 """
1719 logging.debug('_FlattenDep(%r)', dep)
1720
1721 _AddDep(dep, deps, unpinned_deps)
1722
1723 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1724 for recurse_dep_name in (dep.recursedeps or []):
1725 _FlattenRecurse(
1726 deps_by_name[recurse_dep_name], deps, hooks, pre_deps_hooks,
1727 unpinned_deps)
1728
1729 # TODO(phajdan.jr): also handle hooks_os.
1730 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1731 pre_deps_hooks.extend(
1732 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1733
1734
1735def _FlattenRecurse(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1736 """Helper for flatten that recurses into |dep|'s dependencies.
1737
1738 Arguments:
1739 dep (Dependency): dependency to process
1740
1741 Out-parameters:
1742 deps (dict): will be filled with flattened deps
1743 hooks (list): will be filled with flattened hooks
1744 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1745 unpinned_deps (dict): will be filled with unpinned deps
1746 """
1747 logging.debug('_FlattenRecurse(%r)', dep)
1748
1749 # TODO(phajdan.jr): also handle deps_os.
1750 for dep in dep.dependencies:
1751 _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps)
1752
1753
1754def _AddDep(dep, deps, unpinned_deps):
1755 """Helper to add a dependency to flattened lists.
1756
1757 Arguments:
1758 dep (Dependency): dependency to process
1759
1760 Out-parameters:
1761 deps (dict): will be filled with flattened deps
1762 unpinned_deps (dict): will be filled with unpinned deps
1763 """
1764 logging.debug('_AddDep(%r)', dep)
1765
1766 assert dep.name not in deps
1767 deps[dep.name] = dep
1768
1769 # Detect unpinned deps.
1770 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1771 if not revision or not gclient_utils.IsGitSha(revision):
1772 unpinned_deps[dep.name] = dep
1773
1774
1775def _DepsToLines(deps):
1776 """Converts |deps| dict to list of lines for output."""
1777 s = ['deps = {']
1778 for name, dep in sorted(deps.iteritems()):
1779 s.extend([
1780 ' # %s' % dep.hierarchy(include_url=False),
1781 ' "%s": "%s",' % (name, dep.url),
1782 '',
1783 ])
1784 s.extend(['}', ''])
1785 return s
1786
1787
1788def _HooksToLines(name, hooks):
1789 """Converts |hooks| list to list of lines for output."""
1790 s = ['%s = [' % name]
1791 for dep, hook in hooks:
1792 s.extend([
1793 ' # %s' % dep.hierarchy(include_url=False),
1794 ' {',
1795 ])
1796 if 'name' in hook:
1797 s.append(' "name": "%s",' % hook['name'])
1798 if 'pattern' in hook:
1799 s.append(' "pattern": "%s",' % hook['pattern'])
1800 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1801 # i.e. they may be relative to the dependency path, not solution root.
1802 s.extend(
1803 [' "action": ['] +
1804 [' "%s",' % arg for arg in hook['action']] +
1805 [' ]', ' },', '']
1806 )
1807 s.extend([']', ''])
1808 return s
1809
1810
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001811def CMDgrep(parser, args):
1812 """Greps through git repos managed by gclient.
1813
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001814 Runs 'git grep [args...]' for each module.
1815 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001816 # We can't use optparse because it will try to parse arguments sent
1817 # to git grep and throw an error. :-(
1818 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001819 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001820 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1821 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1822 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1823 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001824 ' end of your query.',
1825 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001826 return 1
1827
1828 jobs_arg = ['--jobs=1']
1829 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1830 jobs_arg, args = args[:1], args[1:]
1831 elif re.match(r'(-j|--jobs)$', args[0]):
1832 jobs_arg, args = args[:2], args[2:]
1833
1834 return CMDrecurse(
1835 parser,
1836 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1837 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001838
1839
stip@chromium.orga735da22015-04-29 23:18:20 +00001840def CMDroot(parser, args):
1841 """Outputs the solution root (or current dir if there isn't one)."""
1842 (options, args) = parser.parse_args(args)
1843 client = GClient.LoadCurrentConfig(options)
1844 if client:
1845 print(os.path.abspath(client.root_dir))
1846 else:
1847 print(os.path.abspath('.'))
1848
1849
agablea98a6cd2016-11-15 14:30:10 -08001850@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001851def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001852 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001853
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001854 This specifies the configuration for further commands. After update/sync,
1855 top-level DEPS files in each module are read to determine dependent
1856 modules to operate on as well. If optional [url] parameter is
1857 provided, then configuration is read from a specified Subversion server
1858 URL.
1859 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001860 # We do a little dance with the --gclientfile option. 'gclient config' is the
1861 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1862 # arguments. So, we temporarily stash any --gclientfile parameter into
1863 # options.output_config_file until after the (gclientfile xor spec) error
1864 # check.
1865 parser.remove_option('--gclientfile')
1866 parser.add_option('--gclientfile', dest='output_config_file',
1867 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001868 parser.add_option('--name',
1869 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001870 parser.add_option('--deps-file', default='DEPS',
1871 help='overrides the default name for the DEPS file for the'
1872 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001873 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001874 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001875 'to have the main solution untouched by gclient '
1876 '(gclient will check out unmanaged dependencies but '
1877 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001878 parser.add_option('--cache-dir',
1879 help='(git only) Cache all git repos into this dir and do '
1880 'shared clones from the cache, instead of cloning '
1881 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001882 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001883 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001884 if options.output_config_file:
1885 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001886 if ((options.spec and args) or len(args) > 2 or
1887 (not options.spec and not args)):
1888 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1889
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001890 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001891 if options.spec:
1892 client.SetConfig(options.spec)
1893 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001894 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001895 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001896 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001897 if name.endswith('.git'):
1898 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001899 else:
1900 # specify an alternate relpath for the given URL.
1901 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001902 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1903 os.getcwd()):
1904 parser.error('Do not pass a relative path for --name.')
1905 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1906 parser.error('Do not include relative path components in --name.')
1907
nsylvain@google.comefc80932011-05-31 21:27:56 +00001908 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001909 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001910 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001911 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001912 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001913 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001914
1915
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001916@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001917 gclient pack > patch.txt
1918 generate simple patch for configured client and dependences
1919""")
1920def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001921 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001922
agabled437d762016-10-17 09:35:11 -07001923 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001924 dependencies, and performs minimal postprocessing of the output. The
1925 resulting patch is printed to stdout and can be applied to a freshly
1926 checked out tree via 'patch -p0 < patchfile'.
1927 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001928 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1929 help='override deps for the specified (comma-separated) '
1930 'platform(s); \'all\' will process all deps_os '
1931 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001932 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001933 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001934 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001935 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001936 client = GClient.LoadCurrentConfig(options)
1937 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001938 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001939 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001940 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001941 return client.RunOnDeps('pack', args)
1942
1943
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001944def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001945 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001946 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1947 help='override deps for the specified (comma-separated) '
1948 'platform(s); \'all\' will process all deps_os '
1949 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001950 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001951 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001952 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001953 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001954 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001955 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001956 return client.RunOnDeps('status', args)
1957
1958
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001959@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001960 gclient sync
1961 update files from SCM according to current configuration,
1962 *for modules which have changed since last update or sync*
1963 gclient sync --force
1964 update files from SCM according to current configuration, for
1965 all modules (useful for recovering files deleted from local copy)
1966 gclient sync --revision src@31000
1967 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001968
1969JSON output format:
1970If the --output-json option is specified, the following document structure will
1971be emitted to the provided file. 'null' entries may occur for subprojects which
1972are present in the gclient solution, but were not processed (due to custom_deps,
1973os_deps, etc.)
1974
1975{
1976 "solutions" : {
1977 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001978 "revision": [<git id hex string>|null],
1979 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001980 }
1981 }
1982}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001983""")
1984def CMDsync(parser, args):
1985 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001986 parser.add_option('-f', '--force', action='store_true',
1987 help='force update even for unchanged modules')
1988 parser.add_option('-n', '--nohooks', action='store_true',
1989 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001990 parser.add_option('-p', '--noprehooks', action='store_true',
1991 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001992 parser.add_option('-r', '--revision', action='append',
1993 dest='revisions', metavar='REV', default=[],
1994 help='Enforces revision/hash for the solutions with the '
1995 'format src@rev. The src@ part is optional and can be '
1996 'skipped. -r can be used multiple times when .gclient '
1997 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001998 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001999 parser.add_option('--with_branch_heads', action='store_true',
2000 help='Clone git "branch_heads" refspecs in addition to '
2001 'the default refspecs. This adds about 1/2GB to a '
2002 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002003 parser.add_option('--with_tags', action='store_true',
2004 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002005 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002006 help='DEPRECATED: only made sense with safesync urls.')
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')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002028 parser.add_option('--upstream', action='store_true',
2029 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002030 parser.add_option('--output-json',
2031 help='Output a json document to this path containing '
2032 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002033 parser.add_option('--no-history', action='store_true',
2034 help='GIT ONLY - Reduces the size/time of the checkout at '
2035 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002036 parser.add_option('--shallow', action='store_true',
2037 help='GIT ONLY - Do a shallow clone into the cache dir. '
2038 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002039 parser.add_option('--no_bootstrap', '--no-bootstrap',
2040 action='store_true',
2041 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002042 parser.add_option('--ignore_locks', action='store_true',
2043 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002044 parser.add_option('--break_repo_locks', action='store_true',
2045 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2046 'index.lock). This should only be used if you know for '
2047 'certain that this invocation of gclient is the only '
2048 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002049 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002050 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002051 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002052 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2053 parser.add_option('-t', '--transitive', action='store_true',
2054 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002055 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002056 help='DEPRECATED: This is a no-op.')
Andrii Shyshkalov58abadb2017-05-23 08:49:05 +00002057 parser.add_option('--validate-syntax', action='store_true',
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002058 help='Validate the .gclient and DEPS syntax')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002059 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002060 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002061
2062 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002063 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002064
smutae7ea312016-07-18 11:59:41 -07002065 if options.revisions and options.head:
2066 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2067 print('Warning: you cannot use both --head and --revision')
2068
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002069 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002070 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002071 ret = client.RunOnDeps('update', args)
2072 if options.output_json:
2073 slns = {}
2074 for d in client.subtree(True):
2075 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2076 slns[normed] = {
2077 'revision': d.got_revision,
2078 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002079 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002080 }
2081 with open(options.output_json, 'wb') as f:
2082 json.dump({'solutions': slns}, f)
2083 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002084
2085
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002086CMDupdate = CMDsync
2087
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002088
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002089def CMDvalidate(parser, args):
2090 """Validates the .gclient and DEPS syntax."""
2091 options, args = parser.parse_args(args)
2092 options.validate_syntax = True
2093 client = GClient.LoadCurrentConfig(options)
2094 rv = client.RunOnDeps('validate', args)
2095 if rv == 0:
2096 print('validate: SUCCESS')
2097 else:
2098 print('validate: FAILURE')
2099 return rv
2100
2101
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002102def CMDdiff(parser, args):
2103 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002104 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2105 help='override deps for the specified (comma-separated) '
2106 'platform(s); \'all\' will process all deps_os '
2107 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002108 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002109 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002110 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002111 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002112 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002113 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002114 return client.RunOnDeps('diff', args)
2115
2116
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002117def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002118 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002119
2120 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002121 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002122 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2123 help='override deps for the specified (comma-separated) '
2124 'platform(s); \'all\' will process all deps_os '
2125 'references')
2126 parser.add_option('-n', '--nohooks', action='store_true',
2127 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002128 parser.add_option('-p', '--noprehooks', action='store_true',
2129 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002130 parser.add_option('--upstream', action='store_true',
2131 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002132 parser.add_option('--break_repo_locks', action='store_true',
2133 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2134 'index.lock). This should only be used if you know for '
2135 'certain that this invocation of gclient is the only '
2136 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002137 (options, args) = parser.parse_args(args)
2138 # --force is implied.
2139 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002140 options.reset = False
2141 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002142 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002143 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002144 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002145 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002146 return client.RunOnDeps('revert', args)
2147
2148
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002149def CMDrunhooks(parser, args):
2150 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002151 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2152 help='override deps for the specified (comma-separated) '
2153 'platform(s); \'all\' will process all deps_os '
2154 'references')
2155 parser.add_option('-f', '--force', action='store_true', default=True,
2156 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002157 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002158 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002159 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002160 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002161 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002162 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002163 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002164 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002165 return client.RunOnDeps('runhooks', args)
2166
2167
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002168def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002169 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002170
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002171 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002172 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002173 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2174 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002175 """
2176 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2177 help='override deps for the specified (comma-separated) '
2178 'platform(s); \'all\' will process all deps_os '
2179 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002180 parser.add_option('-a', '--actual', action='store_true',
2181 help='gets the actual checked out revisions instead of the '
2182 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002183 parser.add_option('-s', '--snapshot', action='store_true',
2184 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002185 'version of all repositories to reproduce the tree, '
2186 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002187 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002188 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002189 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002190 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002191 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002192 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002193
2194
szager@google.comb9a78d32012-03-13 18:46:21 +00002195def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002196 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002197 (options, args) = parser.parse_args(args)
2198 options.force = True
2199 client = GClient.LoadCurrentConfig(options)
2200 if not client:
2201 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2202 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002203 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002204 return 0
2205
2206
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002207def CMDverify(parser, args):
2208 """Verifies the DEPS file deps are only from allowed_hosts."""
2209 (options, args) = parser.parse_args(args)
2210 client = GClient.LoadCurrentConfig(options)
2211 if not client:
2212 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2213 client.RunOnDeps(None, [])
2214 # Look at each first-level dependency of this gclient only.
2215 for dep in client.dependencies:
2216 bad_deps = dep.findDepsFromNotAllowedHosts()
2217 if not bad_deps:
2218 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002219 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002220 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002221 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2222 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002223 sys.stdout.flush()
2224 raise gclient_utils.Error(
2225 'dependencies from disallowed hosts; check your DEPS file.')
2226 return 0
2227
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002228class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002229 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002230
2231 def __init__(self, **kwargs):
2232 optparse.OptionParser.__init__(
2233 self, version='%prog ' + __version__, **kwargs)
2234
2235 # Some arm boards have issues with parallel sync.
2236 if platform.machine().startswith('arm'):
2237 jobs = 1
2238 else:
2239 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002240
2241 self.add_option(
2242 '-j', '--jobs', default=jobs, type='int',
2243 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002244 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002245 self.add_option(
2246 '-v', '--verbose', action='count', default=0,
2247 help='Produces additional output for diagnostics. Can be used up to '
2248 'three times for more logging info.')
2249 self.add_option(
2250 '--gclientfile', dest='config_filename',
2251 help='Specify an alternate %s file' % self.gclientfile_default)
2252 self.add_option(
2253 '--spec',
2254 help='create a gclient file containing the provided string. Due to '
2255 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2256 self.add_option(
2257 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002258 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002259
2260 def parse_args(self, args=None, values=None):
2261 """Integrates standard options processing."""
2262 options, args = optparse.OptionParser.parse_args(self, args, values)
2263 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2264 logging.basicConfig(
2265 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002266 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002267 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002268 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002269 if (options.config_filename and
2270 options.config_filename != os.path.basename(options.config_filename)):
2271 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002272 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002273 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002274 options.entries_filename = options.config_filename + '_entries'
2275 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002276 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002277
2278 # These hacks need to die.
2279 if not hasattr(options, 'revisions'):
2280 # GClient.RunOnDeps expects it even if not applicable.
2281 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002282 if not hasattr(options, 'head'):
2283 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002284 if not hasattr(options, 'nohooks'):
2285 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002286 if not hasattr(options, 'noprehooks'):
2287 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002288 if not hasattr(options, 'deps_os'):
2289 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002290 if not hasattr(options, 'force'):
2291 options.force = None
2292 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002293
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002294
2295def disable_buffering():
2296 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2297 # operations. Python as a strong tendency to buffer sys.stdout.
2298 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2299 # Make stdout annotated with the thread ids.
2300 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002301
2302
sbc@chromium.org013731e2015-02-26 18:28:43 +00002303def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002304 """Doesn't parse the arguments here, just find the right subcommand to
2305 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002306 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002307 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002308 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002309 sys.version.split(' ', 1)[0],
2310 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002311 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002312 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002313 print(
2314 '\nPython cannot find the location of it\'s own executable.\n',
2315 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002316 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002317 fix_encoding.fix_encoding()
2318 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002319 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002320 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002321 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002322 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002323 except KeyboardInterrupt:
2324 gclient_utils.GClientChildren.KillAllRemainingChildren()
2325 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002326 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002327 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002328 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002329 finally:
2330 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002331 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002332
2333
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002334if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002335 try:
2336 sys.exit(main(sys.argv[1:]))
2337 except KeyboardInterrupt:
2338 sys.stderr.write('interrupted\n')
2339 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002340
2341# vim: ts=2:sw=2:tw=80:et: